[#1535] Generate dummy ORDER BY clause for ranking functions on databases that require them

This commit is contained in:
lukaseder 2018-10-09 17:51:38 +02:00
parent da3ef5f7eb
commit 0b7af62876
6 changed files with 106 additions and 49 deletions

View File

@ -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<Integer> rank() {
return new org.jooq.impl.Function<Integer>("rank", SQLDataType.INTEGER);
return new org.jooq.impl.Function<Integer>(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<Integer> denseRank() {
return new org.jooq.impl.Function<Integer>("dense_rank", SQLDataType.INTEGER);
return new org.jooq.impl.Function<Integer>(DENSE_RANK, SQLDataType.INTEGER);
}
/**
@ -18186,7 +18196,7 @@ public class DSL {
*/
@Support({ CUBRID, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE })
public static WindowOverStep<BigDecimal> percentRank() {
return new org.jooq.impl.Function<BigDecimal>("percent_rank", SQLDataType.NUMERIC);
return new org.jooq.impl.Function<BigDecimal>(PERCENT_RANK, SQLDataType.NUMERIC);
}
/**
@ -18194,7 +18204,7 @@ public class DSL {
*/
@Support({ CUBRID, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE })
public static WindowOverStep<BigDecimal> cumeDist() {
return new org.jooq.impl.Function<BigDecimal>("cume_dist", SQLDataType.NUMERIC);
return new org.jooq.impl.Function<BigDecimal>(CUME_DIST, SQLDataType.NUMERIC);
}
/**
@ -18202,7 +18212,7 @@ public class DSL {
*/
@Support({ CUBRID, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE })
public static WindowOverStep<Integer> ntile(int number) {
return new org.jooq.impl.Function<Integer>("ntile", SQLDataType.INTEGER, inline(number));
return new org.jooq.impl.Function<Integer>(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 <T> WindowIgnoreNullsStep<T> firstValue(Field<T> field) {
return new org.jooq.impl.Function<T>("first_value", nullSafeDataType(field), nullSafe(field));
return new org.jooq.impl.Function<T>(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 <T> WindowIgnoreNullsStep<T> lastValue(Field<T> field) {
return new org.jooq.impl.Function<T>("last_value", nullSafeDataType(field), nullSafe(field));
return new org.jooq.impl.Function<T>(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 <T> WindowFromFirstLastStep<T> nthValue(Field<T> field, Field<Integer> nth) {
return new org.jooq.impl.Function<T>("nth_value", nullSafeDataType(field), nullSafe(field), nullSafe(nth));
return new org.jooq.impl.Function<T>(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 <T> WindowIgnoreNullsStep<T> lead(Field<T> field) {
return new LeadLag<T>("lead", nullSafe(field));
return new LeadLag<T>(LEAD, nullSafe(field));
}
/**
@ -18266,7 +18276,7 @@ public class DSL {
*/
@Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE })
public static <T> WindowIgnoreNullsStep<T> lead(Field<T> field, int offset) {
return new LeadLag<T>("lead", nullSafe(field), offset);
return new LeadLag<T>(LEAD, nullSafe(field), offset);
}
/**
@ -18286,7 +18296,7 @@ public class DSL {
*/
@Support({ CUBRID, FIREBIRD_3_0, H2, MYSQL_8_0, POSTGRES, SQLITE })
public static <T> WindowIgnoreNullsStep<T> lead(Field<T> field, int offset, Field<T> defaultValue) {
return new LeadLag<T>("lead", nullSafe(field), offset, nullSafe(defaultValue));
return new LeadLag<T>(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 <T> WindowIgnoreNullsStep<T> lag(Field<T> field) {
return new LeadLag<T>("lag", nullSafe(field));
return new LeadLag<T>(LAG, nullSafe(field));
}
/**
@ -18302,7 +18312,7 @@ public class DSL {
*/
@Support({ CUBRID, FIREBIRD_3_0, H2, MARIADB, MYSQL_8_0, POSTGRES, SQLITE })
public static <T> WindowIgnoreNullsStep<T> lag(Field<T> field, int offset) {
return new LeadLag<T>("lag", nullSafe(field), offset);
return new LeadLag<T>(LAG, nullSafe(field), offset);
}
/**
@ -18322,7 +18332,7 @@ public class DSL {
*/
@Support({ CUBRID, FIREBIRD_3_0, H2, MYSQL_8_0, POSTGRES, SQLITE })
public static <T> WindowIgnoreNullsStep<T> lag(Field<T> field, int offset, Field<T> defaultValue) {
return new LeadLag<T>("lag", nullSafe(field), offset, nullSafe(defaultValue));
return new LeadLag<T>(LAG, nullSafe(field), offset, nullSafe(defaultValue));
}
// -------------------------------------------------------------------------

View File

@ -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<T> extends AbstractField<T> 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")

View File

@ -54,12 +54,12 @@ final class LeadLag<T> extends Function<T> {
*/
private static final long serialVersionUID = 7292087943334025737L;
private final String function;
private final Term function;
private final Field<T> field;
private final int offset;
private final Field<T> defaultValue;
LeadLag(String function, Field<T> field) {
LeadLag(Term function, Field<T> field) {
super(function, field.getDataType(), field);
this.function = function;
@ -68,7 +68,7 @@ final class LeadLag<T> extends Function<T> {
this.defaultValue = null;
}
LeadLag(String function, Field<T> field, int offset) {
LeadLag(Term function, Field<T> field, int offset) {
super(function, field.getDataType(), field, inline(offset));
this.function = function;
@ -77,7 +77,7 @@ final class LeadLag<T> extends Function<T> {
this.defaultValue = null;
}
LeadLag(String function, Field<T> field, int offset, Field<T> defaultValue) {
LeadLag(Term function, Field<T> field, int offset, Field<T> defaultValue) {
super(function, field.getDataType(), field, inline(offset), defaultValue);
this.function = function;

View File

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

View File

@ -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,
}
/**

View File

@ -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<SQLDialect> OMIT_PARTITION_BY_ONE = EnumSet.of(CUBRID, MYSQL, SQLITE);
private static final long serialVersionUID = 2996016924769376361L;
private static final EnumSet<SQLDialect> OMIT_PARTITION_BY_ONE = EnumSet.of(CUBRID, MYSQL, SQLITE);
private static final EnumSet<SQLDialect> REQUIRES_ORDER_BY_IN_RANKING = EnumSet.of(H2);
private final WindowDefinitionImpl windowDefinition;
private final QueryPartList<Field<?>> 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);