[jOOQ/jOOQ#7539] Support for SQLDialect.CLICKHOUSE - WIP

This commit is contained in:
Lukas Eder 2024-03-19 09:47:52 +01:00
parent 72f3c5339b
commit bd062f8107
18 changed files with 128 additions and 43 deletions

View File

@ -284,7 +284,12 @@ implements
final void acceptArguments3(Context<?> ctx, QueryPartCollectionView<Field<?>> args, Function<? super Field<?>, ? extends Field<?>> fun) {
if (args.isEmpty() && this instanceof Count)
args = QueryPartListView.wrap(ASTERISK.get());
// [#7539] Work around https://github.com/ClickHouse/ClickHouse/issues/61004
if (ctx.family() == CLICKHOUSE && filter.hasWhere())
args = QueryPartListView.wrap();
else
args = QueryPartListView.wrap(ASTERISK.get());
if (!filter.hasWhere() || supportsFilter(ctx))
ctx.visit(wrap(args).map(fun));

View File

@ -100,6 +100,9 @@ implements
case TRINO:
return false;
case CLICKHOUSE:
return false;
default:
return true;
}
@ -120,6 +123,10 @@ implements
ctx.visit(arrayConcat(arg1, array(arg2)));
break;
case CLICKHOUSE:
ctx.visit(function(N_arrayPushBack, getDataType(), arg1, arg2));
break;
default:
ctx.visit(function(N_ARRAY_APPEND, getDataType(), arg1, arg2));
break;

View File

@ -92,6 +92,17 @@ implements
// XXX: QueryPart API
// -------------------------------------------------------------------------
@Override
final boolean parenthesised(Context<?> ctx) {
switch (ctx.family()) {
case CLICKHOUSE:
return true;
default:
return false;
}
}
@Override
public final void accept(Context<?> ctx) {
switch (ctx.family()) {
@ -101,6 +112,10 @@ implements
case CLICKHOUSE:
ctx.visit(function(N_arrayConcat, getDataType(), arg1, arg2));
break;
default:
ctx.sql('(').visit(arg1).sql(" || ").visit(arg2).sql(')');
break;

View File

@ -100,6 +100,9 @@ implements
case TRINO:
return false;
case CLICKHOUSE:
return false;
default:
return true;
}
@ -120,6 +123,10 @@ implements
ctx.visit(arrayConcat(array(arg1), arg2));
break;
case CLICKHOUSE:
ctx.visit(function(N_arrayPushFront, getDataType(), arg2, arg1));
break;
default:
ctx.visit(function(N_ARRAY_PREPEND, getDataType(), arg1, arg2));
break;

View File

@ -103,6 +103,10 @@ implements
case CLICKHOUSE:
ctx.visit(function(N_LENGTH, getDataType(), array));
break;
case DUCKDB:
ctx.visit(function(N_ARRAY_LENGTH, getDataType(), array));
break;

View File

@ -96,6 +96,7 @@ final class Choose<T> extends AbstractField<T> implements QOM.Choose<T> {
case CLICKHOUSE:
case CUBRID:
case DERBY:
case DUCKDB:

View File

@ -113,6 +113,7 @@ implements
case CLICKHOUSE:
case CUBRID:
case DERBY:
case DUCKDB:
@ -162,6 +163,7 @@ implements
case CLICKHOUSE:
case CUBRID:
case DERBY:
case DUCKDB:

View File

@ -22108,7 +22108,7 @@ public class DSL {
* Calculate the cardinality of an array field.
*/
@NotNull
@Support({ DUCKDB, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, DUCKDB, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static Field<Integer> cardinality(Field<? extends Object[]> array) {
return new Cardinality(array);
}
@ -22121,7 +22121,7 @@ public class DSL {
* @param index is wrapped as {@link DSL#val(Object)}.
*/
@NotNull
@Support({ DUCKDB, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, DUCKDB, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T> arrayGet(Field<T[]> array, int index) {
return new ArrayGet<>(array, Tools.field(index));
}
@ -22132,7 +22132,7 @@ public class DSL {
* Get an array element at a given index (1 based).
*/
@NotNull
@Support({ DUCKDB, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, DUCKDB, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T> arrayGet(Field<T[]> array, Field<Integer> index) {
return new ArrayGet<>(array, index);
}
@ -22143,7 +22143,7 @@ public class DSL {
* Concatenate two arrays.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayConcat(T[] arg1, T[] arg2) {
return new ArrayConcat<>(Tools.field(arg1), Tools.field(arg2));
}
@ -22154,7 +22154,7 @@ public class DSL {
* Concatenate two arrays.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayConcat(T[] arg1, Field<T[]> arg2) {
return new ArrayConcat<>(Tools.field(arg1), arg2);
}
@ -22165,7 +22165,7 @@ public class DSL {
* Concatenate two arrays.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayConcat(Field<T[]> arg1, T[] arg2) {
return new ArrayConcat<>(arg1, Tools.field(arg2, arg1));
}
@ -22176,7 +22176,7 @@ public class DSL {
* Concatenate two arrays.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayConcat(Field<T[]> arg1, Field<T[]> arg2) {
return new ArrayConcat<>(arg1, arg2);
}
@ -22189,7 +22189,7 @@ public class DSL {
* @param arg2 is wrapped as {@link DSL#val(Object)}.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayAppend(T[] arg1, T arg2) {
return new ArrayAppend<>(Tools.field(arg1), Tools.field(arg2));
}
@ -22200,7 +22200,7 @@ public class DSL {
* Append an element to an array.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayAppend(T[] arg1, Field<T> arg2) {
return new ArrayAppend<>(Tools.field(arg1), arg2);
}
@ -22213,7 +22213,7 @@ public class DSL {
* @param arg2 is wrapped as {@link DSL#val(Object)}.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayAppend(Field<T[]> arg1, T arg2) {
return new ArrayAppend<>(arg1, Tools.field(arg2));
}
@ -22224,7 +22224,7 @@ public class DSL {
* Append an element to an array.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayAppend(Field<T[]> arg1, Field<T> arg2) {
return new ArrayAppend<>(arg1, arg2);
}
@ -22237,7 +22237,7 @@ public class DSL {
* @param arg1 is wrapped as {@link DSL#val(Object)}.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayPrepend(T arg1, T[] arg2) {
return new ArrayPrepend<>(Tools.field(arg1), Tools.field(arg2));
}
@ -22250,7 +22250,7 @@ public class DSL {
* @param arg1 is wrapped as {@link DSL#val(Object)}.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayPrepend(T arg1, Field<T[]> arg2) {
return new ArrayPrepend<>(Tools.field(arg1), arg2);
}
@ -22261,7 +22261,7 @@ public class DSL {
* Prepend an element to an array.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayPrepend(Field<T> arg1, T[] arg2) {
return new ArrayPrepend<>(arg1, Tools.field(arg2));
}
@ -22272,7 +22272,7 @@ public class DSL {
* Prepend an element to an array.
*/
@NotNull
@Support({ H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
@Support({ CLICKHOUSE, H2, HSQLDB, POSTGRES, TRINO, YUGABYTEDB })
public static <T> Field<T[]> arrayPrepend(Field<T> arg1, Field<T[]> arg2) {
return new ArrayPrepend<>(arg1, arg2);
}

View File

@ -48,6 +48,7 @@ import static org.jooq.impl.Identifiers.QUOTES;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER_ESCAPED;
import static org.jooq.impl.Identifiers.QUOTE_START_DELIMITER;
import static org.jooq.impl.JoinTable.NO_SUPPORT_NESTED_JOIN;
import static org.jooq.impl.Tools.EMPTY_PARAM;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_COUNT_BIND_VALUES;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_FORCE_STATIC_STATEMENT;
@ -306,18 +307,24 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
// TODO: subqueryLevel() is lower than scopeLevel if we use implicit join in procedural logic
else if (e1.joinNode != null && e1.joinNode.hasJoinPaths()) {
DefaultRenderContext ctx = new DefaultRenderContext(this, false);
ctx.data(DATA_RENDER_IMPLICIT_JOIN, true);
replacedSQL = ctx
.declareTables(true)
.sql('(')
.formatIndentStart(e1.indent)
.formatIndentStart()
.formatNewLine()
.visit(e1.joinNode.joinTree())
.formatNewLine()
.sql(')')
.render();
boolean noNesting = !NO_SUPPORT_NESTED_JOIN.contains(ctx.dialect());
ctx.data(DATA_RENDER_IMPLICIT_JOIN, true);
ctx.declareTables(true);
if (noNesting)
ctx.sql('(')
.formatIndentStart(e1.indent)
.formatIndentStart()
.formatNewLine();
ctx.visit(e1.joinNode.joinTree());
if (noNesting)
ctx.formatNewLine()
.sql(')');
replacedSQL = ctx.render();
insertedBindValues = ctx.bindValues();
}
else {

View File

@ -38,6 +38,7 @@
package org.jooq.impl;
import static org.jooq.SQLDialect.CLICKHOUSE;
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.H2;
@ -66,7 +67,7 @@ import org.jooq.TableOptions;
*/
class DerivedTable<R extends Record> extends AbstractTable<R> implements QOM.DerivedTable<R> {
static final Set<SQLDialect> NO_SUPPORT_CORRELATED_DERIVED_TABLE = SQLDialect.supportedUntil(DERBY, H2, MARIADB);
static final Set<SQLDialect> NO_SUPPORT_CORRELATED_DERIVED_TABLE = SQLDialect.supportedUntil(CLICKHOUSE, DERBY, H2, MARIADB);
private final Lazy<Select<R>> query;
DerivedTable(Select<R> query) {

View File

@ -95,7 +95,8 @@ implements
static final Set<SQLDialect> EMULATE_DISTINCT_PREDICATE = SQLDialect.supportedUntil(CLICKHOUSE, CUBRID, DERBY);
static final Set<SQLDialect> EMULATE_DISTINCT_PREDICATE = SQLDialect.supportedUntil(CUBRID, DERBY);
static final Set<SQLDialect> EMULATE_WITH_ARRAYS = SQLDialect.supportedBy(CLICKHOUSE);
static final Set<SQLDialect> SUPPORT_DISTINCT_WITH_ARROW = SQLDialect.supportedBy(MARIADB, MYSQL);
@ -123,6 +124,10 @@ implements
else if (EMULATE_DISTINCT_PREDICATE.contains(ctx.dialect()))
ctx.visit(notExists(select(arg1.as("x")).intersect(select(arg2.as("x")))));
// [#7539] While INTERSECT is supported, correlating subqueries hardly is in ClickHouse
else if (EMULATE_WITH_ARRAYS.contains(ctx.dialect()))
ctx.visit(function(N_arrayUniq, INTEGER, array(arg1, arg2)).eq(inline(2)));
// MySQL knows the <=> operator
else if (SUPPORT_DISTINCT_WITH_ARROW.contains(ctx.dialect()))
ctx.visit(condition("{not}({0} <=> {1})", arg1, arg2));

View File

@ -116,6 +116,10 @@ implements
else if (IsDistinctFrom.EMULATE_DISTINCT_PREDICATE.contains(ctx.dialect()))
ctx.visit(exists(select(arg1.as("x")).intersect(select(arg2.as("x")))));
// [#7539] While INTERSECT is supported, correlating subqueries hardly is in ClickHouse
else if (IsDistinctFrom.EMULATE_WITH_ARRAYS.contains(ctx.dialect()))
ctx.visit(function(N_arrayUniq, INTEGER, array(arg1, arg2)).eq(inline(1)));
// MySQL knows the <=> operator
else if (IsDistinctFrom.SUPPORT_DISTINCT_WITH_ARROW.contains(ctx.dialect()))
ctx.visit(condition("{0} <=> {1}", arg1, arg2));

View File

@ -178,6 +178,7 @@ abstract class JoinTable<J extends JoinTable<J>> extends AbstractJoinTable<J> {
static final Set<SQLDialect> EMULATE_JOIN_USING = SQLDialect.supportedBy(CUBRID, IGNITE);
static final Set<SQLDialect> EMULATE_APPLY = SQLDialect.supportedBy(FIREBIRD, POSTGRES, TRINO, YUGABYTEDB);
static final Set<SQLDialect> EMUlATE_SEMI_ANTI_JOIN = SQLDialect.supportedBy(CUBRID, DERBY, DUCKDB, FIREBIRD, H2, HSQLDB, IGNITE, MARIADB, MYSQL, POSTGRES, SQLITE, TRINO, YUGABYTEDB);
static final Set<SQLDialect> NO_SUPPORT_NESTED_JOIN = SQLDialect.supportedBy(CLICKHOUSE);
final Table<?> lhs;
final Table<?> rhs;

View File

@ -73,6 +73,7 @@ final class Names {
static final Name N_ARRAY_AGG = systemName("array_agg");
static final Name N_ARRAY_CONSTRUCT = systemName("array_construct");
static final Name N_ARRAY_CONSTRUCT_COMPACT = systemName("array_construct_compact");
static final Name N_arrayUniq = systemName("arrayUniq");
static final Name N_BITCOUNT = systemName("bitcount");
static final Name N_bitCount = systemName("bitCount");
static final Name N_BITWISE_AND_AGG = systemName("bitwise_and_agg");
@ -644,6 +645,9 @@ final class Names {
static final Name N_XMLSERIALIZE = systemName("xmlserialize");
static final Name N_XMLSERIALIZE_CONTENT = systemName("xmlserialize_content");
static final Name N_XOR = systemName("xor");
static final Name N_arrayConcat = systemName("arrayConcat");
static final Name N_arrayPushBack = systemName("arrayPushBack");
static final Name N_arrayPushFront = systemName("arrayPushFront");
static final Name N_bitAnd = systemName("bitAnd");
static final Name N_bitNot = systemName("bitNot");
static final Name N_bitOr = systemName("bitOr");

View File

@ -8738,16 +8738,18 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
return parseFieldAddDatePart(MINUTE);
else if (parseFunctionNameIf("ADD_SECONDS"))
return parseFieldAddDatePart(SECOND);
else if (parseFunctionNameIf("ARRAY_APPEND"))
else if (parseFunctionNameIf("ARRAY_APPEND", "arrayPushBack"))
return parseFunctionArgs2((f1, f2) -> arrayAppend((Field<Void[]>) f1, (Field<Void>) f2));
else if (parseFunctionNameIf("ARRAY_CAT", "ARRAY_CONCAT"))
else if (parseFunctionNameIf("ARRAY_CAT", "ARRAY_CONCAT", "arrayConcat"))
return parseFunctionArgs2((f1, f2) -> arrayConcat(f1, f2));
else if (parseFunctionNameIf("ARRAY_GET"))
else if (parseFunctionNameIf("ARRAY_GET", "arrayElement"))
return parseFunctionArgs2((f1, f2) -> arrayGet(f1, f2));
else if (parseFunctionNameIf("ARRAY_OVERLAP", "ARRAYS_OVERLAP"))
return parseFunctionArgs2((f1, f2) -> arrayOverlap((Field<Void[]>) f1, (Field<Void[]>) f2));
else if (parseFunctionNameIf("ARRAY_PREPEND"))
return parseFunctionArgs2((f1, f2) -> arrayPrepend((Field<Void>) f1, (Field<Void[]>) f2));
else if (parseFunctionNameIf("arrayPushFront"))
return parseFunctionArgs2((f1, f2) -> arrayPrepend((Field<Void>) f2, (Field<Void[]>) f1));
else if (parseFunctionNameIf("ARRAY_REMOVE"))
return parseFunctionArgs2((f1, f2) -> arrayRemove((Field<Void[]>) f1, (Field<Void>) f2));
else if (parseFunctionNameIf("ARRAY_REPLACE"))

View File

@ -1439,16 +1439,16 @@ public final class QOM {
public /*sealed*/ interface ScalarSubquery<T>
extends
Field<T>,
UOperator1<Select<? extends Record1<T>>, ScalarSubquery<T>>
org.jooq.Field<T>,
UOperator1<org.jooq.Select<? extends org.jooq.Record1<T>>, ScalarSubquery<T>>
/*permits
ScalarSubquery*/
{}
public /*sealed*/ interface RowSubquery
extends
Row,
UOperator1<Select<?>, RowSubquery>
org.jooq.Row,
UOperator1<org.jooq.Select<?>, RowSubquery>
{}
public /*sealed*/ interface Neg<T>

View File

@ -65,11 +65,17 @@ import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.SQLDialect.TRINO;
// ...
import static org.jooq.SQLDialect.YUGABYTEDB;
import static org.jooq.impl.DSL.array;
import static org.jooq.impl.DSL.exists;
import static org.jooq.impl.DSL.function;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.notExists;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.Keywords.K_IS;
import static org.jooq.impl.Keywords.K_NOT;
import static org.jooq.impl.Names.NQ_SELECT;
import static org.jooq.impl.Names.N_arrayUniq;
import static org.jooq.impl.SQLDataType.INTEGER;
import static org.jooq.impl.SubqueryCharacteristics.PREDICAND;
import static org.jooq.impl.Tools.visitSubquery;
@ -87,18 +93,19 @@ import org.jooq.impl.QOM.UNotYetImplemented;
* @author Lukas Eder
*/
final class RowIsDistinctFrom extends AbstractCondition implements UNotYetImplemented {
private static final Set<SQLDialect> EMULATE_DISTINCT = SQLDialect.supportedBy(CLICKHOUSE, CUBRID, DERBY);
static final Set<SQLDialect> EMULATE_DISTINCT = SQLDialect.supportedBy(CUBRID, DERBY);
static final Set<SQLDialect> EMULATE_WITH_ARRAYS = SQLDialect.supportedBy(CLICKHOUSE);
// An emulation may be required only for the version where a subquery is used
// E.g. in HSQLDB: https://sourceforge.net/p/hsqldb/bugs/1579/
// Or in PostgreSQL: https://twitter.com/pg_xocolatl/status/1260344255035379714
private static final Set<SQLDialect> EMULATE_DISTINCT_SELECT = SQLDialect.supportedBy(HSQLDB, POSTGRES, TRINO, YUGABYTEDB);
private static final Set<SQLDialect> SUPPORT_DISTINCT_WITH_ARROW = SQLDialect.supportedBy(MARIADB, MYSQL);
static final Set<SQLDialect> EMULATE_DISTINCT_SELECT = SQLDialect.supportedBy(HSQLDB, POSTGRES, TRINO, YUGABYTEDB);
static final Set<SQLDialect> SUPPORT_DISTINCT_WITH_ARROW = SQLDialect.supportedBy(MARIADB, MYSQL);
private final Row lhs;
private final Row rhsRow;
private final Select<?> rhsSelect;
private final boolean not;
private final Row lhs;
private final Row rhsRow;
private final Select<?> rhsSelect;
private final boolean not;
RowIsDistinctFrom(Row lhs, Row rhs, boolean not) {
this.lhs = lhs;
@ -147,6 +154,17 @@ final class RowIsDistinctFrom extends AbstractCondition implements UNotYetImplem
ctx.sql(')');
}
else if (EMULATE_WITH_ARRAYS.contains(ctx.dialect())) {
ctx.visit(
function(N_arrayUniq, INTEGER,
array(
lhs,
rhsRow != null ? rhsRow : CustomField.of(NQ_SELECT, SQLDataType.RECORD, c -> visitSubquery(c, rhsSelect, PREDICAND))
)
).eq(inline(not ? 1 : 2))
);
}
// SQLite knows the IS / IS NOT predicate
else if (SQLITE == ctx.family()) {
ctx.visit(lhs).sql(' ').visit(K_IS).sql(' ');

View File

@ -38,6 +38,7 @@
package org.jooq.impl;
import static org.jooq.SQLDialect.CLICKHOUSE;
// ...
import static org.jooq.SQLDialect.HSQLDB;
// ...
@ -63,6 +64,7 @@ import org.jooq.Select;
@SuppressWarnings("unchecked")
final class ScalarSubquery<T> extends AbstractField<T> implements QOM.ScalarSubquery<T> {
static final Set<SQLDialect> NO_SUPPORT_CORRELATED_SUBQUERY = SQLDialect.supportedBy(CLICKHOUSE);
static final Set<SQLDialect> NO_SUPPORT_WITH_IN_SCALAR_SUBQUERY = SQLDialect.supportedBy(HSQLDB);
final Select<?> query;
final boolean predicandSubquery;