Available in versions: Dev (3.20) | Latest (3.19) | 3.18 | 3.17 | 3.16

This is experimental functionality, and as such subject to change. Use at your own risk!

Traversal

Applies to ❌ Open Source Edition   ✅ Express Edition   ✅ Professional Edition   ✅ Enterprise Edition

While the accessor methods from the model API allow for traversing the expression tree manually, a more generic way to traverse the expression tree is using the org.jooq.Traverser API, which can traverse a org.jooq.QueryPart in a similar fashion as a java.util.stream.Collector can iterate a java.util.stream.Stream, collecting and aggregating data about the expression tree. A Traverser consists of this API:

public interface Traverser<A, R> {

    /**
     * A supplier for a temporary data structure to accumulate {@link QueryPart}
     * objects into during traversal.
     */
    Supplier<A> supplier();

    /**
     * An optional traversal abort condition to short circuit traversal e.g.
     * when the searched object has been found.
     */
    Predicate<A> abort();

    /**
     * An optional recursion condition to prevent entering a specific
     * {@link QueryPart}, e.g. when it is undesired to enter any subqueries.
     */
    Predicate<QueryPart> recurse();

    /**
     * An optional recursion condition to prevent entering a specific
     * {@link QueryPart}'s children, e.g. when it is desired to traverse only
     * into certain operators.
     */
    Predicate<QueryPart> recurseChildren();

    /**
     * A callback that is invoked before recursing into a subtree.
     */
    BiFunction<A, QueryPart, A> before();

    /**
     * A callback that is invoked after recursing into a subtree.
     */
    BiFunction<A, QueryPart, A> after();

    /**
     * An optional transformation function to turn the temporary data structure
     * supplied by {@link #supplier()} into the final data structure.
     */
    Function<A, R> finisher();
}

Some elements are similar to that of a java.util.stream.Collector, others are specific to tree traversal.

A simple illustration shows what can be done:

// Any ordinary QueryPart:
Condition condition = BOOK.ID.eq(1);
int count = condition.$traverse(

    // Supplier of the initial data structure: an int
    () -> 0,

    // Print all traversed QueryParts and increment the counter
    (r, q) -> {
        System.out.println("Part " + r + ": " + q);
        return r + 1;
    }
);
System.out.println("Count : " + count);

The above will print:

Part 0: "BOOK"."ID" = 1
Part 1: "BOOK"."ID"
Part 2: 1
Count : 3

Using the same traverser on a slightly more complex QueryPart

Condition condition = BOOK.ID.eq(1).or(BOOK.ID.eq(2));
// ...
Part 0: ("BOOK"."ID" = 1 or "BOOK"."ID" = 2)
Part 1: "BOOK"."ID" = 1
Part 2: "BOOK"."ID"
Part 3: 1
Part 4: "BOOK"."ID" = 2
Part 5: "BOOK"."ID"
Part 6: 2
Count : 7

Re-using your JDK collectors

Any Collector can be turned into a Traverser using Traversers.collecting(Collector). For example, if you want to count all QueryPart items in an expression, instead of the above hand-written traverser, just use the JDK Collectors.counting():

// Contains 3 query parts
long count1 = BOOK.ID.eq(1)
    .$traverse(Traversers.collecting(Collectors.counting());

// Contains 7 query parts
long count2 = BOOK.ID.eq(1).or(BOOK.ID.eq(2))
    .$traverse(Traversers.collecting(Collectors.counting());

Limitations

Just like model API replacement, traversers cannot traverse into "opaque" org.jooq.QueryPart instances, including custom QueryParts or plain SQL templates. See also features requiring code generation for more details.

Feedback

Do you have any feedback about this page? We'd love to hear it!

The jOOQ Logo