[jOOQ/jOOQ#13503] Parser meta lookups don't work for INSERT .. SELECT .. RETURNING

This commit is contained in:
Lukas Eder 2022-04-29 11:05:07 +02:00
parent c06ad0e6ec
commit 3d9a863467
2 changed files with 160 additions and 139 deletions

View File

@ -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<Record> parseSelect(Integer degree, WithImpl with) {
scopeStart();
scope.scopeStart();
SelectQueryImpl<Record> result = parseQueryExpressionBody(degree, with, null);
List<SortField<?>> 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<Record> 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<Table<?>> 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<Param<?>> bindParamListener;
private int positionBeforeWhitespace;
private int position = 0;
private boolean ignoreHints = true;
private final Object[] bindings;
private int bindIndex = 0;
private final Map<String, Param<?>> bindParams = new LinkedHashMap<>();
private String delimiter = ";";
private final ScopeStack<Name, Table<?>> tableScope = new ScopeStack<>(null);
private final ScopeStack<Name, Field<?>> fieldScope = new ScopeStack<>(null);
private final ScopeStack<Name, FieldProxy<?>> lookupFields = new ScopeStack<>(null);
private boolean scopeClear = false;
private LanguageContext languageContext = LanguageContext.QUERY;
private EnumSet<FunctionKeyword> 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<Param<?>> bindParamListener;
private int positionBeforeWhitespace;
private int position = 0;
private boolean ignoreHints = true;
private final Object[] bindings;
private int bindIndex = 0;
private final Map<String, Param<?>> bindParams = new LinkedHashMap<>();
private String delimiter = ";";
private LanguageContext languageContext = LanguageContext.QUERY;
private EnumSet<FunctionKeyword> 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> T newScope(Supplier<T> 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<Name, Table<?>> tableScope = new ScopeStack<>();
private final ScopeStack<Name, Field<?>> fieldScope = new ScopeStack<>();
private final ScopeStack<Name, FieldProxy<?>> lookupFields = new ScopeStack<>();
private final void scopeStart() {
tableScope.scopeStart();
fieldScope.scopeStart();
lookupFields.scopeStart();
lookupFields.setAll(null);
}
private final void scopeEnd(Query scopeOwner) {
List<FieldProxy<?>> retain = new ArrayList<>();
for (FieldProxy<?> lookup : lookupFields) {
Value<Field<?>> 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<FieldProxy<?>> retain = new ArrayList<>();
for (FieldProxy<?> lookup : scope.lookupFields) {
Value<Field<?>> 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<Field<?>> resolveInTableScope(Iterable<Value<Table<?>>> tables, Name lookupName, FieldProxy<?> lookup, Value<Field<?>> 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<Table<?>> 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<Object>) field(name), positionBeforeName));
scope.lookupFields.set(name, field = new FieldProxy<>((AbstractField<Object>) field(name), positionBeforeName));
return field;
}

View File

@ -63,6 +63,10 @@ final class ScopeStack<K, V> implements Iterable<V> {
private Map<K, List<V>> stack;
private final ObjIntFunction<K, V> constructor;
ScopeStack() {
this((ObjIntFunction<K, V>) null);
}
ScopeStack(V defaultValue) {
this((part, scopeLevel) -> defaultValue);
}