diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractAggregateFunction.java b/jOOQ/src/main/java/org/jooq/impl/AbstractAggregateFunction.java new file mode 100644 index 0000000000..c59e140e1a --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractAggregateFunction.java @@ -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 +extends AbstractWindowFunction +implements + AggregateFunction, + OrderedAggregateFunction, + ArrayAggOrderByStep { + + /** + * Generated UID + */ + private static final long serialVersionUID = -8613744948308064895L; + private static final Set SUPPORT_FILTER = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, SQLITE); + private static final Set SUPPORT_DISTINCT_RVE = SQLDialect.supportedBy(H2, POSTGRES); + + static final Field ASTERISK = DSL.field("*", Integer.class); + + // Other attributes + final QueryPartList> arguments; + final boolean distinct; + Condition filter; + + // Other attributes + SortFieldList withinGroupOrderBy; + SortFieldList keepDenseRankOrderBy; + boolean first; + + AbstractAggregateFunction(boolean distinct, Name name, DataType 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> 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> 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> getArguments() { + return arguments; + } + + @Override + public final WindowBeforeOverStep filterWhere(Condition c) { + filter = c; + return this; + } + + @Override + public final WindowBeforeOverStep filterWhere(Condition... conditions) { + return filterWhere(Arrays.asList(conditions)); + } + + @Override + public final WindowBeforeOverStep filterWhere(Collection conditions) { + ConditionProviderImpl c = new ConditionProviderImpl(); + c.addConditions(conditions); + return filterWhere(c); + } + + @Override + public final WindowBeforeOverStep filterWhere(Field field) { + return filterWhere(condition(field)); + } + + @Override + public final WindowBeforeOverStep filterWhere(Boolean field) { + return filterWhere(condition(field)); + } + + @Override + public final WindowBeforeOverStep filterWhere(SQL sql) { + return filterWhere(condition(sql)); + } + + @Override + public final WindowBeforeOverStep filterWhere(String sql) { + return filterWhere(condition(sql)); + } + + @Override + public final WindowBeforeOverStep filterWhere(String sql, Object... bindings) { + return filterWhere(condition(sql, bindings)); + } + + @Override + public final WindowBeforeOverStep filterWhere(String sql, QueryPart... parts) { + return filterWhere(condition(sql, parts)); + } + + + @Override + public final AggregateFunction withinGroupOrderBy(OrderField... fields) { + return withinGroupOrderBy(Arrays.asList(fields)); + } + + @Override + public final AggregateFunction withinGroupOrderBy(Collection> fields) { + if (withinGroupOrderBy == null) + withinGroupOrderBy = new SortFieldList(); + + withinGroupOrderBy.addAll(Tools.sortFields(fields)); + return this; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Override + public final AbstractAggregateFunction orderBy(OrderField... fields) { + if (windowSpecification != null) + windowSpecification.orderBy(fields); + else + withinGroupOrderBy(fields); + + return this; + } + + @Override + public final AbstractAggregateFunction orderBy(Collection> fields) { + if (windowSpecification != null) + windowSpecification.orderBy(fields); + else + withinGroupOrderBy(fields); + + return this; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/CountTable.java b/jOOQ/src/main/java/org/jooq/impl/CountTable.java index 6add494f35..cf000f2d5b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CountTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/CountTable.java @@ -55,7 +55,7 @@ final class CountTable extends DefaultAggregateFunction { 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 { 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); diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index f2437140d3..1cfbe92beb 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -18220,7 +18220,7 @@ public class DSL { */ @Support public static AggregateFunction 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 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 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 ArrayAggOrderByStep arrayAggDistinct(Field 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 AggregateFunction maxDistinct(Field 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 AggregateFunction minDistinct(Field 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 sumDistinct(Field 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 product(Field 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 productDistinct(Field 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 avgDistinct(Field 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 median(Field 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 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 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)); } /** diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultAggregateFunction.java b/jOOQ/src/main/java/org/jooq/impl/DefaultAggregateFunction.java index 5d63f53ced..06dd35b6b9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultAggregateFunction.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultAggregateFunction.java @@ -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 extends AbstractWindowFunction implements - - // Cascading interface implementations for aggregate function behaviour - OrderedAggregateFunction, - ArrayAggOrderByStep, - AggregateFunction { +class DefaultAggregateFunction extends AbstractAggregateFunction { private static final long serialVersionUID = 347252741712134044L; private static final Set SUPPORT_ARRAY_AGG = SQLDialect.supportedBy(HSQLDB, POSTGRES); - private static final Set SUPPORT_GROUP_CONCAT = SQLDialect.supportedBy(CUBRID, H2, HSQLDB, MARIADB, MYSQL, SQLITE); - private static final Set SUPPORT_STRING_AGG = SQLDialect.supportedBy(POSTGRES); - private static final Set SUPPORT_FILTER = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, SQLITE); - private static final Set SUPPORT_DISTINCT_RVE = SQLDialect.supportedBy(H2, POSTGRES); - - - - - - static final Field ASTERISK = DSL.field("*", Integer.class); // Mutually exclusive attributes: super.getName(), this.term private final Term term; - // Other attributes - private final QueryPartList> arguments; - private final boolean distinct; - private SortFieldList withinGroupOrderBy; - private SortFieldList keepDenseRankOrderBy; - private Condition filter; - private boolean first; - // ------------------------------------------------------------------------- // XXX Constructors // ------------------------------------------------------------------------- DefaultAggregateFunction(String name, DataType type, Field... arguments) { - this(name, false, type, arguments); + this(false, name, type, arguments); + } + + DefaultAggregateFunction(Name name, DataType type, Field... arguments) { + this(false, name, type, arguments); } DefaultAggregateFunction(Term term, DataType type, Field... arguments) { - this(term, false, type, arguments); + this(false, term, type, arguments); } - DefaultAggregateFunction(String name, boolean distinct, DataType type, Field... arguments) { - super(DSL.name(name), type); + DefaultAggregateFunction(boolean distinct, String name, DataType type, Field... arguments) { + this(distinct, DSL.name(name), type, arguments); + } + + DefaultAggregateFunction(boolean distinct, Name name, DataType 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 type, Field... arguments) { - super(term.toName(), type); + DefaultAggregateFunction(boolean distinct, Term term, DataType 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 extends AbstractWindowFunction 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 f = (Field) DSL.field("{0}", arguments.get(0).getDataType(), arguments.get(0)); - final Field negatives = DSL.when(f.lt(zero()), inline(-1)); - - @SuppressWarnings("serial") - Field negativesSum = new CustomField("sum", NUMERIC) { - @Override - public void accept(Context c) { - c.visit(distinct - ? DSL.sumDistinct(negatives) - : DSL.sum(negatives)); - - toSQLFilterClause(c); - acceptOverClause(c); - } - }; - - @SuppressWarnings("serial") - Field zerosSum = new CustomField("sum", NUMERIC) { - @Override - public void accept(Context c) { - c.visit(DSL.sum(choose(f).when(zero(), one()))); - - toSQLFilterClause(c); - acceptOverClause(c); - } - }; - - @SuppressWarnings("serial") - Field logarithmsSum = new CustomField("sum", NUMERIC) { - @Override - public void accept(Context c) { - Field abs = DSL.abs(DSL.nullif(f, zero())); - Field 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] LIST_AGG emulation for Postgres, Sybase + * ARRAY_AGG */ - 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 extends AbstractWindowFunction implements ctx.sql(')'); } - /** - * [#1273] LIST_AGG 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 KEEP (DENSE_RANK [FIRST | LAST] ORDER BY {...}) clause */ @@ -415,172 +185,14 @@ class DefaultAggregateFunction extends AbstractWindowFunction 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> 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> 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> getArguments() { - return arguments; - } - - @Override - public final AggregateFunction withinGroupOrderBy(OrderField... fields) { - return withinGroupOrderBy(Arrays.asList(fields)); - } - - @Override - public final AggregateFunction withinGroupOrderBy(Collection> fields) { - if (withinGroupOrderBy == null) - withinGroupOrderBy = new SortFieldList(); - - withinGroupOrderBy.addAll(Tools.sortFields(fields)); - return this; - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @Override - public final WindowBeforeOverStep filterWhere(Condition c) { - filter = c; - return this; - } - - @Override - public final WindowBeforeOverStep filterWhere(Condition... conditions) { - return filterWhere(Arrays.asList(conditions)); - } - - @Override - public final WindowBeforeOverStep filterWhere(Collection conditions) { - ConditionProviderImpl c = new ConditionProviderImpl(); - c.addConditions(conditions); - return filterWhere(c); - } - - @Override - public final WindowBeforeOverStep filterWhere(Field field) { - return filterWhere(condition(field)); - } - - @Override - public final WindowBeforeOverStep filterWhere(Boolean field) { - return filterWhere(condition(field)); - } - - @Override - public final WindowBeforeOverStep filterWhere(SQL sql) { - return filterWhere(condition(sql)); - } - - @Override - public final WindowBeforeOverStep filterWhere(String sql) { - return filterWhere(condition(sql)); - } - - @Override - public final WindowBeforeOverStep filterWhere(String sql, Object... bindings) { - return filterWhere(condition(sql, bindings)); - } - - @Override - public final WindowBeforeOverStep filterWhere(String sql, QueryPart... parts) { - return filterWhere(condition(sql, parts)); - } - - @Override - public final DefaultAggregateFunction orderBy(OrderField... fields) { - if (windowSpecification != null) - windowSpecification.orderBy(fields); - else - withinGroupOrderBy(fields); - - return this; - } - - @Override - public final DefaultAggregateFunction orderBy(Collection> fields) { - if (windowSpecification != null) - windowSpecification.orderBy(fields); - else - withinGroupOrderBy(fields); - - return this; - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/GroupConcat.java b/jOOQ/src/main/java/org/jooq/impl/GroupConcat.java index 8a997c36d5..9bcab2d9a3 100644 --- a/jOOQ/src/main/java/org/jooq/impl/GroupConcat.java +++ b/jOOQ/src/main/java/org/jooq/impl/GroupConcat.java @@ -89,12 +89,12 @@ final class GroupConcat extends AbstractFunction implements GroupConcatO @Override final Field getFunction0(Configuration configuration) { - DefaultAggregateFunction 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)); diff --git a/jOOQ/src/main/java/org/jooq/impl/ListAgg.java b/jOOQ/src/main/java/org/jooq/impl/ListAgg.java new file mode 100644 index 0000000000..f33388d6b0 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/ListAgg.java @@ -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 { + + /** + * Generated UID + */ + private static final long serialVersionUID = -1760389929938136896L; + private static final Set SUPPORT_GROUP_CONCAT = SQLDialect.supportedBy(CUBRID, H2, HSQLDB, MARIADB, MYSQL, SQLITE); + private static final Set SUPPORT_STRING_AGG = SQLDialect.supportedBy(POSTGRES); + + + + + + ListAgg(boolean distinct, Field arg) { + super(distinct, N_LISTAGG, SQLDataType.VARCHAR, arg); + } + + ListAgg(boolean distinct, Field arg, Field 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] LIST_AGG 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] LIST_AGG 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(')'); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Median.java b/jOOQ/src/main/java/org/jooq/impl/Median.java new file mode 100644 index 0000000000..1fa1028e53 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/Median.java @@ -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 { + + /** + * Generated UID + */ + private static final long serialVersionUID = -7378732863724089028L; + private static final Set EMULATE_WITH_PERCENTILES = SQLDialect.supportedBy(POSTGRES); + + Median(Field 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); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Names.java b/jOOQ/src/main/java/org/jooq/impl/Names.java index 7f164eaf18..6ddd842624 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Names.java +++ b/jOOQ/src/main/java/org/jooq/impl/Names.java @@ -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"); diff --git a/jOOQ/src/main/java/org/jooq/impl/Product.java b/jOOQ/src/main/java/org/jooq/impl/Product.java new file mode 100644 index 0000000000..6b5fc471bd --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/Product.java @@ -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 { + + /** + * 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 f = (Field) DSL.field("{0}", arguments.get(0).getDataType(), arguments.get(0)); + final Field negatives = DSL.when(f.lt(zero()), inline(-1)); + + @SuppressWarnings("serial") + Field negativesSum = new CustomField("sum", NUMERIC) { + @Override + public void accept(Context c) { + c.visit(distinct + ? DSL.sumDistinct(negatives) + : DSL.sum(negatives)); + + acceptFilterClause(c); + acceptOverClause(c); + } + }; + + @SuppressWarnings("serial") + Field zerosSum = new CustomField("sum", NUMERIC) { + @Override + public void accept(Context c) { + c.visit(DSL.sum(choose(f).when(zero(), one()))); + + acceptFilterClause(c); + acceptOverClause(c); + } + }; + + @SuppressWarnings("serial") + Field logarithmsSum = new CustomField("sum", NUMERIC) { + @Override + public void accept(Context c) { + Field abs = DSL.abs(DSL.nullif(f, zero())); + Field 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)) + ); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Term.java b/jOOQ/src/main/java/org/jooq/impl/Term.java index b0b148b4a3..accde4d426 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Term.java +++ b/jOOQ/src/main/java/org/jooq/impl/Term.java @@ -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) {