From 0b7af628766a19dc2978c57e1819add2eaeb3877 Mon Sep 17 00:00:00 2001 From: lukaseder Date: Tue, 9 Oct 2018 17:51:38 +0200 Subject: [PATCH] [#1535] Generate dummy ORDER BY clause for ranking functions on databases that require them --- jOOQ/src/main/java/org/jooq/impl/DSL.java | 38 +++++++++------ .../src/main/java/org/jooq/impl/Function.java | 36 ++++++++++++-- jOOQ/src/main/java/org/jooq/impl/LeadLag.java | 8 ++-- jOOQ/src/main/java/org/jooq/impl/Term.java | 48 +++++++++---------- jOOQ/src/main/java/org/jooq/impl/Tools.java | 5 ++ .../jooq/impl/WindowSpecificationImpl.java | 20 +++++++- 6 files changed, 106 insertions(+), 49 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index f93abff863..d32ec6f3a4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -76,6 +76,16 @@ import static org.jooq.SQLDialect.SQLITE; // ... // ... // ... +import static org.jooq.impl.Term.CUME_DIST; +import static org.jooq.impl.Term.DENSE_RANK; +import static org.jooq.impl.Term.FIRST_VALUE; +import static org.jooq.impl.Term.LAG; +import static org.jooq.impl.Term.LAST_VALUE; +import static org.jooq.impl.Term.LEAD; +import static org.jooq.impl.Term.NTH_VALUE; +import static org.jooq.impl.Term.NTILE; +import static org.jooq.impl.Term.PERCENT_RANK; +import static org.jooq.impl.Term.RANK; import static org.jooq.impl.Term.ROW_NUMBER; import static org.jooq.impl.Tools.EMPTY_FIELD; import static org.jooq.impl.Tools.EMPTY_QUERYPART; @@ -18170,7 +18180,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowOverStep rank() { - return new org.jooq.impl.Function("rank", SQLDataType.INTEGER); + return new org.jooq.impl.Function(RANK, SQLDataType.INTEGER); } /** @@ -18178,7 +18188,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowOverStep denseRank() { - return new org.jooq.impl.Function("dense_rank", SQLDataType.INTEGER); + return new org.jooq.impl.Function(DENSE_RANK, SQLDataType.INTEGER); } /** @@ -18186,7 +18196,7 @@ public class DSL { */ @Support({ CUBRID, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowOverStep percentRank() { - return new org.jooq.impl.Function("percent_rank", SQLDataType.NUMERIC); + return new org.jooq.impl.Function(PERCENT_RANK, SQLDataType.NUMERIC); } /** @@ -18194,7 +18204,7 @@ public class DSL { */ @Support({ CUBRID, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowOverStep cumeDist() { - return new org.jooq.impl.Function("cume_dist", SQLDataType.NUMERIC); + return new org.jooq.impl.Function(CUME_DIST, SQLDataType.NUMERIC); } /** @@ -18202,7 +18212,7 @@ public class DSL { */ @Support({ CUBRID, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowOverStep ntile(int number) { - return new org.jooq.impl.Function("ntile", SQLDataType.INTEGER, inline(number)); + return new org.jooq.impl.Function(NTILE, SQLDataType.INTEGER, inline(number)); } /** @@ -18226,7 +18236,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowIgnoreNullsStep firstValue(Field field) { - return new org.jooq.impl.Function("first_value", nullSafeDataType(field), nullSafe(field)); + return new org.jooq.impl.Function(FIRST_VALUE, nullSafeDataType(field), nullSafe(field)); } /** @@ -18234,7 +18244,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowIgnoreNullsStep lastValue(Field field) { - return new org.jooq.impl.Function("last_value", nullSafeDataType(field), nullSafe(field)); + return new org.jooq.impl.Function(LAST_VALUE, nullSafeDataType(field), nullSafe(field)); } /** @@ -18250,7 +18260,7 @@ public class DSL { */ @Support({ FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowFromFirstLastStep nthValue(Field field, Field nth) { - return new org.jooq.impl.Function("nth_value", nullSafeDataType(field), nullSafe(field), nullSafe(nth)); + return new org.jooq.impl.Function(NTH_VALUE, nullSafeDataType(field), nullSafe(field), nullSafe(nth)); } /** @@ -18258,7 +18268,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowIgnoreNullsStep lead(Field field) { - return new LeadLag("lead", nullSafe(field)); + return new LeadLag(LEAD, nullSafe(field)); } /** @@ -18266,7 +18276,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowIgnoreNullsStep lead(Field field, int offset) { - return new LeadLag("lead", nullSafe(field), offset); + return new LeadLag(LEAD, nullSafe(field), offset); } /** @@ -18286,7 +18296,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowIgnoreNullsStep lead(Field field, int offset, Field defaultValue) { - return new LeadLag("lead", nullSafe(field), offset, nullSafe(defaultValue)); + return new LeadLag(LEAD, nullSafe(field), offset, nullSafe(defaultValue)); } /** @@ -18294,7 +18304,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowIgnoreNullsStep lag(Field field) { - return new LeadLag("lag", nullSafe(field)); + return new LeadLag(LAG, nullSafe(field)); } /** @@ -18302,7 +18312,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowIgnoreNullsStep lag(Field field, int offset) { - return new LeadLag("lag", nullSafe(field), offset); + return new LeadLag(LAG, nullSafe(field), offset); } /** @@ -18322,7 +18332,7 @@ public class DSL { */ @Support({ CUBRID, FIREBIRD_3_0, H2, MYSQL_8_0, POSTGRES, SQLITE }) public static WindowIgnoreNullsStep lag(Field field, int offset, Field defaultValue) { - return new LeadLag("lag", nullSafe(field), offset, nullSafe(defaultValue)); + return new LeadLag(LAG, nullSafe(field), offset, nullSafe(defaultValue)); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Function.java b/jOOQ/src/main/java/org/jooq/impl/Function.java index df05ca1747..1be5b73e01 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Function.java +++ b/jOOQ/src/main/java/org/jooq/impl/Function.java @@ -85,6 +85,7 @@ import static org.jooq.impl.Term.MEDIAN; import static org.jooq.impl.Term.MODE; import static org.jooq.impl.Term.PRODUCT; import static org.jooq.impl.Term.ROW_NUMBER; +import static org.jooq.impl.Tools.DataKey.DATA_RANKING_FUNCTION; import static org.jooq.impl.Tools.DataKey.DATA_WINDOW_DEFINITIONS; import java.math.BigDecimal; @@ -419,13 +420,42 @@ class Function extends AbstractField implements return; // [#1524] Don't render this clause where it is not supported - if (term == ROW_NUMBER && ctx.configuration().dialect() == HSQLDB) + if (term == ROW_NUMBER && ctx.family() == HSQLDB) return; + Boolean ranking = false; + Boolean previous = null; + + if (term != null) { + switch (term) { + case CUME_DIST: + case DENSE_RANK: + case FIRST_VALUE: + case LAG: + case LEAD: + case LAST_VALUE: + case NTH_VALUE: + case NTILE: + case PERCENT_RANK: + case RANK: + case ROW_NUMBER: + ranking = true; + break; + } + } + ctx.sql(' ') .visit(K_OVER) - .sql(' ') - .visit(window); + .sql(' '); + + previous = (Boolean) ctx.data(DATA_RANKING_FUNCTION, ranking); + + ctx.visit(window); + + if (previous != null) + ctx.data(DATA_RANKING_FUNCTION, previous); + else + ctx.data().remove(DATA_RANKING_FUNCTION); } @SuppressWarnings("unchecked") diff --git a/jOOQ/src/main/java/org/jooq/impl/LeadLag.java b/jOOQ/src/main/java/org/jooq/impl/LeadLag.java index 068fb3a888..68c590bcc8 100644 --- a/jOOQ/src/main/java/org/jooq/impl/LeadLag.java +++ b/jOOQ/src/main/java/org/jooq/impl/LeadLag.java @@ -54,12 +54,12 @@ final class LeadLag extends Function { */ private static final long serialVersionUID = 7292087943334025737L; - private final String function; + private final Term function; private final Field field; private final int offset; private final Field defaultValue; - LeadLag(String function, Field field) { + LeadLag(Term function, Field field) { super(function, field.getDataType(), field); this.function = function; @@ -68,7 +68,7 @@ final class LeadLag extends Function { this.defaultValue = null; } - LeadLag(String function, Field field, int offset) { + LeadLag(Term function, Field field, int offset) { super(function, field.getDataType(), field, inline(offset)); this.function = function; @@ -77,7 +77,7 @@ final class LeadLag extends Function { this.defaultValue = null; } - LeadLag(String function, Field field, int offset, Field defaultValue) { + LeadLag(Term function, Field field, int offset, Field defaultValue) { super(function, field.getDataType(), field, inline(offset), defaultValue); this.function = function; diff --git a/jOOQ/src/main/java/org/jooq/impl/Term.java b/jOOQ/src/main/java/org/jooq/impl/Term.java index 000557e119..ae24408367 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Term.java +++ b/jOOQ/src/main/java/org/jooq/impl/Term.java @@ -48,12 +48,7 @@ import org.jooq.SQLDialect; */ enum Term { - ARRAY_AGG { - @Override - public String translate(SQLDialect dialect) { - return "array_agg"; - } - }, + ARRAY_AGG, ATAN2 { @Override public String translate(SQLDialect dialect) { @@ -127,11 +122,12 @@ enum Term { - - - - - + CUME_DIST, + DENSE_RANK, + FIRST_VALUE, + LAG, + LAST_VALUE, + LEAD, LIST_AGG { @Override public String translate(SQLDialect dialect) { @@ -168,12 +164,7 @@ enum Term { return "listagg"; } }, - MEDIAN { - @Override - public String translate(SQLDialect dialect) { - return "median"; - } - }, + MEDIAN, MODE { @Override public String translate(SQLDialect dialect) { @@ -187,6 +178,8 @@ enum Term { } } }, + NTH_VALUE, + NTILE, OCTET_LENGTH { @Override public String translate(SQLDialect dialect) { @@ -214,12 +207,9 @@ enum Term { return "octet_length"; } }, - PRODUCT { - @Override - public String translate(SQLDialect dialect) { - return "product"; - } - }, + PERCENT_RANK, + PRODUCT, + RANK, ROW_NUMBER { @Override public String translate(SQLDialect dialect) { @@ -318,10 +308,12 @@ enum Term { ; - private final Name name; + private final Name name; + private final String translation; private Term() { this.name = DSL.name(name()); + this.translation = name().toLowerCase(); } @Override @@ -334,7 +326,11 @@ enum Term { } /** - * Translate the term to its dialect-specific variant + * Translate the term to its dialect-specific variant. + * + * @param dialect The dialect to translate to */ - abstract String translate(SQLDialect dialect); + String translate(SQLDialect dialect) { + return translation; + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index d087bfe66e..e4772aa5db 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -497,6 +497,11 @@ final class Tools { * [#7467] In SQL Server, the TOP clause is sometimes preferred over the FETCH clause. */ DATA_PREFER_TOP_OVER_FETCH, + + /** + * [#1535] We're currently generating the window specification of a ranking function. + */ + DATA_RANKING_FUNCTION, } /** diff --git a/jOOQ/src/main/java/org/jooq/impl/WindowSpecificationImpl.java b/jOOQ/src/main/java/org/jooq/impl/WindowSpecificationImpl.java index 2bd2cd3e56..2883ec4931 100644 --- a/jOOQ/src/main/java/org/jooq/impl/WindowSpecificationImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/WindowSpecificationImpl.java @@ -37,13 +37,20 @@ */ package org.jooq.impl; +import static java.lang.Boolean.TRUE; // ... import static org.jooq.SQLDialect.CUBRID; +import static org.jooq.SQLDialect.H2; // ... import static org.jooq.SQLDialect.MYSQL; +// ... +// ... import static org.jooq.SQLDialect.SQLITE; // ... +// ... +import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.one; +import static org.jooq.impl.DSL.select; import static org.jooq.impl.Keywords.K_AND; import static org.jooq.impl.Keywords.K_BETWEEN; import static org.jooq.impl.Keywords.K_CURRENT_ROW; @@ -54,6 +61,7 @@ import static org.jooq.impl.Keywords.K_PARTITION_BY; import static org.jooq.impl.Keywords.K_PRECEDING; import static org.jooq.impl.Keywords.K_UNBOUNDED_FOLLOWING; import static org.jooq.impl.Keywords.K_UNBOUNDED_PRECEDING; +import static org.jooq.impl.Tools.DataKey.DATA_RANKING_FUNCTION; import static org.jooq.impl.WindowSpecificationImpl.Exclude.CURRENT_ROW; import static org.jooq.impl.WindowSpecificationImpl.Exclude.GROUP; import static org.jooq.impl.WindowSpecificationImpl.Exclude.NO_OTHERS; @@ -93,8 +101,9 @@ final class WindowSpecificationImpl extends AbstractQueryPart implements /** * Generated UID */ - private static final long serialVersionUID = 2996016924769376361L; - private static final EnumSet OMIT_PARTITION_BY_ONE = EnumSet.of(CUBRID, MYSQL, SQLITE); + private static final long serialVersionUID = 2996016924769376361L; + private static final EnumSet OMIT_PARTITION_BY_ONE = EnumSet.of(CUBRID, MYSQL, SQLITE); + private static final EnumSet REQUIRES_ORDER_BY_IN_RANKING = EnumSet.of(H2); private final WindowDefinitionImpl windowDefinition; private final QueryPartList> partitionBy; @@ -154,6 +163,13 @@ final class WindowSpecificationImpl extends AbstractQueryPart implements glue = " "; } + else if (TRUE.equals(ctx.data(DATA_RANKING_FUNCTION)) && REQUIRES_ORDER_BY_IN_RANKING.contains(ctx.family())) { + ctx.sql(glue) + .visit(K_ORDER_BY).sql(' ') + .visit(field(select(one()))); + + glue = " "; + } if (frameStart != null) { ctx.sql(glue);