diff --git a/jOOQ/src/main/java/org/jooq/SelectLimitPercentAfterOffsetStep.java b/jOOQ/src/main/java/org/jooq/SelectLimitPercentAfterOffsetStep.java index 013e58f80c..4106c241f9 100644 --- a/jOOQ/src/main/java/org/jooq/SelectLimitPercentAfterOffsetStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectLimitPercentAfterOffsetStep.java @@ -43,6 +43,7 @@ import org.jetbrains.annotations.*; import static org.jooq.SQLDialect.H2; // ... // ... +// ... /** * This type is used for the {@link Select}'s DSL API when selecting generic diff --git a/jOOQ/src/main/java/org/jooq/SelectLimitPercentStep.java b/jOOQ/src/main/java/org/jooq/SelectLimitPercentStep.java index 4f17dc608c..8642cc4340 100644 --- a/jOOQ/src/main/java/org/jooq/SelectLimitPercentStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectLimitPercentStep.java @@ -43,6 +43,7 @@ import org.jetbrains.annotations.*; import static org.jooq.SQLDialect.H2; // ... // ... +// ... /** * This type is used for the {@link Select}'s DSL API when selecting generic diff --git a/jOOQ/src/main/java/org/jooq/conf/RenderImplicitWindowRange.java b/jOOQ/src/main/java/org/jooq/conf/RenderImplicitWindowRange.java new file mode 100644 index 0000000000..350b94d14d --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/conf/RenderImplicitWindowRange.java @@ -0,0 +1,43 @@ + +package org.jooq.conf; + +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for RenderImplicitWindowRange. + * + *

The following schema fragment specifies the expected content contained within this class. + *

+ * <simpleType name="RenderImplicitWindowRange">
+ *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
+ *     <enumeration value="OFF"/>
+ *     <enumeration value="ROWS_UNBOUNDED_PRECEDING"/>
+ *     <enumeration value="ROWS_ALL"/>
+ *     <enumeration value="RANGE_UNBOUNDED_PRECEDING"/>
+ *     <enumeration value="RANGE_ALL"/>
+ *   </restriction>
+ * </simpleType>
+ * 
+ * + */ +@XmlType(name = "RenderImplicitWindowRange") +@XmlEnum +public enum RenderImplicitWindowRange { + + OFF, + ROWS_UNBOUNDED_PRECEDING, + ROWS_ALL, + RANGE_UNBOUNDED_PRECEDING, + RANGE_ALL; + + public String value() { + return name(); + } + + public static RenderImplicitWindowRange fromValue(String v) { + return valueOf(v); + } + +} diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index fad547693f..f08180dd25 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -79,6 +79,9 @@ public class Settings @XmlElement(defaultValue = "DEFAULT") @XmlSchemaType(name = "string") protected RenderOptionalKeyword renderOptionalOuterKeyword = RenderOptionalKeyword.DEFAULT; + @XmlElement(defaultValue = "OFF") + @XmlSchemaType(name = "string") + protected RenderImplicitWindowRange renderImplicitWindowRange = RenderImplicitWindowRange.OFF; @XmlElement(defaultValue = "false") protected Boolean renderScalarSubqueriesForStoredFunctions = false; @XmlElement(defaultValue = "DEFAULT") @@ -288,6 +291,9 @@ public class Settings @XmlElement(defaultValue = "OFF") @XmlSchemaType(name = "string") protected ParseWithMetaLookups parseWithMetaLookups = ParseWithMetaLookups.OFF; + @XmlElement(defaultValue = "WHEN_NEEDED") + @XmlSchemaType(name = "string") + protected Transformation parseAppendMissingTableReferences = Transformation.WHEN_NEEDED; @XmlElement(defaultValue = "false") protected Boolean parseSetCommands = false; @XmlElement(defaultValue = "IGNORE") @@ -680,6 +686,22 @@ public class Settings this.renderOptionalOuterKeyword = value; } + /** + * Whether to render an explicit window RANGE clause when an implicit clause is applied. + * + */ + public RenderImplicitWindowRange getRenderImplicitWindowRange() { + return renderImplicitWindowRange; + } + + /** + * Whether to render an explicit window RANGE clause when an implicit clause is applied. + * + */ + public void setRenderImplicitWindowRange(RenderImplicitWindowRange value) { + this.renderImplicitWindowRange = value; + } + /** * Whether stored function calls should be wrapped in scalar subqueries. *

@@ -2628,6 +2650,36 @@ public class Settings this.parseWithMetaLookups = value; } + /** + * Transform the parsed SQL to append missing table references to the query's FROM or USING clause, if applicable. + *

+ * Teradata (and possibly others) allow for referencing tables that are not listed in the FROM + * clause, such as SELECT t.* FROM t WHERE t.i = u.i. This transformation is executed in the + * parser, to produce SELECT t.* FROM t, u WHERE t.i = u.i, instead. By default, it is active + * when the input dialect supports this syntax. + *

+ * This feature is available in the commercial distribution only. + * + */ + public Transformation getParseAppendMissingTableReferences() { + return parseAppendMissingTableReferences; + } + + /** + * Transform the parsed SQL to append missing table references to the query's FROM or USING clause, if applicable. + *

+ * Teradata (and possibly others) allow for referencing tables that are not listed in the FROM + * clause, such as SELECT t.* FROM t WHERE t.i = u.i. This transformation is executed in the + * parser, to produce SELECT t.* FROM t, u WHERE t.i = u.i, instead. By default, it is active + * when the input dialect supports this syntax. + *

+ * This feature is available in the commercial distribution only. + * + */ + public void setParseAppendMissingTableReferences(Transformation value) { + this.parseAppendMissingTableReferences = value; + } + /** * [#9780] Whether commands of the type SET key = value should be parsed rather than ignored. * @@ -2984,6 +3036,15 @@ public class Settings return this; } + /** + * Whether to render an explicit window RANGE clause when an implicit clause is applied. + * + */ + public Settings withRenderImplicitWindowRange(RenderImplicitWindowRange value) { + setRenderImplicitWindowRange(value); + return this; + } + public Settings withRenderScalarSubqueriesForStoredFunctions(Boolean value) { setRenderScalarSubqueriesForStoredFunctions(value); return this; @@ -3667,6 +3728,22 @@ public class Settings return this; } + /** + * Transform the parsed SQL to append missing table references to the query's FROM or USING clause, if applicable. + *

+ * Teradata (and possibly others) allow for referencing tables that are not listed in the FROM + * clause, such as SELECT t.* FROM t WHERE t.i = u.i. This transformation is executed in the + * parser, to produce SELECT t.* FROM t, u WHERE t.i = u.i, instead. By default, it is active + * when the input dialect supports this syntax. + *

+ * This feature is available in the commercial distribution only. + * + */ + public Settings withParseAppendMissingTableReferences(Transformation value) { + setParseAppendMissingTableReferences(value); + return this; + } + public Settings withParseSetCommands(Boolean value) { setParseSetCommands(value); return this; @@ -3805,6 +3882,7 @@ public class Settings builder.append("renderOptionalAsKeywordForFieldAliases", renderOptionalAsKeywordForFieldAliases); builder.append("renderOptionalInnerKeyword", renderOptionalInnerKeyword); builder.append("renderOptionalOuterKeyword", renderOptionalOuterKeyword); + builder.append("renderImplicitWindowRange", renderImplicitWindowRange); builder.append("renderScalarSubqueriesForStoredFunctions", renderScalarSubqueriesForStoredFunctions); builder.append("renderImplicitJoinType", renderImplicitJoinType); builder.append("renderDefaultNullability", renderDefaultNullability); @@ -3893,6 +3971,7 @@ public class Settings builder.append("parseNamedParamPrefix", parseNamedParamPrefix); builder.append("parseNameCase", parseNameCase); builder.append("parseWithMetaLookups", parseWithMetaLookups); + builder.append("parseAppendMissingTableReferences", parseAppendMissingTableReferences); builder.append("parseSetCommands", parseSetCommands); builder.append("parseUnsupportedSyntax", parseUnsupportedSyntax); builder.append("parseUnknownFunctions", parseUnknownFunctions); @@ -4078,6 +4157,15 @@ public class Settings return false; } } + if (renderImplicitWindowRange == null) { + if (other.renderImplicitWindowRange!= null) { + return false; + } + } else { + if (!renderImplicitWindowRange.equals(other.renderImplicitWindowRange)) { + return false; + } + } if (renderScalarSubqueriesForStoredFunctions == null) { if (other.renderScalarSubqueriesForStoredFunctions!= null) { return false; @@ -4870,6 +4958,15 @@ public class Settings return false; } } + if (parseAppendMissingTableReferences == null) { + if (other.parseAppendMissingTableReferences!= null) { + return false; + } + } else { + if (!parseAppendMissingTableReferences.equals(other.parseAppendMissingTableReferences)) { + return false; + } + } if (parseSetCommands == null) { if (other.parseSetCommands!= null) { return false; @@ -4993,6 +5090,7 @@ public class Settings result = ((prime*result)+((renderOptionalAsKeywordForFieldAliases == null)? 0 :renderOptionalAsKeywordForFieldAliases.hashCode())); result = ((prime*result)+((renderOptionalInnerKeyword == null)? 0 :renderOptionalInnerKeyword.hashCode())); result = ((prime*result)+((renderOptionalOuterKeyword == null)? 0 :renderOptionalOuterKeyword.hashCode())); + result = ((prime*result)+((renderImplicitWindowRange == null)? 0 :renderImplicitWindowRange.hashCode())); result = ((prime*result)+((renderScalarSubqueriesForStoredFunctions == null)? 0 :renderScalarSubqueriesForStoredFunctions.hashCode())); result = ((prime*result)+((renderImplicitJoinType == null)? 0 :renderImplicitJoinType.hashCode())); result = ((prime*result)+((renderDefaultNullability == null)? 0 :renderDefaultNullability.hashCode())); @@ -5081,6 +5179,7 @@ public class Settings result = ((prime*result)+((parseNamedParamPrefix == null)? 0 :parseNamedParamPrefix.hashCode())); result = ((prime*result)+((parseNameCase == null)? 0 :parseNameCase.hashCode())); result = ((prime*result)+((parseWithMetaLookups == null)? 0 :parseWithMetaLookups.hashCode())); + result = ((prime*result)+((parseAppendMissingTableReferences == null)? 0 :parseAppendMissingTableReferences.hashCode())); result = ((prime*result)+((parseSetCommands == null)? 0 :parseSetCommands.hashCode())); result = ((prime*result)+((parseUnsupportedSyntax == null)? 0 :parseUnsupportedSyntax.hashCode())); result = ((prime*result)+((parseUnknownFunctions == null)? 0 :parseUnknownFunctions.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/impl/DeleteQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/DeleteQueryImpl.java index fab4ade663..ee650b1c7d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DeleteQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/DeleteQueryImpl.java @@ -143,6 +143,10 @@ final class DeleteQueryImpl extends AbstractDMLQuery implem return condition.hasWhere(); } + final TableList getUsing() { + return using; + } + @Override public final void addUsing(Collection> f) { for (TableLike provider : f) diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldProxy.java b/jOOQ/src/main/java/org/jooq/impl/FieldProxy.java index 2336359dfe..d4e9910ffe 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldProxy.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldProxy.java @@ -41,6 +41,7 @@ import org.jooq.Clause; import org.jooq.Context; import org.jooq.Field; import org.jooq.Name; +import org.jooq.Query; import org.jooq.Record; import org.jooq.Table; import org.jooq.TableField; @@ -54,8 +55,26 @@ import org.jooq.TableField; @SuppressWarnings("unchecked") final class FieldProxy extends AbstractField implements TableField { - private AbstractField delegate; - private int position; + /** + * The resolved field after a successful meta lookup. + */ + private AbstractField delegate; + + /** + * The position in the parsed SQL string where this field proxy was + * encountered. + */ + private int position; + + /** + * The scope owner that produced this field proxy. + */ + Query scopeOwner; + + /** + * Whether this FieldProxy could be resolved at some scope level. + */ + boolean resolved; FieldProxy(AbstractField delegate, int position) { super( @@ -69,16 +88,29 @@ final class FieldProxy extends AbstractField implements TableField newDelegate) { + final void delegate(AbstractField newDelegate) { + resolve(); this.delegate = newDelegate; ((DataTypeProxy) getDataType()).type((AbstractDataType) newDelegate.getDataType()); } + final FieldProxy resolve() { + this.resolved = true; + this.scopeOwner = null; + + return this; + } + + final void scopeOwner(Query query) { + if (!resolved && scopeOwner == null) + scopeOwner = query; + } + @Override public final Name getQualifiedName() { return delegate.getQualifiedName(); diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index afcf9a2cd3..8130de60b7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -81,8 +81,12 @@ import static org.jooq.impl.Tools.EMPTY_SORTFIELD; import static org.jooq.impl.Tools.EMPTY_TABLE; import static org.jooq.impl.Tools.aliased; import static org.jooq.impl.Tools.anyMatch; +import static org.jooq.impl.Tools.deleteQueryImpl; import static org.jooq.impl.Tools.findAny; import static org.jooq.impl.Tools.normaliseNameCase; +import static org.jooq.impl.Tools.selectQueryImpl; +import static org.jooq.impl.Tools.updateQueryImpl; +import static org.jooq.impl.Transformations.transformAppendMissingTableReferences; import static org.jooq.impl.XMLPassingMechanism.BY_REF; import static org.jooq.impl.XMLPassingMechanism.BY_VALUE; import static org.jooq.tools.StringUtils.defaultIfNull; @@ -666,25 +670,26 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { scopeStart(); boolean previousMetaLookupsForceIgnore = metaLookupsForceIgnore(); + Query result = null; try { switch (characterUpper()) { case 'A': if (!parseResultQuery && peekKeyword("ALTER")) - return metaLookupsForceIgnore(true).parseAlter(); + return result = metaLookupsForceIgnore(true).parseAlter(); break; case 'B': if (!parseResultQuery && peekKeyword("BEGIN")) - return parseBlock(false); + return result = parseBlock(false); break; case 'C': if (!parseResultQuery && peekKeyword("CREATE")) - return metaLookupsForceIgnore(true).parseCreate(); + return result = metaLookupsForceIgnore(true).parseCreate(); else if (!parseResultQuery && peekKeyword("COMMENT ON")) - return metaLookupsForceIgnore(true).parseCommentOn(); + return result = metaLookupsForceIgnore(true).parseCommentOn(); else if (peekKeyword("CALL") && requireProEdition()) @@ -699,21 +704,21 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { case 'D': if (!parseResultQuery && peekKeyword("DECLARE") && requireProEdition()) - return parseBlock(true); + return result = parseBlock(true); else if (!parseResultQuery && (peekKeyword("DELETE") || peekKeyword("DEL"))) - return parseDelete(null); + return result = parseDelete(null); else if (!parseResultQuery && peekKeyword("DROP")) - return metaLookupsForceIgnore(true).parseDrop(); + return result = metaLookupsForceIgnore(true).parseDrop(); else if (!parseResultQuery && peekKeyword("DO")) - return parseDo(); + return result = parseDo(); break; case 'E': if (!parseResultQuery && peekKeyword("EXECUTE BLOCK AS")) - return parseBlock(true); + return result = parseBlock(true); else if (!parseResultQuery && peekKeyword("EXEC")) - return parseExec(); + return result = parseExec(); else if (peekKeyword("EXECUTE PROCEDURE") && requireProEdition()) @@ -724,13 +729,13 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { case 'G': if (!parseResultQuery && peekKeyword("GRANT")) - return metaLookupsForceIgnore(true).parseGrant(); + return result = metaLookupsForceIgnore(true).parseGrant(); break; case 'I': if (!parseResultQuery && (peekKeyword("INSERT") || peekKeyword("INS"))) - return parseInsert(null); + return result = parseInsert(null); break; @@ -742,21 +747,21 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { case 'M': if (!parseResultQuery && peekKeyword("MERGE")) - return parseMerge(null); + return result = parseMerge(null); break; case 'O': if (!parseResultQuery && peekKeyword("OPEN")) - return parseOpen(); + return result = parseOpen(); break; case 'R': if (!parseResultQuery && peekKeyword("RENAME")) - return metaLookupsForceIgnore(true).parseRename(); + return result = metaLookupsForceIgnore(true).parseRename(); else if (!parseResultQuery && peekKeyword("REVOKE")) - return metaLookupsForceIgnore(true).parseRevoke(); + return result = metaLookupsForceIgnore(true).parseRevoke(); else if (parseKeywordIf("REPLACE")) throw notImplemented("REPLACE"); else if (parseKeywordIf("ROLLBACK")) @@ -766,9 +771,9 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { case 'S': if (peekSelect(false)) - return parseSelect(); + return result = parseSelect(); else if (!parseResultQuery && peekKeyword("SET")) - return parseSet(); + return result = parseSet(); else if (parseKeywordIf("SAVEPOINT")) throw notImplemented("SAVEPOINT"); @@ -776,17 +781,17 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { case 'T': if (!parseSelect && peekKeyword("TABLE")) - return parseSelect(); + return result = parseSelect(); else if (!parseResultQuery && peekKeyword("TRUNCATE")) - return parseTruncate(); + return result = parseTruncate(); break; case 'U': if (!parseResultQuery && (peekKeyword("UPDATE") || peekKeyword("UPD"))) - return parseUpdate(null); + return result = parseUpdate(null); else if (!parseResultQuery && peekKeyword("USE")) - return parseUse(); + return result = parseUse(); else if (parseKeywordIf("UPSERT")) throw notImplemented("UPSERT"); @@ -794,11 +799,11 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { case 'V': if (!parseSelect && peekKeyword("VALUES")) - return parseSelect(); + return result = parseSelect(); case 'W': if (peekKeyword("WITH")) - return parseWith(parseSelect); + return result = parseWith(parseSelect); break; @@ -806,9 +811,9 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { // TODO are there other possible statement types? if (peekKeyword("WITH", false, true, false)) - return parseWith(true); + return result = parseWith(true); else - return parseSelect(); + return result = parseSelect(); case '{': if (peekKeyword("{ CALL") && requireProEdition()) @@ -832,7 +837,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { throw e; } finally { - scopeEnd(); + scopeEnd(result); scopeResolve(); metaLookupsForceIgnore(previousMetaLookupsForceIgnore); } @@ -1065,7 +1070,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { result.setForUpdateSkipLocked(); } - scopeEnd(); + scopeEnd(result); return result; } @@ -1150,16 +1155,17 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { private final SelectQueryImpl parseQueryExpressionBody(Integer degree, WithImpl with, SelectQueryImpl prefix) { SelectQueryImpl lhs = parseQueryTerm(degree, with, prefix); + SelectQueryImpl local = lhs; CombineOperator combine; while ((combine = parseCombineOperatorIf(false)) != null) { - scopeEnd(); + scopeEnd(local); scopeStart(); if (degree == null) degree = Tools.degree(lhs); - SelectQueryImpl rhs = degreeCheck(degree, parseQueryTerm(degree, null, null)); + SelectQueryImpl rhs = local = degreeCheck(degree, parseQueryTerm(degree, null, null)); switch (combine) { case UNION: lhs = lhs.union(rhs); @@ -1183,16 +1189,17 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { private final SelectQueryImpl parseQueryTerm(Integer degree, WithImpl with, SelectQueryImpl prefix) { SelectQueryImpl lhs = prefix != null ? prefix : parseQueryPrimary(degree, with); + SelectQueryImpl local = lhs; CombineOperator combine; while ((combine = parseCombineOperatorIf(true)) != null) { - scopeEnd(); + scopeEnd(local); scopeStart(); if (degree == null) degree = Tools.degree(lhs); - SelectQueryImpl rhs = degreeCheck(degree, parseQueryPrimary(degree, null)); + SelectQueryImpl rhs = local = degreeCheck(degree, parseQueryPrimary(degree, null)); switch (combine) { case INTERSECT: lhs = lhs.intersect(rhs); @@ -1833,7 +1840,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { // [#10954] These are moved into the INSERT .. SELECT clause handling. They should not be necessary here // either, but it seems we currently don't correctly implement nesting scopes? - scopeEnd(); + scopeEnd(null); scopeStart(); Select select = parseWithOrSelect(); @@ -1908,7 +1915,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { return returning; } finally { - scopeEnd(); + scopeEnd(((InsertImpl) s1).getDelegate()); } } @@ -13155,6 +13162,19 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { return parseDialect().family(); } + private final ParseWithMetaLookups metaLookups() { + if (metaLookupsForceIgnore()) + return ParseWithMetaLookups.OFF; + + + + + + + else + return this.metaLookups; + } + private final boolean metaLookupsForceIgnore() { return this.metaLookupsForceIgnore; } @@ -13436,7 +13456,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { lookupFields.setAll(null); } - private final void scopeEnd() { + private final void scopeEnd(Query scopeOwner) { List> retain = new ArrayList<>(); for (FieldProxy lookup : lookupFields) { @@ -13455,10 +13475,14 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { } found = resolveInTableScope(tableScope.valueIterable(), lookup.getQualifiedName(), lookup, found); - if (found != null) + + if (found != null && !(found.value() instanceof FieldProxy)) { lookup.delegate((AbstractField) found.value()); - else + } + else { + lookup.scopeOwner(scopeOwner); retain.add(lookup); + } } lookupFields.scopeEnd(); @@ -13499,6 +13523,14 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { if (x && q.equals(t.value().getQualifiedName()) || !x && q.last().equals(t.value().getName())) if ((found = Value.of(t.scopeLevel(), t.value().field(lookup.getName()))) != null) break tableScopeLoop; + + + + + + + + } else if ((f = Value.of(t.scopeLevel(), t.value().field(lookup.getName()))) != null) { if (found == null || found.scopeLevel() < f.scopeLevel()) { @@ -13524,9 +13556,41 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { } private final void unknownField(FieldProxy field) { - if (!scopeClear && !metaLookupsForceIgnore && metaLookups == THROW_ON_FAILURE) { - position(field.position()); - throw exception("Unknown field identifier"); + if (!scopeClear) { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if (metaLookups() == THROW_ON_FAILURE) { + position(field.position()); + throw exception("Unknown field identifier"); + } } } @@ -13548,14 +13612,14 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { return tables.get(0); } - if (!metaLookupsForceIgnore && metaLookups == THROW_ON_FAILURE) + if (metaLookups() == THROW_ON_FAILURE) throw exception("Unknown table identifier"); return table(name); } private final Field lookupField(Name name) { - if (metaLookups == ParseWithMetaLookups.OFF) + if (metaLookups() == ParseWithMetaLookups.OFF || lookupFields.scopeLevel() < 0) return field(name); FieldProxy field = lookupFields.get(name); diff --git a/jOOQ/src/main/java/org/jooq/impl/RowCondition.java b/jOOQ/src/main/java/org/jooq/impl/RowCondition.java index 0daaa73bef..e33e25f8b0 100644 --- a/jOOQ/src/main/java/org/jooq/impl/RowCondition.java +++ b/jOOQ/src/main/java/org/jooq/impl/RowCondition.java @@ -63,6 +63,7 @@ import static org.jooq.SQLDialect.FIREBIRD; // ... // ... // ... +import static org.jooq.impl.DSL.select; import static org.jooq.impl.Keywords.K_NOT; import static org.jooq.impl.Tools.map; @@ -76,6 +77,7 @@ import org.jooq.Condition; import org.jooq.Configuration; import org.jooq.Context; import org.jooq.Field; +// ... import org.jooq.QueryPartInternal; import org.jooq.Row; import org.jooq.SQLDialect; @@ -85,9 +87,14 @@ import org.jooq.SQLDialect; */ @SuppressWarnings({ "unchecked", "rawtypes" }) final class RowCondition extends AbstractCondition { - private static final Clause[] CLAUSES = { CONDITION, CONDITION_COMPARISON }; - private static final Set EMULATE_EQ_AND_NE = SQLDialect.supportedBy(DERBY, FIREBIRD); - private static final Set EMULATE_RANGES = SQLDialect.supportedBy(CUBRID, DERBY, FIREBIRD); + private static final Clause[] CLAUSES = { CONDITION, CONDITION_COMPARISON }; + + + + + + private static final Set EMULATE_EQ_AND_NE = SQLDialect.supportedBy(DERBY, FIREBIRD); + private static final Set EMULATE_RANGES = SQLDialect.supportedBy(CUBRID, DERBY, FIREBIRD); private final Row left; private final Row right; @@ -116,6 +123,12 @@ final class RowCondition extends AbstractCondition { } private final QueryPartInternal delegate(Configuration configuration) { + + + + + + // Regular comparison predicate emulation if ((comparator == EQUALS || comparator == NOT_EQUALS) && (forceEmulation || EMULATE_EQ_AND_NE.contains(configuration.dialect()))) { diff --git a/jOOQ/src/main/java/org/jooq/impl/RowIsDistinctFrom.java b/jOOQ/src/main/java/org/jooq/impl/RowIsDistinctFrom.java index 9801e8a5fa..2d60ba7322 100644 --- a/jOOQ/src/main/java/org/jooq/impl/RowIsDistinctFrom.java +++ b/jOOQ/src/main/java/org/jooq/impl/RowIsDistinctFrom.java @@ -61,6 +61,7 @@ import static org.jooq.SQLDialect.SQLITE; // ... // ... // ... +// ... import static org.jooq.impl.DSL.exists; import static org.jooq.impl.DSL.notExists; import static org.jooq.impl.DSL.select; diff --git a/jOOQ/src/main/java/org/jooq/impl/RowIsNull.java b/jOOQ/src/main/java/org/jooq/impl/RowIsNull.java index 8555175dbb..dae7cfd501 100644 --- a/jOOQ/src/main/java/org/jooq/impl/RowIsNull.java +++ b/jOOQ/src/main/java/org/jooq/impl/RowIsNull.java @@ -64,6 +64,7 @@ import static org.jooq.SQLDialect.SQLITE; // ... // ... // ... +// ... import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.selectCount; import static org.jooq.impl.Keywords.K_IS_NOT_NULL; @@ -71,8 +72,6 @@ import static org.jooq.impl.Keywords.K_IS_NULL; import static org.jooq.impl.Tools.map; import static org.jooq.impl.Tools.visitSubquery; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import org.jooq.Clause; diff --git a/jOOQ/src/main/java/org/jooq/impl/RowSubqueryCondition.java b/jOOQ/src/main/java/org/jooq/impl/RowSubqueryCondition.java index 7a851e6886..0f0baa9450 100644 --- a/jOOQ/src/main/java/org/jooq/impl/RowSubqueryCondition.java +++ b/jOOQ/src/main/java/org/jooq/impl/RowSubqueryCondition.java @@ -68,6 +68,7 @@ import static org.jooq.SQLDialect.POSTGRES; // ... import static org.jooq.SQLDialect.SQLITE; // ... +// ... import static org.jooq.impl.DSL.asterisk; import static org.jooq.impl.DSL.exists; import static org.jooq.impl.DSL.name; diff --git a/jOOQ/src/main/java/org/jooq/impl/ScopeMarker.java b/jOOQ/src/main/java/org/jooq/impl/ScopeMarker.java index 3b748f457d..0aedf85783 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ScopeMarker.java +++ b/jOOQ/src/main/java/org/jooq/impl/ScopeMarker.java @@ -41,15 +41,14 @@ package org.jooq.impl; // ... // ... import static org.jooq.impl.Keywords.K_DECLARE; -import static org.jooq.impl.Keywords.K_WITH; import static org.jooq.impl.Tools.increment; import static org.jooq.impl.Tools.DataKey.DATA_TOP_LEVEL_CTE; +import static org.jooq.impl.WithImpl.acceptWithRecursive; import org.jooq.Clause; import org.jooq.Context; // ... import org.jooq.QueryPartInternal; -import org.jooq.SQLDialect; import org.jooq.Statement; import org.jooq.impl.AbstractContext.ScopeStackElement; import org.jooq.impl.Tools.DataExtendedKey; @@ -124,7 +123,7 @@ enum ScopeMarker { boolean noWith = afterLast != null && beforeFirst.positions[0] == afterLast.positions[0]; if (noWith) { - ctx.visit(K_WITH); + acceptWithRecursive(ctx, cte.recursive); if (single) ctx.formatIndentStart() diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index cc984022c3..cbf27a91e9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -3211,12 +3211,12 @@ final class Tools { - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) static final SelectQueryImpl selectQueryImpl(QueryPart part) { if (part instanceof SelectQueryImpl) return (SelectQueryImpl) part; - else if (part instanceof AbstractDelegatingQuery) - return ((AbstractDelegatingQuery>) part).getDelegate(); + else if (part instanceof SelectImpl) + return (SelectQueryImpl) ((SelectImpl) part).getDelegate(); else if (part instanceof ScalarSubquery) return selectQueryImpl(((ScalarSubquery) part).query); else if (part instanceof QuantifiedSelectImpl) @@ -3234,11 +3234,29 @@ final class Tools { return null; } + static final UpdateQueryImpl updateQueryImpl(Query query) { + AbstractDMLQuery result = abstractDMLQuery(query); + + if (result instanceof UpdateQueryImpl) + return (UpdateQueryImpl) result; + else + return null; + } + + static final DeleteQueryImpl deleteQueryImpl(Query query) { + AbstractDMLQuery result = abstractDMLQuery(query); + + if (result instanceof DeleteQueryImpl) + return (DeleteQueryImpl) result; + else + return null; + } + static final AbstractDMLQuery abstractDMLQuery(Query query) { if (query instanceof AbstractDMLQuery) return (AbstractDMLQuery) query; - else if (query instanceof AbstractDelegatingQuery) - return abstractDMLQuery(((AbstractDelegatingQuery) query).getDelegate()); + else if (query instanceof AbstractDelegatingDMLQuery) + return abstractDMLQuery(((AbstractDelegatingDMLQuery) query).getDelegate()); else if (query instanceof DMLQueryAsResultQuery) return ((DMLQueryAsResultQuery) query).getDelegate(); else diff --git a/jOOQ/src/main/java/org/jooq/impl/TopLevelCte.java b/jOOQ/src/main/java/org/jooq/impl/TopLevelCte.java index 4bdb270cc5..6e23a7b216 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TopLevelCte.java +++ b/jOOQ/src/main/java/org/jooq/impl/TopLevelCte.java @@ -48,6 +48,8 @@ import org.jooq.impl.ScopeMarker.ScopeContent; */ final class TopLevelCte extends QueryPartList implements ScopeContent { + boolean recursive; + @Override public void accept(Context ctx) { markTopLevelCteAndAccept(ctx, c -> super.accept(c)); diff --git a/jOOQ/src/main/java/org/jooq/impl/Transformations.java b/jOOQ/src/main/java/org/jooq/impl/Transformations.java index e40b8bf5f5..e5506b22c4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Transformations.java +++ b/jOOQ/src/main/java/org/jooq/impl/Transformations.java @@ -48,6 +48,7 @@ import static org.jooq.SQLDialect.CUBRID; import static org.jooq.SQLDialect.DERBY; // ... import static org.jooq.SQLDialect.FIREBIRD; +import static org.jooq.SQLDialect.H2; // ... import static org.jooq.SQLDialect.HSQLDB; import static org.jooq.SQLDialect.IGNITE; @@ -74,10 +75,8 @@ import java.util.Set; import java.util.function.Predicate; import org.jooq.Configuration; -import org.jooq.Context; import org.jooq.QueryPart; import org.jooq.SQLDialect; -import org.jooq.Scope; import org.jooq.conf.Transformation; /** @@ -87,9 +86,10 @@ import org.jooq.conf.Transformation; */ final class Transformations { - private static final Set NO_SUPPORT_IN_LIMIT = SQLDialect.supportedBy(MARIADB, MYSQL); - private static final Set EMULATE_QUALIFY = SQLDialect.supportedBy(CUBRID, FIREBIRD, MARIADB, MYSQL, POSTGRES, SQLITE); - private static final Set EMULATE_ROWNUM = SQLDialect.supportedBy(CUBRID, DERBY, FIREBIRD, HSQLDB, IGNITE, MARIADB, MYSQL, POSTGRES, SQLITE); + private static final Set NO_SUPPORT_IN_LIMIT = SQLDialect.supportedBy(MARIADB, MYSQL); + private static final Set SUPPORT_MISSING_TABLE_REFERENCES = SQLDialect.supportedBy(); + private static final Set EMULATE_QUALIFY = SQLDialect.supportedBy(CUBRID, FIREBIRD, MARIADB, MYSQL, POSTGRES, SQLITE); + private static final Set EMULATE_ROWNUM = SQLDialect.supportedBy(CUBRID, DERBY, FIREBIRD, HSQLDB, IGNITE, MARIADB, MYSQL, POSTGRES, SQLITE); static final SelectQueryImpl subqueryWithLimit(QueryPart source) { SelectQueryImpl s; @@ -123,6 +123,15 @@ final class Transformations { ); } + static final boolean transformAppendMissingTableReferences(Configuration configuration) { + return transform( + configuration, + "Settings.transformAppendMissingTableReferences", + configuration.settings().getParseAppendMissingTableReferences(), + c -> SUPPORT_MISSING_TABLE_REFERENCES.contains(c.settings().getParseDialect()) + ); + } + /** * Check whether a given SQL transformation needs to be applied. */ diff --git a/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java index ef3bc2c250..c963c5dcc9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java @@ -521,6 +521,10 @@ final class UpdateQueryImpl extends AbstractStoreQuery impl return condition.hasWhere(); } + final TableList getFrom() { + return from; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override final void accept0(Context ctx) { diff --git a/jOOQ/src/main/java/org/jooq/impl/WindowSpecificationImpl.java b/jOOQ/src/main/java/org/jooq/impl/WindowSpecificationImpl.java index b2549aee85..f1ed7579af 100644 --- a/jOOQ/src/main/java/org/jooq/impl/WindowSpecificationImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/WindowSpecificationImpl.java @@ -66,6 +66,7 @@ import static org.jooq.impl.WindowSpecificationImpl.Exclude.TIES; import static org.jooq.impl.WindowSpecificationImpl.FrameUnits.GROUPS; import static org.jooq.impl.WindowSpecificationImpl.FrameUnits.RANGE; import static org.jooq.impl.WindowSpecificationImpl.FrameUnits.ROWS; +import static org.jooq.tools.StringUtils.defaultIfNull; import java.util.Arrays; import java.util.Collection; @@ -81,6 +82,7 @@ import org.jooq.WindowSpecificationFinalStep; import org.jooq.WindowSpecificationOrderByStep; import org.jooq.WindowSpecificationPartitionByStep; import org.jooq.WindowSpecificationRowsAndStep; +import org.jooq.conf.RenderImplicitWindowRange; import org.jooq.impl.Tools.BooleanDataKey; /** @@ -149,7 +151,11 @@ final class WindowSpecificationImpl extends AbstractQueryPart implements boolean hasWindowDefinitions = windowDefinition != null; boolean hasPartitionBy = !partitionBy.isEmpty(); boolean hasOrderBy = !o.isEmpty(); - boolean hasFrame = frameStart != null; + boolean hasFrame = frameStart != null + + + + ; int clauses = 0; @@ -199,17 +205,53 @@ final class WindowSpecificationImpl extends AbstractQueryPart implements if (hasWindowDefinitions || hasPartitionBy || hasOrderBy) ctx.formatSeparator(); - ctx.visit(frameUnits.keyword).sql(' '); + FrameUnits u = frameUnits; + Integer s = frameStart; + Integer e = frameEnd; - if (frameEnd != null) { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ctx.visit(u.keyword).sql(' '); + + if (e != null) { ctx.visit(K_BETWEEN).sql(' '); - toSQLRows(ctx, frameStart); + toSQLRows(ctx, s); ctx.sql(' ').visit(K_AND).sql(' '); - toSQLRows(ctx, frameEnd); + toSQLRows(ctx, e); } else { - toSQLRows(ctx, frameStart); + toSQLRows(ctx, s); } if (exclude != null) diff --git a/jOOQ/src/main/java/org/jooq/impl/WithImpl.java b/jOOQ/src/main/java/org/jooq/impl/WithImpl.java index a0129e701d..65d7c2e6ab 100644 --- a/jOOQ/src/main/java/org/jooq/impl/WithImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/WithImpl.java @@ -43,6 +43,7 @@ import static org.jooq.Clause.WITH; // ... // ... // ... +// ... import static org.jooq.impl.DSL.count; import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.one; @@ -125,7 +126,6 @@ import org.jooq.WithAsStep7; import org.jooq.WithAsStep8; import org.jooq.WithAsStep9; import org.jooq.WithStep; -import org.jooq.impl.Tools.BooleanDataKey; /** * This type models an intermediary DSL construction step, which leads towards @@ -219,14 +219,8 @@ implements - ctx.visit(K_WITH); - if (recursive - - - - ) - ctx.sql(' ').visit(K_RECURSIVE); + acceptWithRecursive(ctx, recursive); ctx.data(DATA_LIST_ALREADY_INDENTED, true, c1 -> c1.formatIndentStart() @@ -238,6 +232,17 @@ implements } } + static final void acceptWithRecursive(Context ctx, boolean recursive) { + ctx.visit(K_WITH); + + if (recursive + + + + ) + ctx.sql(' ').visit(K_RECURSIVE); + } + @Override public final Clause[] clauses(Context ctx) { return CLAUSES; diff --git a/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd b/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd index ab7b273fed..40bc2ed1a7 100644 --- a/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd +++ b/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd @@ -129,6 +129,10 @@ providing a name to parameters, resulting in :1 or @1 OUTER keyword in OUTER JOIN, if it is optional in the output dialect.]]> + + + RANGE clause when an implicit clause is applied.]]> + :1 or @1 + + FROM or USING clause, if applicable. +

+Teradata (and possibly others) allow for referencing tables that are not listed in the FROM +clause, such as SELECT t.* FROM t WHERE t.i = u.i. This transformation is executed in the +parser, to produce SELECT t.* FROM t, u WHERE t.i = u.i, instead. By default, it is active +when the input dialect supports this syntax. +

+This feature is available in the commercial distribution only.]]> + + @@ -1171,6 +1186,26 @@ Either <input/> or <inputExpression/> must be provided]]> + + + + + + + + + + + + + + + + + + + +