diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 130c7469d1..2bcc090bee 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -1063,7 +1063,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { if (done()) return null; - scopeStart(); + scope.scopeStart(); boolean previousMetaLookupsForceIgnore = metaLookupsForceIgnore(); Query result = null; LanguageContext previous = languageContext; @@ -1238,12 +1238,12 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { catch (ParserException e) { // [#9061] Don't hide this pre-existing exceptions in scopeResolve() - scopeClear(); + scope.scopeClear(); throw e; } finally { - scopeEnd(result); - scopeResolve(); + scope.scopeEnd(result); + scope.scopeResolve(); metaLookupsForceIgnore(previousMetaLookupsForceIgnore); languageContext = previous; } @@ -1361,13 +1361,13 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { } private final SelectQueryImpl parseSelect(Integer degree, WithImpl with) { - scopeStart(); + scope.scopeStart(); SelectQueryImpl result = parseQueryExpressionBody(degree, with, null); List> orderBy = null; for (Field field : result.getSelect()) if (aliased(field) != null) - scope(field); + scope.scope(field); if (parseKeywordIf("ORDER")) { if (!ignoreProEdition() && parseKeywordIf("SIBLINGS BY") && requireProEdition()) { @@ -1513,7 +1513,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { result.setForUpdateSkipLocked(); } - scopeEnd(result); + scope.scopeEnd(result); return result; } @@ -1602,8 +1602,8 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { CombineOperator combine; while ((combine = parseCombineOperatorIf(false)) != null) { - scopeEnd(local); - scopeStart(); + scope.scopeEnd(local); + scope.scopeStart(); if (degree == null) degree = Tools.degree(lhs); @@ -1636,8 +1636,8 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { CombineOperator combine; while ((combine = parseCombineOperatorIf(true)) != null) { - scopeEnd(local); - scopeStart(); + scope.scopeEnd(local); + scope.scopeStart(); if (degree == null) degree = Tools.degree(lhs); @@ -1783,7 +1783,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { // TODO: Move this into parseTables() so lateral joins can profit from lookups (?) if (from != null) for (Table table : from) - scope(table); + scope.scope(table); SelectQueryImpl result = new SelectQueryImpl<>(dsl.configuration(), with); @@ -2184,7 +2184,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { parseKeywordIf("FROM"); Table table = parseTable(() -> peekKeyword(KEYWORDS_IN_DELETE_FROM)); - scope(table); + scope.scope(table); DeleteUsingStep s1 = with == null ? dsl.delete(table) : with.delete(table); DeleteWhereStep s2 = parseKeywordIf("USING", "FROM") ? s1.using(parseList(',', t -> parseTable(() -> peekKeyword(KEYWORDS_IN_DELETE_FROM)))) : s1; @@ -2203,7 +2203,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { } private final Query parseInsert(WithImpl with, boolean parseResultQuery) { - scopeStart(); + scope.scopeStart(); parseKeyword("INSERT", "INS"); parseKeywordIf("INTO"); Table table = parseTableNameIf(); @@ -2217,7 +2217,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { && (alias = parseIdentifierIf()) != null) table = table.as(alias); - scope(table); + scope.scope(table); InsertSetStep s1 = (with == null ? dsl.insertInto(table) : with.insertInto(table)); Field[] fields = null; @@ -2278,18 +2278,17 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { returning = onDuplicate = s1.set(map); } - else if (peekSelectOrWith(true)){ + else if (peekSelectOrWith(true)) { + Field[] f = fields; - // [#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(null); - scopeStart(); + // [#13503] The SELECT in INSERT .. SELECT has its own, independent scope + returning = onDuplicate = newScope(() -> { + Select select = parseWithOrSelect(); - Select select = parseWithOrSelect(); - - returning = onDuplicate = (fields == null) - ? s1.select(select) - : s1.columns(fields).select(select); + return (f == null) + ? s1.select(select) + : s1.columns(f).select(select); + }); } else if (parseKeywordIf("DEFAULT VALUES")) { if (fields != null) @@ -2356,7 +2355,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { : returning; } finally { - scopeEnd(((InsertImpl) s1).getDelegate()); + scope.scopeEnd(((InsertImpl) s1).getDelegate()); } } @@ -2374,7 +2373,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { Table table = parseTable(() -> peekKeyword(KEYWORDS_IN_UPDATE_FROM)); - scope(table); + scope.scope(table); UpdateSetFirstStep s1 = (with == null ? dsl.update(table) : with.update(table)); List> from = parseKeywordIf("FROM") ? parseList(',', t -> parseTable(() -> peekKeyword(KEYWORDS_IN_UPDATE_FROM))) : null; @@ -13913,28 +13912,23 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { - private final DSLContext dsl; - private final Locale locale; - private final Meta meta; - private char[] sql; - private final ParseWithMetaLookups metaLookups; - private boolean metaLookupsForceIgnore; - private final Consumer> bindParamListener; - private int positionBeforeWhitespace; - private int position = 0; - private boolean ignoreHints = true; - private final Object[] bindings; - private int bindIndex = 0; - private final Map> bindParams = new LinkedHashMap<>(); - private String delimiter = ";"; - private final ScopeStack> tableScope = new ScopeStack<>(null); - private final ScopeStack> fieldScope = new ScopeStack<>(null); - private final ScopeStack> lookupFields = new ScopeStack<>(null); - private boolean scopeClear = false; - private LanguageContext languageContext = LanguageContext.QUERY; - private EnumSet forbidden = EnumSet.noneOf(FunctionKeyword.class); - - + private final DSLContext dsl; + private final Locale locale; + private final Meta meta; + private char[] sql; + private final ParseWithMetaLookups metaLookups; + private boolean metaLookupsForceIgnore; + private final Consumer> bindParamListener; + private int positionBeforeWhitespace; + private int position = 0; + private boolean ignoreHints = true; + private final Object[] bindings; + private int bindIndex = 0; + private final Map> bindParams = new LinkedHashMap<>(); + private String delimiter = ";"; + private LanguageContext languageContext = LanguageContext.QUERY; + private EnumSet forbidden = EnumSet.noneOf(FunctionKeyword.class); + private ParseScope scope = new ParseScope(); @@ -14330,60 +14324,131 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { + (sql.length > position + 80 ? "..." : ""); } - private final void scope(Table table) { - tableScope.set(table.getQualifiedName(), table); + private final T newScope(Supplier scoped) { + ParseScope old = scope; + + try { + scope = new ParseScope(); + return scoped.get(); + } + finally { + scope = old; + } } - private final void scope(Field field) { - fieldScope.set(field.getQualifiedName(), field); - } + private class ParseScope { + private boolean scopeClear = false; + private final ScopeStack> tableScope = new ScopeStack<>(); + private final ScopeStack> fieldScope = new ScopeStack<>(); + private final ScopeStack> lookupFields = new ScopeStack<>(); - private final void scopeStart() { - tableScope.scopeStart(); - fieldScope.scopeStart(); - lookupFields.scopeStart(); - lookupFields.setAll(null); - } - private final void scopeEnd(Query scopeOwner) { - List> retain = new ArrayList<>(); - for (FieldProxy lookup : lookupFields) { - Value> found = null; - for (Field f : fieldScope) { - if (f.getName().equals(lookup.getName())) { - if (found != null) { - position(lookup.position()); - throw exception("Ambiguous field identifier"); + + private final void scope(Table table) { + tableScope.set(table.getQualifiedName(), table); + } + + private final void scope(Field field) { + fieldScope.set(field.getQualifiedName(), field); + } + + private final void scopeResolve() { + if (!lookupFields.isEmpty()) + unknownField(lookupFields.iterator().next()); + } + + private final void scopeStart() { + tableScope.scopeStart(); + fieldScope.scopeStart(); + lookupFields.scopeStart(); + lookupFields.setAll(null); + } + + private final void scopeEnd(Query scopeOwner) { + List> retain = new ArrayList<>(); + + for (FieldProxy lookup : scope.lookupFields) { + Value> found = null; + + for (Field f : scope.fieldScope) { + if (f.getName().equals(lookup.getName())) { + if (found != null) { + position(lookup.position()); + throw exception("Ambiguous field identifier"); + } + + // TODO: Does this instance of "found" really interact with the one below? + found = new Value<>(0, f); } + } - // TODO: Does this instance of "found" really interact with the one below? - found = new Value<>(0, f); + found = resolveInTableScope(scope.tableScope.valueIterable(), lookup.getQualifiedName(), lookup, found); + + if (found != null && !(found.value() instanceof FieldProxy)) { + lookup.delegate((AbstractField) found.value()); + } + else { + lookup.scopeOwner(scopeOwner); + retain.add(lookup); } } - found = resolveInTableScope(tableScope.valueIterable(), lookup.getQualifiedName(), lookup, found); + scope.lookupFields.scopeEnd(); + scope.tableScope.scopeEnd(); + scope.fieldScope.scopeEnd(); - if (found != null && !(found.value() instanceof FieldProxy)) { - lookup.delegate((AbstractField) found.value()); - } - else { - lookup.scopeOwner(scopeOwner); - retain.add(lookup); - } + for (FieldProxy r : retain) + if (scope.lookupFields.get(r.getQualifiedName()) == null) + if (scope.lookupFields.inScope()) + scope.lookupFields.set(r.getQualifiedName(), r); + else + unknownField(r); } - lookupFields.scopeEnd(); - tableScope.scopeEnd(); - fieldScope.scopeEnd(); + private final void scopeClear() { + scopeClear = true; + } - for (FieldProxy r : retain) - if (lookupFields.get(r.getQualifiedName()) == null) - if (lookupFields.inScope()) - lookupFields.set(r.getQualifiedName(), r); - else - unknownField(r); + private final void unknownField(FieldProxy field) { + if (!scopeClear) { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if (metaLookups() == THROW_ON_FAILURE) { + position(field.position()); + throw exception("Unknown field identifier"); + } + } + } } private final Value> resolveInTableScope(Iterable>> tables, Name lookupName, FieldProxy lookup, Value> found) { @@ -14435,54 +14500,6 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { return found; } - private final void scopeClear() { - scopeClear = true; - } - - private final void scopeResolve() { - if (!lookupFields.isEmpty()) - unknownField(lookupFields.iterator().next()); - } - - private final void unknownField(FieldProxy field) { - if (!scopeClear) { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (metaLookups() == THROW_ON_FAILURE) { - position(field.position()); - throw exception("Unknown field identifier"); - } - } - } - private final Table lookupTable(int positionBeforeName, Name name) { if (meta != null) { List> tables; @@ -14510,12 +14527,12 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { } private final Field lookupField(int positionBeforeName, Name name) { - if (metaLookups() == ParseWithMetaLookups.OFF || lookupFields.scopeLevel() < 0) + if (metaLookups() == ParseWithMetaLookups.OFF || scope.lookupFields.scopeLevel() < 0) return field(name); - FieldProxy field = lookupFields.get(name); + FieldProxy field = scope.lookupFields.get(name); if (field == null) - lookupFields.set(name, field = new FieldProxy<>((AbstractField) field(name), positionBeforeName)); + scope.lookupFields.set(name, field = new FieldProxy<>((AbstractField) field(name), positionBeforeName)); return field; } diff --git a/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java b/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java index e30932617e..d24e33589e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java +++ b/jOOQ/src/main/java/org/jooq/impl/ScopeStack.java @@ -63,6 +63,10 @@ final class ScopeStack implements Iterable { private Map> stack; private final ObjIntFunction constructor; + ScopeStack() { + this((ObjIntFunction) null); + } + ScopeStack(V defaultValue) { this((part, scopeLevel) -> defaultValue); }