/* * 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.acos; 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.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.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.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.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.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.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.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.trim; import static org.jooq.impl.DSL.unique; 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.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.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 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 java.util.TreeSet; 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.CaseConditionStep; import org.jooq.CaseValueStep; import org.jooq.CaseWhenStep; 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.CreateTableConstraintStep; import org.jooq.CreateTableFinalStep; 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.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.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.Queries; import org.jooq.Query; import org.jooq.QueryPart; import org.jooq.Record; import org.jooq.Row; import org.jooq.Row2; import org.jooq.RowN; import org.jooq.Schema; import org.jooq.Select; import org.jooq.Sequence; import org.jooq.SortField; 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.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; /** * @author Lukas Eder */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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) { ParserContext ctx = new ParserContext(dsl, sql); List<Query> result = new ArrayList<Query>(); do { Query query = parseQuery(ctx); if (query != null) result.add(query); } while (parseIf(ctx, ";")); if (!ctx.done()) throw ctx.exception("Unexpected content after end of queries input"); return dsl.queries(result); } @Override public final Query parseQuery(String sql) { ParserContext ctx = new ParserContext(dsl, sql); Query result = parseQuery(ctx); if (!ctx.done()) throw ctx.exception("Unexpected content after end of query input"); return result; } @Override public final Table<?> parseTable(String sql) { ParserContext ctx = new ParserContext(dsl, sql); Table<?> result = parseTable(ctx); if (!ctx.done()) throw ctx.exception("Unexpected content after end of table input"); return result; } @Override public final Field<?> parseField(String sql) { ParserContext ctx = new ParserContext(dsl, sql); Field<?> result = parseField(ctx); if (!ctx.done()) throw ctx.exception("Unexpected content after end of field input"); return result; } @Override public final Row parseRow(String sql) { ParserContext ctx = new ParserContext(dsl, sql); RowN result = parseRow(ctx); if (!ctx.done()) throw ctx.exception("Unexpected content after end of row input"); return result; } @Override public final Condition parseCondition(String sql) { ParserContext ctx = new ParserContext(dsl, sql); Condition result = parseCondition(ctx); if (!ctx.done()) throw ctx.exception("Unexpected content after end of condition input"); return result; } @Override public final Name parseName(String sql) { ParserContext ctx = new ParserContext(dsl, sql); Name result = parseName(ctx); if (!ctx.done()) throw ctx.exception("Unexpected content after end of name input"); return result; } private static final Query parseQuery(ParserContext ctx) { if (ctx.done()) return null; parseWhitespaceIf(ctx); try { switch (ctx.character()) { case 'a': case 'A': if (peekKeyword(ctx, "ALTER")) return parseAlter(ctx); break; case 'c': case 'C': if (peekKeyword(ctx, "CREATE")) return parseCreate(ctx); break; case 'd': case 'D': if (peekKeyword(ctx, "DELETE")) return parseDelete(ctx); else if (peekKeyword(ctx, "DROP")) return parseDrop(ctx); break; case 'i': case 'I': if (peekKeyword(ctx, "INSERT")) return parseInsert(ctx); break; case 'm': case 'M': if (peekKeyword(ctx, "MERGE")) return parseMerge(ctx); break; case 'r': case 'R': if (peekKeyword(ctx, "RENAME")) return parseRename(ctx); break; case 's': case 'S': if (peekKeyword(ctx, "SELECT")) return parseSelect(ctx); break; case 't': case 'T': if (peekKeyword(ctx, "TRUNCATE")) return parseTruncate(ctx); break; case 'u': case 'U': if (peekKeyword(ctx, "UPDATE")) return parseUpdate(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; } } finally { parseWhitespaceIf(ctx); if (!ctx.done() && ctx.character() != ';') throw ctx.unexpectedToken(); } 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); // [#6022] Allow unquoted identifiers for columns parse(ctx, '('); List<Name> columnNames = parseIdentifiers(ctx); String[] columns = new String[columnNames.size()]; for (int i = 0; i < columns.length; i++) columns[i] = columnNames.get(i).last(); parse(ctx, ')'); DerivedColumnList dcl = table.fields(columns); parseKeyword(ctx, "AS"); parse(ctx, '('); cte.add(dcl.as(parseSelect(ctx))); parse(ctx, ')'); } 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); } private static final SelectQueryImpl<Record> parseSelect(ParserContext ctx, Integer degree, WithImpl with) { SelectQueryImpl<Record> result = parseQueryPrimary(ctx, degree, with); CombineOperator combine; while ((combine = parseCombineOperatorIf(ctx)) != null) { if (degree == null) degree = result.getSelect().size(); switch (combine) { case UNION: result = (SelectQueryImpl<Record>) result.union(parseQueryPrimary(ctx, degree)); break; case UNION_ALL: result = (SelectQueryImpl<Record>) result.unionAll(parseQueryPrimary(ctx, degree)); break; case EXCEPT: result = (SelectQueryImpl<Record>) result.except(parseQueryPrimary(ctx, degree)); break; case EXCEPT_ALL: result = (SelectQueryImpl<Record>) result.exceptAll(parseQueryPrimary(ctx, degree)); break; 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; } } 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> 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<?>>>(); do { parse(ctx, '('); 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); 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")) { parse(ctx, '('); InsertOnConflictDoUpdateStep<?> 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 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, "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 DDLQuery parseAlter(ParserContext ctx) { parseKeyword(ctx, "ALTER"); if (parseKeywordIf(ctx, "TABLE")) return parseAlterTable(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, "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); else if (parseKeywordIf(ctx, "INDEX")) return parseDropIndex(ctx); else if (parseKeywordIf(ctx, "VIEW")) return parseDropView(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; } // ----------------------------------------------------------------------------------------------------------------- // Statement clause parsing // ----------------------------------------------------------------------------------------------------------------- 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 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 = parseTableName(ctx); // [#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); CreateTableFinalStep s2 = s1.as(select); return s2; } 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); boolean nullable = false; boolean defaultValue = false; boolean unique = false; boolean identity = type.identity(); 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 (!unique) { if (parseKeywordIf(ctx, "PRIMARY KEY")) { constraints.add(primaryKey(fieldName)); primary = true; unique = true; continue; } else if (parseKeywordIf(ctx, "UNIQUE")) { 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; } } break; } fields.add(field(fieldName, type)); } 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")) 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); CreateTableFinalStep 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(); } return s4; } } private static 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")) 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); boolean nullable = false; boolean defaultValue = false; boolean unique = 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 (!unique) if (parseKeywordIf(ctx, "PRIMARY KEY")) throw ctx.unexpectedToken(); else if (parseKeywordIf(ctx, "UNIQUE")) throw ctx.unexpectedToken(); if (parseKeywordIf(ctx, "CHECK")) throw ctx.unexpectedToken(); break; } return s1.add(field(fieldName, type), type); } } break; case 'd': case 'D': if (parseKeywordIf(ctx, "DROP")) { if (parseKeywordIf(ctx, "COLUMN")) { Field<?> field = parseFieldName(ctx); boolean cascade = parseKeywordIf(ctx, "CASCADE"); boolean restrict = !cascade && parseKeywordIf(ctx, "RESTRICT"); AlterTableDropStep s2 = s1.dropColumn(field); AlterTableFinalStep s3 = cascade ? s2.cascade() : restrict ? s2.restrict() : s2; return s3; } else if (parseKeywordIf(ctx, "CONSTRAINT")) { Name constraint = parseIdentifier(ctx); return s1.dropConstraint(constraint); } } 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, "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 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 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) : 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); parseKeyword(ctx, "RENAME TO"); Schema newName = parseSchemaName(ctx); AlterSchemaStep s1 = ifExists ? ctx.dsl.alterSchemaIfExists(schemaName) : ctx.dsl.alterSchema(schemaName); AlterSchemaFinalStep s2 = s1.renameTo(newName); return s2; } 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 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: 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") || true) sort = field.asc(); 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); Field<?> 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 ((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': 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; 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 '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, '('); 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<? extends Field<? extends Object[]>> fields; if (parseIf(ctx, ']')) { fields = Collections.emptyList(); } else { fields = (List) parseFields(ctx); parse(ctx, ']'); } return DSL.array(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<?> 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<?> 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; WindowBeforeOverStep<?> over; Object keep = null; Field<?> result; Condition filter; keep = over = agg = parseCountIf(ctx); if (agg == null) keep = over = agg = parseGeneralSetFunctionIf(ctx); if (agg == null && !basic) over = agg = parseBinarySetFunctionIf(ctx); if (agg == null && !basic) over = parseOrderedSetFunctionIf(ctx); if (agg == null && over == null) if (!basic) return parseSpecialAggregateFunctionIf(ctx); else return null; if (agg != null && !basic && parseKeywordIf(ctx, "FILTER")) { parse(ctx, '('); parseKeyword(ctx, "WHERE"); filter = parseCondition(ctx); parse(ctx, ')'); result = over = agg.filterWhere(filter); } 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, ')'); AggregateFilterStep<?> result = parseWithinGroupN(ctx, rank(args)); return result; } 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, ')'); AggregateFilterStep<?> result = parseWithinGroupN(ctx, denseRank(args)); return result; } 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, ')'); AggregateFilterStep<?> result = parseWithinGroupN(ctx, percentRank(args)); return result; } 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, ')'); AggregateFilterStep<?> result = parseWithinGroupN(ctx, cumeDist(args)); return result; } 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 WindowBeforeOverStep<?> 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 parseWithinGroupN(ctx, orderedN); ordered1 = parseModeIf(ctx); if (ordered1 != null) return parseWithinGroup1(ctx, ordered1); return null; } private static final AggregateFilterStep<?> parseWithinGroupN(ParserContext ctx, OrderedAggregateFunction<?> ordered) { parseKeyword(ctx, "WITHIN GROUP"); parse(ctx, '('); parseKeyword(ctx, "ORDER BY"); AggregateFilterStep<?> result = ordered.withinGroupOrderBy(parseSortSpecification(ctx)); parse(ctx, ')'); return result; } private static final AggregateFilterStep<?> parseWithinGroup1(ParserContext ctx, OrderedAggregateFunctionOfDeferredType ordered) { parseKeyword(ctx, "WITHIN GROUP"); parse(ctx, '('); parseKeyword(ctx, "ORDER BY"); AggregateFilterStep<?> result = ordered.withinGroupOrderBy(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 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 UNSIGNED")) return SQLDataType.BIGINTUNSIGNED; else if (parseKeywordIf(ctx, "BIGINT")) return SQLDataType.BIGINT; else if (parseKeywordIf(ctx, "BINARY")) return parseDataTypeLength(ctx, SQLDataType.BINARY); else if (parseKeywordIf(ctx, "BIT")) return SQLDataType.BIT; else if (parseKeywordIf(ctx, "BLOB")) return SQLDataType.BLOB; else if (parseKeywordIf(ctx, "BOOLEAN")) return SQLDataType.BOOLEAN; else throw ctx.unexpectedToken(); case 'c': case 'C': if (parseKeywordIf(ctx, "CHAR")) 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, "DECIMAL")) return parseDataTypePrecisionScale(ctx, SQLDataType.DECIMAL); else if (parseKeywordIf(ctx, "DOUBLE PRECISION") || parseKeywordIf(ctx, "DOUBLE")) return SQLDataType.DOUBLE; else throw ctx.unexpectedToken(); case 'f': case 'F': if (parseKeywordIf(ctx, "FLOAT")) return SQLDataType.FLOAT; else throw ctx.unexpectedToken(); case 'i': case 'I': if (parseKeywordIf(ctx, "INTEGER UNSIGNED") || parseKeywordIf(ctx, "INT UNSIGNED")) return SQLDataType.INTEGERUNSIGNED; else if (parseKeywordIf(ctx, "INTEGER") || parseKeywordIf(ctx, "INT")) return SQLDataType.INTEGER; 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 UNSIGNED")) return SQLDataType.INTEGERUNSIGNED; else if (parseKeywordIf(ctx, "MEDIUMINT")) return 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 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, "SMALLINT UNSIGNED")) return SQLDataType.SMALLINTUNSIGNED; else if (parseKeywordIf(ctx, "SMALLINT")) return SQLDataType.SMALLINT; else throw ctx.unexpectedToken(); case 't': case 'T': if (parseKeywordIf(ctx, "TEXT")) return SQLDataType.CLOB; else if (parseKeywordIf(ctx, "TIMESTAMP WITH TIME ZONE") || parseKeywordIf(ctx, "TIMESTAMPTZ")) return SQLDataType.TIMESTAMPWITHTIMEZONE; else if (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")) return SQLDataType.TIME; else if (parseKeywordIf(ctx, "TINYBLOB")) return SQLDataType.BLOB; else if (parseKeywordIf(ctx, "TINYINT UNSIGNED")) return SQLDataType.TINYINTUNSIGNED; else if (parseKeywordIf(ctx, "TINYINT")) return 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") || parseKeywordIf(ctx, "CHARACTER VARYING")) 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<?> parseDataTypeLength(ParserContext ctx, DataType<?> result) { if (parseIf(ctx, '(')) { result = result.length((int) (long) 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; } // ----------------------------------------------------------------------------------------------------------------- // 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(null, Object.class); case ':': parse(ctx, ':'); return DSL.param(parseIdentifier(ctx).last()); default: throw ctx.exception("Illegal bind variable character"); } } private static final String parseStringLiteral(ParserContext ctx) { parseWhitespaceIf(ctx); if (parseIf(ctx, 'q')) return parseOracleQuotedStringLiteral(ctx); else return parseUnquotedStringLiteral(ctx); } 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 '<': end = '>'; ctx.position = ctx.position + 1; break; default: throw ctx.exception("Illegal quote string character"); } 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) { parse(ctx, '\''); StringBuilder sb = new StringBuilder(); for (int i = ctx.position; i < ctx.sql.length; i++) { char c = ctx.character(i); // TODO MySQL string escaping... if (c == '\'') if (ctx.character(i + 1) == '\'') { i++; } else { ctx.position = i + 1; return sb.toString(); } sb.append(c); } 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) { 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 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")) { 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) { parseWhitespaceIf(ctx); if (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 (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 (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 boolean parseIf(ParserContext ctx, String string) { parseWhitespaceIf(ctx); int length = string.length(); ctx.expectedTokens.add(string); 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; ctx.expectedTokens.clear(); 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 string) { if (!parseKeywordIf(ctx, string)) throw ctx.unexpectedToken(); } private static final boolean parseKeywordIf(ParserContext ctx, String string) { ctx.expectedTokens.add(string); if (peekKeyword(ctx, string, true, false, false)) ctx.expectedTokens.clear(); else return false; return true; } 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; } public static final void main(String[] args) { System.out.println(new ParserImpl(new DefaultConfiguration()).parse( "DROP INDEX y on a.b.c" )); } static final class ParserContext { private final DSLContext dsl; private final String sqlString; private final char[] sql; private final List<String> expectedTokens; private int position = 0; ParserContext(DSLContext dsl, String sqlString) { this.dsl = dsl; this.sqlString = sqlString; this.sql = sqlString.toCharArray(); this.expectedTokens = new ArrayList<String>(); } 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(), "Expected tokens: " + new TreeSet<String>(expectedTokens)); } 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; } String mark() { return 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" }; }