[#1268] Add Factory.field(String, QueryPart...) to generate custom clauses - Some simplifications

This commit is contained in:
Lukas Eder 2012-04-06 17:53:34 +00:00
parent 7d78b0a23a
commit e1220a5e75
10 changed files with 417 additions and 608 deletions

View File

@ -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<T> extends Function<T> implements OrderedAggregateFunction<T>, AggregateFunction<T> {
/**
* Generated UID
*/
private static final long serialVersionUID = 1952351506930280715L;
private final boolean distinct;
private final SortFieldList withinGroupOrderBy;
AggregateFunctionImpl(String name, DataType<T> type, Field<?>... arguments) {
this(name, false, type, arguments);
}
AggregateFunctionImpl(Term term, DataType<T> type, Field<?>... arguments) {
this(term, false, type, arguments);
}
AggregateFunctionImpl(String name, boolean distinct, DataType<T> type, Field<?>... arguments) {
super(name, type, arguments);
this.distinct = distinct;
this.withinGroupOrderBy = new SortFieldList();
}
AggregateFunctionImpl(Term term, boolean distinct, DataType<T> type, Field<?>... arguments) {
super(term, type, arguments);
this.distinct = distinct;
this.withinGroupOrderBy = new SortFieldList();
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(Field<?>... fields) {
withinGroupOrderBy.addAll(fields);
return this;
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(SortField<?>... fields) {
withinGroupOrderBy.addAll(Arrays.asList(fields));
return this;
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(Collection<SortField<?>> fields) {
withinGroupOrderBy.addAll(fields);
return this;
}
@Override
public final WindowFunction<T> over() {
if (getTerm() != null) {
return new WindowFunction<T>(getTerm(), getDataType(), withinGroupOrderBy, getArguments());
}
else {
return new WindowFunction<T>(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(")");
}
}
}

View File

@ -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<T, Z> extends AbstractFunction<Z> {
// Oracle actually has this function
case ORACLE: {
return new Function<Z>("decode", getDataType(), getArguments());
return function("decode", getDataType(), getArguments());
}
// Other dialects simulate it with a CASE ... WHEN expression

View File

@ -4070,7 +4070,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static AggregateFunction<Integer> count(Field<?> field) {
return new AggregateFunctionImpl<Integer>("count", SQLDataType.INTEGER, nullSafe(field));
return new Function<Integer>("count", SQLDataType.INTEGER, nullSafe(field));
}
/**
@ -4078,7 +4078,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static AggregateFunction<Integer> countDistinct(Field<?> field) {
return new AggregateFunctionImpl<Integer>("count", true, SQLDataType.INTEGER, nullSafe(field));
return new Function<Integer>("count", true, SQLDataType.INTEGER, nullSafe(field));
}
/**
@ -4086,7 +4086,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static <T> AggregateFunction<T> max(Field<T> field) {
return new AggregateFunctionImpl<T>("max", nullSafeDataType(field), nullSafe(field));
return new Function<T>("max", nullSafeDataType(field), nullSafe(field));
}
/**
@ -4094,7 +4094,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static <T> AggregateFunction<T> maxDistinct(Field<T> field) {
return new AggregateFunctionImpl<T>("max", true, nullSafeDataType(field), nullSafe(field));
return new Function<T>("max", true, nullSafeDataType(field), nullSafe(field));
}
/**
@ -4102,7 +4102,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static <T> AggregateFunction<T> min(Field<T> field) {
return new AggregateFunctionImpl<T>("min", nullSafeDataType(field), nullSafe(field));
return new Function<T>("min", nullSafeDataType(field), nullSafe(field));
}
/**
@ -4110,7 +4110,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static <T> AggregateFunction<T> minDistinct(Field<T> field) {
return new AggregateFunctionImpl<T>("min", true, nullSafeDataType(field), nullSafe(field));
return new Function<T>("min", true, nullSafeDataType(field), nullSafe(field));
}
/**
@ -4118,7 +4118,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static AggregateFunction<BigDecimal> sum(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>("sum", SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>("sum", SQLDataType.NUMERIC, nullSafe(field));
}
/**
@ -4126,7 +4126,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static AggregateFunction<BigDecimal> sumDistinct(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>("sum", true, SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>("sum", true, SQLDataType.NUMERIC, nullSafe(field));
}
/**
@ -4134,7 +4134,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static AggregateFunction<BigDecimal> avg(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>("avg", SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>("avg", SQLDataType.NUMERIC, nullSafe(field));
}
/**
@ -4142,7 +4142,7 @@ public class Factory implements FactoryOperations {
*/
@Support
public static AggregateFunction<BigDecimal> avgDistinct(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>("avg", true, SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>("avg", true, SQLDataType.NUMERIC, nullSafe(field));
}
/**
@ -4157,7 +4157,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ HSQLDB, ORACLE, SYBASE })
public static AggregateFunction<BigDecimal> median(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>("median", SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>("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<BigDecimal> stddevPop(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>(Term.STDDEV_POP, SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>(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<BigDecimal> stddevSamp(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>(Term.STDDEV_SAMP, SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>(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<BigDecimal> varPop(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>(Term.VAR_POP, SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>(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<BigDecimal> varSamp(Field<? extends Number> field) {
return new AggregateFunctionImpl<BigDecimal>(Term.VAR_SAMP, SQLDataType.NUMERIC, nullSafe(field));
return new Function<BigDecimal>(Term.VAR_SAMP, SQLDataType.NUMERIC, nullSafe(field));
}
/**
@ -4251,7 +4251,7 @@ public class Factory implements FactoryOperations {
*/
@Support(ORACLE)
public static OrderedAggregateFunction<String> listAgg(Field<?> field) {
return new AggregateFunctionImpl<String>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field));
return new Function<String>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field));
}
/**
@ -4259,7 +4259,7 @@ public class Factory implements FactoryOperations {
*/
@Support(ORACLE)
public static OrderedAggregateFunction<String> listAgg(Field<?> field, String delimiter) {
return new AggregateFunctionImpl<String>(Term.LIST_AGG, SQLDataType.VARCHAR, nullSafe(field), literal("'" + delimiter.replace("'", "''") + "'"));
return new Function<String>(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<Integer> rowNumber() {
return new WindowFunction<Integer>("row_number", SQLDataType.INTEGER);
return new Function<Integer>("row_number", SQLDataType.INTEGER);
}
/**
@ -4285,7 +4285,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE, SQLSERVER, SYBASE })
public static WindowOverStep<Integer> rank() {
return new WindowFunction<Integer>("rank", SQLDataType.INTEGER);
return new Function<Integer>("rank", SQLDataType.INTEGER);
}
/**
@ -4296,7 +4296,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE, SQLSERVER, SYBASE })
public static WindowOverStep<Integer> denseRank() {
return new WindowFunction<Integer>("dense_rank", SQLDataType.INTEGER);
return new Function<Integer>("dense_rank", SQLDataType.INTEGER);
}
/**
@ -4307,7 +4307,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ POSTGRES, ORACLE, SYBASE })
public static WindowOverStep<BigDecimal> percentRank() {
return new WindowFunction<BigDecimal>("percent_rank", SQLDataType.NUMERIC);
return new Function<BigDecimal>("percent_rank", SQLDataType.NUMERIC);
}
/**
@ -4318,7 +4318,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ POSTGRES, ORACLE, SYBASE })
public static WindowOverStep<BigDecimal> cumeDist() {
return new WindowFunction<BigDecimal>("cume_dist", SQLDataType.NUMERIC);
return new Function<BigDecimal>("cume_dist", SQLDataType.NUMERIC);
}
/**
@ -4329,7 +4329,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ POSTGRES, ORACLE, SQLSERVER })
public static WindowOverStep<Integer> ntile(int number) {
return new WindowFunction<Integer>("ntile", SQLDataType.INTEGER, field("" + number, Integer.class));
return new Function<Integer>("ntile", SQLDataType.INTEGER, field("" + number, Integer.class));
}
/**
@ -4340,7 +4340,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE, SYBASE })
public static <T> WindowIgnoreNullsStep<T> firstValue(Field<T> field) {
return new WindowFunction<T>("first_value", nullSafeDataType(field), nullSafe(field));
return new Function<T>("first_value", nullSafeDataType(field), nullSafe(field));
}
/**
@ -4351,7 +4351,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE, SYBASE })
public static <T> WindowIgnoreNullsStep<T> lastValue(Field<T> field) {
return new WindowFunction<T>("last_value", nullSafeDataType(field), nullSafe(field));
return new Function<T>("last_value", nullSafeDataType(field), nullSafe(field));
}
/**
@ -4362,7 +4362,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE })
public static <T> WindowIgnoreNullsStep<T> lead(Field<T> field) {
return new WindowFunction<T>("lead", nullSafeDataType(field), nullSafe(field));
return new Function<T>("lead", nullSafeDataType(field), nullSafe(field));
}
/**
@ -4373,7 +4373,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE })
public static <T> WindowIgnoreNullsStep<T> lead(Field<T> field, int offset) {
return new WindowFunction<T>("lead", nullSafeDataType(field), nullSafe(field), literal(offset));
return new Function<T>("lead", nullSafeDataType(field), nullSafe(field), literal(offset));
}
/**
@ -4399,7 +4399,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE })
public static <T> WindowIgnoreNullsStep<T> lead(Field<T> field, int offset, Field<T> defaultValue) {
return new WindowFunction<T>("lead", nullSafeDataType(field), nullSafe(field), literal(offset), nullSafe(defaultValue));
return new Function<T>("lead", nullSafeDataType(field), nullSafe(field), literal(offset), nullSafe(defaultValue));
}
/**
@ -4410,7 +4410,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE })
public static <T> WindowIgnoreNullsStep<T> lag(Field<T> field) {
return new WindowFunction<T>("lag", nullSafeDataType(field), nullSafe(field));
return new Function<T>("lag", nullSafeDataType(field), nullSafe(field));
}
/**
@ -4421,7 +4421,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE })
public static <T> WindowIgnoreNullsStep<T> lag(Field<T> field, int offset) {
return new WindowFunction<T>("lag", nullSafeDataType(field), nullSafe(field), literal(offset));
return new Function<T>("lag", nullSafeDataType(field), nullSafe(field), literal(offset));
}
/**
@ -4447,7 +4447,7 @@ public class Factory implements FactoryOperations {
*/
@Support({ DB2, POSTGRES, ORACLE })
public static <T> WindowIgnoreNullsStep<T> lag(Field<T> field, int offset, Field<T> defaultValue) {
return new WindowFunction<T>("lag", nullSafeDataType(field), nullSafe(field), literal(offset), nullSafe(defaultValue));
return new Function<T>("lag", nullSafeDataType(field), nullSafe(field), literal(offset), nullSafe(defaultValue));
}
// -------------------------------------------------------------------------

View File

@ -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<Field<?>> implements FieldProvider {
}
FieldList(Field<?>... wrappedList) {
super(Arrays.asList(wrappedList));
super(wrappedList);
}
@SuppressWarnings("unchecked")

View File

@ -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<T> extends AbstractField<T> {
class Function<T> extends AbstractField<T> implements
private static final long serialVersionUID = 347252741712134044L;
// Cascading interface implementations for aggregate and window function behaviour
OrderedAggregateFunction<T>,
AggregateFunction<T>,
WindowIgnoreNullsStep<T>,
WindowPartitionByStep<T>,
WindowRowsStep<T>,
WindowRowsAndStep<T>
{
private final QueryPart[] arguments;
private final Term term;
private static final long serialVersionUID = 347252741712134044L;
private final QueryPartList<QueryPart> 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<T> type, QueryPart... arguments) {
super(name, type);
this.arguments = arguments;
this.term = null;
this(name, false, type, arguments);
}
Function(Term term, DataType<T> type, QueryPart... arguments) {
this(term, false, type, arguments);
}
Function(String name, boolean distinct, DataType<T> type, QueryPart... arguments) {
super(name, type);
this.term = null;
this.distinct = distinct;
this.arguments = new QueryPartList<QueryPart>(arguments);
this.withinGroupOrderBy = new SortFieldList();
this.partitionBy = new FieldList();
this.orderBy = new SortFieldList();
}
Function(Term term, boolean distinct, DataType<T> type, QueryPart... arguments) {
super(term.name().toLowerCase(), type);
this.arguments = arguments;
this.term = term;
this.distinct = distinct;
this.arguments = new QueryPartList<QueryPart>(arguments);
this.withinGroupOrderBy = new SortFieldList();
this.partitionBy = new FieldList();
this.orderBy = new SortFieldList();
}
// -------------------------------------------------------------------------
// XXX QueryPart API
// -------------------------------------------------------------------------
@Override
public final List<Attachable> 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 <code>WITHIN GROUP (ORDER BY ..)</code> 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<T> extends AbstractField<T> {
}
}
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 <code>WITHIN GROUP (ORDER BY ..)</code>)
*/
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<T> extends AbstractField<T> {
return false;
}
final QueryPart[] getArguments() {
// -------------------------------------------------------------------------
// XXX aggregate and window function fluent API methods
// -------------------------------------------------------------------------
final QueryPartList<QueryPart> getArguments() {
return arguments;
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(Field<?>... fields) {
withinGroupOrderBy.addAll(fields);
return this;
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(SortField<?>... fields) {
withinGroupOrderBy.addAll(Arrays.asList(fields));
return this;
}
@Override
public final AggregateFunction<T> withinGroupOrderBy(Collection<SortField<?>> fields) {
withinGroupOrderBy.addAll(fields);
return this;
}
@Override
public final WindowPartitionByStep<T> over() {
over = true;
return this;
}
@Override
public final WindowOverStep<T> ignoreNulls() {
ignoreNulls = true;
respectNulls = false;
return this;
}
@Override
public final WindowOverStep<T> respectNulls() {
ignoreNulls = false;
respectNulls = true;
return this;
}
@Override
public final WindowOrderByStep<T> partitionBy(Field<?>... fields) {
partitionBy.addAll(Arrays.asList(fields));
return this;
}
@Override
public final WindowOrderByStep<T> partitionByOne() {
partitionByOne = true;
partitionBy.add(one());
return this;
}
@Override
public final WindowRowsStep<T> orderBy(Field<?>... fields) {
orderBy.addAll(fields);
return this;
}
@Override
public final WindowRowsStep<T> orderBy(SortField<?>... fields) {
orderBy.addAll(Arrays.asList(fields));
return this;
}
@Override
public final WindowRowsStep<T> orderBy(Collection<SortField<?>> fields) {
orderBy.addAll(fields);
return this;
}
@Override
public final WindowFinalStep<T> rowsUnboundedPreceding() {
rowsStart = Integer.MIN_VALUE;
return this;
}
@Override
public final WindowFinalStep<T> rowsPreceding(int number) {
rowsStart = -number;
return this;
}
@Override
public final WindowFinalStep<T> rowsCurrentRow() {
rowsStart = 0;
return this;
}
@Override
public final WindowFinalStep<T> rowsUnboundedFollowing() {
rowsStart = Integer.MAX_VALUE;
return this;
}
@Override
public final WindowFinalStep<T> rowsFollowing(int number) {
rowsStart = number;
return this;
}
@Override
public final WindowRowsAndStep<T> rowsBetweenUnboundedPreceding() {
rowsUnboundedPreceding();
return this;
}
@Override
public final WindowRowsAndStep<T> rowsBetweenPreceding(int number) {
rowsPreceding(number);
return this;
}
@Override
public final WindowRowsAndStep<T> rowsBetweenCurrentRow() {
rowsCurrentRow();
return this;
}
@Override
public final WindowRowsAndStep<T> rowsBetweenUnboundedFollowing() {
rowsUnboundedFollowing();
return this;
}
@Override
public final WindowRowsAndStep<T> rowsBetweenFollowing(int number) {
rowsFollowing(number);
return this;
}
@Override
public final WindowFinalStep<T> andUnboundedPreceding() {
rowsEnd = Integer.MIN_VALUE;
return this;
}
@Override
public final WindowFinalStep<T> andPreceding(int number) {
rowsEnd = -number;
return this;
}
@Override
public final WindowFinalStep<T> andCurrentRow() {
rowsEnd = 0;
return this;
}
@Override
public final WindowFinalStep<T> andUnboundedFollowing() {
rowsEnd = Integer.MAX_VALUE;
return this;
}
@Override
public final WindowFinalStep<T> andFollowing(int number) {
rowsEnd = number;
return this;
}
}

View File

@ -56,4 +56,8 @@ class NamedQueryPartList<T extends NamedQueryPart> extends QueryPartList<T> {
NamedQueryPartList(Collection<? extends T> wrappedList) {
super(wrappedList);
}
NamedQueryPartList(T... wrappedList) {
super(wrappedList);
}
}

View File

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

View File

@ -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<T extends QueryPart> extends AbstractQueryPart implements Li
private final List<T> wrappedList = new ArrayList<T>();
QueryPartList() {
this(null);
this((Collection<T>) null);
}
QueryPartList(Collection<? extends T> wrappedList) {
@ -67,6 +68,11 @@ class QueryPartList<T extends QueryPart> extends AbstractQueryPart implements Li
}
}
QueryPartList(T... wrappedList) {
this(Arrays.asList(wrappedList));
}
@Override
public final List<Attachable> getAttachables() {
return getAttachables(this);

View File

@ -71,7 +71,7 @@ class SQLClause<T> extends AbstractField<T> {
@Override
public final List<Attachable> getAttachables() {
return null;
return getAttachables(parts);
}
@Override

View File

@ -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<T> extends AbstractField<T>
implements
// Cascading interface implementations for window function behaviour
WindowIgnoreNullsStep<T>,
WindowPartitionByStep<T>,
WindowRowsStep<T>,
WindowRowsAndStep<T>
{
/**
* 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<T> type, QueryPart... arguments) {
this(name, type, new SortFieldList(), arguments);
}
WindowFunction(Term term, DataType<T> type, QueryPart... arguments) {
this(term, type, new SortFieldList(), arguments);
}
WindowFunction(String name, DataType<T> type, SortFieldList withinGroupOrderBy, QueryPart... arguments) {
super(name, type);
this.partitionBy = new FieldList();
this.orderBy = new SortFieldList();
this.arguments = new QueryPartList<QueryPart>(Arrays.asList(arguments));
this.term = null;
this.withinGroupOrderBy = withinGroupOrderBy;
}
WindowFunction(Term term, DataType<T> type, SortFieldList withinGroupOrderBy, QueryPart... arguments) {
super(term.name().toLowerCase(), type);
this.partitionBy = new FieldList();
this.orderBy = new SortFieldList();
this.arguments = new QueryPartList<QueryPart>(Arrays.asList(arguments));
this.term = term;
this.withinGroupOrderBy = withinGroupOrderBy;
}
// -------------------------------------------------------------------------
// Field API
// -------------------------------------------------------------------------
@Override
public final List<Attachable> 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<T> ignoreNulls() {
ignoreNulls = true;
respectNulls = false;
return this;
}
@Override
public final WindowFunction<T> respectNulls() {
ignoreNulls = false;
respectNulls = true;
return this;
}
@Override
public final WindowFunction<T> over() {
return this;
}
@Override
public final WindowFunction<T> partitionBy(Field<?>... fields) {
partitionBy.addAll(Arrays.asList(fields));
return this;
}
@Override
public final WindowFunction<T> partitionByOne() {
partitionByOne = true;
partitionBy.add(one());
return this;
}
@Override
public final WindowFunction<T> orderBy(Field<?>... fields) {
orderBy.addAll(fields);
return this;
}
@Override
public final WindowFunction<T> orderBy(SortField<?>... fields) {
orderBy.addAll(Arrays.asList(fields));
return this;
}
@Override
public final WindowFunction<T> orderBy(Collection<SortField<?>> fields) {
orderBy.addAll(fields);
return this;
}
@Override
public final WindowFunction<T> rowsUnboundedPreceding() {
rowsStart = Integer.MIN_VALUE;
return this;
}
@Override
public final WindowFunction<T> rowsPreceding(int number) {
rowsStart = -number;
return this;
}
@Override
public final WindowFunction<T> rowsCurrentRow() {
rowsStart = 0;
return this;
}
@Override
public final WindowFunction<T> rowsUnboundedFollowing() {
rowsStart = Integer.MAX_VALUE;
return this;
}
@Override
public final WindowFunction<T> rowsFollowing(int number) {
rowsStart = number;
return this;
}
@Override
public final WindowFunction<T> rowsBetweenUnboundedPreceding() {
rowsUnboundedPreceding();
return this;
}
@Override
public final WindowFunction<T> rowsBetweenPreceding(int number) {
rowsPreceding(number);
return this;
}
@Override
public final WindowFunction<T> rowsBetweenCurrentRow() {
rowsCurrentRow();
return this;
}
@Override
public final WindowFunction<T> rowsBetweenUnboundedFollowing() {
rowsUnboundedFollowing();
return this;
}
@Override
public final WindowFunction<T> rowsBetweenFollowing(int number) {
rowsFollowing(number);
return this;
}
@Override
public final WindowFunction<T> andUnboundedPreceding() {
rowsEnd = Integer.MIN_VALUE;
return this;
}
@Override
public final WindowFunction<T> andPreceding(int number) {
rowsEnd = -number;
return this;
}
@Override
public final WindowFunction<T> andCurrentRow() {
rowsEnd = 0;
return this;
}
@Override
public final WindowFunction<T> andUnboundedFollowing() {
rowsEnd = Integer.MAX_VALUE;
return this;
}
@Override
public final WindowFunction<T> andFollowing(int number) {
rowsEnd = number;
return this;
}
}