From 690f1f9002ff2a12a42bb6e2045d4f5be98c417e Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 16 Oct 2020 23:36:24 +0200 Subject: [PATCH] [jOOQ/jOOQ#3564] Emulate PostgreSQL's DISTINCT ON clause --- .../java/org/jooq/SelectDistinctOnStep.java | 29 +++++++++--- jOOQ/src/main/java/org/jooq/SelectQuery.java | 4 +- jOOQ/src/main/java/org/jooq/impl/Limit.java | 7 +++ .../java/org/jooq/impl/SelectQueryImpl.java | 46 +++++++++++++++++++ jOOQ/src/main/java/org/jooq/impl/Tools.java | 4 +- 5 files changed, 79 insertions(+), 11 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/SelectDistinctOnStep.java b/jOOQ/src/main/java/org/jooq/SelectDistinctOnStep.java index bd8f2da632..bb47156254 100644 --- a/jOOQ/src/main/java/org/jooq/SelectDistinctOnStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectDistinctOnStep.java @@ -37,16 +37,31 @@ */ package org.jooq; -import org.jetbrains.annotations.*; - - // ... // ... +import static org.jooq.SQLDialect.CUBRID; +// ... +import static org.jooq.SQLDialect.FIREBIRD; import static org.jooq.SQLDialect.H2; +// ... +// ... +import static org.jooq.SQLDialect.MARIADB; +// ... +import static org.jooq.SQLDialect.MYSQL; +// ... import static org.jooq.SQLDialect.POSTGRES; +// ... +// ... +import static org.jooq.SQLDialect.SQLITE; +// ... +// ... +// ... +// ... import java.util.Collection; +import org.jetbrains.annotations.NotNull; + /** * This type is used for the {@link Select}'s DSL API when selecting generic * {@link Record} types. @@ -117,7 +132,7 @@ public interface SelectDistinctOnStep extends SelectIntoStep on(SelectFieldOrAsterisk... fields); /** @@ -128,7 +143,7 @@ public interface SelectDistinctOnStep extends SelectIntoStep on(Collection fields); /** @@ -136,7 +151,7 @@ public interface SelectDistinctOnStep extends SelectIntoStepSELECT DISTINCT ON (...) statement. */ @NotNull - @Support({ H2, POSTGRES }) + @Support({ CUBRID, FIREBIRD, H2, MARIADB, MYSQL, POSTGRES, SQLITE }) SelectIntoStep distinctOn(SelectFieldOrAsterisk... fields); /** @@ -144,6 +159,6 @@ public interface SelectDistinctOnStep extends SelectIntoStepSELECT DISTINCT ON (...) statement. */ @NotNull - @Support({ H2, POSTGRES }) + @Support({ CUBRID, FIREBIRD, H2, MARIADB, MYSQL, POSTGRES, SQLITE }) SelectIntoStep distinctOn(Collection fields); } diff --git a/jOOQ/src/main/java/org/jooq/SelectQuery.java b/jOOQ/src/main/java/org/jooq/SelectQuery.java index b4300c6148..2cce35d4e5 100644 --- a/jOOQ/src/main/java/org/jooq/SelectQuery.java +++ b/jOOQ/src/main/java/org/jooq/SelectQuery.java @@ -117,7 +117,7 @@ public interface SelectQuery extends Select, ConditionProvi *

* This also sets the distinct flag to true */ - @Support({ H2, POSTGRES }) + @Support({ CUBRID, FIREBIRD, H2, MARIADB, MYSQL, POSTGRES, SQLITE }) void addDistinctOn(SelectFieldOrAsterisk... fields); /** @@ -125,7 +125,7 @@ public interface SelectQuery extends Select, ConditionProvi *

* This also sets the distinct flag to true */ - @Support({ H2, POSTGRES }) + @Support({ CUBRID, FIREBIRD, H2, MARIADB, MYSQL, POSTGRES, SQLITE }) void addDistinctOn(Collection fields); /** diff --git a/jOOQ/src/main/java/org/jooq/impl/Limit.java b/jOOQ/src/main/java/org/jooq/impl/Limit.java index 097f233e22..77a8c0e734 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Limit.java +++ b/jOOQ/src/main/java/org/jooq/impl/Limit.java @@ -507,4 +507,11 @@ final class Limit extends AbstractQueryPart { return this; } + + final void clear() { + offset = null; + numberOfRows = null; + withTies = false; + percent = false; + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 80d103e9bf..24f1c03d50 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -114,6 +114,7 @@ import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.noCondition; import static org.jooq.impl.DSL.one; import static org.jooq.impl.DSL.orderBy; +import static org.jooq.impl.DSL.partitionBy; import static org.jooq.impl.DSL.regexpReplaceAll; import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.rowNumber; @@ -217,7 +218,11 @@ import org.jooq.Row; import org.jooq.SQLDialect; import org.jooq.Select; import org.jooq.SelectFieldOrAsterisk; +import org.jooq.SelectLimitPercentStep; +import org.jooq.SelectLimitStep; +import org.jooq.SelectOffsetStep; import org.jooq.SelectQuery; +import org.jooq.SelectWithTiesStep; import org.jooq.SortField; import org.jooq.Table; import org.jooq.TableField; @@ -265,6 +270,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp private static final Set EMULATE_EMPTY_GROUP_BY_CONSTANT = SQLDialect.supportedUntil(DERBY, HSQLDB); private static final Set EMULATE_EMPTY_GROUP_BY_OTHER = SQLDialect.supportedUntil(FIREBIRD, MARIADB, MYSQL, SQLITE); private static final Set SUPPORT_FULL_WITH_TIES = SQLDialect.supportedBy(H2); + private static final Set EMULATE_DISTINCT_ON = SQLDialect.supportedBy(DERBY, FIREBIRD, HSQLDB, MARIADB, MYSQL, SQLITE); @@ -1119,6 +1125,41 @@ final class SelectQueryImpl extends AbstractResultQuery imp + + + @SuppressWarnings({ "rawtypes", "unchecked" }) + final Select distinctOnEmulation() { + + // [#3564] TODO: Extract and merge this with getSelectResolveSomeAsterisks0() + List> partitionBy = new ArrayList<>(distinctOn.size()); + + for (SelectFieldOrAsterisk f : distinctOn) + if (f instanceof Field) + partitionBy.add((Field) f); + + Field rn = rowNumber().over(partitionBy(partitionBy).orderBy(orderBy)).as("rn"); + + SelectQueryImpl copy = copy(); + copy.distinctOn = null; + copy.select.add(rn); + copy.orderBy.clear(); + copy.limit.clear(); + + SelectLimitStep s1 = + DSL.select(qualify(table(name("t")), select)) + .from(copy.asTable("t")) + .where(rn.eq(one())) + .orderBy(unqualified(orderBy.toArray(EMPTY_SORTFIELD))); + + if (limit.numberOfRows != null) { + SelectLimitPercentStep s2 = s1.limit((Param) limit.numberOfRows); + SelectWithTiesStep s3 = limit.percent ? s2.percent() : s2; + SelectOffsetStep s4 = limit.withTies ? s3.withTies() : s3; + return limit.offset != null ? s4.offset((Param) limit.offset) : s4; + } + else + return limit.offset != null ? s1.offset((Param) limit.offset) : s1; + } @Override public final void accept(Context ctx) { @@ -1131,6 +1172,11 @@ final class SelectQueryImpl extends AbstractResultQuery imp && containsTable(dmlTable)) { ctx.visit(DSL.select(asterisk()).from(asTable("t"))); } + + // [#3564] Emulate DISINTCT ON queries at the top level + else if (Tools.isNotEmpty(distinctOn) && EMULATE_DISTINCT_ON.contains(ctx.dialect())) { + ctx.visit(distinctOnEmulation()); + } diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 17736fcd5b..43c44f0e58 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -1397,8 +1397,8 @@ final class Tools { SortField[] result = new SortField[fields.length]; - for (SortField field : fields) - result[0] = unqualified(field); + for (int i = 0; i < result.length; i++) + result[i] = unqualified(fields[i]); return result; }