From e1220a5e75ab5bd53df3c23b41f9fa1e5679bc51 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 6 Apr 2012 17:53:34 +0000 Subject: [PATCH] [#1268] Add Factory.field(String, QueryPart...) to generate custom clauses - Some simplifications --- .../org/jooq/impl/AggregateFunctionImpl.java | 126 ------ jOOQ/src/main/java/org/jooq/impl/Decode.java | 4 +- jOOQ/src/main/java/org/jooq/impl/Factory.java | 62 +-- .../main/java/org/jooq/impl/FieldList.java | 3 +- .../src/main/java/org/jooq/impl/Function.java | 405 +++++++++++++++-- .../org/jooq/impl/NamedQueryPartList.java | 4 + jOOQ/src/main/java/org/jooq/impl/Pivot.java | 4 +- .../java/org/jooq/impl/QueryPartList.java | 8 +- .../main/java/org/jooq/impl/SQLClause.java | 2 +- .../java/org/jooq/impl/WindowFunction.java | 407 ------------------ 10 files changed, 417 insertions(+), 608 deletions(-) delete mode 100644 jOOQ/src/main/java/org/jooq/impl/AggregateFunctionImpl.java delete mode 100644 jOOQ/src/main/java/org/jooq/impl/WindowFunction.java diff --git a/jOOQ/src/main/java/org/jooq/impl/AggregateFunctionImpl.java b/jOOQ/src/main/java/org/jooq/impl/AggregateFunctionImpl.java deleted file mode 100644 index 41fb7d304e..0000000000 --- a/jOOQ/src/main/java/org/jooq/impl/AggregateFunctionImpl.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com - * All rights reserved. - * - * This software is licensed to you under the Apache License, Version 2.0 - * (the "License"); You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * . Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * . Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * . Neither the name "jOOQ" nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package org.jooq.impl; - -import java.util.Arrays; -import java.util.Collection; - -import org.jooq.AggregateFunction; -import org.jooq.DataType; -import org.jooq.Field; -import org.jooq.OrderedAggregateFunction; -import org.jooq.QueryPart; -import org.jooq.RenderContext; -import org.jooq.SortField; - -class AggregateFunctionImpl extends Function implements OrderedAggregateFunction, AggregateFunction { - - /** - * Generated UID - */ - private static final long serialVersionUID = 1952351506930280715L; - - private final boolean distinct; - private final SortFieldList withinGroupOrderBy; - - AggregateFunctionImpl(String name, DataType type, Field... arguments) { - this(name, false, type, arguments); - } - - AggregateFunctionImpl(Term term, DataType type, Field... arguments) { - this(term, false, type, arguments); - } - - AggregateFunctionImpl(String name, boolean distinct, DataType type, Field... arguments) { - super(name, type, arguments); - - this.distinct = distinct; - this.withinGroupOrderBy = new SortFieldList(); - } - - AggregateFunctionImpl(Term term, boolean distinct, DataType type, Field... arguments) { - super(term, type, arguments); - - this.distinct = distinct; - this.withinGroupOrderBy = new SortFieldList(); - } - - @Override - public final AggregateFunction withinGroupOrderBy(Field... fields) { - withinGroupOrderBy.addAll(fields); - return this; - } - - @Override - public final AggregateFunction withinGroupOrderBy(SortField... fields) { - withinGroupOrderBy.addAll(Arrays.asList(fields)); - return this; - } - - @Override - public final AggregateFunction withinGroupOrderBy(Collection> fields) { - withinGroupOrderBy.addAll(fields); - return this; - } - - @Override - public final WindowFunction over() { - if (getTerm() != null) { - return new WindowFunction(getTerm(), getDataType(), withinGroupOrderBy, getArguments()); - } - else { - return new WindowFunction(getName(), getDataType(), withinGroupOrderBy, getArguments()); - } - } - - @Override - protected final void toSQLField(RenderContext context, QueryPart field) { - if (distinct) { - context.keyword("distinct "); - } - - super.toSQLField(context, field); - } - - @Override - protected final void toSQLSuffix(RenderContext context) { - if (!withinGroupOrderBy.isEmpty()) { - context.keyword(" within group (order by ") - .sql(withinGroupOrderBy) - .sql(")"); - } - } -} diff --git a/jOOQ/src/main/java/org/jooq/impl/Decode.java b/jOOQ/src/main/java/org/jooq/impl/Decode.java index 462c1811c0..7462de3d0c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Decode.java +++ b/jOOQ/src/main/java/org/jooq/impl/Decode.java @@ -35,6 +35,8 @@ */ package org.jooq.impl; +import static org.jooq.impl.Factory.function; + import org.jooq.CaseConditionStep; import org.jooq.Configuration; import org.jooq.Field; @@ -70,7 +72,7 @@ class Decode extends AbstractFunction { // Oracle actually has this function case ORACLE: { - return new Function("decode", getDataType(), getArguments()); + return function("decode", getDataType(), getArguments()); } // Other dialects simulate it with a CASE ... WHEN expression diff --git a/jOOQ/src/main/java/org/jooq/impl/Factory.java b/jOOQ/src/main/java/org/jooq/impl/Factory.java index 609455915a..53bd5d404c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Factory.java +++ b/jOOQ/src/main/java/org/jooq/impl/Factory.java @@ -4070,7 +4070,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction count(Field field) { - return new AggregateFunctionImpl("count", SQLDataType.INTEGER, nullSafe(field)); + return new Function("count", SQLDataType.INTEGER, nullSafe(field)); } /** @@ -4078,7 +4078,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction countDistinct(Field field) { - return new AggregateFunctionImpl("count", true, SQLDataType.INTEGER, nullSafe(field)); + return new Function("count", true, SQLDataType.INTEGER, nullSafe(field)); } /** @@ -4086,7 +4086,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction max(Field field) { - return new AggregateFunctionImpl("max", nullSafeDataType(field), nullSafe(field)); + return new Function("max", nullSafeDataType(field), nullSafe(field)); } /** @@ -4094,7 +4094,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction maxDistinct(Field field) { - return new AggregateFunctionImpl("max", true, nullSafeDataType(field), nullSafe(field)); + return new Function("max", true, nullSafeDataType(field), nullSafe(field)); } /** @@ -4102,7 +4102,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction min(Field field) { - return new AggregateFunctionImpl("min", nullSafeDataType(field), nullSafe(field)); + return new Function("min", nullSafeDataType(field), nullSafe(field)); } /** @@ -4110,7 +4110,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction minDistinct(Field field) { - return new AggregateFunctionImpl("min", true, nullSafeDataType(field), nullSafe(field)); + return new Function("min", true, nullSafeDataType(field), nullSafe(field)); } /** @@ -4118,7 +4118,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction sum(Field field) { - return new AggregateFunctionImpl("sum", SQLDataType.NUMERIC, nullSafe(field)); + return new Function("sum", SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4126,7 +4126,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction sumDistinct(Field field) { - return new AggregateFunctionImpl("sum", true, SQLDataType.NUMERIC, nullSafe(field)); + return new Function("sum", true, SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4134,7 +4134,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction avg(Field field) { - return new AggregateFunctionImpl("avg", SQLDataType.NUMERIC, nullSafe(field)); + return new Function("avg", SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4142,7 +4142,7 @@ public class Factory implements FactoryOperations { */ @Support public static AggregateFunction avgDistinct(Field field) { - return new AggregateFunctionImpl("avg", true, SQLDataType.NUMERIC, nullSafe(field)); + return new Function("avg", true, SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4157,7 +4157,7 @@ public class Factory implements FactoryOperations { */ @Support({ HSQLDB, ORACLE, SYBASE }) public static AggregateFunction median(Field field) { - return new AggregateFunctionImpl("median", SQLDataType.NUMERIC, nullSafe(field)); + return new Function("median", SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4179,7 +4179,7 @@ public class Factory implements FactoryOperations { */ @Support({ ASE, CUBRID, DB2, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE }) public static AggregateFunction stddevPop(Field field) { - return new AggregateFunctionImpl(Term.STDDEV_POP, SQLDataType.NUMERIC, nullSafe(field)); + return new Function(Term.STDDEV_POP, SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4201,7 +4201,7 @@ public class Factory implements FactoryOperations { */ @Support({ ASE, CUBRID, DB2, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE }) public static AggregateFunction stddevSamp(Field field) { - return new AggregateFunctionImpl(Term.STDDEV_SAMP, SQLDataType.NUMERIC, nullSafe(field)); + return new Function(Term.STDDEV_SAMP, SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4223,7 +4223,7 @@ public class Factory implements FactoryOperations { */ @Support({ ASE, CUBRID, DB2, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE }) public static AggregateFunction varPop(Field field) { - return new AggregateFunctionImpl(Term.VAR_POP, SQLDataType.NUMERIC, nullSafe(field)); + return new Function(Term.VAR_POP, SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4243,7 +4243,7 @@ public class Factory implements FactoryOperations { */ @Support({ ASE, CUBRID, DB2, H2, HSQLDB, INGRES, MYSQL, ORACLE, POSTGRES, SQLSERVER, SYBASE }) public static AggregateFunction varSamp(Field field) { - return new AggregateFunctionImpl(Term.VAR_SAMP, SQLDataType.NUMERIC, nullSafe(field)); + return new Function(Term.VAR_SAMP, SQLDataType.NUMERIC, nullSafe(field)); } /** @@ -4251,7 +4251,7 @@ public class Factory implements FactoryOperations { */ @Support(ORACLE) public static OrderedAggregateFunction listAgg(Field field) { - return new AggregateFunctionImpl(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field)); + return new Function(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field)); } /** @@ -4259,7 +4259,7 @@ public class Factory implements FactoryOperations { */ @Support(ORACLE) public static OrderedAggregateFunction listAgg(Field field, String delimiter) { - return new AggregateFunctionImpl(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal("'" + delimiter.replace("'", "''") + "'")); + return new Function(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal("'" + delimiter.replace("'", "''") + "'")); } // ------------------------------------------------------------------------- @@ -4274,7 +4274,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE, SQLSERVER, SYBASE }) public static WindowOverStep rowNumber() { - return new WindowFunction("row_number", SQLDataType.INTEGER); + return new Function("row_number", SQLDataType.INTEGER); } /** @@ -4285,7 +4285,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE, SQLSERVER, SYBASE }) public static WindowOverStep rank() { - return new WindowFunction("rank", SQLDataType.INTEGER); + return new Function("rank", SQLDataType.INTEGER); } /** @@ -4296,7 +4296,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE, SQLSERVER, SYBASE }) public static WindowOverStep denseRank() { - return new WindowFunction("dense_rank", SQLDataType.INTEGER); + return new Function("dense_rank", SQLDataType.INTEGER); } /** @@ -4307,7 +4307,7 @@ public class Factory implements FactoryOperations { */ @Support({ POSTGRES, ORACLE, SYBASE }) public static WindowOverStep percentRank() { - return new WindowFunction("percent_rank", SQLDataType.NUMERIC); + return new Function("percent_rank", SQLDataType.NUMERIC); } /** @@ -4318,7 +4318,7 @@ public class Factory implements FactoryOperations { */ @Support({ POSTGRES, ORACLE, SYBASE }) public static WindowOverStep cumeDist() { - return new WindowFunction("cume_dist", SQLDataType.NUMERIC); + return new Function("cume_dist", SQLDataType.NUMERIC); } /** @@ -4329,7 +4329,7 @@ public class Factory implements FactoryOperations { */ @Support({ POSTGRES, ORACLE, SQLSERVER }) public static WindowOverStep ntile(int number) { - return new WindowFunction("ntile", SQLDataType.INTEGER, field("" + number, Integer.class)); + return new Function("ntile", SQLDataType.INTEGER, field("" + number, Integer.class)); } /** @@ -4340,7 +4340,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE, SYBASE }) public static WindowIgnoreNullsStep firstValue(Field field) { - return new WindowFunction("first_value", nullSafeDataType(field), nullSafe(field)); + return new Function("first_value", nullSafeDataType(field), nullSafe(field)); } /** @@ -4351,7 +4351,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE, SYBASE }) public static WindowIgnoreNullsStep lastValue(Field field) { - return new WindowFunction("last_value", nullSafeDataType(field), nullSafe(field)); + return new Function("last_value", nullSafeDataType(field), nullSafe(field)); } /** @@ -4362,7 +4362,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE }) public static WindowIgnoreNullsStep lead(Field field) { - return new WindowFunction("lead", nullSafeDataType(field), nullSafe(field)); + return new Function("lead", nullSafeDataType(field), nullSafe(field)); } /** @@ -4373,7 +4373,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE }) public static WindowIgnoreNullsStep lead(Field field, int offset) { - return new WindowFunction("lead", nullSafeDataType(field), nullSafe(field), literal(offset)); + return new Function("lead", nullSafeDataType(field), nullSafe(field), literal(offset)); } /** @@ -4399,7 +4399,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE }) public static WindowIgnoreNullsStep lead(Field field, int offset, Field defaultValue) { - return new WindowFunction("lead", nullSafeDataType(field), nullSafe(field), literal(offset), nullSafe(defaultValue)); + return new Function("lead", nullSafeDataType(field), nullSafe(field), literal(offset), nullSafe(defaultValue)); } /** @@ -4410,7 +4410,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE }) public static WindowIgnoreNullsStep lag(Field field) { - return new WindowFunction("lag", nullSafeDataType(field), nullSafe(field)); + return new Function("lag", nullSafeDataType(field), nullSafe(field)); } /** @@ -4421,7 +4421,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE }) public static WindowIgnoreNullsStep lag(Field field, int offset) { - return new WindowFunction("lag", nullSafeDataType(field), nullSafe(field), literal(offset)); + return new Function("lag", nullSafeDataType(field), nullSafe(field), literal(offset)); } /** @@ -4447,7 +4447,7 @@ public class Factory implements FactoryOperations { */ @Support({ DB2, POSTGRES, ORACLE }) public static WindowIgnoreNullsStep lag(Field field, int offset, Field defaultValue) { - return new WindowFunction("lag", nullSafeDataType(field), nullSafe(field), literal(offset), nullSafe(defaultValue)); + return new Function("lag", nullSafeDataType(field), nullSafe(field), literal(offset), nullSafe(defaultValue)); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldList.java b/jOOQ/src/main/java/org/jooq/impl/FieldList.java index cf4a5b709a..f3255cad93 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldList.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldList.java @@ -36,7 +36,6 @@ package org.jooq.impl; -import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -63,7 +62,7 @@ class FieldList extends NamedQueryPartList> implements FieldProvider { } FieldList(Field... wrappedList) { - super(Arrays.asList(wrappedList)); + super(wrappedList); } @SuppressWarnings("unchecked") diff --git a/jOOQ/src/main/java/org/jooq/impl/Function.java b/jOOQ/src/main/java/org/jooq/impl/Function.java index fef89afedd..a0234043a0 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Function.java +++ b/jOOQ/src/main/java/org/jooq/impl/Function.java @@ -36,59 +36,224 @@ package org.jooq.impl; +import static org.jooq.impl.Factory.one; + +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import org.jooq.AggregateFunction; import org.jooq.Attachable; import org.jooq.BindContext; import org.jooq.DataType; +import org.jooq.Field; +import org.jooq.OrderedAggregateFunction; import org.jooq.QueryPart; import org.jooq.RenderContext; import org.jooq.SQLDialect; +import org.jooq.SortField; +import org.jooq.WindowFinalStep; +import org.jooq.WindowIgnoreNullsStep; +import org.jooq.WindowOrderByStep; +import org.jooq.WindowOverStep; +import org.jooq.WindowPartitionByStep; +import org.jooq.WindowRowsAndStep; +import org.jooq.WindowRowsStep; /** + * A field that handles built-in functions, aggregate functions, and window + * functions. + * * @author Lukas Eder */ -class Function extends AbstractField { +class Function extends AbstractField implements - private static final long serialVersionUID = 347252741712134044L; + // Cascading interface implementations for aggregate and window function behaviour + OrderedAggregateFunction, + AggregateFunction, + WindowIgnoreNullsStep, + WindowPartitionByStep, + WindowRowsStep, + WindowRowsAndStep + { - private final QueryPart[] arguments; - private final Term term; + private static final long serialVersionUID = 347252741712134044L; + + private final QueryPartList arguments; + private final Term term; + private final boolean distinct; + private final SortFieldList withinGroupOrderBy; + private final FieldList partitionBy; + private final SortFieldList orderBy; + + private boolean over; + private boolean partitionByOne; + private boolean ignoreNulls; + private boolean respectNulls; + private Integer rowsStart; + private Integer rowsEnd; + + + // ------------------------------------------------------------------------- + // XXX Constructors + // ------------------------------------------------------------------------- Function(String name, DataType type, QueryPart... arguments) { - super(name, type); - - this.arguments = arguments; - this.term = null; + this(name, false, type, arguments); } Function(Term term, DataType type, QueryPart... arguments) { + this(term, false, type, arguments); + } + + Function(String name, boolean distinct, DataType type, QueryPart... arguments) { + super(name, type); + + this.term = null; + this.distinct = distinct; + this.arguments = new QueryPartList(arguments); + this.withinGroupOrderBy = new SortFieldList(); + this.partitionBy = new FieldList(); + this.orderBy = new SortFieldList(); + } + + Function(Term term, boolean distinct, DataType type, QueryPart... arguments) { super(term.name().toLowerCase(), type); - this.arguments = arguments; this.term = term; + this.distinct = distinct; + this.arguments = new QueryPartList(arguments); + this.withinGroupOrderBy = new SortFieldList(); + this.partitionBy = new FieldList(); + this.orderBy = new SortFieldList(); } + // ------------------------------------------------------------------------- + // XXX QueryPart API + // ------------------------------------------------------------------------- + @Override public final List getAttachables() { - return getAttachables(arguments); + return getAttachables(arguments, withinGroupOrderBy, partitionBy, orderBy); } @Override public final void toSQL(RenderContext context) { context.sql(getFNName(context.getDialect())); - context.sql("("); + toSQLArguments(context); + toSQLWithinGroupClause(context); + toSQLOverClause(context); + } - String separator = ""; - for (QueryPart field : arguments) { - context.sql(separator); - toSQLField(context, field); + private void toSQLOverClause(RenderContext context) { + if (!over) return; - separator = ", "; + String glue = ""; + context.keyword(" over ("); + if (!partitionBy.isEmpty()) { + if (partitionByOne && context.getDialect() == SQLDialect.SYBASE) { + // Ignore partition clause. Sybase does not support this construct + } + else { + context.sql(glue) + .keyword("partition by ") + .sql(partitionBy); + + glue = " "; + } + } + + if (!orderBy.isEmpty()) { + context.sql(glue) + .keyword("order by "); + + switch (context.getDialect()) { + + // SQL Server and Sybase don't allow for fully qualified fields + // in the ORDER BY clause of an analytic expression + case SQLSERVER: // No break + case SYBASE: { + for (SortField f : orderBy) { + SortFieldImpl field = (SortFieldImpl) f; + field.toSQLInAnalyticClause(context); + } + + break; + } + + default: { + context.sql(orderBy); + break; + } + } + + glue = " "; + } + + if (rowsStart != null) { + context.sql(glue); + context.keyword("rows "); + + if (rowsEnd != null) { + context.keyword("between "); + toSQLRows(context, rowsStart); + + context.keyword(" and "); + toSQLRows(context, rowsEnd); + } + else { + toSQLRows(context, rowsStart); + } + + glue = " "; + } + + context.sql(")"); + } + + /** + * Render WITHIN GROUP (ORDER BY ..) clause + */ + private void toSQLWithinGroupClause(RenderContext context) { + if (!withinGroupOrderBy.isEmpty()) { + context.keyword(" within group (order by ") + .sql(withinGroupOrderBy) + .sql(")"); + } + } + + /** + * Render function arguments and argument modifiers + */ + private void toSQLArguments(RenderContext context) { + context.sql("("); + + if (distinct) { + context.keyword("distinct "); + } + + if (!arguments.isEmpty()) { + context.sql(arguments); + } + + if (ignoreNulls) { + if (context.getDialect() == SQLDialect.DB2) { + context.sql(", 'IGNORE NULLS'"); + } + else { + context.keyword(" ignore nulls"); + } + } + else if (respectNulls) { + if (context.getDialect() == SQLDialect.DB2) { + context.sql(", 'RESPECT NULLS'"); + } + else { + context.keyword(" respect nulls"); + } } context.sql(")"); - toSQLSuffix(context); } private final String getFNName(SQLDialect dialect) { @@ -100,29 +265,32 @@ class Function extends AbstractField { } } - final Term getTerm() { - return term; + private final void toSQLRows(RenderContext context, Integer rows) { + if (rows == Integer.MIN_VALUE) { + context.keyword("unbounded preceding"); + } + else if (rows == Integer.MAX_VALUE) { + context.keyword("unbounded following"); + } + else if (rows < 0) { + context.sql(-rows); + context.keyword(" preceding"); + } + else if (rows > 0) { + context.sql(rows); + context.keyword(" following"); + } + else { + context.keyword("current row"); + } } - - /** - * Render the argument field. This renders the field directly, by default. - * Subclasses may override this method, if needed (e.g. to render - * count(distinct [field]) - */ - protected void toSQLField(RenderContext context, QueryPart field) { - context.sql(field); - } - - /** - * Render additional SQL. Subclasses may override this method, if needed - * (e.g. to render WITHIN GROUP (ORDER BY ..)) - */ - protected void toSQLSuffix(RenderContext context) {} - @Override public final void bind(BindContext context) { - context.bind(arguments); + context.bind((QueryPart) arguments) + .bind((QueryPart) withinGroupOrderBy) + .bind((QueryPart) partitionBy) + .bind((QueryPart) orderBy); } @Override @@ -130,7 +298,170 @@ class Function extends AbstractField { return false; } - final QueryPart[] getArguments() { + // ------------------------------------------------------------------------- + // XXX aggregate and window function fluent API methods + // ------------------------------------------------------------------------- + + final QueryPartList getArguments() { return arguments; } + + @Override + public final AggregateFunction withinGroupOrderBy(Field... fields) { + withinGroupOrderBy.addAll(fields); + return this; + } + + @Override + public final AggregateFunction withinGroupOrderBy(SortField... fields) { + withinGroupOrderBy.addAll(Arrays.asList(fields)); + return this; + } + + @Override + public final AggregateFunction withinGroupOrderBy(Collection> fields) { + withinGroupOrderBy.addAll(fields); + return this; + } + + @Override + public final WindowPartitionByStep over() { + over = true; + return this; + } + + @Override + public final WindowOverStep ignoreNulls() { + ignoreNulls = true; + respectNulls = false; + return this; + } + + @Override + public final WindowOverStep respectNulls() { + ignoreNulls = false; + respectNulls = true; + return this; + } + + @Override + public final WindowOrderByStep partitionBy(Field... fields) { + partitionBy.addAll(Arrays.asList(fields)); + return this; + } + + @Override + public final WindowOrderByStep partitionByOne() { + partitionByOne = true; + partitionBy.add(one()); + return this; + } + + @Override + public final WindowRowsStep orderBy(Field... fields) { + orderBy.addAll(fields); + return this; + } + + @Override + public final WindowRowsStep orderBy(SortField... fields) { + orderBy.addAll(Arrays.asList(fields)); + return this; + } + + @Override + public final WindowRowsStep orderBy(Collection> fields) { + orderBy.addAll(fields); + return this; + } + + @Override + public final WindowFinalStep rowsUnboundedPreceding() { + rowsStart = Integer.MIN_VALUE; + return this; + } + + @Override + public final WindowFinalStep rowsPreceding(int number) { + rowsStart = -number; + return this; + } + + @Override + public final WindowFinalStep rowsCurrentRow() { + rowsStart = 0; + return this; + } + + @Override + public final WindowFinalStep rowsUnboundedFollowing() { + rowsStart = Integer.MAX_VALUE; + return this; + } + + @Override + public final WindowFinalStep rowsFollowing(int number) { + rowsStart = number; + return this; + } + + @Override + public final WindowRowsAndStep rowsBetweenUnboundedPreceding() { + rowsUnboundedPreceding(); + return this; + } + + @Override + public final WindowRowsAndStep rowsBetweenPreceding(int number) { + rowsPreceding(number); + return this; + } + + @Override + public final WindowRowsAndStep rowsBetweenCurrentRow() { + rowsCurrentRow(); + return this; + } + + @Override + public final WindowRowsAndStep rowsBetweenUnboundedFollowing() { + rowsUnboundedFollowing(); + return this; + } + + @Override + public final WindowRowsAndStep rowsBetweenFollowing(int number) { + rowsFollowing(number); + return this; + } + + @Override + public final WindowFinalStep andUnboundedPreceding() { + rowsEnd = Integer.MIN_VALUE; + return this; + } + + @Override + public final WindowFinalStep andPreceding(int number) { + rowsEnd = -number; + return this; + } + + @Override + public final WindowFinalStep andCurrentRow() { + rowsEnd = 0; + return this; + } + + @Override + public final WindowFinalStep andUnboundedFollowing() { + rowsEnd = Integer.MAX_VALUE; + return this; + } + + @Override + public final WindowFinalStep andFollowing(int number) { + rowsEnd = number; + return this; + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/NamedQueryPartList.java b/jOOQ/src/main/java/org/jooq/impl/NamedQueryPartList.java index f57b394f5d..73d3990b75 100644 --- a/jOOQ/src/main/java/org/jooq/impl/NamedQueryPartList.java +++ b/jOOQ/src/main/java/org/jooq/impl/NamedQueryPartList.java @@ -56,4 +56,8 @@ class NamedQueryPartList extends QueryPartList { NamedQueryPartList(Collection wrappedList) { super(wrappedList); } + + NamedQueryPartList(T... wrappedList) { + super(wrappedList); + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Pivot.java b/jOOQ/src/main/java/org/jooq/impl/Pivot.java index f886a58b8e..8862c0cd87 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Pivot.java +++ b/jOOQ/src/main/java/org/jooq/impl/Pivot.java @@ -155,8 +155,8 @@ implements // This loop finds all fields that are used in aggregate // functions. They're excluded from the GROUP BY clause for (Field field : aggregateFunctions) { - if (field instanceof AggregateFunctionImpl) { - for (QueryPart argument : ((AggregateFunctionImpl) field).getArguments()) { + if (field instanceof Function) { + for (QueryPart argument : ((Function) field).getArguments()) { if (argument instanceof Field) { aggregatedFields.add((Field) argument); } diff --git a/jOOQ/src/main/java/org/jooq/impl/QueryPartList.java b/jOOQ/src/main/java/org/jooq/impl/QueryPartList.java index 17380693b3..ad5ed40ac4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/QueryPartList.java +++ b/jOOQ/src/main/java/org/jooq/impl/QueryPartList.java @@ -37,6 +37,7 @@ package org.jooq.impl; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -56,7 +57,7 @@ class QueryPartList extends AbstractQueryPart implements Li private final List wrappedList = new ArrayList(); QueryPartList() { - this(null); + this((Collection) null); } QueryPartList(Collection wrappedList) { @@ -67,6 +68,11 @@ class QueryPartList extends AbstractQueryPart implements Li } } + QueryPartList(T... wrappedList) { + this(Arrays.asList(wrappedList)); + } + + @Override public final List getAttachables() { return getAttachables(this); diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLClause.java b/jOOQ/src/main/java/org/jooq/impl/SQLClause.java index ca913783e5..ead788058f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLClause.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLClause.java @@ -71,7 +71,7 @@ class SQLClause extends AbstractField { @Override public final List getAttachables() { - return null; + return getAttachables(parts); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/WindowFunction.java b/jOOQ/src/main/java/org/jooq/impl/WindowFunction.java deleted file mode 100644 index d90921a5da..0000000000 --- a/jOOQ/src/main/java/org/jooq/impl/WindowFunction.java +++ /dev/null @@ -1,407 +0,0 @@ -/** - * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com - * All rights reserved. - * - * This software is licensed to you under the Apache License, Version 2.0 - * (the "License"); You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * . Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * . Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * . Neither the name "jOOQ" nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package org.jooq.impl; - -import static org.jooq.impl.Factory.one; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.jooq.Attachable; -import org.jooq.BindContext; -import org.jooq.DataType; -import org.jooq.Field; -import org.jooq.QueryPart; -import org.jooq.RenderContext; -import org.jooq.SQLDialect; -import org.jooq.SortField; -import org.jooq.WindowIgnoreNullsStep; -import org.jooq.WindowPartitionByStep; -import org.jooq.WindowRowsAndStep; -import org.jooq.WindowRowsStep; - -/** - * Implementation object for window function DSL API - * - * @author Lukas Eder - */ -class WindowFunction extends AbstractField -implements - - // Cascading interface implementations for window function behaviour - WindowIgnoreNullsStep, - WindowPartitionByStep, - WindowRowsStep, - WindowRowsAndStep - { - - /** - * Generated UID - */ - private static final long serialVersionUID = 5505202722420252635L; - - private final Term term; - - private final QueryPartList arguments; - private final SortFieldList withinGroupOrderBy; - private final FieldList partitionBy; - private final SortFieldList orderBy; - - private boolean partitionByOne; - private boolean ignoreNulls; - private boolean respectNulls; - private Integer rowsStart; - private Integer rowsEnd; - - WindowFunction(String name, DataType type, QueryPart... arguments) { - this(name, type, new SortFieldList(), arguments); - } - - WindowFunction(Term term, DataType type, QueryPart... arguments) { - this(term, type, new SortFieldList(), arguments); - } - - WindowFunction(String name, DataType type, SortFieldList withinGroupOrderBy, QueryPart... arguments) { - super(name, type); - - this.partitionBy = new FieldList(); - this.orderBy = new SortFieldList(); - this.arguments = new QueryPartList(Arrays.asList(arguments)); - this.term = null; - this.withinGroupOrderBy = withinGroupOrderBy; - } - - WindowFunction(Term term, DataType type, SortFieldList withinGroupOrderBy, QueryPart... arguments) { - super(term.name().toLowerCase(), type); - - this.partitionBy = new FieldList(); - this.orderBy = new SortFieldList(); - this.arguments = new QueryPartList(Arrays.asList(arguments)); - this.term = term; - this.withinGroupOrderBy = withinGroupOrderBy; - } - - // ------------------------------------------------------------------------- - // Field API - // ------------------------------------------------------------------------- - - @Override - public final List getAttachables() { - return getAttachables(arguments, partitionBy, orderBy); - } - - @Override - public final void toSQL(RenderContext context) { - context.keyword(getFNName(context.getDialect())); - context.sql("("); - - if (!arguments.isEmpty()) { - context.sql(arguments); - } - - if (ignoreNulls) { - if (context.getDialect() == SQLDialect.DB2) { - context.sql(", 'IGNORE NULLS'"); - } - else { - context.keyword(" ignore nulls"); - } - } - else if (respectNulls) { - if (context.getDialect() == SQLDialect.DB2) { - context.sql(", 'RESPECT NULLS'"); - } - else { - context.keyword(" respect nulls"); - } - } - - context.sql(")"); - if (!withinGroupOrderBy.isEmpty()) { - context.keyword(" within group (order by ") - .sql(withinGroupOrderBy) - .sql(")"); - } - - String glue = ""; - context.keyword(" over ("); - if (!partitionBy.isEmpty()) { - if (partitionByOne && context.getDialect() == SQLDialect.SYBASE) { - // Ignore partition clause. Sybase does not support this construct - } - else { - context.sql(glue) - .keyword("partition by ") - .sql(partitionBy); - - glue = " "; - } - } - - if (!orderBy.isEmpty()) { - context.sql(glue) - .keyword("order by "); - - switch (context.getDialect()) { - - // SQL Server and Sybase don't allow for fully qualified fields - // in the ORDER BY clause of an analytic expression - case SQLSERVER: // No break - case SYBASE: { - for (SortField f : orderBy) { - SortFieldImpl field = (SortFieldImpl) f; - field.toSQLInAnalyticClause(context); - } - - break; - } - - default: { - context.sql(orderBy); - break; - } - } - - glue = " "; - } - - if (rowsStart != null) { - context.sql(glue); - context.keyword("rows "); - - if (rowsEnd != null) { - context.keyword("between "); - toSQLRows(context, rowsStart); - - context.keyword(" and "); - toSQLRows(context, rowsEnd); - } - else { - toSQLRows(context, rowsStart); - } - - glue = " "; - } - - context.sql(")"); - } - - private final String getFNName(SQLDialect dialect) { - if (term != null) { - return term.translate(dialect); - } - else { - return getName(); - } - } - - private final void toSQLRows(RenderContext context, Integer rows) { - if (rows == Integer.MIN_VALUE) { - context.keyword("unbounded preceding"); - } - else if (rows == Integer.MAX_VALUE) { - context.keyword("unbounded following"); - } - else if (rows < 0) { - context.sql(-rows); - context.keyword(" preceding"); - } - else if (rows > 0) { - context.sql(rows); - context.keyword(" following"); - } - else { - context.keyword("current row"); - } - } - - @Override - public final void bind(BindContext context) { - context.bind((QueryPart) arguments) - .bind((QueryPart) partitionBy) - .bind((QueryPart) orderBy); - } - - @Override - public final boolean isNullLiteral() { - return false; - } - - // ------------------------------------------------------------------------- - // Window function API - // ------------------------------------------------------------------------- - - @Override - public final WindowFunction ignoreNulls() { - ignoreNulls = true; - respectNulls = false; - return this; - } - - @Override - public final WindowFunction respectNulls() { - ignoreNulls = false; - respectNulls = true; - return this; - } - - @Override - public final WindowFunction over() { - return this; - } - - @Override - public final WindowFunction partitionBy(Field... fields) { - partitionBy.addAll(Arrays.asList(fields)); - return this; - } - - @Override - public final WindowFunction partitionByOne() { - partitionByOne = true; - partitionBy.add(one()); - return this; - } - - @Override - public final WindowFunction orderBy(Field... fields) { - orderBy.addAll(fields); - return this; - } - - @Override - public final WindowFunction orderBy(SortField... fields) { - orderBy.addAll(Arrays.asList(fields)); - return this; - } - - @Override - public final WindowFunction orderBy(Collection> fields) { - orderBy.addAll(fields); - return this; - } - - @Override - public final WindowFunction rowsUnboundedPreceding() { - rowsStart = Integer.MIN_VALUE; - return this; - } - - @Override - public final WindowFunction rowsPreceding(int number) { - rowsStart = -number; - return this; - } - - @Override - public final WindowFunction rowsCurrentRow() { - rowsStart = 0; - return this; - } - - @Override - public final WindowFunction rowsUnboundedFollowing() { - rowsStart = Integer.MAX_VALUE; - return this; - } - - @Override - public final WindowFunction rowsFollowing(int number) { - rowsStart = number; - return this; - } - - @Override - public final WindowFunction rowsBetweenUnboundedPreceding() { - rowsUnboundedPreceding(); - return this; - } - - @Override - public final WindowFunction rowsBetweenPreceding(int number) { - rowsPreceding(number); - return this; - } - - @Override - public final WindowFunction rowsBetweenCurrentRow() { - rowsCurrentRow(); - return this; - } - - @Override - public final WindowFunction rowsBetweenUnboundedFollowing() { - rowsUnboundedFollowing(); - return this; - } - - @Override - public final WindowFunction rowsBetweenFollowing(int number) { - rowsFollowing(number); - return this; - } - - @Override - public final WindowFunction andUnboundedPreceding() { - rowsEnd = Integer.MIN_VALUE; - return this; - } - - @Override - public final WindowFunction andPreceding(int number) { - rowsEnd = -number; - return this; - } - - @Override - public final WindowFunction andCurrentRow() { - rowsEnd = 0; - return this; - } - - @Override - public final WindowFunction andUnboundedFollowing() { - rowsEnd = Integer.MAX_VALUE; - return this; - } - - @Override - public final WindowFunction andFollowing(int number) { - rowsEnd = number; - return this; - } -}