Sunday, June 5, 2022
HomeProgrammingVendor Agnostic, Dynamic Procedural Logic with jOOQ – Java, SQL and jOOQ.

Vendor Agnostic, Dynamic Procedural Logic with jOOQ – Java, SQL and jOOQ.


One of many strengths of contemporary RDBMS is the potential to combine the highly effective SQL language with procedural code.

SQL is a 4th era programming language (4GL), and as such, extraordinarily effectively suited to querying and bulk information manipulation. Its functional-declarative nature permits for it to be optimised in extremely environment friendly methods utilizing cost-based optimisation, but in addition statically as we’ve blogged earlier than.

Generally, nevertheless, an crucial 3GL is healthier suited to a given activity. That’s the place saved procedures shine, or extra particularly, procedural languages of RDBMS.

Amongst those that jOOQ helps, at the very least these ones help procedures:

  • BigQuery
  • Db2
  • Exasol
  • Firebird
  • HANA
  • HSQLDB
  • Informix
  • MariaDB
  • MySQL
  • Oracle
  • PostgreSQL
  • SQL Server
  • Vertica

Others could do, as effectively, however jOOQ isn’t supporting their dialects but.

Many have carried out their very own procedural languages, some in keeping with the ISO/IEC 9075-4 Persistent saved modules (SQL/PSM) normal, others have their very own.

jOOQ help for procedural logic

Since jOOQ 3.12, our business distributions have supported nameless blocks and the procedural statements they comprise, such because the IF assertion, LOOP statements, and many others. Beginning with jOOQ 3.15, we additionally help 3 sorts of statements to handle storing procedural logic within the catalog:

Utilizing these statements through jOOQ is probably not your each day use-case. You could want managing that logic through the native syntax, which remains to be extra highly effective than what jOOQ 3.15 helps (particularly while you’re utilizing Oracle’s PL/SQL), in case of which you’ll u se jOOQ purely to name your process from Java within the ordinary kind secure method.

However perhaps, you’ve gotten considered one of these use-cases?

  • You’re a product vendor, and also you revenue from procedural logic being vendor agnostic with the intention to help a number of of your shoppers’ RDBMS
  • Your procedural logic is dynamic, similar to your SQL logic (and what apart from jOOQ to make use of for that?)
  • You don’t have the required privileges to create procedures, features, or triggers in your schema

In all of these instances, jOOQ is right here that will help you.

How does it work?

The primary constructing block is the nameless block, which isn’t supported by the entire above dialects, regrettably. jOOQ can emulate it on MySQL as mentioned right here, however not presently in different dialects.

Right here’s a easy, empty nameless block:

-- Db2
BEGIN
END

-- Firebird
EXECUTE BLOCK AS
BEGIN
END

-- MariaDB
BEGIN NOT ATOMIC
END;

-- Oracle
BEGIN
  NULL;
END;

-- PostgreSQL
DO $$
BEGIN
  NULL;
END;
$$

It doesn’t actually do a lot, however you may strive executing it as follows, with jOOQ:

Now, let’s do one thing extra attention-grabbing, equivalent to:

// Assuming the standard static imports:
import static org.jooq.impl.DSL.*;
import static org.jooq.impl.SQLDataType.*;

// Then write
Variable<Integer> i = variable(unquotedName("i"), INTEGER);
Desk<?> t = desk(unquotedName("t"));
Discipline<Integer> col = subject(unquotedName("col"), INTEGER);

ctx.start(
    declare(i).set(1),

    while_(i.le(10)).loop(
        insertInto(t).columns(c).values(i),
        i.set(i.plus(1))
    )
).execute();

The above block executes:

-- Db2
BEGIN
  DECLARE i integer;
  SET i = 1;
  WHILE i <= 10 DO
    INSERT INTO t (c)
    VALUES (i);
    SET i = (i + 1);
  END WHILE;
END

-- FIREBIRD
EXECUTE BLOCK AS
  DECLARE i integer;
BEGIN
  :i = 1;
  WHILE (:i <= 10) DO BEGIN
    INSERT INTO t (c)
    VALUES (:i);
    :i = (:i + 1);
  END
END

-- MariaDB
BEGIN NOT ATOMIC
  DECLARE i int;
  SET i = 1;
  WHILE i <= 10 DO
    INSERT INTO t (c)
    VALUES (i);
    SET i = (i + 1);
  END WHILE;
END;

-- Oracle
DECLARE
  i quantity(10);
BEGIN
  i := 1;
  WHILE i <= 10 LOOP
    INSERT INTO t (c)
    VALUES (i);
    i := (i + 1);
  END LOOP;
END;

-- PostgreSQL
DO $$
DECLARE
  i int;
BEGIN
  i := 1;
  WHILE i <= 10 LOOP
    INSERT INTO t (c)
    VALUES (i);
    i := (i + 1);
  END LOOP;
END;
$$

-- SQL Server
BEGIN
  DECLARE @i int;
  SET @i = 1;
  WHILE @i <= 10 BEGIN
    INSERT INTO t (c)
    VALUES (@i);
    SET @i = (@i + 1);
  END;
END;

Straightforward as pie. Maybe you like a FOR loop, as an alternative? Do that:

ctx.start(
    for_(i).in(1, 10).loop(
        insertInto(t).columns(c).values(i)
    )
).execute();

It produces the required emulations, if required, as a result of regrettably, not all dialects help FOR:

-- Db2
BEGIN
  DECLARE i integer;
  SET i = 1;
  WHILE i <= 10 DO
    INSERT INTO t (c)
    VALUES (i);
    SET i = (i + 1);
  END WHILE;
END

-- Firebird
EXECUTE BLOCK AS
  DECLARE i integer;
BEGIN
  :i = 1;
  WHILE (:i <= 10) DO BEGIN
    INSERT INTO t (c)
    VALUES (:i);
    :i = (:i + 1);
  END
END

-- MariaDB
BEGIN NOT ATOMIC
  FOR i IN 1 .. 10 DO
    INSERT INTO t (c)
    VALUES (i);
  END FOR;
END;

-- Oracle
BEGIN
  FOR i IN 1 .. 10 LOOP
    INSERT INTO t (c)
    VALUES (i);
  END LOOP;
END;

-- PostgreSQL
DO $$
BEGIN
  FOR i IN 1 .. 10 LOOP
    INSERT INTO t (c)
    VALUES (i);
  END LOOP;
END;
$$

-- SQL Server
BEGIN
  DECLARE @i int;
  BEGIN
    SET @i = 1;
    WHILE @i <= 10 BEGIN
      INSERT INTO t (c)
      VALUES (@i);
      SET @i = (@i + 1);
    END;
  END;
END;

SQL vs procedures

In fact, this specific SQL assertion can be higher carried out utilizing a single bulk insertion assertion, purely with SQL, not with procedural logic

ctx.insertInto(t, c)
   .choose(selectFrom(generateSeries(1, 10)))
   .execute();

Which interprets to:

-- Db2
INSERT INTO t (c)
SELECT generate_series.generate_series
FROM (
  WITH
    generate_series(generate_series) AS (
      SELECT 1
      FROM SYSIBM.DUAL
      UNION ALL
      SELECT (generate_series + 1)
      FROM generate_series
      WHERE generate_series < 10
    )
  SELECT generate_series
  FROM generate_series
) generate_series;

-- Firebird
INSERT INTO t (c)
SELECT generate_series.generate_series
FROM (
  WITH RECURSIVE
    generate_series(generate_series) AS (
      SELECT 1
      FROM RDB$DATABASE
      UNION ALL
      SELECT (generate_series + 1)
      FROM generate_series
      WHERE generate_series < 10
    )
  SELECT generate_series
  FROM generate_series
) generate_series;

-- MariaDB
INSERT INTO t (c)
SELECT generate_series.generate_series
FROM (
  WITH RECURSIVE
    generate_series(generate_series) AS (
      SELECT 1
      UNION ALL
      SELECT (generate_series + 1)
      FROM generate_series
      WHERE generate_series < 10
    )
  SELECT generate_series
  FROM generate_series
) AS generate_series;

-- Oracle
INSERT INTO t (c)
SELECT generate_series.generate_series
FROM (
  SELECT (degree + (1 - 1)) generate_series
  FROM DUAL
  CONNECT BY degree <= ((10 + 1) - 1)
) generate_series;

-- PostgreSQL
INSERT INTO t (c)
SELECT generate_series.generate_series
FROM generate_series(1, 10);

-- SQL Server
WITH
  generate_series(generate_series) AS (
    SELECT 1
    UNION ALL
    SELECT (generate_series + 1)
    FROM generate_series
    WHERE generate_series < 10
  )
INSERT INTO t (c)
SELECT generate_series.generate_series
FROM (
  SELECT generate_series
  FROM generate_series
) generate_series

… however you get the purpose.

Storing the procedural logic

If in case you have the required privileges, and your procedural logic isn’t tremendous dynamic, chances are you’ll select to retailer your logic in a process or operate instantly in your database. In some databases, this implies a compiler will have the ability to eagerly translate the logic to one thing very environment friendly (e.g. machine code), as an alternative of decoding the logic on the fly.

Take the above WHILE loop, for instance. You could wish to retailer that as a process P:

Title p = unquotedName("p");

ctx.createProcedure(p)
   .modifiesSQLData()
   .as(
        declare(i).set(1),

        while_(i.le(10)).loop(
            insertInto(t).columns(c).values(i),
            i.set(i.plus(1))
        )
   )
   .execute();

This produces the next statements:

-- Db2
CREATE PROCEDURE p()
MODIFIES SQL DATA
BEGIN
  DECLARE i integer;
  SET i = 1;
  WHILE i <= 10 DO
    INSERT INTO t (c)
    VALUES (i);
    SET i = (i + 1);
  END WHILE;
END;

-- Firebird
CREATE PROCEDURE p()
AS
  DECLARE i integer;
BEGIN
  :i = 1;
  WHILE (:i <= 10) DO BEGIN
    INSERT INTO t (c)
    VALUES (:i);
    :i = (:i + 1);
  END
END

-- MariaDB
CREATE PROCEDURE p()
MODIFIES SQL DATA
BEGIN
  DECLARE i int;
  SET i = 1;
  WHILE i <= 10 DO
    INSERT INTO t (c)
    VALUES (i);
    SET i = (i + 1);
  END WHILE;
END;

-- Oracle
CREATE PROCEDURE p
AS
  i quantity(10);
BEGIN
  i := 1;
  WHILE i <= 10 LOOP
    INSERT INTO t (c)
    VALUES (i);
    i := (i + 1);
  END LOOP;
END;

-- PostgreSQL
CREATE PROCEDURE p()
LANGUAGE plpgsql
AS
$$
DECLARE
  i int;
BEGIN
  i := 1;
  WHILE i <= 10 LOOP
    INSERT INTO t (c)
    VALUES (i);
    i := (i + 1);
  END LOOP;
END;
$$

-- SQL Server
CREATE PROCEDURE p
AS
BEGIN
  DECLARE @i int;
  SET @i = 1;
  WHILE @i <= 10 BEGIN
    INSERT INTO t (c)
    VALUES (@i);
    SET @i = (@i + 1);
  END;
END;

And now, what higher solution to name this process than, once more, an nameless block?

ctx.start(name(unquotedName("p"))).execute();

Producing:

-- Db2
BEGIN
  CALL p();
END

-- Firebird
EXECUTE BLOCK AS
BEGIN
  EXECUTE PROCEDURE p;
END

-- MariaDB
BEGIN NOT ATOMIC
  CALL p();
END;

-- Oracle
BEGIN
  p();
END;

-- PostgreSQL
DO $$
BEGIN
  CALL p();
END;
$$

-- SQL Server
BEGIN
  EXEC p ;
END;

For those who’re utilizing jOOQ in Flyway or Liquibase to generate procedures throughout your database migrations, you may clearly generate jOOQ process stubs to name in a extra kind secure method, as an alternative of the above dynamic process name.

Parsing procedural logic

This jOOQ characteristic is just not actually distinctive. You possibly can mess around with our parser / translator right here: https://www.jooq.org/translate. It will probably undoubtedly enable you translate your (easier) saved procedures between dialects, equivalent to PL/SQL, T-SQL, PL/pgSQL, and many others.

Conclusion

As a rule of thumb, if you are able to do it with SQL (the 4GL), do it with SQL alone. However generally, you may’t. A 3GL is a more sensible choice for an algorithm. When utilizing jOOQ, you’ll naturally consider utilizing Java to implement that 3GL algorithm. However wait, you might transfer the logic to the server for (drastically) elevated efficiency!

Because of jOOQ, you may generate procedural logic that’s:

  • Dynamic
  • Vendor agnostic
  • Nameless or saved

Similar to you’re used to, from jOOQ, for SQL

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments