From 2f60f4728207e27a428da746e58d9dc1e354eebe Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 7 Feb 2025 07:57:18 +0100 Subject: [PATCH] [jOOQ/jOOQ#14402] Add support for Databricks SQL - WIP This includes: - Support backslash escaping in string literals - Support inline COMMENT syntax for CREATE TABLE - Support DISTINCT ON - Support GENERATE_SERIES - Support CTAS and SELECT .. INTO - Support CREATE MATERIALIZED VIEW with column renames - Support H2 style MERGE - Avoid exists(selectOne()), as selectOne() aliases its column and Databricks has a bug here - TIMESTAMP datatype (ignore precision) - Support LIMIT (subquery) - Use TIME -> TIMESTAMP emulation - Support INSERT .. DEFAULT VALUES - Support DELETE .. USING - Fix CHOOSE (index can't be out of bounds, unlike in MySQL) --- .../java/org/jooq/SelectDistinctOnStep.java | 1 + jOOQ/src/main/java/org/jooq/SelectQuery.java | 2 +- .../java/org/jooq/impl/ArrayAllMatch.java | 2 +- .../java/org/jooq/impl/ArrayAnyMatch.java | 2 +- .../java/org/jooq/impl/ArrayNoneMatch.java | 2 +- jOOQ/src/main/java/org/jooq/impl/Choose.java | 7 +++++++ .../java/org/jooq/impl/CreateTableImpl.java | 21 +++++++++++++++++-- .../main/java/org/jooq/impl/CurrentTime.java | 1 + .../java/org/jooq/impl/DefaultBinding.java | 15 ++++++++++++- .../java/org/jooq/impl/DefaultDSLContext.java | 2 +- .../src/main/java/org/jooq/impl/DivideBy.java | 6 ++++-- .../java/org/jooq/impl/GenerateSeries.java | 14 ++++++++++++- .../java/org/jooq/impl/InsertQueryImpl.java | 6 ++++-- .../main/java/org/jooq/impl/JoinTable.java | 6 ++++-- jOOQ/src/main/java/org/jooq/impl/Names.java | 2 ++ .../java/org/jooq/impl/SelectQueryImpl.java | 11 +++++++++- 16 files changed, 84 insertions(+), 16 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/SelectDistinctOnStep.java b/jOOQ/src/main/java/org/jooq/SelectDistinctOnStep.java index d9db5816db..a0c6ec8e78 100644 --- a/jOOQ/src/main/java/org/jooq/SelectDistinctOnStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectDistinctOnStep.java @@ -42,6 +42,7 @@ import static org.jooq.SQLDialect.CLICKHOUSE; // ... import static org.jooq.SQLDialect.CUBRID; // ... +// ... import static org.jooq.SQLDialect.DUCKDB; // ... import static org.jooq.SQLDialect.FIREBIRD; diff --git a/jOOQ/src/main/java/org/jooq/SelectQuery.java b/jOOQ/src/main/java/org/jooq/SelectQuery.java index bb81fe961f..f8baf6c3b3 100644 --- a/jOOQ/src/main/java/org/jooq/SelectQuery.java +++ b/jOOQ/src/main/java/org/jooq/SelectQuery.java @@ -146,7 +146,7 @@ public interface SelectQuery extends Select, ConditionProvi * Add a T-SQL style INTO clause to the SELECT * statement to create a new table from a SELECT statement. */ - @Support({ CUBRID, DERBY, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE, YUGABYTEDB }) + @Support({ CUBRID, DERBY, DUCKDB, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE, YUGABYTEDB }) void setInto(Table table); diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayAllMatch.java b/jOOQ/src/main/java/org/jooq/impl/ArrayAllMatch.java index 561553d9c0..433648787e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayAllMatch.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayAllMatch.java @@ -138,7 +138,7 @@ implements case POSTGRES: case YUGABYTEDB: ctx.visit(ifNotNull(array, notExists( - selectOne() + select(one()) .from(unnest(array).as(N_T, predicate.$arg1().getUnqualifiedName())) .where(DSL.not(predicate.$result())) ))); diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayAnyMatch.java b/jOOQ/src/main/java/org/jooq/impl/ArrayAnyMatch.java index dc21b50513..79816bb422 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayAnyMatch.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayAnyMatch.java @@ -138,7 +138,7 @@ implements case POSTGRES: case YUGABYTEDB: ctx.visit(ifNotNull(array, exists( - selectOne() + select(one()) .from(unnest(array).as(N_T, predicate.$arg1().getUnqualifiedName())) .where(predicate.$result()) ))); diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayNoneMatch.java b/jOOQ/src/main/java/org/jooq/impl/ArrayNoneMatch.java index 1e0c2d73f4..93a33d3094 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayNoneMatch.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayNoneMatch.java @@ -137,7 +137,7 @@ implements case POSTGRES: case YUGABYTEDB: ctx.visit(ifNotNull(array, notExists( - selectOne() + select(one()) .from(unnest(array).as(N_T, predicate.$arg1().getUnqualifiedName())) .where(predicate.$result()) ))); diff --git a/jOOQ/src/main/java/org/jooq/impl/Choose.java b/jOOQ/src/main/java/org/jooq/impl/Choose.java index 0a56481c11..8f7d2fd5ac 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Choose.java +++ b/jOOQ/src/main/java/org/jooq/impl/Choose.java @@ -40,6 +40,7 @@ package org.jooq.impl; import static org.jooq.impl.DSL.choose; import static org.jooq.impl.DSL.function; import static org.jooq.impl.DSL.inline; +import static org.jooq.impl.DSL.when; import static org.jooq.impl.Names.*; import static org.jooq.impl.Tools.EMPTY_FIELD; import static org.jooq.impl.Tools.nullSafeDataType; @@ -123,6 +124,12 @@ final class Choose extends AbstractField implements QOM.Choose { + + + + + + case MARIADB: case MYSQL: { ctx.visit(function(N_ELT, getDataType(), Tools.combine(index, values))); diff --git a/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java b/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java index a10631251c..5dbcd2acef 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java @@ -368,6 +368,8 @@ implements + + final QOM.UnmodifiableList> $columns() { return QOM.unmodifiable(map(filter(tableElements, e -> e instanceof Field), e -> (Field) e)); } @@ -461,8 +463,13 @@ implements else { toSQLCreateTable(ctx); toSQLOnCommit(ctx); + toSQLTableClauses(ctx); } + ctx.end(Clause.CREATE_TABLE); + } + + private final void toSQLTableClauses(Context ctx) { // [#7539] ClickHouse has a mandatory ENGINE clause. We default to the two most popular engines for now. if (ctx.family() == CLICKHOUSE) { ctx.formatSeparator().visit(K_ENGINE).sql(' '); @@ -505,8 +512,6 @@ implements - - ctx.end(Clause.CREATE_TABLE); } private void toSQLCreateTable(Context ctx) { @@ -669,6 +674,12 @@ implements private final void acceptCreateTableAsSelect(Context ctx) { toSQLCreateTable(ctx); toSQLOnCommit(ctx); + + + + + + ctx.formatSeparator() .visit(K_AS); @@ -705,6 +716,11 @@ implements else if (REQUIRES_WITH_DATA.contains(ctx.dialect())) ctx.formatSeparator() .visit(K_WITH_DATA); + + + + + toSQLTableClauses(ctx); } @@ -739,6 +755,7 @@ implements + private final void toSQLCreateTableName(Context ctx) { diff --git a/jOOQ/src/main/java/org/jooq/impl/CurrentTime.java b/jOOQ/src/main/java/org/jooq/impl/CurrentTime.java index 4f1a328fec..a4c62a8b3d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CurrentTime.java +++ b/jOOQ/src/main/java/org/jooq/impl/CurrentTime.java @@ -91,6 +91,7 @@ final class CurrentTime extends AbstractField implements QOM.CurrentTime implements Binding { + default: @@ -5241,10 +5242,17 @@ public class DefaultBinding implements Binding { @Override final void set0(BindingSetStatementContext ctx, Time value) throws SQLException { switch (ctx.family()) { + + + + + + case DUCKDB: case SQLITE: ctx.statement().setString(ctx.index(), value.toString()); break; + default: ctx.statement().setTime(ctx.index(), value); break; @@ -5258,9 +5266,14 @@ public class DefaultBinding implements Binding { @Override final Time get0(BindingGetResultSetContext ctx) throws SQLException { - switch (ctx.family()) { + + + + + + // ResultSet.getTime() isn't implemented correctly, see: https://github.com/duckdb/duckdb/issues/10682 case DUCKDB: { String time = ctx.resultSet().getString(ctx.index()); diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java index a12ca3debc..11671a6342 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java @@ -5318,7 +5318,7 @@ public class DefaultDSLContext extends AbstractScope implements DSLContext, Seri @Override public boolean fetchExists(Table table, Condition condition) { - return fetchExists(selectOne().from(table).where(condition)); + return fetchExists(select(one()).from(table).where(condition)); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/DivideBy.java b/jOOQ/src/main/java/org/jooq/impl/DivideBy.java index 4e395af24e..b724337b67 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DivideBy.java +++ b/jOOQ/src/main/java/org/jooq/impl/DivideBy.java @@ -40,6 +40,8 @@ package org.jooq.impl; import static org.jooq.impl.DSL.condition; import static org.jooq.impl.DSL.exists; import static org.jooq.impl.DSL.notExists; +import static org.jooq.impl.DSL.one; +import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.selectDistinct; import static org.jooq.impl.DSL.selectOne; @@ -121,10 +123,10 @@ implements return selectDistinct(select) .from(outer) .whereNotExists( - selectOne() + select(one()) .from(divisor) .whereNotExists( - selectOne() + select(one()) .from(dividend) .where(selfJoin) .and(condition))) diff --git a/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java b/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java index 4fb006200f..2678ca803b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java +++ b/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java @@ -43,6 +43,7 @@ import static org.jooq.SQLDialect.CLICKHOUSE; import static org.jooq.SQLDialect.CUBRID; // ... // ... +// ... import static org.jooq.SQLDialect.FIREBIRD; import static org.jooq.SQLDialect.H2; import static org.jooq.SQLDialect.HSQLDB; @@ -75,13 +76,14 @@ import static org.jooq.impl.Internal.idiv; import static org.jooq.impl.Internal.imul; import static org.jooq.impl.Internal.isub; import static org.jooq.impl.Keywords.K_TABLE; +import static org.jooq.impl.Names.N_EXPLODE; import static org.jooq.impl.Names.N_GENERATE_ARRAY; import static org.jooq.impl.Names.N_GENERATE_SERIES; import static org.jooq.impl.Names.N_GENERATOR; import static org.jooq.impl.Names.N_NUMBERS; +import static org.jooq.impl.Names.N_SEQUENCE; import static org.jooq.impl.Names.N_SYSTEM_RANGE; import static org.jooq.impl.Names.N_UNNEST; -import static org.jooq.impl.SQLDataType.BIGINT; import static org.jooq.impl.SQLDataType.INTEGER; import static org.jooq.impl.SubqueryCharacteristics.DERIVED_TABLE; import static org.jooq.impl.Tools.visitSubquery; @@ -124,6 +126,8 @@ implements + + private final Field from; private final Field to; private final Field step; @@ -232,6 +236,12 @@ implements + + + + + + @@ -279,6 +289,8 @@ implements + + else return null; } diff --git a/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java index d226026753..fc84bcc954 100644 --- a/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java @@ -77,6 +77,7 @@ import static org.jooq.impl.ConditionProviderImpl.extractCondition; import static org.jooq.impl.DSL.constraint; import static org.jooq.impl.DSL.default_; import static org.jooq.impl.DSL.name; +import static org.jooq.impl.DSL.one; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.selectFrom; import static org.jooq.impl.DSL.selectOne; @@ -864,6 +865,7 @@ implements + case DERBY: case MARIADB: @@ -969,7 +971,7 @@ implements rows = (Select) selectFrom(select.asTable(DSL.table(name("t")), names)) .whereNotExists( - selectOne() + select(one()) .from(table()) .where(matchByConflictingKeys(ctx, map)) ); @@ -984,7 +986,7 @@ implements Select row = select(aliasedFields(map.entrySet().stream().filter(e -> fields.contains(e.getKey())).map(Entry::getValue).collect(toList()))) .whereNotExists( - selectOne() + select(one()) .from(table()) .where(matchByConflictingKeys(ctx, map)) ); diff --git a/jOOQ/src/main/java/org/jooq/impl/JoinTable.java b/jOOQ/src/main/java/org/jooq/impl/JoinTable.java index e58ac5008d..1032441418 100755 --- a/jOOQ/src/main/java/org/jooq/impl/JoinTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/JoinTable.java @@ -109,6 +109,8 @@ import static org.jooq.impl.DSL.exists; import static org.jooq.impl.DSL.lateral; import static org.jooq.impl.DSL.noCondition; import static org.jooq.impl.DSL.notExists; +import static org.jooq.impl.DSL.one; +import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.selectFrom; import static org.jooq.impl.DSL.selectOne; import static org.jooq.impl.Keywords.K_ANTI_JOIN; @@ -315,11 +317,11 @@ abstract class JoinTable> extends AbstractJoinTable { switch (translatedType) { case LEFT_SEMI_JOIN: - semiAntiJoinPredicates.add(exists(selectOne().from(rhs).where(condition()))); + semiAntiJoinPredicates.add(exists(select(one()).from(rhs).where(condition()))); break; case LEFT_ANTI_JOIN: - semiAntiJoinPredicates.add(notExists(selectOne().from(rhs).where(condition()))); + semiAntiJoinPredicates.add(notExists(select(one()).from(rhs).where(condition()))); break; } diff --git a/jOOQ/src/main/java/org/jooq/impl/Names.java b/jOOQ/src/main/java/org/jooq/impl/Names.java index c25ca71614..3de9cbc2bb 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Names.java +++ b/jOOQ/src/main/java/org/jooq/impl/Names.java @@ -123,6 +123,7 @@ final class Names { static final Name N_ELT = systemName("elt"); static final Name N_ENCODE = systemName("encode"); static final Name N_EVERY = systemName("every"); + static final Name N_EXPLODE = systemName("explode"); static final Name N_EXTRACT = systemName("extract"); static final Name N_FIRST_VALUE = systemName("first_value"); static final Name N_FLASHBACK = systemName("flashback"); @@ -260,6 +261,7 @@ final class Names { static final Name N_SECONDS_BETWEEN = systemName("seconds_between"); static final Name N_SEQ4 = systemName("seq4"); static final Name N_SEQ8 = systemName("seq8"); + static final Name N_SEQUENCE = systemName("sequence"); static final Name N_SHL = systemName("shl"); static final Name N_SHR = systemName("shr"); static final Name N_SQL_TSI_DAY = systemName("sql_tsi_day"); diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 1e5d3a0c31..f3b04e7a49 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -75,6 +75,7 @@ import static org.jooq.SQLDialect.CUBRID; // ... // ... // ... +// ... import static org.jooq.SQLDialect.DERBY; // ... import static org.jooq.SQLDialect.FIREBIRD; @@ -1972,6 +1973,15 @@ final class SelectQueryImpl extends AbstractResultQuery imp + + + + + + + + + @@ -2030,7 +2040,6 @@ final class SelectQueryImpl extends AbstractResultQuery imp - case CUBRID: case DUCKDB: case YUGABYTEDB: {