[jOOQ/jOOQ#9061] Implemented lookups for DML statements

This commit is contained in:
Lukas Eder 2020-04-16 19:56:13 +02:00
parent 17c8f59ca5
commit 86615abb78
2 changed files with 142 additions and 117 deletions

View File

@ -67,6 +67,8 @@ import org.jooq.Result;
import org.jooq.Select;
import org.jooq.SortField;
import org.jooq.SortOrder;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.WindowIgnoreNullsStep;
import org.jooq.WindowPartitionByStep;
import org.jooq.exception.DataAccessException;
@ -78,7 +80,7 @@ import org.jooq.exception.DataAccessException;
* @author Lukas Eder
*/
@SuppressWarnings("unchecked")
final class FieldProxy<T> implements Field<T>, QueryPartInternal {
final class FieldProxy<T> implements TableField<Record, T>, QueryPartInternal {
/**
* Generated UID
@ -1831,4 +1833,9 @@ final class FieldProxy<T> implements Field<T>, QueryPartInternal {
public final Field<T> coalesce(Field<T> option, Field<?>... options) {
return delegate.coalesce(option, options);
}
@Override
public final Table<Record> getTable() {
return delegate instanceof TableField ? ((TableField<Record, ?>) delegate).getTable() : null;
}
}

View File

@ -1437,7 +1437,7 @@ final class ParserImpl implements Parser {
// TODO: Move this into parseTables() so lateral joins can profit from lookups (?)
if (from != null)
for (Table<?> table : from)
ctx.tableScope.set(table.getName(), table);
ctx.scope(table);
if (parseKeywordIf(ctx, "WHERE"))
where = parseCondition(ctx);
@ -1819,6 +1819,8 @@ final class ParserImpl implements Parser {
table = table.as(parseIdentifierIf(ctx));
}
ctx.scope(table);
DeleteUsingStep<?> s1 = with == null ? ctx.dsl.delete(table) : with.delete(table);
DeleteWhereStep<?> s2 = parseKeywordIf(ctx, "USING") ? s1.using(parseTables(ctx)) : s1;
DeleteOrderByStep<?> s3 = parseKeywordIf(ctx, "WHERE") ? s2.where(parseCondition(ctx)) : s2;
@ -1832,6 +1834,7 @@ final class ParserImpl implements Parser {
}
private static final Insert<?> parseInsert(ParserContext ctx, WithImpl with) {
ctx.scopeStart();
if (!parseKeywordIf(ctx, "INS"))
parseKeyword(ctx, "INSERT");
@ -1847,130 +1850,139 @@ final class ParserImpl implements Parser {
&& (alias = parseIdentifierIf(ctx)) != null)
table = table.as(alias);
ctx.scope(table);
InsertSetStep<?> s1 = (with == null ? ctx.dsl.insertInto(table) : with.insertInto(table));
Field<?>[] fields = null;
if (parseIf(ctx, '(')) {
fields = Tools.fieldsByName(parseIdentifiers(ctx).toArray(EMPTY_NAME));
fields = parseFieldNames(ctx).toArray(EMPTY_FIELD);
parse(ctx, ')');
}
InsertOnDuplicateStep<?> onDuplicate;
InsertReturningStep<?> returning;
if (parseKeywordIf(ctx, "VALUES")) {
List<List<Field<?>>> allValues = new ArrayList<>();
ctx.scopeEnd();
ctx.scopeStart();
try {
if (parseKeywordIf(ctx, "VALUES")) {
List<List<Field<?>>> allValues = new ArrayList<>();
valuesLoop:
do {
parse(ctx, '(');
// [#6936] MySQL treats an empty VALUES() clause as the same thing as the standard DEFAULT VALUES
if (fields == null && parseIf(ctx, ')'))
break valuesLoop;
List<Field<?>> values = new ArrayList<>();
valuesLoop:
do {
Field<?> value = parseKeywordIf(ctx, "DEFAULT") ? default_() : parseField(ctx);
values.add(value);
parse(ctx, '(');
// [#6936] MySQL treats an empty VALUES() clause as the same thing as the standard DEFAULT VALUES
if (fields == null && parseIf(ctx, ')'))
break valuesLoop;
List<Field<?>> values = new ArrayList<>();
do {
Field<?> value = parseKeywordIf(ctx, "DEFAULT") ? default_() : parseField(ctx);
values.add(value);
}
while (parseIf(ctx, ','));
if (fields != null && fields.length != values.size())
throw ctx.exception("Insert field size (" + fields.length + ") must match values size (" + values.size() + ")");
allValues.add(values);
parse(ctx, ')');
}
while (parseIf(ctx, ','));
if (fields != null && fields.length != values.size())
throw ctx.exception("Insert field size (" + fields.length + ") must match values size (" + values.size() + ")");
allValues.add(values);
parse(ctx, ')');
}
while (parseIf(ctx, ','));
if (allValues.isEmpty()) {
returning = onDuplicate = s1.defaultValues();
}
else {
InsertValuesStepN<?> step2 = (fields != null)
? s1.columns(fields)
: (InsertValuesStepN<?>) s1;
for (List<Field<?>> values : allValues)
step2 = step2.values(values);
returning = onDuplicate = step2;
}
}
else if (parseKeywordIf(ctx, "SET")) {
Map<Field<?>, Object> map = parseSetClauseList(ctx);
returning = onDuplicate = s1.set(map);
}
else if (peekKeyword(ctx, "SELECT", false, true, false)
|| peekKeyword(ctx, "SEL", false, true, false)){
SelectQueryImpl<Record> select = parseSelect(ctx);
returning = onDuplicate = (fields == null)
? s1.select(select)
: s1.columns(fields).select(select);
}
else if (parseKeywordIf(ctx, "DEFAULT VALUES")) {
if (fields != null)
throw ctx.notImplemented("DEFAULT VALUES without INSERT field list");
else
returning = onDuplicate = s1.defaultValues();
}
else
throw ctx.expected("DEFAULT VALUES", "SELECT", "SET", "VALUES");
if (parseKeywordIf(ctx, "ON")) {
if (parseKeywordIf(ctx, "DUPLICATE KEY UPDATE")) {
parseKeywordIf(ctx, "SET");
InsertOnConflictWhereStep<?> where = onDuplicate.onDuplicateKeyUpdate().set(parseSetClauseList(ctx));
if (parseKeywordIf(ctx, "WHERE"))
returning = where.where(parseCondition(ctx));
else
returning = where;
}
else if (parseKeywordIf(ctx, "DUPLICATE KEY IGNORE")) {
returning = onDuplicate.onDuplicateKeyIgnore();
}
else if (parseKeywordIf(ctx, "CONFLICT")) {
InsertOnConflictDoUpdateStep<?> doUpdate;
if (parseKeywordIf(ctx, "ON CONSTRAINT")) {
doUpdate = onDuplicate.onConflictOnConstraint(parseName(ctx));
}
else if (parseIf(ctx, '(')) {
doUpdate = onDuplicate.onConflict(parseFieldNames(ctx));
parse(ctx, ')');
if (allValues.isEmpty()) {
returning = onDuplicate = s1.defaultValues();
}
else {
doUpdate = onDuplicate.onConflict();
}
InsertValuesStepN<?> step2 = (fields != null)
? s1.columns(fields)
: (InsertValuesStepN<?>) s1;
parseKeyword(ctx, "DO");
if (parseKeywordIf(ctx, "NOTHING")) {
returning = doUpdate.doNothing();
for (List<Field<?>> values : allValues)
step2 = step2.values(values);
returning = onDuplicate = step2;
}
else if (parseKeywordIf(ctx, "UPDATE SET")) {
InsertOnConflictWhereStep<?> where = doUpdate.doUpdate().set(parseSetClauseList(ctx));
}
else if (parseKeywordIf(ctx, "SET")) {
Map<Field<?>, Object> map = parseSetClauseList(ctx);
returning = onDuplicate = s1.set(map);
}
else if (peekKeyword(ctx, "SELECT", false, true, false)
|| peekKeyword(ctx, "SEL", false, true, false)){
SelectQueryImpl<Record> select = parseSelect(ctx);
returning = onDuplicate = (fields == null)
? s1.select(select)
: s1.columns(fields).select(select);
}
else if (parseKeywordIf(ctx, "DEFAULT VALUES")) {
if (fields != null)
throw ctx.notImplemented("DEFAULT VALUES without INSERT field list");
else
returning = onDuplicate = s1.defaultValues();
}
else
throw ctx.expected("DEFAULT VALUES", "SELECT", "SET", "VALUES");
if (parseKeywordIf(ctx, "ON")) {
if (parseKeywordIf(ctx, "DUPLICATE KEY UPDATE")) {
parseKeywordIf(ctx, "SET");
InsertOnConflictWhereStep<?> where = onDuplicate.onDuplicateKeyUpdate().set(parseSetClauseList(ctx));
if (parseKeywordIf(ctx, "WHERE"))
returning = where.where(parseCondition(ctx));
else
returning = where;
}
else
throw ctx.expected("NOTHING", "UPDATE");
}
else
throw ctx.expected("CONFLICT", "DUPLICATE");
}
else if (parseKeywordIf(ctx, "DUPLICATE KEY IGNORE")) {
returning = onDuplicate.onDuplicateKeyIgnore();
}
else if (parseKeywordIf(ctx, "CONFLICT")) {
InsertOnConflictDoUpdateStep<?> doUpdate;
if (parseKeywordIf(ctx, "RETURNING"))
return returning.returning(parseSelectList(ctx));
else
return returning;
if (parseKeywordIf(ctx, "ON CONSTRAINT")) {
doUpdate = onDuplicate.onConflictOnConstraint(parseName(ctx));
}
else if (parseIf(ctx, '(')) {
doUpdate = onDuplicate.onConflict(parseFieldNames(ctx));
parse(ctx, ')');
}
else {
doUpdate = onDuplicate.onConflict();
}
parseKeyword(ctx, "DO");
if (parseKeywordIf(ctx, "NOTHING")) {
returning = doUpdate.doNothing();
}
else if (parseKeywordIf(ctx, "UPDATE SET")) {
InsertOnConflictWhereStep<?> where = doUpdate.doUpdate().set(parseSetClauseList(ctx));
if (parseKeywordIf(ctx, "WHERE"))
returning = where.where(parseCondition(ctx));
else
returning = where;
}
else
throw ctx.expected("NOTHING", "UPDATE");
}
else
throw ctx.expected("CONFLICT", "DUPLICATE");
}
if (parseKeywordIf(ctx, "RETURNING"))
return returning.returning(parseSelectList(ctx));
else
return returning;
}
finally {
ctx.scopeEnd();
}
}
private static final Update<?> parseUpdate(ParserContext ctx, WithImpl with) {
@ -1996,6 +2008,8 @@ final class ParserImpl implements Parser {
else if (!peekKeyword(ctx, "SET"))
table = table.as(parseIdentifierIf(ctx));
ctx.scope(table);
UpdateSetFirstStep<?> s1 = (with == null ? ctx.dsl.update(table) : with.update(table));
parseKeyword(ctx, "SET");
@ -9716,7 +9730,7 @@ final class ParserImpl implements Parser {
if (name == null)
return null;
return table(name);
return ctx.lookupTable(name);
}
private static final Field<?> parseFieldNameOrSequenceExpression(ParserContext ctx) {
@ -9760,7 +9774,7 @@ final class ParserImpl implements Parser {
}
private static final TableField<?, ?> parseFieldName(ParserContext ctx) {
return (TableField<?, ?>) field(parseName(ctx));
return (TableField<?, ?>) ctx.lookupField(parseName(ctx));
}
private static final List<Field<?>> parseFieldNames(ParserContext ctx) {
@ -11597,22 +11611,22 @@ final class ParserImpl implements Parser {
}
final class ParserContext {
private static final boolean PRO_EDITION = false ;
private static final boolean PRO_EDITION = false ;
final DSLContext dsl;
final Locale locale;
final Meta meta;
final char[] sql;
private final ParseWithMetaLookups metaLookups;
private boolean metaLookupsForceIgnore;
private int position = 0;
private boolean ignoreHints = true;
private final Object[] bindings;
private int bindIndex = 0;
private String delimiter = ";";
final ScopeStack<String, Table<?>> tableScope = new ScopeStack<>(null);
final ScopeStack<String, FieldProxy<?>> lookupFields = new ScopeStack<>(null);
private boolean scopeClear = false;
final DSLContext dsl;
final Locale locale;
final Meta meta;
final char[] sql;
private final ParseWithMetaLookups metaLookups;
private boolean metaLookupsForceIgnore;
private int position = 0;
private boolean ignoreHints = true;
private final Object[] bindings;
private int bindIndex = 0;
private String delimiter = ";";
private final ScopeStack<String, Table<?>> tableScope = new ScopeStack<>(null);
private final ScopeStack<String, FieldProxy<?>> lookupFields = new ScopeStack<>(null);
private boolean scopeClear = false;
@ -11880,6 +11894,10 @@ final class ParserContext {
+ (sql.length > position + 80 ? "..." : "");
}
void scope(Table<?> table) {
tableScope.set(table.getName(), table);
}
void scopeStart() {
tableScope.scopeStart();
lookupFields.scopeStart();