[jOOQ/jOOQ#9930] Extracted AbstractAggregateFunction

This commit is contained in:
Lukas Eder 2020-03-10 14:34:25 +01:00
parent 0879b28c3b
commit c7e1389245
10 changed files with 773 additions and 465 deletions

View File

@ -0,0 +1,289 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
// ...
// ...
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.POSTGRES;
// ...
import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.impl.DSL.condition;
import static org.jooq.impl.DSL.one;
import static org.jooq.impl.Keywords.K_DISTINCT;
import static org.jooq.impl.Keywords.K_FILTER;
import static org.jooq.impl.Keywords.K_WHERE;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import org.jooq.AggregateFilterStep;
import org.jooq.AggregateFunction;
import org.jooq.ArrayAggOrderByStep;
import org.jooq.Condition;
import org.jooq.Context;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Name;
import org.jooq.OrderField;
import org.jooq.OrderedAggregateFunction;
// ...
import org.jooq.QueryPart;
import org.jooq.SQL;
import org.jooq.SQLDialect;
import org.jooq.WindowBeforeOverStep;
/**
* @author Lukas Eder
*/
abstract class AbstractAggregateFunction<T>
extends AbstractWindowFunction<T>
implements
AggregateFunction<T>,
OrderedAggregateFunction<T>,
ArrayAggOrderByStep<T> {
/**
* Generated UID
*/
private static final long serialVersionUID = -8613744948308064895L;
private static final Set<SQLDialect> SUPPORT_FILTER = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, SQLITE);
private static final Set<SQLDialect> SUPPORT_DISTINCT_RVE = SQLDialect.supportedBy(H2, POSTGRES);
static final Field<Integer> ASTERISK = DSL.field("*", Integer.class);
// Other attributes
final QueryPartList<Field<?>> arguments;
final boolean distinct;
Condition filter;
// Other attributes
SortFieldList withinGroupOrderBy;
SortFieldList keepDenseRankOrderBy;
boolean first;
AbstractAggregateFunction(boolean distinct, Name name, DataType<T> type, Field<?>... arguments) {
super(name, type);
this.distinct = distinct;
this.arguments = new QueryPartList<>(arguments);
}
// -------------------------------------------------------------------------
// XXX QueryPart API
// -------------------------------------------------------------------------
final void acceptArguments(Context<?> ctx) {
ctx.sql(getName());
ctx.sql('(');
acceptArguments0(ctx);
ctx.sql(')');
}
final void acceptArguments0(Context<?> ctx) {
acceptArguments1(ctx, arguments);
}
final void acceptArguments1(Context<?> ctx, QueryPartList<Field<?>> args) {
if (distinct) {
ctx.visit(K_DISTINCT).sql(' ');
// [#2883][#9109] PostgreSQL and H2 can use the DISTINCT keyword with formal row value expressions.
if (args.size() > 1 && SUPPORT_DISTINCT_RVE.contains(ctx.family()))
ctx.sql('(');
}
if (!args.isEmpty()) {
if (filter == null || SUPPORT_FILTER.contains(ctx.dialect())) {
ctx.visit(args);
}
else {
QueryPartList<Field<?>> expressions = new QueryPartList<>();
for (Field<?> argument : args)
expressions.add(DSL.when(filter, argument == ASTERISK ? one() : argument));
ctx.visit(expressions);
}
}
if (distinct)
if (args.size() > 1 && SUPPORT_DISTINCT_RVE.contains(ctx.family()))
ctx.sql(')');
}
final void acceptFilterClause(Context<?> ctx) {
if (filter != null && SUPPORT_FILTER.contains(ctx.dialect()))
ctx.sql(' ')
.visit(K_FILTER)
.sql(" (")
.visit(K_WHERE)
.sql(' ')
.visit(filter)
.sql(')');
}
// -------------------------------------------------------------------------
// XXX Aggregate function API
// -------------------------------------------------------------------------
final QueryPartList<Field<?>> getArguments() {
return arguments;
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Condition c) {
filter = c;
return this;
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Condition... conditions) {
return filterWhere(Arrays.asList(conditions));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Collection<? extends Condition> conditions) {
ConditionProviderImpl c = new ConditionProviderImpl();
c.addConditions(conditions);
return filterWhere(c);
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Field<Boolean> field) {
return filterWhere(condition(field));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Boolean field) {
return filterWhere(condition(field));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(SQL sql) {
return filterWhere(condition(sql));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(String sql) {
return filterWhere(condition(sql));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(String sql, Object... bindings) {
return filterWhere(condition(sql, bindings));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(String sql, QueryPart... parts) {
return filterWhere(condition(sql, parts));
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(OrderField<?>... fields) {
return withinGroupOrderBy(Arrays.asList(fields));
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(Collection<? extends OrderField<?>> fields) {
if (withinGroupOrderBy == null)
withinGroupOrderBy = new SortFieldList();
withinGroupOrderBy.addAll(Tools.sortFields(fields));
return this;
}
@Override
public final AbstractAggregateFunction<T> orderBy(OrderField<?>... fields) {
if (windowSpecification != null)
windowSpecification.orderBy(fields);
else
withinGroupOrderBy(fields);
return this;
}
@Override
public final AbstractAggregateFunction<T> orderBy(Collection<? extends OrderField<?>> fields) {
if (windowSpecification != null)
windowSpecification.orderBy(fields);
else
withinGroupOrderBy(fields);
return this;
}
}

View File

@ -55,7 +55,7 @@ final class CountTable extends DefaultAggregateFunction<Integer> {
private final boolean distinct;
CountTable(Table<?> table, boolean distinct) {
super("count", distinct, SQLDataType.INTEGER, DSL.field(DSL.name(table.getName())));
super(distinct, "count", SQLDataType.INTEGER, DSL.field(DSL.name(table.getName())));
this.table = table;
this.distinct = distinct;
@ -78,7 +78,7 @@ final class CountTable extends DefaultAggregateFunction<Integer> {
UniqueKey<?> pk = table.getPrimaryKey();
if (pk != null)
ctx.visit(new DefaultAggregateFunction<>("count", distinct, SQLDataType.INTEGER, table.fields(pk.getFieldsArray())));
ctx.visit(new DefaultAggregateFunction<>(distinct, "count", SQLDataType.INTEGER, table.fields(pk.getFieldsArray())));
else
super.accept(ctx);

View File

@ -18220,7 +18220,7 @@ public class DSL {
*/
@Support
public static AggregateFunction<Integer> countDistinct(Field<?> field) {
return new DefaultAggregateFunction<>("count", true, SQLDataType.INTEGER, nullSafe(field));
return new DefaultAggregateFunction<>(true, "count", SQLDataType.INTEGER, nullSafe(field));
}
/**
@ -18228,7 +18228,7 @@ public class DSL {
*/
@Support
public static AggregateFunction<Integer> countDistinct(SelectFieldOrAsterisk field) {
return new DefaultAggregateFunction<>("count", true, SQLDataType.INTEGER, field("{0}", field));
return new DefaultAggregateFunction<>(true, "count", SQLDataType.INTEGER, field("{0}", field));
}
/**
@ -18255,7 +18255,7 @@ public class DSL {
@Support({ H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static AggregateFunction<Integer> countDistinct(Field<?>... fields) {
fields = nullSafe(fields);
return fields.length == 0 ? countDistinct(asterisk()) : new DefaultAggregateFunction<>("count", true, SQLDataType.INTEGER, fields);
return fields.length == 0 ? countDistinct(asterisk()) : new DefaultAggregateFunction<>(true, "count", SQLDataType.INTEGER, fields);
}
/**
@ -18323,7 +18323,7 @@ public class DSL {
*/
@Support({ HSQLDB, POSTGRES })
public static <T> ArrayAggOrderByStep<T[]> arrayAggDistinct(Field<T> field) {
return new DefaultAggregateFunction<>(Term.ARRAY_AGG, true, field.getDataType().getArrayDataType(), nullSafe(field));
return new DefaultAggregateFunction<>(true, Term.ARRAY_AGG, field.getDataType().getArrayDataType(), nullSafe(field));
}
@ -18483,7 +18483,7 @@ public class DSL {
*/
@Support
public static <T> AggregateFunction<T> maxDistinct(Field<T> field) {
return new DefaultAggregateFunction<>("max", true, nullSafeDataType(field), nullSafe(field));
return new DefaultAggregateFunction<>(true, "max", nullSafeDataType(field), nullSafe(field));
}
/**
@ -18499,7 +18499,7 @@ public class DSL {
*/
@Support
public static <T> AggregateFunction<T> minDistinct(Field<T> field) {
return new DefaultAggregateFunction<>("min", true, nullSafeDataType(field), nullSafe(field));
return new DefaultAggregateFunction<>(true, "min", nullSafeDataType(field), nullSafe(field));
}
/**
@ -18515,7 +18515,7 @@ public class DSL {
*/
@Support
public static AggregateFunction<BigDecimal> sumDistinct(Field<? extends Number> field) {
return new DefaultAggregateFunction<>("sum", true, SQLDataType.NUMERIC, nullSafe(field));
return new DefaultAggregateFunction<>(true, "sum", SQLDataType.NUMERIC, nullSafe(field));
}
/**
@ -18533,7 +18533,7 @@ public class DSL {
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static AggregateFunction<BigDecimal> product(Field<? extends Number> field) {
return new DefaultAggregateFunction<>(Term.PRODUCT, SQLDataType.NUMERIC, nullSafe(field));
return new Product(false, nullSafe(field));
}
/**
@ -18551,7 +18551,7 @@ public class DSL {
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static AggregateFunction<BigDecimal> productDistinct(Field<? extends Number> field) {
return new DefaultAggregateFunction<>(Term.PRODUCT, true, SQLDataType.NUMERIC, nullSafe(field));
return new Product(true, nullSafe(field));
}
/**
@ -18567,7 +18567,7 @@ public class DSL {
*/
@Support
public static AggregateFunction<BigDecimal> avgDistinct(Field<? extends Number> field) {
return new DefaultAggregateFunction<>("avg", true, SQLDataType.NUMERIC, nullSafe(field));
return new DefaultAggregateFunction<>(true, "avg", SQLDataType.NUMERIC, nullSafe(field));
}
/**
@ -18583,7 +18583,7 @@ public class DSL {
*/
@Support({ CUBRID, H2, HSQLDB, MARIADB, POSTGRES })
public static AggregateFunction<BigDecimal> median(Field<? extends Number> field) {
return new DefaultAggregateFunction<>(Term.MEDIAN, SQLDataType.NUMERIC, nullSafe(field));
return new Median(nullSafe(field));
}
/**
@ -18772,7 +18772,7 @@ public class DSL {
*/
@Support({ CUBRID, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static OrderedAggregateFunction<String> listAgg(Field<?> field) {
return new DefaultAggregateFunction<>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field));
return new ListAgg(false, nullSafe(field));
}
/**
@ -18794,7 +18794,7 @@ public class DSL {
*/
@Support({ CUBRID, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static OrderedAggregateFunction<String> listAgg(Field<?> field, String separator) {
return new DefaultAggregateFunction<>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), inline(separator));
return new ListAgg(false, nullSafe(field), inline(separator));
}
/**

View File

@ -40,73 +40,28 @@ package org.jooq.impl;
// ...
// ...
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
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 static org.jooq.impl.DSL.choose;
import static org.jooq.impl.DSL.condition;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.mode;
import static org.jooq.impl.DSL.one;
import static org.jooq.impl.DSL.percentileCont;
import static org.jooq.impl.DSL.when;
import static org.jooq.impl.DSL.zero;
import static org.jooq.impl.Keywords.F_CONCAT;
import static org.jooq.impl.Keywords.F_SUBSTR;
import static org.jooq.impl.Keywords.F_XMLAGG;
import static org.jooq.impl.Keywords.F_XMLSERIALIZE;
import static org.jooq.impl.Keywords.F_XMLTEXT;
import static org.jooq.impl.Keywords.K_AS;
import static org.jooq.impl.Keywords.K_DENSE_RANK;
import static org.jooq.impl.Keywords.K_DISTINCT;
import static org.jooq.impl.Keywords.K_FILTER;
import static org.jooq.impl.Keywords.K_FIRST;
import static org.jooq.impl.Keywords.K_KEEP;
import static org.jooq.impl.Keywords.K_LAST;
import static org.jooq.impl.Keywords.K_NULL;
import static org.jooq.impl.Keywords.K_ORDER_BY;
import static org.jooq.impl.Keywords.K_SEPARATOR;
import static org.jooq.impl.Keywords.K_WHERE;
import static org.jooq.impl.Keywords.K_WITHIN_GROUP;
import static org.jooq.impl.SQLDataType.NUMERIC;
import static org.jooq.impl.Term.ARRAY_AGG;
import static org.jooq.impl.Term.LIST_AGG;
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.Tools.castIfNeeded;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import org.jooq.AggregateFilterStep;
import org.jooq.AggregateFunction;
import org.jooq.ArrayAggOrderByStep;
import org.jooq.Condition;
import org.jooq.Context;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.OrderField;
import org.jooq.OrderedAggregateFunction;
// ...
import org.jooq.QueryPart;
import org.jooq.SQL;
import org.jooq.Name;
import org.jooq.SQLDialect;
import org.jooq.WindowBeforeOverStep;
// ...
/**
* A field that handles built-in functions, aggregate functions, and window
@ -114,64 +69,45 @@ import org.jooq.WindowBeforeOverStep;
*
* @author Lukas Eder
*/
class DefaultAggregateFunction<T> extends AbstractWindowFunction<T> implements
// Cascading interface implementations for aggregate function behaviour
OrderedAggregateFunction<T>,
ArrayAggOrderByStep<T>,
AggregateFunction<T> {
class DefaultAggregateFunction<T> extends AbstractAggregateFunction<T> {
private static final long serialVersionUID = 347252741712134044L;
private static final Set<SQLDialect> SUPPORT_ARRAY_AGG = SQLDialect.supportedBy(HSQLDB, POSTGRES);
private static final Set<SQLDialect> SUPPORT_GROUP_CONCAT = SQLDialect.supportedBy(CUBRID, H2, HSQLDB, MARIADB, MYSQL, SQLITE);
private static final Set<SQLDialect> SUPPORT_STRING_AGG = SQLDialect.supportedBy(POSTGRES);
private static final Set<SQLDialect> SUPPORT_FILTER = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, SQLITE);
private static final Set<SQLDialect> SUPPORT_DISTINCT_RVE = SQLDialect.supportedBy(H2, POSTGRES);
static final Field<Integer> ASTERISK = DSL.field("*", Integer.class);
// Mutually exclusive attributes: super.getName(), this.term
private final Term term;
// Other attributes
private final QueryPartList<Field<?>> arguments;
private final boolean distinct;
private SortFieldList withinGroupOrderBy;
private SortFieldList keepDenseRankOrderBy;
private Condition filter;
private boolean first;
// -------------------------------------------------------------------------
// XXX Constructors
// -------------------------------------------------------------------------
DefaultAggregateFunction(String name, DataType<T> type, Field<?>... arguments) {
this(name, false, type, arguments);
this(false, name, type, arguments);
}
DefaultAggregateFunction(Name name, DataType<T> type, Field<?>... arguments) {
this(false, name, type, arguments);
}
DefaultAggregateFunction(Term term, DataType<T> type, Field<?>... arguments) {
this(term, false, type, arguments);
this(false, term, type, arguments);
}
DefaultAggregateFunction(String name, boolean distinct, DataType<T> type, Field<?>... arguments) {
super(DSL.name(name), type);
DefaultAggregateFunction(boolean distinct, String name, DataType<T> type, Field<?>... arguments) {
this(distinct, DSL.name(name), type, arguments);
}
DefaultAggregateFunction(boolean distinct, Name name, DataType<T> type, Field<?>... arguments) {
super(distinct, name, type, arguments);
this.term = null;
this.distinct = distinct;
this.arguments = new QueryPartList<>(arguments);
}
DefaultAggregateFunction(Term term, boolean distinct, DataType<T> type, Field<?>... arguments) {
super(term.toName(), type);
DefaultAggregateFunction(boolean distinct, Term term, DataType<T> type, Field<?>... arguments) {
super(distinct, term.toName(), type, arguments);
this.term = term;
this.distinct = distinct;
this.arguments = new QueryPartList<>(arguments);
}
// -------------------------------------------------------------------------
@ -181,162 +117,29 @@ class DefaultAggregateFunction<T> extends AbstractWindowFunction<T> implements
@Override
public /* final */ void accept(Context<?> ctx) {
if (term == ARRAY_AGG && SUPPORT_ARRAY_AGG.contains(ctx.dialect())) {
toSQLGroupConcat(ctx);
toSQLFilterClause(ctx);
toSQLArrayAgg(ctx);
acceptFilterClause(ctx);
acceptOverClause(ctx);
}
else if (term == LIST_AGG && SUPPORT_GROUP_CONCAT.contains(ctx.dialect())) {
toSQLGroupConcat(ctx);
}
else if (term == LIST_AGG && SUPPORT_STRING_AGG .contains(ctx.dialect())) {
toSQLStringAgg(ctx);
toSQLFilterClause(ctx);
acceptOverClause(ctx);
}
else if (term == MODE && ( ctx.family() == H2 || ctx.family() == POSTGRES)) {
ctx.visit(mode().withinGroupOrderBy(DSL.field("{0}", arguments.get(0))));
}
else if (term == MEDIAN && ( ctx.family() == POSTGRES)) {
Field<?>[] fields = new Field[arguments.size()];
for (int i = 0; i < fields.length; i++)
fields[i] = DSL.field("{0}", arguments.get(i));
ctx.visit(percentileCont(new BigDecimal("0.5")).withinGroupOrderBy(fields));
}
else if (term == PRODUCT) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Field<Integer> f = (Field) DSL.field("{0}", arguments.get(0).getDataType(), arguments.get(0));
final Field<Integer> negatives = DSL.when(f.lt(zero()), inline(-1));
@SuppressWarnings("serial")
Field<BigDecimal> negativesSum = new CustomField<BigDecimal>("sum", NUMERIC) {
@Override
public void accept(Context<?> c) {
c.visit(distinct
? DSL.sumDistinct(negatives)
: DSL.sum(negatives));
toSQLFilterClause(c);
acceptOverClause(c);
}
};
@SuppressWarnings("serial")
Field<BigDecimal> zerosSum = new CustomField<BigDecimal>("sum", NUMERIC) {
@Override
public void accept(Context<?> c) {
c.visit(DSL.sum(choose(f).when(zero(), one())));
toSQLFilterClause(c);
acceptOverClause(c);
}
};
@SuppressWarnings("serial")
Field<BigDecimal> logarithmsSum = new CustomField<BigDecimal>("sum", NUMERIC) {
@Override
public void accept(Context<?> c) {
Field<Integer> abs = DSL.abs(DSL.nullif(f, zero()));
Field<BigDecimal> ln =
DSL.ln(abs);
c.visit(distinct
? DSL.sumDistinct(ln)
: DSL.sum(ln));
toSQLFilterClause(c);
acceptOverClause(c);
}
};
ctx.visit(
when(zerosSum.gt(inline(BigDecimal.ZERO)), zero())
.when(negativesSum.mod(inline(2)).lt(inline(BigDecimal.ZERO)), inline(-1))
.otherwise(one()).mul(DSL.exp(logarithmsSum))
);
}
else {
toSQLArguments(ctx);
toSQLKeepDenseRankOrderByClause(ctx);
toSQLWithinGroupClause(ctx);
toSQLFilterClause(ctx);
acceptFilterClause(ctx);
acceptOverClause(ctx);
}
}
/**
* [#1275] <code>LIST_AGG</code> emulation for Postgres, Sybase
* <code>ARRAY_AGG</code>
*/
final void toSQLStringAgg(Context<?> ctx) {
final void toSQLArrayAgg(Context<?> ctx) {
toSQLFunctionName(ctx);
ctx.sql('(');
if (distinct)
ctx.visit(K_DISTINCT).sql(' ');
// The explicit cast is needed in Postgres
ctx.visit(castIfNeeded((Field<?>) arguments.get(0), String.class));
if (arguments.size() > 1)
ctx.sql(", ").visit(arguments.get(1));
else
ctx.sql(", ''");
acceptArguments1(ctx, new QueryPartList<>(Arrays.asList(arguments.get(0))));
if (!Tools.isEmpty(withinGroupOrderBy))
ctx.sql(' ').visit(K_ORDER_BY).sql(' ')
@ -345,39 +148,6 @@ class DefaultAggregateFunction<T> extends AbstractWindowFunction<T> implements
ctx.sql(')');
}
/**
* [#1273] <code>LIST_AGG</code> emulation for MySQL
*/
final void toSQLGroupConcat(Context<?> ctx) {
toSQLFunctionName(ctx);
ctx.sql('(');
toSQLArguments1(ctx, new QueryPartList<>(Arrays.asList(arguments.get(0))));
if (!Tools.isEmpty(withinGroupOrderBy))
ctx.sql(' ').visit(K_ORDER_BY).sql(' ')
.visit(withinGroupOrderBy);
if (arguments.size() > 1)
if (ctx.family() == SQLITE)
ctx.sql(", ").visit(arguments.get(1));
else
ctx.sql(' ').visit(K_SEPARATOR).sql(' ')
.visit(arguments.get(1));
ctx.sql(')');
}
final void toSQLFilterClause(Context<?> ctx) {
if (filter != null && SUPPORT_FILTER.contains(ctx.dialect()))
ctx.sql(' ')
.visit(K_FILTER)
.sql(" (")
.visit(K_WHERE)
.sql(' ')
.visit(filter)
.sql(')');
}
/**
* Render <code>KEEP (DENSE_RANK [FIRST | LAST] ORDER BY {...})</code> clause
*/
@ -415,172 +185,14 @@ class DefaultAggregateFunction<T> extends AbstractWindowFunction<T> implements
final void toSQLArguments(Context<?> ctx) {
toSQLFunctionName(ctx);
ctx.sql('(');
toSQLArguments0(ctx);
acceptArguments0(ctx);
ctx.sql(')');
}
final void toSQLArguments0(Context<?> ctx) {
toSQLArguments1(ctx, arguments);
}
final void toSQLArguments1(Context<?> ctx, QueryPartList<Field<?>> args) {
if (distinct) {
ctx.visit(K_DISTINCT).sql(' ');
// [#2883][#9109] PostgreSQL and H2 can use the DISTINCT keyword with formal row value expressions.
if (args.size() > 1 && SUPPORT_DISTINCT_RVE.contains(ctx.family()))
ctx.sql('(');
}
if (!args.isEmpty()) {
if (filter == null || SUPPORT_FILTER.contains(ctx.dialect())) {
ctx.visit(args);
}
else {
QueryPartList<Field<?>> expressions = new QueryPartList<>();
for (Field<?> argument : args)
expressions.add(DSL.when(filter, argument == ASTERISK ? one() : argument));
ctx.visit(expressions);
}
}
if (distinct)
if (args.size() > 1 && SUPPORT_DISTINCT_RVE.contains(ctx.family()))
ctx.sql(')');
}
final void toSQLFunctionName(Context<?> ctx) {
if (term != null)
ctx.sql(term.translate(ctx.dialect()));
else
ctx.sql(getName());
}
// -------------------------------------------------------------------------
// XXX aggregate and window function fluent API methods
// -------------------------------------------------------------------------
final QueryPartList<Field<?>> getArguments() {
return arguments;
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(OrderField<?>... fields) {
return withinGroupOrderBy(Arrays.asList(fields));
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(Collection<? extends OrderField<?>> fields) {
if (withinGroupOrderBy == null)
withinGroupOrderBy = new SortFieldList();
withinGroupOrderBy.addAll(Tools.sortFields(fields));
return this;
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Condition c) {
filter = c;
return this;
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Condition... conditions) {
return filterWhere(Arrays.asList(conditions));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Collection<? extends Condition> conditions) {
ConditionProviderImpl c = new ConditionProviderImpl();
c.addConditions(conditions);
return filterWhere(c);
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Field<Boolean> field) {
return filterWhere(condition(field));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(Boolean field) {
return filterWhere(condition(field));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(SQL sql) {
return filterWhere(condition(sql));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(String sql) {
return filterWhere(condition(sql));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(String sql, Object... bindings) {
return filterWhere(condition(sql, bindings));
}
@Override
public final WindowBeforeOverStep<T> filterWhere(String sql, QueryPart... parts) {
return filterWhere(condition(sql, parts));
}
@Override
public final DefaultAggregateFunction<T> orderBy(OrderField<?>... fields) {
if (windowSpecification != null)
windowSpecification.orderBy(fields);
else
withinGroupOrderBy(fields);
return this;
}
@Override
public final DefaultAggregateFunction<T> orderBy(Collection<? extends OrderField<?>> fields) {
if (windowSpecification != null)
windowSpecification.orderBy(fields);
else
withinGroupOrderBy(fields);
return this;
}
}

View File

@ -89,12 +89,12 @@ final class GroupConcat extends AbstractFunction<String> implements GroupConcatO
@Override
final Field<String> getFunction0(Configuration configuration) {
DefaultAggregateFunction<String> result;
ListAgg result;
if (separator == null)
result = new DefaultAggregateFunction<>(Term.LIST_AGG, distinct, SQLDataType.VARCHAR, field, inline(","));
result = new ListAgg(distinct, field, inline(","));
else
result = new DefaultAggregateFunction<>(Term.LIST_AGG, distinct, SQLDataType.VARCHAR, field, inline(separator));
result = new ListAgg(distinct, field, inline(separator));

View File

@ -0,0 +1,231 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
// ...
// ...
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
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 static org.jooq.impl.Keywords.F_CONCAT;
import static org.jooq.impl.Keywords.F_SUBSTR;
import static org.jooq.impl.Keywords.F_XMLAGG;
import static org.jooq.impl.Keywords.F_XMLSERIALIZE;
import static org.jooq.impl.Keywords.F_XMLTEXT;
import static org.jooq.impl.Keywords.K_AS;
import static org.jooq.impl.Keywords.K_DISTINCT;
import static org.jooq.impl.Keywords.K_ORDER_BY;
import static org.jooq.impl.Keywords.K_SEPARATOR;
import static org.jooq.impl.Names.N_GROUP_CONCAT;
import static org.jooq.impl.Names.N_LIST;
import static org.jooq.impl.Names.N_LISTAGG;
import static org.jooq.impl.Names.N_STRING_AGG;
import static org.jooq.impl.Tools.castIfNeeded;
import java.util.Arrays;
import java.util.Set;
import org.jooq.Context;
import org.jooq.Field;
// ...
import org.jooq.SQLDialect;
// ...
/**
* @author Lukas Eder
*/
final class ListAgg extends DefaultAggregateFunction<String> {
/**
* Generated UID
*/
private static final long serialVersionUID = -1760389929938136896L;
private static final Set<SQLDialect> SUPPORT_GROUP_CONCAT = SQLDialect.supportedBy(CUBRID, H2, HSQLDB, MARIADB, MYSQL, SQLITE);
private static final Set<SQLDialect> SUPPORT_STRING_AGG = SQLDialect.supportedBy(POSTGRES);
ListAgg(boolean distinct, Field<?> arg) {
super(distinct, N_LISTAGG, SQLDataType.VARCHAR, arg);
}
ListAgg(boolean distinct, Field<?> arg, Field<String> separator) {
super(distinct, N_LISTAGG, SQLDataType.VARCHAR, arg, separator);
}
// -------------------------------------------------------------------------
// XXX QueryPart API
// -------------------------------------------------------------------------
@Override
public final void accept(Context<?> ctx) {
if (SUPPORT_GROUP_CONCAT.contains(ctx.dialect())) {
acceptGroupConcat(ctx);
}
else if (SUPPORT_STRING_AGG.contains(ctx.dialect())) {
acceptStringAgg(ctx);
acceptFilterClause(ctx);
acceptOverClause(ctx);
}
else {
super.accept(ctx);
}
}
/**
* [#1273] <code>LIST_AGG</code> emulation for MySQL
*/
private final void acceptGroupConcat(Context<?> ctx) {
ctx.visit(N_GROUP_CONCAT).sql('(');
acceptArguments1(ctx, new QueryPartList<>(Arrays.asList(arguments.get(0))));
if (!Tools.isEmpty(withinGroupOrderBy))
ctx.sql(' ').visit(K_ORDER_BY).sql(' ')
.visit(withinGroupOrderBy);
if (arguments.size() > 1)
if (ctx.family() == SQLITE)
ctx.sql(", ").visit(arguments.get(1));
else
ctx.sql(' ').visit(K_SEPARATOR).sql(' ')
.visit(arguments.get(1));
ctx.sql(')');
}
/**
* [#1275] <code>LIST_AGG</code> emulation for Postgres, Sybase
*/
private final void acceptStringAgg(Context<?> ctx) {
switch (ctx.family()) {
default:
ctx.visit(N_STRING_AGG);
break;
}
ctx.sql('(');
if (distinct)
ctx.visit(K_DISTINCT).sql(' ');
// The explicit cast is needed in Postgres
ctx.visit(castIfNeeded((Field<?>) arguments.get(0), String.class));
if (arguments.size() > 1)
ctx.sql(", ").visit(arguments.get(1));
else
ctx.sql(", ''");
if (!Tools.isEmpty(withinGroupOrderBy))
ctx.sql(' ').visit(K_ORDER_BY).sql(' ')
.visit(withinGroupOrderBy);
ctx.sql(')');
}
}

View File

@ -0,0 +1,79 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
// ...
import static org.jooq.SQLDialect.POSTGRES;
import static org.jooq.impl.DSL.percentileCont;
import static org.jooq.impl.Names.N_MEDIAN;
import java.math.BigDecimal;
import java.util.Set;
import org.jooq.Context;
import org.jooq.Field;
import org.jooq.SQLDialect;
/**
* @author Lukas Eder
*/
final class Median extends DefaultAggregateFunction<BigDecimal> {
/**
* Generated UID
*/
private static final long serialVersionUID = -7378732863724089028L;
private static final Set<SQLDialect> EMULATE_WITH_PERCENTILES = SQLDialect.supportedBy(POSTGRES);
Median(Field<? extends Number> arg) {
super(false, N_MEDIAN, SQLDataType.NUMERIC, arg);
}
@Override
public final void accept(Context<?> ctx) {
if (EMULATE_WITH_PERCENTILES.contains(ctx.dialect())) {
Field<?>[] fields = new Field[arguments.size()];
for (int i = 0; i < fields.length; i++)
fields[i] = DSL.field("{0}", arguments.get(i));
ctx.visit(percentileCont(new BigDecimal("0.5")).withinGroupOrderBy(fields));
}
else
super.accept(ctx);
}
}

View File

@ -78,15 +78,19 @@ final class Names {
static final Name N_FLOOR = DSL.name("floor");
static final Name N_FUNCTION = DSL.name("function");
static final Name N_GENERATE_SERIES = DSL.name("generate_series");
static final Name N_GROUP_CONCAT = DSL.unquotedName("group_concat");
static final Name N_IIF = DSL.name("iif");
static final Name N_JOIN = DSL.name("join");
static final Name N_JSON_ARRAY = DSL.name("json_array");
static final Name N_JSON_OBJECT = DSL.name("json_object");
static final Name N_LEFT = DSL.name("left");
static final Name N_LIST = DSL.unquotedName("list");
static final Name N_LISTAGG = DSL.unquotedName("listagg");
static final Name N_LOWER = DSL.name("lower");
static final Name N_LPAD = DSL.name("lpad");
static final Name N_LTRIM = DSL.name("ltrim");
static final Name N_MD5 = DSL.name("md5");
static final Name N_MEDIAN = DSL.name("median");
static final Name N_MOD = DSL.name("mod");
static final Name N_NEXTVAL = DSL.name("nextval");
static final Name N_NOT = DSL.name("not");
@ -99,6 +103,7 @@ final class Names {
static final Name N_POSITION = DSL.name("position");
static final Name N_POWER = DSL.name("power");
static final Name N_PRIOR = DSL.name("prior");
static final Name N_PRODUCT = DSL.unquotedName("product");
static final Name N_RANDOM = DSL.name("rand");
static final Name N_REVERSE = DSL.name("reverse");
static final Name N_RIGHT = DSL.name("right");
@ -114,6 +119,7 @@ final class Names {
static final Name N_SINH = DSL.name("sinh");
static final Name N_SPACE = DSL.name("space");
static final Name N_SQRT = DSL.name("sqrt");
static final Name N_STRING_AGG = DSL.unquotedName("string_agg");
static final Name N_SUBSTRING = DSL.name("substring");
static final Name N_SYSTEM_TIME = DSL.unquotedName("system_time");
static final Name N_T = DSL.name("t");

View File

@ -0,0 +1,127 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
// ...
import static org.jooq.impl.DSL.choose;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.one;
import static org.jooq.impl.DSL.when;
import static org.jooq.impl.DSL.zero;
import static org.jooq.impl.Names.N_PRODUCT;
import static org.jooq.impl.SQLDataType.NUMERIC;
import java.math.BigDecimal;
import org.jooq.Context;
import org.jooq.Field;
/**
* @author Lukas Eder
*/
final class Product extends AbstractAggregateFunction<BigDecimal> {
/**
* Generated UID
*/
private static final long serialVersionUID = 1027749554935573353L;
Product(boolean distinct, Field<?>... arguments) {
super(distinct, N_PRODUCT, NUMERIC, arguments);
}
@Override
public final void accept(Context<?> ctx) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Field<Integer> f = (Field) DSL.field("{0}", arguments.get(0).getDataType(), arguments.get(0));
final Field<Integer> negatives = DSL.when(f.lt(zero()), inline(-1));
@SuppressWarnings("serial")
Field<BigDecimal> negativesSum = new CustomField<BigDecimal>("sum", NUMERIC) {
@Override
public void accept(Context<?> c) {
c.visit(distinct
? DSL.sumDistinct(negatives)
: DSL.sum(negatives));
acceptFilterClause(c);
acceptOverClause(c);
}
};
@SuppressWarnings("serial")
Field<BigDecimal> zerosSum = new CustomField<BigDecimal>("sum", NUMERIC) {
@Override
public void accept(Context<?> c) {
c.visit(DSL.sum(choose(f).when(zero(), one())));
acceptFilterClause(c);
acceptOverClause(c);
}
};
@SuppressWarnings("serial")
Field<BigDecimal> logarithmsSum = new CustomField<BigDecimal>("sum", NUMERIC) {
@Override
public void accept(Context<?> c) {
Field<Integer> abs = DSL.abs(DSL.nullif(f, zero()));
Field<BigDecimal> ln =
DSL.ln(abs);
c.visit(distinct
? DSL.sumDistinct(ln)
: DSL.sum(ln));
acceptFilterClause(c);
acceptOverClause(c);
}
};
ctx.visit(
when(zerosSum.gt(inline(BigDecimal.ZERO)), zero())
.when(negativesSum.mod(inline(2)).lt(inline(BigDecimal.ZERO)), inline(-1))
.otherwise(one()).mul(DSL.exp(logarithmsSum))
);
}
}

View File

@ -126,41 +126,6 @@ enum Term {
LIST_AGG {
@Override
public String translate(SQLDialect dialect) {
switch (dialect.family()) {
case CUBRID:
case H2:
case HSQLDB:
case MARIADB:
case MYSQL:
case SQLITE:
return "group_concat";
case POSTGRES:
return "string_agg";
}
return "listagg";
}
},
MEDIAN,
MODE {
@Override
public String translate(SQLDialect dialect) {
@ -201,7 +166,6 @@ enum Term {
return "octet_length";
}
},
PRODUCT,
STDDEV_POP {
@Override
public String translate(SQLDialect dialect) {