jooq/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java
2018-01-26 11:28:52 +01:00

6987 lines
232 KiB
Java

/*
* 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;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.jooq.impl.DSL.abs;
import static org.jooq.impl.DSL.acos;
import static org.jooq.impl.DSL.arrayAgg;
import static org.jooq.impl.DSL.arrayAggDistinct;
import static org.jooq.impl.DSL.ascii;
import static org.jooq.impl.DSL.asin;
import static org.jooq.impl.DSL.atan;
import static org.jooq.impl.DSL.atan2;
import static org.jooq.impl.DSL.avg;
import static org.jooq.impl.DSL.avgDistinct;
import static org.jooq.impl.DSL.bitLength;
import static org.jooq.impl.DSL.boolOr;
import static org.jooq.impl.DSL.cast;
import static org.jooq.impl.DSL.catalog;
import static org.jooq.impl.DSL.ceil;
import static org.jooq.impl.DSL.charLength;
import static org.jooq.impl.DSL.check;
import static org.jooq.impl.DSL.choose;
import static org.jooq.impl.DSL.coalesce;
import static org.jooq.impl.DSL.concat;
import static org.jooq.impl.DSL.condition;
import static org.jooq.impl.DSL.constraint;
import static org.jooq.impl.DSL.cos;
import static org.jooq.impl.DSL.cosh;
import static org.jooq.impl.DSL.cot;
import static org.jooq.impl.DSL.coth;
import static org.jooq.impl.DSL.count;
import static org.jooq.impl.DSL.countDistinct;
import static org.jooq.impl.DSL.cube;
import static org.jooq.impl.DSL.cumeDist;
import static org.jooq.impl.DSL.currentDate;
import static org.jooq.impl.DSL.currentSchema;
import static org.jooq.impl.DSL.currentTime;
import static org.jooq.impl.DSL.currentTimestamp;
import static org.jooq.impl.DSL.currentUser;
import static org.jooq.impl.DSL.date;
import static org.jooq.impl.DSL.day;
import static org.jooq.impl.DSL.defaultValue;
import static org.jooq.impl.DSL.deg;
import static org.jooq.impl.DSL.denseRank;
import static org.jooq.impl.DSL.every;
import static org.jooq.impl.DSL.exists;
import static org.jooq.impl.DSL.exp;
import static org.jooq.impl.DSL.extract;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.firstValue;
import static org.jooq.impl.DSL.floor;
import static org.jooq.impl.DSL.foreignKey;
import static org.jooq.impl.DSL.greatest;
import static org.jooq.impl.DSL.grouping;
import static org.jooq.impl.DSL.groupingId;
import static org.jooq.impl.DSL.groupingSets;
import static org.jooq.impl.DSL.hour;
import static org.jooq.impl.DSL.ifnull;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.isnull;
import static org.jooq.impl.DSL.keyword;
import static org.jooq.impl.DSL.lag;
import static org.jooq.impl.DSL.lastValue;
import static org.jooq.impl.DSL.lateral;
import static org.jooq.impl.DSL.lead;
import static org.jooq.impl.DSL.least;
import static org.jooq.impl.DSL.left;
import static org.jooq.impl.DSL.length;
import static org.jooq.impl.DSL.level;
import static org.jooq.impl.DSL.list;
import static org.jooq.impl.DSL.listAgg;
import static org.jooq.impl.DSL.ln;
import static org.jooq.impl.DSL.log;
import static org.jooq.impl.DSL.lower;
import static org.jooq.impl.DSL.lpad;
import static org.jooq.impl.DSL.ltrim;
import static org.jooq.impl.DSL.max;
import static org.jooq.impl.DSL.maxDistinct;
import static org.jooq.impl.DSL.md5;
import static org.jooq.impl.DSL.median;
import static org.jooq.impl.DSL.mid;
import static org.jooq.impl.DSL.min;
import static org.jooq.impl.DSL.minDistinct;
import static org.jooq.impl.DSL.minute;
import static org.jooq.impl.DSL.mode;
import static org.jooq.impl.DSL.month;
import static org.jooq.impl.DSL.nthValue;
import static org.jooq.impl.DSL.ntile;
import static org.jooq.impl.DSL.nullif;
import static org.jooq.impl.DSL.nvl;
import static org.jooq.impl.DSL.nvl2;
import static org.jooq.impl.DSL.octetLength;
import static org.jooq.impl.DSL.one;
import static org.jooq.impl.DSL.orderBy;
import static org.jooq.impl.DSL.partitionBy;
import static org.jooq.impl.DSL.percentRank;
import static org.jooq.impl.DSL.percentileCont;
import static org.jooq.impl.DSL.percentileDisc;
import static org.jooq.impl.DSL.position;
import static org.jooq.impl.DSL.primaryKey;
import static org.jooq.impl.DSL.prior;
import static org.jooq.impl.DSL.privilege;
import static org.jooq.impl.DSL.rad;
import static org.jooq.impl.DSL.rangeBetweenCurrentRow;
import static org.jooq.impl.DSL.rangeBetweenFollowing;
import static org.jooq.impl.DSL.rangeBetweenPreceding;
import static org.jooq.impl.DSL.rangeBetweenUnboundedFollowing;
import static org.jooq.impl.DSL.rangeBetweenUnboundedPreceding;
import static org.jooq.impl.DSL.rangeCurrentRow;
import static org.jooq.impl.DSL.rangeFollowing;
import static org.jooq.impl.DSL.rangePreceding;
import static org.jooq.impl.DSL.rangeUnboundedFollowing;
import static org.jooq.impl.DSL.rangeUnboundedPreceding;
import static org.jooq.impl.DSL.rank;
import static org.jooq.impl.DSL.regrAvgX;
import static org.jooq.impl.DSL.regrAvgY;
import static org.jooq.impl.DSL.regrCount;
import static org.jooq.impl.DSL.regrIntercept;
import static org.jooq.impl.DSL.regrR2;
import static org.jooq.impl.DSL.regrSXX;
import static org.jooq.impl.DSL.regrSXY;
import static org.jooq.impl.DSL.regrSYY;
import static org.jooq.impl.DSL.regrSlope;
import static org.jooq.impl.DSL.repeat;
import static org.jooq.impl.DSL.replace;
import static org.jooq.impl.DSL.reverse;
import static org.jooq.impl.DSL.right;
import static org.jooq.impl.DSL.rollup;
import static org.jooq.impl.DSL.round;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.rowNumber;
import static org.jooq.impl.DSL.rownum;
import static org.jooq.impl.DSL.rowsBetweenCurrentRow;
import static org.jooq.impl.DSL.rowsBetweenFollowing;
import static org.jooq.impl.DSL.rowsBetweenPreceding;
import static org.jooq.impl.DSL.rowsBetweenUnboundedFollowing;
import static org.jooq.impl.DSL.rowsBetweenUnboundedPreceding;
import static org.jooq.impl.DSL.rowsCurrentRow;
import static org.jooq.impl.DSL.rowsFollowing;
import static org.jooq.impl.DSL.rowsPreceding;
import static org.jooq.impl.DSL.rowsUnboundedFollowing;
import static org.jooq.impl.DSL.rowsUnboundedPreceding;
import static org.jooq.impl.DSL.rpad;
import static org.jooq.impl.DSL.rtrim;
import static org.jooq.impl.DSL.schema;
import static org.jooq.impl.DSL.second;
import static org.jooq.impl.DSL.sequence;
import static org.jooq.impl.DSL.sign;
import static org.jooq.impl.DSL.sin;
import static org.jooq.impl.DSL.sinh;
import static org.jooq.impl.DSL.space;
import static org.jooq.impl.DSL.sql;
import static org.jooq.impl.DSL.sqrt;
import static org.jooq.impl.DSL.stddevPop;
import static org.jooq.impl.DSL.stddevSamp;
import static org.jooq.impl.DSL.substring;
import static org.jooq.impl.DSL.sum;
import static org.jooq.impl.DSL.sumDistinct;
import static org.jooq.impl.DSL.table;
import static org.jooq.impl.DSL.tan;
import static org.jooq.impl.DSL.tanh;
import static org.jooq.impl.DSL.time;
import static org.jooq.impl.DSL.timestamp;
import static org.jooq.impl.DSL.translate;
import static org.jooq.impl.DSL.trim;
import static org.jooq.impl.DSL.unique;
import static org.jooq.impl.DSL.user;
import static org.jooq.impl.DSL.values;
import static org.jooq.impl.DSL.varPop;
import static org.jooq.impl.DSL.varSamp;
import static org.jooq.impl.DSL.when;
import static org.jooq.impl.DSL.year;
import static org.jooq.impl.DSL.zero;
import static org.jooq.impl.Keywords.K_DELETE;
import static org.jooq.impl.Keywords.K_INSERT;
import static org.jooq.impl.Keywords.K_SELECT;
import static org.jooq.impl.Keywords.K_UPDATE;
import static org.jooq.impl.ParserImpl.Type.A;
import static org.jooq.impl.ParserImpl.Type.B;
import static org.jooq.impl.ParserImpl.Type.D;
import static org.jooq.impl.ParserImpl.Type.N;
import static org.jooq.impl.ParserImpl.Type.S;
import static org.jooq.impl.ParserImpl.Type.X;
import static org.jooq.impl.SQLDataType.INTEGER;
import static org.jooq.impl.Tools.EMPTY_BYTE;
import static org.jooq.impl.Tools.EMPTY_COLLECTION;
import static org.jooq.impl.Tools.EMPTY_COMMON_TABLE_EXPRESSION;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.EMPTY_NAME;
import static org.jooq.impl.Tools.EMPTY_QUERYPART;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.jooq.AggregateFilterStep;
import org.jooq.AggregateFunction;
import org.jooq.AlterIndexFinalStep;
import org.jooq.AlterIndexStep;
import org.jooq.AlterSchemaFinalStep;
import org.jooq.AlterSchemaStep;
import org.jooq.AlterSequenceStep;
import org.jooq.AlterTableDropStep;
import org.jooq.AlterTableFinalStep;
import org.jooq.AlterTableStep;
import org.jooq.ArrayAggOrderByStep;
import org.jooq.Block;
import org.jooq.CaseConditionStep;
import org.jooq.CaseValueStep;
import org.jooq.CaseWhenStep;
import org.jooq.Catalog;
import org.jooq.Comment;
import org.jooq.CommentOnIsStep;
import org.jooq.CommonTableExpression;
import org.jooq.Comparator;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Constraint;
import org.jooq.ConstraintForeignKeyOnStep;
import org.jooq.ConstraintTypeStep;
import org.jooq.CreateIndexFinalStep;
import org.jooq.CreateIndexStep;
import org.jooq.CreateIndexWhereStep;
import org.jooq.CreateTableAsStep;
import org.jooq.CreateTableColumnStep;
import org.jooq.CreateTableCommentStep;
import org.jooq.CreateTableConstraintStep;
import org.jooq.CreateTableStorageStep;
import org.jooq.DDLQuery;
import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.DatePart;
import org.jooq.Delete;
import org.jooq.DeleteReturningStep;
import org.jooq.DeleteWhereStep;
import org.jooq.DerivedColumnList;
import org.jooq.DropIndexFinalStep;
import org.jooq.DropIndexOnStep;
import org.jooq.DropSchemaFinalStep;
import org.jooq.DropSchemaStep;
import org.jooq.DropTableFinalStep;
import org.jooq.DropTableStep;
import org.jooq.DropViewFinalStep;
import org.jooq.Field;
import org.jooq.FieldOrRow;
import org.jooq.GrantOnStep;
import org.jooq.GrantToStep;
import org.jooq.GrantWithGrantOptionStep;
import org.jooq.GroupConcatOrderByStep;
import org.jooq.GroupConcatSeparatorStep;
import org.jooq.GroupField;
import org.jooq.Insert;
import org.jooq.InsertOnConflictDoUpdateStep;
import org.jooq.InsertOnConflictWhereStep;
import org.jooq.InsertOnDuplicateStep;
import org.jooq.InsertReturningStep;
import org.jooq.InsertSetStep;
import org.jooq.InsertValuesStepN;
import org.jooq.JoinType;
import org.jooq.Keyword;
import org.jooq.Merge;
import org.jooq.MergeFinalStep;
import org.jooq.MergeMatchedStep;
import org.jooq.MergeNotMatchedStep;
import org.jooq.Name;
import org.jooq.OrderedAggregateFunction;
import org.jooq.OrderedAggregateFunctionOfDeferredType;
import org.jooq.Param;
import org.jooq.Parser;
import org.jooq.Privilege;
import org.jooq.Queries;
import org.jooq.Query;
import org.jooq.QueryPart;
import org.jooq.QueryPartInternal;
import org.jooq.Record;
import org.jooq.ResultQuery;
import org.jooq.RevokeFromStep;
import org.jooq.RevokeOnStep;
import org.jooq.Row;
import org.jooq.Row2;
import org.jooq.RowN;
import org.jooq.SQL;
import org.jooq.Schema;
import org.jooq.Select;
import org.jooq.Sequence;
import org.jooq.SortField;
import org.jooq.Statement;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.TableLike;
import org.jooq.TableOnStep;
import org.jooq.TableOptionalOnStep;
import org.jooq.TablePartitionByStep;
import org.jooq.Truncate;
import org.jooq.TruncateCascadeStep;
import org.jooq.TruncateFinalStep;
import org.jooq.TruncateIdentityStep;
import org.jooq.Update;
import org.jooq.UpdateReturningStep;
import org.jooq.User;
// ...
import org.jooq.WindowBeforeOverStep;
import org.jooq.WindowIgnoreNullsStep;
import org.jooq.WindowOverStep;
import org.jooq.WindowSpecification;
import org.jooq.WindowSpecificationOrderByStep;
import org.jooq.WindowSpecificationRowsAndStep;
import org.jooq.WindowSpecificationRowsStep;
import org.jooq.tools.reflect.Reflect;
/**
* @author Lukas Eder
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
final class ParserImpl implements Parser {
private final DSLContext dsl;
ParserImpl(Configuration configuration) {
this.dsl = DSL.using(configuration);
}
// -----------------------------------------------------------------------------------------------------------------
// Top level parsing
// -----------------------------------------------------------------------------------------------------------------
@Override
public final Queries parse(String sql) {
return parse(sql, new Object[0]);
}
@Override
public final Queries parse(String sql, Object... bindings) {
ParserContext ctx = new ParserContext(dsl, sql, bindings);
List<Query> result = new ArrayList<Query>();
Query query;
do {
parseDelimiterSpecifications(ctx);
query = parseQuery(ctx, false);
if (query == IGNORE || query == IGNORE_NO_DELIMITER)
continue;
if (query != null)
result.add(query);
}
while (query == IGNORE_NO_DELIMITER || parseIf(ctx, ctx.delimiter));
ctx.done("Unexpected content after end of queries input");
return dsl.queries(result);
}
@Override
public final Query parseQuery(String sql) {
return parseQuery(sql, new Object[0]);
}
@Override
public final Query parseQuery(String sql, Object... bindings) {
ParserContext ctx = new ParserContext(dsl, sql, bindings);
Query result = parseQuery(ctx, false);
ctx.done("Unexpected content after end of query input");
return result;
}
@Override
public final ResultQuery<?> parseResultQuery(String sql) {
return parseResultQuery(sql, new Object[0]);
}
@Override
public final ResultQuery<?> parseResultQuery(String sql, Object... bindings) {
ParserContext ctx = new ParserContext(dsl, sql, bindings);
ResultQuery<?> result = (ResultQuery<?>) parseQuery(ctx, true);
ctx.done("Unexpected content after end of query input");
return result;
}
@Override
public final Table<?> parseTable(String sql) {
return parseTable(sql, new Object[0]);
}
@Override
public final Table<?> parseTable(String sql, Object... bindings) {
ParserContext ctx = new ParserContext(dsl, sql, bindings);
Table<?> result = parseTable(ctx);
ctx.done("Unexpected content after end of table input");
return result;
}
@Override
public final Field<?> parseField(String sql) {
return parseField(sql, new Object[0]);
}
@Override
public final Field<?> parseField(String sql, Object... bindings) {
ParserContext ctx = new ParserContext(dsl, sql, bindings);
Field<?> result = parseField(ctx);
ctx.done("Unexpected content after end of field input");
return result;
}
@Override
public final Row parseRow(String sql) {
return parseRow(sql, new Object[0]);
}
@Override
public final Row parseRow(String sql, Object... bindings) {
ParserContext ctx = new ParserContext(dsl, sql, bindings);
RowN result = parseRow(ctx);
ctx.done("Unexpected content after end of row input");
return result;
}
@Override
public final Condition parseCondition(String sql) {
return parseCondition(sql, new Object[0]);
}
@Override
public final Condition parseCondition(String sql, Object... bindings) {
ParserContext ctx = new ParserContext(dsl, sql, bindings);
Condition result = parseCondition(ctx);
ctx.done("Unexpected content after end of condition input");
return result;
}
@Override
public final Name parseName(String sql) {
return parseName(sql, new Object[0]);
}
@Override
public final Name parseName(String sql, Object... bindings) {
ParserContext ctx = new ParserContext(dsl, sql, bindings);
Name result = parseName(ctx);
ctx.done("Unexpected content after end of name input");
return result;
}
private static final void parseDelimiterSpecifications(ParserContext ctx) {
while (parseKeywordIf(ctx, "DELIMITER")) {
if (ctx.character() != ' ')
throw ctx.unexpectedToken();
ctx.delimiter = parseUntilEOL(ctx).trim();
}
}
private static final Query parseQuery(ParserContext ctx, boolean resultQuery) {
parseWhitespaceIf(ctx);
if (ctx.done())
return null;
switch (ctx.character()) {
case 'a':
case 'A':
if (!resultQuery && peekKeyword(ctx, "ALTER"))
return parseAlter(ctx);
break;
case 'b':
case 'B':
if (!resultQuery && peekKeyword(ctx, "BEGIN"))
return parseBlock(ctx);
break;
case 'c':
case 'C':
if (!resultQuery && peekKeyword(ctx, "CREATE"))
return parseCreate(ctx);
else if (!resultQuery && peekKeyword(ctx, "COMMENT ON"))
return parseCommentOn(ctx);
break;
case 'd':
case 'D':
if (!resultQuery && peekKeyword(ctx, "DELETE"))
return parseDelete(ctx);
else if (!resultQuery && peekKeyword(ctx, "DROP"))
return parseDrop(ctx);
else if (!resultQuery && peekKeyword(ctx, "DO"))
return parseDo(ctx);
break;
case 'e':
case 'E':
if (!resultQuery && peekKeyword(ctx, "EXECUTE BLOCK AS BEGIN"))
return parseBlock(ctx);
else if (!resultQuery && peekKeyword(ctx, "EXEC"))
return parseExec(ctx);
break;
case 'g':
case 'G':
if (!resultQuery && peekKeyword(ctx, "GRANT"))
return parseGrant(ctx);
break;
case 'i':
case 'I':
if (!resultQuery && peekKeyword(ctx, "INSERT"))
return parseInsert(ctx);
break;
case 'm':
case 'M':
if (!resultQuery && peekKeyword(ctx, "MERGE"))
return parseMerge(ctx);
break;
case 'r':
case 'R':
if (!resultQuery && peekKeyword(ctx, "RENAME"))
return parseRename(ctx);
else if (!resultQuery && peekKeyword(ctx, "REVOKE"))
return parseRevoke(ctx);
break;
case 's':
case 'S':
if (peekKeyword(ctx, "SELECT"))
return parseSelect(ctx);
else if (!resultQuery && peekKeyword(ctx, "SET"))
return parseSet(ctx);
break;
case 't':
case 'T':
if (!resultQuery && peekKeyword(ctx, "TRUNCATE"))
return parseTruncate(ctx);
break;
case 'u':
case 'U':
if (!resultQuery && peekKeyword(ctx, "UPDATE"))
return parseUpdate(ctx);
else if (!resultQuery && peekKeyword(ctx, "USE"))
return parseUse(ctx);
break;
case 'v':
case 'V':
if (peekKeyword(ctx, "VALUES"))
return ctx.dsl.selectFrom(parseTableValueConstructor(ctx));
case 'w':
case 'W':
if (peekKeyword(ctx, "WITH"))
return parseWith(ctx);
break;
case '(':
// TODO are there other possible statement types?
return parseSelect(ctx);
default:
break;
}
throw ctx.exception("Unsupported query type");
}
// -----------------------------------------------------------------------------------------------------------------
// Statement parsing
// -----------------------------------------------------------------------------------------------------------------
private static final Query parseWith(ParserContext ctx) {
parseKeyword(ctx, "WITH");
boolean recursive = parseKeywordIf(ctx, "RECURSIVE");
List<CommonTableExpression<?>> cte = new ArrayList<CommonTableExpression<?>>();
do {
Name table = parseIdentifier(ctx);
DerivedColumnList dcl = null;
if (parseIf(ctx, '(')) {
List<Name> columnNames = parseIdentifiers(ctx);
parse(ctx, ')');
dcl = table.fields(columnNames.toArray(EMPTY_NAME));
}
parseKeyword(ctx, "AS");
parse(ctx, '(');
Select<?> select = parseSelect(ctx);
parse(ctx, ')');
cte.add(dcl != null ? dcl.as(select) : table.as(select));
}
while (parseIf(ctx, ','));
// TODO Better model API for WITH clause
return parseSelect(ctx, null, (WithImpl) new WithImpl(ctx.dsl.configuration(), recursive).with(cte.toArray(EMPTY_COMMON_TABLE_EXPRESSION)));
// TODO Other statements than SELECT
}
private static final SelectQueryImpl<Record> parseSelect(ParserContext ctx) {
return parseSelect(ctx, null, null);
}
private static final SelectQueryImpl<Record> parseSelect(ParserContext ctx, Integer degree) {
return parseSelect(ctx, degree, null);
}
/*
* QueryPart condition = parseNot(ctx);
while (parseKeywordIf(ctx, "AND"))
condition = toCondition(ctx, condition).and(toCondition(ctx, parseNot(ctx)));
return condition;
*/
private static final SelectQueryImpl<Record> parseSelect(ParserContext ctx, Integer degree, WithImpl with) {
SelectQueryImpl<Record> result = parseQueryExpressionBody(ctx, degree, with);
if (parseKeywordIf(ctx, "ORDER"))
if (parseKeywordIf(ctx, "SIBLINGS BY")) {
result.addOrderBy(parseSortSpecification(ctx));
result.setOrderBySiblings(true);
}
else if (parseKeywordIf(ctx, "BY"))
result.addOrderBy(parseSortSpecification(ctx));
else
throw ctx.unexpectedToken();
if (!result.getLimit().isApplicable()) {
boolean offsetStandard = false;
boolean offsetPostgres = false;
if (parseKeywordIf(ctx, "OFFSET")) {
result.addOffset(inline((int) (long) parseUnsignedInteger(ctx)));
if (parseKeywordIf(ctx, "ROWS") || parseKeywordIf(ctx, "ROW"))
offsetStandard = true;
// Ingres doesn't have a ROWS keyword after offset
else if (peekKeyword(ctx, "FETCH"))
offsetStandard = true;
else
offsetPostgres = true;
}
if (!offsetStandard && parseKeywordIf(ctx, "LIMIT")) {
Param<Integer> limit = inline((int) (long) parseUnsignedInteger(ctx));
if (offsetPostgres) {
result.addLimit(limit);
if (parseKeywordIf(ctx, "WITH TIES"))
result.setWithTies(true);
}
else if (parseIf(ctx, ',')) {
result.addLimit(limit, inline((int) (long) parseUnsignedInteger(ctx)));
}
else {
if (parseKeywordIf(ctx, "WITH TIES"))
result.setWithTies(true);
if (parseKeywordIf(ctx, "OFFSET"))
result.addLimit(inline((int) (long) parseUnsignedInteger(ctx)), limit);
else
result.addLimit(limit);
}
}
else if (!offsetPostgres && parseKeywordIf(ctx, "FETCH")) {
if (!parseKeywordIf(ctx, "FIRST") && !parseKeywordIf(ctx, "NEXT"))
throw ctx.unexpectedToken();
result.addLimit(inline((int) (long) parseUnsignedInteger(ctx)));
if (!parseKeywordIf(ctx, "ROW") && !parseKeywordIf(ctx, "ROWS"))
throw ctx.unexpectedToken();
if (parseKeywordIf(ctx, "WITH TIES"))
result.setWithTies(true);
else
parseKeyword(ctx, "ONLY");
}
}
if (parseKeywordIf(ctx, "FOR")) {
if (parseKeywordIf(ctx, "SHARE")) {
result.setForShare(true);
}
else if (parseKeywordIf(ctx, "UPDATE")) {
result.setForUpdate(true);
if (parseKeywordIf(ctx, "OF"))
result.setForUpdateOf(parseFields(ctx));
if (parseKeywordIf(ctx, "NOWAIT"))
result.setForUpdateNoWait();
else if (parseKeywordIf(ctx, "SKIP LOCKED"))
result.setForUpdateSkipLocked();
}
else
throw ctx.unexpectedToken();
}
return result;
}
private static final SelectQueryImpl<Record> parseQueryExpressionBody(ParserContext ctx, Integer degree, WithImpl with) {
SelectQueryImpl<Record> result = parseQueryTerm(ctx, degree, with);
CombineOperator combine;
while ((combine = parseCombineOperatorIf(ctx, false)) != null) {
if (degree == null)
degree = result.getSelect().size();
switch (combine) {
case UNION:
result = (SelectQueryImpl<Record>) result.union(parseQueryTerm(ctx, degree));
break;
case UNION_ALL:
result = (SelectQueryImpl<Record>) result.unionAll(parseQueryTerm(ctx, degree));
break;
case EXCEPT:
result = (SelectQueryImpl<Record>) result.except(parseQueryTerm(ctx, degree));
break;
case EXCEPT_ALL:
result = (SelectQueryImpl<Record>) result.exceptAll(parseQueryTerm(ctx, degree));
break;
default:
ctx.unexpectedToken();
break;
}
}
return result;
}
private static final SelectQueryImpl<Record> parseQueryTerm(ParserContext ctx, Integer degree) {
return parseQueryTerm(ctx, degree, null);
}
private static final SelectQueryImpl<Record> parseQueryTerm(ParserContext ctx, Integer degree, WithImpl with) {
SelectQueryImpl<Record> result = parseQueryPrimary(ctx, degree, with);
CombineOperator combine;
while ((combine = parseCombineOperatorIf(ctx, true)) != null) {
if (degree == null)
degree = result.getSelect().size();
switch (combine) {
case INTERSECT:
result = (SelectQueryImpl<Record>) result.intersect(parseQueryPrimary(ctx, degree));
break;
case INTERSECT_ALL:
result = (SelectQueryImpl<Record>) result.intersectAll(parseQueryPrimary(ctx, degree));
break;
default:
ctx.unexpectedToken();
break;
}
}
return result;
}
private static final SelectQueryImpl<Record> parseQueryPrimary(ParserContext ctx, Integer degree) {
return parseQueryPrimary(ctx, degree, null);
}
private static final SelectQueryImpl<Record> parseQueryPrimary(ParserContext ctx, Integer degree, WithImpl with) {
if (parseIf(ctx, '(')) {
SelectQueryImpl<Record> result = parseSelect(ctx, degree, with);
parse(ctx, ')');
return result;
}
parseKeyword(ctx, "SELECT");
boolean distinct = parseKeywordIf(ctx, "DISTINCT") || parseKeywordIf(ctx, "UNIQUE");
List<Field<?>> distinctOn = null;
if (distinct) {
if (parseKeywordIf(ctx, "ON")) {
parse(ctx, '(');
distinctOn = parseFields(ctx);
parse(ctx, ')');
}
}
else
parseKeywordIf(ctx, "ALL");
Long limit = null;
Long offset = null;
boolean withTies = false;
// T-SQL style TOP .. START AT
if (parseKeywordIf(ctx, "TOP")) {
limit = parseUnsignedInteger(ctx);
if (parseKeywordIf(ctx, "START AT"))
offset = parseUnsignedInteger(ctx);
else if (parseKeywordIf(ctx, "WITH TIES"))
withTies = true;
}
// Informix style SKIP .. FIRST
else if (parseKeywordIf(ctx, "SKIP")) {
offset = parseUnsignedInteger(ctx);
if (parseKeywordIf(ctx, "FIRST"))
limit = parseUnsignedInteger(ctx);
}
else if (parseKeywordIf(ctx, "FIRST")) {
limit = parseUnsignedInteger(ctx);
}
List<Field<?>> select = parseSelectList(ctx);
if (degree != null && select.size() != degree)
throw ctx.exception("Select list must contain " + degree + " columns. Got: " + select.size());
Table<?> into = null;
List<Table<?>> from = null;
Condition startWith = null;
Condition connectBy = null;
boolean connectByNoCycle = false;
Condition where = null;
List<GroupField> groupBy = null;
Condition having = null;
if (parseKeywordIf(ctx, "INTO"))
into = parseTableName(ctx);
if (parseKeywordIf(ctx, "FROM"))
from = parseTables(ctx);
// TODO is there a better way?
if (from != null && from.size() == 1 && from.get(0).getName().equalsIgnoreCase("dual"))
from = null;
if (parseKeywordIf(ctx, "START WITH")) {
startWith = parseCondition(ctx);
parseKeyword(ctx, "CONNECT BY");
connectByNoCycle = parseKeywordIf(ctx, "NOCYCLE");
connectBy = parseCondition(ctx);
}
else if (parseKeywordIf(ctx, "CONNECT BY")) {
connectByNoCycle = parseKeywordIf(ctx, "NOCYCLE");
connectBy = parseCondition(ctx);
if (parseKeywordIf(ctx, "START WITH"))
startWith = parseCondition(ctx);
}
if (parseKeywordIf(ctx, "WHERE"))
where = parseCondition(ctx);
if (parseKeywordIf(ctx, "GROUP BY")) {
if (parseIf(ctx, '(')) {
parse(ctx, ')');
groupBy = emptyList();
}
else if (parseKeywordIf(ctx, "ROLLUP")) {
parse(ctx, '(');
groupBy = singletonList(rollup(parseFields(ctx).toArray(EMPTY_FIELD)));
parse(ctx, ')');
}
else if (parseKeywordIf(ctx, "CUBE")) {
parse(ctx, '(');
groupBy = singletonList(cube(parseFields(ctx).toArray(EMPTY_FIELD)));
parse(ctx, ')');
}
else if (parseKeywordIf(ctx, "GROUPING SETS")) {
List<List<Field<?>>> fieldSets = new ArrayList<List<Field<?>>>();
parse(ctx, '(');
do {
parse(ctx, '(');
if (parseIf(ctx, ')')) {
fieldSets.add(Collections.<Field<?>>emptyList());
}
else {
fieldSets.add(parseFields(ctx));
parse(ctx, ')');
}
}
while (parseIf(ctx, ','));
parse(ctx, ')');
groupBy = singletonList(groupingSets(fieldSets.toArray((Collection[]) EMPTY_COLLECTION)));
}
else {
groupBy = (List) parseFields(ctx);
if (parseKeywordIf(ctx, "WITH ROLLUP"))
groupBy = singletonList(rollup(groupBy.toArray(EMPTY_FIELD)));
}
}
if (parseKeywordIf(ctx, "HAVING"))
having = parseCondition(ctx);
// TODO support WINDOW
SelectQueryImpl<Record> result = new SelectQueryImpl<Record>(ctx.dsl.configuration(), with);
if (distinct)
result.setDistinct(distinct);
if (distinctOn != null)
result.addDistinctOn(distinctOn);
if (select.size() > 0)
result.addSelect(select);
if (into != null)
result.setInto(into);
if (from != null)
result.addFrom(from);
if (connectBy != null)
if (connectByNoCycle)
result.addConnectByNoCycle(connectBy);
else
result.addConnectBy(connectBy);
if (startWith != null)
result.setConnectByStartWith(startWith);
if (where != null)
result.addConditions(where);
if (groupBy != null)
result.addGroupBy(groupBy);
if (having != null)
result.addHaving(having);
if (limit != null)
if (offset != null)
result.addLimit((int) (long) offset, (int) (long) limit);
else
result.addLimit((int) (long) limit);
if (withTies)
result.setWithTies(true);
return result;
}
private static final Delete<?> parseDelete(ParserContext ctx) {
parseKeyword(ctx, "DELETE");
parseKeywordIf(ctx, "FROM");
Table<?> tableName = parseTableName(ctx);
boolean where = parseKeywordIf(ctx, "WHERE");
Condition condition = where ? parseCondition(ctx) : null;
DeleteWhereStep<?> s1;
DeleteReturningStep<?> s2;
s1 = ctx.dsl.delete(tableName);
s2 = where
? s1.where(condition)
: s1;
if (parseKeywordIf(ctx, "RETURNING"))
if (parseIf(ctx, '*'))
return s2.returning();
else
return s2.returning(parseFields(ctx));
else
return s2;
}
private static final Insert<?> parseInsert(ParserContext ctx) {
parseKeyword(ctx, "INSERT INTO");
Table<?> tableName = parseTableName(ctx);
Field<?>[] fields = null;
if (parseIf(ctx, '(')) {
fields = Tools.fieldsByName(parseIdentifiers(ctx).toArray(EMPTY_NAME));
parse(ctx, ')');
}
InsertOnDuplicateStep<?> onDuplicate;
InsertReturningStep<?> returning;
if (parseKeywordIf(ctx, "VALUES")) {
List<List<Field<?>>> allValues = new ArrayList<List<Field<?>>>();
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 = parseFields(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, ','));
InsertSetStep<?> step1 = ctx.dsl.insertInto(tableName);
if (allValues.isEmpty()) {
returning = onDuplicate = step1.defaultValues();
}
else {
InsertValuesStepN<?> step2 = (fields != null)
? step1.columns(fields)
: (InsertValuesStepN<?>) step1;
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 = ctx.dsl.insertInto(tableName).set(map);
}
else if (peekKeyword(ctx, "SELECT", false, true, false)){
SelectQueryImpl<Record> select = parseSelect(ctx);
returning = onDuplicate = (fields == null)
? ctx.dsl.insertInto(tableName).select(select)
: ctx.dsl.insertInto(tableName).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 = ctx.dsl.insertInto(tableName).defaultValues();
}
else
throw ctx.unexpectedToken();
if (parseKeywordIf(ctx, "ON")) {
if (parseKeywordIf(ctx, "DUPLICATE KEY UPDATE SET")) {
returning = onDuplicate.onDuplicateKeyUpdate().set(parseSetClauseList(ctx));
}
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 {
parse(ctx, '(');
doUpdate = onDuplicate.onConflict(parseFieldNames(ctx));
parse(ctx, ')');
}
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.unexpectedToken();
}
else
throw ctx.unexpectedToken();
}
if (parseKeywordIf(ctx, "RETURNING"))
if (parseIf(ctx, '*'))
return returning.returning();
else
return returning.returning(parseFields(ctx));
else
return returning;
}
private static final Update<?> parseUpdate(ParserContext ctx) {
parseKeyword(ctx, "UPDATE");
Table<?> tableName = parseTableName(ctx);
parseKeyword(ctx, "SET");
// TODO Row value expression updates
Map<Field<?>, Object> map = parseSetClauseList(ctx);
// TODO support FROM
Condition condition = parseKeywordIf(ctx, "WHERE") ? parseCondition(ctx) : null;
// TODO support RETURNING
UpdateReturningStep<?> returning = condition == null
? ctx.dsl.update(tableName).set(map)
: ctx.dsl.update(tableName).set(map).where(condition);
if (parseKeywordIf(ctx, "RETURNING"))
if (parseIf(ctx, '*'))
return returning.returning();
else
return returning.returning(parseFields(ctx));
else
return returning;
}
private static final Map<Field<?>, Object> parseSetClauseList(ParserContext ctx) {
Map<Field<?>, Object> map = new LinkedHashMap<Field<?>, Object>();
do {
Field<?> field = parseFieldName(ctx);
if (map.containsKey(field))
throw ctx.exception("Duplicate column in set clause list: " + field);
parse(ctx, '=');
Field<?> value = parseField(ctx);
map.put(field, value);
}
while (parseIf(ctx, ','));
return map;
}
private static final Merge<?> parseMerge(ParserContext ctx) {
parseKeyword(ctx, "MERGE INTO");
Table<?> target = parseTableName(ctx);
parseKeyword(ctx, "USING");
parse(ctx, '(');
Select<?> using = parseSelect(ctx);
TableLike<?> usingTable = using;
parse(ctx, ')');
if (parseKeywordIf(ctx, "AS"))
usingTable = DSL.table(using).as(parseIdentifier(ctx));
parseKeyword(ctx, "ON");
Condition on = parseCondition(ctx);
boolean update = false;
boolean insert = false;
Field<?>[] insertColumns = null;
List<Field<?>> insertValues = null;
Map<Field<?>, Object> updateSet = null;
for (;;) {
if (!update && (update = parseKeywordIf(ctx, "WHEN MATCHED THEN UPDATE SET"))) {
updateSet = parseSetClauseList(ctx);
}
else if (!insert && (insert = parseKeywordIf(ctx, "WHEN NOT MATCHED THEN INSERT"))) {
parse(ctx, '(');
insertColumns = Tools.fieldsByName(parseIdentifiers(ctx).toArray(EMPTY_NAME));
parse(ctx, ')');
parseKeyword(ctx, "VALUES");
parse(ctx, '(');
insertValues = parseFields(ctx);
parse(ctx, ')');
if (insertColumns.length != insertValues.size())
throw ctx.exception("Insert column size (" + insertColumns.length + ") must match values size (" + insertValues.size() + ")");
}
else
break;
}
if (!update && !insert)
throw ctx.exception("At least one of UPDATE or INSERT clauses is required");
// TODO support WHERE
// TODO support multi clause MERGE
// TODO support DELETE
MergeMatchedStep<?> s1 = ctx.dsl.mergeInto(target).using(usingTable).on(on);
MergeNotMatchedStep<?> s2 = update ? s1.whenMatchedThenUpdate().set(updateSet) : s1;
MergeFinalStep<?> s3 = insert ? s2.whenNotMatchedThenInsert(insertColumns).values(insertValues) : s2;
return s3;
}
private static final Query parseSet(ParserContext ctx) {
parseKeyword(ctx, "SET");
if (parseKeywordIf(ctx, "CATALOG"))
return parseSetCatalog(ctx);
else if (parseKeywordIf(ctx, "CURRENT SCHEMA"))
return parseSetSchema(ctx);
else if (parseKeywordIf(ctx, "CURRENT SQLID"))
return parseSetSchema(ctx);
else if (parseKeywordIf(ctx, "GENERATOR"))
return parseSetGenerator(ctx);
else if (parseKeywordIf(ctx, "SCHEMA"))
return parseSetSchema(ctx);
else if (parseKeywordIf(ctx, "SEARCH_PATH"))
return parseSetSearchPath(ctx);
// There are many SET commands in programs like sqlplus, which we'll simply ignore
parseUntilEOL(ctx);
return IGNORE_NO_DELIMITER;
}
private static final Query parseSetCatalog(ParserContext ctx) {
return ctx.dsl.setCatalog(parseCatalogName(ctx));
}
private static final Query parseUse(ParserContext ctx) {
parseKeyword(ctx, "USE");
return ctx.dsl.setCatalog(parseCatalogName(ctx));
}
private static final Query parseSetSchema(ParserContext ctx) {
parseIf(ctx, '=');
return ctx.dsl.setSchema(parseSchemaName(ctx));
}
private static final Query parseSetSearchPath(ParserContext ctx) {
if (!parseIf(ctx, '='))
parseKeyword(ctx, "TO");
Schema schema = null;
do {
Schema s = parseSchemaName(ctx);
if (schema == null)
schema = s;
}
while (parseIf(ctx, ','));
return ctx.dsl.setSchema(schema);
}
private static final DDLQuery parseCommentOn(ParserContext ctx) {
parseKeyword(ctx, "COMMENT ON");
CommentOnIsStep s1;
if (parseKeywordIf(ctx, "COLUMN"))
s1 = ctx.dsl.commentOnColumn(parseFieldName(ctx));
else if (parseKeywordIf(ctx, "TABLE"))
s1 = ctx.dsl.commentOnTable(parseTableName(ctx));
else if (parseKeywordIf(ctx, "VIEW"))
s1 = ctx.dsl.commentOnView(parseTableName(ctx));
// Ignored no-arg object comments
// https://www.postgresql.org/docs/10/static/sql-comment.html
// https://docs.oracle.com/database/121/SQLRF/statements_4010.htm
else if (parseAndGetKeywordIf(ctx,
"ACCESS METHOD",
"AUDIT POLICY",
"COLLATION",
"CONVERSION",
"DATABASE",
"DOMAIN",
"EDITION",
"EXTENSION",
"EVENT TRIGGER",
"FOREIGN DATA WRAPPER",
"FOREIGN TABLE",
"INDEX",
"INDEXTYPE",
"LANGUAGE",
"LARGE OBJECT",
"MATERIALIZED VIEW",
"MINING MODEL",
"OPERATOR",
"PROCEDURAL LANGUAGE",
"PUBLICATION",
"ROLE",
"SCHEMA",
"SEQUENCE",
"SERVER",
"STATISTICS",
"SUBSCRIPTION",
"TABLESPACE",
"TEXT SEARCH CONFIGURATION",
"TEXT SEARCH DICTIONARY",
"TEXT SEARCH PARSER",
"TEXT SEARCH TEMPLATE",
"TYPE",
"VIEW"
) != null) {
parseIdentifier(ctx);
parseKeyword(ctx, "IS");
parseStringLiteral(ctx);
return IGNORE;
}
// TODO: (PostgreSQL)
// AGGREGATE, CAST, FUNCTION, OPERATOR, OPERATOR CLASS, OPERATOR FAMILY
// Ignored object comments with arguments
// https://www.postgresql.org/docs/10/static/sql-comment.html
else if (parseKeywordIf(ctx, "CONSTRAINT")) {
parseIdentifier(ctx);
parseKeyword(ctx, "ON");
parseKeywordIf(ctx, "DOMAIN");
parseIdentifier(ctx);
parseKeyword(ctx, "IS");
parseStringLiteral(ctx);
return IGNORE;
}
else if (parseAndGetKeywordIf(ctx,
"POLICY",
"RULE",
"TRIGGER"
) != null) {
parseIdentifier(ctx);
parseKeyword(ctx, "ON");
parseIdentifier(ctx);
parseKeyword(ctx, "IS");
parseStringLiteral(ctx);
return IGNORE;
}
else if (parseKeywordIf(ctx, "TRANSFORM FOR")) {
parseIdentifier(ctx);
parseKeyword(ctx, "LANGUAGE");
parseIdentifier(ctx);
parseKeyword(ctx, "IS");
parseStringLiteral(ctx);
return IGNORE;
}
else
throw ctx.unexpectedToken();
parseKeyword(ctx, "IS");
return s1.is(parseStringLiteral(ctx));
}
private static final DDLQuery parseCreate(ParserContext ctx) {
parseKeyword(ctx, "CREATE");
if (parseKeywordIf(ctx, "TABLE"))
return parseCreateTable(ctx, false);
else if (parseKeywordIf(ctx, "TEMPORARY TABLE"))
return parseCreateTable(ctx, true);
else if (parseKeywordIf(ctx, "GENERATOR"))
return parseCreateSequence(ctx);
else if (parseKeywordIf(ctx, "GLOBAL TEMPORARY TABLE"))
return parseCreateTable(ctx, true);
else if (parseKeywordIf(ctx, "INDEX"))
return parseCreateIndex(ctx, false);
else if (parseKeywordIf(ctx, "UNIQUE INDEX"))
return parseCreateIndex(ctx, true);
else if (parseKeywordIf(ctx, "SCHEMA"))
return parseCreateSchema(ctx);
else if (parseKeywordIf(ctx, "SEQUENCE"))
return parseCreateSequence(ctx);
else if (parseKeywordIf(ctx, "VIEW"))
return parseCreateView(ctx);
else
throw ctx.unexpectedToken();
}
private static final Query parseAlter(ParserContext ctx) {
parseKeyword(ctx, "ALTER");
if (parseKeywordIf(ctx, "DOMAIN"))
return parseAlterDomain(ctx);
else if (parseKeywordIf(ctx, "INDEX"))
return parseAlterIndex(ctx);
else if (parseKeywordIf(ctx, "SCHEMA"))
return parseAlterSchema(ctx);
else if (parseKeywordIf(ctx, "SEQUENCE"))
return parseAlterSequence(ctx);
else if (parseKeywordIf(ctx, "SESSION"))
return parseAlterSession(ctx);
else if (parseKeywordIf(ctx, "TABLE"))
return parseAlterTable(ctx);
else if (parseKeywordIf(ctx, "VIEW"))
return parseAlterView(ctx);
else
throw ctx.unexpectedToken();
}
private static final DDLQuery parseDrop(ParserContext ctx) {
parseKeyword(ctx, "DROP");
if (parseKeywordIf(ctx, "TABLE"))
return parseDropTable(ctx, false);
else if (parseKeywordIf(ctx, "TEMPORARY TABLE"))
return parseDropTable(ctx, true);
else if (parseKeywordIf(ctx, "INDEX"))
return parseDropIndex(ctx);
else if (parseKeywordIf(ctx, "VIEW"))
return parseDropView(ctx);
else if (parseKeywordIf(ctx, "GENERATOR"))
return parseDropSequence(ctx);
else if (parseKeywordIf(ctx, "SEQUENCE"))
return parseDropSequence(ctx);
else if (parseKeywordIf(ctx, "SCHEMA"))
return parseDropSchema(ctx);
else
throw ctx.unexpectedToken();
}
private static final Truncate<?> parseTruncate(ParserContext ctx) {
parseKeyword(ctx, "TRUNCATE");
parseKeyword(ctx, "TABLE");
Table<?> table = parseTableName(ctx);
boolean continueIdentity = parseKeywordIf(ctx, "CONTINUE IDENTITY");
boolean restartIdentity = !continueIdentity && parseKeywordIf(ctx, "RESTART IDENTITY");
boolean cascade = parseKeywordIf(ctx, "CASCADE");
boolean restrict = !cascade && parseKeywordIf(ctx, "RESTRICT");
TruncateIdentityStep<?> step1 = ctx.dsl.truncate(table);
TruncateCascadeStep<?> step2 =
continueIdentity
? step1.continueIdentity()
: restartIdentity
? step1.restartIdentity()
: step1;
TruncateFinalStep<?> step3 =
cascade
? step2.cascade()
: restrict
? step2.restrict()
: step2;
return step3;
}
private static final DDLQuery parseGrant(ParserContext ctx) {
parseKeyword(ctx, "GRANT");
Privilege privilege = parsePrivilege(ctx);
List<Privilege> privileges = null;
while (parseIf(ctx, ',')) {
if (privileges == null) {
privileges = new ArrayList<Privilege>();
privileges.add(privilege);
}
privileges.add(parsePrivilege(ctx));
}
parseKeyword(ctx, "ON");
parseKeywordIf(ctx, "TABLE");
Table<?> table = parseTableName(ctx);
parseKeyword(ctx, "TO");
User user = parseKeywordIf(ctx, "PUBLIC") ? null : parseUser(ctx);
GrantOnStep s1 = privileges == null ? ctx.dsl.grant(privilege) : ctx.dsl.grant(privileges);
GrantToStep s2 = s1.on(table);
GrantWithGrantOptionStep s3 = user == null ? s2.toPublic() : s2.to(user);
return parseKeywordIf(ctx, "WITH GRANT OPTION")
? s3.withGrantOption()
: s3;
}
private static final DDLQuery parseRevoke(ParserContext ctx) {
parseKeyword(ctx, "REVOKE");
boolean grantOptionFor = parseKeywordIf(ctx, "GRANT OPTION FOR");
Privilege privilege = parsePrivilege(ctx);
List<Privilege> privileges = null;
while (parseIf(ctx, ',')) {
if (privileges == null) {
privileges = new ArrayList<Privilege>();
privileges.add(privilege);
}
privileges.add(parsePrivilege(ctx));
}
parseKeyword(ctx, "ON");
parseKeywordIf(ctx, "TABLE");
Table<?> table = parseTableName(ctx);
RevokeOnStep s1 = grantOptionFor
? privileges == null
? ctx.dsl.revokeGrantOptionFor(privilege)
: ctx.dsl.revokeGrantOptionFor(privileges)
: privileges == null
? ctx.dsl.revoke(privilege)
: ctx.dsl.revoke(privileges);
parseKeyword(ctx, "FROM");
User user = parseKeywordIf(ctx, "PUBLIC") ? null : parseUser(ctx);
RevokeFromStep s2 = s1.on(table);
return user == null ? s2.fromPublic() : s2.from(user);
}
private static final Query parseExec(ParserContext ctx) {
parseKeyword(ctx, "EXEC");
if (parseKeywordIf(ctx, "SP_RENAME")) {
if (parseKeywordIf(ctx, "@OBJNAME"))
parse(ctx, '=');
Name oldName = ctx.dsl.parser().parseName(parseStringLiteral(ctx));
parse(ctx, ',');
if (parseKeywordIf(ctx, "@NEWNAME"))
parse(ctx, '=');
Name newName = ctx.dsl.parser().parseName(parseStringLiteral(ctx));
String objectType = "TABLE";
if (parseIf(ctx, ',')) {
if (parseKeywordIf(ctx, "@OBJTYPE"))
parse(ctx, '=');
if (!parseKeywordIf(ctx, "NULL"))
objectType = parseStringLiteral(ctx);
}
if ("TABLE".equalsIgnoreCase(objectType))
return ctx.dsl.alterTable(oldName).renameTo(newName.unqualifiedName());
else if ("INDEX".equalsIgnoreCase(objectType))
return ctx.dsl.alterIndex(oldName).renameTo(newName.unqualifiedName());
else if ("COLUMN".equalsIgnoreCase(objectType))
return ctx.dsl.alterTable(oldName.qualifier()).renameColumn(oldName.unqualifiedName()).to(newName.unqualifiedName());
else
throw ctx.exception("Unsupported object type: " + objectType);
}
else {
throw ctx.unexpectedToken();
}
}
private static final Block parseBlock(ParserContext ctx) {
parseKeywordIf(ctx, "EXECUTE BLOCK AS");
parseKeyword(ctx, "BEGIN");
List<Statement> statements = new ArrayList<Statement>();
for (;;) {
Statement statement = parseStatement(ctx);
statements.add(statement);
if (!(statement instanceof Block))
parse(ctx, ';');
if (parseKeywordIf(ctx, "END"))
break;
}
parse(ctx, ';');
return ctx.dsl.begin(statements);
}
private static final Block parseDo(ParserContext ctx) {
parseKeyword(ctx, "DO");
String block = parseStringLiteral(ctx);
return (Block) ctx.dsl.parser().parseQuery(block);
}
private static final Statement parseStatement(ParserContext ctx) {
parseWhitespaceIf(ctx);
switch (ctx.character()) {
case 'n':
case 'N':
if (peekKeyword(ctx, "NULL"))
return parseNullStatement(ctx);
break;
}
return parseQuery(ctx, false);
}
// -----------------------------------------------------------------------------------------------------------------
// Statement parsing
// -----------------------------------------------------------------------------------------------------------------
private static final Statement parseNullStatement(ParserContext ctx) {
parseKeyword(ctx, "NULL");
return new NullStatement();
}
// -----------------------------------------------------------------------------------------------------------------
// Statement clause parsing
// -----------------------------------------------------------------------------------------------------------------
private static final Privilege parsePrivilege(ParserContext ctx) {
if (parseKeywordIf(ctx, "SELECT"))
return privilege(K_SELECT);
else if (parseKeywordIf(ctx, "INSERT"))
return privilege(K_INSERT);
else if (parseKeywordIf(ctx, "UPDATE"))
return privilege(K_UPDATE);
else if (parseKeywordIf(ctx, "DELETE"))
return privilege(K_DELETE);
else
throw ctx.unexpectedToken();
}
private static final User parseUser(ParserContext ctx) {
return user(parseName(ctx));
}
private static final DDLQuery parseCreateView(ParserContext ctx) {
boolean ifNotExists = parseKeywordIf(ctx, "IF NOT EXISTS");
Table<?> view = parseTableName(ctx);
Field<?>[] fields = EMPTY_FIELD;
if (parseIf(ctx, '(')) {
fields = parseFieldNames(ctx).toArray(fields);
parse(ctx, ')');
}
parseKeyword(ctx, "AS");
Select<?> select = parseSelect(ctx);
if (fields.length > 0 && fields.length != select.getSelect().size())
throw ctx.exception("Select list size (" + select.getSelect().size() + ") must match declared field size (" + fields.length + ")");
return ifNotExists
? ctx.dsl.createViewIfNotExists(view, fields).as(select)
: ctx.dsl.createView(view, fields).as(select);
}
private static final DDLQuery parseAlterView(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Table<?> oldName = parseTableName(ctx);
parseKeyword(ctx, "RENAME TO");
Table<?> newName = parseTableName(ctx);
return ifExists
? ctx.dsl.alterViewIfExists(oldName).renameTo(newName)
: ctx.dsl.alterView(oldName).renameTo(newName);
}
private static final DDLQuery parseDropView(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Table<?> tableName = parseTableName(ctx);
DropViewFinalStep s1;
s1 = ifExists
? ctx.dsl.dropViewIfExists(tableName)
: ctx.dsl.dropView(tableName);
return s1;
}
private static final DDLQuery parseCreateSequence(ParserContext ctx) {
boolean ifNotExists = parseKeywordIf(ctx, "IF NOT EXISTS");
Sequence<?> schemaName = parseSequenceName(ctx);
return ifNotExists
? ctx.dsl.createSequenceIfNotExists(schemaName)
: ctx.dsl.createSequence(schemaName);
}
private static final DDLQuery parseAlterSequence(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Sequence<?> sequenceName = parseSequenceName(ctx);
AlterSequenceStep s1 = ifExists
? ctx.dsl.alterSequenceIfExists(sequenceName)
: ctx.dsl.alterSequence(sequenceName);
if (parseKeywordIf(ctx, "RENAME TO"))
return s1.renameTo(parseSequenceName(ctx));
else if (parseKeywordIf(ctx, "RESTART"))
if (parseKeywordIf(ctx, "WITH"))
return s1.restartWith(parseUnsignedInteger(ctx));
else
return s1.restart();
else
throw ctx.unexpectedToken();
}
private static final Query parseAlterSession(ParserContext ctx) {
parseKeyword(ctx, "SET CURRENT_SCHEMA");
parse(ctx, '=');
return ctx.dsl.setSchema(parseSchemaName(ctx));
}
private static final DDLQuery parseSetGenerator(ParserContext ctx) {
Sequence<?> sequenceName = parseSequenceName(ctx);
parseKeyword(ctx, "TO");
return ctx.dsl.alterSequence((Sequence) sequenceName).restartWith(parseUnsignedInteger(ctx));
}
private static final DDLQuery parseDropSequence(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Sequence<?> sequenceName = parseSequenceName(ctx);
return ifExists
? ctx.dsl.dropSequenceIfExists(sequenceName)
: ctx.dsl.dropSequence(sequenceName);
}
private static final DDLQuery parseCreateTable(ParserContext ctx, boolean temporary) {
boolean ifNotExists = !temporary && parseKeywordIf(ctx, "IF NOT EXISTS");
Table<?> tableName = DSL.table(parseTableName(ctx).getQualifiedName());
CreateTableCommentStep commentStep;
CreateTableStorageStep storageStep;
// [#5309] TODO: Move this after the column specification
if (parseKeywordIf(ctx, "AS")) {
Select<?> select = parseSelect(ctx);
CreateTableAsStep<Record> s1 = ifNotExists
? ctx.dsl.createTableIfNotExists(tableName)
: temporary
? ctx.dsl.createTemporaryTable(tableName)
: ctx.dsl.createTable(tableName);
storageStep = commentStep = s1.as(select);
}
else {
List<Field<?>> fields = new ArrayList<Field<?>>();
List<Constraint> constraints = new ArrayList<Constraint>();
boolean primary = false;
boolean noConstraint = true;
parse(ctx, '(');
do {
Name fieldName = parseIdentifier(ctx);
DataType<?> type = parseDataType(ctx);
Comment fieldComment = null;
boolean nullable = false;
boolean defaultValue = false;
boolean onUpdate = false;
boolean unique = false;
boolean identity = type.identity();
boolean comment = false;
for (;;) {
if (!nullable) {
if (parseKeywordIf(ctx, "NULL")) {
type = type.nullable(true);
nullable = true;
continue;
}
else if (parseKeywordIf(ctx, "NOT NULL")) {
type = type.nullable(false);
nullable = true;
continue;
}
}
if (!defaultValue) {
if (parseKeywordIf(ctx, "DEFAULT")) {
// TODO: Ignored keyword from Oracle
parseKeywordIf(ctx, "ON NULL");
type = type.defaultValue((Field) toField(ctx, parseConcat(ctx, null)));
defaultValue = true;
identity = true;
continue;
}
else if (parseKeywordIf(ctx, "GENERATED")) {
if (!parseKeywordIf(ctx, "ALWAYS")) {
parseKeyword(ctx, "BY DEFAULT");
// TODO: Ignored keyword from Oracle
parseKeywordIf(ctx, "ON NULL");
}
parseKeyword(ctx, "AS IDENTITY");
// TODO: Ignored identity options from Oracle
if (parseIf(ctx, '(')) {
boolean identityOption = false;
for (;;) {
if (parseKeywordIf(ctx, "START WITH")) {
if (!parseKeywordIf(ctx, "LIMIT VALUE"))
parseUnsignedInteger(ctx);
identityOption = true;
continue;
}
else if (parseKeywordIf(ctx, "INCREMENT BY")
|| parseKeywordIf(ctx, "MAXVALUE")
|| parseKeywordIf(ctx, "MINVALUE")
|| parseKeywordIf(ctx, "CACHE")) {
parseUnsignedInteger(ctx);
identityOption = true;
continue;
}
else if (parseKeywordIf(ctx, "NOMAXVALUE")
|| parseKeywordIf(ctx, "NOMINVALUE")
|| parseKeywordIf(ctx, "CYCLE")
|| parseKeywordIf(ctx, "NOCYCLE")
|| parseKeywordIf(ctx, "NOCACHE")
|| parseKeywordIf(ctx, "ORDER")
|| parseKeywordIf(ctx, "NOORDER")) {
identityOption = true;
continue;
}
if (identityOption)
break;
else
throw ctx.unexpectedToken();
}
parse(ctx, ')');
}
type = type.identity(true);
defaultValue = true;
identity = true;
continue;
}
}
if (!onUpdate) {
if (parseKeywordIf(ctx, "ON UPDATE")) {
// [#6132] TODO: Support this feature in the jOOQ DDL API
parseConcat(ctx, null);
onUpdate = true;
continue;
}
}
if (!unique) {
if (parseKeywordIf(ctx, "PRIMARY KEY")) {
constraints.add(primaryKey(fieldName));
primary = true;
unique = true;
continue;
}
else if (parseKeywordIf(ctx, "UNIQUE")) {
if (!parseKeywordIf(ctx, "KEY"))
parseKeywordIf(ctx, "INDEX");
constraints.add(unique(fieldName));
unique = true;
continue;
}
}
if (parseKeywordIf(ctx, "CHECK")) {
constraints.add(parseCheckSpecification(ctx, null));
continue;
}
if (!identity) {
if (parseKeywordIf(ctx, "AUTO_INCREMENT") ||
parseKeywordIf(ctx, "AUTOINCREMENT")) {
type = type.identity(true);
identity = true;
continue;
}
}
if (!comment) {
if (parseKeywordIf(ctx, "COMMENT")) {
fieldComment = parseComment(ctx);
continue;
}
}
break;
}
fields.add(field(fieldName, type, fieldComment));
}
while (parseIf(ctx, ',')
&& (noConstraint =
!peekKeyword(ctx, "PRIMARY KEY")
&& !peekKeyword(ctx, "UNIQUE")
&& !peekKeyword(ctx, "FOREIGN KEY")
&& !peekKeyword(ctx, "CHECK")
&& !peekKeyword(ctx, "CONSTRAINT"))
);
if (!noConstraint) {
do {
ConstraintTypeStep constraint = null;
if (parseKeywordIf(ctx, "CONSTRAINT"))
constraint = constraint(parseIdentifier(ctx));
if (parseKeywordIf(ctx, "PRIMARY KEY")) {
if (primary)
throw ctx.exception("Duplicate primary key specification");
primary = true;
constraints.add(parsePrimaryKeySpecification(ctx, constraint));
}
else if (parseKeywordIf(ctx, "UNIQUE")) {
if (!parseKeywordIf(ctx, "KEY"))
parseKeywordIf(ctx, "INDEX");
constraints.add(parseUniqueSpecification(ctx, constraint));
}
else if (parseKeywordIf(ctx, "FOREIGN KEY"))
constraints.add(parseForeignKeySpecification(ctx, constraint));
else if (parseKeywordIf(ctx, "CHECK"))
constraints.add(parseCheckSpecification(ctx, constraint));
else
throw ctx.unexpectedToken();
}
while (parseIf(ctx, ','));
}
parse(ctx, ')');
CreateTableAsStep<Record> s1 = ifNotExists
? ctx.dsl.createTableIfNotExists(tableName)
: temporary
? ctx.dsl.createTemporaryTable(tableName)
: ctx.dsl.createTable(tableName);
CreateTableColumnStep s2 = s1.columns(fields);
CreateTableConstraintStep s3 = constraints.isEmpty()
? s2
: s2.constraints(constraints);
CreateTableCommentStep s4 = s3;
if (temporary && parseKeywordIf(ctx, "ON COMMIT")) {
if (parseKeywordIf(ctx, "DELETE ROWS"))
s4 = s3.onCommitDeleteRows();
else if (parseKeywordIf(ctx, "DROP"))
s4 = s3.onCommitDrop();
else if (parseKeywordIf(ctx, "PRESERVE ROWS"))
s4 = s3.onCommitPreserveRows();
else
throw ctx.unexpectedToken();
}
storageStep = commentStep = s4;
}
List<SQL> storage = new ArrayList<SQL>();
Comment comment = null;
storageLoop:
for (boolean first = true;; first = false) {
boolean optional = first || !parseIf(ctx, ',');
Keyword keyword = null;
// MySQL storage clauses (see: https://dev.mysql.com/doc/refman/5.7/en/create-table.html)
if ((keyword = parseAndGetKeywordIf(ctx, "AUTO_INCREMENT")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(ctx, Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "AVG_ROW_LENGTH")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(ctx, Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "CHARACTER SET")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseIdentifier(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "DEFAULT CHARACTER SET")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseIdentifier(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "CHECKSUM")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseZeroOne(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "COLLATE")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseIdentifier(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "DEFAULT COLLATE")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseIdentifier(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "COMMENT")) != null) {
parseIf(ctx, '=');
comment = parseComment(ctx);
}
else if ((keyword = parseAndGetKeywordIf(ctx, "COMPRESSION")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "CONNECTION")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "DATA DIRECTORY")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "INDEX DIRECTORY")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "DELAY_KEY_WRITE")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseZeroOne(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "ENCRYPTION")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "ENGINE")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseIdentifier(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "INSERT_METHOD")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseAndGetKeyword(ctx, "NO", "FIRST", "LAST")));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "KEY_BLOCK_SIZE")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(ctx, Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "MAX_ROWS")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(ctx, Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "MIN_ROWS")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(ctx, Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "PACK_KEYS")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseZeroOneDefault(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "PASSWORD")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "ROW_FORMAT")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseAndGetKeyword(ctx, "DEFAULT", "DYNAMIC", "FIXED", "COMPRESSED", "REDUNDANT", "COMPACT")));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "STATS_AUTO_RECALC")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseZeroOneDefault(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "STATS_PERSISTENT")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseZeroOneDefault(ctx)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "STATS_SAMPLE_PAGES")) != null) {
parseIf(ctx, '=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(ctx, Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "TABLESPACE")) != null) {
storage.add(sql("{0} {1}", keyword, parseIdentifier(ctx)));
if ((keyword = parseAndGetKeywordIf(ctx, "STORAGE")) != null)
storage.add(sql("{0} {1}", keyword, parseAndGetKeyword(ctx, "DISK", "MEMORY", "DEFAULT")));
}
else if ((keyword = parseAndGetKeywordIf(ctx, "UNION")) != null) {
parseIf(ctx, '=');
parse(ctx, '(');
storage.add(sql("{0} ({1})", keyword, list(parseIdentifiers(ctx))));
parse(ctx, ')');
}
else if (optional)
break storageLoop;
else
throw ctx.expected("storage clause after ','");
}
if (comment != null)
storageStep = commentStep.comment(comment);
if (storage.size() > 0)
return storageStep.storage(new SQLConcatenationImpl(storage.toArray(EMPTY_QUERYPART)));
else
return storageStep;
}
private static final Constraint parsePrimaryKeySpecification(ParserContext ctx, ConstraintTypeStep constraint) {
parse(ctx, '(');
Field<?>[] fieldNames = parseFieldNames(ctx).toArray(EMPTY_FIELD);
parse(ctx, ')');
Constraint e = constraint == null
? primaryKey(fieldNames)
: constraint.primaryKey(fieldNames);
return e;
}
private static final Constraint parseUniqueSpecification(ParserContext ctx, ConstraintTypeStep constraint) {
parse(ctx, '(');
Field<?>[] fieldNames = parseFieldNames(ctx).toArray(EMPTY_FIELD);
parse(ctx, ')');
return constraint == null
? unique(fieldNames)
: constraint.unique(fieldNames);
}
private static final Constraint parseCheckSpecification(ParserContext ctx, ConstraintTypeStep constraint) {
parse(ctx, '(');
Condition condition = parseCondition(ctx);
parse(ctx, ')');
return constraint == null
? check(condition)
: constraint.check(condition);
}
private static final Constraint parseForeignKeySpecification(ParserContext ctx, ConstraintTypeStep constraint) {
parse(ctx, '(');
Field<?>[] referencing = parseFieldNames(ctx).toArray(EMPTY_FIELD);
parse(ctx, ')');
parseKeyword(ctx, "REFERENCES");
Table<?> referencedTable = parseTableName(ctx);
parse(ctx, '(');
Field<?>[] referencedFields = parseFieldNames(ctx).toArray(EMPTY_FIELD);
parse(ctx, ')');
if (referencing.length != referencedFields.length)
throw ctx.exception("Number of referencing columns (" + referencing.length + ") must match number of referenced columns (" + referencedFields.length + ")");
ConstraintForeignKeyOnStep e = constraint == null
? foreignKey(referencing).references(referencedTable, referencedFields)
: constraint.foreignKey(referencing).references(referencedTable, referencedFields);
boolean onDelete = false;
boolean onUpdate = false;
while ((!onDelete || !onUpdate) && parseKeywordIf(ctx, "ON")) {
if (!onDelete && parseKeywordIf(ctx, "DELETE")) {
onDelete = true;
if (parseKeywordIf(ctx, "CASCADE"))
e = e.onDeleteCascade();
else if (parseKeywordIf(ctx, "NO ACTION"))
e = e.onDeleteNoAction();
else if (parseKeywordIf(ctx, "RESTRICT"))
e = e.onDeleteRestrict();
else if (parseKeywordIf(ctx, "SET DEFAULT"))
e = e.onDeleteSetDefault();
else if (parseKeywordIf(ctx, "SET NULL"))
e = e.onDeleteSetNull();
else
throw ctx.unexpectedToken();
}
else if (!onUpdate && parseKeywordIf(ctx, "UPDATE")) {
onUpdate = true;
if (parseKeywordIf(ctx, "CASCADE"))
e = e.onUpdateCascade();
else if (parseKeywordIf(ctx, "NO ACTION"))
e = e.onUpdateNoAction();
else if (parseKeywordIf(ctx, "RESTRICT"))
e = e.onUpdateRestrict();
else if (parseKeywordIf(ctx, "SET DEFAULT"))
e = e.onUpdateSetDefault();
else if (parseKeywordIf(ctx, "SET NULL"))
e = e.onUpdateSetNull();
else
throw ctx.unexpectedToken();
}
else
throw ctx.unexpectedToken();
}
return e;
}
private static final DDLQuery parseAlterTable(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Table<?> tableName = parseTableName(ctx);
parseWhitespaceIf(ctx);
AlterTableStep s1 = ifExists
? ctx.dsl.alterTableIfExists(tableName)
: ctx.dsl.alterTable(tableName);
switch (ctx.character()) {
case 'a':
case 'A':
if (parseKeywordIf(ctx, "ADD")) {
ConstraintTypeStep constraint = null;
if (parseKeywordIf(ctx, "CONSTRAINT"))
constraint = constraint(parseIdentifier(ctx));
if (parseKeywordIf(ctx, "PRIMARY KEY"))
return s1.add(parsePrimaryKeySpecification(ctx, constraint));
else if (parseKeywordIf(ctx, "UNIQUE")) {
if (!parseKeywordIf(ctx, "KEY"))
parseKeywordIf(ctx, "INDEX");
return s1.add(parseUniqueSpecification(ctx, constraint));
}
else if (parseKeywordIf(ctx, "FOREIGN KEY"))
return s1.add(parseForeignKeySpecification(ctx, constraint));
else if (parseKeywordIf(ctx, "CHECK"))
return s1.add(parseCheckSpecification(ctx, constraint));
else if (constraint != null)
throw ctx.unexpectedToken();
else {
parseKeywordIf(ctx, "COLUMN");
// The below code is taken from CREATE TABLE, with minor modifications as
// https://github.com/jOOQ/jOOQ/issues/5317 has not yet been implemented
// Once implemented, we might be able to factor out the common logic into
// a new parseXXX() method.
Name fieldName = parseIdentifier(ctx);
DataType type = parseDataType(ctx);
Comment fieldComment = null;
boolean nullable = false;
boolean defaultValue = false;
boolean onUpdate = false;
boolean unique = false;
boolean comment = false;
for (;;) {
if (!nullable) {
if (parseKeywordIf(ctx, "NULL")) {
type = type.nullable(true);
nullable = true;
continue;
}
else if (parseKeywordIf(ctx, "NOT NULL")) {
type = type.nullable(false);
nullable = true;
continue;
}
}
if (!defaultValue) {
if (parseKeywordIf(ctx, "DEFAULT")) {
type = type.defaultValue(toField(ctx, parseConcat(ctx, null)));
defaultValue = true;
continue;
}
}
if (!onUpdate) {
if (parseKeywordIf(ctx, "ON UPDATE")) {
// [#6132] TODO: Support this feature in the jOOQ DDL API
parseConcat(ctx, null);
onUpdate = true;
continue;
}
}
if (!unique)
if (parseKeywordIf(ctx, "PRIMARY KEY"))
throw ctx.unexpectedToken();
else if (parseKeywordIf(ctx, "UNIQUE"))
throw ctx.unexpectedToken();
if (parseKeywordIf(ctx, "CHECK"))
throw ctx.unexpectedToken();
if (!comment) {
if (parseKeywordIf(ctx, "COMMENT")) {
fieldComment = parseComment(ctx);
continue;
}
}
break;
}
return s1.add(field(fieldName, type, fieldComment), type);
}
}
else if (parseKeywordIf(ctx, "ALTER")) {
parseKeywordIf(ctx, "COLUMN");
return parseAlterTableAlterColumn(ctx, s1);
}
break;
case 'c':
case 'C':
// TODO: support all of the storageLoop from the CREATE TABLE statement
if (parseKeywordIf(ctx, "COMMENT")) {
parseIf(ctx, '=');
return ctx.dsl.commentOnTable(tableName).is(parseStringLiteral(ctx));
}
break;
case 'd':
case 'D':
if (parseKeywordIf(ctx, "DROP")) {
if (parseKeywordIf(ctx, "CONSTRAINT")) {
Name constraint = parseIdentifier(ctx);
return s1.dropConstraint(constraint);
}
else {
parseKeywordIf(ctx, "COLUMN");
boolean parens = parseIf(ctx, '(');
Field<?> field = parseFieldName(ctx);
List<Field<?>> fields = null;
while (parseIf(ctx, ',')) {
if (fields == null) {
fields = new ArrayList<Field<?>>();
fields.add(field);
}
fields.add(parseFieldName(ctx));
}
if (parens)
parse(ctx, ')');
boolean cascade = parseKeywordIf(ctx, "CASCADE");
boolean restrict = !cascade && parseKeywordIf(ctx, "RESTRICT");
AlterTableDropStep s2 = fields == null ? s1.dropColumn(field) : s1.dropColumns(fields);
AlterTableFinalStep s3 =
cascade
? s2.cascade()
: restrict
? s2.restrict()
: s2;
return s3;
}
}
break;
case 'm':
case 'M':
if (parseKeywordIf(ctx, "MODIFY")) {
parseKeywordIf(ctx, "COLUMN");
return parseAlterTableAlterColumn(ctx, s1);
}
break;
case 'r':
case 'R':
if (parseKeywordIf(ctx, "RENAME")) {
if (parseKeywordIf(ctx, "TO")) {
Name newName = parseIdentifier(ctx);
return s1.renameTo(newName);
}
else if (parseKeywordIf(ctx, "COLUMN")) {
Name oldName = parseIdentifier(ctx);
parseKeyword(ctx, "TO");
Name newName = parseIdentifier(ctx);
return s1.renameColumn(oldName).to(newName);
}
else if (parseKeywordIf(ctx, "INDEX")) {
Name oldName = parseIdentifier(ctx);
parseKeyword(ctx, "TO");
Name newName = parseIdentifier(ctx);
return s1.renameIndex(oldName).to(newName);
}
else if (parseKeywordIf(ctx, "CONSTRAINT")) {
Name oldName = parseIdentifier(ctx);
parseKeyword(ctx, "TO");
Name newName = parseIdentifier(ctx);
return s1.renameConstraint(oldName).to(newName);
}
}
break;
}
throw ctx.unexpectedToken();
}
private static final DDLQuery parseAlterTableAlterColumn(ParserContext ctx, AlterTableStep s1) {
TableField<?, ?> field = parseFieldName(ctx);
if (parseKeywordIf(ctx, "DROP NOT NULL"))
return s1.alter(field).dropNotNull();
else if (parseKeywordIf(ctx, "SET NOT NULL"))
return s1.alter(field).setNotNull();
else if (parseKeywordIf(ctx, "TO") || parseKeywordIf(ctx, "RENAME TO"))
return s1.renameColumn(field).to(parseFieldName(ctx));
else if (parseKeywordIf(ctx, "TYPE") || parseKeywordIf(ctx, "SET DATA TYPE"))
;
DataType<?> type = parseDataType(ctx);
if (parseKeywordIf(ctx, "NULL"))
type = type.nullable(true);
else if (parseKeywordIf(ctx, "NOT NULL"))
type = type.nullable(false);
return s1.alter(field).set(type);
}
private static final DDLQuery parseRename(ParserContext ctx) {
parseKeyword(ctx, "RENAME");
parseWhitespaceIf(ctx);
switch (ctx.character()) {
case 'c':
case 'C':
if (parseKeywordIf(ctx, "COLUMN")) {
TableField<?, ?> oldName = parseFieldName(ctx);
parseKeyword(ctx, "TO");
Field<?> newName = parseFieldName(ctx);
return ctx.dsl.alterTable(oldName.getTable()).renameColumn(oldName).to(newName);
}
break;
case 'i':
case 'I':
if (parseKeywordIf(ctx, "INDEX")) {
Name oldName = parseIndexName(ctx);
parseKeyword(ctx, "TO");
Name newName = parseIndexName(ctx);
return ctx.dsl.alterIndex(oldName).renameTo(newName);
}
break;
case 's':
case 'S':
if (parseKeywordIf(ctx, "SCHEMA")) {
Schema oldName = parseSchemaName(ctx);
parseKeyword(ctx, "TO");
Schema newName = parseSchemaName(ctx);
return ctx.dsl.alterSchema(oldName).renameTo(newName);
}
else if (parseKeywordIf(ctx, "SEQUENCE")) {
Sequence<?> oldName = parseSequenceName(ctx);
parseKeyword(ctx, "TO");
Sequence<?> newName = parseSequenceName(ctx);
return ctx.dsl.alterSequence(oldName).renameTo(newName);
}
break;
case 'v':
case 'V':
if (parseKeywordIf(ctx, "VIEW")) {
Table<?> oldName = parseTableName(ctx);
parseKeyword(ctx, "TO");
Table<?> newName = parseTableName(ctx);
return ctx.dsl.alterView(oldName).renameTo(newName);
}
break;
}
// If all of the above fails, we can assume we're renaming a table.
parseKeywordIf(ctx, "TABLE");
Table<?> oldName = parseTableName(ctx);
parseKeyword(ctx, "TO");
Table<?> newName = parseTableName(ctx);
return ctx.dsl.alterTable(oldName).renameTo(newName);
}
private static final DDLQuery parseDropTable(ParserContext ctx, boolean temporary) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Table<?> tableName = parseTableName(ctx);
boolean cascade = parseKeywordIf(ctx, "CASCADE");
boolean restrict = !cascade && parseKeywordIf(ctx, "RESTRICT");
DropTableStep s1;
DropTableFinalStep s2;
s1 = ifExists
? ctx.dsl.dropTableIfExists(tableName)
: temporary
? ctx.dsl.dropTemporaryTable(tableName)
: ctx.dsl.dropTable(tableName);
s2 = cascade
? s1.cascade()
: restrict
? s1.restrict()
: s1;
return s2;
}
private static final DDLQuery parseCreateSchema(ParserContext ctx) {
boolean ifNotExists = parseKeywordIf(ctx, "IF NOT EXISTS");
Schema schemaName = parseSchemaName(ctx);
return ifNotExists
? ctx.dsl.createSchemaIfNotExists(schemaName)
: ctx.dsl.createSchema(schemaName);
}
private static final DDLQuery parseAlterSchema(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Schema schemaName = parseSchemaName(ctx);
AlterSchemaStep s1 = ifExists
? ctx.dsl.alterSchemaIfExists(schemaName)
: ctx.dsl.alterSchema(schemaName);
if (parseKeywordIf(ctx, "RENAME TO")) {
Schema newName = parseSchemaName(ctx);
AlterSchemaFinalStep s2 = s1.renameTo(newName);
return s2;
}
else if (parseKeywordIf(ctx, "OWNER TO")) {
parseUser(ctx);
return IGNORE;
}
else
throw ctx.unexpectedToken();
}
private static final DDLQuery parseDropSchema(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Schema schemaName = parseSchemaName(ctx);
boolean cascade = parseKeywordIf(ctx, "CASCADE");
boolean restrict = !cascade && parseKeywordIf(ctx, "RESTRICT");
DropSchemaStep s1;
DropSchemaFinalStep s2;
s1 = ifExists
? ctx.dsl.dropSchemaIfExists(schemaName)
: ctx.dsl.dropSchema(schemaName);
s2 = cascade
? s1.cascade()
: restrict
? s1.restrict()
: s1;
return s2;
}
private static final DDLQuery parseCreateIndex(ParserContext ctx, boolean unique) {
boolean ifNotExists = parseKeywordIf(ctx, "IF NOT EXISTS");
Name indexName = parseIndexName(ctx);
parseKeyword(ctx, "ON");
Table<?> tableName = parseTableName(ctx);
parse(ctx, '(');
Field<?>[] fieldNames = Tools.fieldsByName(parseIdentifiers(ctx).toArray(EMPTY_NAME));
parse(ctx, ')');
Condition condition = parseKeywordIf(ctx, "WHERE")
? parseCondition(ctx)
: null;
CreateIndexStep s1 = ifNotExists
? unique
? ctx.dsl.createUniqueIndexIfNotExists(indexName)
: ctx.dsl.createIndexIfNotExists(indexName)
: unique
? ctx.dsl.createUniqueIndex(indexName)
: ctx.dsl.createIndex(indexName);
CreateIndexWhereStep s2 = s1.on(tableName, fieldNames);
CreateIndexFinalStep s3 = condition != null
? s2.where(condition)
: s2;
return s3;
}
private static final DDLQuery parseAlterDomain(ParserContext ctx) {
parseIdentifier(ctx);
// Some known PostgreSQL no-arg ALTER DOMAIN statements:
// https://www.postgresql.org/docs/current/static/sql-alterdomain.html
if (parseAndGetKeywordIf(ctx,
"DROP DEFAULT",
"DROP NOT NULL",
"SET NOT NULL"
) != null)
return IGNORE;
// ALTER DOMAIN statements with arguments:
else if (parseKeywordIf(ctx, "SET DEFAULT")) {
parseConcat(ctx, null);
return IGNORE;
}
else if (parseKeywordIf(ctx, "DROP CONSTRAINT")) {
parseKeywordIf(ctx, "IF EXISTS");
parseIdentifier(ctx);
if (parseKeywordIf(ctx, "RESTRICT") || parseKeywordIf(ctx, "CASCADE"));
return IGNORE;
}
else if (parseKeywordIf(ctx, "RENAME CONSTRAINT")) {
parseIdentifier(ctx);
parseKeyword(ctx, "TO");
parseIdentifier(ctx);
return IGNORE;
}
else if (parseAndGetKeywordIf(ctx,
"OWNER TO",
"RENAME TO",
"SET SCHEMA",
"VALIDATE CONSTRAINT"
) != null) {
parseIdentifier(ctx);
return IGNORE;
}
// TODO (PostgreSQL): ADD
else
throw ctx.unexpectedToken();
}
private static final DDLQuery parseAlterIndex(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Name indexName = parseIndexName(ctx);
parseKeyword(ctx, "RENAME TO");
Name newName = parseIndexName(ctx);
AlterIndexStep s1 = ifExists
? ctx.dsl.alterIndexIfExists(indexName)
: ctx.dsl.alterIndex(indexName);
AlterIndexFinalStep s2 = s1.renameTo(newName);
return s2;
}
private static final DDLQuery parseDropIndex(ParserContext ctx) {
boolean ifExists = parseKeywordIf(ctx, "IF EXISTS");
Name indexName = parseIndexName(ctx);
boolean on = parseKeywordIf(ctx, "ON");
Table<?> onTable = on ? parseTableName(ctx) : null;
DropIndexOnStep s1;
DropIndexFinalStep s2;
s1 = ifExists
? ctx.dsl.dropIndexIfExists(indexName)
: ctx.dsl.dropIndex(indexName);
s2 = on
? s1.on(onTable)
: s1;
return s2;
}
// -----------------------------------------------------------------------------------------------------------------
// QueryPart parsing
// -----------------------------------------------------------------------------------------------------------------
private static final Condition parseCondition(ParserContext ctx) {
return toCondition(ctx, parseOr(ctx));
}
private static final QueryPart parseOr(ParserContext ctx) {
QueryPart condition = parseAnd(ctx);
while (parseKeywordIf(ctx, "OR"))
condition = toCondition(ctx, condition).or(toCondition(ctx, parseAnd(ctx)));
return condition;
}
private static final QueryPart parseAnd(ParserContext ctx) {
QueryPart condition = parseNot(ctx);
while (parseKeywordIf(ctx, "AND"))
condition = toCondition(ctx, condition).and(toCondition(ctx, parseNot(ctx)));
return condition;
}
private static final QueryPart parseNot(ParserContext ctx) {
boolean not = parseKeywordIf(ctx, "NOT");
QueryPart condition = parsePredicate(ctx);
return not ? toCondition(ctx, condition).not() : condition;
}
private static final QueryPart parsePredicate(ParserContext ctx) {
if (parseKeywordIf(ctx, "EXISTS")) {
parse(ctx, '(');
Select<?> select = parseSelect(ctx);
parse(ctx, ')');
return exists(select);
}
else {
FieldOrRow left;
Comparator comp;
boolean not;
left = parseConcat(ctx, null);
not = parseKeywordIf(ctx, "NOT");
if (!not && (comp = parseComparatorIf(ctx)) != null) {
boolean all = parseKeywordIf(ctx, "ALL");
boolean any = !all && (parseKeywordIf(ctx, "ANY") || parseKeywordIf(ctx, "SOME"));
if (all || any)
parse(ctx, '(');
// TODO equal degrees
Condition result =
all
? left instanceof Field
? ((Field) left).compare(comp, DSL.all(parseSelect(ctx, 1)))
: ((RowN) left).compare(comp, DSL.all(parseSelect(ctx, ((RowN) left).size())))
: any
? left instanceof Field
? ((Field) left).compare(comp, DSL.any(parseSelect(ctx, 1)))
: ((RowN) left).compare(comp, DSL.any(parseSelect(ctx, ((RowN) left).size())))
: left instanceof Field
? ((Field) left).compare(comp, toField(ctx, parseConcat(ctx, null)))
: ((RowN) left).compare(comp, parseRow(ctx, ((RowN) left).size(), true));
if (all || any)
parse(ctx, ')');
return result;
}
else if (!not && parseKeywordIf(ctx, "IS")) {
not = parseKeywordIf(ctx, "NOT");
if (parseKeywordIf(ctx, "NULL"))
return not
? left instanceof Field
? ((Field) left).isNotNull()
: ((RowN) left).isNotNull()
: left instanceof Field
? ((Field) left).isNull()
: ((RowN) left).isNotNull();
parseKeyword(ctx, "DISTINCT FROM");
// TODO: Support this for ROW as well
if (((Field) left) == null)
throw ctx.notImplemented("DISTINCT predicate for rows");
Field right = toField(ctx, parseConcat(ctx, null));
return not ? ((Field) left).isNotDistinctFrom(right) : ((Field) left).isDistinctFrom(right);
}
else if (parseKeywordIf(ctx, "IN")) {
Condition result;
parse(ctx, '(');
if (peekKeyword(ctx, "SELECT"))
result = not
? left instanceof Field
? ((Field) left).notIn(parseSelect(ctx, 1))
: ((RowN) left).notIn(parseSelect(ctx, ((RowN) left).size()))
: left instanceof Field
? ((Field) left).in(parseSelect(ctx, 1))
: ((RowN) left).in(parseSelect(ctx, ((RowN) left).size()));
else
result = not
? left instanceof Field
? ((Field) left).notIn(parseFields(ctx))
: ((RowN) left).notIn(parseRows(ctx, ((RowN) left).size()))
: left instanceof Field
? ((Field) left).in(parseFields(ctx))
: ((RowN) left).in(parseRows(ctx, ((RowN) left).size()));
parse(ctx, ')');
return result;
}
else if (parseKeywordIf(ctx, "BETWEEN")) {
boolean symmetric = parseKeywordIf(ctx, "SYMMETRIC");
FieldOrRow r1 = left instanceof Field
? parseConcat(ctx, null)
: parseRow(ctx, ((RowN) left).size());
parseKeyword(ctx, "AND");
FieldOrRow r2 = left instanceof Field
? parseConcat(ctx, null)
: parseRow(ctx, ((RowN) left).size());
return symmetric
? not
? left instanceof Field
? ((Field) left).notBetweenSymmetric((Field) r1, (Field) r2)
: ((RowN) left).notBetweenSymmetric((RowN) r1, (RowN) r2)
: left instanceof Field
? ((Field) left).betweenSymmetric((Field) r1, (Field) r2)
: ((RowN) left).betweenSymmetric((RowN) r1, (RowN) r2)
: not
? left instanceof Field
? ((Field) left).notBetween((Field) r1, (Field) r2)
: ((RowN) left).notBetween((RowN) r1, (RowN) r2)
: left instanceof Field
? ((Field) left).between((Field) r1, (Field) r2)
: ((RowN) left).between((RowN) r1, (RowN) r2);
}
else if (left instanceof Field && parseKeywordIf(ctx, "LIKE")) {
Field right = toField(ctx, parseConcat(ctx, null));
boolean escape = parseKeywordIf(ctx, "ESCAPE");
char character = escape ? parseCharacterLiteral(ctx) : ' ';
return escape
? not
? ((Field) left).notLike(right, character)
: ((Field) left).like(right, character)
: not
? ((Field) left).notLike(right)
: ((Field) left).like(right);
}
else if (left instanceof RowN && ((RowN) left).size() == 2 && parseKeywordIf(ctx, "OVERLAPS")) {
return ((Row2) left).overlaps((Row2) parseRow(ctx, 2));
}
else
return left;
}
}
private static final List<Table<?>> parseTables(ParserContext ctx) {
parseWhitespaceIf(ctx);
List<Table<?>> result = new ArrayList<Table<?>>();
do {
result.add(parseTable(ctx));
}
while (parseIf(ctx, ','));
return result;
}
private static final Table<?> parseTable(ParserContext ctx) {
Table<?> result = parseTableFactor(ctx);
for (;;) {
Table<?> joined = parseJoinedTableIf(ctx, result);
if (joined == null)
return result;
else
result = joined;
}
}
private static final Table<?> parseTableFactor(ParserContext ctx) {
Table<?> result = null;
// TODO [#5306] Support FINAL TABLE (<data change statement>)
// TOOD ONLY ( table primary )
if (parseKeywordIf(ctx, "LATERAL")) {
parse(ctx, '(');
result = lateral(parseSelect(ctx));
parse(ctx, ')');
}
else if (parseFunctionNameIf(ctx, "UNNEST")) {
// TODO
throw ctx.notImplemented("UNNEST");
}
else if (parseIf(ctx, '(')) {
if (peekKeyword(ctx, "SELECT")) {
result = table(parseSelect(ctx));
parse(ctx, ')');
}
else if (peekKeyword(ctx, "VALUES")) {
result = parseTableValueConstructor(ctx);
parse(ctx, ')');
}
else {
int parens = 0;
while (parseIf(ctx, '('))
parens++;
result = parseJoinedTable(ctx);
while (parens --> 0)
parse(ctx, ')');
parse(ctx, ')');
return result;
}
}
else {
result = parseTableName(ctx);
// TODO Sample clause
}
// TODO UNPIVOT
// else if (parseKeywordIf(ctx, "UNPIVOT")) {
//
// }
Name alias = null;
List<Name> columnAliases = null;
if (parseKeywordIf(ctx, "AS"))
alias = parseIdentifier(ctx);
else if (!peekKeyword(ctx, SELECT_KEYWORDS))
alias = parseIdentifierIf(ctx);
if (alias != null) {
if (parseIf(ctx, '(')) {
columnAliases = parseIdentifiers(ctx);
parse(ctx, ')');
}
if (columnAliases != null)
result = result.as(alias, columnAliases.toArray(EMPTY_NAME));
else
result = result.as(alias);
}
return result;
}
private static final Table<?> parseTableValueConstructor(ParserContext ctx) {
parseKeyword(ctx, "VALUES");
List<RowN> rows = new ArrayList<RowN>();
do {
rows.add(parseTuple(ctx));
}
while (parseIf(ctx, ','));
return values(rows.toArray(Tools.EMPTY_ROWN));
}
private static final RowN parseTuple(ParserContext ctx) {
return parseTuple(ctx, null, false);
}
private static final RowN parseTuple(ParserContext ctx, Integer degree) {
return parseTuple(ctx, degree, false);
}
private static final RowN parseTuple(ParserContext ctx, Integer degree, boolean allowDoubleParens) {
parse(ctx, '(');
List<? extends FieldOrRow> fieldsOrRows;
if (allowDoubleParens)
fieldsOrRows = parseFieldsOrRows(ctx);
else
fieldsOrRows = parseFields(ctx);
RowN row;
if (fieldsOrRows.size() == 0)
row = row();
else if (fieldsOrRows.get(0) instanceof Field)
row = row(fieldsOrRows);
else if (fieldsOrRows.size() == 1)
row = (RowN) fieldsOrRows.get(0);
else
throw ctx.exception("Unsupported row size");
if (degree != null && row.size() != degree)
throw ctx.exception("Expected row of degree: " + degree + ". Got: " + row.size());
parse(ctx, ')');
return row;
}
private static final Table<?> parseJoinedTable(ParserContext ctx) {
Table<?> result = parseTableFactor(ctx);
for (int i = 0;; i++) {
Table<?> joined = parseJoinedTableIf(ctx, result);
if (joined == null)
if (i == 0)
ctx.unexpectedToken();
else
return result;
else
result = joined;
}
}
private static final Table<?> parseJoinedTableIf(ParserContext ctx, Table<?> left) {
JoinType joinType = parseJoinTypeIf(ctx);
if (joinType == null)
return null;
Table<?> right = joinType.qualified() ? parseTable(ctx) : parseTableFactor(ctx);
TableOptionalOnStep<?> s0;
TablePartitionByStep<?> s1;
TableOnStep<?> s2;
s2 = s1 = (TablePartitionByStep<?>) (s0 = left.join(right, joinType));
switch (joinType) {
case LEFT_OUTER_JOIN:
case FULL_OUTER_JOIN:
case RIGHT_OUTER_JOIN:
case JOIN:
case STRAIGHT_JOIN:
case LEFT_SEMI_JOIN:
case LEFT_ANTI_JOIN:
boolean on = parseKeywordIf(ctx, "ON");
if (on) {
return s2.on(parseCondition(ctx));
}
else {
parseKeyword(ctx, "USING");
parse(ctx, '(');
Table result = s2.using(Tools.fieldsByName(parseIdentifiers(ctx).toArray(EMPTY_NAME)));
parse(ctx, ')');
return result;
}
default:
return s0;
}
}
private static final List<Field<?>> parseSelectList(ParserContext ctx) {
parseWhitespaceIf(ctx);
if (parseIf(ctx, '*'))
return Collections.emptyList();
// TODO Support qualified asterisk
List<Field<?>> result = new ArrayList<Field<?>>();
do {
if (peekKeyword(ctx, SELECT_KEYWORDS))
throw ctx.unexpectedToken();
Field<?> field = parseField(ctx);
Name alias = null;
if (parseKeywordIf(ctx, "AS"))
alias = parseIdentifier(ctx);
else if (!peekKeyword(ctx, SELECT_KEYWORDS))
alias = parseIdentifierIf(ctx);
result.add(alias == null ? field : field.as(alias));
}
while (parseIf(ctx, ','));
return result;
}
private static final List<SortField<?>> parseSortSpecification(ParserContext ctx) {
List<SortField<?>> result = new ArrayList<SortField<?>>();
do {
result.add(parseSortField(ctx));
}
while (parseIf(ctx, ','));
return result;
}
private static final SortField<?> parseSortField(ParserContext ctx) {
Field<?> field = parseField(ctx);
SortField<?> sort;
if (parseKeywordIf(ctx, "DESC"))
sort = field.desc();
else if (parseKeywordIf(ctx, "ASC"))
sort = field.asc();
else
sort = field.sortDefault();
if (parseKeywordIf(ctx, "NULLS FIRST"))
sort = sort.nullsFirst();
else if (parseKeywordIf(ctx, "NULLS LAST"))
sort = sort.nullsLast();
return sort;
}
private static final List<Field<?>> parseFields(ParserContext ctx) {
parseWhitespaceIf(ctx);
List<Field<?>> result = new ArrayList<Field<?>>();
do {
result.add(parseField(ctx));
}
while (parseIf(ctx, ','));
return result;
}
private static final List<FieldOrRow> parseFieldsOrRows(ParserContext ctx) {
parseWhitespaceIf(ctx);
List<FieldOrRow> result = new ArrayList<FieldOrRow>();
do {
result.add(parseFieldOrRow(ctx));
}
while (parseIf(ctx, ','));
return result;
}
private static final Field<?> parseField(ParserContext ctx) {
return parseField(ctx, null);
}
private static final FieldOrRow parseFieldOrRow(ParserContext ctx) {
return parseFieldOrRow(ctx, null);
}
private static final RowN parseRow(ParserContext ctx) {
return parseRow(ctx, null);
}
private static final List<RowN> parseRows(ParserContext ctx, Integer degree) {
List<RowN> result = new ArrayList<RowN>();
do {
result.add(parseRow(ctx, degree));
}
while (parseIf(ctx, ','));
return result;
}
private static final RowN parseRow(ParserContext ctx, Integer degree) {
parseFunctionNameIf(ctx, "ROW");
RowN row = parseTuple(ctx, degree);
return row;
}
private static final RowN parseRow(ParserContext ctx, Integer degree, boolean allowDoubleParens) {
parseFunctionNameIf(ctx, "ROW");
RowN row = parseTuple(ctx, degree, allowDoubleParens);
return row;
}
static enum Type {
A("array"),
D("date"),
S("string"),
N("numeric"),
B("boolean"),
X("binary");
private final String name;
private Type(String name) {
this.name = name;
}
boolean is(Type type) {
return type == null || type == this;
}
String getName() {
return name;
}
}
private static final FieldOrRow parseFieldOrRow(ParserContext ctx, Type type) {
if (B.is(type))
return toFieldOrRow(ctx, parseOr(ctx));
else
return parseConcat(ctx, type);
}
private static final Field<?> parseField(ParserContext ctx, Type type) {
if (B.is(type))
return toField(ctx, parseOr(ctx));
else
return toField(ctx, parseConcat(ctx, type));
}
private static final Condition toCondition(ParserContext ctx, QueryPart part) {
if (part == null)
return null;
else if (part instanceof Condition)
return (Condition) part;
else if (part instanceof Field)
if (((Field) part).getDataType().getType() == Boolean.class)
return condition((Field) part);
else
throw ctx.expected("Boolean field");
else
throw ctx.expected("Condition");
}
private static final FieldOrRow toFieldOrRow(ParserContext ctx, QueryPart part) {
if (part == null)
return null;
else if (part instanceof Field)
return (Field) part;
else if (part instanceof Condition)
return field((Condition) part);
else if (part instanceof Row)
return (Row) part;
else
throw ctx.expected("Field or row");
}
private static final Field<?> toField(ParserContext ctx, QueryPart part) {
if (part == null)
return null;
else if (part instanceof Field)
return (Field) part;
else if (part instanceof Condition)
return field((Condition) part);
else
throw ctx.expected("Field");
}
private static final FieldOrRow parseConcat(ParserContext ctx, Type type) {
FieldOrRow r = parseSum(ctx, type);
if (S.is(type) && r instanceof Field)
while (parseIf(ctx, "||"))
r = concat((Field) r, toField(ctx, parseSum(ctx, type)));
return r;
}
private static final Field<?> parseFieldSumParenthesised(ParserContext ctx) {
parse(ctx, '(');
Field<?> r = toField(ctx, parseSum(ctx, N));
parse(ctx, ')');
return r;
}
private static final FieldOrRow parseSum(ParserContext ctx, Type type) {
FieldOrRow r = parseFactor(ctx, type);
if (N.is(type) && r instanceof Field)
for (;;)
if (parseIf(ctx, '+'))
r = ((Field) r).add((Field) parseFactor(ctx, type));
else if (parseIf(ctx, '-'))
r = ((Field) r).sub((Field) parseFactor(ctx, type));
else
break;
return r;
}
private static final FieldOrRow parseFactor(ParserContext ctx, Type type) {
FieldOrRow r = parseExp(ctx, type);
if (N.is(type) && r instanceof Field)
for (;;)
if (parseIf(ctx, '*'))
r = ((Field) r).mul((Field) parseExp(ctx, type));
else if (parseIf(ctx, '/'))
r = ((Field) r).div((Field) parseExp(ctx, type));
else if (parseIf(ctx, '%'))
r = ((Field) r).mod((Field) parseExp(ctx, type));
else
break;
return r;
}
private static final FieldOrRow parseExp(ParserContext ctx, Type type) {
FieldOrRow r = parseUnaryOps(ctx, type);
if (N.is(type) && r instanceof Field)
for (;;)
if (parseIf(ctx, '^'))
r = ((Field) r).pow(toField(ctx, parseUnaryOps(ctx, type)));
else
break;
return r;
}
private static final FieldOrRow parseUnaryOps(ParserContext ctx, Type type) {
FieldOrRow r;
Sign sign = parseSign(ctx);
if (sign == Sign.NONE)
r = parseTerm(ctx, type);
else if (sign == Sign.PLUS)
r = toField(ctx, parseTerm(ctx, type));
else if ((r = parseFieldUnsignedNumericLiteralIf(ctx, Sign.MINUS)) == null)
r = toField(ctx, parseTerm(ctx, type)).neg();
while (parseIf(ctx, "::"))
r = cast(toField(ctx, r), parseDataType(ctx));
return r;
}
private static final Sign parseSign(ParserContext ctx) {
Sign sign = Sign.NONE;
for (;;)
if (parseIf(ctx, '+'))
sign = sign == Sign.NONE ? Sign.PLUS : sign;
else if (parseIf(ctx, '-'))
sign = sign == Sign.NONE ? Sign.MINUS : sign.invert();
else
break;
return sign;
}
private static enum Sign {
NONE,
PLUS,
MINUS;
final Sign invert() {
if (this == PLUS)
return MINUS;
else if (this == MINUS)
return PLUS;
else
return NONE;
}
}
private static final FieldOrRow parseTerm(ParserContext ctx, Type type) {
parseWhitespaceIf(ctx);
FieldOrRow field;
Object value;
switch (ctx.character()) {
case ':':
case '?':
return parseBindVariable(ctx);
case '\'':
return inline(parseStringLiteral(ctx));
case 'a':
case 'A':
if (N.is(type))
if (parseFunctionNameIf(ctx, "ABS"))
return abs((Field) parseFieldSumParenthesised(ctx));
else if ((field = parseFieldAsciiIf(ctx)) != null)
return field;
else if (parseFunctionNameIf(ctx, "ACOS"))
return acos((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "ASIN"))
return asin((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "ATAN"))
return atan((Field) parseFieldSumParenthesised(ctx));
else if ((field = parseFieldAtan2If(ctx)) != null)
return field;
if (A.is(type))
if ((field = parseArrayValueConstructorIf(ctx)) != null)
return field;
break;
case 'b':
case 'B':
if (N.is(type))
if ((field = parseFieldBitLengthIf(ctx)) != null)
return field;
break;
case 'c':
case 'C':
if (S.is(type))
if ((field = parseFieldConcatIf(ctx)) != null)
return field;
else if (parseKeywordIf(ctx, "CURRENT_SCHEMA"))
return currentSchema();
else if (parseKeywordIf(ctx, "CURRENT_USER"))
return currentUser();
if (N.is(type))
if ((field = parseFieldCharIndexIf(ctx)) != null)
return field;
else if ((field = parseFieldCharLengthIf(ctx)) != null)
return field;
else if (parseFunctionNameIf(ctx, "CEILING") || parseFunctionNameIf(ctx, "CEIL"))
return ceil((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "COSH"))
return cosh((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "COS"))
return cos((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "COTH"))
return coth((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "COT"))
return cot((Field) parseFieldSumParenthesised(ctx));
else if ((field = parseNextvalCurrvalIf(ctx, SequenceMethod.CURRVAL)) != null)
return field;
if (D.is(type))
if (parseKeywordIf(ctx, "CURRENT_TIMESTAMP"))
return currentTimestamp();
else if (parseKeywordIf(ctx, "CURRENT_TIME"))
return currentTime();
else if (parseKeywordIf(ctx, "CURRENT_DATE"))
return currentDate();
if ((field = parseFieldCaseIf(ctx)) != null)
return field;
else if ((field = parseCastIf(ctx)) != null)
return field;
else if ((field = parseFieldCoalesceIf(ctx)) != null)
return field;
else if ((field = parseFieldCumeDistIf(ctx)) != null)
return field;
break;
case 'd':
case 'D':
if (D.is(type))
if ((field = parseFieldDateLiteralIf(ctx)) != null)
return field;
if (N.is(type))
if ((field = parseFieldDenseRankIf(ctx)) != null)
return field;
else if ((field = parseFieldDayIf(ctx)) != null)
return field;
else if (parseFunctionNameIf(ctx, "DEGREE") || parseFunctionNameIf(ctx, "DEG"))
return deg((Field) parseFieldSumParenthesised(ctx));
break;
case 'e':
case 'E':
// [#6704] PostgreSQL E'...' escaped string literals
if (S.is(type))
if (ctx.character(ctx.position + 1) == '\'')
return inline(parseStringLiteral(ctx));
if (N.is(type))
if ((field = parseFieldExtractIf(ctx)) != null)
return field;
else if (parseFunctionNameIf(ctx, "EXP"))
return exp((Field) parseFieldSumParenthesised(ctx));
break;
case 'f':
case 'F':
if (N.is(type))
if (parseFunctionNameIf(ctx, "FLOOR"))
return floor((Field) parseFieldSumParenthesised(ctx));
if ((field = parseFieldFirstValueIf(ctx)) != null)
return field;
break;
case 'g':
case 'G':
if ((field = parseFieldGreatestIf(ctx)) != null)
return field;
else if (N.is(type) && (field = parseFieldGroupingIdIf(ctx)) != null)
return field;
else if (N.is(type) && (field = parseFieldGroupingIf(ctx)) != null)
return field;
else
break;
case 'h':
case 'H':
if (N.is(type))
if ((field = parseFieldHourIf(ctx)) != null)
return field;
break;
case 'i':
case 'I':
if (N.is(type) && (field = parseFieldInstrIf(ctx)) != null)
return field;
else if ((field = parseFieldIfnullIf(ctx)) != null)
return field;
else if ((field = parseFieldIsnullIf(ctx)) != null)
return field;
else
break;
case 'l':
case 'L':
if (S.is(type))
if ((field = parseFieldLowerIf(ctx)) != null)
return field;
else if ((field = parseFieldLpadIf(ctx)) != null)
return field;
else if ((field = parseFieldLtrimIf(ctx)) != null)
return field;
else if ((field = parseFieldLeftIf(ctx)) != null)
return field;
if (N.is(type))
if ((field = parseFieldLengthIf(ctx)) != null)
return field;
else if (parseFunctionNameIf(ctx, "LN"))
return ln((Field) parseFieldSumParenthesised(ctx));
else if ((field = parseFieldLogIf(ctx)) != null)
return field;
else if (parseKeywordIf(ctx, "LEVEL"))
return level();
if ((field = parseFieldLeastIf(ctx)) != null)
return field;
else if ((field = parseFieldLeadLagIf(ctx)) != null)
return field;
else if ((field = parseFieldLastValueIf(ctx)) != null)
return field;
break;
case 'm':
case 'M':
if (N.is(type))
if ((field = parseFieldModIf(ctx)) != null)
return field;
else if ((field = parseFieldMonthIf(ctx)) != null)
return field;
else if ((field = parseFieldMinuteIf(ctx)) != null)
return field;
if (S.is(type))
if ((field = parseFieldMidIf(ctx)) != null)
return field;
else if ((field = parseFieldMd5If(ctx)) != null)
return field;
break;
case 'n':
case 'N':
if ((field = parseFieldNvl2If(ctx)) != null)
return field;
else if ((field = parseFieldNvlIf(ctx)) != null)
return field;
else if ((field = parseFieldNullifIf(ctx)) != null)
return field;
else if ((field = parseFieldNtileIf(ctx)) != null)
return field;
else if ((field = parseFieldNthValueIf(ctx)) != null)
return field;
else if ((field = parseNextValueIf(ctx)) != null)
return field;
else if ((field = parseNextvalCurrvalIf(ctx, SequenceMethod.NEXTVAL)) != null)
return field;
break;
case 'o':
case 'O':
if (N.is(type))
if ((field = parseFieldOctetLengthIf(ctx)) != null)
return field;
break;
case 'p':
case 'P':
if (N.is(type))
if ((field = parseFieldPositionIf(ctx)) != null)
return field;
else if ((field = parseFieldPercentRankIf(ctx)) != null)
return field;
else if ((field = parseFieldPowerIf(ctx)) != null)
return field;
if (parseKeywordIf(ctx, "PRIOR"))
return prior(toField(ctx, parseConcat(ctx, type)));
break;
case 'q':
case 'Q':
if (S.is(type))
if (ctx.character(ctx.position + 1) == '\'')
return inline(parseStringLiteral(ctx));
case 'r':
case 'R':
if (S.is(type))
if ((field = parseFieldReplaceIf(ctx)) != null)
return field;
else if ((field = parseFieldRepeatIf(ctx)) != null)
return field;
else if ((field = parseFieldReverseIf(ctx)) != null)
return field;
else if ((field = parseFieldRpadIf(ctx)) != null)
return field;
else if ((field = parseFieldRtrimIf(ctx)) != null)
return field;
else if ((field = parseFieldRightIf(ctx)) != null)
return field;
if (N.is(type))
if ((field = parseFieldRowNumberIf(ctx)) != null)
return field;
else if ((field = parseFieldRankIf(ctx)) != null)
return field;
else if ((field = parseFieldRoundIf(ctx)) != null)
return field;
else if (parseKeywordIf(ctx, "ROWNUM"))
return rownum();
else if (parseFunctionNameIf(ctx, "RADIAN") || parseFunctionNameIf(ctx, "RAD"))
return rad((Field) parseFieldSumParenthesised(ctx));
if (parseFunctionNameIf(ctx, "ROW"))
return parseTuple(ctx);
break;
case 's':
case 'S':
if (S.is(type))
if ((field = parseFieldSubstringIf(ctx)) != null)
return field;
else if ((field = parseFieldSpaceIf(ctx)) != null)
return field;
if (N.is(type))
if ((field = parseFieldSecondIf(ctx)) != null)
return field;
else if ((field = parseFieldSignIf(ctx)) != null)
return field;
else if (parseFunctionNameIf(ctx, "SQRT") || parseFunctionNameIf(ctx, "SQR"))
return sqrt((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "SINH"))
return sinh((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "SIN"))
return sin((Field) parseFieldSumParenthesised(ctx));
break;
case 't':
case 'T':
if (B.is(type))
if ((field = parseBooleanValueExpressionIf(ctx)) != null)
return field;
if (S.is(type))
if ((field = parseFieldTrimIf(ctx)) != null)
return field;
else if ((field = parseFieldTranslateIf(ctx)) != null)
return field;
if (N.is(type))
if ((field = parseFieldTruncIf(ctx)) != null)
return field;
else if (parseFunctionNameIf(ctx, "TANH"))
return tanh((Field) parseFieldSumParenthesised(ctx));
else if (parseFunctionNameIf(ctx, "TAN"))
return tan((Field) parseFieldSumParenthesised(ctx));
if (D.is(type))
if ((field = parseFieldTimestampLiteralIf(ctx)) != null)
return field;
else if ((field = parseFieldTimeLiteralIf(ctx)) != null)
return field;
break;
case 'u':
case 'U':
if (S.is(type))
if ((field = parseFieldUpperIf(ctx)) != null)
return field;
break;
case 'w':
case 'W':
if (N.is(type))
if ((field = parseFieldWidthBucketIf(ctx)) != null)
return field;
break;
case 'x':
case 'X':
if (X.is(type))
if ((value = parseBinaryLiteralIf(ctx)) != null)
return inline((byte[]) value);
break;
case 'y':
case 'Y':
if (N.is(type))
if ((field = parseFieldYearIf(ctx)) != null)
return field;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
if (N.is(type))
if ((field = parseFieldUnsignedNumericLiteralIf(ctx, Sign.NONE)) != null)
return field;
break;
case '{':
parse(ctx, '{');
switch (ctx.character()) {
case 'd':
case 'D':
parseKeyword(ctx, "D");
field = inline(parseDateLiteral(ctx));
break;
case 'f':
case 'F':
parseKeyword(ctx, "FN");
// TODO: Limit the supported expressions in this context to the ones specified here:
// http://download.oracle.com/otn-pub/jcp/jdbc-4_2-mrel2-eval-spec/jdbc4.2-fr-spec.pdf
field = parseTerm(ctx, type);
break;
case 't':
case 'T':
if (parseKeywordIf(ctx, "TS")) {
field = inline(parseTimestampLiteral(ctx));
}
else {
parseKeyword(ctx, "T");
field = inline(parseTimeLiteral(ctx));
}
break;
default:
throw ctx.unexpectedToken();
}
parse(ctx, '}');
return field;
case '(':
parse(ctx, '(');
if (peekKeyword(ctx, "SELECT")) {
SelectQueryImpl<Record> select = parseSelect(ctx);
if (select.getSelect().size() > 1)
throw ctx.exception("Select list must contain at least one column");
field = field((Select) select);
parse(ctx, ')');
return field;
}
else {
FieldOrRow r = parseFieldOrRow(ctx, type);
List<Field<?>> list = null;
if (r instanceof Field) {
while (parseIf(ctx, ',')) {
if (list == null) {
list = new ArrayList<Field<?>>();
list.add((Field) r);
}
// TODO Allow for nesting ROWs
list.add(parseField(ctx, type));
}
}
parse(ctx, ')');
return list != null ? row(list) : r;
}
}
if ((field = parseAggregateFunctionIf(ctx)) != null)
return field;
else if ((field = parseBooleanValueExpressionIf(ctx)) != null)
return field;
else
return parseFieldNameOrSequenceExpression(ctx);
}
private static final Field<?> parseNextValueIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "NEXT VALUE FOR"))
return sequence(parseName(ctx)).nextval();
return null;
}
private static final Field<?> parseNextvalCurrvalIf(ParserContext ctx, SequenceMethod method) {
if (parseFunctionNameIf(ctx, method.name())) {
parse(ctx, '(');
Name name = parseNameIf(ctx);
Sequence s = name != null
? sequence(name)
: sequence(ctx.dsl.parser().parseName(parseStringLiteral(ctx)));
parse(ctx, ')');
if (method == SequenceMethod.NEXTVAL)
return s.nextval();
else if (method == SequenceMethod.CURRVAL)
return s.currval();
else
throw ctx.exception("Only NEXTVAL and CURRVAL methods supported");
}
return null;
}
private static enum SequenceMethod {
NEXTVAL,
CURRVAL;
}
private static final Field<?> parseArrayValueConstructorIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "ARRAY")) {
parse(ctx, '[');
List<Field<?>> fields;
if (parseIf(ctx, ']')) {
fields = Collections.<Field<?>>emptyList();
}
else {
fields = parseFields(ctx);
parse(ctx, ']');
}
// Prevent "wrong" javac method bind
return DSL.array((Collection) fields);
}
return null;
}
private static final Field<?> parseFieldAtan2If(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "ATN2") || parseFunctionNameIf(ctx, "ATAN2")) {
parse(ctx, '(');
Field<?> x = toField(ctx, parseSum(ctx, N));
parse(ctx, ',');
Field<?> y = toField(ctx, parseSum(ctx, N));
parse(ctx, ')');
return atan2((Field) x, (Field) y);
}
return null;
}
private static final Field<?> parseFieldLogIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "LOG")) {
parse(ctx, '(');
Field<?> arg1 = toField(ctx, parseSum(ctx, N));
parse(ctx, ',');
long arg2 = parseUnsignedInteger(ctx);
parse(ctx, ')');
return log((Field) arg1, (int) arg2);
}
return null;
}
private static final Field<?> parseFieldTruncIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "TRUNC")) {
parse(ctx, '(');
Field<?> arg1 = toField(ctx, parseSum(ctx, N));
parse(ctx, ',');
Field<?> arg2 = toField(ctx, parseSum(ctx, N));
parse(ctx, ')');
return DSL.trunc((Field) arg1, (Field) arg2);
}
return null;
}
private static final Field<?> parseFieldRoundIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "ROUND")) {
Field arg1 = null;
Integer arg2 = null;
parse(ctx, '(');
arg1 = toField(ctx, parseSum(ctx, N));
if (parseIf(ctx, ','))
arg2 = (int) (long) parseUnsignedInteger(ctx);
parse(ctx, ')');
return arg2 == null ? round(arg1) : round(arg1, arg2);
}
return null;
}
private static final Field<?> parseFieldPowerIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "POWER") || parseFunctionNameIf(ctx, "POW")) {
parse(ctx, '(');
Field arg1 = toField(ctx, parseSum(ctx, N));
parse(ctx, ',');
Field arg2 = toField(ctx, parseSum(ctx, N));
parse(ctx, ')');
return DSL.power(arg1, arg2);
}
return null;
}
private static final Field<?> parseFieldModIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "MOD")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx, N);
parse(ctx, ',');
Field<?> f2 = parseField(ctx, N);
parse(ctx, ')');
return f1.mod((Field) f2);
}
return null;
}
private static final Field<?> parseFieldWidthBucketIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "WIDTH_BUCKET")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx, N);
parse(ctx, ',');
Field<?> f2 = parseField(ctx, N);
parse(ctx, ',');
Field<?> f3 = parseField(ctx, N);
parse(ctx, ',');
Field<?> f4 = parseField(ctx, N);
parse(ctx, ')');
return DSL.widthBucket((Field) f1, (Field) f2, (Field) f3, (Field) f4);
}
return null;
}
private static final Field<?> parseFieldLeastIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "LEAST")) {
parse(ctx, '(');
List<Field<?>> fields = parseFields(ctx);
parse(ctx, ')');
return least(fields.get(0), fields.size() > 1 ? fields.subList(1, fields.size()).toArray(EMPTY_FIELD) : EMPTY_FIELD);
}
return null;
}
private static final Field<?> parseFieldGreatestIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "GREATEST")) {
parse(ctx, '(');
List<Field<?>> fields = parseFields(ctx);
parse(ctx, ')');
return greatest(fields.get(0), fields.size() > 1 ? fields.subList(1, fields.size()).toArray(EMPTY_FIELD) : EMPTY_FIELD);
}
return null;
}
private static final Field<?> parseFieldGroupingIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "GROUPING")) {
parse(ctx, '(');
Field<?> field = parseField(ctx);
parse(ctx, ')');
return grouping(field);
}
return null;
}
private static final Field<?> parseFieldGroupingIdIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "GROUPING_ID")) {
parse(ctx, '(');
List<Field<?>> fields = parseFields(ctx);
parse(ctx, ')');
return groupingId(fields.toArray(EMPTY_FIELD));
}
return null;
}
private static final Field<?> parseFieldTimestampLiteralIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "TIMESTAMP")) {
if (parseKeywordIf(ctx, "WITHOUT TIME ZONE")) {
return inline(parseTimestampLiteral(ctx));
}
else if (parseIf(ctx, '(')) {
Field<?> f = parseField(ctx, S);
parse(ctx, ')');
return timestamp((Field) f);
}
else {
return inline(parseTimestampLiteral(ctx));
}
}
return null;
}
private static final Timestamp parseTimestampLiteral(ParserContext ctx) {
try {
return Timestamp.valueOf(parseStringLiteral(ctx));
}
catch (IllegalArgumentException e) {
throw ctx.exception("Illegal timestamp literal");
}
}
private static final Field<?> parseFieldTimeLiteralIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "TIME")) {
if (parseKeywordIf(ctx, "WITHOUT TIME ZONE")) {
return inline(parseTimeLiteral(ctx));
}
else if (parseIf(ctx, '(')) {
Field<?> f = parseField(ctx, S);
parse(ctx, ')');
return time((Field) f);
}
else {
return inline(parseTimeLiteral(ctx));
}
}
return null;
}
private static final Time parseTimeLiteral(ParserContext ctx) {
try {
return Time.valueOf(parseStringLiteral(ctx));
}
catch (IllegalArgumentException e) {
throw ctx.exception("Illegal time literal");
}
}
private static final Field<?> parseFieldDateLiteralIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "DATE")) {
if (parseIf(ctx, '(')) {
Field<?> f = parseField(ctx, S);
parse(ctx, ')');
return date((Field) f);
}
else {
return inline(parseDateLiteral(ctx));
}
}
return null;
}
private static final Date parseDateLiteral(ParserContext ctx) {
try {
return Date.valueOf(parseStringLiteral(ctx));
}
catch (IllegalArgumentException e) {
throw ctx.exception("Illegal date literal");
}
}
private static final Field<?> parseFieldExtractIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "EXTRACT")) {
parse(ctx, '(');
DatePart part = parseDatePart(ctx);
parseKeyword(ctx, "FROM");
Field<?> field = parseField(ctx);
parse(ctx, ')');
return extract(field, part);
}
return null;
}
private static final DatePart parseDatePart(ParserContext ctx) {
for (DatePart part : DatePart.values())
if (parseKeywordIf(ctx, part.name()))
return part;
throw ctx.unexpectedToken();
}
private static final Field<?> parseFieldAsciiIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "ASCII")) {
parse(ctx, '(');
Field<?> arg = parseField(ctx, S);
parse(ctx, ')');
return ascii((Field) arg);
}
return null;
}
private static final Field<?> parseFieldConcatIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "CONCAT")) {
parse(ctx, '(');
Field<String> result = concat(parseFields(ctx).toArray(EMPTY_FIELD));
parse(ctx, ')');
return result;
}
return null;
}
private static final Field<?> parseFieldInstrIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "INSTR")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<String> f2 = (Field) parseField(ctx, S);
parse(ctx, ')');
return position(f1, f2);
}
return null;
}
private static final Field<?> parseFieldCharIndexIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "CHARINDEX")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<String> f2 = (Field) parseField(ctx, S);
parse(ctx, ')');
return position(f2, f1);
}
return null;
}
private static final Field<?> parseFieldLpadIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "LPAD")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<Integer> f2 = (Field) parseField(ctx, N);
Field<String> f3 = parseIf(ctx, ',')
? (Field) parseField(ctx, S)
: null;
parse(ctx, ')');
return f3 == null
? lpad(f1, f2)
: lpad(f1, f2, f3);
}
return null;
}
private static final Field<?> parseFieldRpadIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "RPAD")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<Integer> f2 = (Field) parseField(ctx, N);
Field<String> f3 = parseIf(ctx, ',')
? (Field) parseField(ctx, S)
: null;
parse(ctx, ')');
return f3 == null
? rpad(f1, f2)
: rpad(f1, f2, f3);
}
return null;
}
private static final Field<?> parseFieldPositionIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "POSITION")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parseKeyword(ctx, "IN");
Field<String> f2 = (Field) parseField(ctx, S);
parse(ctx, ')');
return position(f2, f1);
}
return null;
}
private static final Field<?> parseFieldRepeatIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "REPEAT")) {
parse(ctx, '(');
Field<String> field = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<Integer> count = (Field) parseField(ctx, N);
parse(ctx, ')');
return repeat(field, count);
}
return null;
}
private static final Field<?> parseFieldReplaceIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "REPLACE")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<String> f2 = (Field) parseField(ctx, S);
Field<String> f3 = parseIf(ctx, ',')
? (Field) parseField(ctx, S)
: null;
parse(ctx, ')');
return f3 == null
? replace(f1, f2)
: replace(f1, f2, f3);
}
return null;
}
private static final Field<?> parseFieldReverseIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "REVERSE")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return reverse(f1);
}
return null;
}
private static final Field<?> parseFieldSpaceIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "SPACE")) {
parse(ctx, '(');
Field<Integer> f1 = (Field) parseField(ctx, N);
parse(ctx, ')');
return space(f1);
}
return null;
}
private static final Field<?> parseFieldSubstringIf(ParserContext ctx) {
boolean substring = parseFunctionNameIf(ctx, "SUBSTRING");
boolean substr = !substring && parseFunctionNameIf(ctx, "SUBSTR");
if (substring || substr) {
boolean keywords = !substr;
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
if (substr || !(keywords = parseKeywordIf(ctx, "FROM")))
parse(ctx, ',');
Field f2 = toField(ctx, parseSum(ctx, N));
Field f3 =
((keywords && parseKeywordIf(ctx, "FOR")) || (!keywords && parseIf(ctx, ',')))
? (Field) toField(ctx, parseSum(ctx, N))
: null;
parse(ctx, ')');
return f3 == null
? substring(f1, f2)
: substring(f1, f2, f3);
}
return null;
}
private static final Field<?> parseFieldTrimIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "TRIM")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return trim(f1);
}
return null;
}
private static final Field<?> parseFieldTranslateIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "TRANSLATE")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<String> f2 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<String> f3 = (Field) parseField(ctx, S);
parse(ctx, ')');
return translate(f1, f2, f3);
}
return null;
}
private static final Field<?> parseFieldRtrimIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "RTRIM")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return rtrim(f1);
}
return null;
}
private static final Field<?> parseFieldLtrimIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "LTRIM")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return ltrim(f1);
}
return null;
}
private static final Field<?> parseFieldMidIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "MID")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<? extends Number> f2 = (Field) parseField(ctx, N);
parse(ctx, ',');
Field<? extends Number> f3 = (Field) parseField(ctx, N);
parse(ctx, ')');
return mid(f1, f2, f3);
}
return null;
}
private static final Field<?> parseFieldLeftIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "LEFT")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<? extends Number> f2 = (Field) parseField(ctx, N);
parse(ctx, ')');
return left(f1, f2);
}
return null;
}
private static final Field<?> parseFieldRightIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "RIGHT")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ',');
Field<? extends Number> f2 = (Field) parseField(ctx, N);
parse(ctx, ')');
return right(f1, f2);
}
return null;
}
private static final Field<?> parseFieldMd5If(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "MD5")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return md5(f1);
}
return null;
}
private static final Field<?> parseFieldLengthIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "LENGTH")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return length(f1);
}
return null;
}
private static final Field<?> parseFieldCharLengthIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "CHAR_LENGTH")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return charLength(f1);
}
return null;
}
private static final Field<?> parseFieldBitLengthIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "BIT_LENGTH")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return bitLength(f1);
}
return null;
}
private static final Field<?> parseFieldOctetLengthIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "OCTET_LENGTH")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return octetLength(f1);
}
return null;
}
private static final Field<?> parseFieldLowerIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "LOWER") || parseFunctionNameIf(ctx, "LCASE")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return lower(f1);
}
return null;
}
private static final Field<?> parseFieldUpperIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "UPPER") || parseFunctionNameIf(ctx, "UCASE")) {
parse(ctx, '(');
Field<String> f1 = (Field) parseField(ctx, S);
parse(ctx, ')');
return DSL.upper(f1);
}
return null;
}
private static final Field<?> parseFieldYearIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "YEAR")) {
parse(ctx, '(');
Field<Timestamp> f1 = (Field) parseField(ctx, D);
parse(ctx, ')');
return year(f1);
}
return null;
}
private static final Field<?> parseFieldMonthIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "MONTH")) {
parse(ctx, '(');
Field<Timestamp> f1 = (Field) parseField(ctx, D);
parse(ctx, ')');
return month(f1);
}
return null;
}
private static final Field<?> parseFieldDayIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "DAY")) {
parse(ctx, '(');
Field<Timestamp> f1 = (Field) parseField(ctx, D);
parse(ctx, ')');
return day(f1);
}
return null;
}
private static final Field<?> parseFieldHourIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "HOUR")) {
parse(ctx, '(');
Field<Timestamp> f1 = (Field) parseField(ctx, D);
parse(ctx, ')');
return hour(f1);
}
return null;
}
private static final Field<?> parseFieldMinuteIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "MINUTE")) {
parse(ctx, '(');
Field<Timestamp> f1 = (Field) parseField(ctx, D);
parse(ctx, ')');
return minute(f1);
}
return null;
}
private static final Field<?> parseFieldSecondIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "SECOND")) {
parse(ctx, '(');
Field<Timestamp> f1 = (Field) parseField(ctx, D);
parse(ctx, ')');
return second(f1);
}
return null;
}
private static final Field<?> parseFieldSignIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "SIGN")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx, N);
parse(ctx, ')');
return sign((Field) f1);
}
return null;
}
private static final Field<?> parseFieldIfnullIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "IFNULL")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx);
parse(ctx, ',');
Field<?> f2 = parseField(ctx);
parse(ctx, ')');
return ifnull(f1, f2);
}
return null;
}
private static final Field<?> parseFieldIsnullIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "ISNULL")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx);
parse(ctx, ',');
Field<?> f2 = parseField(ctx);
parse(ctx, ')');
return isnull(f1, f2);
}
return null;
}
private static final Field<?> parseFieldNvlIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "NVL")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx);
parse(ctx, ',');
Field<?> f2 = parseField(ctx);
parse(ctx, ')');
return nvl(f1, f2);
}
return null;
}
private static final Field<?> parseFieldNvl2If(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "NVL2")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx);
parse(ctx, ',');
Field<?> f2 = parseField(ctx);
parse(ctx, ',');
Field<?> f3 = parseField(ctx);
parse(ctx, ')');
return nvl2(f1, f2, f3);
}
return null;
}
private static final Field<?> parseFieldNullifIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "NULLIF")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx);
parse(ctx, ',');
Field<?> f2 = parseField(ctx);
parse(ctx, ')');
return nullif(f1, f2);
}
return null;
}
private static final Field<?> parseFieldCoalesceIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "COALESCE")) {
parse(ctx, '(');
List<Field<?>> fields = parseFields(ctx);
parse(ctx, ')');
Field[] a = EMPTY_FIELD;
return coalesce(fields.get(0), fields.size() == 1 ? a : fields.subList(1, fields.size()).toArray(a));
}
return null;
}
private static final Field<?> parseFieldCaseIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "CASE")) {
if (parseKeywordIf(ctx, "WHEN")) {
CaseConditionStep step = null;
Field result;
do {
Condition condition = parseCondition(ctx);
parseKeyword(ctx, "THEN");
Field value = parseField(ctx);
step = step == null ? when(condition, value) : step.when(condition, value);
}
while (parseKeywordIf(ctx, "WHEN"));
if (parseKeywordIf(ctx, "ELSE"))
result = step.otherwise(parseField(ctx));
else
result = step;
parseKeyword(ctx, "END");
return result;
}
else {
CaseValueStep init = choose(parseField(ctx));
CaseWhenStep step = null;
Field result;
parseKeyword(ctx, "WHEN");
do {
Field when = parseField(ctx);
parseKeyword(ctx, "THEN");
Field then = parseField(ctx);
step = step == null ? init.when(when, then) : step.when(when, then);
}
while (parseKeywordIf(ctx, "WHEN"));
if (parseKeywordIf(ctx, "ELSE"))
result = step.otherwise(parseField(ctx));
else
result = step;
parseKeyword(ctx, "END");
return result;
}
}
return null;
}
private static final Field<?> parseCastIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "CAST")) {
parse(ctx, '(');
Field<?> field = parseField(ctx);
parseKeyword(ctx, "AS");
DataType<?> type = parseDataType(ctx);
parse(ctx, ')');
return cast(field, type);
}
return null;
}
private static final Field<Boolean> parseBooleanValueExpressionIf(ParserContext ctx) {
TruthValue truth = parseTruthValueIf(ctx);
if (truth != null) {
switch (truth) {
case TRUE:
return inline(true);
case FALSE:
return inline(false);
case NULL:
return inline((Boolean) null);
default:
throw ctx.exception("Truth value not supported: " + truth);
}
}
return null;
}
private static final Field<?> parseAggregateFunctionIf(ParserContext ctx) {
return parseAggregateFunctionIf(ctx, false);
}
private static final Field<?> parseAggregateFunctionIf(ParserContext ctx, boolean basic) {
AggregateFunction<?> agg;
AggregateFilterStep<?> filter;
WindowBeforeOverStep<?> over;
Object keep = null;
Field<?> result;
Condition condition;
keep = over = filter = agg = parseCountIf(ctx);
if (agg == null)
keep = over = filter = agg = parseGeneralSetFunctionIf(ctx);
if (agg == null && !basic)
over = filter = agg = parseBinarySetFunctionIf(ctx);
if (agg == null && !basic)
over = filter = parseOrderedSetFunctionIf(ctx);
if (agg == null && !basic)
over = filter = parseArrayAggFunctionIf(ctx);
if (agg == null && over == null)
if (!basic)
return parseSpecialAggregateFunctionIf(ctx);
else
return null;
if (filter != null && !basic && parseKeywordIf(ctx, "FILTER")) {
parse(ctx, '(');
parseKeyword(ctx, "WHERE");
condition = parseCondition(ctx);
parse(ctx, ')');
result = over = filter.filterWhere(condition);
}
else if (agg != null)
result = agg;
else
result = over;
if (!basic && parseKeywordIf(ctx, "OVER")) {
Object nameOrSpecification = parseWindowNameOrSpecification(ctx, agg != null);
if (nameOrSpecification instanceof Name)
result = over.over((Name) nameOrSpecification);
else if (nameOrSpecification instanceof WindowSpecification)
result = over.over((WindowSpecification) nameOrSpecification);
else
result = over.over();
}
return result;
}
private static final Field<?> parseSpecialAggregateFunctionIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "GROUP_CONCAT")) {
parse(ctx, '(');
GroupConcatOrderByStep s1;
GroupConcatSeparatorStep s2;
AggregateFunction<String> s3;
if (parseKeywordIf(ctx, "DISTINCT"))
s1 = DSL.groupConcatDistinct(parseField(ctx));
else
s1 = DSL.groupConcat(parseField(ctx));
if (parseKeywordIf(ctx, "ORDER BY"))
s2 = s1.orderBy(parseSortSpecification(ctx));
else
s2 = s1;
if (parseKeywordIf(ctx, "SEPARATOR"))
s3 = s2.separator(parseStringLiteral(ctx));
else
s3 = s2;
parse(ctx, ')');
return s3;
}
return null;
}
private static final Object parseWindowNameOrSpecification(ParserContext ctx, boolean orderByAllowed) {
Object result;
if (parseIf(ctx, '(')) {
WindowSpecificationOrderByStep s1 = null;
WindowSpecificationRowsStep s2 = null;
WindowSpecificationRowsAndStep s3 = null;
s1 = parseKeywordIf(ctx, "PARTITION BY")
? partitionBy(parseFields(ctx))
: null;
s2 = orderByAllowed && parseKeywordIf(ctx, "ORDER BY")
? s1 == null
? orderBy(parseSortSpecification(ctx))
: s1.orderBy(parseSortSpecification(ctx))
: s1;
boolean rows = orderByAllowed && parseKeywordIf(ctx, "ROWS");
if (rows || (orderByAllowed && parseKeywordIf(ctx, "RANGE"))) {
if (parseKeywordIf(ctx, "BETWEEN")) {
if (parseKeywordIf(ctx, "UNBOUNDED")) {
if (parseKeywordIf(ctx, "PRECEDING")) {
s3 = s2 == null
? rows
? rowsBetweenUnboundedPreceding()
: rangeBetweenUnboundedPreceding()
: rows
? s2.rowsBetweenUnboundedPreceding()
: s2.rangeBetweenUnboundedPreceding();
}
else {
parseKeyword(ctx, "FOLLOWING");
s3 = s2 == null
? rows
? rowsBetweenUnboundedFollowing()
: rangeBetweenUnboundedFollowing()
: rows
? s2.rowsBetweenUnboundedFollowing()
: s2.rangeBetweenUnboundedFollowing();
}
}
else if (parseKeywordIf(ctx, "CURRENT ROW")) {
s3 = s2 == null
? rows
? rowsBetweenCurrentRow()
: rangeBetweenCurrentRow()
: rows
? s2.rowsBetweenCurrentRow()
: s2.rangeBetweenCurrentRow();
}
else {
int number = (int) (long) parseUnsignedInteger(ctx);
if (parseKeywordIf(ctx, "PRECEDING")) {
s3 = s2 == null
? rows
? rowsBetweenPreceding(number)
: rangeBetweenPreceding(number)
: rows
? s2.rowsBetweenPreceding(number)
: s2.rangeBetweenPreceding(number);
}
else {
parseKeyword(ctx, "FOLLOWING");
s3 = s2 == null
? rows
? rowsBetweenFollowing(number)
: rangeBetweenFollowing(number)
: rows
? s2.rowsBetweenFollowing(number)
: s2.rangeBetweenFollowing(number);
}
}
parseKeyword(ctx, "AND");
if (parseKeywordIf(ctx, "UNBOUNDED")) {
if (parseKeywordIf(ctx, "PRECEDING")) {
result = s3.andUnboundedPreceding();
}
else {
parseKeyword(ctx, "FOLLOWING");
result = s3.andUnboundedFollowing();
}
}
else if (parseKeywordIf(ctx, "CURRENT ROW")) {
result = s3.andCurrentRow();
}
else {
int number = (int) (long) parseUnsignedInteger(ctx);
if (parseKeywordIf(ctx, "PRECEDING")) {
result = s3.andPreceding(number);
}
else {
parseKeyword(ctx, "FOLLOWING");
result = s3.andFollowing(number);
}
}
}
else {
if (parseKeywordIf(ctx, "UNBOUNDED")) {
if (parseKeywordIf(ctx, "PRECEDING")) {
result = s2 == null
? rows
? rowsUnboundedPreceding()
: rangeUnboundedPreceding()
: rows
? s2.rowsUnboundedPreceding()
: s2.rangeUnboundedPreceding();
}
else {
parseKeyword(ctx, "FOLLOWING");
result = s2 == null
? rows
? rowsUnboundedFollowing()
: rangeUnboundedFollowing()
: rows
? s2.rowsUnboundedFollowing()
: s2.rangeUnboundedFollowing();
}
}
else if (parseKeywordIf(ctx, "CURRENT ROW")) {
result = s2 == null
? rows
? rowsCurrentRow()
: rangeCurrentRow()
: rows
? s2.rowsCurrentRow()
: s2.rangeCurrentRow();
}
else {
int number = (int) (long) parseUnsignedInteger(ctx);
if (parseKeywordIf(ctx, "PRECEDING")) {
result = s2 == null
? rows
? rowsPreceding(number)
: rangePreceding(number)
: rows
? s2.rowsPreceding(number)
: s2.rangePreceding(number);
}
else {
parseKeyword(ctx, "FOLLOWING");
result = s2 == null
? rows
? rowsFollowing(number)
: rangeFollowing(number)
: rows
? s2.rowsFollowing(number)
: s2.rangeFollowing(number);
}
}
}
}
else {
result = s2;
}
parse(ctx, ')');
}
else {
result = parseIdentifier(ctx);
}
return result;
}
private static final Field<?> parseFieldRankIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "RANK")) {
parse(ctx, '(');
if (parseIf(ctx, ')'))
return parseWindowFunction(ctx, null, rank());
// Hypothetical set function
List<Field<?>> args = parseFields(ctx);
parse(ctx, ')');
return rank(args).withinGroupOrderBy(parseWithinGroupN(ctx));
}
return null;
}
private static final Field<?> parseFieldDenseRankIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "DENSE_RANK")) {
parse(ctx, '(');
if (parseIf(ctx, ')'))
return parseWindowFunction(ctx, null, denseRank());
// Hypothetical set function
List<Field<?>> args = parseFields(ctx);
parse(ctx, ')');
return denseRank(args).withinGroupOrderBy(parseWithinGroupN(ctx));
}
return null;
}
private static final Field<?> parseFieldPercentRankIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "PERCENT_RANK")) {
parse(ctx, '(');
if (parseIf(ctx, ')'))
return parseWindowFunction(ctx, null, percentRank());
// Hypothetical set function
List<Field<?>> args = parseFields(ctx);
parse(ctx, ')');
return percentRank(args).withinGroupOrderBy(parseWithinGroupN(ctx));
}
return null;
}
private static final Field<?> parseFieldCumeDistIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "CUME_DIST")) {
parse(ctx, '(');
if (parseIf(ctx, ')'))
return parseWindowFunction(ctx, null, cumeDist());
// Hypothetical set function
List<Field<?>> args = parseFields(ctx);
parse(ctx, ')');
return cumeDist(args).withinGroupOrderBy(parseWithinGroupN(ctx));
}
return null;
}
private static final Field<?> parseFieldRowNumberIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "ROW_NUMBER")) {
parse(ctx, '(');
parse(ctx, ')');
return parseWindowFunction(ctx, null, rowNumber());
}
return null;
}
private static final Field<?> parseFieldNtileIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "NTILE")) {
parse(ctx, '(');
int number = (int) (long) parseUnsignedInteger(ctx);
parse(ctx, ')');
return parseWindowFunction(ctx, null, ntile(number));
}
return null;
}
private static final Field<?> parseFieldLeadLagIf(ParserContext ctx) {
boolean lead = parseFunctionNameIf(ctx, "LEAD");
boolean lag = !lead && parseFunctionNameIf(ctx, "LAG");
if (lead || lag) {
parse(ctx, '(');
Field<Void> f1 = (Field) parseField(ctx);
Integer f2 = null;
Field<Void> f3 = null;
if (parseIf(ctx, ',')) {
f2 = (int) (long) parseUnsignedInteger(ctx);
if (parseIf(ctx, ',')) {
f3 = (Field) parseField(ctx);
}
}
parse(ctx, ')');
return parseWindowFunction(ctx, lead
? f2 == null
? lead(f1)
: f3 == null
? lead(f1, f2)
: lead(f1, f2, f3)
: f2 == null
? lag(f1)
: f3 == null
? lag(f1, f2)
: lag(f1, f2, f3), null);
}
return null;
}
private static final Field<?> parseFieldFirstValueIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "FIRST_VALUE")) {
parse(ctx, '(');
Field<Void> arg = (Field) parseField(ctx);
parse(ctx, ')');
return parseWindowFunction(ctx, firstValue(arg), null);
}
return null;
}
private static final Field<?> parseFieldLastValueIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "LAST_VALUE")) {
parse(ctx, '(');
Field<Void> arg = (Field) parseField(ctx);
parse(ctx, ')');
return parseWindowFunction(ctx, lastValue(arg), null);
}
return null;
}
private static final Field<?> parseFieldNthValueIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "NTH_VALUE")) {
parse(ctx, '(');
Field<?> f1 = parseField(ctx);
parse(ctx, ',');
int f2 = (int) (long) parseUnsignedInteger(ctx);
parse(ctx, ')');
return parseWindowFunction(ctx, nthValue(f1, f2), null);
}
return null;
}
private static final Field<?> parseWindowFunction(ParserContext ctx, WindowIgnoreNullsStep s1, WindowOverStep<?> s2) {
if (s1 != null) {
s2 = s1;
}
parseKeyword(ctx, "OVER");
Object nameOrSpecification = parseWindowNameOrSpecification(ctx, true);
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=494897
Field<?> result = (nameOrSpecification instanceof Name)
? s2.over((Name) nameOrSpecification)
: (nameOrSpecification instanceof WindowSpecification)
? s2.over((WindowSpecification) nameOrSpecification)
: s2.over();
return result;
}
private static final AggregateFunction<?> parseBinarySetFunctionIf(ParserContext ctx) {
Field<? extends Number> arg1;
Field<? extends Number> arg2;
BinarySetFunctionType type = parseBinarySetFunctionTypeIf(ctx);
if (type == null)
return null;
parse(ctx, '(');
arg1 = (Field) toField(ctx, parseSum(ctx, N));
parse(ctx, ',');
arg2 = (Field) toField(ctx, parseSum(ctx, N));
parse(ctx, ')');
switch (type) {
case REGR_AVGX:
return regrAvgX(arg1, arg2);
case REGR_AVGY:
return regrAvgY(arg1, arg2);
case REGR_COUNT:
return regrCount(arg1, arg2);
case REGR_INTERCEPT:
return regrIntercept(arg1, arg2);
case REGR_R2:
return regrR2(arg1, arg2);
case REGR_SLOPE:
return regrSlope(arg1, arg2);
case REGR_SXX:
return regrSXX(arg1, arg2);
case REGR_SXY:
return regrSXY(arg1, arg2);
case REGR_SYY:
return regrSYY(arg1, arg2);
default:
throw ctx.exception("Binary set function not supported: " + type);
}
}
private static final AggregateFilterStep<?> parseOrderedSetFunctionIf(ParserContext ctx) {
// TODO Listagg set function
OrderedAggregateFunction<?> orderedN;
OrderedAggregateFunctionOfDeferredType ordered1;
orderedN = parseHypotheticalSetFunctionIf(ctx);
if (orderedN == null)
orderedN = parseInverseDistributionFunctionIf(ctx);
if (orderedN == null)
orderedN = parseListaggFunctionIf(ctx);
if (orderedN != null)
return orderedN.withinGroupOrderBy(parseWithinGroupN(ctx));
ordered1 = parseModeIf(ctx);
if (ordered1 != null)
return ordered1.withinGroupOrderBy(parseWithinGroup1(ctx));
return null;
}
private static final AggregateFilterStep<?> parseArrayAggFunctionIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "ARRAY_AGG")) {
parse(ctx, '(');
boolean distinct = parseKeywordIf(ctx, "DISTINCT");
Field<?> a1 = parseField(ctx);
List<SortField<?>> sort = null;
if (parseKeywordIf(ctx, "ORDER BY"))
sort = parseSortSpecification(ctx);
parse(ctx, ')');
ArrayAggOrderByStep<?> s1 = distinct
? arrayAggDistinct(a1)
: arrayAgg(a1);
return sort == null ? s1 : s1.orderBy(sort);
}
return null;
}
private static final List<SortField<?>> parseWithinGroupN(ParserContext ctx) {
parseKeyword(ctx, "WITHIN GROUP");
parse(ctx, '(');
parseKeyword(ctx, "ORDER BY");
List<SortField<?>> result = parseSortSpecification(ctx);
parse(ctx, ')');
return result;
}
private static final SortField<?> parseWithinGroup1(ParserContext ctx) {
parseKeyword(ctx, "WITHIN GROUP");
parse(ctx, '(');
parseKeyword(ctx, "ORDER BY");
SortField<?> result = parseSortField(ctx);
parse(ctx, ')');
return result;
}
private static final OrderedAggregateFunction<?> parseHypotheticalSetFunctionIf(ParserContext ctx) {
// This currently never parses hypothetical set functions, as the function names are already
// consumed earlier in parseFieldTerm(). We should implement backtracking...
OrderedAggregateFunction<?> ordered;
if (parseFunctionNameIf(ctx, "RANK")) {
parse(ctx, '(');
ordered = rank(parseFields(ctx));
parse(ctx, ')');
}
else if (parseFunctionNameIf(ctx, "DENSE_RANK")) {
parse(ctx, '(');
ordered = denseRank(parseFields(ctx));
parse(ctx, ')');
}
else if (parseFunctionNameIf(ctx, "PERCENT_RANK")) {
parse(ctx, '(');
ordered = percentRank(parseFields(ctx));
parse(ctx, ')');
}
else if (parseFunctionNameIf(ctx, "CUME_DIST")) {
parse(ctx, '(');
ordered = cumeDist(parseFields(ctx));
parse(ctx, ')');
}
else
ordered = null;
return ordered;
}
private static final OrderedAggregateFunction<BigDecimal> parseInverseDistributionFunctionIf(ParserContext ctx) {
OrderedAggregateFunction<BigDecimal> ordered;
if (parseFunctionNameIf(ctx, "PERCENTILE_CONT")) {
parse(ctx, '(');
ordered = percentileCont(parseFieldUnsignedNumericLiteral(ctx, Sign.NONE));
parse(ctx, ')');
}
else if (parseFunctionNameIf(ctx, "PERCENTILE_DISC")) {
parse(ctx, '(');
ordered = percentileDisc(parseFieldUnsignedNumericLiteral(ctx, Sign.NONE));
parse(ctx, ')');
}
else
ordered = null;
return ordered;
}
private static final OrderedAggregateFunction<String> parseListaggFunctionIf(ParserContext ctx) {
OrderedAggregateFunction<String> ordered;
if (parseFunctionNameIf(ctx, "LISTAGG")) {
parse(ctx, '(');
Field<?> field = parseField(ctx);
if (parseIf(ctx, ','))
ordered = listAgg(field, parseStringLiteral(ctx));
else
ordered = listAgg(field);
parse(ctx, ')');
}
else
ordered = null;
return ordered;
}
private static final OrderedAggregateFunctionOfDeferredType parseModeIf(ParserContext ctx) {
OrderedAggregateFunctionOfDeferredType ordered;
if (parseFunctionNameIf(ctx, "MODE")) {
parse(ctx, '(');
parse(ctx, ')');
ordered = mode();
}
else
ordered = null;
return ordered;
}
private static final AggregateFunction<?> parseGeneralSetFunctionIf(ParserContext ctx) {
boolean distinct;
Field arg;
ComputationalOperation operation = parseComputationalOperationIf(ctx);
if (operation == null)
return null;
parse(ctx, '(');
switch (operation) {
case AVG:
case MAX:
case MIN:
case SUM:
distinct = parseSetQuantifier(ctx);
break;
default:
distinct = false;
break;
}
arg = parseField(ctx);
parse(ctx, ')');
switch (operation) {
case AVG:
return distinct ? avgDistinct(arg) : avg(arg);
case MAX:
return distinct ? maxDistinct(arg) : max(arg);
case MIN:
return distinct ? minDistinct(arg) : min(arg);
case SUM:
return distinct ? sumDistinct(arg) : sum(arg);
case MEDIAN:
return median(arg);
case EVERY:
return every(arg);
case ANY:
return boolOr(arg);
case STDDEV_POP:
return stddevPop(arg);
case STDDEV_SAMP:
return stddevSamp(arg);
case VAR_POP:
return varPop(arg);
case VAR_SAMP:
return varSamp(arg);
default:
throw ctx.unexpectedToken();
}
}
private static final AggregateFunction<?> parseCountIf(ParserContext ctx) {
if (parseFunctionNameIf(ctx, "COUNT")) {
parse(ctx, '(');
if (parseIf(ctx, '*')) {
parse(ctx, ')');
return count();
}
boolean distinct = parseSetQuantifier(ctx);
List<Field<?>> fields = distinct
? parseFields(ctx)
: Collections.<Field<?>>singletonList(parseField(ctx));
parse(ctx, ')');
if (distinct)
if (fields.size() > 0)
return countDistinct(fields.toArray(EMPTY_FIELD));
else
return countDistinct(fields.get(0));
else
return count(fields.get(0));
}
return null;
}
private static final boolean parseSetQuantifier(ParserContext ctx) {
boolean distinct = parseKeywordIf(ctx, "DISTINCT");
if (!distinct)
parseKeywordIf(ctx, "ALL");
return distinct;
}
// -----------------------------------------------------------------------------------------------------------------
// Name parsing
// -----------------------------------------------------------------------------------------------------------------
private static final Catalog parseCatalogName(ParserContext ctx) {
return catalog(parseName(ctx));
}
private static final Schema parseSchemaName(ParserContext ctx) {
return schema(parseName(ctx));
}
private static final Table<?> parseTableName(ParserContext ctx) {
return table(parseName(ctx));
}
private static final Field<?> parseFieldNameOrSequenceExpression(ParserContext ctx) {
Name name = parseName(ctx);
if (name.qualified()) {
String last = name.last();
if ("NEXTVAL".equalsIgnoreCase(last))
return sequence(name.qualifier()).nextval();
else if ("CURRVAL".equalsIgnoreCase(last))
return sequence(name.qualifier()).currval();
}
return field(name);
}
private static final TableField<?, ?> parseFieldName(ParserContext ctx) {
return (TableField<?, ?>) field(parseName(ctx));
}
private static final List<Field<?>> parseFieldNames(ParserContext ctx) {
List<Field<?>> result = new ArrayList<Field<?>>();
do {
result.add(parseFieldName(ctx));
}
while (parseIf(ctx, ','));
return result;
}
private static final Sequence<?> parseSequenceName(ParserContext ctx) {
return sequence(parseName(ctx));
}
private static final Name parseIndexName(ParserContext ctx) {
return parseName(ctx);
}
private static final Name parseName(ParserContext ctx) {
Name result = parseNameIf(ctx);
if (result == null)
throw ctx.unexpectedToken();
return result;
}
private static final Name parseNameIf(ParserContext ctx) {
Name identifier = parseIdentifierIf(ctx);
if (identifier == null)
return null;
List<Name> result = new ArrayList<Name>();
result.add(identifier);
while (parseIf(ctx, '.'))
result.add(parseIdentifier(ctx));
return result.size() == 1 ? result.get(0) : DSL.name(result.toArray(EMPTY_NAME));
}
private static final List<Name> parseIdentifiers(ParserContext ctx) {
LinkedHashSet<Name> result = new LinkedHashSet<Name>();
do {
if (!result.add(parseIdentifier(ctx)))
throw ctx.exception("Duplicate identifier encountered");
}
while (parseIf(ctx, ','));
return new ArrayList<Name>(result);
}
private static final Name parseIdentifier(ParserContext ctx) {
Name result = parseIdentifierIf(ctx);
if (result == null)
throw ctx.expected("Identifier");
return result;
}
private static final Name parseIdentifierIf(ParserContext ctx) {
parseWhitespaceIf(ctx);
char quoteEnd =
parseIf(ctx, '"') ? '"'
: parseIf(ctx, '`') ? '`'
: parseIf(ctx, '[') ? ']'
: 0;
int start = ctx.position;
if (quoteEnd != 0)
while (ctx.character() != quoteEnd && ctx.position < ctx.sql.length)
ctx.position = ctx.position + 1;
else
while (ctx.isIdentifierPart() && ctx.position < ctx.sql.length)
ctx.position = ctx.position + 1;
if (ctx.position == start)
return null;
String result = new String(ctx.sql, start, ctx.position - start);
if (quoteEnd != 0) {
if (ctx.character() != quoteEnd)
throw ctx.exception("Quoted identifier must terminate in " + quoteEnd);
ctx.position = ctx.position + 1;
return DSL.quotedName(result);
}
else
return DSL.unquotedName(result);
}
private static final DataType<?> parseDataType(ParserContext ctx) {
parseWhitespaceIf(ctx);
switch (ctx.character()) {
case 'b':
case 'B':
if (parseKeywordIf(ctx, "BIGINT"))
return parseUnsigned(ctx, parseAndIgnoreDataTypeLength(ctx, SQLDataType.BIGINT));
else if (parseKeywordIf(ctx, "BIGSERIAL"))
return SQLDataType.BIGINT.identity(true);
else if (parseKeywordIf(ctx, "BINARY"))
return parseDataTypeLength(ctx, SQLDataType.BINARY);
else if (parseKeywordIf(ctx, "BIT"))
return parseDataTypeLength(ctx, SQLDataType.BIT);
else if (parseKeywordIf(ctx, "BLOB"))
return parseDataTypeLength(ctx, SQLDataType.BLOB);
else if (parseKeywordIf(ctx, "BOOLEAN"))
return SQLDataType.BOOLEAN;
else if (parseKeywordIf(ctx, "BYTEA"))
return SQLDataType.BLOB;
else
throw ctx.unexpectedToken();
case 'c':
case 'C':
if (parseKeywordIf(ctx, "CHARACTER VARYING"))
return parseDataTypeLength(ctx, SQLDataType.VARCHAR);
else if (parseKeywordIf(ctx, "CHAR") ||
parseKeywordIf(ctx, "CHARACTER"))
return parseDataTypeLength(ctx, SQLDataType.CHAR);
else if (parseKeywordIf(ctx, "CLOB"))
return parseDataTypeLength(ctx, SQLDataType.CLOB);
else
throw ctx.unexpectedToken();
case 'd':
case 'D':
if (parseKeywordIf(ctx, "DATE"))
return SQLDataType.DATE;
else if (parseKeywordIf(ctx, "DATETIME"))
return SQLDataType.TIMESTAMP;
else if (parseKeywordIf(ctx, "DECIMAL"))
return parseDataTypePrecisionScale(ctx, SQLDataType.DECIMAL);
else if (parseKeywordIf(ctx, "DOUBLE PRECISION") ||
parseKeywordIf(ctx, "DOUBLE"))
return parseAndIgnoreDataTypePrecisionScale(ctx, SQLDataType.DOUBLE);
else
throw ctx.unexpectedToken();
case 'e':
case 'E':
if (parseKeywordIf(ctx, "ENUM"))
return parseDataTypeEnum(ctx);
else
throw ctx.unexpectedToken();
case 'f':
case 'F':
if (parseKeywordIf(ctx, "FLOAT"))
return parseAndIgnoreDataTypePrecisionScale(ctx, SQLDataType.FLOAT);
else
throw ctx.unexpectedToken();
case 'i':
case 'I':
if (parseKeywordIf(ctx, "INTEGER") ||
parseKeywordIf(ctx, "INT") ||
parseKeywordIf(ctx, "INT4"))
return parseUnsigned(ctx, parseAndIgnoreDataTypeLength(ctx, SQLDataType.INTEGER));
else if (parseKeywordIf(ctx, "INT2"))
return SQLDataType.SMALLINT;
else if (parseKeywordIf(ctx, "INT8"))
return SQLDataType.BIGINT;
else
throw ctx.unexpectedToken();
case 'l':
case 'L':
if (parseKeywordIf(ctx, "LONGBLOB"))
return SQLDataType.BLOB;
else if (parseKeywordIf(ctx, "LONGTEXT"))
return SQLDataType.CLOB;
else if (parseKeywordIf(ctx, "LONG NVARCHAR"))
return parseDataTypeLength(ctx, SQLDataType.LONGNVARCHAR);
else if (parseKeywordIf(ctx, "LONG VARBINARY"))
return parseDataTypeLength(ctx, SQLDataType.LONGVARBINARY);
else if (parseKeywordIf(ctx, "LONG VARCHAR"))
return parseDataTypeLength(ctx, SQLDataType.LONGVARCHAR);
else
throw ctx.unexpectedToken();
case 'm':
case 'M':
if (parseKeywordIf(ctx, "MEDIUMBLOB"))
return SQLDataType.BLOB;
else if (parseKeywordIf(ctx, "MEDIUMINT"))
return parseUnsigned(ctx, parseAndIgnoreDataTypeLength(ctx, SQLDataType.INTEGER));
else if (parseKeywordIf(ctx, "MEDIUMTEXT"))
return SQLDataType.CLOB;
else
throw ctx.unexpectedToken();
case 'n':
case 'N':
if (parseKeywordIf(ctx, "NCHAR"))
return parseDataTypeLength(ctx, SQLDataType.NCHAR);
else if (parseKeywordIf(ctx, "NCLOB"))
return SQLDataType.NCLOB;
else if (parseKeywordIf(ctx, "NUMBER") ||
parseKeywordIf(ctx, "NUMERIC"))
return parseDataTypePrecisionScale(ctx, SQLDataType.NUMERIC);
else if (parseKeywordIf(ctx, "NVARCHAR"))
return parseDataTypeLength(ctx, SQLDataType.NVARCHAR);
else
throw ctx.unexpectedToken();
case 'r':
case 'R':
if (parseKeywordIf(ctx, "REAL"))
return parseAndIgnoreDataTypePrecisionScale(ctx, SQLDataType.REAL);
else
throw ctx.unexpectedToken();
case 's':
case 'S':
if (parseKeywordIf(ctx, "SERIAL4") || parseKeywordIf(ctx, "SERIAL"))
return SQLDataType.INTEGER.identity(true);
else if (parseKeywordIf(ctx, "SERIAL8"))
return SQLDataType.BIGINT.identity(true);
else if (parseKeywordIf(ctx, "SET"))
return parseDataTypeEnum(ctx);
else if (parseKeywordIf(ctx, "SMALLINT"))
return parseUnsigned(ctx, parseAndIgnoreDataTypeLength(ctx, SQLDataType.SMALLINT));
else if (parseKeywordIf(ctx, "SMALLSERIAL"))
return SQLDataType.SMALLINT.identity(true);
else
throw ctx.unexpectedToken();
case 't':
case 'T':
if (parseKeywordIf(ctx, "TEXT"))
return parseAndIgnoreDataTypeLength(ctx, SQLDataType.CLOB);
else if (parseKeywordIf(ctx, "TIMESTAMP WITH TIME ZONE") ||
parseKeywordIf(ctx, "TIMESTAMPTZ"))
return SQLDataType.TIMESTAMPWITHTIMEZONE;
else if (parseKeywordIf(ctx, "TIMESTAMP WITHOUT TIME ZONE") ||
parseKeywordIf(ctx, "TIMESTAMP"))
return SQLDataType.TIMESTAMP;
else if (parseKeywordIf(ctx, "TIME WITH TIME ZONE") ||
parseKeywordIf(ctx, "TIMETZ"))
return SQLDataType.TIMEWITHTIMEZONE;
else if (parseKeywordIf(ctx, "TIME WITHOUT TIME ZONE") ||
parseKeywordIf(ctx, "TIME"))
return SQLDataType.TIME;
else if (parseKeywordIf(ctx, "TINYBLOB"))
return SQLDataType.BLOB;
else if (parseKeywordIf(ctx, "TINYINT"))
return parseUnsigned(ctx, parseAndIgnoreDataTypeLength(ctx, SQLDataType.TINYINT));
else if (parseKeywordIf(ctx, "TINYTEXT"))
return SQLDataType.CLOB;
else
throw ctx.unexpectedToken();
case 'u':
case 'U':
if (parseKeywordIf(ctx, "UUID"))
return SQLDataType.UUID;
else
throw ctx.unexpectedToken();
case 'v':
case 'V':
if (parseKeywordIf(ctx, "VARCHAR") ||
parseKeywordIf(ctx, "VARCHAR2"))
return parseDataTypeLength(ctx, SQLDataType.VARCHAR);
else if (parseKeywordIf(ctx, "VARBINARY"))
return parseDataTypeLength(ctx, SQLDataType.VARBINARY);
else
throw ctx.unexpectedToken();
}
throw ctx.unexpectedToken();
}
private static final DataType<?> parseUnsigned(ParserContext ctx, DataType result) {
if (parseKeywordIf(ctx, "UNSIGNED"))
if (result == SQLDataType.TINYINT)
return SQLDataType.TINYINTUNSIGNED;
else if (result == SQLDataType.SMALLINT)
return SQLDataType.SMALLINTUNSIGNED;
else if (result == SQLDataType.INTEGER)
return SQLDataType.INTEGERUNSIGNED;
else if (result == SQLDataType.BIGINT)
return SQLDataType.BIGINTUNSIGNED;
return result;
}
private static final DataType<?> parseAndIgnoreDataTypeLength(ParserContext ctx, DataType<?> result) {
if (parseIf(ctx, '(')) {
parseUnsignedInteger(ctx);
parse(ctx, ')');
}
return result;
}
private static final DataType<?> parseDataTypeLength(ParserContext ctx, DataType<?> result) {
if (parseIf(ctx, '(')) {
if (!parseKeywordIf(ctx, "MAX"))
result = result.length((int) (long) parseUnsignedInteger(ctx));
parse(ctx, ')');
}
return result;
}
private static final DataType<?> parseAndIgnoreDataTypePrecisionScale(ParserContext ctx, DataType<?> result) {
if (parseIf(ctx, '(')) {
parseUnsignedInteger(ctx);
if (parseIf(ctx, ','))
parseUnsignedInteger(ctx);
parse(ctx, ')');
}
return result;
}
private static final DataType<?> parseDataTypePrecisionScale(ParserContext ctx, DataType<?> result) {
if (parseIf(ctx, '(')) {
int precision = (int) (long) parseUnsignedInteger(ctx);
if (parseIf(ctx, ','))
result = result.precision(precision, (int) (long) parseUnsignedInteger(ctx));
else
result = result.precision(precision);
parse(ctx, ')');
}
return result;
}
private static final DataType<?> parseDataTypeEnum(ParserContext ctx) {
parse(ctx, '(');
List<String> literals = new ArrayList<String>();
do {
literals.add(parseStringLiteral(ctx));
}
while (parseIf(ctx, ','));
parse(ctx, ')');
// [#7025] TODO, replace this by a dynamic enum data type encoding, once available
return SQLDataType.VARCHAR;
}
// -----------------------------------------------------------------------------------------------------------------
// Literal parsing
// -----------------------------------------------------------------------------------------------------------------
private static final char parseCharacterLiteral(ParserContext ctx) {
parseWhitespaceIf(ctx);
parse(ctx, '\'');
char c = ctx.character();
// TODO MySQL string escaping...
if (c == '\'')
parse(ctx, '\'');
ctx.position = ctx.position + 1;
parse(ctx, '\'');
return c;
}
private static final Field<?> parseBindVariable(ParserContext ctx) {
switch (ctx.character()) {
case '?':
parse(ctx, '?');
return DSL.val(ctx.nextBinding(), Object.class);
case ':':
parse(ctx, ':');
return DSL.param(parseIdentifier(ctx).last(), ctx.nextBinding());
default:
throw ctx.exception("Illegal bind variable character");
}
}
private static final Comment parseComment(ParserContext ctx) {
return DSL.comment(parseStringLiteral(ctx));
}
private static final String parseStringLiteral(ParserContext ctx) {
parseWhitespaceIf(ctx);
if (parseIf(ctx, 'q') || parseIf(ctx, 'Q'))
return parseOracleQuotedStringLiteral(ctx);
else if (parseIf(ctx, 'e') || parseIf(ctx, 'E'))
return parseUnquotedStringLiteral(ctx, true);
else
return parseUnquotedStringLiteral(ctx, false);
}
private static final byte[] parseBinaryLiteralIf(ParserContext ctx) {
parseWhitespaceIf(ctx);
if (parseIf(ctx, "X'") || parseIf(ctx, "x'")) {
if (parseIf(ctx, '\''))
return EMPTY_BYTE;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
char c1 = 0;
char c2 = 0;
do {
while (ctx.position < ctx.sql.length) {
c1 = ctx.character(ctx.position);
if (c1 == ' ')
ctx.position = ctx.position + 1;
else
break;
}
c2 = ctx.character(ctx.position + 1);
if (c1 == '\'')
break;
if (c2 == '\'')
throw ctx.unexpectedToken();
try {
buffer.write(Integer.parseInt("" + c1 + c2, 16));
}
catch (NumberFormatException e) {
throw ctx.exception("Illegal character for binary literal");
}
}
while ((ctx.position = ctx.position + 2) < ctx.sql.length);
if (c1 == '\'') {
ctx.position = ctx.position + 1;
return buffer.toByteArray();
}
throw ctx.exception("Binary literal not terminated");
}
return null;
}
private static final String parseOracleQuotedStringLiteral(ParserContext ctx) {
parse(ctx, '\'');
char start = ctx.character();
char end;
switch (start) {
case '[' : end = ']'; ctx.position = ctx.position + 1; break;
case '{' : end = '}'; ctx.position = ctx.position + 1; break;
case '(' : end = ')'; ctx.position = ctx.position + 1; break;
case '<' : end = '>'; ctx.position = ctx.position + 1; break;
case ' ' :
case '\t':
case '\r':
case '\n': throw ctx.exception("Illegal quote string character");
default : end = start; ctx.position = ctx.position + 1; break;
}
StringBuilder sb = new StringBuilder();
for (int i = ctx.position; i < ctx.sql.length; i++) {
char c = ctx.character(i);
if (c == end)
if (ctx.character(i + 1) == '\'') {
ctx.position = i + 2;
return sb.toString();
}
else {
i++;
}
sb.append(c);
}
throw ctx.exception("Quoted string literal not terminated");
}
private static final String parseUnquotedStringLiteral(ParserContext ctx, boolean postgresEscaping) {
parse(ctx, '\'');
StringBuilder sb = new StringBuilder();
characterLoop:
for (int i = ctx.position; i < ctx.sql.length; i++) {
char c1 = ctx.character(i);
// TODO MySQL string escaping...
switch (c1) {
case '\\': {
if (!postgresEscaping)
break;
i++;
char c2 = ctx.character(i);
switch (c2) {
// Escaped whitespace characters
case 'b':
c1 = '\b';
break;
case 'n':
c1 = '\n';
break;
case 't':
c1 = '\t';
break;
case 'r':
c1 = '\r';
break;
case 'f':
c1 = '\f';
break;
// Hexadecimal byte value
case 'x': {
char c3 = ctx.character(i + 1);
char c4 = ctx.character(i + 2);
int d3;
if ((d3 = Character.digit(c3, 16)) != -1) {
i++;
int d4;
if ((d4 = Character.digit(c4, 16)) != -1) {
c1 = (char) (0x10 * d3 + d4);
i++;
}
else
c1 = (char) d3;
}
else
throw ctx.exception("Illegal hexadecimal byte value");
break;
}
// Unicode character value UTF-16
case 'u':
c1 = (char) Integer.parseInt(new String(ctx.sql, i + 1, 4), 16);
i += 4;
break;
// Unicode character value UTF-32
case 'U':
sb.appendCodePoint(Integer.parseInt(new String(ctx.sql, i + 1, 8), 16));
i += 8;
continue characterLoop;
default:
// Octal byte value
if (Character.digit(c2, 8) != -1) {
char c3 = ctx.character(i + 1);
if (Character.digit(c3, 8) != -1) {
i++;
char c4 = ctx.character(i + 1);
if (Character.digit(c4, 8) != -1) {
i++;
c1 = (char) Integer.parseInt("" + c2 + c3 + c4, 8);
}
else {
c1 = (char) Integer.parseInt("" + c2 + c3, 8);
}
}
else {
c1 = (char) Integer.parseInt("" + c2, 8);
}
}
// All other characters
else {
c1 = c2;
}
break;
}
break;
}
case '\'': {
if (ctx.character(i + 1) != '\'') {
ctx.position = i + 1;
return sb.toString();
}
i++;
break;
}
}
sb.append(c1);
}
throw ctx.exception("String literal not terminated");
}
private static final Field<Number> parseFieldUnsignedNumericLiteral(ParserContext ctx, Sign sign) {
Field<Number> result = parseFieldUnsignedNumericLiteralIf(ctx, sign);
if (result == null)
throw ctx.unexpectedToken();
return result;
}
private static final Field<Number> parseFieldUnsignedNumericLiteralIf(ParserContext ctx, Sign sign) {
Number r = parseUnsignedNumericLiteralIf(ctx, sign);
return r == null ? null : inline(r);
}
private static final Number parseUnsignedNumericLiteralIf(ParserContext ctx, Sign sign) {
parseWhitespaceIf(ctx);
StringBuilder sb = new StringBuilder();
char c;
for (;;) {
c = ctx.character();
if (c >= '0' && c <= '9') {
sb.append(c);
ctx.position = ctx.position + 1;
}
else
break;
}
if (c == '.') {
sb.append(c);
ctx.position = ctx.position + 1;
}
else {
if (sb.length() == 0)
return null;
try {
return sign == Sign.MINUS
? -Long.valueOf(sb.toString())
: Long.valueOf(sb.toString());
}
catch (Exception e1) {
return sign == Sign.MINUS
? new BigInteger(sb.toString()).negate()
: new BigInteger(sb.toString());
}
}
for (;;) {
c = ctx.character();
if (c >= '0' && c <= '9') {
sb.append(c);
ctx.position = ctx.position + 1;
}
else
break;
}
if (sb.length() == 0)
return null;
return sign == Sign.MINUS
? new BigDecimal(sb.toString()).negate()
: new BigDecimal(sb.toString());
// TODO add floating point support
}
private static final Field<Integer> parseZeroOne(ParserContext ctx) {
if (parseIf(ctx, '0'))
return zero();
else if (parseIf(ctx, '1'))
return one();
else
throw ctx.expected("0 or 1");
}
private static final Field<Integer> parseZeroOneDefault(ParserContext ctx) {
if (parseIf(ctx, '0'))
return zero();
else if (parseIf(ctx, '1'))
return one();
else if (parseKeywordIf(ctx, "DEFAULT"))
return defaultValue(INTEGER);
else
throw ctx.expected("0 or 1");
}
private static final Long parseSignedInteger(ParserContext ctx) {
Long result = parseSignedIntegerIf(ctx);
if (result == null)
throw ctx.expected("Signed integer");
return result;
}
private static final Long parseSignedIntegerIf(ParserContext ctx) {
parseWhitespaceIf(ctx);
Sign sign = parseSign(ctx);
Long unsigned;
if (sign == Sign.MINUS)
unsigned = parseUnsignedInteger(ctx);
else
unsigned = parseUnsignedIntegerIf(ctx);
return unsigned == null
? null
: sign == Sign.MINUS
? -unsigned
: unsigned;
}
private static final Long parseUnsignedInteger(ParserContext ctx) {
Long result = parseUnsignedIntegerIf(ctx);
if (result == null)
throw ctx.expected("Unsigned integer");
return result;
}
private static final Long parseUnsignedIntegerIf(ParserContext ctx) {
parseWhitespaceIf(ctx);
StringBuilder sb = new StringBuilder();
char c;
for (;;) {
c = ctx.character();
if (c >= '0' && c <= '9') {
sb.append(c);
ctx.position = ctx.position + 1;
}
else
break;
}
if (sb.length() == 0)
return null;
return Long.valueOf(sb.toString());
}
private static final JoinType parseJoinTypeIf(ParserContext ctx) {
if (parseKeywordIf(ctx, "CROSS JOIN"))
return JoinType.CROSS_JOIN;
else if (parseKeywordIf(ctx, "CROSS APPLY"))
return JoinType.CROSS_APPLY;
else if (parseKeywordIf(ctx, "CROSS JOIN"))
return JoinType.CROSS_JOIN;
else if (parseKeywordIf(ctx, "INNER")) {
parseKeyword(ctx, "JOIN");
return JoinType.JOIN;
}
else if (parseKeywordIf(ctx, "JOIN"))
return JoinType.JOIN;
else if (parseKeywordIf(ctx, "LEFT")) {
if (parseKeywordIf(ctx, "SEMI")) {
parseKeyword(ctx, "JOIN");
return JoinType.LEFT_SEMI_JOIN;
}
else if (parseKeywordIf(ctx, "ANTI")) {
parseKeyword(ctx, "JOIN");
return JoinType.LEFT_ANTI_JOIN;
}
else {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
return JoinType.LEFT_OUTER_JOIN;
}
}
else if (parseKeywordIf(ctx, "RIGHT")) {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
return JoinType.RIGHT_OUTER_JOIN;
}
else if (parseKeywordIf(ctx, "FULL")) {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
return JoinType.FULL_OUTER_JOIN;
}
else if (parseKeywordIf(ctx, "OUTER APPLY"))
return JoinType.OUTER_APPLY;
else if (parseKeywordIf(ctx, "NATURAL")) {
if (parseKeywordIf(ctx, "LEFT")) {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
return JoinType.NATURAL_LEFT_OUTER_JOIN;
}
else if (parseKeywordIf(ctx, "RIGHT")) {
parseKeywordIf(ctx, "OUTER");
parseKeyword(ctx, "JOIN");
return JoinType.NATURAL_RIGHT_OUTER_JOIN;
}
else if (parseKeywordIf(ctx, "JOIN"))
return JoinType.NATURAL_JOIN;
}
else if (parseKeywordIf(ctx, "STRAIGHT_JOIN"))
return JoinType.STRAIGHT_JOIN;
return null;
// TODO partitioned join
}
private static final TruthValue parseTruthValueIf(ParserContext ctx) {
parseWhitespaceIf(ctx);
if (parseKeywordIf(ctx, "TRUE"))
return TruthValue.TRUE;
else if (parseKeywordIf(ctx, "FALSE"))
return TruthValue.FALSE;
else if (parseKeywordIf(ctx, "NULL"))
return TruthValue.NULL;
return null;
}
private static final CombineOperator parseCombineOperatorIf(ParserContext ctx, boolean intersectOnly) {
parseWhitespaceIf(ctx);
if (!intersectOnly && parseKeywordIf(ctx, "UNION"))
if (parseKeywordIf(ctx, "ALL"))
return CombineOperator.UNION_ALL;
else if (parseKeywordIf(ctx, "DISTINCT"))
return CombineOperator.UNION;
else
return CombineOperator.UNION;
else if (!intersectOnly && parseKeywordIf(ctx, "EXCEPT") || parseKeywordIf(ctx, "MINUS"))
if (parseKeywordIf(ctx, "ALL"))
return CombineOperator.EXCEPT_ALL;
else if (parseKeywordIf(ctx, "DISTINCT"))
return CombineOperator.EXCEPT;
else
return CombineOperator.EXCEPT;
else if (intersectOnly && parseKeywordIf(ctx, "INTERSECT"))
if (parseKeywordIf(ctx, "ALL"))
return CombineOperator.INTERSECT_ALL;
else if (parseKeywordIf(ctx, "DISTINCT"))
return CombineOperator.INTERSECT;
else
return CombineOperator.INTERSECT;
return null;
}
private static final ComputationalOperation parseComputationalOperationIf(ParserContext ctx) {
parseWhitespaceIf(ctx);
if (parseFunctionNameIf(ctx, "AVG"))
return ComputationalOperation.AVG;
else if (parseFunctionNameIf(ctx, "MAX"))
return ComputationalOperation.MAX;
else if (parseFunctionNameIf(ctx, "MIN"))
return ComputationalOperation.MIN;
else if (parseFunctionNameIf(ctx, "SUM"))
return ComputationalOperation.SUM;
else if (parseFunctionNameIf(ctx, "MEDIAN"))
return ComputationalOperation.MEDIAN;
else if (parseFunctionNameIf(ctx, "EVERY") || parseFunctionNameIf(ctx, "BOOL_AND"))
return ComputationalOperation.EVERY;
else if (parseFunctionNameIf(ctx, "ANY") || parseFunctionNameIf(ctx, "SOME") || parseFunctionNameIf(ctx, "BOOL_OR"))
return ComputationalOperation.ANY;
else if (parseFunctionNameIf(ctx, "STDDEV_POP"))
return ComputationalOperation.STDDEV_POP;
else if (parseFunctionNameIf(ctx, "STDDEV_SAMP"))
return ComputationalOperation.STDDEV_SAMP;
else if (parseFunctionNameIf(ctx, "VAR_POP"))
return ComputationalOperation.VAR_POP;
else if (parseFunctionNameIf(ctx, "VAR_SAMP"))
return ComputationalOperation.VAR_SAMP;
return null;
}
private static final BinarySetFunctionType parseBinarySetFunctionTypeIf(ParserContext ctx) {
parseWhitespaceIf(ctx);
// TODO speed this up
for (BinarySetFunctionType type : BinarySetFunctionType.values())
if (parseFunctionNameIf(ctx, type.name()))
return type;
return null;
}
private static final Comparator parseComparatorIf(ParserContext ctx) {
parseWhitespaceIf(ctx);
if (parseIf(ctx, "="))
return Comparator.EQUALS;
else if (parseIf(ctx, "!=") || parseIf(ctx, "<>"))
return Comparator.NOT_EQUALS;
else if (parseIf(ctx, ">="))
return Comparator.GREATER_OR_EQUAL;
else if (parseIf(ctx, ">"))
return Comparator.GREATER;
// MySQL DISTINCT operator
else if (parseIf(ctx, "<=>"))
return Comparator.IS_NOT_DISTINCT_FROM;
else if (parseIf(ctx, "<="))
return Comparator.LESS_OR_EQUAL;
else if (parseIf(ctx, "<"))
return Comparator.LESS;
return null;
}
// -----------------------------------------------------------------------------------------------------------------
// Other tokens
// -----------------------------------------------------------------------------------------------------------------
private static final String parseUntilEOL(ParserContext ctx) {
parseWhitespaceIf(ctx);
int start = ctx.position;
int stop = start;
for (; stop < ctx.sql.length; stop++) {
char c = ctx.character(stop);
if (c == '\r') {
if (ctx.character(stop + 1) == '\n')
stop++;
break;
}
else if (c == '\n')
break;
}
if (start == stop)
throw ctx.unexpectedToken();
ctx.position = stop;
return new String(ctx.sql, start, stop - start);
}
private static final boolean parseIf(ParserContext ctx, String string) {
parseWhitespaceIf(ctx);
int length = string.length();
if (ctx.sql.length < ctx.position + length)
return false;
for (int i = 0; i < length; i++) {
char c = string.charAt(i);
if (ctx.sql[ctx.position + i] != c)
return false;
}
ctx.position = ctx.position + length;
return true;
}
private static final void parse(ParserContext ctx, char c) {
if (!parseIf(ctx, c))
throw ctx.unexpectedToken();
}
private static final boolean parseIf(ParserContext ctx, char c) {
parseWhitespaceIf(ctx);
if (ctx.character() != c)
return false;
ctx.position = ctx.position + 1;
return true;
}
private static final boolean parseFunctionNameIf(ParserContext ctx, String string) {
return peekKeyword(ctx, string, true, false, true);
}
private static final void parseKeyword(ParserContext ctx, String keyword) {
if (!parseKeywordIf(ctx, keyword))
throw ctx.unexpectedToken();
}
private static final boolean parseKeywordIf(ParserContext ctx, String keyword) {
return peekKeyword(ctx, keyword, true, false, false);
}
private static final Keyword parseAndGetKeyword(ParserContext ctx, String... keywords) {
Keyword result = parseAndGetKeywordIf(ctx, keywords);
if (result == null)
throw ctx.unexpectedToken();
return result;
}
private static final Keyword parseAndGetKeyword(ParserContext ctx, String keyword) {
Keyword result = parseAndGetKeywordIf(ctx, keyword);
if (result == null)
throw ctx.unexpectedToken();
return result;
}
private static final Keyword parseAndGetKeywordIf(ParserContext ctx, String... keywords) {
for (String keyword : keywords)
if (parseKeywordIf(ctx, keyword))
return keyword(keyword.toLowerCase());
return null;
}
private static final Keyword parseAndGetKeywordIf(ParserContext ctx, String keyword) {
if (parseKeywordIf(ctx, keyword))
return keyword(keyword.toLowerCase());
return null;
}
private static final boolean peekKeyword(ParserContext ctx, String... keywords) {
for (String keyword : keywords)
if (peekKeyword(ctx, keyword))
return true;
return false;
}
private static final boolean peekKeyword(ParserContext ctx, String keyword) {
return peekKeyword(ctx, keyword, false, false, false);
}
private static final boolean peekKeyword(ParserContext ctx, String keyword, boolean updatePosition, boolean peekIntoParens, boolean requireFunction) {
parseWhitespaceIf(ctx);
int length = keyword.length();
int skip;
if (ctx.sql.length < ctx.position + length)
return false;
// TODO is this correct?
skipLoop:
for (skip = 0; ctx.position + skip < ctx.sql.length; skip++) {
char c = ctx.character(ctx.position + skip);
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n':
continue skipLoop;
case '(':
if (peekIntoParens)
continue skipLoop;
else
break skipLoop;
default:
break skipLoop;
}
}
for (int i = 0; i < length; i++) {
char c = keyword.charAt(i);
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n':
skip = skip + (afterWhitespace(ctx, ctx.position + i + skip) - ctx.position - i - 1);
break;
default:
if (upper(ctx.sql[ctx.position + i + skip]) != keyword.charAt(i))
return false;
break;
}
}
if (ctx.isIdentifierPart(ctx.position + length + skip))
return false;
if (requireFunction)
if (ctx.character(afterWhitespace(ctx, ctx.position + length + skip)) != '(')
return false;
if (updatePosition)
ctx.position = ctx.position + length + skip;
return true;
}
private static final boolean parseWhitespaceIf(ParserContext ctx) {
int position = ctx.position;
ctx.position = afterWhitespace(ctx, ctx.position);
return position != ctx.position;
}
private static final int afterWhitespace(ParserContext ctx, int position) {
loop:
for (int i = position; i < ctx.sql.length; i++) {
switch (ctx.sql[i]) {
case ' ':
case '\t':
case '\r':
case '\n':
position = i + 1;
continue loop;
case '/':
if (i + 1 < ctx.sql.length && ctx.sql[i + 1] == '*') {
i = i + 2;
while (i < ctx.sql.length) {
switch (ctx.sql[i]) {
case '*':
if (i + 1 < ctx.sql.length && ctx.sql[i + 1] == '/') {
position = i = i + 1;
continue loop;
}
// No break
default:
i++;
}
}
}
break loop;
case '-':
if (i + 1 < ctx.sql.length && ctx.sql[i + 1] == '-') {
i = i + 2;
while (i < ctx.sql.length) {
switch (ctx.sql[i]) {
case '\r':
case '\n':
position = i;
continue loop;
default:
i++;
}
}
}
break loop;
// TODO MySQL comments require a whitespace after --. Should we deal with this?
// TODO Some databases also support # as a single line comment character.
// TODO support Oracle-style hints
default:
position = i;
break loop;
}
}
return position;
}
private static final char upper(char c) {
return c >= 'a' && c <= 'z' ? (char) (c - ('a' - 'A')) : c;
}
static final class ParserContext {
final DSLContext dsl;
final String sqlString;
final char[] sql;
int position = 0;
final Object[] bindings;
int bindIndex = 0;
String delimiter = ";";
ParserContext(DSLContext dsl, String sqlString, Object[] bindings) {
this.dsl = dsl;
this.sqlString = sqlString;
this.sql = sqlString.toCharArray();
this.bindings = bindings;
}
ParserException internalError() {
return exception("Internal Error");
}
ParserException expected(String object) {
return new ParserException(mark(), object + " expected");
}
ParserException notImplemented(String feature) {
return new ParserException(mark(), feature + " not yet implemented");
}
ParserException exception(String message) {
return new ParserException(mark(), message);
}
ParserException unexpectedToken() {
return new ParserException(mark());
}
Object nextBinding() {
if (bindIndex < bindings.length)
return bindings[bindIndex++];
else if (bindings.length == 0)
return null;
else
throw exception("No binding provided for bind index " + (bindIndex + 1));
}
int[] line() {
int line = 1;
int column = 1;
for (int i = 0; i < position; i++) {
if (sql[i] == '\r') {
line++;
column = 1;
if (i + 1 < sql.length && sql[i + 1] == '\n')
i++;
}
else if (sql[i] == '\n') {
line++;
column = 1;
}
else {
column++;
}
}
return new int[] { line, column };
}
char character() {
return character(position);
}
char character(int pos) {
return pos >= 0 && pos < sql.length ? sql[pos] : ' ';
}
boolean isWhitespace() {
return Character.isWhitespace(character());
}
boolean isWhitespace(int pos) {
return Character.isWhitespace(character(pos));
}
boolean isIdentifierPart() {
return Character.isJavaIdentifierPart(character());
}
boolean isIdentifierPart(int pos) {
return Character.isJavaIdentifierPart(character(pos));
}
boolean done() {
return position >= sql.length && (bindings.length == 0 || bindings.length == bindIndex);
}
boolean done(String message) {
if (done())
return true;
else
throw exception(message);
}
String mark() {
int[] line = line();
return "[" + line[0] + ":" + line[1] + "] " + sqlString.substring(Math.max(0, position - 50), position) + "[*]" + sqlString.substring(position, Math.min(sqlString.length(), position + 80));
}
@Override
public String toString() {
return mark();
}
}
private static enum TruthValue {
TRUE,
FALSE,
NULL;
}
private static enum ComputationalOperation {
AVG,
MAX,
MIN,
SUM,
EVERY,
ANY,
SOME,
COUNT,
STDDEV_POP,
STDDEV_SAMP,
VAR_SAMP,
VAR_POP,
MEDIAN,
// COLLECT,
// FUSION,
// INTERSECTION;
}
private static enum BinarySetFunctionType {
// COVAR_POP,
// COVAR_SAMP,
// CORR,
REGR_SLOPE,
REGR_INTERCEPT,
REGR_COUNT,
REGR_R2,
REGR_AVGX,
REGR_AVGY,
REGR_SXX,
REGR_SYY,
REGR_SXY,
}
private static final String[] SELECT_KEYWORDS = {
"CONNECT",
"CROSS",
"EXCEPT",
"FETCH",
"FOR",
"FROM",
"FULL",
"GROUP BY",
"HAVING",
"INNER",
"INTERSECT",
"INTO",
"JOIN",
"LEFT",
"LIMIT",
"MINUS",
"NATURAL",
"OFFSET",
"ON",
"ORDER BY",
"OUTER",
"PARTITION",
"RETURNING",
"RIGHT",
"SELECT",
"START",
"STRAIGHT_JOIN",
"UNION",
"USING",
"WHERE",
};
private static final String[] PIVOT_KEYWORDS = {
"FOR"
};
private static final Ignore IGNORE = Reflect.on(DSL.query("/* ignored */")).as(Ignore.class);
private static final Ignore IGNORE_NO_DELIMITER = Reflect.on(DSL.query("/* ignored */")).as(Ignore.class);
private static interface Ignore
extends
DDLQuery,
ResultQuery<Record>,
QueryPartInternal {}
}