blaze-persistence icon indicating copy to clipboard operation
blaze-persistence copied to clipboard

Create a dedicated Java 8 API for conditionals

Open beikov opened this issue 9 years ago • 1 comments

Today I thought about a Java 8 API that is more elegant and less error prone for defining conditions. The API usage would roughly look like this

query.where(() -> {
    and("doc.name").eq("abc");
    or(() -> {
        or("doc.name").like("abc%");
        or("doc.done").eq(true);
    });
});

The conditions would be implemented against clause neutral interfaces and could be moved e.g. to a having clause without a change. It's less error prone because you don't have to write the "end" methods to close a scope anymore. Another benefit is that IDEs can automatically format the code in a nice way which wasn't really possible in an automatic fashion before.

Maybe in a future version when we have our custom static metamodel we can even do something like the following.

Document_ doc = query.from(Document_.class, "doc");
query.where(() -> {
    doc.NAME.eq("abc");
    or(() -> {
        doc.NAME.like("abc%");
        doc.DONE.eq(true);
    });
});

Contrary to what one might expect, the resulting query would be the following.

SELECT doc FROM Document doc WHERE 
    doc.name = 'abc' AND
    (
        doc.name LIKE 'abc%' OR
        doc.done = true
    )

This aligns with what we already have in our API. If we want a different behavior we would first need to decide what the result of the following should be.

Document_ doc = query.from(Document_.class, "doc");
query.where(() -> {
    doc.NAME.eq("abc");
    or(() -> {
        doc.NAME.like("abc%");
        // Problem 1
        doc.DONE.eq(true);
    });
    // Problem 2
    and(() -> {
        doc.DELETED.eq(false);
    });
    // Problem 3
    doc.ARCHIVED.eq(false);
});

Problem 1 is about which logic operator to apply. One possible way to go is to always use AND.

To fix 2 we could maybe disallow the use of the and method in the context of a builder for conjuncts or use the proposed solution for Problem 1. Note that complex left hand side expressions require some start method so a method is unavoidable.

Problem 3 is about whether calling and or or methods in a context changes anything about the logic operator used for subsequent predicates. We could either say we have a AND and OR context like in the existing API, or use the proposed solution for Problem 1.

Another problem is about how to handle complex left hand side expressions of predicates like subqueries, case when statements or others. I can imagine an API like the following usage example shows.

Document_ doc = query.from(Document_.class, "doc");
query.where(() -> {
    // Introduce methods for functions
    SUBSTR(doc.NAME, 1, 20).eq("abc");
    // Multiple or conditions in one group
    or(() -> {
        doc.NAME.like("abc%");
    }, () -> {
        doc.DONE.eq(true);
    });
    // Possible simplification?
    or(
        () -> doc.NAME.like("abc%"),
        () -> doc.DONE.eq(true)
    );
    // Implement complex expressions by providing appropriate toStrings
    and("(", doc.AGE, "+ 1) / 2 > ", doc.PARENT.AGE);
    // Subqueries
    subquery(() ->{
        Viewer_ v = from(Viewer_.class, "viewer");
        where(doc.memberOf(v.VIEWING_DOCUMENTS))
        return COUNT(v.ID);
    }).gt(1)
});

This would result in the following

SELECT doc FROM Document doc WHERE 
    SUBSTR(doc.name, 1, 20) = 'abc' AND
    (
        doc.name LIKE 'abc%' OR
        doc.done = true
    ) AND
    (
        doc.name LIKE 'abc%' OR
        doc.done = true
    ) AND
    (doc.age + 1) / 2 > doc.parent.age AND
    (
        SELECT COUNT(viewer.id)
        FROM Viewer viewer
        WHERE doc MEMBER OF viewer.viewingDocuments
    ) > 1

beikov avatar Apr 14 '16 14:04 beikov

As I just found out, it is not possible to use default methods of a functional interface within the lambda body because of http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27.2. One way around that would be to use a static import which shouldn't be that bad I guess.

beikov avatar Apr 14 '16 15:04 beikov