[jOOQ/jOOQ#2333] Add DSL.noField() for "conditional" LIMIT, OFFSET, GROUP BY, ORDER BY support when creating dynamic SQL
This commit is contained in:
parent
b0674b341e
commit
f59c3b8d8c
@ -110,6 +110,7 @@ import static org.jooq.impl.Names.N_PERCENT_RANK;
|
||||
import static org.jooq.impl.Names.N_RANK;
|
||||
import static org.jooq.impl.Names.N_SYSTEM_TIME;
|
||||
import static org.jooq.impl.Names.N_VALUE;
|
||||
import static org.jooq.impl.SQLDataType.BOOLEAN;
|
||||
import static org.jooq.impl.SQLDataType.DATE;
|
||||
import static org.jooq.impl.SQLDataType.JSON;
|
||||
import static org.jooq.impl.SQLDataType.JSONB;
|
||||
@ -361,6 +362,10 @@ import org.jooq.Select;
|
||||
import org.jooq.SelectField;
|
||||
import org.jooq.SelectFieldOrAsterisk;
|
||||
import org.jooq.SelectForStep;
|
||||
import org.jooq.SelectGroupByStep;
|
||||
import org.jooq.SelectLimitStep;
|
||||
import org.jooq.SelectOffsetStep;
|
||||
import org.jooq.SelectOrderByStep;
|
||||
import org.jooq.SelectSelectStep;
|
||||
import org.jooq.SelectWhereStep;
|
||||
import org.jooq.Sequence;
|
||||
@ -12068,6 +12073,132 @@ public class DSL {
|
||||
return default_(field.getDataType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Field} that behaves like no field being present.
|
||||
* <p>
|
||||
* When creating dynamic SQL queries using expressions, it is often useful
|
||||
* to be able to decide dynamically whether a clause is being added to a
|
||||
* query or not. In case that clause accepts fields, the {@link #noField()}
|
||||
* can be used to avoid creating a clause, which is useful for the following
|
||||
* {@link Select} clauses, for example:
|
||||
* <ul>
|
||||
* <li>{@link SelectGroupByStep#groupBy(GroupField...)}</li>
|
||||
* <li>{@link SelectOrderByStep#orderBy(OrderField...)}</li>
|
||||
* <li>{@link SelectLimitStep#limit(Field)}</li>
|
||||
* <li>{@link SelectOffsetStep#offset(Field)}</li>
|
||||
* </ul>
|
||||
* It can also be useful for other, similar clauses, e.g. when passing
|
||||
* optional expressions to window function clauses, such as:
|
||||
* <ul>
|
||||
* <li>{@link DSL#partitionBy(Field...)}</li>
|
||||
* <li>{@link DSL#orderBy(Field...)}</li>
|
||||
* </ul>
|
||||
* In clauses that project fields to a given {@link Record} type, the
|
||||
* {@link #noField()} simply projects <code>NULL</code> and cannot be used
|
||||
* to avoid the clause.
|
||||
*/
|
||||
@NotNull
|
||||
@Support
|
||||
public static Field<?> noField() {
|
||||
return NoField.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Field} that behaves like no field being present.
|
||||
* <p>
|
||||
* When creating dynamic SQL queries using expressions, it is often useful
|
||||
* to be able to decide dynamically whether a clause is being added to a
|
||||
* query or not. In case that clause accepts fields, the {@link #noField()}
|
||||
* can be used to avoid creating a clause, which is useful for the following
|
||||
* {@link Select} clauses, for example:
|
||||
* <ul>
|
||||
* <li>{@link SelectGroupByStep#groupBy(GroupField...)}</li>
|
||||
* <li>{@link SelectOrderByStep#orderBy(OrderField...)}</li>
|
||||
* <li>{@link SelectLimitStep#limit(Field)}</li>
|
||||
* <li>{@link SelectOffsetStep#offset(Field)}</li>
|
||||
* </ul>
|
||||
* It can also be useful for other, similar clauses, e.g. when passing
|
||||
* optional expressions to window function clauses, such as:
|
||||
* <ul>
|
||||
* <li>{@link DSL#partitionBy(Field...)}</li>
|
||||
* <li>{@link DSL#orderBy(Field...)}</li>
|
||||
* </ul>
|
||||
* In clauses that project fields to a given {@link Record} type, the
|
||||
* {@link #noField()} simply projects <code>NULL</code> and cannot be used
|
||||
* to avoid the clause.
|
||||
*
|
||||
* @param type A class to derive the {@link Field#getDataType()} from.
|
||||
*/
|
||||
@NotNull
|
||||
@Support
|
||||
public static <T> Field<T> noField(Class<T> type) {
|
||||
return noField(getDataType(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Field} that behaves like no field being present.
|
||||
* <p>
|
||||
* When creating dynamic SQL queries using expressions, it is often useful
|
||||
* to be able to decide dynamically whether a clause is being added to a
|
||||
* query or not. In case that clause accepts fields, the {@link #noField()}
|
||||
* can be used to avoid creating a clause, which is useful for the following
|
||||
* {@link Select} clauses, for example:
|
||||
* <ul>
|
||||
* <li>{@link SelectGroupByStep#groupBy(GroupField...)}</li>
|
||||
* <li>{@link SelectOrderByStep#orderBy(OrderField...)}</li>
|
||||
* <li>{@link SelectLimitStep#limit(Field)}</li>
|
||||
* <li>{@link SelectOffsetStep#offset(Field)}</li>
|
||||
* </ul>
|
||||
* It can also be useful for other, similar clauses, e.g. when passing
|
||||
* optional expressions to window function clauses, such as:
|
||||
* <ul>
|
||||
* <li>{@link DSL#partitionBy(Field...)}</li>
|
||||
* <li>{@link DSL#orderBy(Field...)}</li>
|
||||
* </ul>
|
||||
* In clauses that project fields to a given {@link Record} type, the
|
||||
* {@link #noField()} simply projects <code>NULL</code> and cannot be used
|
||||
* to avoid the clause.
|
||||
*
|
||||
* @param type A type to derive the {@link Field#getDataType()} from.
|
||||
*/
|
||||
@NotNull
|
||||
@Support
|
||||
public static <T> Field<T> noField(DataType<T> type) {
|
||||
return new NoField<>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Field} that behaves like no field being present.
|
||||
* <p>
|
||||
* When creating dynamic SQL queries using expressions, it is often useful
|
||||
* to be able to decide dynamically whether a clause is being added to a
|
||||
* query or not. In case that clause accepts fields, the {@link #noField()}
|
||||
* can be used to avoid creating a clause, which is useful for the following
|
||||
* {@link Select} clauses, for example:
|
||||
* <ul>
|
||||
* <li>{@link SelectGroupByStep#groupBy(GroupField...)}</li>
|
||||
* <li>{@link SelectOrderByStep#orderBy(OrderField...)}</li>
|
||||
* <li>{@link SelectLimitStep#limit(Field)}</li>
|
||||
* <li>{@link SelectOffsetStep#offset(Field)}</li>
|
||||
* </ul>
|
||||
* It can also be useful for other, similar clauses, e.g. when passing
|
||||
* optional expressions to window function clauses, such as:
|
||||
* <ul>
|
||||
* <li>{@link DSL#partitionBy(Field...)}</li>
|
||||
* <li>{@link DSL#orderBy(Field...)}</li>
|
||||
* </ul>
|
||||
* In clauses that project fields to a given {@link Record} type, the
|
||||
* {@link #noField()} simply projects <code>NULL</code> and cannot be used
|
||||
* to avoid the clause.
|
||||
*
|
||||
* @param type A field to derive the {@link Field#getDataType()} from.
|
||||
*/
|
||||
@NotNull
|
||||
@Support
|
||||
public static <T> Field<T> noField(Field<T> field) {
|
||||
return noField(field.getDataType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a qualified catalog, given its catalog name.
|
||||
* <p>
|
||||
@ -20075,7 +20206,11 @@ public class DSL {
|
||||
@NotNull
|
||||
@Support
|
||||
public static Field<Boolean> field(Condition condition) {
|
||||
return condition instanceof FieldCondition ? ((FieldCondition) condition).field : new ConditionAsField(condition);
|
||||
return condition instanceof NoCondition
|
||||
? noField(BOOLEAN)
|
||||
: condition instanceof FieldCondition
|
||||
? ((FieldCondition) condition).field
|
||||
: new ConditionAsField(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -20094,7 +20229,11 @@ public class DSL {
|
||||
@NotNull
|
||||
@Support
|
||||
public static Condition condition(Field<Boolean> field) {
|
||||
return field instanceof ConditionAsField ? ((ConditionAsField) field).condition : new FieldCondition(field);
|
||||
return field instanceof NoField
|
||||
? noCondition()
|
||||
: field instanceof ConditionAsField
|
||||
? ((ConditionAsField) field).condition
|
||||
: new FieldCondition(field);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -91,6 +91,11 @@ final class GroupFieldList extends QueryPartList<GroupField> {
|
||||
super(wrappedList);
|
||||
}
|
||||
|
||||
@Override
|
||||
final boolean canAdd(GroupField e) {
|
||||
return super.canAdd(e) && !(e instanceof NoField);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean rendersContent(Context<?> ctx) {
|
||||
return true;
|
||||
|
||||
@ -257,6 +257,7 @@ final class JSONReader<R extends Record> {
|
||||
if (multiset) {
|
||||
|
||||
// [#12134] PostgreSQL encodes binary data as hex
|
||||
// TODO [#13427] This doesn't work if bytea_output is set to escape
|
||||
if (ENCODE_BINARY_AS_HEX.contains(ctx.dialect()))
|
||||
if (s.startsWith("\\x"))
|
||||
record.set(i, convertHexToBytes(s, 1, Integer.MAX_VALUE));
|
||||
|
||||
@ -446,6 +446,9 @@ final class Limit extends AbstractQueryPart implements UTransient {
|
||||
}
|
||||
|
||||
final void setOffset(Field<? extends Number> offset) {
|
||||
if (offset instanceof NoField)
|
||||
return;
|
||||
|
||||
this.offset = offset;
|
||||
this.offsetOrZero = offset == null ? ZERO : offset;
|
||||
}
|
||||
@ -456,6 +459,9 @@ final class Limit extends AbstractQueryPart implements UTransient {
|
||||
}
|
||||
|
||||
final void setLimit(Field<? extends Number> l) {
|
||||
if (l instanceof NoField)
|
||||
return;
|
||||
|
||||
this.limit = l;
|
||||
this.limitOrMax = l == null ? MAX : l;
|
||||
}
|
||||
|
||||
@ -224,6 +224,7 @@ final class Names {
|
||||
static final Name N_NOW = systemName("now");
|
||||
static final Name N_NTH_VALUE = systemName("nth_value");
|
||||
static final Name N_NTILE = systemName("ntile");
|
||||
static final Name N_NULL = systemName("null");
|
||||
static final Name N_NVL2 = systemName("nvl2");
|
||||
static final Name N_OPENJSON = systemName("openjson");
|
||||
static final Name N_OPENXML = systemName("openxml");
|
||||
|
||||
107
jOOQ/src/main/java/org/jooq/impl/NoField.java
Normal file
107
jOOQ/src/main/java/org/jooq/impl/NoField.java
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.inline;
|
||||
import static org.jooq.impl.Names.N_NULL;
|
||||
import static org.jooq.impl.SQLDataType.OTHER;
|
||||
|
||||
import org.jooq.Context;
|
||||
import org.jooq.DataType;
|
||||
import org.jooq.Field;
|
||||
import org.jooq.SortField;
|
||||
import org.jooq.SortOrder;
|
||||
import org.jooq.impl.QOM.NullOrdering;
|
||||
import org.jooq.impl.QOM.UEmptyField;
|
||||
|
||||
/**
|
||||
* @author Lukas Eder
|
||||
*/
|
||||
final class NoField<T>
|
||||
extends
|
||||
AbstractField<T>
|
||||
implements
|
||||
SortField<T>,
|
||||
UEmptyField<T>
|
||||
{
|
||||
|
||||
static final NoField<?> INSTANCE = new NoField<>(OTHER);
|
||||
|
||||
NoField(DataType<T> type) {
|
||||
super(N_NULL, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void accept(Context<?> ctx) {
|
||||
ctx.visit(inline(null, getDataType()));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// XXX: SortField API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public final SortOrder getOrder() {
|
||||
return SortOrder.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SortField<T> nullsFirst() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SortField<T> nullsLast() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Field<T> $field() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SortOrder $sortOrder() {
|
||||
return SortOrder.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final NullOrdering $nullOrdering() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -284,9 +284,13 @@ implements
|
||||
return wrapped.toArray(a);
|
||||
}
|
||||
|
||||
/* non-final */ boolean canAdd(T e) {
|
||||
return e != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean add(T e) {
|
||||
if (e != null)
|
||||
if (canAdd(e))
|
||||
return wrapped.add(e);
|
||||
|
||||
return false;
|
||||
@ -302,40 +306,20 @@ implements
|
||||
return wrapped.containsAll(c);
|
||||
}
|
||||
|
||||
final void addAll(Iterable<? extends T> c) {
|
||||
final boolean addAll0(Iterable<? extends T> c) {
|
||||
boolean modified = false;
|
||||
|
||||
if (c != null)
|
||||
for (T t : c)
|
||||
if (t != null)
|
||||
add(t);
|
||||
for (T e : c)
|
||||
if (add(e))
|
||||
modified = true;
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean addAll(Collection<? extends T> c) {
|
||||
return wrapped.addAll(removeNulls(c));
|
||||
}
|
||||
|
||||
final Collection<? extends T> removeNulls(Collection<? extends T> c) {
|
||||
|
||||
// [#2145] Collections that contain nulls are quite rare, so it is wise
|
||||
// to add a relatively cheap defender check to avoid unnecessary loops
|
||||
boolean containsNulls;
|
||||
|
||||
try {
|
||||
containsNulls = c.contains(null);
|
||||
}
|
||||
|
||||
// [#7991] Some immutable collections do not allow for nulls to be contained
|
||||
catch (NullPointerException ignore) {
|
||||
containsNulls = false;
|
||||
}
|
||||
|
||||
if (containsNulls) {
|
||||
List<T> list = new ArrayList<>(c);
|
||||
list.removeIf(Objects::isNull);
|
||||
return list;
|
||||
}
|
||||
else
|
||||
return c;
|
||||
return addAll0(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -75,7 +75,7 @@ extends
|
||||
QueryPartList(Iterable<? extends T> wrappedList) {
|
||||
super(new ArrayList<>());
|
||||
|
||||
addAll(wrappedList);
|
||||
addAll0(wrappedList);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -119,7 +119,13 @@ implements
|
||||
|
||||
@Override
|
||||
public final boolean addAll(int index, Collection<? extends T> c) {
|
||||
return wrapped().addAll(index, removeNulls(c));
|
||||
boolean modified = false;
|
||||
|
||||
for (T e : c)
|
||||
if (canAdd(e) && (modified |= true))
|
||||
wrapped().add(index++, e);
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -129,7 +135,7 @@ implements
|
||||
|
||||
@Override
|
||||
public final T set(int index, T element) {
|
||||
if (element != null)
|
||||
if (canAdd(element))
|
||||
return wrapped().set(index, element);
|
||||
|
||||
return null;
|
||||
@ -137,7 +143,7 @@ implements
|
||||
|
||||
@Override
|
||||
public final void add(int index, T element) {
|
||||
if (element != null)
|
||||
if (canAdd(element))
|
||||
wrapped().add(index, element);
|
||||
}
|
||||
|
||||
|
||||
@ -194,6 +194,7 @@ import static org.jooq.impl.Tools.autoAlias;
|
||||
import static org.jooq.impl.Tools.camelCase;
|
||||
import static org.jooq.impl.Tools.containsUnaliasedTable;
|
||||
import static org.jooq.impl.Tools.fieldArray;
|
||||
import static org.jooq.impl.Tools.filter;
|
||||
import static org.jooq.impl.Tools.hasAmbiguousNames;
|
||||
import static org.jooq.impl.Tools.isEmpty;
|
||||
import static org.jooq.impl.Tools.isNotEmpty;
|
||||
@ -4128,8 +4129,8 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
|
||||
// semantics
|
||||
if (fields.isEmpty())
|
||||
groupBy.add(emptyGroupingSet());
|
||||
|
||||
groupBy.addAll(fields);
|
||||
else
|
||||
groupBy.addAll(fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -65,6 +65,11 @@ final class SortFieldList extends QueryPartList<SortField<?>> {
|
||||
addAll(Tools.map(fields, f -> f.asc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
final boolean canAdd(SortField<?> e) {
|
||||
return super.canAdd(e) && !(e instanceof NoField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the {@link SortField}s in this list are uniformly sorted, e.g.
|
||||
* all {@link SortOrder#ASC} or all {@link SortOrder#DESC}.
|
||||
|
||||
@ -124,12 +124,12 @@ implements
|
||||
WindowSpecificationExcludeStep
|
||||
{
|
||||
|
||||
private static final Set<SQLDialect> OMIT_PARTITION_BY_ONE = SQLDialect.supportedBy(CUBRID, MYSQL, SQLITE);
|
||||
private static final Set<SQLDialect> OMIT_PARTITION_BY_ONE = SQLDialect.supportedBy(CUBRID, MYSQL, SQLITE);
|
||||
|
||||
private static final Set<SQLDialect> REQUIRES_ORDER_BY_IN_LEAD_LAG = SQLDialect.supportedBy(H2, MARIADB);
|
||||
private static final Set<SQLDialect> REQUIRES_ORDER_BY_IN_NTILE = SQLDialect.supportedBy(H2);
|
||||
private static final Set<SQLDialect> REQUIRES_ORDER_BY_IN_RANK_DENSE_RANK = SQLDialect.supportedBy(H2, MARIADB);
|
||||
private static final Set<SQLDialect> REQUIRES_ORDER_BY_IN_PERCENT_RANK_CUME_DIST = SQLDialect.supportedBy(MARIADB);
|
||||
private static final Set<SQLDialect> REQUIRES_ORDER_BY_IN_LEAD_LAG = SQLDialect.supportedBy(H2, MARIADB);
|
||||
private static final Set<SQLDialect> REQUIRES_ORDER_BY_IN_NTILE = SQLDialect.supportedBy(H2);
|
||||
private static final Set<SQLDialect> REQUIRES_ORDER_BY_IN_RANK_DENSE_RANK = SQLDialect.supportedBy(H2, MARIADB);
|
||||
private static final Set<SQLDialect> REQUIRES_ORDER_BY_IN_PERCENT_RANK_CUME_DIST = SQLDialect.supportedBy(MARIADB);
|
||||
|
||||
|
||||
|
||||
@ -138,14 +138,14 @@ implements
|
||||
|
||||
|
||||
|
||||
private final WindowDefinitionImpl windowDefinition;
|
||||
private final QueryPartList<Field<?>> partitionBy;
|
||||
private final SortFieldList orderBy;
|
||||
private Integer frameStart;
|
||||
private Integer frameEnd;
|
||||
private FrameUnits frameUnits;
|
||||
private FrameExclude exclude;
|
||||
private boolean partitionByOne;
|
||||
private final WindowDefinitionImpl windowDefinition;
|
||||
private final GroupFieldList partitionBy;
|
||||
private final SortFieldList orderBy;
|
||||
private Integer frameStart;
|
||||
private Integer frameEnd;
|
||||
private FrameUnits frameUnits;
|
||||
private FrameExclude exclude;
|
||||
private boolean partitionByOne;
|
||||
|
||||
WindowSpecificationImpl() {
|
||||
this(null);
|
||||
@ -153,7 +153,7 @@ implements
|
||||
|
||||
WindowSpecificationImpl(WindowDefinitionImpl windowDefinition) {
|
||||
this.windowDefinition = windowDefinition;
|
||||
this.partitionBy = new QueryPartList<>();
|
||||
this.partitionBy = new GroupFieldList();
|
||||
this.orderBy = new SortFieldList();
|
||||
}
|
||||
|
||||
@ -699,7 +699,7 @@ implements
|
||||
|
||||
@Override
|
||||
public final UnmodifiableList<? extends Field<?>> $partitionBy() {
|
||||
return QOM.unmodifiable(partitionBy);
|
||||
return QOM.unmodifiable((QueryPartList) partitionBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Loading…
Reference in New Issue
Block a user