[jOOQ/jOOQ#3564] Emulate PostgreSQL's DISTINCT ON clause

This commit is contained in:
Lukas Eder 2020-10-16 23:36:24 +02:00
parent 57322fae99
commit 690f1f9002
5 changed files with 79 additions and 11 deletions

View File

@ -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<R extends Record> extends SelectIntoStep<R
* it is added explicitly via the jOOQ API.
*/
@NotNull
@Support({ H2, POSTGRES })
@Support({ CUBRID, FIREBIRD, H2, MARIADB, MYSQL, POSTGRES, SQLITE })
SelectIntoStep<R> on(SelectFieldOrAsterisk... fields);
/**
@ -128,7 +143,7 @@ public interface SelectDistinctOnStep<R extends Record> extends SelectIntoStep<R
* it is added explicitly via the jOOQ API.
*/
@NotNull
@Support({ H2, POSTGRES })
@Support({ CUBRID, FIREBIRD, H2, MARIADB, MYSQL, POSTGRES, SQLITE })
SelectIntoStep<R> on(Collection<? extends SelectFieldOrAsterisk> fields);
/**
@ -136,7 +151,7 @@ public interface SelectDistinctOnStep<R extends Record> extends SelectIntoStep<R
* <code>SELECT DISTINCT ON (...)</code> statement.
*/
@NotNull
@Support({ H2, POSTGRES })
@Support({ CUBRID, FIREBIRD, H2, MARIADB, MYSQL, POSTGRES, SQLITE })
SelectIntoStep<R> distinctOn(SelectFieldOrAsterisk... fields);
/**
@ -144,6 +159,6 @@ public interface SelectDistinctOnStep<R extends Record> extends SelectIntoStep<R
* <code>SELECT DISTINCT ON (...)</code> statement.
*/
@NotNull
@Support({ H2, POSTGRES })
@Support({ CUBRID, FIREBIRD, H2, MARIADB, MYSQL, POSTGRES, SQLITE })
SelectIntoStep<R> distinctOn(Collection<? extends SelectFieldOrAsterisk> fields);
}

View File

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

View File

@ -507,4 +507,11 @@ final class Limit extends AbstractQueryPart {
return this;
}
final void clear() {
offset = null;
numberOfRows = null;
withTies = false;
percent = false;
}
}

View File

@ -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<R extends Record> extends AbstractResultQuery<R> imp
private static final Set<SQLDialect> EMULATE_EMPTY_GROUP_BY_CONSTANT = SQLDialect.supportedUntil(DERBY, HSQLDB);
private static final Set<SQLDialect> EMULATE_EMPTY_GROUP_BY_OTHER = SQLDialect.supportedUntil(FIREBIRD, MARIADB, MYSQL, SQLITE);
private static final Set<SQLDialect> SUPPORT_FULL_WITH_TIES = SQLDialect.supportedBy(H2);
private static final Set<SQLDialect> EMULATE_DISTINCT_ON = SQLDialect.supportedBy(DERBY, FIREBIRD, HSQLDB, MARIADB, MYSQL, SQLITE);
@ -1119,6 +1125,41 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
@SuppressWarnings({ "rawtypes", "unchecked" })
final Select<?> distinctOnEmulation() {
// [#3564] TODO: Extract and merge this with getSelectResolveSomeAsterisks0()
List<Field<?>> partitionBy = new ArrayList<>(distinctOn.size());
for (SelectFieldOrAsterisk f : distinctOn)
if (f instanceof Field)
partitionBy.add((Field<?>) f);
Field<Integer> rn = rowNumber().over(partitionBy(partitionBy).orderBy(orderBy)).as("rn");
SelectQueryImpl<R> 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<R extends Record> extends AbstractResultQuery<R> 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());
}

View File

@ -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;
}