diff --git a/jOOQ/src/main/java/org/jooq/Comparator.java b/jOOQ/src/main/java/org/jooq/Comparator.java index 615cf590b2..691f8511bc 100644 --- a/jOOQ/src/main/java/org/jooq/Comparator.java +++ b/jOOQ/src/main/java/org/jooq/Comparator.java @@ -66,82 +66,66 @@ import org.jetbrains.annotations.NotNull; public enum Comparator { @NotNull - @Support IN("in", false, true), @NotNull - @Support NOT_IN("not in", false, true), @NotNull - @Support EQUALS("=", true, true), @NotNull - @Support NOT_EQUALS("<>", true, true), @NotNull - @Support LESS("<", true, true), @NotNull - @Support LESS_OR_EQUAL("<=", true, true), @NotNull - @Support GREATER(">", true, true), @NotNull - @Support GREATER_OR_EQUAL(">=", true, true), @NotNull - @Support IS_DISTINCT_FROM("is distinct from", false, false), @NotNull - @Support IS_NOT_DISTINCT_FROM("is not distinct from", false, false), @NotNull - @Support LIKE("like", false, false), @NotNull - @Support NOT_LIKE("not like", false, false), @NotNull - @Support({ FIREBIRD, POSTGRES }) SIMILAR_TO("similar to", false, false), @NotNull - @Support({ FIREBIRD, POSTGRES }) NOT_SIMILAR_TO("not similar to", false, false), @NotNull - @Support LIKE_IGNORE_CASE("ilike", false, false), @NotNull - @Support NOT_LIKE_IGNORE_CASE("not ilike", false, false), @@ -173,6 +157,10 @@ public enum Comparator { return keyword; } + /** + * Get the inverse comparator such that A [op] B and + * NOT(A [inverse op] B). + */ public Comparator inverse() { switch (this) { case EQUALS: return NOT_EQUALS; @@ -195,6 +183,25 @@ public enum Comparator { } } + /** + * Get the mirrored comparator such that A [op] B and + * B [mirrored op] A, or null if the comparator + * cannot be mirrored. + */ + public Comparator mirror() { + switch (this) { + case EQUALS: return EQUALS; + case GREATER: return LESS; + case GREATER_OR_EQUAL: return LESS_OR_EQUAL; + case IS_DISTINCT_FROM: return IS_DISTINCT_FROM; + case IS_NOT_DISTINCT_FROM: return IS_NOT_DISTINCT_FROM; + case LESS: return GREATER; + case LESS_OR_EQUAL: return GREATER_OR_EQUAL; + case NOT_EQUALS: return NOT_EQUALS; + default: return null; + } + } + /** * Whether this comparator supports quantifiers on the right-hand side. * diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index 8cff54b40b..b142df9b86 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -94,6 +94,8 @@ public class Settings protected Boolean transformAnsiJoinToTableLists = false; @XmlElement(defaultValue = "false") protected Boolean transformTableListsToAnsiJoin = false; + @XmlElement(defaultValue = "false") + protected Boolean transformRownumToLimit = false; @XmlElement(defaultValue = "NEVER") @XmlSchemaType(name = "string") protected TransformUnneededArithmeticExpressions transformUnneededArithmeticExpressions = TransformUnneededArithmeticExpressions.NEVER; @@ -847,6 +849,36 @@ public class Settings this.transformTableListsToAnsiJoin = value; } + /** + * Transform ROWNUM expressions to corresponding LIMIT clauses or ROW_NUMBER() expressions. + *

+ * In Oracle 11g and less, ROWNUM filtering was the most popular way to paginate. This pseudo + * column is not supported in other RDBMS, and should be replaced in Oracle 12c by the FETCH clause. This + * transformation allows for replacing such a filter by equivalent SQL, if possible. + *

+ * This feature is available in the commercial distribution only. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isTransformRownumToLimit() { + return transformRownumToLimit; + } + + /** + * Sets the value of the transformRownumToLimit property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setTransformRownumToLimit(Boolean value) { + this.transformRownumToLimit = value; + } + /** * Transform arithmetic expressions on literals and bind variables. *

@@ -2432,6 +2464,11 @@ public class Settings return this; } + public Settings withTransformRownumToLimit(Boolean value) { + setTransformRownumToLimit(value); + return this; + } + /** * Transform arithmetic expressions on literals and bind variables. *

@@ -3018,6 +3055,7 @@ public class Settings builder.append("fetchTriggerValuesAfterSQLServerOutput", fetchTriggerValuesAfterSQLServerOutput); builder.append("transformAnsiJoinToTableLists", transformAnsiJoinToTableLists); builder.append("transformTableListsToAnsiJoin", transformTableListsToAnsiJoin); + builder.append("transformRownumToLimit", transformRownumToLimit); builder.append("transformUnneededArithmeticExpressions", transformUnneededArithmeticExpressions); builder.append("backslashEscaping", backslashEscaping); builder.append("paramType", paramType); @@ -3324,6 +3362,15 @@ public class Settings return false; } } + if (transformRownumToLimit == null) { + if (other.transformRownumToLimit!= null) { + return false; + } + } else { + if (!transformRownumToLimit.equals(other.transformRownumToLimit)) { + return false; + } + } if (transformUnneededArithmeticExpressions == null) { if (other.transformUnneededArithmeticExpressions!= null) { return false; @@ -3976,6 +4023,7 @@ public class Settings result = ((prime*result)+((fetchTriggerValuesAfterSQLServerOutput == null)? 0 :fetchTriggerValuesAfterSQLServerOutput.hashCode())); result = ((prime*result)+((transformAnsiJoinToTableLists == null)? 0 :transformAnsiJoinToTableLists.hashCode())); result = ((prime*result)+((transformTableListsToAnsiJoin == null)? 0 :transformTableListsToAnsiJoin.hashCode())); + result = ((prime*result)+((transformRownumToLimit == null)? 0 :transformRownumToLimit.hashCode())); result = ((prime*result)+((transformUnneededArithmeticExpressions == null)? 0 :transformUnneededArithmeticExpressions.hashCode())); result = ((prime*result)+((backslashEscaping == null)? 0 :backslashEscaping.hashCode())); result = ((prime*result)+((paramType == null)? 0 :paramType.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/impl/CombinedCondition.java b/jOOQ/src/main/java/org/jooq/impl/CombinedCondition.java index d558662168..1c0b15258b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CombinedCondition.java +++ b/jOOQ/src/main/java/org/jooq/impl/CombinedCondition.java @@ -104,10 +104,8 @@ final class CombinedCondition extends AbstractCondition { return noCondition(); // [#9998] Otherwise, return the identity for the operator - else if (operator == AND) - return trueCondition(); else - return falseCondition(); + return identity(operator); } @Override @@ -119,6 +117,36 @@ final class CombinedCondition extends AbstractCondition { return false; } + static final Condition identity(Operator operator) { + return operator == AND ? trueCondition() : falseCondition(); + } + + final Condition transform(F1 function) { + List newList = null; + + for (int i = 0; i < conditions.size(); i++) { + Condition oldC = conditions.get(i); + Condition newC = oldC instanceof CombinedCondition + ? ((CombinedCondition) oldC).transform(function) + : function.apply(oldC); + + if (newC != oldC) { + if (newList == null) + newList = new ArrayList<>(conditions.subList(0, i)); + + if (newC != null) + newList.add(newC); + } + else if (newList != null) + newList.add(newC); + } + + if (newList == null) + return this; + else + return of(operator, newList); + } + private CombinedCondition(Operator operator, int size) { if (operator == null) throw new IllegalArgumentException("The argument 'operator' must not be null"); diff --git a/jOOQ/src/main/java/org/jooq/impl/ConditionProviderImpl.java b/jOOQ/src/main/java/org/jooq/impl/ConditionProviderImpl.java index a574395945..dd4aabdb77 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ConditionProviderImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ConditionProviderImpl.java @@ -55,14 +55,15 @@ import org.jooq.Select; /** * @author Lukas Eder */ -@SuppressWarnings("deprecation") final class ConditionProviderImpl extends AbstractQueryPart implements ConditionProvider, Condition { private static final long serialVersionUID = 6073328960551062973L; private Condition condition; - ConditionProviderImpl() { + ConditionProviderImpl() {} + ConditionProviderImpl(Condition condition) { + this.condition = condition; } final Condition getWhere() { diff --git a/jOOQ/src/main/java/org/jooq/impl/Limit.java b/jOOQ/src/main/java/org/jooq/impl/Limit.java index ce667ab191..6273232e31 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Limit.java +++ b/jOOQ/src/main/java/org/jooq/impl/Limit.java @@ -84,14 +84,14 @@ final class Limit extends AbstractQueryPart { private static final Field ONE = one(); private static final Param MAX = DSL.inline(Integer.MAX_VALUE); - private Field numberOfRows; + Param numberOfRows; private Field numberOfRowsOrMax = MAX; - private Field offset; + Param offset; private Field offsetOrZero = ZERO; private Field offsetPlusOne = ONE; private boolean rendersParams; - private boolean withTies; - private boolean percent; + boolean withTies; + boolean percent; @Override public final void accept(Context ctx) { @@ -402,8 +402,7 @@ final class Limit extends AbstractQueryPart { return !limitZero() && !withTies() && !percent() - && numberOfRows instanceof Param - && Long.valueOf(1L).equals(((Param) numberOfRows).getValue()); + && Long.valueOf(1L).equals(numberOfRows.getValue()); } /** @@ -452,7 +451,7 @@ final class Limit extends AbstractQueryPart { this.offsetPlusOne = val(offset.longValue() + 1L, BIGINT); } - final void setOffset(Param offset) { + final void setOffset(Param offset) { this.offset = offset; this.offsetOrZero = offset; this.rendersParams = rendersParams |= offset.isInline(); @@ -463,7 +462,7 @@ final class Limit extends AbstractQueryPart { this.numberOfRowsOrMax = this.numberOfRows; } - final void setNumberOfRows(Param numberOfRows) { + final void setNumberOfRows(Param numberOfRows) { this.numberOfRows = numberOfRows; this.numberOfRowsOrMax = numberOfRows; this.rendersParams |= numberOfRows.isInline(); @@ -484,4 +483,17 @@ final class Limit extends AbstractQueryPart { final boolean withTies() { return withTies; } + + final Limit from(Limit limit) { + if (limit.numberOfRows != null) + this.setNumberOfRows(limit.numberOfRows); + + if (limit.offset != null) + this.setOffset(limit.offset); + + this.setPercent(limit.percent); + this.setWithTies(limit.withTies); + + return this; + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Plus.java b/jOOQ/src/main/java/org/jooq/impl/Plus.java index 762c8fc6e5..cba147283f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Plus.java +++ b/jOOQ/src/main/java/org/jooq/impl/Plus.java @@ -89,5 +89,6 @@ package org.jooq.impl; + diff --git a/jOOQ/src/main/java/org/jooq/impl/Rownum.java b/jOOQ/src/main/java/org/jooq/impl/Rownum.java new file mode 100644 index 0000000000..84a31199a1 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/Rownum.java @@ -0,0 +1,88 @@ +/* + * 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; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index a7274d399a..3bf0f2b18e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -81,6 +81,7 @@ import static org.jooq.SQLDialect.MYSQL; // ... // ... // ... +// ... import static org.jooq.SQLDialect.POSTGRES; // ... // ... @@ -191,6 +192,7 @@ import java.util.stream.Stream; import org.jooq.Asterisk; import org.jooq.Clause; +import org.jooq.Comparator; import org.jooq.Condition; import org.jooq.Configuration; import org.jooq.Context; @@ -228,6 +230,7 @@ import org.jooq.impl.ForLock.ForLockWaitMode; import org.jooq.impl.Tools.BooleanDataKey; import org.jooq.impl.Tools.DataExtendedKey; import org.jooq.impl.Tools.DataKey; +import org.jooq.tools.Convert; import org.jooq.tools.JooqLogger; import org.jooq.tools.StringUtils; @@ -281,6 +284,8 @@ final class SelectQueryImpl extends AbstractResultQuery imp + + @@ -373,6 +378,50 @@ final class SelectQueryImpl extends AbstractResultQuery imp this.from.add(from.asTable()); } + SelectQueryImpl copy() { + SelectQueryImpl result = new SelectQueryImpl<>(configuration(), with); + + result.condition.setWhere(condition.getWhere()); + result.connectBy.setWhere(connectBy.getWhere()); + result.connectByNoCycle = connectByNoCycle; + result.connectByStartWith.setWhere(connectByStartWith.getWhere()); + result.distinct = distinct; + result.distinctOn = distinctOn; + result.forJSON = forJSON; + result.forLock = forLock; + result.forXML = forXML; + result.from.addAll(from); + result.groupBy = groupBy; + result.grouping = grouping; + result.having.setWhere(having.getWhere()); + result.hint = hint; + result.into = into; + result.limit.from(limit); + + result.option = option; + result.orderBy.addAll(orderBy); + result.orderBySiblings = orderBySiblings; + result.qualify.setWhere(qualify.getWhere()); + result.seek.addAll(seek); + result.select.addAll(select); + + // TODO: Should the remaining union subqueries also be copied? + result.union.addAll(union); + result.unionLimit.from(unionLimit); + result.unionOp.addAll(unionOp); + result.unionOrderBy.addAll(unionOrderBy); + result.unionOrderBySiblings = unionOrderBySiblings; + result.unionSeek.addAll(unionSeek); + result.unionSeekBefore = unionSeekBefore; + + if (window != null) + result.addWindow(window); + result.withCheckOption = withCheckOption; + result.withReadOnly = withReadOnly; + + return result; + } + @Override public final int fetchCount() throws DataAccessException { return DSL.using(configuration()).fetchCount(this); @@ -862,6 +911,173 @@ final class SelectQueryImpl extends AbstractResultQuery imp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -884,15 +1100,15 @@ final class SelectQueryImpl extends AbstractResultQuery imp @Override - public final void accept(Context context) { + public final void accept(Context ctx) { Table dmlTable; // [#6583] Work around MySQL's self-reference-in-DML-subquery restriction - if (context.subqueryLevel() == 1 - && REQUIRES_DERIVED_TABLE_DML.contains(context.dialect()) - && (dmlTable = (Table) context.data(DATA_DML_TARGET_TABLE)) != null + if (ctx.subqueryLevel() == 1 + && REQUIRES_DERIVED_TABLE_DML.contains(ctx.dialect()) + && (dmlTable = (Table) ctx.data(DATA_DML_TARGET_TABLE)) != null && containsTable(dmlTable)) { - context.visit(DSL.select(asterisk()).from(asTable("t"))); + ctx.visit(DSL.select(asterisk()).from(asTable("t"))); } @@ -914,8 +1130,28 @@ final class SelectQueryImpl extends AbstractResultQuery imp - else - accept0(context); + + + + + + + + + + else { + + + + + + + accept0(ctx); + + + + + } } public final void accept0(Context context) { diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 8e718ec23d..3d132fbb32 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -656,6 +656,12 @@ final class Tools { + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/Val.java b/jOOQ/src/main/java/org/jooq/impl/Val.java index 37e5c2d3b5..01c1ce43ad 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Val.java +++ b/jOOQ/src/main/java/org/jooq/impl/Val.java @@ -104,6 +104,12 @@ final class Val extends AbstractParam { return (Val) this; } + final Val copy(Object newValue) { + Val w = new Val<>(getDataType().convert(newValue), getDataType(), getParamName()); + w.setInline(isInline()); + return w; + } + private final Val convertTo0(DataType type) { Val w = new Val<>(type.convert(getValue()), type, getParamName()); w.setInline(isInline()); diff --git a/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd b/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd index 1ddb7e8bd3..d364602c8b 100644 --- a/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd +++ b/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd @@ -216,6 +216,16 @@ this flag enables the transformation to ANSI join syntax. This feature is available in the commercial distribution only.]]> + + ROWNUM expressions to corresponding LIMIT clauses or ROW_NUMBER() expressions. +

+In Oracle 11g and less, ROWNUM filtering was the most popular way to paginate. This pseudo +column is not supported in other RDBMS, and should be replaced in Oracle 12c by the FETCH clause. This +transformation allows for replacing such a filter by equivalent SQL, if possible. +

+This feature is available in the commercial distribution only.]]> + +