user1189332
user1189332

Reputation: 1941

Dynamic SQL for Reporting App

Just about to start playing with jOOQ for a proof of concept. jOOQ looks really simple, expressive and makes SQL maintenance a lot more easier.

We're a Java 8 shop. The usecase here is to write the data layer for a reporting app that dynamically queries tables, columns, filters and functions based on the user selection on the screen.

Although I really like the idea of writing type-safe queries (using the jOOQ codegen), I suppose for my usecase, that won't be a best fit. Because tables, columns etc etc are completely unknown, I suppose I just need the jOOQ SQL builder. That means I have to give up type safety. Is my assessment correct? Or are there any patterns I could use for building "dynamic" SQLs without compromising the type safety? Any pointers would be much appreciated.

Upvotes: 2

Views: 243

Answers (1)

Lukas Eder
Lukas Eder

Reputation: 220987

You don't have to use jOOQ's code generator to take advantage of most features in jOOQ. The manual's introduction section states that jOOQ can be easily used as a SQL builder without the extra type static safety provided by the code generator:

https://www.jooq.org/doc/latest/manual/getting-started/use-cases/jooq-as-a-standalone-sql-builder

The type safety provided by code generation

The code generator essentially provides two type safety elements:

  • Names of objects are hard-wired into class names (tables, schemas, sequences, data types, procedures, etc.) and attribute names (columns, type attributes, procedure parameters).
  • Types of attributes (columns, attributes, parameters) are hard-wired into the generic attribute definitions.

These things certainly helps develop your application

The type safety provided by the jOOQ API

... but beware that the code generator simply reverse engineers a static snapshot of your schema. It is type safe because the entire jOOQ API allows for this kind of type safety. For instance, a Field<T> type has a generic type <T>, which can be used without the code generator as well, e.g. by using the plain SQL APIs:

Field<String> firstName = field(name("USER", "FIRST_NAME"), SQLDataType.VARCHAR(50));

The above API usage (DSL.field(Name, DataType)) does roughly the same as what the code generator would do anyway. It creates a column reference with column type information attached to it. You can use it like the columns generated by the code generator:

DSL.using(configuration)
   .select(firstName)
   .from(name("USER"))
   .where(firstName.like("A%")) // Compiles
   .and(firstName.eq(1))        // Doesn't compile: firstName must be compared to String
   .join(name("ADDRESS"))       // Doesn't compile: the SQL syntax is wrong
   .fetch();

As you can see, the only thing that changed compared to using the code generator is the table / column references.

Dynamic SQL

But this means, that jOOQ is even more powerful for you without the code generator. You can still create dynamic SQL statements very easily. For instance:

// Construct your SQL query elements dynamically, and type safely
Condition condition = hasFirstNameFilter()
    ? firstName.like("A%")
    : DSL.trueCondition();

DSL.using(configuration)
   .select(firstName)
   .from(name("USER"))
   .where(condition)     // Use dynamically constructed element here
   .fetch();

You could also do this in a "functional way":

DSL.using(configuration)
   .select(firstName)
   .from(name("USER"))
   .where(condition())   // Call a function to create the condition here
   .fetch();

Or even better

public static Select<Record1<String>> firstNames(
    Function<? super Field<String>, ? extends Condition> condition
) {
    return
    DSL.using(configuration)
       .select(firstName)
       .from(name("USER"))
       .where(condition.apply(firstName)); // Lazy evaluate the predicate here
}

// Use it like this:
firstNames(col -> col.like("A%")).fetch();

Or even better, make the above a higher order function:

public static Function<
    ? super Function<? super Field<String>, ? extends Condition>, 
    ? extends Select<Record1<String>>
> firstNames() {
    // Lazy construct query here
    return f -> DSL.using(configuration)
                   .select(firstName)
                   .from(name("USER"))
                   .where(f.apply(firstName)); // Lazy evaluate the predicate here
}

// Use it like this:
firstNames().apply(col -> col.like("A%")).fetch();

More details here: https://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql

Conclusion:

As you can see, while the code generator does add a lot of value for static schemas, there's nothing really static in the jOOQ API. jOOQ is an API for dynamic SQL query construction, that just happens to work well for static queries as well.

Upvotes: 2

Related Questions