jooq/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java

13581 lines
400 KiB
Java

/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.jooq.Comparator.IN;
import static org.jooq.Comparator.NOT_IN;
import static org.jooq.JoinType.JOIN;
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
// ...
// ...
// ...
// ...
import static org.jooq.conf.ParseWithMetaLookups.IGNORE_ON_FAILURE;
import static org.jooq.conf.ParseWithMetaLookups.THROW_ON_FAILURE;
import static org.jooq.conf.SettingsTools.parseLocale;
import static org.jooq.impl.AbstractName.NO_NAME;
import static org.jooq.impl.DSL.abs;
import static org.jooq.impl.DSL.acos;
import static org.jooq.impl.DSL.all;
import static org.jooq.impl.DSL.any;
import static org.jooq.impl.DSL.arrayAgg;
import static org.jooq.impl.DSL.arrayAggDistinct;
import static org.jooq.impl.DSL.arrayGet;
import static org.jooq.impl.DSL.ascii;
import static org.jooq.impl.DSL.asin;
import static org.jooq.impl.DSL.asterisk;
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.begin;
import static org.jooq.impl.DSL.bitAnd;
import static org.jooq.impl.DSL.bitCount;
import static org.jooq.impl.DSL.bitLength;
import static org.jooq.impl.DSL.bitNand;
import static org.jooq.impl.DSL.bitNor;
import static org.jooq.impl.DSL.bitNot;
import static org.jooq.impl.DSL.bitOr;
import static org.jooq.impl.DSL.bitXNor;
import static org.jooq.impl.DSL.bitXor;
import static org.jooq.impl.DSL.boolOr;
// ...
import static org.jooq.impl.DSL.cardinality;
import static org.jooq.impl.DSL.cast;
import static org.jooq.impl.DSL.catalog;
import static org.jooq.impl.DSL.ceil;
import static org.jooq.impl.DSL.century;
import static org.jooq.impl.DSL.charLength;
import static org.jooq.impl.DSL.characterSet;
import static org.jooq.impl.DSL.check;
import static org.jooq.impl.DSL.choose;
import static org.jooq.impl.DSL.chr;
import static org.jooq.impl.DSL.coalesce;
import static org.jooq.impl.DSL.coerce;
import static org.jooq.impl.DSL.collation;
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.covarPop;
import static org.jooq.impl.DSL.covarSamp;
import static org.jooq.impl.DSL.cube;
import static org.jooq.impl.DSL.cumeDist;
import static org.jooq.impl.DSL.currentCatalog;
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.dayOfWeek;
import static org.jooq.impl.DSL.dayOfYear;
import static org.jooq.impl.DSL.decade;
// ...
import static org.jooq.impl.DSL.defaultValue;
import static org.jooq.impl.DSL.default_;
import static org.jooq.impl.DSL.deg;
import static org.jooq.impl.DSL.denseRank;
import static org.jooq.impl.DSL.digits;
import static org.jooq.impl.DSL.domain;
import static org.jooq.impl.DSL.epoch;
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.function;
import static org.jooq.impl.DSL.generateSeries;
import static org.jooq.impl.DSL.greatest;
import static org.jooq.impl.DSL.grouping;
// ...
import static org.jooq.impl.DSL.groupingSets;
import static org.jooq.impl.DSL.groupsBetweenCurrentRow;
import static org.jooq.impl.DSL.groupsBetweenFollowing;
import static org.jooq.impl.DSL.groupsBetweenPreceding;
import static org.jooq.impl.DSL.groupsBetweenUnboundedFollowing;
import static org.jooq.impl.DSL.groupsBetweenUnboundedPreceding;
import static org.jooq.impl.DSL.groupsCurrentRow;
import static org.jooq.impl.DSL.groupsFollowing;
import static org.jooq.impl.DSL.groupsPreceding;
import static org.jooq.impl.DSL.groupsUnboundedFollowing;
import static org.jooq.impl.DSL.groupsUnboundedPreceding;
import static org.jooq.impl.DSL.hour;
// ...
import static org.jooq.impl.DSL.ifnull;
import static org.jooq.impl.DSL.iif;
import static org.jooq.impl.DSL.in;
import static org.jooq.impl.DSL.inOut;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.insert;
import static org.jooq.impl.DSL.isnull;
import static org.jooq.impl.DSL.isoDayOfWeek;
import static org.jooq.impl.DSL.jsonExists;
import static org.jooq.impl.DSL.jsonTable;
import static org.jooq.impl.DSL.jsonValue;
import static org.jooq.impl.DSL.key;
import static org.jooq.impl.DSL.keyword;
import static org.jooq.impl.DSL.lag;
import static org.jooq.impl.DSL.lastValue;
import static org.jooq.impl.DSL.lateral;
import static org.jooq.impl.DSL.lead;
import static org.jooq.impl.DSL.least;
import static org.jooq.impl.DSL.left;
import static org.jooq.impl.DSL.length;
// ...
import static org.jooq.impl.DSL.list;
import static org.jooq.impl.DSL.listAgg;
import static org.jooq.impl.DSL.ln;
import static org.jooq.impl.DSL.log;
import static org.jooq.impl.DSL.log10;
// ...
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.microsecond;
import static org.jooq.impl.DSL.mid;
import static org.jooq.impl.DSL.millennium;
import static org.jooq.impl.DSL.millisecond;
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.name;
import static org.jooq.impl.DSL.now;
import static org.jooq.impl.DSL.nthValue;
import static org.jooq.impl.DSL.ntile;
import static org.jooq.impl.DSL.nullif;
import static org.jooq.impl.DSL.nvl;
import static org.jooq.impl.DSL.nvl2;
import static org.jooq.impl.DSL.octetLength;
import static org.jooq.impl.DSL.one;
import static org.jooq.impl.DSL.orderBy;
import static org.jooq.impl.DSL.out;
import static org.jooq.impl.DSL.overlay;
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.pi;
import static org.jooq.impl.DSL.primaryKey;
// ...
import static org.jooq.impl.DSL.privilege;
import static org.jooq.impl.DSL.product;
import static org.jooq.impl.DSL.productDistinct;
import static org.jooq.impl.DSL.quarter;
import static org.jooq.impl.DSL.rad;
import static org.jooq.impl.DSL.rand;
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.ratioToReport;
import static org.jooq.impl.DSL.regexpReplaceAll;
import static org.jooq.impl.DSL.regexpReplaceFirst;
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.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.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.shl;
import static org.jooq.impl.DSL.shr;
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.splitPart;
import static org.jooq.impl.DSL.sql;
import static org.jooq.impl.DSL.sqrt;
import static org.jooq.impl.DSL.square;
import static org.jooq.impl.DSL.stddevPop;
import static org.jooq.impl.DSL.stddevSamp;
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.timezone;
import static org.jooq.impl.DSL.timezoneHour;
import static org.jooq.impl.DSL.timezoneMinute;
import static org.jooq.impl.DSL.toChar;
import static org.jooq.impl.DSL.toDate;
import static org.jooq.impl.DSL.toTimestamp;
import static org.jooq.impl.DSL.translate;
import static org.jooq.impl.DSL.trim;
import static org.jooq.impl.DSL.trunc;
import static org.jooq.impl.DSL.unique;
import static org.jooq.impl.DSL.unnest;
import static org.jooq.impl.DSL.unquotedName;
import static org.jooq.impl.DSL.user;
import static org.jooq.impl.DSL.values0;
// ...
import static org.jooq.impl.DSL.varPop;
import static org.jooq.impl.DSL.varSamp;
import static org.jooq.impl.DSL.week;
import static org.jooq.impl.DSL.when;
// ...
import static org.jooq.impl.DSL.xmlagg;
import static org.jooq.impl.DSL.xmlattributes;
import static org.jooq.impl.DSL.xmlcomment;
import static org.jooq.impl.DSL.xmlconcat;
// ...
import static org.jooq.impl.DSL.xmlelement;
import static org.jooq.impl.DSL.xmlexists;
import static org.jooq.impl.DSL.xmlforest;
import static org.jooq.impl.DSL.xmlparseContent;
import static org.jooq.impl.DSL.xmlparseDocument;
import static org.jooq.impl.DSL.xmlpi;
import static org.jooq.impl.DSL.xmlquery;
import static org.jooq.impl.DSL.xmltable;
import static org.jooq.impl.DSL.year;
import static org.jooq.impl.DSL.zero;
import static org.jooq.impl.JSONOnNull.ABSENT_ON_NULL;
import static org.jooq.impl.JSONOnNull.NULL_ON_NULL;
import static org.jooq.impl.Keywords.K_DELETE;
import static org.jooq.impl.Keywords.K_INSERT;
import static org.jooq.impl.Keywords.K_SELECT;
import static org.jooq.impl.Keywords.K_UPDATE;
import static org.jooq.impl.ParserContext.Type.A;
import static org.jooq.impl.ParserContext.Type.B;
import static org.jooq.impl.ParserContext.Type.D;
import static org.jooq.impl.ParserContext.Type.J;
import static org.jooq.impl.ParserContext.Type.N;
import static org.jooq.impl.ParserContext.Type.S;
import static org.jooq.impl.ParserContext.Type.X;
import static org.jooq.impl.ParserContext.Type.Y;
import static org.jooq.impl.SQLDataType.BIGINT;
import static org.jooq.impl.SQLDataType.INTEGER;
import static org.jooq.impl.SQLDataType.NVARCHAR;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.SelectQueryImpl.EMULATE_SELECT_INTO_AS_CTAS;
import static org.jooq.impl.Tools.EMPTY_BYTE;
import static org.jooq.impl.Tools.EMPTY_COLLECTION;
import static org.jooq.impl.Tools.EMPTY_COMMON_TABLE_EXPRESSION;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.EMPTY_NAME;
import static org.jooq.impl.Tools.EMPTY_OBJECT;
import static org.jooq.impl.Tools.EMPTY_QUERYPART;
import static org.jooq.impl.Tools.EMPTY_ROW;
import static org.jooq.impl.Tools.EMPTY_SORTFIELD;
import static org.jooq.impl.Tools.aliased;
import static org.jooq.impl.Tools.normaliseNameCase;
import static org.jooq.impl.XMLPassingMechanism.BY_REF;
import static org.jooq.impl.XMLPassingMechanism.BY_VALUE;
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.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jooq.AggregateFilterStep;
import org.jooq.AggregateFunction;
import org.jooq.AlterDatabaseStep;
import org.jooq.AlterDomainDropConstraintCascadeStep;
import org.jooq.AlterDomainRenameConstraintStep;
import org.jooq.AlterDomainStep;
import org.jooq.AlterIndexFinalStep;
import org.jooq.AlterIndexStep;
import org.jooq.AlterSchemaStep;
import org.jooq.AlterSequenceFlagsStep;
import org.jooq.AlterSequenceStep;
import org.jooq.AlterTableAddStep;
import org.jooq.AlterTableDropStep;
import org.jooq.AlterTableFinalStep;
import org.jooq.AlterTableStep;
import org.jooq.AlterTypeStep;
import org.jooq.ArrayAggOrderByStep;
import org.jooq.Block;
import org.jooq.CaseConditionStep;
import org.jooq.CaseValueStep;
import org.jooq.CaseWhenStep;
import org.jooq.Catalog;
import org.jooq.CharacterSet;
import org.jooq.Collation;
import org.jooq.Comment;
import org.jooq.CommentOnIsStep;
import org.jooq.CommonTableExpression;
import org.jooq.Comparator;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Constraint;
import org.jooq.ConstraintEnforcementStep;
import org.jooq.ConstraintForeignKeyOnStep;
import org.jooq.ConstraintTypeStep;
import org.jooq.CreateDomainConstraintStep;
import org.jooq.CreateDomainDefaultStep;
// ...
// ...
// ...
import org.jooq.CreateIndexFinalStep;
import org.jooq.CreateIndexIncludeStep;
import org.jooq.CreateIndexStep;
import org.jooq.CreateIndexWhereStep;
// ...
// ...
// ...
import org.jooq.CreateSequenceFlagsStep;
import org.jooq.CreateTableColumnStep;
import org.jooq.CreateTableCommentStep;
import org.jooq.CreateTableConstraintStep;
import org.jooq.CreateTableOnCommitStep;
import org.jooq.CreateTableStorageStep;
import org.jooq.CreateTableWithDataStep;
// ...
// ...
// ...
// ...
// ...
import org.jooq.DDLQuery;
import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.DatePart;
// ...
import org.jooq.Delete;
import org.jooq.DeleteLimitStep;
import org.jooq.DeleteOrderByStep;
import org.jooq.DeleteReturningStep;
import org.jooq.DeleteUsingStep;
import org.jooq.DeleteWhereStep;
import org.jooq.DerivedColumnList;
import org.jooq.Domain;
import org.jooq.DropDomainCascadeStep;
import org.jooq.DropIndexCascadeStep;
import org.jooq.DropIndexFinalStep;
import org.jooq.DropIndexOnStep;
import org.jooq.DropSchemaStep;
import org.jooq.DropTableFinalStep;
import org.jooq.DropTableStep;
import org.jooq.DropTypeFinalStep;
import org.jooq.DropTypeStep;
import org.jooq.DropViewFinalStep;
import org.jooq.Field;
import org.jooq.FieldOrConstraint;
import org.jooq.FieldOrRow;
// ...
// ...
import org.jooq.GrantOnStep;
import org.jooq.GrantToStep;
import org.jooq.GrantWithGrantOptionStep;
import org.jooq.GroupConcatOrderByStep;
import org.jooq.GroupConcatSeparatorStep;
import org.jooq.GroupField;
// ...
import org.jooq.Index;
import org.jooq.Insert;
import org.jooq.InsertOnConflictDoUpdateStep;
import org.jooq.InsertOnConflictWhereIndexPredicateStep;
import org.jooq.InsertOnConflictWhereStep;
import org.jooq.InsertOnDuplicateStep;
import org.jooq.InsertReturningStep;
import org.jooq.InsertSetStep;
import org.jooq.InsertValuesStepN;
import org.jooq.JSON;
import org.jooq.JSONArrayAggNullStep;
import org.jooq.JSONArrayAggOrderByStep;
import org.jooq.JSONArrayAggReturningStep;
import org.jooq.JSONArrayNullStep;
import org.jooq.JSONArrayReturningStep;
import org.jooq.JSONEntry;
import org.jooq.JSONObjectAggNullStep;
import org.jooq.JSONObjectAggReturningStep;
import org.jooq.JSONObjectNullStep;
import org.jooq.JSONObjectReturningStep;
import org.jooq.JSONTableColumnPathStep;
import org.jooq.JSONTableColumnsStep;
import org.jooq.JSONValueDefaultStep;
import org.jooq.JSONValueOnStep;
import org.jooq.JoinType;
import org.jooq.Keyword;
// ...
import org.jooq.LikeEscapeStep;
// ...
import org.jooq.Merge;
import org.jooq.MergeFinalStep;
import org.jooq.MergeMatchedDeleteStep;
import org.jooq.MergeMatchedStep;
import org.jooq.MergeMatchedWhereStep;
import org.jooq.MergeUsingStep;
import org.jooq.Meta;
import org.jooq.Name;
import org.jooq.Name.Quoted;
import org.jooq.OrderedAggregateFunction;
import org.jooq.OrderedAggregateFunctionOfDeferredType;
import org.jooq.Param;
import org.jooq.ParamMode;
import org.jooq.Parameter;
import org.jooq.Parser;
// ...
// ...
import org.jooq.Privilege;
// ...
import org.jooq.QualifiedAsterisk;
import org.jooq.Queries;
import org.jooq.Query;
import org.jooq.QueryPart;
import org.jooq.QueryPartInternal;
import org.jooq.Record;
import org.jooq.ResultQuery;
import org.jooq.RevokeFromStep;
import org.jooq.RevokeOnStep;
import org.jooq.Row;
import org.jooq.Row2;
import org.jooq.SQL;
import org.jooq.SQLDialect;
import org.jooq.Schema;
import org.jooq.Select;
import org.jooq.SelectFieldOrAsterisk;
import org.jooq.Sequence;
import org.jooq.SortField;
import org.jooq.SortOrder;
import org.jooq.Statement;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.TableLike;
import org.jooq.TableOnStep;
import org.jooq.TableOptionalOnStep;
import org.jooq.TablePartitionByStep;
import org.jooq.Truncate;
import org.jooq.TruncateCascadeStep;
import org.jooq.TruncateFinalStep;
import org.jooq.TruncateIdentityStep;
import org.jooq.Update;
import org.jooq.UpdateFromStep;
import org.jooq.UpdateLimitStep;
import org.jooq.UpdateOrderByStep;
import org.jooq.UpdateReturningStep;
import org.jooq.UpdateSetFirstStep;
import org.jooq.UpdateWhereStep;
import org.jooq.User;
// ...
// ...
import org.jooq.VisitContext;
import org.jooq.WindowBeforeOverStep;
import org.jooq.WindowDefinition;
import org.jooq.WindowFromFirstLastStep;
import org.jooq.WindowIgnoreNullsStep;
import org.jooq.WindowOverStep;
import org.jooq.WindowSpecification;
import org.jooq.WindowSpecificationExcludeStep;
import org.jooq.WindowSpecificationOrderByStep;
import org.jooq.WindowSpecificationRowsAndStep;
import org.jooq.WindowSpecificationRowsStep;
import org.jooq.XML;
import org.jooq.XMLAggOrderByStep;
import org.jooq.XMLAttributes;
import org.jooq.XMLTableColumnPathStep;
import org.jooq.XMLTableColumnsStep;
import org.jooq.XMLTablePassingStep;
import org.jooq.conf.ParseSearchSchema;
import org.jooq.conf.ParseUnknownFunctions;
import org.jooq.conf.ParseUnsupportedSyntax;
import org.jooq.conf.ParseWithMetaLookups;
import org.jooq.conf.RenderKeywordCase;
import org.jooq.conf.RenderNameCase;
import org.jooq.conf.RenderQuotedNames;
import org.jooq.conf.Settings;
import org.jooq.conf.SettingsTools;
import org.jooq.impl.ScopeStack.Value;
import org.jooq.impl.XMLParse.DocumentOrContent;
import org.jooq.tools.StringUtils;
import org.jooq.tools.reflect.Reflect;
import org.jooq.types.DayToSecond;
import org.jooq.types.Interval;
import org.jooq.types.YearToMonth;
import org.jooq.types.YearToSecond;
/**
* @author Lukas Eder
*/
final class ParserImpl implements Parser {
private final DSLContext dsl;
private final ParseWithMetaLookups metaLookups;
private final Meta meta;
ParserImpl(Configuration configuration) {
this.dsl = DSL.using(configuration);
this.metaLookups = configuration.settings().getParseWithMetaLookups();
this.meta = metaLookups == IGNORE_ON_FAILURE || metaLookups == THROW_ON_FAILURE ? dsl.meta() : null;
}
// -------------------------------------------------------------------------
// XXX: Top level parsing
// -------------------------------------------------------------------------
private final ParserContext ctx(String sql, Object... bindings) {
return new ParserContext(dsl, meta, metaLookups, sql, bindings);
}
@Override
public final Queries parse(String sql) {
return parse(sql, EMPTY_OBJECT);
}
@Override
public final Queries parse(String sql, Object... bindings) {
return ctx(sql, bindings).parse();
}
@Override
public final Query parseQuery(String sql) {
return parseQuery(sql, EMPTY_OBJECT);
}
@Override
public final Query parseQuery(String sql, Object... bindings) {
return ctx(sql, bindings).parseQuery0();
}
@Override
public final Statement parseStatement(String sql) {
return parseStatement(sql, EMPTY_OBJECT);
}
@Override
public final Statement parseStatement(String sql, Object... bindings) {
return ctx(sql, bindings).parseStatementAndSemicolonIf();
}
@Override
public final ResultQuery<?> parseResultQuery(String sql) {
return parseResultQuery(sql, EMPTY_OBJECT);
}
@Override
public final ResultQuery<?> parseResultQuery(String sql, Object... bindings) {
return ctx(sql, bindings).parseResultQuery0();
}
@Override
public final Select<?> parseSelect(String sql) {
return parseSelect(sql, EMPTY_OBJECT);
}
@Override
public final Select<?> parseSelect(String sql, Object... bindings) {
return ctx(sql, bindings).parseSelect0();
}
@Override
public final Table<?> parseTable(String sql) {
return parseTable(sql, EMPTY_OBJECT);
}
@Override
public final Table<?> parseTable(String sql, Object... bindings) {
return ctx(sql, bindings).parseTable0();
}
@Override
public final Field<?> parseField(String sql) {
return parseField(sql, EMPTY_OBJECT);
}
@Override
public final Field<?> parseField(String sql, Object... bindings) {
return ctx(sql, bindings).parseField0();
}
@Override
public final Row parseRow(String sql) {
return parseRow(sql, EMPTY_OBJECT);
}
@Override
public final Row parseRow(String sql, Object... bindings) {
return ctx(sql, bindings).parseRow0();
}
@Override
public final Condition parseCondition(String sql) {
return parseCondition(sql, EMPTY_OBJECT);
}
@Override
public final Condition parseCondition(String sql, Object... bindings) {
return ctx(sql, bindings).parseCondition0();
}
@Override
public final Name parseName(String sql) {
return parseName(sql, EMPTY_OBJECT);
}
@Override
public final Name parseName(String sql, Object... bindings) {
return ctx(sql, bindings).parseName0();
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
final class ParserContext {
static final Set<SQLDialect> SUPPORTS_HASH_COMMENT_SYNTAX = SQLDialect.supportedBy(MARIADB, MYSQL);
final Queries parse() {
List<Query> result = new ArrayList<>();
Query query;
do {
parseDelimiterSpecifications();
while (parseDelimiterIf(false));
query = patchParsedQuery(parseQuery(false, false));
if (query == IGNORE || query == IGNORE_NO_DELIMITER)
continue;
if (query != null)
result.add(query);
}
while (parseDelimiterIf(true) && !done());
return done("Unexpected token or missing query delimiter", dsl.queries(result));
}
private static final Pattern P_SEARCH_PATH = Pattern.compile("(?i:select\\s+(pg_catalog\\s*\\.\\s*)?set_config\\s*\\(\\s*'search_path'\\s*,\\s*'([^']*)'\\s*,\\s*\\w+\\s*\\))");
private final Query patchParsedQuery(Query query) {
// [#8910] Some statements can be parsed differently when we know we're
// parsing them for the DDLDatabase. This method patches these
// statements.
if (TRUE.equals(configuration().data("org.jooq.ddl.parse-for-ddldatabase"))) {
if (query instanceof Select) {
String s =
configuration().derive(SettingsTools.clone(configuration().settings())
.withRenderFormatted(false)
.withRenderKeywordCase(RenderKeywordCase.LOWER)
.withRenderNameCase(RenderNameCase.LOWER)
.withRenderQuotedNames(RenderQuotedNames.NEVER)
.withRenderSchema(false))
.dsl()
.render(query);
// [#8910] special treatment for PostgreSQL pg_dump's curious
// usage of the SET SCHEMA command
Matcher matcher = P_SEARCH_PATH.matcher(s);
String schema;
if (matcher.find())
if (!StringUtils.isBlank(schema = matcher.group(2)))
return configuration().dsl().setSchema(schema);
else
return IGNORE;
}
}
return query;
}
public final Query parseQuery0() {
return done("Unexpected clause", parseQuery(false, false));
}
final Statement parseStatement0() {
return this.done("Unexpected content", parseStatementAndSemicolonIf());
}
final ResultQuery<?> parseResultQuery0() {
return done("Unexpected content after end of query input", (ResultQuery<?>) parseQuery(true, false));
}
final Select<?> parseSelect0() {
return done("Unexpected content after end of query input", (Select<?>) parseQuery(true, true));
}
final Table<?> parseTable0() {
return done("Unexpected content after end of table input", parseTable());
}
final Field<?> parseField0() {
return done("Unexpected content after end of field input", parseField());
}
final Row parseRow0() {
return done("Unexpected content after end of row input", parseRow());
}
final Condition parseCondition0() {
return done("Unexpected content after end of condition input", parseCondition());
}
final Name parseName0() {
return done("Unexpected content after end of name input", parseName());
}
private final void parseDelimiterSpecifications() {
while (parseKeywordIf("DELIMITER"))
delimiter(parseUntilEOL().trim());
}
private final boolean parseDelimiterIf(boolean optional) {
if (parseIf(delimiter()))
return true;
if (peekKeyword("GO")) {
positionInc(2);
String line = parseUntilEOLIf();
if (line != null && !"".equals(line.trim()))
throw exception("GO must be only token on line");
parseWhitespaceIf();
return true;
}
return optional;
}
private final Query parseQuery(boolean parseResultQuery, boolean parseSelect) {
if (done())
return null;
scopeStart();
boolean previousMetaLookupsForceIgnore = metaLookupsForceIgnore();
try {
switch (characterUpper()) {
case 'A':
if (!parseResultQuery && peekKeyword("ALTER"))
return metaLookupsForceIgnore(true).parseAlter();
break;
case 'B':
if (!parseResultQuery && peekKeyword("BEGIN"))
return parseBlock(false);
break;
case 'C':
if (!parseResultQuery && peekKeyword("CREATE"))
return metaLookupsForceIgnore(true).parseCreate();
else if (!parseResultQuery && peekKeyword("COMMENT ON"))
return metaLookupsForceIgnore(true).parseCommentOn();
else if (parseKeywordIf("CALL"))
throw notImplemented("CALL");
else if (parseKeywordIf("COMMIT"))
throw notImplemented("COMMIT");
else if (parseKeywordIf("CONNECT"))
throw notImplemented("CONNECT");
break;
case 'D':
if (!parseResultQuery && peekKeyword("DECLARE") && requireProEdition())
return parseBlock(true);
else if (!parseResultQuery && (peekKeyword("DELETE") || peekKeyword("DEL")))
return parseDelete(null);
else if (!parseResultQuery && peekKeyword("DROP"))
return metaLookupsForceIgnore(true).parseDrop();
else if (!parseResultQuery && peekKeyword("DO"))
return parseDo();
break;
case 'E':
if (!parseResultQuery && peekKeyword("EXECUTE BLOCK AS"))
return parseBlock(true);
else if (!parseResultQuery && peekKeyword("EXEC"))
return parseExec();
break;
case 'G':
if (!parseResultQuery && peekKeyword("GRANT"))
return metaLookupsForceIgnore(true).parseGrant();
break;
case 'I':
if (!parseResultQuery && (peekKeyword("INSERT") || peekKeyword("INS")))
return parseInsert(null);
break;
case 'L':
if (parseKeywordIf("LOAD"))
throw notImplemented("LOAD");
break;
case 'M':
if (!parseResultQuery && peekKeyword("MERGE"))
return parseMerge(null);
break;
case 'R':
if (!parseResultQuery && peekKeyword("RENAME"))
return metaLookupsForceIgnore(true).parseRename();
else if (!parseResultQuery && peekKeyword("REVOKE"))
return metaLookupsForceIgnore(true).parseRevoke();
else if (parseKeywordIf("REPLACE"))
throw notImplemented("REPLACE");
else if (parseKeywordIf("ROLLBACK"))
throw notImplemented("ROLLBACK");
break;
case 'S':
if (peekSelect(false))
return parseSelect();
else if (!parseResultQuery && peekKeyword("SET"))
return parseSet();
else if (parseKeywordIf("SAVEPOINT"))
throw notImplemented("SAVEPOINT");
break;
case 'T':
if (!parseSelect && peekKeyword("TABLE"))
return parseSelect();
else if (!parseResultQuery && peekKeyword("TRUNCATE"))
return parseTruncate();
break;
case 'U':
if (!parseResultQuery && (peekKeyword("UPDATE") || peekKeyword("UPD")))
return parseUpdate(null);
else if (!parseResultQuery && peekKeyword("USE"))
return parseUse();
else if (parseKeywordIf("UPSERT"))
throw notImplemented("UPSERT");
break;
case 'V':
if (!parseSelect && peekKeyword("VALUES"))
return parseSelect();
case 'W':
if (peekKeyword("WITH"))
return parseWith(parseSelect);
break;
case '(':
// TODO are there other possible statement types?
if (peekKeyword("WITH", false, true, false))
return parseWith(true);
else
return parseSelect();
default:
break;
}
throw exception("Unsupported query type");
}
catch (ParserException e) {
// [#9061] Don't hide this pre-existing exceptions in scopeResolve()
scopeClear();
throw e;
}
finally {
scopeEnd();
scopeResolve();
metaLookupsForceIgnore(previousMetaLookupsForceIgnore);
}
}
// -----------------------------------------------------------------------------------------------------------------
// Statement parsing
// -----------------------------------------------------------------------------------------------------------------
private final Query parseWith(boolean parseSelect) {
return parseWith(parseSelect, null);
}
private final Query parseWith(boolean parseSelect, Integer degree) {
int parens = 0;
while (parseIf('('))
parens++;
parseKeyword("WITH");
boolean recursive = parseKeywordIf("RECURSIVE");
List<CommonTableExpression<?>> cte = new ArrayList<>();
do {
Name name = parseIdentifier();
DerivedColumnList dcl = null;
if (parseIf('(')) {
List<Name> columnNames = parseIdentifiers();
parse(')');
dcl = name.fields(columnNames.toArray(EMPTY_NAME));
}
parseKeyword("AS");
boolean materialized = parseKeywordIf("MATERIALIZED");
boolean notMaterialized = !materialized && parseKeywordIf("NOT MATERIALIZED");
parse('(');
Select<?> select = parseSelect();
parse(')');
cte.add(dcl != null
? materialized
? dcl.asMaterialized(select)
: notMaterialized
? dcl.asNotMaterialized(select)
: dcl.as(select)
: materialized
? name.asMaterialized(select)
: notMaterialized
? name.asNotMaterialized(select)
: name.as(select)
);
}
while (parseIf(','));
// TODO Better model API for WITH clause
WithImpl with = (WithImpl) new WithImpl(dsl.configuration(), recursive).with(cte.toArray(EMPTY_COMMON_TABLE_EXPRESSION));
Query result;
if (!parseSelect && (peekKeyword("DELETE") || peekKeyword("DEL")))
result = parseDelete(with);
else if (!parseSelect && (peekKeyword("INSERT") || peekKeyword("INS")))
result = parseInsert(with);
else if (!parseSelect && peekKeyword("MERGE"))
result = parseMerge(with);
else if (peekSelect(true))
result = parseSelect(degree, with);
else if (!parseSelect && (peekKeyword("UPDATE") || peekKeyword("UPD")))
result = parseUpdate(with);
else if ((parseWhitespaceIf() || true) && done())
throw exception("Missing statement after WITH");
else
throw exception("Unsupported statement after WITH");
while (parens --> 0)
parse(')');
return result;
}
private final Select<?> parseWithOrSelect() {
return parseWithOrSelect(null);
}
private final Select<?> parseWithOrSelect(Integer degree) {
return peekKeyword("WITH") ? (Select<?>) parseWith(true, degree) : parseSelect(degree, null);
}
private final SelectQueryImpl<Record> parseSelect() {
return parseSelect(null, null);
}
private final SelectQueryImpl<Record> parseSelect(Integer degree, WithImpl with) {
scopeStart();
SelectQueryImpl<Record> result = parseQueryExpressionBody(degree, with, null);
List<SortField<?>> orderBy = null;
for (Field<?> field : result.getSelect())
if (aliased(field) != null)
scope(field);
if (parseKeywordIf("ORDER")) {
if (parseKeywordIf("SIBLINGS BY") && requireProEdition()) {
}
else if (parseKeywordIf("BY"))
result.addOrderBy(orderBy = parseSortSpecification());
else
throw expected("SIBLINGS BY", "BY");
}
if (orderBy != null && parseKeywordIf("SEEK")) {
boolean before = parseKeywordIf("BEFORE");
if (!before)
parseKeywordIf("AFTER");
List<Field<?>> seek = parseFields();
if (seek.size() != orderBy.size())
throw exception("ORDER BY size (" + orderBy.size() + ") and SEEK size (" + seek.size() + ") must match");
if (before)
result.addSeekBefore(seek);
else
result.addSeekAfter(seek);
if (!result.getLimit().isApplicable())
parseLimit(result, false);
}
else if (!result.getLimit().isApplicable()) {
parseLimit(result, true);
}
forClause:
if (parseKeywordIf("FOR")) {
boolean jsonb;
if (parseKeywordIf("KEY SHARE"))
result.setForKeyShare(true);
else if (parseKeywordIf("NO KEY UPDATE"))
result.setForNoKeyUpdate(true);
else if (parseKeywordIf("SHARE"))
result.setForShare(true);
else if (parseKeywordIf("UPDATE"))
result.setForUpdate(true);
else if (parseKeywordIf("XML") && requireProEdition()) {
}
else if ((jsonb = parseKeywordIf("JSONB") || parseKeywordIf("JSON")) && requireProEdition()) {
}
else
throw expected("UPDATE", "NO KEY UPDATE", "SHARE", "KEY SHARE", "XML", "JSON");
if (parseKeywordIf("OF"))
result.setForUpdateOf(parseFields());
if (parseKeywordIf("NOWAIT"))
result.setForUpdateNoWait();
else if (parseKeywordIf("WAIT") && requireProEdition())
;
else if (parseKeywordIf("SKIP LOCKED"))
result.setForUpdateSkipLocked();
}
scopeEnd();
return result;
}
private final void parseLimit(SelectQueryImpl<Record> result, boolean offset) {
boolean offsetStandard = false;
boolean offsetPostgres = false;
if (offset && parseKeywordIf("OFFSET")) {
result.addOffset(requireParam(parseParenthesisedUnsignedIntegerOrBindVariable()));
if (parseKeywordIf("ROWS") || parseKeywordIf("ROW"))
offsetStandard = true;
// Ingres doesn't have a ROWS keyword after offset
else if (peekKeyword("FETCH"))
offsetStandard = true;
else
offsetPostgres = true;
}
if (!offsetStandard && parseKeywordIf("LIMIT")) {
Param<Long> limit = requireParam(parseParenthesisedUnsignedIntegerOrBindVariable());
if (offsetPostgres) {
result.addLimit(limit);
if (parseKeywordIf("PERCENT"))
result.setLimitPercent(true);
if (parseKeywordIf("WITH TIES"))
result.setWithTies(true);
}
else if (offset && parseIf(',')) {
result.addLimit(limit, requireParam(parseParenthesisedUnsignedIntegerOrBindVariable()));
}
else {
if (parseKeywordIf("PERCENT"))
result.setLimitPercent(true);
if (parseKeywordIf("WITH TIES"))
result.setWithTies(true);
if (offset && parseKeywordIf("OFFSET"))
result.addLimit(requireParam(parseParenthesisedUnsignedIntegerOrBindVariable()), limit);
else
result.addLimit(limit);
}
}
else if (!offsetPostgres && parseKeywordIf("FETCH")) {
parseAndGetKeyword("FIRST", "NEXT");
if (parseAndGetKeywordIf("ROW", "ROWS") != null) {
result.addLimit(inline(1L));
}
else {
result.addLimit(requireParam(parseParenthesisedUnsignedIntegerOrBindVariable()));
if (parseKeywordIf("PERCENT"))
result.setLimitPercent(true);
parseAndGetKeyword("ROW", "ROWS");
}
if (parseKeywordIf("WITH TIES"))
result.setWithTies(true);
else
parseKeyword("ONLY");
}
else if (!offsetStandard && !offsetPostgres && parseKeywordIf("ROWS")) {
Long from = parseUnsignedInteger();
if (parseKeywordIf("TO")) {
Long to = parseUnsignedInteger();
result.addLimit(to - from);
result.addOffset(from - 1);
}
else {
result.addLimit(from);
}
}
}
private final SelectQueryImpl<Record> parseQueryExpressionBody(Integer degree, WithImpl with, SelectQueryImpl<Record> prefix) {
SelectQueryImpl<Record> lhs = parseQueryTerm(degree, with, prefix);
CombineOperator combine;
while ((combine = parseCombineOperatorIf(false)) != null) {
scopeEnd();
scopeStart();
if (degree == null)
degree = Tools.degree(lhs);
SelectQueryImpl<Record> rhs = degreeCheck(degree, parseQueryTerm(degree, null, null));
switch (combine) {
case UNION:
lhs = (SelectQueryImpl<Record>) lhs.union(rhs);
break;
case UNION_ALL:
lhs = (SelectQueryImpl<Record>) lhs.unionAll(rhs);
break;
case EXCEPT:
lhs = (SelectQueryImpl<Record>) lhs.except(rhs);
break;
case EXCEPT_ALL:
lhs = (SelectQueryImpl<Record>) lhs.exceptAll(rhs);
break;
default:
throw internalError();
}
}
return lhs;
}
private final SelectQueryImpl<Record> parseQueryTerm(Integer degree, WithImpl with, SelectQueryImpl<Record> prefix) {
SelectQueryImpl<Record> lhs = prefix != null ? prefix : parseQueryPrimary(degree, with);
CombineOperator combine;
while ((combine = parseCombineOperatorIf(true)) != null) {
scopeEnd();
scopeStart();
if (degree == null)
degree = Tools.degree(lhs);
SelectQueryImpl<Record> rhs = degreeCheck(degree, parseQueryPrimary(degree, null));
switch (combine) {
case INTERSECT:
lhs = (SelectQueryImpl<Record>) lhs.intersect(rhs);
break;
case INTERSECT_ALL:
lhs = (SelectQueryImpl<Record>) lhs.intersectAll(rhs);
break;
default:
throw internalError();
}
}
return lhs;
}
private SelectQueryImpl<Record> degreeCheck(int expected, SelectQueryImpl<Record> s) {
if (expected == 0)
return s;
int actual = Tools.degree(s);
if (actual == 0)
return s;
if (expected != actual)
throw exception("Select list must contain " + expected + " columns. Got: " + actual);
return s;
}
private final SelectQueryImpl<Record> parseQueryPrimary(Integer degree, WithImpl with) {
if (parseIf('(')) {
SelectQueryImpl<Record> result = parseSelect(degree, with);
parse(')');
return result;
}
if (peekKeyword("VALUES"))
return (SelectQueryImpl<Record>) dsl.selectQuery(parseTableValueConstructor());
else if (peekKeyword("TABLE"))
return (SelectQueryImpl<Record>) dsl.selectQuery(parseExplicitTable());
ignoreHints(false);
if (!parseKeywordIf("SEL"))
parseKeyword("SELECT");
String hints = parseHints();
boolean distinct = parseKeywordIf("DISTINCT") || parseKeywordIf("UNIQUE");
List<Field<?>> distinctOn = null;
if (distinct) {
if (parseKeywordIf("ON")) {
parse('(');
distinctOn = parseFields();
parse(')');
}
}
else
parseKeywordIf("ALL");
Param<Long> limit = null;
Param<Long> offset = null;
boolean percent = false;
boolean withTies = false;
// T-SQL style TOP .. START AT
if (parseKeywordIf("TOP")) {
limit = requireParam(parseParenthesisedUnsignedIntegerOrBindVariable());
percent = parseKeywordIf("PERCENT") && requireProEdition();
if (parseKeywordIf("START AT"))
offset = requireParam(parseParenthesisedUnsignedIntegerOrBindVariable());
else if (parseKeywordIf("WITH TIES"))
withTies = true;
}
// Informix style SKIP .. FIRST
else if (parseKeywordIf("SKIP")) {
offset = requireParam(parseParenthesisedUnsignedIntegerOrBindVariable());
if (parseKeywordIf("FIRST"))
limit = requireParam(parseParenthesisedUnsignedIntegerOrBindVariable());
}
else if (parseKeywordIf("FIRST")) {
limit = requireParam(parseParenthesisedUnsignedIntegerOrBindVariable());
}
List<SelectFieldOrAsterisk> select = parseSelectList();
degreeCheck:
if (degree != null && !degree.equals(0) && !degree.equals(select.size())) {
for (SelectFieldOrAsterisk s : select)
if (!(s instanceof Field<?>))
break degreeCheck;
throw exception("Select list must contain " + degree + " columns. Got: " + select.size());
}
Table<?> intoTable = null;
List<Table<?>> from = null;
if (parseKeywordIf("INTO")) {
if (proEdition()) {
}
else
intoTable = parseTableName();
}
if (parseKeywordIf("FROM"))
from = parseTables();
// TODO is there a better way?
if (from != null && from.size() == 1 && from.get(0).getName().equalsIgnoreCase("dual"))
from = null;
// [#9061] Register tables in scope as early as possible
// TODO: Move this into parseTables() so lateral joins can profit from lookups (?)
if (from != null)
for (Table<?> table : from)
scope(table);
SelectQueryImpl<Record> result = new SelectQueryImpl<>(dsl.configuration(), with);
if (hints != null)
result.addHint(hints);
if (distinct)
result.setDistinct(distinct);
if (distinctOn != null)
result.addDistinctOn(distinctOn);
if (!select.isEmpty())
result.addSelect(select);
if (intoTable != null)
result.setInto(intoTable);
if (from != null)
result.addFrom(from);
// [#10638] [#11403] Oracle and Teradata seem to support (but not document)
// arbitrary ordering between these clauses
boolean where = false;
boolean connectBy = false;
boolean startWith = false;
boolean groupBy = false;
boolean having = false;
while ((!where && (where = parseQueryPrimaryWhere(result)))
|| (!connectBy && (connectBy = parseQueryPrimaryConnectBy(result)))
|| (!startWith && (startWith = parseQueryPrimaryStartWith(result)))
|| (!groupBy && (groupBy = parseQueryPrimaryGroupBy(result)))
|| (!having && (having = parseQueryPrimaryHaving(result))))
;
if (startWith && !connectBy)
throw expected("CONNECT BY");
if (parseKeywordIf("WINDOW"))
result.addWindow(parseWindowDefinitions());
if (parseKeywordIf("QUALIFY"))
result.addQualify(parseCondition());
if (limit != null)
if (offset != null)
result.addLimit(offset, limit);
else
result.addLimit(limit);
if (percent)
;
if (withTies)
result.setWithTies(true);
return result;
}
private final boolean parseQueryPrimaryWhere(SelectQueryImpl<Record> result) {
if (parseKeywordIf("WHERE")) {
result.addConditions(parseCondition());
return true;
}
else
return false;
}
private final boolean parseQueryPrimaryHaving(SelectQueryImpl<Record> result) {
if (parseKeywordIf("HAVING")) {
result.addHaving(parseCondition());
return true;
}
else
return false;
}
private final boolean parseQueryPrimaryGroupBy(SelectQueryImpl<Record> result) {
List<GroupField> groupBy;
if (parseKeywordIf("GROUP BY")) {
if (parseIf('(')) {
parse(')');
result.addGroupBy();
}
else if (parseKeywordIf("ROLLUP")) {
parse('(');
result.addGroupBy(rollup(parseFields().toArray(EMPTY_FIELD)));
parse(')');
}
else if (parseKeywordIf("CUBE")) {
parse('(');
result.addGroupBy(cube(parseFields().toArray(EMPTY_FIELD)));
parse(')');
}
else if (parseKeywordIf("GROUPING SETS")) {
List<List<Field<?>>> fieldSets = new ArrayList<>();
parse('(');
do
fieldSets.add(parseFieldsOrEmptyParenthesised());
while (parseIf(','));
parse(')');
result.addGroupBy(groupingSets(fieldSets.toArray((Collection[]) EMPTY_COLLECTION)));
}
else {
groupBy = (List) parseFields();
if (parseKeywordIf("WITH ROLLUP"))
result.addGroupBy(rollup(groupBy.toArray(EMPTY_FIELD)));
else
result.addGroupBy(groupBy);
}
return true;
}
else
return false;
}
private final boolean parseQueryPrimaryConnectBy(SelectQueryImpl<Record> result) {
if (parseKeywordIf("CONNECT BY") && requireProEdition()) {
return true;
}
else
return false;
}
private final boolean parseQueryPrimaryStartWith(SelectQueryImpl<Record> result) {
if (parseKeywordIf("START WITH") && requireProEdition()) {
return true;
}
else
return false;
}
private final List<WindowDefinition> parseWindowDefinitions() {
List<WindowDefinition> result = new ArrayList<>();
do {
Name name = parseIdentifier();
parseKeyword("AS");
parse('(');
result.add(name.as(parseWindowSpecificationIf(null, true)));
parse(')');
}
while (parseIf(','));
return result;
}
private final WindowSpecification parseWindowSpecificationIf(Name windowName, boolean orderByAllowed) {
final WindowSpecificationOrderByStep s1;
final WindowSpecificationRowsStep s2;
final WindowSpecificationRowsAndStep s3;
final WindowSpecificationExcludeStep s4;
final WindowSpecification result;
s1 = windowName != null
? windowName.as()
: parseKeywordIf("PARTITION BY")
? partitionBy(parseFields())
: null;
if (parseKeywordIf("ORDER BY"))
if (orderByAllowed)
s2 = s1 == null
? orderBy(parseSortSpecification())
: s1.orderBy(parseSortSpecification());
else
throw exception("ORDER BY not allowed");
else
s2 = s1;
boolean rows = parseKeywordIf("ROWS");
boolean range = !rows && parseKeywordIf("RANGE");
boolean groups = !rows && !range && parseKeywordIf("GROUPS");
if ((rows || range || groups) && !orderByAllowed)
throw exception("ROWS, RANGE, or GROUPS not allowed");
if (rows || range || groups) {
Long n;
if (parseKeywordIf("BETWEEN")) {
if (parseKeywordIf("UNBOUNDED"))
if (parseKeywordIf("PRECEDING"))
s3 = s2 == null
? rows
? rowsBetweenUnboundedPreceding()
: range
? rangeBetweenUnboundedPreceding()
: groupsBetweenUnboundedPreceding()
: rows
? s2.rowsBetweenUnboundedPreceding()
: range
? s2.rangeBetweenUnboundedPreceding()
: s2.groupsBetweenUnboundedPreceding();
else if (parseKeywordIf("FOLLOWING"))
s3 = s2 == null
? rows
? rowsBetweenUnboundedFollowing()
: range
? rangeBetweenUnboundedFollowing()
: groupsBetweenUnboundedFollowing()
: rows
? s2.rowsBetweenUnboundedFollowing()
: range
? s2.rangeBetweenUnboundedFollowing()
: s2.groupsBetweenUnboundedFollowing();
else
throw expected("FOLLOWING", "PRECEDING");
else if (parseKeywordIf("CURRENT ROW"))
s3 = s2 == null
? rows
? rowsBetweenCurrentRow()
: range
? rangeBetweenCurrentRow()
: groupsBetweenCurrentRow()
: rows
? s2.rowsBetweenCurrentRow()
: range
? s2.rangeBetweenCurrentRow()
: s2.groupsBetweenCurrentRow();
else if ((n = parseUnsignedIntegerIf()) != null)
if (parseKeywordIf("PRECEDING"))
s3 = s2 == null
? rows
? rowsBetweenPreceding(n.intValue())
: range
? rangeBetweenPreceding(n.intValue())
: groupsBetweenPreceding(n.intValue())
: rows
? s2.rowsBetweenPreceding(n.intValue())
: range
? s2.rangeBetweenPreceding(n.intValue())
: s2.groupsBetweenPreceding(n.intValue());
else if (parseKeywordIf("FOLLOWING"))
s3 = s2 == null
? rows
? rowsBetweenFollowing(n.intValue())
: range
? rangeBetweenFollowing(n.intValue())
: groupsBetweenFollowing(n.intValue())
: rows
? s2.rowsBetweenFollowing(n.intValue())
: range
? s2.rangeBetweenFollowing(n.intValue())
: s2.groupsBetweenFollowing(n.intValue());
else
throw expected("FOLLOWING", "PRECEDING");
else
throw expected("CURRENT ROW", "UNBOUNDED", "integer literal");
parseKeyword("AND");
if (parseKeywordIf("UNBOUNDED"))
if (parseKeywordIf("PRECEDING"))
s4 = s3.andUnboundedPreceding();
else if (parseKeywordIf("FOLLOWING"))
s4 = s3.andUnboundedFollowing();
else
throw expected("FOLLOWING", "PRECEDING");
else if (parseKeywordIf("CURRENT ROW"))
s4 = s3.andCurrentRow();
else if ((n = parseUnsignedInteger()) != null)
if (parseKeywordIf("PRECEDING"))
s4 = s3.andPreceding(n.intValue());
else if (parseKeywordIf("FOLLOWING"))
s4 = s3.andFollowing(n.intValue());
else
throw expected("FOLLOWING", "PRECEDING");
else
throw expected("CURRENT ROW", "UNBOUNDED", "integer literal");
}
else if (parseKeywordIf("UNBOUNDED"))
if (parseKeywordIf("PRECEDING"))
s4 = s2 == null
? rows
? rowsUnboundedPreceding()
: range
? rangeUnboundedPreceding()
: groupsUnboundedPreceding()
: rows
? s2.rowsUnboundedPreceding()
: range
? s2.rangeUnboundedPreceding()
: s2.groupsUnboundedPreceding();
else if (parseKeywordIf("FOLLOWING"))
s4 = s2 == null
? rows
? rowsUnboundedFollowing()
: range
? rangeUnboundedFollowing()
: groupsUnboundedFollowing()
: rows
? s2.rowsUnboundedFollowing()
: range
? s2.rangeUnboundedFollowing()
: s2.groupsUnboundedFollowing();
else
throw expected("FOLLOWING", "PRECEDING");
else if (parseKeywordIf("CURRENT ROW"))
s4 = s2 == null
? rows
? rowsCurrentRow()
: range
? rangeCurrentRow()
: groupsCurrentRow()
: rows
? s2.rowsCurrentRow()
: range
? s2.rangeCurrentRow()
: s2.groupsCurrentRow();
else if ((n = parseUnsignedInteger()) != null)
if (parseKeywordIf("PRECEDING"))
s4 = s2 == null
? rows
? rowsPreceding(n.intValue())
: range
? rangePreceding(n.intValue())
: groupsPreceding(n.intValue())
: rows
? s2.rowsPreceding(n.intValue())
: range
? s2.rangePreceding(n.intValue())
: s2.groupsPreceding(n.intValue());
else if (parseKeywordIf("FOLLOWING"))
s4 = s2 == null
? rows
? rowsFollowing(n.intValue())
: range
? rangeFollowing(n.intValue())
: groupsFollowing(n.intValue())
: rows
? s2.rowsFollowing(n.intValue())
: range
? s2.rangeFollowing(n.intValue())
: s2.groupsFollowing(n.intValue());
else
throw expected("FOLLOWING", "PRECEDING");
else
throw expected("BETWEEN", "CURRENT ROW", "UNBOUNDED", "integer literal");
if (parseKeywordIf("EXCLUDE"))
if (parseKeywordIf("CURRENT ROW"))
result = s4.excludeCurrentRow();
else if (parseKeywordIf("TIES"))
result = s4.excludeTies();
else if (parseKeywordIf("GROUP"))
result = s4.excludeGroup();
else if (parseKeywordIf("NO OTHERS"))
result = s4.excludeNoOthers();
else
throw expected("CURRENT ROW", "TIES", "GROUP", "NO OTHERS");
else
result = s4;
}
else
result = s2;
if (result != null)
return result;
else if (windowName != null)
return null;
else if ((windowName = parseIdentifierIf()) != null)
return parseWindowSpecificationIf(windowName, orderByAllowed);
else
return null;
}
private final Delete<?> parseDelete(WithImpl with) {
if (!parseKeywordIf("DEL"))
parseKeyword("DELETE");
Param<Long> limit = null;
// T-SQL style TOP .. START AT
if (parseKeywordIf("TOP")) {
limit = requireParam(parseParenthesisedUnsignedIntegerOrBindVariable());
// [#8623] TODO Support this
// percent = parseKeywordIf("PERCENT") && requireProEdition();
}
parseKeywordIf("FROM");
Table<?> table = parseTableNameIf();
if (table == null)
table = table(parseSelect());
Name alias;
if (parseKeywordIf("AS"))
table = table.as(parseIdentifier());
else if (!peekKeyword("USING", "WHERE", "ORDER BY", "LIMIT", "RETURNING")
&& !peekKeyword(KEYWORDS_IN_STATEMENTS)
&& (alias = parseIdentifierIf()) != null)
table = table.as(alias);
scope(table);
DeleteUsingStep<?> s1 = with == null ? dsl.delete(table) : with.delete(table);
DeleteWhereStep<?> s2 = parseKeywordIf("USING") ? s1.using(parseTables()) : s1;
DeleteOrderByStep<?> s3 = parseKeywordIf("WHERE") ? s2.where(parseCondition()) : s2;
DeleteLimitStep<?> s4 = parseKeywordIf("ORDER BY") ? s3.orderBy(parseSortSpecification()) : s3;
DeleteReturningStep<?> s5 = (limit != null || parseKeywordIf("LIMIT"))
? s4.limit(limit != null ? limit : requireParam(parseParenthesisedUnsignedIntegerOrBindVariable()))
: s4;
Delete<?> s6 = parseKeywordIf("RETURNING") ? s5.returning(parseSelectList()) : s5;
return s6;
}
private final Insert<?> parseInsert(WithImpl with) {
scopeStart();
if (!parseKeywordIf("INS"))
parseKeyword("INSERT");
parseKeywordIf("INTO");
Table<?> table = parseTableNameIf();
if (table == null)
table = table(parseSelect());
Name alias;
if (parseKeywordIf("AS"))
table = table.as(parseIdentifier());
else if (!peekKeyword("DEFAULT VALUES", "SEL", "SELECT", "SET", "VALUES")
&& (alias = parseIdentifierIf()) != null)
table = table.as(alias);
scope(table);
InsertSetStep<?> s1 = (with == null ? dsl.insertInto(table) : with.insertInto(table));
Field<?>[] fields = null;
if (parseIf('(')) {
fields = parseFieldNames().toArray(EMPTY_FIELD);
parse(')');
}
InsertOnDuplicateStep<?> onDuplicate;
InsertReturningStep<?> returning;
try {
if (parseKeywordIf("VALUES")) {
List<List<Field<?>>> allValues = new ArrayList<>();
valuesLoop:
do {
parse('(');
// [#6936] MySQL treats an empty VALUES() clause as the same thing as the standard DEFAULT VALUES
if (fields == null && parseIf(')'))
break valuesLoop;
List<Field<?>> values = new ArrayList<>();
do
values.add(parseKeywordIf("DEFAULT") ? default_() : parseField());
while (parseIf(','));
if (fields != null && fields.length != values.size())
throw exception("Insert field size (" + fields.length + ") must match values size (" + values.size() + ")");
allValues.add(values);
parse(')');
}
while (parseIf(','));
InsertValuesStepN<?> step2 = (fields != null)
? s1.columns(fields)
: (InsertValuesStepN<?>) s1;
for (List<Field<?>> values : allValues)
step2 = step2.values(values);
returning = onDuplicate = step2;
}
else if (parseKeywordIf("SET")) {
Map<Field<?>, Object> map = parseSetClauseList();
returning = onDuplicate = s1.set(map);
}
else if (peekSelectOrWith(true)){
// [#10954] These are moved into the INSERT .. SELECT clause handling. They should not be necessary here
// either, but it seems we currently don't correctly implement nesting scopes?
scopeEnd();
scopeStart();
Select<?> select = parseWithOrSelect();
returning = onDuplicate = (fields == null)
? s1.select(select)
: s1.columns(fields).select(select);
}
else if (parseKeywordIf("DEFAULT VALUES")) {
if (fields != null)
throw notImplemented("DEFAULT VALUES without INSERT field list");
else
returning = onDuplicate = s1.defaultValues();
}
else
throw expected("DEFAULT VALUES", "WITH", "SELECT", "SET", "VALUES");
if (parseKeywordIf("ON")) {
if (parseKeywordIf("DUPLICATE KEY UPDATE")) {
parseKeywordIf("SET");
InsertOnConflictWhereStep<?> where = onDuplicate.onDuplicateKeyUpdate().set(parseSetClauseList());
if (parseKeywordIf("WHERE"))
returning = where.where(parseCondition());
else
returning = where;
}
else if (parseKeywordIf("DUPLICATE KEY IGNORE")) {
returning = onDuplicate.onDuplicateKeyIgnore();
}
else if (parseKeywordIf("CONFLICT")) {
InsertOnConflictDoUpdateStep<?> doUpdate;
if (parseKeywordIf("ON CONSTRAINT")) {
doUpdate = onDuplicate.onConflictOnConstraint(parseName());
}
else if (parseIf('(')) {
InsertOnConflictWhereIndexPredicateStep<?> where = onDuplicate.onConflict(parseFieldNames());
parse(')');
doUpdate = parseKeywordIf("WHERE")
? where.where(parseCondition())
: where;
}
else {
doUpdate = onDuplicate.onConflict();
}
parseKeyword("DO");
if (parseKeywordIf("NOTHING")) {
returning = doUpdate.doNothing();
}
else if (parseKeywordIf("UPDATE SET")) {
InsertOnConflictWhereStep<?> where = doUpdate.doUpdate().set(parseSetClauseList());
if (parseKeywordIf("WHERE"))
returning = where.where(parseCondition());
else
returning = where;
}
else
throw expected("NOTHING", "UPDATE");
}
else
throw expected("CONFLICT", "DUPLICATE");
}
if (parseKeywordIf("RETURNING"))
return returning.returning(parseSelectList());
else
return returning;
}
finally {
scopeEnd();
}
}
private final Update<?> parseUpdate(WithImpl with) {
if (!parseKeywordIf("UPD"))
parseKeyword("UPDATE");
Param<Long> limit = null;
// T-SQL style TOP .. START AT
if (parseKeywordIf("TOP")) {
limit = requireParam(parseParenthesisedUnsignedIntegerOrBindVariable());
// [#8623] TODO Support this
// percent = parseKeywordIf("PERCENT") && requireProEdition();
}
Table<?> table = parseTableNameIf();
if (table == null)
table = table(parseSelect());
if (parseKeywordIf("AS"))
table = table.as(parseIdentifier());
else if (!peekKeyword("SET", "FROM"))
table = table.as(parseIdentifierIf());
scope(table);
UpdateSetFirstStep<?> s1 = (with == null ? dsl.update(table) : with.update(table));
List<Table<?>> from = parseKeywordIf("FROM") ? parseTables() : null;
parseKeyword("SET");
UpdateFromStep<?> s2;
if (peek('(')) {
Row row = parseRow();
parse('=');
// TODO Can we extract a public API for this?
if (peekSelectOrWith(true))
((UpdateImpl<?>) s1).getDelegate().addValues0(row, parseWithOrSelect(row.size()));
else
((UpdateImpl<?>) s1).getDelegate().addValues0(row, parseRow(row.size()));
s2 = (UpdateFromStep<?>) s1;
}
else {
Map<Field<?>, Object> map = parseSetClauseList();
s2 = s1.set(map);
}
UpdateWhereStep<?> s3 = from != null
? s2.from(from)
: parseKeywordIf("FROM")
? s2.from(parseTables())
: s2;
UpdateOrderByStep<?> s4 = parseKeywordIf("WHERE") ? s3.where(parseCondition()) : s3;
UpdateLimitStep<?> s5 = parseKeywordIf("ORDER BY") ? s4.orderBy(parseSortSpecification()) : s4;
UpdateReturningStep<?> s6 = (limit != null || parseKeywordIf("LIMIT"))
? s5.limit(limit != null ? limit : requireParam(parseParenthesisedUnsignedIntegerOrBindVariable()))
: s5;
Update<?> s7 = parseKeywordIf("RETURNING") ? s6.returning(parseSelectList()) : s6;
return s7;
}
private final Map<Field<?>, Object> parseSetClauseList() {
Map<Field<?>, Object> map = new LinkedHashMap<>();
do {
Field<?> field = parseFieldName();
if (map.containsKey(field))
throw exception("Duplicate column in set clause list: " + field);
parse('=');
Field<?> value = parseKeywordIf("DEFAULT") ? default_() : parseField();
map.put(field, value);
}
while (parseIf(','));
return map;
}
@SuppressWarnings("null")
private final Merge<?> parseMerge(WithImpl with) {
parseKeyword("MERGE");
parseKeywordIf("INTO");
Table<?> target = parseTableName();
if (parseKeywordIf("AS") || !peekKeyword("USING"))
target = target.as(parseIdentifier());
parseKeyword("USING");
Table<?> table = null;
Select<?> using = null;
if (parseIf('(')) {
using = parseSelect();
parse(')');
}
else {
table = parseTableName();
}
TableLike<?> usingTable = (table != null ? table : using);
if (parseKeywordIf("AS") || !peekKeyword("ON"))
usingTable = usingTable.asTable(parseIdentifier());
parseKeyword("ON");
Condition on = parseCondition();
boolean update = false;
boolean insert = false;
Field<?>[] insertColumns = null;
List<Field<?>> insertValues = null;
Condition insertWhere = null;
Map<Field<?>, Object> updateSet = null;
Condition updateAnd = null;
Condition updateWhere = null;
Condition deleteWhere = null;
MergeUsingStep<?> s1 = (with == null ? dsl.mergeInto(target) : with.mergeInto(target));
MergeMatchedStep<?> s2 = s1.using(usingTable).on(on);
for (;;) {
if (parseKeywordIf("WHEN MATCHED")) {
update = true;
if (parseKeywordIf("AND"))
updateAnd = parseCondition();
if (parseKeywordIf("THEN DELETE")) {
s2 = updateAnd != null
? s2.whenMatchedAnd(updateAnd).thenDelete()
: s2.whenMatchedThenDelete();
}
else {
parseKeyword("THEN UPDATE SET");
updateSet = parseSetClauseList();
if (updateAnd == null && parseKeywordIf("WHERE"))
updateWhere = parseCondition();
if (updateAnd == null && parseKeywordIf("DELETE WHERE"))
deleteWhere = parseCondition();
if (updateAnd != null) {
s2.whenMatchedAnd(updateAnd).thenUpdate().set(updateSet);
}
else {
MergeMatchedWhereStep<?> s3 = s2.whenMatchedThenUpdate().set(updateSet);
MergeMatchedDeleteStep<?> s4 = updateWhere != null ? s3.where(updateWhere) : s3;
s2 = deleteWhere != null ? s4.deleteWhere(deleteWhere) : s3;
}
}
}
else if (!insert && (insert = parseKeywordIf("WHEN NOT MATCHED"))) {
if (parseKeywordIf("AND"))
insertWhere = parseCondition();
parseKeyword("THEN INSERT");
parse('(');
insertColumns = Tools.fieldsByName(parseIdentifiers().toArray(EMPTY_NAME));
parse(')');
parseKeyword("VALUES");
parse('(');
insertValues = new ArrayList<>();
do
insertValues.add(parseKeywordIf("DEFAULT") ? default_() : parseField());
while (parseIf(','));
parse(')');
if (insertColumns.length != insertValues.size())
throw exception("Insert column size (" + insertColumns.length + ") must match values size (" + insertValues.size() + ")");
if (insertWhere == null && parseKeywordIf("WHERE"))
insertWhere = parseCondition();
}
else
break;
}
if (!update && !insert)
throw exception("At least one of UPDATE or INSERT clauses is required");
// TODO support multi clause MERGE
// TODO support DELETE
MergeFinalStep<?> s3 = insert
? insertWhere != null
? s2.whenNotMatchedThenInsert(insertColumns).values(insertValues).where(insertWhere)
: s2.whenNotMatchedThenInsert(insertColumns).values(insertValues)
: s2;
return s3;
}
private final Query parseSet() {
parseKeyword("SET");
if (parseKeywordIf("CATALOG"))
return parseSetCatalog();
else if (parseKeywordIf("CURRENT SCHEMA"))
return parseSetSchema();
else if (parseKeywordIf("CURRENT SQLID"))
return parseSetSchema();
else if (parseKeywordIf("GENERATOR"))
return parseSetGenerator();
else if (parseKeywordIf("SCHEMA"))
return parseSetSchema();
else if (parseKeywordIf("SEARCH_PATH"))
return parseSetSearchPath();
else
return parseSetCommand();
}
private final Query parseSetCommand() {
if (TRUE.equals(settings().isParseSetCommands())) {
Name name = parseIdentifier();
// TODO: [#9780] Are there any possible syntaxes and data types?
parseIf('=');
Object value = parseSignedIntegerIf();
return dsl.set(name, value != null ? inline(value) : inline(parseStringLiteral()));
}
// There are many SET commands in programs like sqlplus, which we'll simply ignore
else {
parseUntilEOL();
return IGNORE_NO_DELIMITER;
}
}
private final Query parseSetCatalog() {
return dsl.setCatalog(parseCatalogName());
}
private final Query parseUse() {
parseKeyword("USE");
return dsl.setCatalog(parseCatalogName());
}
private final Query parseSetSchema() {
parseIf('=');
return dsl.setSchema(parseSchemaName());
}
private final Query parseSetSearchPath() {
if (!parseIf('='))
parseKeyword("TO");
Schema schema = null;
do {
Schema s = parseSchemaName();
if (schema == null)
schema = s;
}
while (parseIf(','));
return dsl.setSchema(schema);
}
private final DDLQuery parseCommentOn() {
parseKeyword("COMMENT ON");
CommentOnIsStep s1;
if (parseKeywordIf("COLUMN"))
s1 = dsl.commentOnColumn(parseFieldName());
else if (parseKeywordIf("TABLE"))
s1 = dsl.commentOnTable(parseTableName());
else if (parseKeywordIf("VIEW"))
s1 = dsl.commentOnView(parseTableName());
// Ignored no-arg object comments
// https://www.postgresql.org/docs/10/static/sql-comment.html
// https://docs.oracle.com/database/121/SQLRF/statements_4010.htm
else if (parseAndGetKeywordIf(
"ACCESS METHOD",
"AUDIT POLICY",
"COLLATION",
"CONVERSION",
"DATABASE",
"DOMAIN",
"EDITION",
"EXTENSION",
"EVENT TRIGGER",
"FOREIGN DATA WRAPPER",
"FOREIGN TABLE",
"INDEX",
"INDEXTYPE",
"LANGUAGE",
"LARGE OBJECT",
"MATERIALIZED VIEW",
"MINING MODEL",
"OPERATOR",
"PROCEDURAL LANGUAGE",
"PUBLICATION",
"ROLE",
"SCHEMA",
"SEQUENCE",
"SERVER",
"STATISTICS",
"SUBSCRIPTION",
"TABLESPACE",
"TEXT SEARCH CONFIGURATION",
"TEXT SEARCH DICTIONARY",
"TEXT SEARCH PARSER",
"TEXT SEARCH TEMPLATE",
"TYPE",
"VIEW"
) != null) {
parseIdentifier();
parseKeyword("IS");
parseStringLiteral();
return IGNORE;
}
// TODO: (PostgreSQL)
// AGGREGATE, CAST, FUNCTION, OPERATOR, OPERATOR CLASS, OPERATOR FAMILY
// Ignored object comments with arguments
// https://www.postgresql.org/docs/10/static/sql-comment.html
else if (parseKeywordIf("CONSTRAINT")) {
parseIdentifier();
parseKeyword("ON");
parseKeywordIf("DOMAIN");
parseIdentifier();
parseKeyword("IS");
parseStringLiteral();
return IGNORE;
}
else if (parseAndGetKeywordIf(
"POLICY",
"RULE",
"TRIGGER"
) != null) {
parseIdentifier();
parseKeyword("ON");
parseIdentifier();
parseKeyword("IS");
parseStringLiteral();
return IGNORE;
}
else if (parseKeywordIf("TRANSFORM FOR")) {
parseIdentifier();
parseKeyword("LANGUAGE");
parseIdentifier();
parseKeyword("IS");
parseStringLiteral();
return IGNORE;
}
else
throw unsupportedClause();
parseKeyword("IS");
return s1.is(parseStringLiteral());
}
private final DDLQuery parseCreate() {
parseKeyword("CREATE");
switch (characterUpper()) {
case 'D':
if (parseKeywordIf("DATABASE"))
return parseCreateDatabase();
else if (parseKeywordIf("DOMAIN"))
return parseCreateDomain();
break;
case 'E':
if (parseKeywordIf("EXTENSION"))
return parseCreateExtension();
break;
case 'F':
if (parseKeywordIf("FORCE VIEW"))
return parseCreateView(false);
else if (parseKeywordIf("FULLTEXT INDEX") && requireUnsupportedSyntax())
return parseCreateIndex(false);
else if (parseKeywordIf("FUNCTION") && requireProEdition())
;
break;
case 'G':
if (parseKeywordIf("GENERATOR"))
return parseCreateSequence();
else if (parseKeywordIf("GLOBAL TEMP TABLE", "GLOBAL TEMPORARY TABLE"))
return parseCreateTable(true);
break;
case 'I':
if (parseKeywordIf("INDEX"))
return parseCreateIndex(false);
break;
case 'O':
if (parseKeywordIf("OR")) {
parseKeyword("REPLACE", "ALTER");
if (parseKeywordIf("TRIGGER") && requireProEdition())
;
else if (parseKeywordIf("VIEW", "FORCE VIEW"))
return parseCreateView(true);
else if (parseKeywordIf("FUNCTION") && requireProEdition())
;
else if (parseKeywordIf("PACKAGE"))
throw notImplemented("CREATE PACKAGE", "https://github.com/jOOQ/jOOQ/issues/9190");
else if (parseKeywordIf("PROC", "PROCEDURE") && requireProEdition())
;
else
throw expected("FUNCTION", "PACKAGE", "PROCEDURE", "TRIGGER", "VIEW");
}
break;
case 'P':
if (parseKeywordIf("PACKAGE"))
throw notImplemented("CREATE PACKAGE", "https://github.com/jOOQ/jOOQ/issues/9190");
else if (parseKeywordIf("PROC", "PROCEDURE") && requireProEdition())
;
break;
case 'R':
if (parseKeywordIf("ROLE"))
throw notImplemented("CREATE ROLE", "https://github.com/jOOQ/jOOQ/issues/10167");
break;
case 'S':
if (parseKeywordIf("SCHEMA"))
return parseCreateSchema();
else if (parseKeywordIf("SEQUENCE"))
return parseCreateSequence();
else if (parseKeywordIf("SPATIAL INDEX") && requireUnsupportedSyntax())
return parseCreateIndex(false);
else if (parseKeywordIf("SYNONYM"))
throw notImplemented("CREATE SYNONYM", "https://github.com/jOOQ/jOOQ/issues/9574");
break;
case 'T':
if (parseKeywordIf("TABLE"))
return parseCreateTable(false);
else if (parseKeywordIf("TEMP TABLE", "TEMPORARY TABLE"))
return parseCreateTable(true);
else if (parseKeywordIf("TRIGGER") && requireProEdition())
;
else if (parseKeywordIf("TYPE"))
return parseCreateType();
else if (parseKeywordIf("TABLESPACE"))
throw notImplemented("CREATE TABLESPACE");
break;
case 'U':
if (parseKeywordIf("UNIQUE INDEX"))
return parseCreateIndex(true);
else if (parseKeywordIf("USER"))
throw notImplemented("CREATE USER", "https://github.com/jOOQ/jOOQ/issues/10167");
break;
case 'V':
if (parseKeywordIf("VIEW"))
return parseCreateView(false);
else if (parseKeywordIf("VIRTUAL") && parseKeyword("TABLE"))
return parseCreateTable(false);
break;
}
throw expected(
"FUNCTION",
"GENERATOR",
"GLOBAL TEMPORARY TABLE",
"INDEX",
"OR ALTER",
"OR REPLACE",
"PROCEDURE",
"SCHEMA",
"SEQUENCE",
"TABLE",
"TEMPORARY TABLE",
"TRIGGER",
"TYPE",
"UNIQUE INDEX",
"VIEW"
);
}
private final Query parseAlter() {
parseKeyword("ALTER");
switch (characterUpper()) {
case 'D':
if (parseKeywordIf("DATABASE"))
return parseAlterDatabase();
else if (parseKeywordIf("DOMAIN"))
return parseAlterDomain();
break;
case 'E':
if (parseKeywordIf("EXTENSION"))
throw notImplemented("ALTER EXTENSION");
break;
case 'F':
if (parseKeywordIf("FUNCTION"))
throw notImplemented("ALTER FUNCTION", "https://github.com/jOOQ/jOOQ/issues/9190");
break;
case 'I':
if (parseKeywordIf("INDEX"))
return parseAlterIndex();
break;
case 'P':
if (parseKeywordIf("PACKAGE"))
throw notImplemented("ALTER PACKAGE", "https://github.com/jOOQ/jOOQ/issues/9190");
else if (parseKeywordIf("PROCEDURE"))
throw notImplemented("ALTER PROCEDURE", "https://github.com/jOOQ/jOOQ/issues/9190");
break;
case 'R':
if (parseKeywordIf("ROLE"))
throw notImplemented("ALTER ROLE", "https://github.com/jOOQ/jOOQ/issues/10167");
break;
case 'S':
if (parseKeywordIf("SCHEMA"))
return parseAlterSchema();
else if (parseKeywordIf("SEQUENCE"))
return parseAlterSequence();
else if (parseKeywordIf("SESSION"))
return parseAlterSession();
else if (parseKeywordIf("SYNONYM"))
throw notImplemented("ALTER SYNONYM", "https://github.com/jOOQ/jOOQ/issues/9574");
break;
case 'T':
if (parseKeywordIf("TABLE"))
return parseAlterTable();
else if (parseKeywordIf("TYPE"))
return parseAlterType();
else if (parseKeywordIf("TABLESPACE"))
throw notImplemented("ALTER TABLESPACE");
else if (parseKeywordIf("TRIGGER"))
throw notImplemented("ALTER TRIGGER", "https://github.com/jOOQ/jOOQ/issues/6956");
break;
case 'U':
if (parseKeywordIf("USER"))
throw notImplemented("ALTER USER", "https://github.com/jOOQ/jOOQ/issues/10167");
break;
case 'V':
if (parseKeywordIf("VIEW"))
return parseAlterView();
break;
}
throw expected("DOMAIN", "INDEX", "SCHEMA", "SEQUENCE", "SESSION", "TABLE", "TYPE", "VIEW");
}
private final DDLQuery parseDrop() {
parseKeyword("DROP");
switch (characterUpper()) {
case 'D':
if (parseKeywordIf("DATABASE"))
return parseDropDatabase();
else if (parseKeywordIf("DOMAIN"))
return parseDropDomain();
break;
case 'E':
if (parseKeywordIf("EXTENSION"))
return parseDropExtension();
break;
case 'F':
if (parseKeywordIf("FUNCTION") && requireProEdition())
;
break;
case 'G':
if (parseKeywordIf("GENERATOR"))
return parseDropSequence();
break;
case 'I':
if (parseKeywordIf("INDEX"))
return parseDropIndex();
break;
case 'P':
if (parseKeywordIf("PACKAGE"))
throw notImplemented("DROP PACKAGE", "https://github.com/jOOQ/jOOQ/issues/9190");
else if (parseKeywordIf("PROC", "PROCEDURE") && requireProEdition())
;
break;
case 'R':
if (parseKeywordIf("ROLE"))
throw notImplemented("DROP ROLE", "https://github.com/jOOQ/jOOQ/issues/10167");
break;
case 'S':
if (parseKeywordIf("SEQUENCE"))
return parseDropSequence();
else if (parseKeywordIf("SCHEMA"))
return parseDropSchema();
break;
case 'T':
if (parseKeywordIf("TABLE"))
return parseDropTable(false);
else if (parseKeywordIf("TEMPORARY TABLE"))
return parseDropTable(true);
else if (parseKeywordIf("TRIGGER") && requireProEdition())
;
else if (parseKeywordIf("TYPE"))
return parseDropType();
else if (parseKeywordIf("TABLESPACE"))
throw notImplemented("DROP TABLESPACE");
break;
case 'U':
if (parseKeywordIf("USER"))
throw notImplemented("DROP USER", "https://github.com/jOOQ/jOOQ/issues/10167");
break;
case 'V':
if (parseKeywordIf("VIEW"))
return parseDropView();
break;
}
throw expected(
"GENERATOR",
"FUNCTION",
"INDEX",
"PROCEDURE",
"SCHEMA",
"SEQUENCE",
"TABLE",
"TEMPORARY TABLE",
"TRIGGER",
"TYPE",
"VIEW"
);
}
private final Truncate<?> parseTruncate() {
parseKeyword("TRUNCATE");
parseKeyword("TABLE");
Table<?> table = parseTableName();
boolean continueIdentity = parseKeywordIf("CONTINUE IDENTITY");
boolean restartIdentity = !continueIdentity && parseKeywordIf("RESTART IDENTITY");
boolean cascade = parseKeywordIf("CASCADE");
boolean restrict = !cascade && parseKeywordIf("RESTRICT");
TruncateIdentityStep<?> step1 = dsl.truncate(table);
TruncateCascadeStep<?> step2 =
continueIdentity
? step1.continueIdentity()
: restartIdentity
? step1.restartIdentity()
: step1;
TruncateFinalStep<?> step3 =
cascade
? step2.cascade()
: restrict
? step2.restrict()
: step2;
return step3;
}
private final DDLQuery parseGrant() {
parseKeyword("GRANT");
Privilege privilege = parsePrivilege();
List<Privilege> privileges = null;
while (parseIf(',')) {
if (privileges == null) {
privileges = new ArrayList<>();
privileges.add(privilege);
}
privileges.add(parsePrivilege());
}
parseKeyword("ON");
parseKeywordIf("TABLE");
Table<?> table = parseTableName();
parseKeyword("TO");
User user = parseKeywordIf("PUBLIC") ? null : parseUser();
GrantOnStep s1 = privileges == null ? dsl.grant(privilege) : dsl.grant(privileges);
GrantToStep s2 = s1.on(table);
GrantWithGrantOptionStep s3 = user == null ? s2.toPublic() : s2.to(user);
return parseKeywordIf("WITH GRANT OPTION")
? s3.withGrantOption()
: s3;
}
private final DDLQuery parseRevoke() {
parseKeyword("REVOKE");
boolean grantOptionFor = parseKeywordIf("GRANT OPTION FOR");
Privilege privilege = parsePrivilege();
List<Privilege> privileges = null;
while (parseIf(',')) {
if (privileges == null) {
privileges = new ArrayList<>();
privileges.add(privilege);
}
privileges.add(parsePrivilege());
}
parseKeyword("ON");
parseKeywordIf("TABLE");
Table<?> table = parseTableName();
RevokeOnStep s1 = grantOptionFor
? privileges == null
? dsl.revokeGrantOptionFor(privilege)
: dsl.revokeGrantOptionFor(privileges)
: privileges == null
? dsl.revoke(privilege)
: dsl.revoke(privileges);
parseKeyword("FROM");
User user = parseKeywordIf("PUBLIC") ? null : parseUser();
RevokeFromStep s2 = s1.on(table);
return user == null ? s2.fromPublic() : s2.from(user);
}
private final Query parseExec() {
parseKeyword("EXEC");
if (parseKeywordIf("SP_RENAME")) {
if (parseKeywordIf("@OBJNAME"))
parse('=');
Name oldName = dsl.parser().parseName(parseStringLiteral());
parse(',');
if (parseKeywordIf("@NEWNAME"))
parse('=');
Name newName = dsl.parser().parseName(parseStringLiteral());
String objectType = "TABLE";
if (parseIf(',')) {
if (parseKeywordIf("@OBJTYPE"))
parse('=');
if (!parseKeywordIf("NULL"))
objectType = parseStringLiteral();
}
if ("TABLE".equalsIgnoreCase(objectType))
return dsl.alterTable(oldName).renameTo(newName.unqualifiedName());
else if ("INDEX".equalsIgnoreCase(objectType))
return dsl.alterIndex(oldName).renameTo(newName.unqualifiedName());
else if ("COLUMN".equalsIgnoreCase(objectType))
return dsl.alterTable(oldName.qualifier()).renameColumn(oldName.unqualifiedName()).to(newName.unqualifiedName());
else
throw exception("Unsupported object type: " + objectType);
}
else {
throw unsupportedClause();
}
}
private final Block parseBlock(boolean allowDeclareSection) {
List<Statement> statements = new ArrayList<>();
if (allowDeclareSection && parseKeywordIf("DECLARE") && requireProEdition())
;
else
parseKeywordIf("EXECUTE BLOCK AS");
parseKeyword("BEGIN");
if (!(parseKeywordIf("NOT") && parseKeyword("ATOMIC")))
parseKeywordIf("ATOMIC");
statements.addAll(parseStatements("END"));
parseKeyword("END");
parseIf(';');
return dsl.begin(statements);
}
private final void parseSemicolonAfterNonBlocks(Statement result) {
if (!(result instanceof Block))
parseIf(';');
else if (result instanceof BlockImpl && !((BlockImpl) result).alwaysWrapInBeginEnd)
parseIf(';');
}
private final Statement parseStatementAndSemicolon() {
Statement result = parseStatementAndSemicolonIf();
if (result == null)
throw expected("Statement");
return result;
}
final Statement parseStatementAndSemicolonIf() {
Statement result = parseStatement();
parseSemicolonAfterNonBlocks(result);
return result;
}
private final List<Statement> parseStatements(String... peek) {
List<Statement> statements = new ArrayList<>();
for (;;) {
Statement parsed;
Statement stored;
stored = parsed = parseStatement();
if (parsed == null)
break;
statements.add(stored);
parseSemicolonAfterNonBlocks(parsed);
if (peekKeyword(peek))
break;
}
return statements;
}
private final Block parseDo() {
parseKeyword("DO");
return (Block) dsl.parser().parseQuery(parseStringLiteral());
}
private final Statement parseStatement() {
switch (characterUpper()) {
case 'C':
if (peekKeyword("CALL") && requireProEdition())
;
else if (peekKeyword("CONTINUE") && requireProEdition())
;
break;
case 'D':
if (peekKeyword("DECLARE") && requireProEdition())
;
else if (peekKeyword("DEFINE") && requireProEdition())
;
break;
case 'E':
if (peekKeyword("EXECUTE PROCEDURE", "EXEC") && requireProEdition())
;
if (peekKeyword("EXECUTE") && !peekKeyword("EXECUTE BLOCK") && requireProEdition())
;
else if (peekKeyword("EXIT") && requireProEdition())
;
break;
case 'F':
if (peekKeyword("FOR") && requireProEdition())
;
break;
case 'G':
if (peekKeyword("GOTO") && requireProEdition())
;
break;
case 'I':
if (peekKeyword("IF") && requireProEdition())
;
else if (peekKeyword("ITERATE") && requireProEdition())
;
break;
case 'L':
if (peekKeyword("LEAVE") && requireProEdition())
;
else if (peekKeyword("LET") && requireProEdition())
;
else if (peekKeyword("LOOP") && requireProEdition())
;
break;
case 'N':
if (peekKeyword("NULL"))
return parseNullStatement();
break;
case 'R':
if (peekKeyword("REPEAT") && requireProEdition())
;
else if (peekKeyword("RETURN") && requireProEdition())
;
else if (peekKeyword("RAISE") && requireProEdition())
;
break;
case 'S':
if (peekKeyword("SET") && requireProEdition())
;
else if (peekKeyword("SIGNAL") && requireProEdition())
;
break;
case 'W':
if (peekKeyword("WHILE") && requireProEdition())
;
break;
}
return parseQuery(false, false);
}
// -----------------------------------------------------------------------------------------------------------------
// Statement parsing
// -----------------------------------------------------------------------------------------------------------------
private final Statement parseNullStatement() {
parseKeyword("NULL");
return new NullStatement();
}
// -----------------------------------------------------------------------------------------------------------------
// Statement clause parsing
// -----------------------------------------------------------------------------------------------------------------
private final Privilege parsePrivilege() {
if (parseKeywordIf("SELECT"))
return privilege(K_SELECT);
else if (parseKeywordIf("INSERT"))
return privilege(K_INSERT);
else if (parseKeywordIf("UPDATE"))
return privilege(K_UPDATE);
else if (parseKeywordIf("DELETE"))
return privilege(K_DELETE);
else
throw expected("DELETE", "INSERT", "SELECT", "UPDATE");
}
private final User parseUser() {
return user(parseName());
}
private final DDLQuery parseCreateView(boolean orReplace) {
boolean ifNotExists = !orReplace && parseKeywordIf("IF NOT EXISTS");
Table<?> view = parseTableName();
Field<?>[] fields = EMPTY_FIELD;
if (parseIf('(')) {
fields = parseFieldNames().toArray(fields);
parse(')');
}
parseKeyword("AS");
Select<?> select = parseWithOrSelect();
int degree = Tools.degree(select);
if (fields.length > 0 && fields.length != degree)
throw exception("Select list size (" + degree + ") must match declared field size (" + fields.length + ")");
return ifNotExists
? dsl.createViewIfNotExists(view, fields).as(select)
: orReplace
? dsl.createOrReplaceView(view, fields).as(select)
: dsl.createView(view, fields).as(select);
}
private final DDLQuery parseCreateExtension() {
parseKeywordIf("IF NOT EXISTS");
parseIdentifier();
parseKeywordIf("WITH");
if (parseKeywordIf("SCHEMA"))
parseIdentifier();
if (parseKeywordIf("VERSION"))
if (parseIdentifierIf() == null)
parseStringLiteral();
if (parseKeywordIf("FROM"))
if (parseIdentifierIf() == null)
parseStringLiteral();
parseKeywordIf("CASCADE");
return IGNORE;
}
private final DDLQuery parseDropExtension() {
parseKeywordIf("IF EXISTS");
parseIdentifiers();
if (!parseKeywordIf("CASCADE"))
parseKeywordIf("RESTRICT");
return IGNORE;
}
private final DDLQuery parseAlterView() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Table<?> oldName = parseTableName();
if (parseKeywordIf("RENAME")) {
if (!parseKeywordIf("AS"))
parseKeyword("TO");
Table<?> newName = parseTableName();
return ifExists
? dsl.alterViewIfExists(oldName).renameTo(newName)
: dsl.alterView(oldName).renameTo(newName);
}
else if (parseKeywordIf("OWNER TO") && parseUser() != null)
return IGNORE;
else if (parseKeywordIf("SET"))
return dsl.alterView(oldName).comment(parseOptionsDescription());
else
throw expected("OWNER TO", "RENAME", "SET");
}
private final Comment parseOptionsDescription() {
parseKeyword("OPTIONS");
parse('(');
parseKeyword("DESCRIPTION");
parse('=');
Comment comment = parseComment();
parse(')');
return comment;
}
private final DDLQuery parseDropView() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Table<?> tableName = parseTableName();
DropViewFinalStep s1;
s1 = ifExists
? dsl.dropViewIfExists(tableName)
: dsl.dropView(tableName);
return s1;
}
private final DDLQuery parseCreateSequence() {
boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
Sequence<?> schemaName = parseSequenceName();
CreateSequenceFlagsStep s = ifNotExists
? dsl.createSequenceIfNotExists(schemaName)
: dsl.createSequence(schemaName);
boolean restart = false;
boolean startWith = false;
boolean incrementBy = false;
boolean minvalue = false;
boolean maxvalue = false;
boolean cycle = false;
boolean cache = false;
for (;;) {
Field<Long> field;
if (!startWith && (startWith |= (field = parseSequenceStartWithIf()) != null))
s = s.startWith(field);
else if (!incrementBy && (incrementBy |= (field = parseSequenceIncrementByIf()) != null))
s = s.incrementBy(field);
else if (!minvalue && (minvalue |= (field = parseSequenceMinvalueIf()) != null))
s = s.minvalue(field);
else if (!minvalue && (minvalue |= parseSequenceNoMinvalueIf()))
s = s.noMinvalue();
else if (!maxvalue && (maxvalue |= (field = parseSequenceMaxvalueIf()) != null))
s = s.maxvalue(field);
else if (!maxvalue && (maxvalue |= parseSequenceNoMaxvalueIf()))
s = s.noMaxvalue();
else if (!cycle && (cycle |= parseKeywordIf("CYCLE")))
s = s.cycle();
else if (!cycle && (cycle |= parseSequenceNoCycleIf()))
s = s.noCycle();
else if (!cache && (cache |= (field = parseSequenceCacheIf()) != null))
s = s.cache(field);
else if (!cache && (cache |= parseSequenceNoCacheIf()))
s = s.noCache();
else
break;
}
return s;
}
private final DDLQuery parseAlterSequence() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Sequence<?> sequenceName = parseSequenceName();
AlterSequenceStep s = ifExists
? dsl.alterSequenceIfExists(sequenceName)
: dsl.alterSequence(sequenceName);
if (parseKeywordIf("RENAME")) {
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return s.renameTo(parseSequenceName());
}
else if (parseKeywordIf("OWNER TO") && parseUser() != null) {
return IGNORE;
}
else {
boolean found = false;
boolean restart = false;
boolean startWith = false;
boolean incrementBy = false;
boolean minvalue = false;
boolean maxvalue = false;
boolean cycle = false;
boolean cache = false;
AlterSequenceFlagsStep s1 = s;
while (true) {
Field<Long> field;
if (!startWith && (startWith |= (field = parseSequenceStartWithIf()) != null))
s1 = s1.startWith(field);
else if (!incrementBy && (incrementBy |= (field = parseSequenceIncrementByIf()) != null))
s1 = s1.incrementBy(field);
else if (!minvalue && (minvalue |= (field = parseSequenceMinvalueIf()) != null))
s1 = s1.minvalue(field);
else if (!minvalue && (minvalue |= parseSequenceNoMinvalueIf()))
s1 = s1.noMinvalue();
else if (!maxvalue && (maxvalue |= (field = parseSequenceMaxvalueIf()) != null))
s1 = s1.maxvalue(field);
else if (!maxvalue && (maxvalue |= parseSequenceNoMaxvalueIf()))
s1 = s1.noMaxvalue();
else if (!cycle && (cycle |= parseKeywordIf("CYCLE")))
s1 = s1.cycle();
else if (!cycle && (cycle |= parseSequenceNoCycleIf()))
s1 = s1.noCycle();
else if (!cache && (cache |= (field = parseSequenceCacheIf()) != null))
s1 = s1.cache(field);
else if (!cache && (cache |= parseSequenceNoCacheIf()))
s1 = s1.noCache();
else if (!restart && (restart |= parseKeywordIf("RESTART"))) {
if (parseKeywordIf("WITH"))
s1 = s1.restartWith(parseUnsignedIntegerOrBindVariable());
else
s1 = s1.restart();
}
else
break;
found = true;
}
if (!found)
throw expected(
"CACHE",
"CYCLE",
"INCREMENT BY",
"MAXVALUE",
"MINVALUE",
"NO CACHE",
"NO CYCLE",
"NO MAXVALUE",
"NO MINVALUE",
"OWNER TO",
"RENAME TO",
"RESTART",
"START WITH"
);
return s1;
}
}
private final boolean parseSequenceNoCacheIf() {
return parseKeywordIf("NO CACHE") || parseKeywordIf("NOCACHE");
}
private final Field<Long> parseSequenceCacheIf() {
return parseKeywordIf("CACHE") && (parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
}
private final boolean parseSequenceNoCycleIf() {
return parseKeywordIf("NO CYCLE") || parseKeywordIf("NOCYCLE");
}
private final boolean parseSequenceNoMaxvalueIf() {
return parseKeywordIf("NO MAXVALUE") || parseKeywordIf("NOMAXVALUE");
}
private final Field<Long> parseSequenceMaxvalueIf() {
return parseKeywordIf("MAXVALUE") && (parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
}
private final boolean parseSequenceNoMinvalueIf() {
return parseKeywordIf("NO MINVALUE") || parseKeywordIf("NOMINVALUE");
}
private final Field<Long> parseSequenceMinvalueIf() {
return parseKeywordIf("MINVALUE") && (parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
}
private final Field<Long> parseSequenceIncrementByIf() {
return parseKeywordIf("INCREMENT") && (parseKeywordIf("BY") || parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
}
private final Field<Long> parseSequenceStartWithIf() {
return parseKeywordIf("START") && (parseKeywordIf("WITH") || parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
}
private final Query parseAlterSession() {
parseKeyword("SET CURRENT_SCHEMA");
parse('=');
return dsl.setSchema(parseSchemaName());
}
private final DDLQuery parseSetGenerator() {
Sequence<?> sequenceName = parseSequenceName();
parseKeyword("TO");
return dsl.alterSequence((Sequence) sequenceName).restartWith(parseUnsignedInteger());
}
private final DDLQuery parseDropSequence() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Sequence<?> sequenceName = parseSequenceName();
return ifExists
? dsl.dropSequenceIfExists(sequenceName)
: dsl.dropSequence(sequenceName);
}
private final DDLQuery parseCreateTable(boolean temporary) {
boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
Table<?> tableName = DSL.table(parseTableName().getQualifiedName());
if (parseKeywordIf("USING"))
parseIdentifier();
CreateTableCommentStep commentStep;
CreateTableStorageStep storageStep;
List<Field<?>> fields = new ArrayList<>();
List<Constraint> constraints = new ArrayList<>();
List<Index> indexes = new ArrayList<>();
boolean primary = false;
boolean identity = false;
// Three valued boolean:
// null: Possibly CTAS
// true: Definitely CTAS
// false: Definitely not CTAS
Boolean ctas = null;
if (parseIf('(')) {
columnLoop:
do {
int p = position();
ConstraintTypeStep constraint = parseConstraintNameSpecification();
if (parsePrimaryKeyClusteredNonClusteredKeywordIf()) {
if (primary)
throw exception("Duplicate primary key specification");
primary = true;
constraints.add(parsePrimaryKeySpecification(constraint));
continue columnLoop;
}
else if (parseKeywordIf("UNIQUE")) {
if (!parseKeywordIf("KEY"))
parseKeywordIf("INDEX");
// [#9132] Avoid parsing "using" as an identifier
parseUsingIndexTypeIf();
// [#7268] MySQL has some legacy syntax where an index name
// can override a constraint name
Name index = parseIdentifierIf();
if (index != null)
constraint = constraint(index);
constraints.add(parseUniqueSpecification(constraint));
continue columnLoop;
}
else if (parseKeywordIf("FOREIGN KEY")) {
constraints.add(parseForeignKeySpecification(constraint));
continue columnLoop;
}
else if (parseKeywordIf("CHECK")) {
constraints.add(parseCheckSpecification(constraint));
continue columnLoop;
}
else if (constraint == null && parseIndexOrKeyIf()) {
parseUsingIndexTypeIf();
int p2 = position();
// [#7348] [#7651] [#9132] Look ahead if the next tokens
// indicate a MySQL index definition
if (parseIf('(') || (parseIdentifierIf() != null
&& parseUsingIndexTypeIf()
&& parseIf('('))) {
position(p2);
indexes.add(parseIndexSpecification(tableName));
parseUsingIndexTypeIf();
continue columnLoop;
}
else {
position(p);
}
}
else if (constraint != null)
throw expected("CHECK", "CONSTRAINT", "FOREIGN KEY", "INDEX", "KEY", "PRIMARY KEY", "UNIQUE");
Name fieldName = parseIdentifier();
if (ctas == null)
ctas = peek(',') || peek(')');
// If only we had multiple return values or destructuring...
ParseInlineConstraints inlineConstraints = parseInlineConstraints(
fieldName,
!TRUE.equals(ctas) ? parseDataType() : SQLDataType.OTHER,
constraints,
primary,
identity
);
primary = inlineConstraints.primary;
identity = inlineConstraints.identity;
if (ctas)
fields.add(field(fieldName));
else
fields.add(field(fieldName, inlineConstraints.type, inlineConstraints.fieldComment));
}
while (parseIf(','));
if (fields.isEmpty())
throw expected("At least one column");
parse(')');
}
else
ctas = true;
CreateTableColumnStep columnStep = ifNotExists
? temporary
? dsl.createTemporaryTableIfNotExists(tableName)
: dsl.createTableIfNotExists(tableName)
: temporary
? dsl.createTemporaryTable(tableName)
: dsl.createTable(tableName);
if (!fields.isEmpty())
columnStep = columnStep.columns(fields);
if (TRUE.equals(ctas) && parseKeyword("AS") ||
!FALSE.equals(ctas) && parseKeywordIf("AS")) {
boolean previousMetaLookupsForceIgnore = metaLookupsForceIgnore();
CreateTableWithDataStep withDataStep = columnStep.as((Select<Record>) metaLookupsForceIgnore(false).parseQuery(true, true));
metaLookupsForceIgnore(previousMetaLookupsForceIgnore);
commentStep =
parseKeywordIf("WITH DATA")
? withDataStep.withData()
: parseKeywordIf("WITH NO DATA")
? withDataStep.withNoData()
: withDataStep;
}
else {
CreateTableConstraintStep constraintStep = constraints.isEmpty()
? columnStep
: columnStep.constraints(constraints);
CreateTableOnCommitStep onCommitStep = indexes.isEmpty()
? constraintStep
: constraintStep.indexes(indexes);
// [#6133] TODO Support this also with CTAS
if (temporary && parseKeywordIf("ON COMMIT")) {
if (parseKeywordIf("DELETE ROWS"))
commentStep = onCommitStep.onCommitDeleteRows();
else if (parseKeywordIf("DROP"))
commentStep = onCommitStep.onCommitDrop();
else if (parseKeywordIf("PRESERVE ROWS"))
commentStep = onCommitStep.onCommitPreserveRows();
else
throw unsupportedClause();
}
else
commentStep = onCommitStep;
}
storageStep = commentStep;
List<SQL> storage = new ArrayList<>();
Comment comment = null;
storageLoop:
for (boolean first = true;; first = false) {
boolean optional = first || !parseIf(',');
Keyword keyword = null;
// MySQL storage clauses (see: https://dev.mysql.com/doc/refman/5.7/en/create-table.html)
if ((keyword = parseAndGetKeywordIf("AUTO_INCREMENT")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf("AVG_ROW_LENGTH")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf("CHARACTER SET")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
}
else if ((keyword = parseAndGetKeywordIf("DEFAULT CHARACTER SET")) != null
|| (keyword = parseAndGetKeywordIf("DEFAULT CHARSET")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
}
else if ((keyword = parseAndGetKeywordIf("CHECKSUM")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseZeroOne()));
}
else if ((keyword = parseAndGetKeywordIf("COLLATE")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
}
else if ((keyword = parseAndGetKeywordIf("DEFAULT COLLATE")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
}
// [#10164] In a statement batch, this could already be the next statement
else if (!peekKeyword("COMMENT ON") && parseKeywordIf("COMMENT")) {
parseIf('=');
comment = parseComment();
}
else if (peekKeyword("OPTIONS")) {
comment = parseOptionsDescription();
}
else if ((keyword = parseAndGetKeywordIf("COMPRESSION")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral()));
}
else if ((keyword = parseAndGetKeywordIf("CONNECTION")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral()));
}
else if ((keyword = parseAndGetKeywordIf("DATA DIRECTORY")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral()));
}
else if ((keyword = parseAndGetKeywordIf("INDEX DIRECTORY")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral()));
}
else if ((keyword = parseAndGetKeywordIf("DELAY_KEY_WRITE")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseZeroOne()));
}
else if ((keyword = parseAndGetKeywordIf("ENCRYPTION")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral()));
}
else if ((keyword = parseAndGetKeywordIf("ENGINE")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
}
else if ((keyword = parseAndGetKeywordIf("INSERT_METHOD")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseAndGetKeyword("NO", "FIRST", "LAST")));
}
else if ((keyword = parseAndGetKeywordIf("KEY_BLOCK_SIZE")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf("MAX_ROWS")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf("MIN_ROWS")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf("PACK_KEYS")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseZeroOneDefault()));
}
else if ((keyword = parseAndGetKeywordIf("PASSWORD")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseStringLiteral()));
}
else if ((keyword = parseAndGetKeywordIf("ROW_FORMAT")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseAndGetKeyword("DEFAULT", "DYNAMIC", "FIXED", "COMPRESSED", "REDUNDANT", "COMPACT")));
}
else if ((keyword = parseAndGetKeywordIf("STATS_AUTO_RECALC")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseZeroOneDefault()));
}
else if ((keyword = parseAndGetKeywordIf("STATS_PERSISTENT")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseZeroOneDefault()));
}
else if ((keyword = parseAndGetKeywordIf("STATS_SAMPLE_PAGES")) != null) {
parseIf('=');
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(Sign.NONE)));
}
else if ((keyword = parseAndGetKeywordIf("TABLESPACE")) != null) {
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
if ((keyword = parseAndGetKeywordIf("STORAGE")) != null)
storage.add(sql("{0} {1}", keyword, parseAndGetKeyword("DISK", "MEMORY", "DEFAULT")));
}
else if ((keyword = parseAndGetKeywordIf("UNION")) != null) {
parseIf('=');
parse('(');
storage.add(sql("{0} ({1})", keyword, list(parseIdentifiers())));
parse(')');
}
else if (optional)
break storageLoop;
else
throw expected("storage clause after ','");
}
if (comment != null)
storageStep = commentStep.comment(comment);
if (storage.size() > 0)
return storageStep.storage(new SQLConcatenationImpl(storage.toArray(EMPTY_QUERYPART)));
else
return storageStep;
}
private static final class ParseInlineConstraints {
final DataType<?> type;
final Comment fieldComment;
final boolean primary;
final boolean identity;
ParseInlineConstraints(DataType<?> type, Comment fieldComment, boolean primary, boolean identity) {
this.type = type;
this.fieldComment = fieldComment;
this.primary = primary;
this.identity = identity;
}
}
private final ParseInlineConstraints parseInlineConstraints(
Name fieldName,
DataType<?> type,
List<? super Constraint> constraints,
boolean primary,
boolean identity
) {
boolean nullable = false;
boolean defaultValue = false;
boolean onUpdate = false;
boolean unique = false;
boolean comment = false;
Comment fieldComment = null;
identity |= type.identity();
for (;;) {
if (!nullable) {
if (parseKeywordIf("NULL")) {
type = type.nullable(true);
nullable = true;
continue;
}
else if (parseNotNullOptionalEnable()) {
type = type.nullable(false);
nullable = true;
continue;
}
}
if (!defaultValue) {
if (!identity && parseKeywordIf("IDENTITY")) {
if (parseIf('(')) {
parseSignedInteger();
parse(',');
parseSignedInteger();
parse(')');
}
type = type.identity(true);
defaultValue = true;
identity = true;
continue;
}
else if (parseKeywordIf("DEFAULT")) {
// [#10963] Special case nextval('<id>_seq'::regclass)
if (parseSerialIf()) {
type = type.identity(true);
}
else {
// TODO: [#10116] Support this clause also in the jOOQ API
parseKeywordIf("ON NULL");
type = type.defaultValue((Field) toField(parseConcat(null)));
// TODO: [#10115] Support this clause also in the jOOQ API
parseKeywordIf("WITH VALUES");
defaultValue = true;
}
continue;
}
else if (!identity && parseKeywordIf("GENERATED")) {
if (!parseKeywordIf("ALWAYS")) {
parseKeyword("BY DEFAULT");
// TODO: Ignored keyword from Oracle
parseKeywordIf("ON NULL");
}
parseKeyword("AS IDENTITY");
// TODO: Ignored identity options from Oracle
if (parseIf('(')) {
boolean identityOption = false;
for (;;) {
if (identityOption)
parseIf(',');
if (parseKeywordIf("START WITH")) {
if (!parseKeywordIf("LIMIT VALUE"))
parseUnsignedIntegerOrBindVariable();
identityOption = true;
continue;
}
else if (parseKeywordIf("INCREMENT BY")
|| parseKeywordIf("MAXVALUE")
|| parseKeywordIf("MINVALUE")
|| parseKeywordIf("CACHE")) {
parseUnsignedIntegerOrBindVariable();
identityOption = true;
continue;
}
else if (parseKeywordIf("NOMAXVALUE")
|| parseKeywordIf("NOMINVALUE")
|| parseKeywordIf("CYCLE")
|| parseKeywordIf("NOCYCLE")
|| parseKeywordIf("NOCACHE")
|| parseKeywordIf("ORDER")
|| parseKeywordIf("NOORDER")) {
identityOption = true;
continue;
}
if (identityOption)
break;
else
throw unsupportedClause();
}
parse(')');
}
type = type.identity(true);
defaultValue = true;
identity = true;
continue;
}
}
if (!onUpdate) {
if (parseKeywordIf("ON UPDATE")) {
// [#6132] TODO: Support this feature in the jOOQ DDL API
parseConcat(null);
onUpdate = true;
continue;
}
}
ConstraintTypeStep inlineConstraint = parseConstraintNameSpecification();
if (!unique) {
if (!primary && parsePrimaryKeyClusteredNonClusteredKeywordIf()) {
if (!parseKeywordIf("CLUSTERED"))
parseKeywordIf("NONCLUSTERED");
constraints.add(parseConstraintEnforcementIf(inlineConstraint == null
? primaryKey(fieldName)
: inlineConstraint.primaryKey(fieldName)));
primary = true;
unique = true;
continue;
}
else if (parseKeywordIf("UNIQUE")) {
if (!parseKeywordIf("KEY"))
parseKeywordIf("INDEX");
constraints.add(parseConstraintEnforcementIf(inlineConstraint == null
? unique(fieldName)
: inlineConstraint.unique(fieldName)));
unique = true;
continue;
}
}
if (parseKeywordIf("CHECK")) {
constraints.add(parseCheckSpecification(inlineConstraint));
continue;
}
if (parseKeywordIf("REFERENCES")) {
constraints.add(parseForeignKeyReferenceSpecification(inlineConstraint, new Field[] { field(fieldName) }));
continue;
}
if (!nullable) {
if (parseKeywordIf("NULL")) {
type = type.nullable(true);
nullable = true;
continue;
}
else if (parseNotNullOptionalEnable()) {
type = type.nullable(false);
nullable = true;
continue;
}
}
if (inlineConstraint != null)
throw expected("CHECK", "NOT NULL", "NULL", "PRIMARY KEY", "REFERENCES", "UNIQUE");
if (!identity) {
if (parseKeywordIf("AUTO_INCREMENT") ||
parseKeywordIf("AUTOINCREMENT")) {
type = type.identity(true);
identity = true;
continue;
}
}
if (!comment) {
// [#10164] In a statement batch, this could already be the next statement
if (!peekKeyword("COMMENT ON") && parseKeywordIf("COMMENT")) {
fieldComment = parseComment();
continue;
}
else if (peekKeyword("OPTIONS")) {
fieldComment = parseOptionsDescription();
continue;
}
}
break;
}
return new ParseInlineConstraints(type, fieldComment, primary, identity);
}
private final boolean parseSerialIf() {
int i = position();
String s;
if (parseFunctionNameIf("NEXTVAL")
&& parseIf('(')
&& ((s = parseStringLiteralIf()) != null)
&& s.toLowerCase().endsWith("_seq")
&& parseIf("::")
&& parseKeywordIf("REGCLASS")
&& parseIf(')'))
return true;
position(i);
return false;
}
private final boolean parsePrimaryKeyClusteredNonClusteredKeywordIf() {
if (!parseKeywordIf("PRIMARY KEY"))
return false;
if (!parseKeywordIf("CLUSTERED"))
parseKeywordIf("NONCLUSTERED");
return true;
}
private final DDLQuery parseCreateType() {
Name name = parseName();
parseKeyword("AS ENUM");
List<String> values = new ArrayList<>();
parse('(');
if (!parseIf(')')) {
do
values.add(parseStringLiteral());
while (parseIf(','));
parse(')');
}
return dsl.createType(name).asEnum(values);
}
private final Index parseIndexSpecification(Table<?> table) {
Name name = parseIdentifierIf();
parseUsingIndexTypeIf();
parse('(');
SortField<?>[] fields = parseSortSpecification().toArray(EMPTY_SORTFIELD);
parse(')');
return Internal.createIndex(name == null ? NO_NAME : name, table, fields, false);
}
private final Constraint parseConstraintEnforcementIf(ConstraintEnforcementStep e) {
boolean deferrable = parseConstraintDeferrableIf();
parseConstraintInitiallyIf();
if (!deferrable)
parseConstraintDeferrableIf();
if ((parseKeywordIf("ENABLE") || parseKeywordIf("ENFORCED")))
return e.enforced();
else if ((parseKeywordIf("DISABLE") || parseKeywordIf("NOT ENFORCED")))
return e.notEnforced();
else
return e;
}
private final boolean parseConstraintDeferrableIf() {
return parseKeywordIf("DEFERRABLE") || parseKeywordIf("NOT DEFERRABLE");
}
private final boolean parseConstraintInitiallyIf() {
return parseKeywordIf("INITIALLY") && parseKeyword("DEFERRED", "IMMEDIATE");
}
private final Constraint parsePrimaryKeySpecification(ConstraintTypeStep constraint) {
parseUsingIndexTypeIf();
Field<?>[] fieldNames = parseKeyColumnList();
ConstraintEnforcementStep e = constraint == null
? primaryKey(fieldNames)
: constraint.primaryKey(fieldNames);
parseUsingIndexTypeIf();
return parseConstraintEnforcementIf(e);
}
private final Constraint parseUniqueSpecification(ConstraintTypeStep constraint) {
parseUsingIndexTypeIf();
// [#9246] In MySQL, there's a syntax where the unique constraint looks like an index:
// ALTER TABLE t ADD UNIQUE INDEX i (c)
Name constraintName;
if (constraint == null && (constraintName = parseIdentifierIf()) != null)
constraint = constraint(constraintName);
Field<?>[] fieldNames = parseKeyColumnList();
ConstraintEnforcementStep e = constraint == null
? unique(fieldNames)
: constraint.unique(fieldNames);
parseUsingIndexTypeIf();
return parseConstraintEnforcementIf(e);
}
private final Field<?>[] parseKeyColumnList() {
parse('(');
SortField<?>[] fieldExpressions = parseSortSpecification().toArray(EMPTY_SORTFIELD);
parse(')');
Field<?>[] fieldNames = new Field[fieldExpressions.length];
for (int i = 0; i < fieldExpressions.length; i++)
if (fieldExpressions[i].getOrder() != SortOrder.DESC)
fieldNames[i] = ((SortFieldImpl<?>) fieldExpressions[i]).getField();
// [#7899] TODO: Support this in jOOQ
else
throw notImplemented("DESC sorting in constraints");
return fieldNames;
}
private final Constraint parseCheckSpecification(ConstraintTypeStep constraint) {
boolean parens = parseIf('(');
Condition condition = parseCondition();
if (parens)
parse(')');
ConstraintEnforcementStep e = constraint == null
? check(condition)
: constraint.check(condition);
return parseConstraintEnforcementIf(e);
}
private final Constraint parseForeignKeySpecification(ConstraintTypeStep constraint) {
Name constraintName = null;
if ((constraintName = parseIdentifierIf()) != null)
if (constraint == null)
constraint = constraint(constraintName);
parse('(');
Field<?>[] referencing = parseFieldNames().toArray(EMPTY_FIELD);
parse(')');
parseKeyword("REFERENCES");
return parseForeignKeyReferenceSpecification(constraint, referencing);
}
private final Constraint parseForeignKeyReferenceSpecification(ConstraintTypeStep constraint, Field<?>[] referencing) {
Table<?> referencedTable = parseTableName();
Field<?>[] referencedFields = EMPTY_FIELD;
if (parseIf('(')) {
referencedFields = parseFieldNames().toArray(EMPTY_FIELD);
parse(')');
if (referencing.length != referencedFields.length)
throw 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("ON")) {
if (!onDelete && parseKeywordIf("DELETE")) {
onDelete = true;
if (parseKeywordIf("CASCADE"))
e = e.onDeleteCascade();
else if (parseKeywordIf("NO ACTION"))
e = e.onDeleteNoAction();
else if (parseKeywordIf("RESTRICT"))
e = e.onDeleteRestrict();
else if (parseKeywordIf("SET DEFAULT"))
e = e.onDeleteSetDefault();
else if (parseKeywordIf("SET NULL"))
e = e.onDeleteSetNull();
else
throw expected("CASCADE", "NO ACTION", "RESTRICT", "SET DEFAULT", "SET NULL");
}
else if (!onUpdate && parseKeywordIf("UPDATE")) {
onUpdate = true;
if (parseKeywordIf("CASCADE"))
e = e.onUpdateCascade();
else if (parseKeywordIf("NO ACTION"))
e = e.onUpdateNoAction();
else if (parseKeywordIf("RESTRICT"))
e = e.onUpdateRestrict();
else if (parseKeywordIf("SET DEFAULT"))
e = e.onUpdateSetDefault();
else if (parseKeywordIf("SET NULL"))
e = e.onUpdateSetNull();
else
throw expected("CASCADE", "NO ACTION", "RESTRICT", "SET DEFAULT", "SET NULL");
}
else
throw expected("DELETE", "UPDATE");
}
return parseConstraintEnforcementIf(e);
}
private static final Set<String> ALTER_KEYWORDS = new HashSet<>(Arrays.asList("ADD", "ALTER", "COMMENT", "DROP", "MODIFY", "RENAME"));
private final DDLQuery parseAlterTable() {
boolean ifTableExists = parseKeywordIf("IF EXISTS");
Table<?> tableName;
if (peekKeyword("ONLY")) {
// [#7751] ONLY is only supported by PostgreSQL. In other RDBMS, it
// corresponds to a table name.
Name only = parseIdentifier();
int p = position();
if ((tableName = parseTableNameIf()) == null || (
!tableName.getQualifiedName().qualified()
&& tableName.getUnqualifiedName().quoted() == Quoted.UNQUOTED
&& ALTER_KEYWORDS.contains(tableName.getName().toUpperCase()))) {
tableName = table(only);
position(p);
}
}
else {
tableName = parseTableName();
}
AlterTableStep s1 = ifTableExists
? dsl.alterTableIfExists(tableName)
: dsl.alterTable(tableName);
switch (characterUpper()) {
case 'A':
if (parseKeywordIf("ADD"))
return parseAlterTableAdd(s1, tableName);
else if (parseKeywordIf("ALTER"))
if (parseKeywordIf("CONSTRAINT"))
return parseAlterTableAlterConstraint(s1);
else if ((parseKeywordIf("COLUMN") || true))
return parseAlterTableAlterColumn(s1);
break;
case 'C':
// TODO: support all of the storageLoop from the CREATE TABLE statement
if (parseKeywordIf("COMMENT")) {
parseIf('=');
return dsl.commentOnTable(tableName).is(parseStringLiteral());
}
break;
case 'D':
if (parseKeywordIf("DROP")) {
if (parseKeywordIf("CONSTRAINT")) {
return parseCascadeRestrictIf(parseKeywordIf("IF EXISTS")
? s1.dropConstraintIfExists(parseIdentifier())
: s1.dropConstraint(parseIdentifier()));
}
else if (parseKeywordIf("UNIQUE")) {
return parseCascadeRestrictIf(s1.dropUnique(
peek('(')
? unique(parseKeyColumnList())
: constraint(parseIdentifier())));
}
else if (parseKeywordIf("PRIMARY KEY")) {
Name identifier = parseIdentifierIf();
return parseCascadeRestrictIf(identifier == null ? s1.dropPrimaryKey() : s1.dropPrimaryKey(identifier));
}
else if (parseKeywordIf("FOREIGN KEY")) {
return s1.dropForeignKey(parseIdentifier());
}
else if (parseKeywordIf("INDEX")
|| parseKeywordIf("KEY")) {
return dsl.dropIndex(parseIdentifier()).on(tableName);
}
else {
parseKeywordIf("COLUMN");
boolean ifColumnExists = parseKeywordIf("IF EXISTS");
boolean parens = parseIf('(');
Field<?> field = parseFieldName();
List<Field<?>> fields = null;
if (!ifColumnExists) {
while (parseIf(',') || parseKeywordIf("DROP") && (parseKeywordIf("COLUMN") || true)) {
if (fields == null) {
fields = new ArrayList<>();
fields.add(field);
}
fields.add(parseFieldName());
}
}
if (parens)
parse(')');
return parseCascadeRestrictIf(fields == null
? ifColumnExists
? s1.dropColumnIfExists(field)
: s1.dropColumn(field)
: s1.dropColumns(fields)
);
}
}
break;
case 'M':
if (parseKeywordIf("MODIFY"))
if (parseKeywordIf("CONSTRAINT"))
return parseAlterTableAlterConstraint(s1);
else if ((parseKeywordIf("COLUMN") || true))
return parseAlterTableAlterColumn(s1);
break;
case 'O':
if (parseKeywordIf("OWNER TO") && parseUser() != null)
return IGNORE;
break;
case 'R':
if (parseKeywordIf("RENAME")) {
if (parseKeywordIf("AS") || parseKeywordIf("TO")) {
Table<?> newName = parseTableName();
return s1.renameTo(newName);
}
else if (parseKeywordIf("COLUMN")) {
Name oldName = parseIdentifier();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
Name newName = parseIdentifier();
return s1.renameColumn(oldName).to(newName);
}
else if (parseKeywordIf("INDEX")) {
Name oldName = parseIdentifier();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
Name newName = parseIdentifier();
return s1.renameIndex(oldName).to(newName);
}
else if (parseKeywordIf("CONSTRAINT")) {
Name oldName = parseIdentifier();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
Name newName = parseIdentifier();
return s1.renameConstraint(oldName).to(newName);
}
}
break;
case 'S':
if (parseKeywordIf("SET"))
return s1.comment(parseOptionsDescription());
break;
}
throw expected("ADD", "ALTER", "COMMENT", "DROP", "MODIFY", "OWNER TO", "RENAME", "SET");
}
private final AlterTableFinalStep parseCascadeRestrictIf(AlterTableDropStep step) {
boolean cascade = parseKeywordIf("CASCADE");
boolean restrict = !cascade && parseKeywordIf("RESTRICT");
return cascade
? step.cascade()
: restrict
? step.restrict()
: step;
}
private final DDLQuery parseAlterTableAdd(AlterTableStep s1, Table<?> tableName) {
List<FieldOrConstraint> list = new ArrayList<>();
if (parseIndexOrKeyIf()) {
Name name = parseIdentifierIf();
parse('(');
List<SortField<?>> sort = parseSortSpecification();
parse(')');
return name == null
? dsl.createIndex().on(tableName, sort)
: dsl.createIndex(name).on(tableName, sort);
}
if (parseIf('(')) {
do
parseAlterTableAddFieldsOrConstraints(list);
while (parseIf(','));
parse(')');
}
else if (parseKeywordIf("COLUMN IF NOT EXISTS")
|| parseKeywordIf("IF NOT EXISTS")) {
return parseAlterTableAddFieldFirstBeforeLast(s1.addColumnIfNotExists(parseAlterTableAddField(null)));
}
else {
do
parseAlterTableAddFieldsOrConstraints(list);
while (
parseKeywordIf("ADD") ||
parseIf(',') && (parseKeywordIf("ADD") || !peekKeyword("ALTER", "COMMENT", "DROP", "MODIFY", "OWNER TO", "RENAME"))
);
}
if (list.size() == 1)
if (list.get(0) instanceof Constraint)
return s1.add((Constraint) list.get(0));
else
return parseAlterTableAddFieldFirstBeforeLast(s1.add((Field<?>) list.get(0)));
else
return parseAlterTableAddFieldFirstBeforeLast(s1.add(list));
}
private final DDLQuery parseAlterTableAddFieldFirstBeforeLast(AlterTableAddStep step) {
if (parseKeywordIf("FIRST"))
return step.first();
else if (parseKeywordIf("BEFORE"))
return step.before(parseFieldName());
else if (parseKeywordIf("AFTER"))
return step.after(parseFieldName());
else
return step;
}
private final boolean parseIndexOrKeyIf() {
return ((parseKeywordIf("SPATIAL INDEX")
|| parseKeywordIf("SPATIAL KEY")
|| parseKeywordIf("FULLTEXT INDEX")
|| parseKeywordIf("FULLTEXT KEY"))
&& requireUnsupportedSyntax())
|| parseKeywordIf("INDEX")
|| parseKeywordIf("KEY");
}
private final void parseAlterTableAddFieldsOrConstraints(List<FieldOrConstraint> list) {
ConstraintTypeStep constraint = parseConstraintNameSpecification();
if (parsePrimaryKeyClusteredNonClusteredKeywordIf())
list.add(parsePrimaryKeySpecification(constraint));
else if (parseKeywordIf("UNIQUE") && (parseKeywordIf("KEY") || parseKeywordIf("INDEX") || true))
list.add(parseUniqueSpecification(constraint));
else if (parseKeywordIf("FOREIGN KEY"))
list.add(parseForeignKeySpecification(constraint));
else if (parseKeywordIf("CHECK"))
list.add(parseCheckSpecification(constraint));
else if (constraint != null)
throw expected("CHECK", "FOREIGN KEY", "PRIMARY KEY", "UNIQUE");
else if (parseKeywordIf("COLUMN") || true)
parseAlterTableAddField(list);
}
private final ConstraintTypeStep parseConstraintNameSpecification() {
if (parseKeywordIf("CONSTRAINT") && !peekKeyword("PRIMARY KEY", "UNIQUE", "FOREIGN KEY", "CHECK"))
return constraint(parseIdentifier());
return null;
}
private final Field<?> parseAlterTableAddField(List<FieldOrConstraint> list) {
// 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();
DataType type = parseDataType();
int p = list == null ? -1 : list.size();
ParseInlineConstraints inline = parseInlineConstraints(fieldName, type, list, false, false);
Field<?> result = field(fieldName, inline.type, inline.fieldComment);
if (list != null)
list.add(p, result);
return result;
}
private final DDLQuery parseAlterTableAlterColumn(AlterTableStep s1) {
boolean paren = parseIf('(');
TableField<?, ?> field = parseFieldName();
if (!paren)
if (parseKeywordIf("CONSTRAINT") && parseIdentifier() != null)
if (parseKeywordIf("NULL"))
return s1.alter(field).dropNotNull();
else if (parseNotNullOptionalEnable())
return s1.alter(field).setNotNull();
else
throw expected("NOT NULL", "NULL");
else if (parseKeywordIf("DROP NOT NULL") || parseKeywordIf("SET NULL") || parseKeywordIf("NULL"))
return s1.alter(field).dropNotNull();
else if (parseKeywordIf("DROP DEFAULT"))
return s1.alter(field).dropDefault();
else if (parseKeywordIf("SET NOT NULL") || parseNotNullOptionalEnable())
return s1.alter(field).setNotNull();
else if (parseKeywordIf("SET DEFAULT"))
return s1.alter(field).default_((Field) toField(parseConcat(null)));
else if (parseKeywordIf("TO") || parseKeywordIf("RENAME TO") || parseKeywordIf("RENAME AS"))
return s1.renameColumn(field).to(parseFieldName());
else if (parseKeywordIf("TYPE") || parseKeywordIf("SET DATA TYPE"))
;
DataType<?> type = parseDataType();
if (parseKeywordIf("NULL"))
type = type.nullable(true);
else if (parseNotNullOptionalEnable())
type = type.nullable(false);
if (paren)
parse(')');
return s1.alter(field).set(type);
}
private final boolean parseNotNullOptionalEnable() {
return parseKeywordIf("NOT NULL") && (parseKeywordIf("ENABLE") || true);
}
private final DDLQuery parseAlterTableAlterConstraint(AlterTableStep s1) {
requireProEdition();
throw expected("ENABLE", "ENFORCED", "DISABLE", "NOT ENFORCED");
}
private final DDLQuery parseAlterType() {
AlterTypeStep s1 = dsl.alterType(parseName());
if (parseKeywordIf("ADD VALUE"))
return s1.addValue(parseStringLiteral());
else if (parseKeywordIf("OWNER TO") && parseUser() != null)
return IGNORE;
else if (parseKeywordIf("RENAME TO"))
return s1.renameTo(parseIdentifier());
else if (parseKeywordIf("RENAME VALUE"))
return s1.renameValue(parseStringLiteral()).to(parseKeyword("TO") ? parseStringLiteral() : null);
else if (parseKeywordIf("SET SCHEMA"))
return s1.setSchema(parseIdentifier());
throw expected("ADD VALUE", "OWNER TO", "RENAME TO", "RENAME VALUE", "SET SCHEMA");
}
private final DDLQuery parseRename() {
parseKeyword("RENAME");
switch (characterUpper()) {
case 'C':
if (parseKeywordIf("COLUMN")) {
TableField<?, ?> oldName = parseFieldName();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return dsl.alterTable(oldName.getTable()).renameColumn(oldName).to(parseFieldName());
}
break;
case 'D':
if (parseKeywordIf("DATABASE")) {
Catalog oldName = parseCatalogName();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return dsl.alterDatabase(oldName).renameTo(parseCatalogName());
}
break;
case 'I':
if (parseKeywordIf("INDEX")) {
Name oldName = parseIndexName();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return dsl.alterIndex(oldName).renameTo(parseIndexName());
}
break;
case 'S':
if (parseKeywordIf("SCHEMA")) {
Schema oldName = parseSchemaName();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return dsl.alterSchema(oldName).renameTo(parseSchemaName());
}
else if (parseKeywordIf("SEQUENCE")) {
Sequence<?> oldName = parseSequenceName();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return dsl.alterSequence(oldName).renameTo(parseSequenceName());
}
break;
case 'V':
if (parseKeywordIf("VIEW")) {
Table<?> oldName = parseTableName();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return dsl.alterView(oldName).renameTo(parseTableName());
}
break;
}
// If all of the above fails, we can assume we're renaming a table.
parseKeywordIf("TABLE");
Table<?> oldName = parseTableName();
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return dsl.alterTable(oldName).renameTo(parseTableName());
}
private final DDLQuery parseDropTable(boolean temporary) {
boolean ifExists = parseKeywordIf("IF EXISTS");
Table<?> tableName = parseTableName();
boolean cascade = parseKeywordIf("CASCADE");
boolean restrict = !cascade && parseKeywordIf("RESTRICT");
DropTableStep s1;
DropTableFinalStep s2;
s1 = ifExists
? dsl.dropTableIfExists(tableName)
: temporary
? dsl.dropTemporaryTable(tableName)
: dsl.dropTable(tableName);
s2 = cascade
? s1.cascade()
: restrict
? s1.restrict()
: s1;
return s2;
}
private final DDLQuery parseDropType() {
boolean ifExists = parseKeywordIf("IF EXISTS");
List<Name> typeNames = parseIdentifiers();
boolean cascade = parseKeywordIf("CASCADE");
boolean restrict = !cascade && parseKeywordIf("RESTRICT");
DropTypeStep s1;
DropTypeFinalStep s2;
s1 = ifExists
? dsl.dropTypeIfExists(typeNames)
: dsl.dropType(typeNames);
s2 = cascade
? s1.cascade()
: restrict
? s1.restrict()
: s1;
return s2;
}
private final DDLQuery parseCreateDomain() {
boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
Domain<?> domainName = parseDomainName();
parseKeyword("AS");
DataType<?> dataType = parseDataType();
CreateDomainDefaultStep<?> s1 = ifNotExists
? dsl.createDomainIfNotExists(domainName).as(dataType)
: dsl.createDomain(domainName).as(dataType);
CreateDomainConstraintStep s2 = parseKeywordIf("DEFAULT")
? s1.default_((Field) parseField())
: s1;
List<Constraint> constraints = new ArrayList<>();
constraintLoop:
for (;;) {
ConstraintTypeStep constraint = parseConstraintNameSpecification();
// TODO: NOT NULL constraints
if (parseKeywordIf("CHECK")) {
constraints.add(parseCheckSpecification(constraint));
continue constraintLoop;
}
else if (constraint != null)
throw expected("CHECK", "CONSTRAINT");
break;
}
if (!constraints.isEmpty())
s2 = s2.constraints(constraints);
return s2;
}
private final DDLQuery parseAlterDomain() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Domain<?> domainName = parseDomainName();
AlterDomainStep s1 = ifExists
? dsl.alterDomainIfExists(domainName)
: dsl.alterDomain(domainName);
if (parseKeywordIf("ADD")) {
ConstraintTypeStep constraint = parseConstraintNameSpecification();
// TODO: NOT NULL constraints
if (parseKeywordIf("CHECK"))
return s1.add(parseCheckSpecification(constraint));
else
throw expected("CHECK", "CONSTRAINT");
}
else if (parseKeywordIf("DROP CONSTRAINT")) {
boolean ifConstraintExists = parseKeywordIf("IF EXISTS");
Constraint constraint = constraint(parseIdentifier());
AlterDomainDropConstraintCascadeStep s2 = ifConstraintExists
? s1.dropConstraintIfExists(constraint)
: s1.dropConstraint(constraint);
return parseKeywordIf("CASCADE")
? s2.cascade()
: parseKeywordIf("RESTRICT")
? s2.restrict()
: s2;
}
else if (parseKeywordIf("RENAME")) {
if (parseKeywordIf("TO") || parseKeywordIf("AS")) {
return s1.renameTo(parseDomainName());
}
else if (parseKeywordIf("CONSTRAINT")) {
boolean ifConstraintExists = parseKeywordIf("IF EXISTS");
Constraint oldName = constraint(parseIdentifier());
AlterDomainRenameConstraintStep s2 = ifConstraintExists
? s1.renameConstraintIfExists(oldName)
: s1.renameConstraint(oldName);
if (!parseKeywordIf("TO"))
parseKeyword("AS");
return s2.to(constraint(parseIdentifier()));
}
else
throw expected("CONSTRAINT", "TO", "AS");
}
else if (parseKeywordIf("SET DEFAULT"))
return s1.setDefault(parseField());
else if (parseKeywordIf("DROP DEFAULT"))
return s1.dropDefault();
else if (parseKeywordIf("SET NOT NULL"))
return s1.setNotNull();
else if (parseKeywordIf("DROP NOT NULL"))
return s1.dropNotNull();
else if (parseKeywordIf("OWNER TO")) {
parseUser();
return IGNORE;
}
else
throw expected("ADD", "DROP", "RENAME", "SET", "OWNER TO");
}
private final DDLQuery parseDropDomain() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Domain<?> domainName = parseDomainName();
boolean cascade = parseKeywordIf("CASCADE");
boolean restrict = !cascade && parseKeywordIf("RESTRICT");
DropDomainCascadeStep s1 = ifExists
? dsl.dropDomainIfExists(domainName)
: dsl.dropDomain(domainName);
return cascade
? s1.cascade()
: restrict
? s1.restrict()
: s1;
}
private final DDLQuery parseCreateDatabase() {
boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
Catalog catalogName = parseCatalogName();
return ifNotExists
? dsl.createDatabaseIfNotExists(catalogName)
: dsl.createDatabase(catalogName);
}
private final DDLQuery parseAlterDatabase() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Catalog catalogName = parseCatalogName();
AlterDatabaseStep s1 = ifExists
? dsl.alterDatabaseIfExists(catalogName)
: dsl.alterDatabase(catalogName);
if (parseKeywordIf("RENAME")) {
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return s1.renameTo(parseCatalogName());
}
else if (parseKeywordIf("OWNER TO") && parseUser() != null)
return IGNORE;
else if (parseAlterDatabaseFlags(true))
return IGNORE;
else
throw expected("OWNER TO", "RENAME TO");
}
private final boolean parseAlterDatabaseFlags(boolean throwOnFail) {
parseKeywordIf("DEFAULT");
if (parseCharacterSetSpecificationIf() != null)
return true;
if (parseCollateSpecificationIf() != null)
return true;
if (parseKeywordIf("ENCRYPTION")) {
parseIf('=');
parseStringLiteral();
return true;
}
if (throwOnFail)
throw expected("CHARACTER SET", "COLLATE", "DEFAULT ENCRYPTION");
else
return false;
}
private final DDLQuery parseDropDatabase() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Catalog catalogName = parseCatalogName();
return ifExists
? dsl.dropDatabaseIfExists(catalogName)
: dsl.dropDatabase(catalogName);
}
private final DDLQuery parseCreateSchema() {
boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
boolean authorization = parseKeywordIf("AUTHORIZATION");
Schema schemaName = parseSchemaName();
if (!authorization && parseKeywordIf("AUTHORIZATION"))
parseUser();
return ifNotExists
? dsl.createSchemaIfNotExists(schemaName)
: dsl.createSchema(schemaName);
}
private final DDLQuery parseAlterSchema() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Schema schemaName = parseSchemaName();
AlterSchemaStep s1 = ifExists
? dsl.alterSchemaIfExists(schemaName)
: dsl.alterSchema(schemaName);
if (parseKeywordIf("RENAME")) {
if (!parseKeywordIf("AS"))
parseKeyword("TO");
return s1.renameTo(parseSchemaName());
}
else if (parseKeywordIf("OWNER TO") && parseUser() != null)
return IGNORE;
else if (parseAlterDatabaseFlags(false))
return IGNORE;
else
throw expected("OWNER TO", "RENAME TO");
}
private final DDLQuery parseDropSchema() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Schema schemaName = parseSchemaName();
boolean cascade = parseKeywordIf("CASCADE");
boolean restrict = !cascade && parseKeywordIf("RESTRICT");
DropSchemaStep s1 = ifExists
? dsl.dropSchemaIfExists(schemaName)
: dsl.dropSchema(schemaName);
return cascade
? s1.cascade()
: restrict
? s1.restrict()
: s1;
}
private final DDLQuery parseCreateIndex(boolean unique) {
boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
Name indexName = parseIndexNameIf();
parseUsingIndexTypeIf();
parseKeyword("ON");
Table<?> tableName = parseTableName();
parseUsingIndexTypeIf();
parse('(');
SortField<?>[] fields = parseSortSpecification().toArray(EMPTY_SORTFIELD);
parse(')');
parseUsingIndexTypeIf();
Name[] include = null;
if (parseKeywordIf("INCLUDE") || parseKeywordIf("COVERING") || parseKeywordIf("STORING")) {
parse('(');
include = parseIdentifiers().toArray(EMPTY_NAME);
parse(')');
}
Condition condition = parseKeywordIf("WHERE")
? parseCondition()
: null;
boolean excludeNullKeys = condition == null && parseKeywordIf("EXCLUDE NULL KEYS");
CreateIndexStep s1 = ifNotExists
? unique
? dsl.createUniqueIndexIfNotExists(indexName)
: dsl.createIndexIfNotExists(indexName)
: unique
? indexName == null
? dsl.createUniqueIndex()
: dsl.createUniqueIndex(indexName)
: indexName == null
? dsl.createIndex()
: dsl.createIndex(indexName);
CreateIndexIncludeStep s2 = s1.on(tableName, fields);
CreateIndexWhereStep s3 = include != null
? s2.include(include)
: s2;
CreateIndexFinalStep s4 = condition != null
? s3.where(condition)
: excludeNullKeys
? s3.excludeNullKeys()
: s3;
return s4;
}
private final boolean parseUsingIndexTypeIf() {
if (parseKeywordIf("USING"))
parseIdentifier();
return true;
}
private final DDLQuery parseAlterIndex() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Name indexName = parseIndexName();
parseKeyword("RENAME");
if (!parseKeywordIf("AS"))
parseKeyword("TO");
Name newName = parseIndexName();
AlterIndexStep s1 = ifExists
? dsl.alterIndexIfExists(indexName)
: dsl.alterIndex(indexName);
AlterIndexFinalStep s2 = s1.renameTo(newName);
return s2;
}
private final DDLQuery parseDropIndex() {
boolean ifExists = parseKeywordIf("IF EXISTS");
Name indexName = parseIndexName();
boolean on = parseKeywordIf("ON");
Table<?> onTable = on ? parseTableName() : null;
DropIndexOnStep s1;
DropIndexCascadeStep s2;
DropIndexFinalStep s3;
s1 = ifExists
? dsl.dropIndexIfExists(indexName)
: dsl.dropIndex(indexName);
s2 = on
? s1.on(onTable)
: s1;
s3 = parseKeywordIf("CASCADE")
? s2.cascade()
: parseKeywordIf("RESTRICT")
? s2.restrict()
: s2;
return s3;
}
// -----------------------------------------------------------------------------------------------------------------
// QueryPart parsing
// -----------------------------------------------------------------------------------------------------------------
private final Condition parseCondition() {
return toCondition(parseOr());
}
private final QueryPart parseOr() {
QueryPart condition = parseAnd();
while (parseKeywordIf("OR"))
condition = toCondition(condition).or(toCondition(parseAnd()));
return condition;
}
private final QueryPart parseAnd() {
QueryPart condition = parseNot();
while (parseKeywordIf("AND"))
condition = toCondition(condition).and(toCondition(parseNot()));
return condition;
}
private final QueryPart parseNot() {
boolean not = parseKeywordIf("NOT");
QueryPart condition = parsePredicate();
return not ? toCondition(condition).not() : condition;
}
private final QueryPart parsePredicate() {
if (parseKeywordIf("EXISTS")) {
parse('(');
Select<?> select = parseWithOrSelect();
parse(')');
return exists(select);
}
else if (parseKeywordIf("REGEXP_LIKE")) {
parse('(');
Field<?> f1 = parseField();
parse(',');
Field<?> f2 = parseField();
parse(')');
return f1.likeRegex((Field) f2);
}
else if (parseKeywordIf("UNIQUE")) {
parse('(');
Select<?> select = parseWithOrSelect();
parse(')');
return unique(select);
}
else if (parseKeywordIf("JSON_EXISTS")) {
parse('(');
Field json = parseField();
parse(',');
Field<String> path = (Field<String>) parseField();
JSONExists.Behaviour b = parseJSONExistsOnErrorBehaviourIf();
parse(')');
return jsonExists(json, path);
}
else if (parseKeywordIf("XMLEXISTS")) {
parse('(');
Field<String> xpath = (Field<String>) parseField();
XMLPassingMechanism m = parseXMLPassingMechanism();
Field<XML> xml = (Field<XML>) parseField();
parse(')');
if (m == BY_REF)
return xmlexists(xpath).passingByRef(xml);
else if (m == BY_VALUE)
return xmlexists(xpath).passingByValue(xml);
else
return xmlexists(xpath).passing(xml);
}
else {
FieldOrRow left;
Comparator comp;
TSQLOuterJoinComparator outer;
boolean not;
boolean notOp = false;
left = parseConcat(null);
not = parseKeywordIf("NOT");
if (!not && ((outer = parseTSQLOuterJoinComparatorIf()) != null) && requireProEdition()) {
Condition result = null;
return result;
}
else if (!not && (comp = parseComparatorIf()) != null) {
boolean all = parseKeywordIf("ALL");
boolean any = !all && (parseKeywordIf("ANY") || parseKeywordIf("SOME"));
if (all || any)
parse('(');
// TODO equal degrees
Condition result =
all
? left instanceof Field
? peekSelectOrWith(true)
? ((Field) left).compare(comp, DSL.all(parseWithOrSelect(1)))
: ((Field) left).compare(comp, DSL.all(parseFields().toArray(EMPTY_FIELD)))
// TODO: Support quantifiers also for rows
: new RowSubqueryCondition((Row) left, DSL.all(parseWithOrSelect(((Row) left).size())), comp)
: any
? left instanceof Field
? peekSelectOrWith(true)
? ((Field) left).compare(comp, DSL.any(parseWithOrSelect(1)))
: ((Field) left).compare(comp, DSL.any(parseFields().toArray(EMPTY_FIELD)))
// TODO: Support quantifiers also for rows
: new RowSubqueryCondition((Row) left, DSL.any(parseWithOrSelect(((Row) left).size())), comp)
: left instanceof Field
? ((Field) left).compare(comp, toField(parseConcat(null)))
: new RowCondition((Row) left, parseRow(((Row) left).size(), true), comp);
if (all || any)
parse(')');
return result;
}
else if (!not && parseKeywordIf("IS")) {
not = parseKeywordIf("NOT");
if (parseKeywordIf("NULL"))
return not
? left instanceof Field
? ((Field) left).isNotNull()
: ((Row) left).isNotNull()
: left instanceof Field
? ((Field) left).isNull()
: ((Row) left).isNull();
else if (left instanceof Field && parseKeywordIf("JSON"))
return not
? ((Field) left).isNotJson()
: ((Field) left).isJson();
else if (left instanceof Field && parseKeywordIf("DOCUMENT"))
return not
? ((Field) left).isNotDocument()
: ((Field) left).isDocument();
not = parseKeywordIf("DISTINCT FROM") == not;
if (left instanceof Field) {
Field right = toField(parseConcat(null));
return not ? ((Field) left).isNotDistinctFrom(right) : ((Field) left).isDistinctFrom(right);
}
else {
Row right = parseRow(((Row) left).size(), true);
return new RowIsDistinctFrom((Row) left, right, not);
}
}
else if (!not && parseIf("@>")) {
return toField(left).contains((Field) toField(parseConcat(null)));
}
else if (parseKeywordIf("IN")) {
Condition result;
parse('(');
if (peek(')'))
result = not
? left instanceof Field
? ((Field) left).notIn(EMPTY_FIELD)
: new RowInCondition((Row) left, new QueryPartList<>(), true)
: left instanceof Field
? ((Field) left).in(EMPTY_FIELD)
: new RowInCondition((Row) left, new QueryPartList<>(), false);
else if (peekSelectOrWith(true))
result = not
? left instanceof Field
? ((Field) left).notIn(parseWithOrSelect(1))
: new RowSubqueryCondition((Row) left, parseWithOrSelect(((Row) left).size()), NOT_IN)
: left instanceof Field
? ((Field) left).in(parseWithOrSelect(1))
: new RowSubqueryCondition((Row) left, parseWithOrSelect(((Row) left).size()), IN);
else
result = not
? left instanceof Field
? ((Field) left).notIn(parseFields())
: new RowInCondition((Row) left, new QueryPartList<>(parseRows(((Row) left).size())), true)
: left instanceof Field
? ((Field) left).in(parseFields())
: new RowInCondition((Row) left, new QueryPartList<>(parseRows(((Row) left).size())), false);
parse(')');
return result;
}
else if (parseKeywordIf("BETWEEN")) {
boolean symmetric = !parseKeywordIf("ASYMMETRIC") && parseKeywordIf("SYMMETRIC");
FieldOrRow r1 = left instanceof Field
? parseConcat(null)
: parseRow(((Row) left).size());
parseKeyword("AND");
FieldOrRow r2 = left instanceof Field
? parseConcat(null)
: parseRow(((Row) left).size());
return symmetric
? not
? left instanceof Field
? ((Field) left).notBetweenSymmetric((Field) r1, (Field) r2)
: new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2)
: left instanceof Field
? ((Field) left).betweenSymmetric((Field) r1, (Field) r2)
: new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2)
: not
? left instanceof Field
? ((Field) left).notBetween((Field) r1, (Field) r2)
: new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2)
: left instanceof Field
? ((Field) left).between((Field) r1, (Field) r2)
: new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2);
}
else if (left instanceof Field && (parseKeywordIf("LIKE") || parseOperatorIf("~~") || (notOp = parseOperatorIf("!~~")))) {
if (parseKeywordIf("ANY")) {
parse('(');
if (peekSelectOrWith(true)) {
Select<?> select = parseWithOrSelect();
parse(')');
LikeEscapeStep result = (not ^ notOp) ? ((Field) left).notLike(any(select)) : ((Field) left).like(any(select));
return parseEscapeClauseIf(result);
}
else {
List<Field<?>> fields = null;
if (parseIf(')')) {
fields = emptyList();
}
else {
fields = new ArrayList<>();
do
fields.add(toField(parseConcat(null)));
while (parseIf(','));
parse(')');
}
Field<String>[] fieldArray = fields.toArray(new Field[0]);
LikeEscapeStep result = (not ^ notOp) ? ((Field<String>) left).notLike(any(fieldArray)) : ((Field<String>) left).like(any(fieldArray));
return parseEscapeClauseIf(result);
}
}
else if (parseKeywordIf("ALL")) {
parse('(');
if (peekSelectOrWith(true)) {
Select<?> select = parseWithOrSelect();
parse(')');
LikeEscapeStep result = (not ^ notOp) ? ((Field) left).notLike(all(select)) : ((Field) left).like(all(select));
return parseEscapeClauseIf(result);
}
else {
List<Field<?>> fields = null;
if (parseIf(')')) {
fields = emptyList();
}
else {
fields = new ArrayList<>();
do
fields.add(toField(parseConcat(null)));
while (parseIf(','));
parse(')');
}
Field<String>[] fieldArray = fields.toArray(new Field[0]);
LikeEscapeStep result = (not ^ notOp) ? ((Field<String>) left).notLike(all(fieldArray)) : ((Field<String>) left).like(all(fieldArray));
return parseEscapeClauseIf(result);
}
}
else {
Field right = toField(parseConcat(null));
LikeEscapeStep like = (not ^ notOp) ? ((Field) left).notLike(right) : ((Field) left).like(right);
return parseEscapeClauseIf(like);
}
}
else if (left instanceof Field && (parseKeywordIf("ILIKE") || parseOperatorIf("~~*") || (notOp = parseOperatorIf("!~~*")))) {
Field right = toField(parseConcat(null));
LikeEscapeStep like = (not ^ notOp) ? ((Field) left).notLikeIgnoreCase(right) : ((Field) left).likeIgnoreCase(right);
return parseEscapeClauseIf(like);
}
else if (left instanceof Field && (parseKeywordIf("REGEXP")
|| parseKeywordIf("RLIKE")
|| parseKeywordIf("LIKE_REGEX")
|| parseOperatorIf("~")
|| (notOp = parseOperatorIf("!~")))) {
Field right = toField(parseConcat(null));
return (not ^ notOp)
? ((Field) left).notLikeRegex(right)
: ((Field) left).likeRegex(right);
}
else if (left instanceof Field && parseKeywordIf("SIMILAR TO")) {
Field right = toField(parseConcat(null));
LikeEscapeStep like = not ? ((Field) left).notSimilarTo(right) : ((Field) left).similarTo(right);
return parseEscapeClauseIf(like);
}
else if (left instanceof Row && ((Row) left).size() == 2 && parseKeywordIf("OVERLAPS")) {
Row leftRow = (Row) left;
Row rightRow = parseRow(2);
Row2 leftRow2 = row(leftRow.field(0), leftRow.field(1));
Row2 rightRow2 = row(rightRow.field(0), rightRow.field(1));
return leftRow2.overlaps(rightRow2);
}
else
return left;
}
}
private final QueryPart parseEscapeClauseIf(LikeEscapeStep like) {
return parseKeywordIf("ESCAPE") ? like.escape(parseCharacterLiteral()) : like;
}
private final List<Table<?>> parseTables() {
List<Table<?>> result = new ArrayList<>();
do
result.add(parseTable());
while (parseIf(','));
return result;
}
private final Table<?> parseTable() {
Table<?> result = parseLateral();
for (;;) {
Table<?> joined = parseJoinedTableIf(result);
if (joined == null)
return result;
else
result = joined;
}
}
private final Table<?> parseLateral() {
if (parseKeywordIf("LATERAL"))
return lateral(parseTableFactor());
else
return parseTableFactor();
}
private final <R extends Record> Table<R> t(TableLike<R> table) {
return t(table, false);
}
private final <R extends Record> Table<R> t(TableLike<R> table, boolean dummyAlias) {
return
table instanceof Table
? (Table<R>) table
: dummyAlias
? table.asTable("x")
: table.asTable();
}
private final Table<?> parseTableFactor() {
// [#7982] Postpone turning Select into a Table in case there is an alias
TableLike<?> result = null;
// TODO [#5306] Support FINAL TABLE (<data change statement>)
// TOOD ONLY ( table primary )
if (parseFunctionNameIf("UNNEST", "TABLE")) {
parse('(');
Field<?> f = parseField(Type.A);
// Work around a missing feature in unnest()
if (!f.getType().isArray())
f = f.coerce(f.getDataType().getArrayDataType());
result = unnest(f);
parse(')');
}
else if (parseFunctionNameIf("GENERATE_SERIES")) {
parse('(');
Field from = toField(parseConcat(Type.N));
parse(',');
Field to = toField(parseConcat(Type.N));
Field step = parseIf(',')
? toField(parseConcat(Type.N))
: null;
parse(')');
result = step == null
? generateSeries(from, to)
: generateSeries(from, to, step);
}
else if (parseFunctionNameIf("JSON_TABLE")) {
parse('(');
Field json = parseField();
parse(',');
Field path = toField(parseConcat(Type.S));
JSONTableColumnsStep s1 = (JSONTableColumnsStep) jsonTable(json, path);
parseKeyword("COLUMNS");
parse('(');
do {
Name fieldName = parseIdentifier();
if (parseKeywordIf("FOR ORDINALITY")) {
s1 = s1.column(fieldName).forOrdinality();
}
else {
JSONTableColumnPathStep s2 = s1.column(fieldName, parseDataType());
s1 = parseKeywordIf("PATH") ? s2.path(parseStringLiteral()) : s2;
}
}
while (parseIf(','));
parse(')');
parse(')');
result = s1;
}
else if (parseFunctionNameIf("XMLTABLE")) {
parse('(');
XMLTablePassingStep s1 = xmltable((Field) toField(parseConcat(Type.S)));
XMLPassingMechanism m = parseXMLPassingMechanismIf();
Field<XML> passing = m == null ? null : (Field<XML>) parseField();
XMLTableColumnsStep s2 = (XMLTableColumnsStep) (
m == BY_REF
? s1.passingByRef(passing)
: m == BY_VALUE
? s1.passingByValue(passing)
: m == XMLPassingMechanism.DEFAULT
? s1.passing(passing)
: s1
);
parseKeyword("COLUMNS");
do {
Name fieldName = parseIdentifier();
if (parseKeywordIf("FOR ORDINALITY")) {
s2 = s2.column(fieldName).forOrdinality();
}
else {
XMLTableColumnPathStep s3 = s2.column(fieldName, parseDataType());
s2 = parseKeywordIf("PATH") ? s3.path(parseStringLiteral()) : s3;
}
}
while (parseIf(','));
parse(')');
result = s2;
}
else if (parseIf('(')) {
// A table factor parenthesis can mark the beginning of any of:
// - A derived table: E.g. (select 1)
// - A derived table with nested set ops: E.g. ((select 1) union (select 2))
// - A values derived table: E.g. (values (1))
// - A joined table: E.g. ((a join b on p) right join c on q)
// - A combination of the above: E.g. ((a join (select 1) on p) right join (((select 1)) union (select 2)) on q)
if (peekKeyword("SELECT") || peekKeyword("SEL")) {
SelectQueryImpl<Record> select = parseSelect();
parse(')');
result = parseQueryExpressionBody(null, null, select);
}
else if (peekKeyword("VALUES")) {
result = parseTableValueConstructor();
parse(')');
}
else {
result = parseJoinedTable();
parse(')');
}
}
else {
result = parseTableName();
// TODO Sample clause
}
if (parseKeywordIf("VERSIONS") && requireProEdition()) {
}
else if (peekKeyword("FOR")
&& !peekKeyword("FOR JSON")
&& !peekKeyword("FOR KEY SHARE")
&& !peekKeyword("FOR NO KEY UPDATE")
&& !peekKeyword("FOR SHARE")
&& !peekKeyword("FOR UPDATE")
&& !peekKeyword("FOR XML")
&& parseKeyword("FOR") && requireProEdition()) {
}
else if (parseKeywordIf("AS OF") && requireProEdition()) {
}
if (parseKeywordIf("PIVOT") && requireProEdition()) {
}
// TODO UNPIVOT
Name alias = null;
List<Name> columnAliases = null;
if (parseKeywordIf("AS"))
alias = parseIdentifier();
else if (!peekKeyword(KEYWORDS_IN_FROM) && !peekKeyword(KEYWORDS_IN_STATEMENTS))
alias = parseIdentifierIf();
if (alias != null) {
if (parseIf('(')) {
columnAliases = parseIdentifiers();
parse(')');
}
if (columnAliases != null)
result = t(result, true).as(alias, columnAliases.toArray(EMPTY_NAME));
else
result = t(result, true).as(alias);
}
int p = position();
if (parseKeywordIf("WITH")) {
if (parseIf('(') && requireProEdition()) {
}
// [#10164] Without parens, WITH is part of the next statement in delimiter free statement batches
else
position(p);
}
return t(result);
}
private final Table<?> parseTableValueConstructor() {
parseKeyword("VALUES");
List<Row> rows = new ArrayList<>();
Integer degree = null;
do {
parseKeywordIf("ROW");
Row row = parseTuple(degree);
rows.add(row);
if (degree == null)
degree = row.size();
}
while (parseIf(','));
return values0(rows.toArray(EMPTY_ROW));
}
private final Table<?> parseExplicitTable() {
parseKeyword("TABLE");
return parseTableName();
}
private final Row parseTuple() {
return parseTuple(null, false);
}
private final Row parseTuple(Integer degree) {
return parseTuple(degree, false);
}
private final Row parseTupleIf(Integer degree) {
return parseTupleIf(degree, false);
}
private final Row parseTuple(Integer degree, boolean allowDoubleParens) {
parse('(');
List<? extends FieldOrRow> fieldsOrRows;
if (allowDoubleParens)
fieldsOrRows = parseFieldsOrRows();
else
fieldsOrRows = parseFields();
Row row;
if (fieldsOrRows.size() == 0)
row = row();
else if (fieldsOrRows.get(0) instanceof Field)
row = row(fieldsOrRows);
else if (fieldsOrRows.size() == 1)
row = (Row) fieldsOrRows.get(0);
else
throw exception("Unsupported row size");
if (degree != null && row.size() != degree)
throw exception("Expected row of degree: " + degree + ". Got: " + row.size());
parse(')');
return row;
}
private final Row parseTupleIf(Integer degree, boolean allowDoubleParens) {
if (peek('('))
return parseTuple(degree, allowDoubleParens);
return null;
}
private final Table<?> parseJoinedTable() {
Table<?> result = parseLateral();
for (;;) {
Table<?> joined = parseJoinedTableIf(result);
if (joined == null)
return result;
else
result = joined;
}
}
private final Table<?> parseJoinedTableIf(Table<?> left) {
JoinType joinType = parseJoinTypeIf();
if (joinType == null)
return null;
Table<?> right = joinType.qualified() ? parseTable() : parseLateral();
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:
if (parseKeywordIf("PARTITION BY")) {
requireProEdition();
}
// No break
case JOIN:
case STRAIGHT_JOIN:
case LEFT_SEMI_JOIN:
case LEFT_ANTI_JOIN:
if (parseKeywordIf("ON"))
return s2.on(parseCondition());
else if (parseKeywordIf("USING"))
return parseJoinUsing(s2);
// [#9476] MySQL treats INNER JOIN and CROSS JOIN as the same
else if (joinType == JOIN)
return s0;
else
throw expected("ON", "USING");
case CROSS_JOIN:
// [#9476] MySQL treats INNER JOIN and CROSS JOIN as the same
if (parseKeywordIf("ON"))
return left.join(right).on(parseCondition());
else if (parseKeywordIf("USING"))
return parseJoinUsing(left.join(right));
// No break
default:
return s0;
}
}
private final Table<?> parseJoinUsing(TableOnStep<?> join) {
Table<?> result;
parse('(');
if (parseIf(')')) {
result = join.using();
}
else {
result = join.using(Tools.fieldsByName(parseIdentifiers().toArray(EMPTY_NAME)));
parse(')');
}
return result;
}
private final List<SelectFieldOrAsterisk> parseSelectList() {
List<SelectFieldOrAsterisk> result = new ArrayList<>();
do {
QualifiedAsterisk qa;
if (parseIf('*')) {
if (parseKeywordIf("EXCEPT")) {
parse('(');
result.add(DSL.asterisk().except(parseFieldNames().toArray(EMPTY_FIELD)));
parse(')');
}
else
result.add(DSL.asterisk());
}
else if ((qa = parseQualifiedAsteriskIf()) != null) {
if (parseKeywordIf("EXCEPT")) {
parse('(');
result.add(qa.except(parseFieldNames().toArray(EMPTY_FIELD)));
parse(')');
}
else
result.add(qa);
}
else {
Name alias = null;
Field<?> field = null;
if (field == null) {
field = parseField();
if (parseKeywordIf("AS"))
alias = parseIdentifier(true);
else if (!peekKeyword(KEYWORDS_IN_SELECT) && !peekKeyword(KEYWORDS_IN_STATEMENTS))
alias = parseIdentifierIf(true);
}
result.add(alias == null ? field : field.as(alias));
}
}
while (parseIf(','));
return result;
}
private final List<SortField<?>> parseSortSpecification() {
List<SortField<?>> result = new ArrayList<>();
do
result.add(parseSortField());
while (parseIf(','));
return result;
}
private final SortField<?> parseSortField() {
Field<?> field = parseField();
SortField<?> sort;
if (parseKeywordIf("DESC"))
sort = field.desc();
else if (parseKeywordIf("ASC"))
sort = field.asc();
else
sort = field.sortDefault();
if (parseKeywordIf("NULLS FIRST"))
sort = sort.nullsFirst();
else if (parseKeywordIf("NULLS LAST"))
sort = sort.nullsLast();
return sort;
}
private final List<Field<?>> parseFieldsOrEmptyParenthesised() {
parse('(');
if (parseIf(')')) {
return emptyList();
}
else {
List<Field<?>> result = parseFields();
parse(')');
return result;
}
}
private final List<Field<?>> parseFields() {
List<Field<?>> result = new ArrayList<>();
do
result.add(parseField());
while (parseIf(','));
return result;
}
private final List<FieldOrRow> parseFieldsOrRows() {
List<FieldOrRow> result = new ArrayList<>();
do
result.add(parseFieldOrRow());
while (parseIf(','));
return result;
}
private final Field<?> parseField() {
return parseField(null);
}
private final FieldOrRow parseFieldOrRow() {
return parseFieldOrRow(null);
}
private final Row parseRow() {
return parseRow(null);
}
private final Row parseRowIf() {
return parseRowIf(null);
}
private final List<Row> parseRows(Integer degree) {
List<Row> result = new ArrayList<>();
do
result.add(parseRow(degree));
while (parseIf(','));
return result;
}
private final Row parseRow(Integer degree) {
parseFunctionNameIf("ROW");
return parseTuple(degree);
}
private final Row parseRowIf(Integer degree) {
parseFunctionNameIf("ROW");
return parseTupleIf(degree);
}
private final Row parseRow(Integer degree, boolean allowDoubleParens) {
parseFunctionNameIf("ROW");
return parseTuple(degree, allowDoubleParens);
}
static enum Type {
A("array"),
D("date"),
S("string"),
N("numeric"),
B("boolean"),
Y("binary"),
J("json"),
X("xml");
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 final FieldOrRow parseFieldOrRow(Type type) {
if (B.is(type))
return toFieldOrRow(parseOr());
else
return parseConcat(type);
}
private final Field<?> parseField(Type type) {
if (B.is(type))
return toField(parseOr());
else
return toField(parseConcat(type));
}
private final String parseHints() {
StringBuilder sb = new StringBuilder();
do {
int p = position();
if (parseIf('/', false)) {
parse('*', false);
int i = position();
loop:
while (i < sql.length) {
switch (sql[i]) {
case '*':
if (i + 1 < sql.length && sql[i + 1] == '/')
break loop;
}
i++;
}
position(i + 2);
if (sb.length() > 0)
sb.append(' ');
sb.append(substring(p, position()));
}
}
while (parseWhitespaceIf());
ignoreHints(true);
return sb.length() > 0 ? sb.toString() : null;
}
private final Condition toCondition(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);
// [#7266] Support parsing column references as predicates
else if (part instanceof TableFieldImpl)
return condition((Field) part);
else
throw expected("Boolean field");
else
throw expected("Condition");
}
private final FieldOrRow toFieldOrRow(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 expected("Field or row");
}
private final Field<?> toField(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 expected("Field");
}
private final FieldOrRow parseConcat(Type type) {
FieldOrRow r = parseCollated(type);
if (S.is(type) && r instanceof Field)
while (parseIf("||"))
r = concat((Field) r, toField(parseCollated(type)));
return r;
}
private final FieldOrRow parseCollated(Type type) {
FieldOrRow r = parseNumericOp(type);
if (S.is(type) && r instanceof Field)
if (parseKeywordIf("COLLATE"))
r = ((Field) r).collate(parseCollation());
return r;
}
private final Field<?> parseFieldNumericOpParenthesised() {
parse('(');
Field<?> r = toField(parseNumericOp(N));
parse(')');
return r;
}
private final Field<?> parseFieldParenthesised(Type type) {
parse('(');
Field<?> r = toField(parseField(type));
parse(')');
return r;
}
// Any numeric operator of low precedence
// See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-PRECEDENCE
private final FieldOrRow parseNumericOp(Type type) {
FieldOrRow r = parseSum(type);
if (N.is(type) && r instanceof Field)
for (;;)
if (parseIf("<<"))
r = ((Field) r).shl((Field) parseSum(type));
else if (parseIf(">>"))
r = ((Field) r).shr((Field) parseSum(type));
else
break;
return r;
}
private final FieldOrRow parseSum(Type type) {
FieldOrRow r = parseFactor(type);
if (N.is(type) && r instanceof Field)
for (;;)
if (parseIf('+'))
r = parseSumRightOperand(type, r, true);
else if (parseIf('-'))
r = parseSumRightOperand(type, r, false);
else
break;
return r;
}
private final Field parseSumRightOperand(Type type, FieldOrRow r, boolean add) {
Field rhs = (Field) parseFactor(type);
DatePart part;
if ((parseKeywordIf("YEAR") || parseKeywordIf("YEARS")) && requireProEdition())
part = DatePart.YEAR;
else if ((parseKeywordIf("MONTH") || parseKeywordIf("MONTHS")) && requireProEdition())
part = DatePart.MONTH;
else if ((parseKeywordIf("DAY") || parseKeywordIf("DAYS")) && requireProEdition())
part = DatePart.DAY;
else if ((parseKeywordIf("HOUR") || parseKeywordIf("HOURS")) && requireProEdition())
part = DatePart.HOUR;
else if ((parseKeywordIf("MINUTE") || parseKeywordIf("MINUTES")) && requireProEdition())
part = DatePart.MINUTE;
else if ((parseKeywordIf("SECOND") || parseKeywordIf("SECONDS")) && requireProEdition())
part = DatePart.SECOND;
else
part = null;
Field lhs = (Field) r;
if (add)
return lhs.add(rhs);
else if (lhs.getDataType().isDate() && rhs.getDataType().isDate())
return DSL.dateDiff(lhs, rhs);
else if (lhs.getDataType().isTimestamp() && rhs.getDataType().isTimestamp())
return DSL.timestampDiff(lhs, rhs);
else
return lhs.sub(rhs);
}
private final FieldOrRow parseFactor(Type type) {
FieldOrRow r = parseExp(type);
if (N.is(type) && r instanceof Field)
for (;;)
if (!peek("*=") && parseIf('*'))
r = ((Field) r).mul((Field) parseExp(type));
else if (parseIf('/'))
r = ((Field) r).div((Field) parseExp(type));
else if (parseIf('%'))
r = ((Field) r).mod((Field) parseExp(type));
else
break;
return r;
}
private final FieldOrRow parseExp(Type type) {
FieldOrRow r = parseUnaryOps(type);
if (N.is(type) && r instanceof Field)
for (;;)
if (!peek("^=") && parseIf('^'))
r = ((Field) r).pow(toField(parseUnaryOps(type)));
else
break;
return r;
}
private final FieldOrRow parseUnaryOps(Type type) {
if (parseKeywordIf("CONNECT_BY_ROOT") && requireProEdition()) {
}
FieldOrRow r;
Sign sign = parseSign();
if (sign == Sign.NONE)
r = parseTerm(type);
else if (sign == Sign.PLUS)
r = toField(parseTerm(type));
else if ((r = parseFieldUnsignedNumericLiteralIf(Sign.MINUS)) == null)
r = toField(parseTerm(type)).neg();
if (parseTokensIf('(', '+', ')') && requireProEdition())
;
// [#7171] Only identifier based field expressions could have been functions
// E.g. 'abc' ('xyz') may be some other type of syntax, e.g. from Db2 SIGNAL statements
if (r instanceof TableField && parseIf('('))
throw exception("Unknown function");
while (parseIf("::"))
r = cast(toField(r), parseDataType());
if (parseIf('[')) {
r = arrayGet((Field) toField(r), (Field) parseField(N));
parse(']');
}
return r;
}
private final Sign parseSign() {
Sign sign = Sign.NONE;
for (;;)
if (parseIf('+'))
sign = sign == Sign.NONE ? Sign.PLUS : sign;
else if (parseIf('-'))
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 final FieldOrRow parseTerm(Type type) {
FieldOrRow field;
Object value;
switch (characterUpper()) {
case ':':
case '?':
return parseBindVariable();
case '\'':
return inline(parseStringLiteral());
case '$':
if ((value = parseDollarQuotedStringLiteralIf()) != null)
return inline((String) value);
break;
case 'A':
if (N.is(type))
if (parseFunctionNameIf("ABS"))
return abs((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("ASC", "ASCII", "ASCII_VAL"))
return ascii((Field) parseFieldParenthesised(S));
else if (parseFunctionNameIf("ACOS"))
return acos((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("ASIN"))
return asin((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("ATAN", "ATN"))
return atan((Field) parseFieldNumericOpParenthesised());
else if ((field = parseFieldAtan2If()) != null)
return field;
if (S.is(type))
if (parseFunctionNameIf("ASCII_CHAR"))
return chr((Field) parseFieldParenthesised(N));
if (A.is(type))
if ((field = parseArrayValueConstructorIf()) != null)
return field;
if ((field = parseFieldArrayGetIf()) != null)
return field;
break;
case 'B':
if (N.is(type))
if (parseFunctionNameIf("BIT_LENGTH"))
return bitLength((Field) parseFieldParenthesised(S));
else if (parseFunctionNameIf("BIT_COUNT"))
return bitCount((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("BYTE_LENGTH"))
return octetLength((Field) parseFieldParenthesised(S));
else if ((field = parseFieldBitwiseFunctionIf()) != null)
return field;
if (B.is(type))
if ((value = parseBitLiteralIf()) != null)
return DSL.inline((Boolean) value);
break;
case 'C':
if (S.is(type))
if ((field = parseFieldConcatIf()) != null)
return field;
else if ((parseFunctionNameIf("CURRENT_CATALOG") && parse('(') && parse(')')))
return currentCatalog();
else if ((parseFunctionNameIf("CURRENT_DATABASE") && parse('(') && parse(')')))
return currentCatalog();
else if ((parseKeywordIf("CURRENT_SCHEMA", "CURRENT SCHEMA")) && (parseIf('(') && parse(')') || true))
return currentSchema();
else if ((parseKeywordIf("CURRENT_USER", "CURRENT USER", "CURRENTUSER")) && (parseIf('(') && parse(')') || true))
return currentUser();
else if (parseFunctionNameIf("CHR", "CHAR"))
return chr((Field) parseFieldParenthesised(N));
if (N.is(type))
if ((field = parseFieldCharIndexIf()) != null)
return field;
else if (parseFunctionNameIf("CHAR_LENGTH"))
return charLength((Field) parseFieldParenthesised(S));
else if (parseFunctionNameIf("CARDINALITY"))
return cardinality((Field) parseFieldParenthesised(A));
else if (parseFunctionNameIf("CEILING", "CEIL"))
return ceil((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("COSH"))
return cosh((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("COS"))
return cos((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("COTH"))
return coth((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("COT"))
return cot((Field) parseFieldNumericOpParenthesised());
else if ((field = parseNextvalCurrvalIf(SequenceMethod.CURRVAL)) != null)
return field;
else if (parseFunctionNameIf("CENTURY"))
return century(parseFieldParenthesised(D));
if (D.is(type))
if ((parseKeywordIf("CURRENT_DATE") || parseKeywordIf("CURRENT DATE")) && (parseIf('(') && parse(')') || true))
return currentDate();
else if (parseKeywordIf("CURRENT_TIMESTAMP") || parseKeywordIf("CURRENT TIMESTAMP")) {
Field<Integer> precision = null;
if (parseIf('('))
if (!parseIf(')')) {
precision = (Field<Integer>) parseField(N);
parse(')');
}
return precision != null ? currentTimestamp(precision) : currentTimestamp();
}
else if ((parseKeywordIf("CURRENT_TIME") || parseKeywordIf("CURRENT TIME")) && (parseIf('(') && parse(')') || true))
return currentTime();
else if (parseFunctionNameIf("CURDATE") && parse('(') && parse(')'))
return currentDate();
else if (parseFunctionNameIf("CURTIME") && parse('(') && parse(')'))
return currentTime();
if ((field = parseFieldCaseIf()) != null)
return field;
else if ((field = parseFieldCastIf()) != null)
return field;
else if ((field = parseFieldCoalesceIf()) != null)
return field;
else if ((field = parseFieldCumeDistIf()) != null)
return field;
else if ((field = parseFieldConvertIf()) != null)
return field;
else if ((field = parseFieldChooseIf()) != null)
return field;
else if (parseKeywordIf("CONNECT_BY_ISCYCLE") && requireProEdition()) {
}
else if (parseKeywordIf("CONNECT_BY_ISLEAF") && requireProEdition()) {
}
break;
case 'D':
if (S.is(type))
if ((parseFunctionNameIf("DB_NAME") && parse('(') && parse(')')))
return currentCatalog();
else if ((parseFunctionNameIf("DBINFO") && parse('(') && parseStringLiteral("dbname") != null && parse(')')))
return currentCatalog();
else if (parseFunctionNameIf("DIGITS"))
return digits((Field) parseFieldParenthesised(N));
if (D.is(type))
if ((field = parseFieldDateLiteralIf()) != null)
return field;
else if ((field = parseFieldDateTruncIf()) != null)
return field;
else if ((field = parseFieldDateAddIf()) != null)
return field;
else if ((field = parseFieldDateDiffIf()) != null)
return field;
else if ((field = parseFieldDatePartIf()) != null)
return field;
if (N.is(type))
if ((field = parseFieldDenseRankIf()) != null)
return field;
else if (parseFunctionNameIf("DECADE"))
return decade(parseFieldParenthesised(D));
else if (parseFunctionNameIf("DAY")
|| parseFunctionNameIf("DAYOFMONTH"))
return day(parseFieldParenthesised(D));
// DB2 and MySQL support the non-ISO version where weeks go from Sunday = 1 to Saturday = 7
else if (parseFunctionNameIf("DAYOFWEEK_ISO"))
return isoDayOfWeek(parseFieldParenthesised(D));
else if (parseFunctionNameIf("DAYOFWEEK")
|| parseFunctionNameIf("DAY_OF_WEEK"))
return dayOfWeek(parseFieldParenthesised(D));
else if (parseFunctionNameIf("DAYOFYEAR")
|| parseFunctionNameIf("DAY_OF_YEAR"))
return dayOfYear(parseFieldParenthesised(D));
else if (parseFunctionNameIf("DEGREES")
|| parseFunctionNameIf("DEGREE")
|| parseFunctionNameIf("DEG"))
return deg((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("DATALENGTH"))
return octetLength((Field) parseFieldParenthesised(S));
if ((field = parseFieldDecodeIf()) != null)
return field;
break;
case 'E':
// [#6704] PostgreSQL E'...' escaped string literals
if (S.is(type))
if (characterNext() == '\'')
return inline(parseStringLiteral());
if (N.is(type))
if ((field = parseFieldExtractIf()) != null)
return field;
else if (parseFunctionNameIf("EXP"))
return exp((Field) parseFieldNumericOpParenthesised());
if (D.is(type))
if (parseFunctionNameIf("EPOCH"))
return epoch(parseFieldParenthesised(D));
break;
case 'F':
if (N.is(type))
if (parseFunctionNameIf("FLOOR"))
return floor((Field) parseFieldNumericOpParenthesised());
if ((field = parseFieldFirstValueIf()) != null)
return field;
else if ((field = parseFieldFieldIf()) != null)
return field;
break;
case 'G':
if (D.is(type))
if (parseKeywordIf("GETDATE") && parse('(') && parse(')'))
return currentTimestamp();
if ((field = parseFieldGreatestIf()) != null)
return field;
else if (N.is(type) && (field = parseFieldGroupIdIf()) != null)
return field;
else if (N.is(type) && (field = parseFieldGroupingIdIf()) != null)
return field;
else if (N.is(type) && (field = parseFieldGroupingIf()) != null)
return field;
else
break;
case 'H':
if (N.is(type))
if (parseFunctionNameIf("HOUR"))
return hour(parseFieldParenthesised(D));
break;
case 'I':
if (D.is(type))
if ((field = parseFieldIntervalLiteralIf()) != null)
return field;
else if (parseFunctionNameIf("ISO_DAY_OF_WEEK"))
return isoDayOfWeek(parseFieldParenthesised(D));
if (N.is(type))
if ((field = parseFieldInstrIf()) != null)
return field;
if (S.is(type))
if ((field = parseFieldInsertIf()) != null)
return field;
if ((field = parseFieldIfnullIf()) != null)
return field;
else if ((field = parseFieldIsnullIf()) != null)
return field;
else if ((field = parseFieldIfIf()) != null)
return field;
else
break;
case 'J':
if (J.is(type))
if ((field = parseFieldJSONArrayConstructorIf()) != null)
return field;
else if ((field = parseFieldJSONObjectConstructorIf()) != null)
return field;
else if ((field = parseFieldJSONValueIf()) != null)
return field;
break;
case 'L':
if (S.is(type))
if (parseFunctionNameIf("LOWER", "LCASE"))
return lower((Field) parseFieldParenthesised(S));
else if ((field = parseFieldLpadIf()) != null)
return field;
else if ((field = parseFieldLtrimIf()) != null)
return field;
else if ((field = parseFieldLeftIf()) != null)
return field;
if (N.is(type))
if (parseFunctionNameIf("LENGTH", "LEN"))
return length((Field) parseFieldParenthesised(S));
else if (parseFunctionNameIf("LENGTHB"))
return octetLength((Field) parseFieldParenthesised(S));
else if (parseFunctionNameIf("LN", "LOGN"))
return ln((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("LOG10"))
return log10((Field) parseFieldNumericOpParenthesised());
else if ((field = parseFieldLogIf()) != null)
return field;
else if ((field = parseFieldLocateIf()) != null)
return field;
else if (parseKeywordIf("LEVEL") && requireProEdition()) {
}
else if ((field = parseFieldShlIf()) != null)
return field;
if ((field = parseFieldLeastIf()) != null)
return field;
else if ((field = parseFieldLeadLagIf()) != null)
return field;
else if ((field = parseFieldLastValueIf()) != null)
return field;
break;
case 'M':
if (N.is(type))
if ((field = parseFieldModIf()) != null)
return field;
else if (parseFunctionNameIf("MICROSECOND"))
return microsecond(parseFieldParenthesised(D));
else if (parseFunctionNameIf("MILLENNIUM"))
return millennium(parseFieldParenthesised(D));
else if (parseFunctionNameIf("MILLISECOND"))
return millisecond(parseFieldParenthesised(D));
else if (parseFunctionNameIf("MINUTE"))
return minute(parseFieldParenthesised(D));
else if (parseFunctionNameIf("MONTH"))
return month(parseFieldParenthesised(D));
if (S.is(type))
if ((field = parseFieldMidIf()) != null)
return field;
else if (parseFunctionNameIf("MD5"))
return md5((Field) parseFieldParenthesised(S));
if ((field = parseFieldGreatestIf()) != null)
return field;
else if ((field = parseFieldLeastIf()) != null)
return field;
break;
case 'N':
// [#9540] N'...' NVARCHAR literals
if (S.is(type))
if (characterNext() == '\'')
return inline(parseStringLiteral(), NVARCHAR);
if ((field = parseFieldNvl2If()) != null)
return field;
else if ((field = parseFieldNvlIf()) != null)
return field;
else if ((field = parseFieldNullifIf()) != null)
return field;
else if ((field = parseFieldNtileIf()) != null)
return field;
else if ((field = parseFieldNthValueIf()) != null)
return field;
else if ((field = parseNextValueIf()) != null)
return field;
else if ((field = parseNextvalCurrvalIf(SequenceMethod.NEXTVAL)) != null)
return field;
else if (parseFunctionNameIf("NOW") && parse('(')) {
if (parseIf(')'))
return now();
Field<Integer> precision = (Field<Integer>) parseField(N);
parse(')');
return now(precision);
}
break;
case 'O':
if (S.is(type))
if ((field = parseFieldReplaceIf()) != null)
return field;
else if ((field = parseFieldOverlayIf()) != null)
return field;
if (N.is(type))
if (parseFunctionNameIf("OCTET_LENGTH"))
return octetLength((Field) parseFieldParenthesised(S));
break;
case 'P':
if (N.is(type))
if ((field = parseFieldPositionIf()) != null)
return field;
else if ((field = parseFieldPercentRankIf()) != null)
return field;
else if ((field = parseFieldPowerIf()) != null)
return field;
else if (parseFunctionNameIf("PI") && parse('(') && parse(')'))
return pi();
if (parseKeywordIf("PRIOR") && requireProEdition()) {
}
break;
case 'Q':
if (S.is(type))
if (characterNext() == '\'')
return inline(parseStringLiteral());
if (D.is(type))
if (parseFunctionNameIf("QUARTER"))
return quarter(parseFieldParenthesised(D));
case 'R':
if (S.is(type))
if ((field = parseFieldReplaceIf()) != null)
return field;
else if ((field = parseFieldRegexpReplaceIf()) != null)
return field;
else if ((field = parseFieldRepeatIf()) != null)
return field;
else if (parseFunctionNameIf("REVERSE"))
return reverse((Field) parseFieldParenthesised(S));
else if ((field = parseFieldRpadIf()) != null)
return field;
else if ((field = parseFieldRtrimIf()) != null)
return field;
else if ((field = parseFieldRightIf()) != null)
return field;
if (N.is(type))
if ((field = parseFieldRowNumberIf()) != null)
return field;
else if ((field = parseFieldRankIf()) != null)
return field;
else if ((field = parseFieldRoundIf()) != null)
return field;
else if (parseKeywordIf("ROWNUM") && requireProEdition()) {
}
else if (parseFunctionNameIf("RADIANS")
|| parseFunctionNameIf("RADIAN")
|| parseFunctionNameIf("RAD"))
return rad((Field) parseFieldNumericOpParenthesised());
else if ((field = parseFieldRandIf()) != null)
return field;
else if ((field = parseFieldRatioToReportIf()) != null)
return field;
else if ((field = parseFieldShrIf()) != null)
return field;
if (parseFunctionNameIf("ROW"))
return parseTuple();
break;
case 'S':
if (S.is(type))
if ((field = parseFieldSubstringIf()) != null)
return field;
else if (parseFunctionNameIf("SPACE"))
return space((Field) parseFieldParenthesised(N));
else if ((field = parseFieldSplitPartIf()) != null)
return field;
else if ((field = parseFieldReplaceIf()) != null)
return field;
else if (parseFunctionNameIf("SCHEMA") && parseIf('(') && parse(')'))
return currentSchema();
else if (parseFunctionNameIf("STRREVERSE"))
return reverse((Field) parseFieldParenthesised(S));
if (N.is(type))
if (parseFunctionNameIf("SECOND"))
return second(parseFieldParenthesised(D));
else if (parseFunctionNameIf("SIGN", "SGN"))
return sign((Field) parseFieldParenthesised(N));
else if (parseFunctionNameIf("SQRT", "SQR"))
return sqrt((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("SQUARE"))
return square((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("SINH"))
return sinh((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("SIN"))
return sin((Field) parseFieldNumericOpParenthesised());
else if ((field = parseFieldShlIf()) != null)
return field;
else if ((field = parseFieldShrIf()) != null)
return field;
if ((field = parseFieldSysConnectByPathIf()) != null)
return field;
break;
case 'T':
if (B.is(type))
if ((field = parseBooleanValueExpressionIf()) != null)
return field;
if (S.is(type))
if ((field = parseFieldTrimIf()) != null)
return field;
else if ((field = parseFieldTranslateIf()) != null)
return field;
else if ((field = parseFieldToCharIf()) != null)
return field;
if (N.is(type))
if (parseFunctionNameIf("TANH"))
return tanh((Field) parseFieldNumericOpParenthesised());
else if (parseFunctionNameIf("TAN"))
return tan((Field) parseFieldNumericOpParenthesised());
else if ((field = parseFieldToNumberIf()) != null)
return field;
else if (parseFunctionNameIf("TIMEZONE_HOUR"))
return timezoneHour(parseFieldParenthesised(D));
else if (parseFunctionNameIf("TIMEZONE_MINUTE"))
return timezoneMinute(parseFieldParenthesised(D));
else if (parseFunctionNameIf("TIMEZONE"))
return timezone(parseFieldParenthesised(D));
if (D.is(type))
if ((field = parseFieldTimestampLiteralIf()) != null)
return field;
else if ((field = parseFieldTimeLiteralIf()) != null)
return field;
else if ((field = parseFieldToDateIf()) != null)
return field;
else if ((field = parseFieldToTimestampIf()) != null)
return field;
else if ((field = parseFieldTimestampDiffIf()) != null)
return field;
if (N.is(type) || D.is(type))
if ((field = parseFieldTruncIf()) != null)
return field;
break;
case 'U':
if (S.is(type))
if (parseFunctionNameIf("UPPER")
|| parseFunctionNameIf("UCASE"))
return DSL.upper((Field) parseFieldParenthesised(S));
if (D.is(type))
if (parseFunctionNameIf("UNIX_TIMESTAMP"))
return epoch(parseFieldParenthesised(D));
break;
case 'W':
if (N.is(type))
if ((field = parseFieldWidthBucketIf()) != null)
return field;
else if (parseFunctionNameIf("WEEK"))
return week(parseFieldParenthesised(D));
break;
case 'X':
if (Y.is(type))
if ((value = parseBinaryLiteralIf()) != null)
return inline((byte[]) value);
if (X.is(type))
if ((field = parseFieldXMLCommentIf()) != null)
return field;
else if ((field = parseFieldXMLConcatIf()) != null)
return field;
else if ((field = parseFieldXMLElementIf()) != null)
return field;
else if ((field = parseFieldXMLPIIf()) != null)
return field;
else if ((field = parseFieldXMLForestIf()) != null)
return field;
else if ((field = parseFieldXMLParseIf()) != null)
return field;
else if ((field = parseFieldXMLDocumentIf()) != null)
return field;
else if ((field = parseFieldXMLQueryIf()) != null)
return field;
break;
case 'Y':
if (N.is(type))
if (parseFunctionNameIf("YEAR"))
return year(parseFieldParenthesised(D));
break;
case 'Z':
if (N.is(type))
if (parseFunctionNameIf("ZEROIFNULL"))
return coalesce(parseFieldParenthesised(type), zero());
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(Sign.NONE)) != null)
return field;
break;
case '{':
parse('{', false);
switch (characterUpper()) {
case 'D':
parseKeyword("D");
field = inline(parseDateLiteral());
break;
case 'F':
parseKeyword("FN");
// TODO: Limit the supported expressions in this context to the ones specified here:
// http://download.oracle.com/otn-pub/jcp/jdbc-4_2-mrel2-eval-spec/jdbc4.2-fr-spec.pdf
field = parseTerm(type);
break;
case 'T':
if (parseKeywordIf("TS")) {
field = inline(parseTimestampLiteral());
}
else {
parseKeyword("T");
field = inline(parseTimeLiteral());
}
break;
default:
throw exception("Unsupported JDBC escape literal");
}
parse('}');
return field;
case '(':
// A term parenthesis can mark the beginning of any of:
// - ROW expression without ROW keyword: E.g. (1, 2)
// - Parenthesised field expression: E.g. (1 + 2)
// - A correlated subquery: E.g. (select 1)
// - A correlated subquery with nested set ops: E.g. ((select 1) except (select 2))
// - A combination of the above: E.g. ((select 1) + 2, ((select 1) except (select 2)) + 2)
int p = position();
try {
if (peekSelect(true)) {
parse('(');
SelectQueryImpl<Record> select = parseSelect();
parse(')');
if (Tools.degree(select) != 1)
throw exception("Select list must contain exactly one column");
return field((Select) select);
}
}
catch (ParserException e) {
// TODO: Find a better solution than backtracking, here, which doesn't complete in O(N)
if (e.getMessage().contains("Token ')' expected"))
position(p);
else
throw e;
}
parse('(');
FieldOrRow r = parseFieldOrRow(type);
List<Field<?>> list = null;
if (r instanceof Field) {
while (parseIf(',')) {
if (list == null) {
list = new ArrayList<>();
list.add((Field) r);
}
// TODO Allow for nesting ROWs
list.add(parseField(type));
}
}
parse(')');
return list != null ? row(list) : r;
}
if ((field = parseAggregateFunctionIf()) != null)
return field;
else if ((field = parseBooleanValueExpressionIf()) != null)
return field;
else
return parseFieldNameOrSequenceExpression();
}
private final boolean peekSelectOrWith(boolean peekIntoParens) {
return peekKeyword("WITH", false, peekIntoParens, false) || peekSelect(peekIntoParens);
}
private final boolean peekSelect(boolean peekIntoParens) {
return peekKeyword("SELECT", false, peekIntoParens, false) ||
peekKeyword("SEL", false, peekIntoParens, false);
}
private final Field<?> parseFieldSplitPartIf() {
if (parseKeywordIf("SPLIT_PART")) {
parse('(');
Field<?> f1 = parseField(S);
parse(',');
Field<?> f2 = parseField(S);
parse(',');
Field<?> f3 = parseField(N);
parse(')');
return splitPart((Field) f1, (Field) f2, (Field) f3);
}
return null;
}
private final Field<?> parseFieldShlIf() {
if (parseKeywordIf("SHL") || parseKeywordIf("SHIFTLEFT") || parseKeywordIf("LSHIFT")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return shl((Field) x, (Field) y);
}
return null;
}
private final Field<?> parseFieldShrIf() {
if (parseKeywordIf("SHR") || parseKeywordIf("SHIFTRIGHT") || parseKeywordIf("RSHIFT")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return shr((Field) x, (Field) y);
}
return null;
}
private final Field<?> parseFieldSysConnectByPathIf() {
if (parseFunctionNameIf("SYS_CONNECT_BY_PATH") && requireProEdition()) {
}
return null;
}
private final Field<?> parseFieldBitwiseFunctionIf() {
int p = position();
char c1 = character(p + 1);
char c2 = character(p + 2);
if (c1 != 'I' && c1 != 'i')
return null;
if (c2 != 'T' && c2 != 't' && c2 != 'N' && c2 != 'n')
return null;
if (parseKeywordIf("BIT_AND") || parseKeywordIf("BITAND") || parseKeywordIf("BIN_AND")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return bitAnd((Field) x, (Field) y);
}
else if (parseKeywordIf("BIT_NAND") || parseKeywordIf("BITNAND") || parseKeywordIf("BIN_NAND")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return bitNand((Field) x, (Field) y);
}
else if (parseKeywordIf("BIT_OR") || parseKeywordIf("BITOR") || parseKeywordIf("BIN_OR")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return bitOr((Field) x, (Field) y);
}
else if (parseKeywordIf("BIT_NOR") || parseKeywordIf("BITNOR") || parseKeywordIf("BIN_NOR")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return bitNor((Field) x, (Field) y);
}
else if (parseKeywordIf("BIT_XOR") || parseKeywordIf("BITXOR") || parseKeywordIf("BIN_XOR")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return bitXor((Field) x, (Field) y);
}
else if (parseKeywordIf("BIT_XNOR") || parseKeywordIf("BITXNOR") || parseKeywordIf("BIN_XNOR")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return bitXNor((Field) x, (Field) y);
}
else if (parseKeywordIf("BIT_NOT") || parseKeywordIf("BITNOT") || parseKeywordIf("BIN_NOT")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(')');
return bitNot((Field) x);
}
else if (parseKeywordIf("BIN_SHL")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return shl((Field) x, (Field) y);
}
else if (parseKeywordIf("BIN_SHR")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return shr((Field) x, (Field) y);
}
return null;
}
private final Field<?> parseNextValueIf() {
if (parseKeywordIf("NEXT VALUE FOR"))
return sequence(parseName()).nextval();
return null;
}
private final Field<?> parseNextvalCurrvalIf(SequenceMethod method) {
if (parseFunctionNameIf(method.name())) {
parse('(');
Name name = parseNameIf();
Sequence s = name != null
? sequence(name)
: sequence(dsl.parser().parseName(parseStringLiteral()));
parse(')');
if (method == SequenceMethod.NEXTVAL)
return s.nextval();
else if (method == SequenceMethod.CURRVAL)
return s.currval();
else
throw exception("Only NEXTVAL and CURRVAL methods supported");
}
return null;
}
private static enum SequenceMethod {
NEXTVAL,
CURRVAL;
}
private final Field<?> parseFieldXMLCommentIf() {
if (parseFunctionNameIf("XMLCOMMENT")) {
parse('(');
Field<String> comment = (Field<String>) parseField();
parse(')');
return xmlcomment(comment);
}
return null;
}
private final Field<?> parseFieldXMLConcatIf() {
if (parseFunctionNameIf("XMLCONCAT")) {
parse('(');
List<Field<?>> fields = parseFields();
parse(')');
return xmlconcat(fields);
}
return null;
}
private final Field<?> parseFieldXMLElementIf() {
if (parseFunctionNameIf("XMLELEMENT")) {
parse('(');
parseKeywordIf("NAME");
if (parseIf(')'))
return xmlelement(unquotedName("NAME"));
Name name = parseIdentifier();
XMLAttributes attr = null;
List<Field<?>> content = new ArrayList<>();
while (parseIf(',')) {
if (attr == null && parseKeywordIf("XMLATTRIBUTES")) {
parse('(');
List<Field<?>> attrs = parseAliasedXMLContent();
parse(')');
attr = xmlattributes(attrs);
}
else
content.add(parseField());
}
parse(')');
return attr == null
? xmlelement(name, content)
: xmlelement(name, attr, content);
}
return null;
}
private final Field<?> parseFieldXMLDocumentIf() {
if (parseFunctionNameIf("XMLDOCUMENT") && requireProEdition()) {
}
return null;
}
private final Field<?> parseFieldXMLPIIf() {
if (parseFunctionNameIf("XMLPI")) {
parse('(');
parseKeyword("NAME");
Name target = parseIdentifier();
Field<?> content = parseIf(',') ? parseField() : null;
parse(')');
return content == null ? xmlpi(target) : xmlpi(target, content);
}
return null;
}
private final Field<?> parseFieldXMLForestIf() {
if (parseFunctionNameIf("XMLFOREST")) {
parse('(');
List<Field<?>> content = parseAliasedXMLContent();
parse(')');
return xmlforest(content);
}
return null;
}
private final Field<?> parseFieldXMLParseIf() {
if (parseFunctionNameIf("XMLPARSE")) {
parse('(');
DocumentOrContent documentOrContent;
if (parseKeywordIf("DOCUMENT"))
documentOrContent = DocumentOrContent.DOCUMENT;
else if (parseKeywordIf("CONTENT"))
documentOrContent = DocumentOrContent.CONTENT;
else
throw expected("CONTENT", "DOCUMENT");
Field<String> xml = (Field<String>) parseField();
parse(')');
return documentOrContent == DocumentOrContent.DOCUMENT
? xmlparseDocument(xml)
: xmlparseContent(xml);
}
return null;
}
private final Field<?> parseFieldXMLQueryIf() {
if (parseFunctionNameIf("XMLQUERY")) {
parse('(');
Field<String> xpath = (Field<String>) parseField();
XMLPassingMechanism m = parseXMLPassingMechanism();
Field<XML> xml = (Field<XML>) parseField();
parseKeywordIf("RETURNING CONTENT");
parse(')');
if (m == BY_REF)
return xmlquery(xpath).passingByRef(xml);
else
return xmlquery(xpath).passing(xml);
}
return null;
}
private final XMLPassingMechanism parseXMLPassingMechanism() {
XMLPassingMechanism result = parseXMLPassingMechanismIf();
if (result == null)
throw expected("PASSING");
return result;
}
private final XMLPassingMechanism parseXMLPassingMechanismIf() {
if (!parseKeywordIf("PASSING"))
return null;
else if (!parseKeywordIf("BY"))
return XMLPassingMechanism.DEFAULT;
else if (parseKeywordIf("REF"))
return BY_REF;
else if (parseKeywordIf("VALUE"))
return BY_VALUE;
else
throw expected("REF", "VALUE");
}
private final List<Field<?>> parseAliasedXMLContent() {
List<Field<?>> result = new ArrayList<>();
do {
Field<?> field = parseField();
if (parseKeywordIf("AS"))
field = field.as(parseIdentifier(true));
result.add(field);
}
while (parseIf(','));
return result;
}
private final AggregateFilterStep<?> parseXMLAggFunctionIf() {
if (parseFunctionNameIf("XMLAGG")) {
XMLAggOrderByStep<?> s1;
AggregateFilterStep<?> s2;
parse('(');
s2 = s1 = xmlagg((Field<XML>) parseField());
if (parseKeywordIf("ORDER BY"))
s2 = s1.orderBy(parseSortSpecification());
parse(')');
return s2;
}
return null;
}
private final Field<?> parseFieldJSONValueIf() {
if (parseFunctionNameIf("JSON_VALUE")) {
parse('(');
Field json = parseField();
parse(',');
Field<String> path = (Field<String>) parseField();
JSONValueOnStep<?> s1 = jsonValue(json, path);
JSONValue.Behaviour behaviour = parseJSONValueBehaviourIf();
DataType<?> returning = parseJSONReturningIf();
parse(')');
return returning == null ? s1 : s1.returning(returning);
}
return null;
}
private final JSONValue.Behaviour parseJSONValueBehaviourIf() {
if (parseKeywordIf("ERROR") && requireProEdition())
return JSONValue.Behaviour.ERROR;
else if (parseKeywordIf("NULL") && requireProEdition())
return JSONValue.Behaviour.NULL;
else if (parseKeywordIf("DEFAULT") && requireProEdition())
return JSONValue.Behaviour.DEFAULT;
else
return null;
}
private final JSONExists.Behaviour parseJSONExistsOnErrorBehaviourIf() {
if (parseKeywordIf("ERROR") && parseKeyword("ON ERROR") && requireProEdition())
return JSONExists.Behaviour.ERROR;
else if (parseKeywordIf("TRUE") && parseKeyword("ON ERROR") && requireProEdition())
return JSONExists.Behaviour.TRUE;
else if (parseKeywordIf("FALSE") && parseKeyword("ON ERROR") && requireProEdition())
return JSONExists.Behaviour.FALSE;
else if (parseKeywordIf("UNKNOWN") && parseKeyword("ON ERROR") && requireProEdition())
return JSONExists.Behaviour.UNKNOWN;
else
return null;
}
private final DataType<?> parseJSONReturningIf() {
return parseKeywordIf("RETURNING") ? parseDataType() : null;
}
private final Field<?> parseFieldJSONArrayConstructorIf() {
if (parseFunctionNameIf("JSON_ARRAY")) {
parse('(');
if (parseIf(')'))
return DSL.jsonArray();
List<Field<?>> result = null;
JSONOnNull onNull = parseJSONNullTypeIf();
DataType<?> returning = parseJSONReturningIf();
if (onNull == null && returning == null) {
result = parseFields();
onNull = parseJSONNullTypeIf();
returning = parseJSONReturningIf();
}
parse(')');
JSONArrayNullStep<?> s1 = result == null ? DSL.jsonArray() : DSL.jsonArray(result);
JSONArrayReturningStep<?> s2 = onNull == NULL_ON_NULL
? s1.nullOnNull()
: onNull == ABSENT_ON_NULL
? s1.absentOnNull()
: s1;
return returning == null ? s2 : s2.returning(returning);
}
return null;
}
private final AggregateFilterStep<?> parseJSONArrayAggFunctionIf() {
if (parseFunctionNameIf("JSON_ARRAYAGG")) {
AggregateFilterStep<?> result;
JSONArrayAggOrderByStep<?> s1;
JSONArrayAggNullStep<?> s2;
JSONArrayAggReturningStep<?> s3;
JSONOnNull onNull;
DataType<?> returning;
parse('(');
result = s3 = s2 = s1 = DSL.jsonArrayAgg(parseField());
if (parseKeywordIf("ORDER BY"))
result = s3 = s2 = s1.orderBy(parseSortSpecification());
if ((onNull = parseJSONNullTypeIf()) != null)
result = s3 = onNull == ABSENT_ON_NULL ? s2.absentOnNull() : s2.nullOnNull();
if ((returning = parseJSONReturningIf()) != null)
result = s3.returning(returning);
parse(')');
return result;
}
return null;
}
private final Field<?> parseFieldJSONObjectConstructorIf() {
if (parseFunctionNameIf("JSON_OBJECT")) {
parse('(');
if (parseIf(')'))
return DSL.jsonObject();
List<JSONEntry<?>> result = new ArrayList<>();
JSONOnNull onNull = parseJSONNullTypeIf();
DataType<?> returning = parseJSONReturningIf();
if (onNull == null && returning == null) {
do
result.add(parseJSONEntry());
while (parseIf(','));
onNull = parseJSONNullTypeIf();
returning = parseJSONReturningIf();
}
parse(')');
JSONObjectNullStep<?> s1 = DSL.jsonObject(result);
JSONObjectReturningStep<?> s2 = onNull == NULL_ON_NULL
? s1.nullOnNull()
: onNull == ABSENT_ON_NULL
? s1.absentOnNull()
: s1;
return returning == null ? s2 : s2.returning(returning);
}
return null;
}
private final AggregateFilterStep<?> parseJSONObjectAggFunctionIf() {
if (parseFunctionNameIf("JSON_OBJECTAGG")) {
AggregateFilterStep<?> result;
JSONObjectAggNullStep<?> s1;
JSONObjectAggReturningStep<?> s2;
JSONOnNull onNull;
DataType<?> returning;
parse('(');
result = s2 = s1 = DSL.jsonObjectAgg(parseJSONEntry());
if ((onNull = parseJSONNullTypeIf()) != null)
result = s2 = onNull == ABSENT_ON_NULL ? s1.absentOnNull() : s1.nullOnNull();
if ((returning = parseJSONReturningIf()) != null)
result = s2.returning(returning);
parse(')');
return result;
}
return null;
}
private final JSONOnNull parseJSONNullTypeIf() {
if (parseKeywordIf("NULL ON NULL"))
return NULL_ON_NULL;
else if (parseKeywordIf("ABSENT ON NULL"))
return ABSENT_ON_NULL;
else
return null;
}
private final JSONEntry<?> parseJSONEntry() {
boolean valueRequired = parseKeywordIf("KEY");
Field<String> key = (Field<String>) parseField(Type.S);
if (parseKeywordIf("VALUE"))
;
else if (valueRequired)
throw expected("VALUE");
else
parse(',');
return key(key).value(parseField());
}
private final Field<?> parseArrayValueConstructorIf() {
if (parseKeywordIf("ARRAY")) {
parse('[');
List<Field<?>> fields;
if (parseIf(']')) {
fields = emptyList();
}
else {
fields = parseFields();
parse(']');
}
// Prevent "wrong" javac method bind
return DSL.array((Collection) fields);
}
return null;
}
private final Field<?> parseFieldArrayGetIf() {
if (parseFunctionNameIf("ARRAY_GET")) {
parse('(');
Field f1 = parseField(A);
parse(',');
Field f2 = parseField(N);
parse(')');
return arrayGet(f1, f2);
}
return null;
}
private final Field<?> parseFieldAtan2If() {
if (parseFunctionNameIf("ATN2", "ATAN2")) {
parse('(');
Field<?> x = toField(parseNumericOp(N));
parse(',');
Field<?> y = toField(parseNumericOp(N));
parse(')');
return atan2((Field) x, (Field) y);
}
return null;
}
private final Field<?> parseFieldLogIf() {
if (parseFunctionNameIf("LOG")) {
parse('(');
switch (family()) {
default:
Field<?> base = toField(parseNumericOp(N));
parse(',');
Field<?> value = toField(parseNumericOp(N));
parse(')');
return log((Field) value, (Field) base);
}
}
return null;
}
private final Field<?> parseFieldTruncIf() {
boolean forceNumericPrecision = false;
if (parseFunctionNameIf("TRUNC") || (forceNumericPrecision |= parseFunctionNameIf("TRUNCATE", "TRUNCNUM"))) {
parse('(');
Field<?> arg1 = parseField();
if (forceNumericPrecision && parse(',') || parseIf(',')) {
String part;
if (!forceNumericPrecision && (part = parseStringLiteralIf()) != null) {
part = part.toUpperCase();
DatePart p;
if ("YY".equals(part) || "YYYY".equals(part) || "YEAR".equals(part))
p = DatePart.YEAR;
else if ("MM".equals(part) || "MONTH".equals(part))
p = DatePart.MONTH;
else if ("DD".equals(part))
p = DatePart.DAY;
else if ("HH".equals(part))
p = DatePart.HOUR;
else if ("MI".equals(part))
p = DatePart.MINUTE;
else if ("SS".equals(part))
p = DatePart.SECOND;
else
throw exception("Unsupported date part");
parse(')');
return DSL.trunc((Field) arg1, p);
}
else {
Field<?> arg2 = toField(parseNumericOp(N));
parse(')');
return DSL.trunc((Field) arg1, (Field) arg2);
}
}
parse(')');
// [#10668] Ignore TRUNC() when calling TRUNC(CURRENT_DATE) or TRUNC(SYSDATE) in Oracle
if (arg1 instanceof CurrentDate)
return arg1;
else if (arg1.getDataType().isDateTime())
return DSL.trunc((Field) arg1, DatePart.DAY);
else if (arg1.getDataType().isNumeric())
return DSL.trunc((Field) arg1, inline(0));
// [#9044] By default, assume historic TRUNC(date) behaviour
else
return DSL.trunc((Field) arg1);
}
return null;
}
private final Field<?> parseFieldRoundIf() {
if (parseFunctionNameIf("ROUND")) {
Field arg1 = null;
Field arg2 = null;
parse('(');
arg1 = toField(parseNumericOp(N));
if (parseIf(','))
arg2 = toField(parseNumericOp(N));
parse(')');
return arg2 == null ? round(arg1) : round(arg1, arg2);
}
return null;
}
private final Field<?> parseFieldPowerIf() {
if (parseFunctionNameIf("POWER", "POW")) {
parse('(');
Field arg1 = toField(parseNumericOp(N));
parse(',');
Field arg2 = toField(parseNumericOp(N));
parse(')');
return DSL.power(arg1, arg2);
}
return null;
}
private final Field<?> parseFieldModIf() {
if (parseFunctionNameIf("MOD")) {
parse('(');
Field<?> f1 = parseField(N);
parse(',');
Field<?> f2 = parseField(N);
parse(')');
return f1.mod((Field) f2);
}
return null;
}
private final Field<?> parseFieldWidthBucketIf() {
if (parseFunctionNameIf("WIDTH_BUCKET")) {
parse('(');
Field<?> f1 = parseField(N);
parse(',');
Field<?> f2 = parseField(N);
parse(',');
Field<?> f3 = parseField(N);
parse(',');
Field<?> f4 = parseField(N);
parse(')');
return DSL.widthBucket((Field) f1, (Field) f2, (Field) f3, (Field) f4);
}
return null;
}
private final Field<?> parseFieldLeastIf() {
if (parseFunctionNameIf("LEAST", "MINVALUE")) {
parse('(');
List<Field<?>> fields = parseFields();
parse(')');
return least(fields.get(0), fields.size() > 1 ? fields.subList(1, fields.size()).toArray(EMPTY_FIELD) : EMPTY_FIELD);
}
return null;
}
private final Field<?> parseFieldGreatestIf() {
if (parseFunctionNameIf("GREATEST", "MAXVALUE")) {
parse('(');
List<Field<?>> fields = parseFields();
parse(')');
return greatest(fields.get(0), fields.size() > 1 ? fields.subList(1, fields.size()).toArray(EMPTY_FIELD) : EMPTY_FIELD);
}
return null;
}
private final Field<?> parseFieldGroupingIf() {
if (parseFunctionNameIf("GROUPING")) {
parse('(');
Field<?> field = parseField();
parse(')');
return grouping(field);
}
return null;
}
private final Field<?> parseFieldGroupIdIf() {
if (parseFunctionNameIf("GROUP_ID")) {
requireProEdition();
}
return null;
}
private final Field<?> parseFieldGroupingIdIf() {
if (parseFunctionNameIf("GROUPING_ID") && requireProEdition()) {
}
return null;
}
private final Field<?> parseFieldTimestampLiteralIf() {
int p = position();
if (parseKeywordIf("TIMESTAMP")) {
if (parseKeywordIf("WITHOUT TIME ZONE")) {
return inline(parseTimestampLiteral());
}
else if (parseIf('(')) {
Field<?> f = parseField(S);
parse(')');
return timestamp((Field) f);
}
else if (peek('\'')) {
return inline(parseTimestampLiteral());
}
else {
position(p);
return field(parseIdentifier());
}
}
return null;
}
private final Timestamp parseTimestampLiteral() {
try {
return Timestamp.valueOf(parseStringLiteral());
}
catch (IllegalArgumentException e) {
throw exception("Illegal timestamp literal");
}
}
private final Field<?> parseFieldTimeLiteralIf() {
int p = position();
if (parseKeywordIf("TIME")) {
if (parseKeywordIf("WITHOUT TIME ZONE")) {
return inline(parseTimeLiteral());
}
else if (parseIf('(')) {
Field<?> f = parseField(S);
parse(')');
return time((Field) f);
}
else if (peek('\'')) {
return inline(parseTimeLiteral());
}
else {
position(p);
return field(parseIdentifier());
}
}
return null;
}
private final Time parseTimeLiteral() {
try {
return Time.valueOf(parseStringLiteral());
}
catch (IllegalArgumentException e) {
throw exception("Illegal time literal");
}
}
private final Field<?> parseFieldIntervalLiteralIf() {
int p = position();
if (parseKeywordIf("INTERVAL")) {
if (peek('\'')) {
return inline(parseIntervalLiteral());
}
else {
Long interval = parseUnsignedIntegerIf();
if (interval != null) {
DatePart part = parseIntervalDatePart();
long l = interval;
int i = (int) l;
switch (part) {
case YEAR:
return inline(new YearToMonth(i));
case QUARTER:
return inline(new YearToMonth(0, 3 * i));
case MONTH:
return inline(new YearToMonth(0, i));
case WEEK:
return inline(new DayToSecond(7 * i));
case DAY:
return inline(new DayToSecond(i));
case HOUR:
return inline(new DayToSecond(0, i));
case MINUTE:
return inline(new DayToSecond(0, 0, i));
case SECOND:
return inline(new DayToSecond(0, 0, 0, i));
case MILLISECOND:
return inline(new DayToSecond(0, 0, 0, (int) (l / 1000), (int) (l % 1000 * 1000000)));
case MICROSECOND:
return inline(new DayToSecond(0, 0, 0, (int) (l / 1000000), (int) (l % 1000000 * 1000)));
case NANOSECOND:
return inline(new DayToSecond(0, 0, 0, (int) (l / 1000000000), (int) (l % 1000000000)));
}
}
else {
position(p);
return field(parseIdentifier());
}
}
}
return null;
}
private final Interval parsePostgresIntervalLiteralIf() {
int p = position();
p:
if (parseIf('\'')) {
parseIf('@');
Number year = null;
Number month = null;
Number day = null;
Number hour = null;
Number minute = null;
Number second = null;
do {
boolean minus = parseIf('-');
if (!minus)
parseIf('+');
Number n = parseUnsignedNumericLiteralIf(minus ? Sign.MINUS : Sign.NONE);
if (n == null)
break p;
switch (characterUpper()) {
case 'D':
if (parseKeywordIf("D") ||
parseKeywordIf("DAY") ||
parseKeywordIf("DAYS"))
if (day == null)
day = n;
else
throw exception("Day part already defined");
break;
case 'H':
if (parseKeywordIf("H") ||
parseKeywordIf("HOUR") ||
parseKeywordIf("HOURS"))
if (hour == null)
hour = n;
else
throw exception("Hour part already defined");
break;
case 'M':
if (parseKeywordIf("M") ||
parseKeywordIf("MIN") ||
parseKeywordIf("MINS") ||
parseKeywordIf("MINUTE") ||
parseKeywordIf("MINUTES"))
if (minute == null)
minute = n;
else
throw exception("Minute part already defined");
else if (parseKeywordIf("MON") ||
parseKeywordIf("MONS") ||
parseKeywordIf("MONTH") ||
parseKeywordIf("MONTHS"))
if (month == null)
month = n;
else
throw exception("Month part already defined");
break;
case 'S':
if (parseKeywordIf("S") ||
parseKeywordIf("SEC") ||
parseKeywordIf("SECS") ||
parseKeywordIf("SECOND") ||
parseKeywordIf("SECONDS"))
if (second == null)
second = n;
else
throw exception("Second part already defined");
break;
case 'Y':
if (parseKeywordIf("Y") ||
parseKeywordIf("YEAR") ||
parseKeywordIf("YEARS"))
if (year == null)
year = n;
else
throw exception("Year part already defined");
break;
default:
break p;
}
}
while (!parseIf('\''));
int months = (month == null ? 0 : month.intValue())
+ (year == null ? 0 : (int) (year.doubleValue() * 12));
double seconds = (month == null ? 0.0 : ((month.doubleValue() % 1.0) * 30 * 86400))
+ (day == null ? 0.0 : ((day.doubleValue() * 86400)))
+ (hour == null ? 0.0 : ((hour.doubleValue() * 3600)))
+ (minute == null ? 0.0 : ((minute.doubleValue() * 60)))
+ (second == null ? 0.0 : ((second.doubleValue())));
return new YearToSecond(
new YearToMonth(0, months),
new DayToSecond(0, 0, 0, (int) seconds, (int) ((seconds % 1.0) * 1000000000))
);
}
position(p);
return null;
}
private final Interval parseIntervalLiteral() {
Interval result = parsePostgresIntervalLiteralIf();
if (result != null)
return result;
String string = parseStringLiteral();
String message = "Illegal interval literal";
if (parseKeywordIf("YEAR"))
if (parseKeywordIf("TO") && parseKeyword("MONTH"))
return requireNotNull(YearToMonth.yearToMonth(string), message);
else
return requireNotNull(YearToMonth.year(string), message);
else if (parseKeywordIf("MONTH"))
return requireNotNull(YearToMonth.month(string), message);
else if (parseKeywordIf("DAY"))
if (parseKeywordIf("TO"))
if (parseKeywordIf("SECOND"))
return requireNotNull(DayToSecond.dayToSecond(string), message);
else if (parseKeywordIf("MINUTE"))
return requireNotNull(DayToSecond.dayToMinute(string), message);
else if (parseKeywordIf("HOUR"))
return requireNotNull(DayToSecond.dayToHour(string), message);
else
throw expected("HOUR", "MINUTE", "SECOND");
else
return requireNotNull(DayToSecond.day(string), message);
else if (parseKeywordIf("HOUR"))
if (parseKeywordIf("TO"))
if (parseKeywordIf("SECOND"))
return requireNotNull(DayToSecond.hourToSecond(string), message);
else if (parseKeywordIf("MINUTE"))
return requireNotNull(DayToSecond.hourToMinute(string), message);
else
throw expected("MINUTE", "SECOND");
else
return requireNotNull(DayToSecond.hour(string), message);
else if (parseKeywordIf("MINUTE"))
if (parseKeywordIf("TO") && parseKeyword("SECOND"))
return requireNotNull(DayToSecond.minuteToSecond(string), message);
else
return requireNotNull(DayToSecond.minute(string), message);
else if (parseKeywordIf("SECOND"))
return requireNotNull(DayToSecond.second(string), message);
DayToSecond ds = DayToSecond.valueOf(string);
if (ds != null)
return ds;
YearToMonth ym = YearToMonth.valueOf(string);
if (ym != null)
return ym;
YearToSecond ys = YearToSecond.valueOf(string);
if (ys != null)
return ys;
throw exception(message);
}
private final <T> T requireNotNull(T value, String message) {
if (value != null)
return value;
else
throw exception(message);
}
private final Field<?> parseFieldDateLiteralIf() {
int p = position();
if (parseKeywordIf("DATE")) {
if (parseIf('(')) {
Field<?> f = parseField(S);
parse(')');
return date((Field) f);
}
else if (peek('\'')) {
return inline(parseDateLiteral());
}
else {
position(p);
return field(parseIdentifier());
}
}
return null;
}
private final Field<?> parseFieldDateTruncIf() {
if (parseFunctionNameIf("DATE_TRUNC")) {
parse('(');
parse('\'');
DatePart part = parseDatePart();
parse('\'');
parse(',');
Field<?> field = parseField(D);
parse(')');
return trunc(field, part);
}
return null;
}
private final Field<?> parseFieldDateAddIf() {
boolean sub = false;
// SQL Server style
if (parseFunctionNameIf("DATEADD")) {
parse('(');
DatePart part = parseDatePart();
parse(',');
Field<Number> interval = (Field<Number>) parseField(Type.N);
parse(',');
Field<Date> date = (Field<Date>) parseField(Type.D);
parse(')');
return DSL.dateAdd(date, interval, part);
}
// MySQL style
else if (parseFunctionNameIf("DATE_ADD") || (sub = parseFunctionNameIf("DATE_SUB"))) {
parse('(');
Field<?> date = parseField(Type.D);
parse(',');
// [#8792] TODO: Support parsing interval expressions
Field<?> interval = parseFieldIntervalLiteralIf();
parse(')');
return sub ? date.sub(interval) : date.add(interval);
}
return null;
}
private final Field<?> parseFieldDateDiffIf() {
if (parseFunctionNameIf("DATEDIFF")) {
parse('(');
DatePart datePart = parseDatePartIf();
if (datePart != null)
parse(',');
Field<Date> d1 = (Field<Date>) parseField(Type.D);
if (parseIf(',')) {
Field<Date> d2 = (Field<Date>) parseField(Type.D);
parse(')');
if (datePart != null)
return DSL.dateDiff(datePart, d1, d2);
else
return DSL.dateDiff(d1, d2);
}
parse(')');
if (datePart != null)
return DSL.dateDiff((Field) field(datePart.toName()), d1);
else
throw unsupportedClause();
}
return null;
}
private final Date parseDateLiteral() {
try {
return Date.valueOf(parseStringLiteral());
}
catch (IllegalArgumentException e) {
throw exception("Illegal date literal");
}
}
private final Field<?> parseFieldExtractIf() {
if (parseFunctionNameIf("EXTRACT")) {
parse('(');
DatePart part = parseDatePart();
parseKeyword("FROM");
Field<?> field = parseField();
parse(')');
return extract(field, part);
}
return null;
}
private final Field<?> parseFieldDatePartIf() {
if (parseFunctionNameIf("DATEPART")) {
parse('(');
DatePart part = parseDatePart();
parse(',');
Field<?> field = parseField();
parse(')');
return extract(field, part);
}
return null;
}
private final DatePart parseDatePart() {
DatePart result = parseDatePartIf();
if (result == null)
throw expected("DatePart");
return result;
}
private final DatePart parseDatePartIf() {
char character = characterUpper();
switch (character) {
case 'C':
if (parseKeywordIf("CENTURY") ||
parseKeywordIf("CENTURIES"))
return DatePart.CENTURY;
break;
case 'D':
if (parseKeywordIf("DAYOFYEAR") ||
parseKeywordIf("DAY_OF_YEAR") ||
parseKeywordIf("DOY") ||
parseKeywordIf("DY"))
return DatePart.DAY_OF_YEAR;
else if (parseKeywordIf("DAY_OF_WEEK") ||
parseKeywordIf("DAYOFWEEK") ||
parseKeywordIf("DW"))
return DatePart.DAY_OF_WEEK;
else if (parseKeywordIf("DAY") ||
parseKeywordIf("DAYS") ||
parseKeywordIf("DD") ||
parseKeywordIf("D"))
return DatePart.DAY;
else if (parseKeywordIf("DECADE") ||
parseKeywordIf("DECADES"))
return DatePart.DECADE;
break;
case 'E':
if (parseKeywordIf("EPOCH"))
return DatePart.EPOCH;
break;
case 'H':
if (parseKeywordIf("HOUR") ||
parseKeywordIf("HOURS") ||
parseKeywordIf("HH"))
return DatePart.HOUR;
break;
case 'I':
if (parseKeywordIf("ISODOW") ||
parseKeywordIf("ISO_DAY_OF_WEEK"))
return DatePart.ISO_DAY_OF_WEEK;
case 'M':
if (parseKeywordIf("MINUTE") ||
parseKeywordIf("MINUTES") ||
parseKeywordIf("MI"))
return DatePart.MINUTE;
else if (parseKeywordIf("MILLENNIUM") ||
parseKeywordIf("MILLENNIUMS") ||
parseKeywordIf("MILLENNIA"))
return DatePart.MILLENNIUM;
else if (parseKeywordIf("MICROSECOND") ||
parseKeywordIf("MICROSECONDS") ||
parseKeywordIf("MCS"))
return DatePart.MICROSECOND;
else if (parseKeywordIf("MILLISECOND") ||
parseKeywordIf("MILLISECONDS") ||
parseKeywordIf("MS"))
return DatePart.MILLISECOND;
else if (parseKeywordIf("MONTH") ||
parseKeywordIf("MONTHS") ||
parseKeywordIf("MM") ||
parseKeywordIf("M"))
return DatePart.MONTH;
break;
case 'N':
if (parseKeywordIf("N"))
return DatePart.MINUTE;
else if (parseKeywordIf("NANOSECOND") ||
parseKeywordIf("NANOSECONDS") ||
parseKeywordIf("NS"))
return DatePart.NANOSECOND;
break;
case 'Q':
if (parseKeywordIf("QUARTER") ||
parseKeywordIf("QUARTERS") ||
parseKeywordIf("QQ") ||
parseKeywordIf("Q"))
return DatePart.QUARTER;
break;
case 'S':
if (parseKeywordIf("SECOND") ||
parseKeywordIf("SECONDS") ||
parseKeywordIf("SS") ||
parseKeywordIf("S"))
return DatePart.SECOND;
break;
case 'T':
if (parseKeywordIf("TIMEZONE"))
return DatePart.TIMEZONE;
else if (parseKeywordIf("TIMEZONE_HOUR"))
return DatePart.TIMEZONE_HOUR;
else if (parseKeywordIf("TIMEZONE_MINUTE"))
return DatePart.TIMEZONE_MINUTE;
break;
case 'W':
if (parseKeywordIf("WEEK") ||
parseKeywordIf("WEEKS") ||
parseKeywordIf("WK") ||
parseKeywordIf("WW"))
return DatePart.WEEK;
else if (parseKeywordIf("WEEKDAY") ||
parseKeywordIf("W"))
return DatePart.DAY_OF_WEEK;
break;
case 'Y':
if (parseKeywordIf("YEAR") ||
parseKeywordIf("YEARS") ||
parseKeywordIf("YYYY") ||
parseKeywordIf("YY"))
return DatePart.YEAR;
else if (parseKeywordIf("Y"))
return DatePart.DAY_OF_YEAR;
break;
}
return null;
}
private final DatePart parseIntervalDatePart() {
char character = characterUpper();
switch (character) {
case 'D':
if (parseKeywordIf("DAY") ||
parseKeywordIf("DAYS"))
return DatePart.DAY;
break;
case 'H':
if (parseKeywordIf("HOUR") ||
parseKeywordIf("HOURS"))
return DatePart.HOUR;
break;
case 'M':
if (parseKeywordIf("MINUTE") ||
parseKeywordIf("MINUTES"))
return DatePart.MINUTE;
else if (parseKeywordIf("MICROSECOND") ||
parseKeywordIf("MICROSECONDS"))
return DatePart.MICROSECOND;
else if (parseKeywordIf("MILLISECOND") ||
parseKeywordIf("MILLISECONDS"))
return DatePart.MILLISECOND;
else if (parseKeywordIf("MONTH") ||
parseKeywordIf("MONTHS"))
return DatePart.MONTH;
break;
case 'N':
if (parseKeywordIf("NANOSECOND") ||
parseKeywordIf("NANOSECONDS"))
return DatePart.NANOSECOND;
break;
case 'Q':
if (parseKeywordIf("QUARTER") ||
parseKeywordIf("QUARTERS"))
return DatePart.QUARTER;
break;
case 'S':
if (parseKeywordIf("SECOND") ||
parseKeywordIf("SECONDS"))
return DatePart.SECOND;
break;
case 'W':
if (parseKeywordIf("WEEK") ||
parseKeywordIf("WEEKS"))
return DatePart.WEEK;
break;
case 'Y':
if (parseKeywordIf("YEAR") ||
parseKeywordIf("YEARS"))
return DatePart.YEAR;
break;
}
throw expected("Interval DatePart");
}
private final Field<?> parseFieldConcatIf() {
if (parseFunctionNameIf("CONCAT")) {
parse('(');
Field<String> result = concat(parseFields().toArray(EMPTY_FIELD));
parse(')');
return result;
}
return null;
}
private final Field<?> parseFieldInstrIf() {
if (parseFunctionNameIf("INSTR")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<String> f2 = (Field) parseField(S);
Field<Integer> f3 = parseIf(',') ? (Field) parseField(N) : null;
parse(')');
return f3 == null ? DSL.position(f1, f2) : DSL.position(f1, f2, f3);
}
return null;
}
private final Field<?> parseFieldCharIndexIf() {
if (parseFunctionNameIf("CHARINDEX")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<String> f2 = (Field) parseField(S);
Field<Integer> f3 = parseIf(',') ? (Field) parseField(N) : null;
parse(')');
return f3 == null ? DSL.position(f2, f1) : DSL.position(f2, f1, f3);
}
return null;
}
private final Field<?> parseFieldLpadIf() {
if (parseFunctionNameIf("LPAD")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<Integer> f2 = (Field) parseField(N);
Field<String> f3 = parseIf(',') ? (Field) parseField(S) : null;
parse(')');
return f3 == null ? lpad(f1, f2) : lpad(f1, f2, f3);
}
return null;
}
private final Field<?> parseFieldRpadIf() {
if (parseFunctionNameIf("RPAD")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<Integer> f2 = (Field) parseField(N);
Field<String> f3 = parseIf(',') ? (Field) parseField(S) : null;
parse(')');
return f3 == null ? rpad(f1, f2) : rpad(f1, f2, f3);
}
return null;
}
private final Field<?> parseFieldInsertIf() {
if (parseFunctionNameIf("INSERT")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<Number> f2 = (Field) parseField(N);
parse(',');
Field<Number> f3 = (Field) parseField(N);
parse(',');
Field<String> f4 = (Field) parseField(S);
parse(')');
return insert(f1, f2, f3, f4);
}
return null;
}
private final Field<?> parseFieldOverlayIf() {
if (parseFunctionNameIf("OVERLAY")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parseKeyword("PLACING");
Field<String> f2 = (Field) parseField(S);
parseKeyword("FROM");
Field<Number> f3 = (Field) parseField(N);
Field<Number> f4 =
parseKeywordIf("FOR")
? (Field) parseField(N)
: null;
parse(')');
return f4 == null ? overlay(f1, f2, f3) : overlay(f1, f2, f3, f4);
}
return null;
}
private final Field<?> parseFieldPositionIf() {
if (parseFunctionNameIf("POSITION")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parseKeyword("IN");
Field<String> f2 = (Field) parseField(S);
parse(')');
return DSL.position(f2, f1);
}
return null;
}
private final Field<?> parseFieldLocateIf() {
if (parseFunctionNameIf("LOCATE")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<String> f2 = (Field) parseField(S);
parse(')');
switch (dialect()) {
default:
return DSL.position(f2, f1);
}
}
return null;
}
private final Field<?> parseFieldRepeatIf() {
if (parseFunctionNameIf("REPEAT", "REPLICATE")) {
parse('(');
Field<String> field = (Field) parseField(S);
parse(',');
Field<Integer> count = (Field) parseField(N);
parse(')');
return DSL.repeat(field, count);
}
return null;
}
private final Field<?> parseFieldReplaceIf() {
if (parseFunctionNameIf("REPLACE", "OREPLACE", "STR_REPLACE")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<String> f2 = (Field) parseField(S);
Field<String> f3 = parseIf(',')
? (Field) parseField(S)
: null;
parse(')');
return f3 == null
? replace(f1, f2)
: replace(f1, f2, f3);
}
return null;
}
private final Field<?> parseFieldRegexpReplaceIf() {
boolean all = parseFunctionNameIf("REGEXP_REPLACE_ALL");
boolean first = !all && parseFunctionNameIf("REGEXP_REPLACE_FIRST");
boolean ifx = !all && !first && parseFunctionNameIf("REGEX_REPLACE");
if (all || first || ifx || parseFunctionNameIf("REGEXP_REPLACE")) {
parse('(');
Field field = parseField(S);
parse(',');
Field pattern = parseField(S);
Field replacement = parseIf(',') ? parseField(S) : null;
Long i1;
Long i2;
if (replacement == null) {
replacement = inline("");
}
else if (ifx) {
if (parseIf(','))
if (1L == parseUnsignedInteger())
first = true;
else
throw expected("Only a limit of 1 is currently supported");
}
else if (!all && !first) {
if (parseIf(',')) {
String s = parseStringLiteralIf();
if (s != null) {
if (s.contains("g"))
all = true;
}
else {
i1 = parseUnsignedInteger();
parse(',');
i2 = parseUnsignedInteger();
if (Long.valueOf(1L).equals(i1) && Long.valueOf(1L).equals(i2))
all = true;
else
throw expected("Only start and occurence values of 1 are currently supported");
}
}
if (!all) switch (family()) {
case POSTGRES:
first = true;
break;
}
}
parse(')');
return first
? regexpReplaceFirst(field, pattern, replacement)
: regexpReplaceAll(field, pattern, replacement);
}
return null;
}
private final Field<?> parseFieldSubstringIf() {
boolean substring = parseFunctionNameIf("SUBSTRING");
boolean substr = !substring && parseFunctionNameIf("SUBSTR");
if (substring || substr) {
boolean keywords = !substr;
parse('(');
Field<String> f1 = (Field) parseField(S);
if (substr || !(keywords = parseKeywordIf("FROM")))
parse(',');
Field f2 = toField(parseNumericOp(N));
Field f3 =
((keywords && parseKeywordIf("FOR")) || (!keywords && parseIf(',')))
? (Field) toField(parseNumericOp(N))
: null;
parse(')');
return f3 == null
? DSL.substring(f1, f2)
: DSL.substring(f1, f2, f3);
}
return null;
}
private final Field<?> parseFieldTrimIf() {
if (parseFunctionNameIf("TRIM")) {
parse('(');
int p = position();
boolean leading = parseKeywordIf("LEADING") || parseKeywordIf("L");
boolean trailing = !leading && (parseKeywordIf("TRAILING") || parseKeywordIf("T"));
boolean both = !leading && !trailing && (parseKeywordIf("BOTH") || parseKeywordIf("B"));
if (leading || trailing || both) {
if (parseIf(',')) {
position(p);
}
else if (parseIf(')')) {
position(p);
}
else if (parseKeywordIf("FROM")) {
Field<String> f = (Field) parseField(S);
parse(')');
return leading ? ltrim(f)
: trailing ? rtrim(f)
: trim(f);
}
}
Field<String> f1 = (Field) parseField(S);
if (parseKeywordIf("FROM")) {
Field<String> f2 = (Field) parseField(S);
parse(')');
return leading ? ltrim(f2, f1)
: trailing ? rtrim(f2, f1)
: trim(f2, f1);
}
else {
Field<String> f2 = parseIf(',') ? (Field) parseField(S) : null;
parse(')');
return f2 == null ? trim(f1) : trim(f1, f2);
}
}
return null;
}
private final Field<?> parseFieldTranslateIf() {
if (parseFunctionNameIf("TRANSLATE")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<String> f2 = (Field) parseField(S);
parse(',');
Field<String> f3 = (Field) parseField(S);
parse(')');
return translate(f1, f2, f3);
}
return null;
}
private final Field<?> parseFieldToCharIf() {
if (parseFunctionNameIf("TO_CHAR")) {
parse('(');
Field<?> f1 = parseField();
Field<String> f2 = (Field) (parseIf(',') ? parseField() : null);
parse(')');
return f2 == null ? toChar(f1) : toChar(f1, f2);
}
return null;
}
private final Field<?> parseFieldToNumberIf() {
if (parseFunctionNameIf("TO_NUMBER")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(')');
return cast(f1, SQLDataType.NUMERIC);
}
return null;
}
private final Field<?> parseFieldToDateIf() {
if (parseFunctionNameIf("TO_DATE")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<String> f2 = (Field) parseField(S);
parse(')');
return toDate(f1, f2);
}
return null;
}
private final Field<?> parseFieldToTimestampIf() {
if (parseFunctionNameIf("TO_TIMESTAMP")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<String> f2 = (Field) parseField(S);
parse(')');
return toTimestamp(f1, f2);
}
return null;
}
private final Field<?> parseFieldTimestampDiffIf() {
if (parseFunctionNameIf("TIMESTAMPDIFF")) {
parse('(');
Field<Timestamp> ts1 = (Field<Timestamp>) parseField(Type.D);
parse(',');
Field<Timestamp> ts2 = (Field<Timestamp>) parseField(Type.D);
parse(')');
return DSL.timestampDiff(ts1, ts2);
}
return null;
}
private final Field<?> parseFieldRtrimIf() {
if (parseFunctionNameIf("RTRIM")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
Field<String> f2 = parseIf(',') ? (Field) parseField(S) : null;
parse(')');
return f2 == null ? rtrim(f1) : rtrim(f1, f2);
}
return null;
}
private final Field<?> parseFieldLtrimIf() {
if (parseFunctionNameIf("LTRIM")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
Field<String> f2 = parseIf(',') ? (Field) parseField(S) : null;
parse(')');
return f2 == null ? ltrim(f1) : ltrim(f1, f2);
}
return null;
}
private final Field<?> parseFieldMidIf() {
if (parseFunctionNameIf("MID")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<? extends Number> f2 = (Field) parseField(N);
parse(',');
Field<? extends Number> f3 = (Field) parseField(N);
parse(')');
return mid(f1, f2, f3);
}
return null;
}
private final Field<?> parseFieldLeftIf() {
if (parseFunctionNameIf("LEFT")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<? extends Number> f2 = (Field) parseField(N);
parse(')');
return left(f1, f2);
}
return null;
}
private final Field<?> parseFieldRightIf() {
if (parseFunctionNameIf("RIGHT")) {
parse('(');
Field<String> f1 = (Field) parseField(S);
parse(',');
Field<? extends Number> f2 = (Field) parseField(N);
parse(')');
return right(f1, f2);
}
return null;
}
private final Field<?> parseFieldDecodeIf() {
if (parseFunctionNameIf("DECODE")) {
parse('(');
List<Field<?>> fields = parseFields();
int size = fields.size();
if (size < 3)
throw expected("At least three arguments to DECODE()");
parse(')');
return DSL.decode(
(Field<Object>) fields.get(0),
(Field<Object>) fields.get(1),
(Field<Object>) fields.get(2),
(Field<Object>[]) (size == 3 ? EMPTY_FIELD : fields.subList(3, size).toArray(EMPTY_FIELD))
);
}
return null;
}
private final Field<?> parseFieldChooseIf() {
if (parseFunctionNameIf("CHOOSE")) {
parse('(');
Field<Integer> index = (Field<Integer>) parseField(Type.N);
parse(',');
List<Field<?>> fields = parseFields();
parse(')');
return DSL.choose(index, fields.toArray(EMPTY_FIELD));
}
return null;
}
private final Field<?> parseFieldIfnullIf() {
if (parseFunctionNameIf("IFNULL")) {
parse('(');
Field<?> f1 = parseField();
parse(',');
Field<?> f2 = parseField();
parse(')');
return ifnull(f1, f2);
}
return null;
}
private final Field<?> parseFieldIsnullIf() {
if (parseFunctionNameIf("ISNULL")) {
parse('(');
Field<?> f1 = parseField();
Field<?> f2 = parseIf(',') ? parseField() : null;
parse(')');
return f2 != null ? isnull(f1, f2) : field(f1.isNull());
}
return null;
}
private final Field<?> parseFieldIfIf() {
if (parseFunctionNameIf("IF", "IIF")) {
parse('(');
Condition c = parseCondition();
parse(',');
Field<?> f1 = parseField();
parse(',');
Field<?> f2 = parseField();
parse(')');
return iif(c, f1, f2);
}
return null;
}
private final Field<?> parseFieldNvlIf() {
if (parseFunctionNameIf("NVL")) {
parse('(');
Field<?> f1 = parseField();
parse(',');
Field<?> f2 = parseField();
parse(')');
return nvl(f1, f2);
}
return null;
}
private final Field<?> parseFieldNvl2If() {
if (parseFunctionNameIf("NVL2")) {
parse('(');
Field<?> f1 = parseField();
parse(',');
Field<?> f2 = parseField();
parse(',');
Field<?> f3 = parseField();
parse(')');
return nvl2(f1, f2, f3);
}
return null;
}
private final Field<?> parseFieldNullifIf() {
if (parseFunctionNameIf("NULLIF")) {
parse('(');
Field<?> f1 = parseField();
parse(',');
Field<?> f2 = parseField();
parse(')');
return nullif(f1, f2);
}
return null;
}
private final Field<?> parseFieldCoalesceIf() {
if (parseFunctionNameIf("COALESCE")) {
parse('(');
List<Field<?>> fields = parseFields();
parse(')');
Field[] a = EMPTY_FIELD;
return coalesce(fields.get(0), fields.size() == 1 ? a : fields.subList(1, fields.size()).toArray(a));
}
return null;
}
private final <T> Field<?> parseFieldFieldIf() {
if (parseFunctionNameIf("FIELD")) {
parse('(');
Field<?> f1 = parseField();
parse(',');
List<Field<?>> f2 = parseFields();
parse(')');
return DSL.field((Field<T>) f1, (Field<T>[]) f2.toArray(EMPTY_FIELD));
}
return null;
}
private final Field<?> parseFieldCaseIf() {
if (parseKeywordIf("CASE")) {
if (parseKeywordIf("WHEN")) {
CaseConditionStep step = null;
Field result;
do {
Condition condition = parseCondition();
parseKeyword("THEN");
Field value = parseField();
step = step == null ? when(condition, value) : step.when(condition, value);
}
while (parseKeywordIf("WHEN"));
if (parseKeywordIf("ELSE"))
result = step.otherwise(parseField());
else
result = step;
parseKeyword("END");
return result;
}
else {
CaseValueStep init = choose(parseField());
CaseWhenStep step = null;
Field result;
parseKeyword("WHEN");
do {
Field when = parseField();
parseKeyword("THEN");
Field then = parseField();
step = step == null ? init.when(when, then) : step.when(when, then);
}
while (parseKeywordIf("WHEN"));
if (parseKeywordIf("ELSE"))
result = step.otherwise(parseField());
else
result = step;
parseKeyword("END");
return result;
}
}
return null;
}
private final Field<?> parseFieldCastIf() {
boolean cast = parseFunctionNameIf("CAST");
boolean coerce = !cast && parseFunctionNameIf("COERCE");
if (cast || coerce) {
parse('(');
Field<?> field = parseField();
parseKeyword("AS");
DataType<?> type = parseCastDataType();
parse(')');
return cast ? cast(field, type) : coerce(field, type);
}
return null;
}
private final Field<?> parseFieldConvertIf() {
if (parseFunctionNameIf("CONVERT")) {
parse('(');
DataType<?> type = parseDataType();
parse(',');
Field<?> field = parseField();
Long style = null;
if (parseIf(',') && requireProEdition())
style = parseUnsignedInteger();
parse(')');
if (style == null)
return cast(field, type);
}
return null;
}
private final Field<Boolean> parseBooleanValueExpressionIf() {
TruthValue truth = parseTruthValueIf();
if (truth != null) {
switch (truth) {
case T_TRUE:
return inline(true);
case T_FALSE:
return inline(false);
case T_NULL:
return inline((Boolean) null);
default:
throw exception("Truth value not supported: " + truth);
}
}
return null;
}
private final Field<?> parseAggregateFunctionIf() {
return parseAggregateFunctionIf(false);
}
private final Field<?> parseAggregateFunctionIf(boolean basic) {
AggregateFunction<?> agg = null;
AggregateFilterStep<?> filter = null;
WindowBeforeOverStep<?> over = null;
Object keep = null;
Field<?> result = null;
Condition condition = null;
keep = over = filter = agg = parseCountIf();
if (filter == null) {
Field<?> field = parseGeneralSetFunctionIf();
if (field != null && !(field instanceof AggregateFunction))
return field;
keep = over = filter = agg = (AggregateFunction<?>) field;
}
if (filter == null && !basic)
over = filter = agg = parseBinarySetFunctionIf();
if (filter == null && !basic)
over = filter = parseOrderedSetFunctionIf();
if (filter == null && !basic)
over = filter = parseArrayAggFunctionIf();
if (filter == null && !basic)
over = filter = parseXMLAggFunctionIf();
if (filter == null && !basic)
over = filter = parseJSONArrayAggFunctionIf();
if (filter == null && !basic)
over = filter = parseJSONObjectAggFunctionIf();
if (filter == null && over == null)
if (!basic)
return parseSpecialAggregateFunctionIf();
else
return null;
if (keep != null && filter != null && !basic && parseKeywordIf("KEEP")) {
requireProEdition();
}
else if (filter != null && !basic && parseKeywordIf("FILTER")) {
parse('(');
parseKeyword("WHERE");
condition = parseCondition();
parse(')');
result = over = filter.filterWhere(condition);
}
else if (filter != null)
result = filter;
else
result = over;
if (!basic && parseKeywordIf("OVER")) {
Object nameOrSpecification = parseWindowNameOrSpecification(filter != 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 final Field<?> parseSpecialAggregateFunctionIf() {
if (parseFunctionNameIf("GROUP_CONCAT")) {
parse('(');
GroupConcatOrderByStep s1;
GroupConcatSeparatorStep s2;
AggregateFunction<String> s3;
if (parseKeywordIf("DISTINCT"))
s1 = DSL.groupConcatDistinct(parseField());
else
s1 = DSL.groupConcat(parseField());
if (parseKeywordIf("ORDER BY"))
s2 = s1.orderBy(parseSortSpecification());
else
s2 = s1;
if (parseKeywordIf("SEPARATOR"))
s3 = s2.separator(parseStringLiteral());
else
s3 = s2;
parse(')');
return s3;
}
return null;
}
private final Object parseWindowNameOrSpecification(boolean orderByAllowed) {
Object result;
if (parseIf('(')) {
result = parseWindowSpecificationIf(null, orderByAllowed);
parse(')');
}
else {
result = parseIdentifier();
}
return result;
}
private final Field<?> parseFieldRankIf() {
if (parseFunctionNameIf("RANK")) {
parse('(');
if (parseIf(')'))
return parseWindowFunction(null, null, rank());
// Hypothetical set function
List<Field<?>> args = parseFields();
parse(')');
return rank(args).withinGroupOrderBy(parseWithinGroupN());
}
return null;
}
private final Field<?> parseFieldDenseRankIf() {
if (parseFunctionNameIf("DENSE_RANK")) {
parse('(');
if (parseIf(')'))
return parseWindowFunction(null, null, denseRank());
// Hypothetical set function
List<Field<?>> args = parseFields();
parse(')');
return denseRank(args).withinGroupOrderBy(parseWithinGroupN());
}
return null;
}
private final Field<?> parseFieldPercentRankIf() {
if (parseFunctionNameIf("PERCENT_RANK")) {
parse('(');
if (parseIf(')'))
return parseWindowFunction(null, null, percentRank());
// Hypothetical set function
List<Field<?>> args = parseFields();
parse(')');
return percentRank(args).withinGroupOrderBy(parseWithinGroupN());
}
return null;
}
private final Field<?> parseFieldCumeDistIf() {
if (parseFunctionNameIf("CUME_DIST")) {
parse('(');
if (parseIf(')'))
return parseWindowFunction(null, null, cumeDist());
// Hypothetical set function
List<Field<?>> args = parseFields();
parse(')');
return cumeDist(args).withinGroupOrderBy(parseWithinGroupN());
}
return null;
}
private final Field<?> parseFieldRandIf() {
if (parseFunctionNameIf("RAND", "RANDOM")) {
parse('(');
parse(')');
return rand();
}
return null;
}
private final Field<?> parseFieldRatioToReportIf() {
if (parseFunctionNameIf("RATIO_TO_REPORT")) {
parse('(');
Field<Number> field = (Field<Number>) parseField();
parse(')');
return parseWindowFunction(null, null, ratioToReport(field));
}
return null;
}
private final Field<?> parseFieldRowNumberIf() {
if (parseFunctionNameIf("ROW_NUMBER")) {
parse('(');
parse(')');
return parseWindowFunction(null, null, rowNumber());
}
return null;
}
private final Field<?> parseFieldNtileIf() {
if (parseFunctionNameIf("NTILE")) {
parse('(');
int number = (int) (long) parseUnsignedInteger();
parse(')');
return parseWindowFunction(null, null, ntile(number));
}
return null;
}
private final Field<?> parseFieldLeadLagIf() {
boolean lead = parseFunctionNameIf("LEAD");
boolean lag = !lead && parseFunctionNameIf("LAG");
if (lead || lag) {
parse('(');
Field<Void> f1 = (Field) parseField();
Integer f2 = null;
Field<Void> f3 = null;
if (parseIf(',')) {
f2 = (int) (long) parseUnsignedInteger();
if (parseIf(','))
f3 = (Field) parseField();
}
WindowIgnoreNullsStep s1 = 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);
WindowOverStep<?> s2 = parseWindowRespectIgnoreNulls(s1, s1);
parse(')');
return parseWindowFunction(null, s1, s2);
}
return null;
}
private final Field<?> parseFieldFirstValueIf() {
if (parseFunctionNameIf("FIRST_VALUE")) {
parse('(');
Field<Void> arg = (Field) parseField();
WindowIgnoreNullsStep<Void> s1 = firstValue(arg);
WindowOverStep<?> s2 = parseWindowRespectIgnoreNulls(s1, s1);
parse(')');
return parseWindowFunction(null, s1, s2);
}
return null;
}
private final Field<?> parseFieldLastValueIf() {
if (parseFunctionNameIf("LAST_VALUE")) {
parse('(');
Field<Void> arg = (Field) parseField();
WindowIgnoreNullsStep<Void> s1 = lastValue(arg);
WindowOverStep<?> s2 = parseWindowRespectIgnoreNulls(s1, s1);
parse(')');
return parseWindowFunction(null, s1, s2);
}
return null;
}
private final Field<?> parseFieldNthValueIf() {
if (parseFunctionNameIf("NTH_VALUE")) {
parse('(');
Field<?> f1 = parseField();
parse(',');
int f2 = (int) (long) parseUnsignedInteger();
WindowFromFirstLastStep<?> s1 = nthValue(f1, f2);
WindowIgnoreNullsStep s2 = parseWindowFromFirstLast(s1, s1);
WindowOverStep<?> s3 = parseWindowRespectIgnoreNulls(s2, s2);
parse(')');
return parseWindowFunction(s1, s2, s3);
}
return null;
}
private final Field<?> parseWindowFunction(WindowFromFirstLastStep s1, WindowIgnoreNullsStep s2, WindowOverStep<?> s3) {
s2 = parseWindowFromFirstLast(s1, s2);
s3 = parseWindowRespectIgnoreNulls(s2, s3);
parseKeyword("OVER");
Object nameOrSpecification = parseWindowNameOrSpecification(true);
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=494897
Field<?> result = (nameOrSpecification instanceof Name)
? s3.over((Name) nameOrSpecification)
: (nameOrSpecification instanceof WindowSpecification)
? s3.over((WindowSpecification) nameOrSpecification)
: s3.over();
return result;
}
private final WindowOverStep<?> parseWindowRespectIgnoreNulls(WindowIgnoreNullsStep s2, WindowOverStep<?> s3) {
if (s2 != null)
if (parseKeywordIf("RESPECT NULLS"))
s3 = s2.respectNulls();
else if (parseKeywordIf("IGNORE NULLS"))
s3 = s2.ignoreNulls();
else
s3 = s2;
return s3;
}
private final WindowIgnoreNullsStep parseWindowFromFirstLast(WindowFromFirstLastStep s1, WindowIgnoreNullsStep s2) {
if (s1 != null)
if (parseKeywordIf("FROM FIRST"))
s2 = s1.fromFirst();
else if (parseKeywordIf("FROM LAST"))
s2 = s1.fromLast();
else
s2 = s1;
return s2;
}
private final AggregateFunction<?> parseBinarySetFunctionIf() {
Field<? extends Number> arg1;
Field<? extends Number> arg2;
BinarySetFunctionType type = parseBinarySetFunctionTypeIf();
if (type == null)
return null;
parse('(');
arg1 = (Field) toField(parseNumericOp(N));
parse(',');
arg2 = (Field) toField(parseNumericOp(N));
parse(')');
switch (type) {
case COVAR_POP:
return covarPop(arg1, arg2);
case COVAR_SAMP:
return covarSamp(arg1, arg2);
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 exception("Binary set function not supported: " + type);
}
}
private final AggregateFilterStep<?> parseOrderedSetFunctionIf() {
// TODO Listagg set function
OrderedAggregateFunction<?> orderedN;
OrderedAggregateFunctionOfDeferredType ordered1;
boolean optionalWithinGroup = false;
orderedN = parseHypotheticalSetFunctionIf();
if (orderedN == null)
orderedN = parseInverseDistributionFunctionIf();
if (orderedN == null)
optionalWithinGroup = (orderedN = parseListaggFunctionIf()) != null;
if (orderedN != null)
return orderedN.withinGroupOrderBy(parseWithinGroupN(optionalWithinGroup));
ordered1 = parseModeIf();
if (ordered1 != null)
return ordered1.withinGroupOrderBy(parseWithinGroup1());
return null;
}
private final AggregateFilterStep<?> parseArrayAggFunctionIf() {
if (parseKeywordIf("ARRAY_AGG")) {
parse('(');
boolean distinct = parseKeywordIf("DISTINCT");
Field<?> a1 = parseField();
List<SortField<?>> sort = null;
if (parseKeywordIf("ORDER BY"))
sort = parseSortSpecification();
parse(')');
ArrayAggOrderByStep<?> s1 = distinct
? arrayAggDistinct(a1)
: arrayAgg(a1);
return sort == null ? s1 : s1.orderBy(sort);
}
return null;
}
private final List<SortField<?>> parseWithinGroupN() {
return parseWithinGroupN(false);
}
private final List<SortField<?>> parseWithinGroupN(boolean optional) {
if (optional) {
if (!parseKeywordIf("WITHIN GROUP"))
return emptyList();
}
else
parseKeyword("WITHIN GROUP");
parse('(');
parseKeyword("ORDER BY");
List<SortField<?>> result = parseSortSpecification();
parse(')');
return result;
}
private final SortField<?> parseWithinGroup1() {
parseKeyword("WITHIN GROUP");
parse('(');
parseKeyword("ORDER BY");
SortField<?> result = parseSortField();
parse(')');
return result;
}
private final OrderedAggregateFunction<?> parseHypotheticalSetFunctionIf() {
// 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("RANK")) {
parse('(');
ordered = rank(parseFields());
parse(')');
}
else if (parseFunctionNameIf("DENSE_RANK")) {
parse('(');
ordered = denseRank(parseFields());
parse(')');
}
else if (parseFunctionNameIf("PERCENT_RANK")) {
parse('(');
ordered = percentRank(parseFields());
parse(')');
}
else if (parseFunctionNameIf("CUME_DIST")) {
parse('(');
ordered = cumeDist(parseFields());
parse(')');
}
else
ordered = null;
return ordered;
}
private final OrderedAggregateFunction<BigDecimal> parseInverseDistributionFunctionIf() {
OrderedAggregateFunction<BigDecimal> ordered;
if (parseFunctionNameIf("PERCENTILE_CONT")) {
parse('(');
ordered = percentileCont(parseFieldUnsignedNumericLiteral(Sign.NONE));
parse(')');
}
else if (parseFunctionNameIf("PERCENTILE_DISC")) {
parse('(');
ordered = percentileDisc(parseFieldUnsignedNumericLiteral(Sign.NONE));
parse(')');
}
else
ordered = null;
return ordered;
}
private final OrderedAggregateFunction<String> parseListaggFunctionIf() {
OrderedAggregateFunction<String> ordered;
if (parseFunctionNameIf("LISTAGG")) {
parse('(');
Field<?> field = parseField();
if (parseIf(','))
ordered = listAgg(field, parseStringLiteral());
else
ordered = listAgg(field);
parse(')');
}
else
ordered = null;
return ordered;
}
private final OrderedAggregateFunctionOfDeferredType parseModeIf() {
OrderedAggregateFunctionOfDeferredType ordered;
if (parseFunctionNameIf("MODE")) {
parse('(');
parse(')');
ordered = mode();
}
else
ordered = null;
return ordered;
}
private final Field<?> parseGeneralSetFunctionIf() {
boolean distinct;
Field arg;
Field arg2;
ComputationalOperation operation = parseComputationalOperationIf();
if (operation == null)
return null;
parse('(');
switch (operation) {
case AVG:
case MAX:
case MIN:
case SUM:
case PRODUCT:
distinct = parseSetQuantifier();
break;
default:
distinct = false;
break;
}
arg = parseField();
switch (operation) {
case MAX:
case MIN: {
if (!distinct && parseIf(',')) {
List<Field<?>> fields = parseFields();
parse(')');
return operation == ComputationalOperation.MAX ? greatest(arg, fields.toArray(EMPTY_FIELD)) : least(arg, fields.toArray(EMPTY_FIELD));
}
}
}
parse(')');
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 PRODUCT:
return distinct ? productDistinct(arg) : product(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 exception("Unsupported computational operation");
}
}
private final AggregateFunction<?> parseCountIf() {
if (parseFunctionNameIf("COUNT")) {
parse('(');
boolean distinct = parseSetQuantifier();
if (parseIf('*') && parse(')'))
if (distinct)
return countDistinct(asterisk());
else
return count();
Field<?>[] fields = null;
QualifiedAsterisk asterisk = null;
Row row = parseRowIf();
if (row != null)
fields = row.fields();
else if ((asterisk = parseQualifiedAsteriskIf()) == null)
fields = distinct
? parseFields().toArray(EMPTY_FIELD)
: new Field[] { parseField() };
parse(')');
if (distinct)
if (fields == null)
return countDistinct(asterisk);
else if (fields.length == 1)
return countDistinct(fields[0]);
else
return countDistinct(fields);
else if (fields == null)
return count(asterisk);
else
return count(fields[0]);
}
return null;
}
private final boolean parseSetQuantifier() {
boolean distinct = parseKeywordIf("DISTINCT");
if (!distinct)
parseKeywordIf("ALL");
return distinct;
}
// -----------------------------------------------------------------------------------------------------------------
// Name parsing
// -----------------------------------------------------------------------------------------------------------------
private final Domain<?> parseDomainName() {
return domain(parseName());
}
private final Catalog parseCatalogName() {
return catalog(parseName());
}
private final Schema parseSchemaName() {
return schema(parseName());
}
private final Table<?> parseTableName() {
return lookupTable(parseName());
}
private final Table<?> parseTableNameIf() {
Name name = parseNameIf();
if (name == null)
return null;
return lookupTable(name);
}
private final Field<?> parseFieldNameOrSequenceExpression() {
Name name = parseName();
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();
}
if (dsl.settings().getParseUnknownFunctions() == ParseUnknownFunctions.IGNORE && peek('(') && !peekTokens('(', '+', ')')) {
List<Field<?>> arguments = new ArrayList<>();
parse('(');
if (!parseIf(')')) {
do
arguments.add(parseField());
while (parseIf(','));
parse(')');
}
return function(name, Object.class, arguments.toArray(EMPTY_FIELD));
}
else {
return lookupField(name);
}
}
private final TableField<?, ?> parseFieldName() {
return (TableField<?, ?>) lookupField(parseName());
}
private final List<Field<?>> parseFieldNames() {
List<Field<?>> result = new ArrayList<>();
do
result.add(parseFieldName());
while (parseIf(','));
return result;
}
private final Sequence<?> parseSequenceName() {
return sequence(parseName());
}
private final Name parseIndexName() {
Name result = parseNameIf();
if (result == null)
throw expected("Identifier");
return result;
}
private final Name parseIndexNameIf() {
if (!peekKeyword("ON"))
return parseNameIf();
else
return null;
}
private final Collation parseCollation() {
return collation(parseName());
}
private final CharacterSet parseCharacterSet() {
return characterSet(parseName());
}
private final Name parseName() {
Name result = parseNameIf();
if (result == null)
throw expected("Identifier");
return result;
}
private final Name parseNameIf() {
Name identifier = parseIdentifierIf();
if (identifier == null)
return null;
// Avoid .. token in indexed for loops:
// FOR i IN identifier1 .. identifier2 LOOP <...> END LOOP;
if (peek('.') && !peek("..")) {
List<Name> result = new ArrayList<>();
result.add(identifier);
while (parseIf('.'))
result.add(parseIdentifier());
return DSL.name(result.toArray(EMPTY_NAME));
}
else
return identifier;
}
private final QualifiedAsterisk parseQualifiedAsteriskIf() {
int p = position();
Name i1 = parseIdentifierIf();
if (i1 == null)
return null;
if (parseIf('.')) {
List<Name> result = null;
Name i2;
do {
if ((i2 = parseIdentifierIf()) != null) {
if (result == null) {
result = new ArrayList<>();
result.add(i1);
}
result.add(i2);
}
else {
parse('*');
return lookupTable(result == null ? i1 : DSL.name(result.toArray(EMPTY_NAME))).asterisk();
}
}
while (parseIf('.'));
}
position(p);
return null;
}
private final List<Name> parseIdentifiers() {
LinkedHashSet<Name> result = new LinkedHashSet<>();
do
if (!result.add(parseIdentifier()))
throw exception("Duplicate identifier encountered");
while (parseIf(','));
return new ArrayList<>(result);
}
private final Name parseIdentifier() {
return parseIdentifier(false);
}
private final Name parseIdentifier(boolean allowAposQuotes) {
Name result = parseIdentifierIf(allowAposQuotes);
if (result == null)
throw expected("Identifier");
return result;
}
private final Name parseIdentifierIf() {
return parseIdentifierIf(false);
}
private final Name parseIdentifierIf(boolean allowAposQuotes) {
char quoteEnd = parseQuote(allowAposQuotes);
boolean quoted = quoteEnd != 0;
int start = position();
StringBuilder sb = new StringBuilder();
char c;
if (quoted)
while ((c = character()) != quoteEnd && hasMore() && positionInc() || character(position + 1) == quoteEnd && hasMore(1) && positionInc(2))
sb.append(c);
else
for (; isIdentifierPart() && hasMore(); positionInc())
sb.append(character());
if (position() == start)
return null;
String name = normaliseNameCase(configuration(), sb.toString(), quoted, locale);
if (quoted) {
if (character() != quoteEnd)
throw exception("Quoted identifier must terminate in " + quoteEnd);
positionInc();
parseWhitespaceIf();
return DSL.quotedName(name);
}
else {
parseWhitespaceIf();
return DSL.unquotedName(name);
}
}
private final char parseQuote(boolean allowAposQuotes) {
return parseIf('"', false) ? '"'
: parseIf('`', false) ? '`'
: parseIf('[', false) ? ']'
: allowAposQuotes && parseIf('\'', false) ? '\''
: 0;
}
private final DataType<?> parseCastDataType() {
char character = characterUpper();
switch (character) {
case 'S':
if (parseKeywordIf("SIGNED") && (parseKeywordIf("INTEGER") || true))
return SQLDataType.BIGINT;
break;
case 'U':
if (parseKeywordIf("UNSIGNED") && (parseKeywordIf("INTEGER") || true))
return SQLDataType.BIGINTUNSIGNED;
break;
}
return parseDataType();
}
private final DataType<?> parseDataType() {
DataType<?> result = parseDataTypePrefix();
boolean array = false;
if (parseKeywordIf("ARRAY"))
array = true;
if (parseIf('[')) {
parseUnsignedIntegerIf();
parse(']');
array = true;
}
if (array)
result = result.getArrayDataType();
return result;
}
private final DataType<?> parseDataTypePrefix() {
char character = characterUpper();
if (character == '[' || character == '"' || character == '`')
character = characterNextUpper();
switch (character) {
case 'A':
if (parseKeywordOrIdentifierIf("ARRAY"))
return SQLDataType.OTHER.getArrayDataType();
break;
case 'B':
if (parseKeywordOrIdentifierIf("BIGINT"))
return parseUnsigned(parseAndIgnoreDataTypeLength(SQLDataType.BIGINT));
else if (parseKeywordOrIdentifierIf("BIGSERIAL"))
return SQLDataType.BIGINT.identity(true);
else if (parseKeywordOrIdentifierIf("BINARY"))
return parseDataTypeLength(SQLDataType.BINARY);
else if (parseKeywordOrIdentifierIf("BIT"))
return parseDataTypeLength(SQLDataType.BIT);
else if (parseKeywordOrIdentifierIf("BLOB"))
return parseDataTypeLength(SQLDataType.BLOB);
else if (parseKeywordOrIdentifierIf("BOOLEAN") ||
parseKeywordOrIdentifierIf("BOOL"))
return SQLDataType.BOOLEAN;
else if (parseKeywordOrIdentifierIf("BYTEA"))
return SQLDataType.BLOB;
break;
case 'C':
if (parseKeywordOrIdentifierIf("CHARACTER VARYING"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.VARCHAR));
else if (parseKeywordOrIdentifierIf("CHAR") ||
parseKeywordOrIdentifierIf("CHARACTER"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.CHAR));
// [#5934] [#10291] TODO: support as actual data type as well
else if (parseKeywordOrIdentifierIf("CITEXT"))
return parseDataTypeCollation(parseAndIgnoreDataTypeLength(SQLDataType.CLOB));
else if (parseKeywordOrIdentifierIf("CLOB"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.CLOB));
break;
case 'D':
if (parseKeywordOrIdentifierIf("DATE"))
return SQLDataType.DATE;
else if (parseKeywordOrIdentifierIf("DATETIME"))
return parseDataTypePrecisionIf(SQLDataType.TIMESTAMP);
else if (parseKeywordOrIdentifierIf("DECIMAL"))
return parseDataTypePrecisionScaleIf(SQLDataType.DECIMAL);
else if (parseKeywordOrIdentifierIf("DOUBLE PRECISION") ||
parseKeywordOrIdentifierIf("DOUBLE"))
return parseAndIgnoreDataTypePrecisionScaleIf(SQLDataType.DOUBLE);
break;
case 'E':
if (parseKeywordOrIdentifierIf("ENUM"))
return parseDataTypeCollation(parseDataTypeEnum());
break;
case 'F':
if (parseKeywordOrIdentifierIf("FLOAT"))
return parseAndIgnoreDataTypePrecisionScaleIf(SQLDataType.FLOAT);
break;
case 'I':
if (parseKeywordOrIdentifierIf("INTEGER") ||
parseKeywordOrIdentifierIf("INT") ||
parseKeywordOrIdentifierIf("INT4"))
return parseUnsigned(parseAndIgnoreDataTypeLength(SQLDataType.INTEGER));
else if (parseKeywordOrIdentifierIf("INT2"))
return SQLDataType.SMALLINT;
else if (parseKeywordOrIdentifierIf("INT8"))
return SQLDataType.BIGINT;
else if (parseKeywordIf("INTERVAL")) {
if (parseKeywordIf("YEAR")) {
parseDataTypePrecisionIf();
parseKeyword("TO MONTH");
return SQLDataType.INTERVALYEARTOMONTH;
}
else if (parseKeywordIf("DAY")) {
parseDataTypePrecisionIf();
parseKeyword("TO SECOND");
parseDataTypePrecisionIf();
return SQLDataType.INTERVALDAYTOSECOND;
}
else
return SQLDataType.INTERVAL;
}
break;
case 'J':
if (parseKeywordOrIdentifierIf("JSON"))
return SQLDataType.JSON;
else if (parseKeywordOrIdentifierIf("JSONB"))
return SQLDataType.JSONB;
break;
case 'L':
if (parseKeywordOrIdentifierIf("LONGBLOB"))
return SQLDataType.BLOB;
else if (parseKeywordOrIdentifierIf("LONGTEXT"))
return parseDataTypeCollation(SQLDataType.CLOB);
else if (parseKeywordOrIdentifierIf("LONG NVARCHAR"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.LONGNVARCHAR));
else if (parseKeywordOrIdentifierIf("LONG VARBINARY"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.LONGVARBINARY));
else if (parseKeywordOrIdentifierIf("LONG VARCHAR"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.LONGVARCHAR));
break;
case 'M':
if (parseKeywordOrIdentifierIf("MEDIUMBLOB"))
return SQLDataType.BLOB;
else if (parseKeywordOrIdentifierIf("MEDIUMINT"))
return parseUnsigned(parseAndIgnoreDataTypeLength(SQLDataType.INTEGER));
else if (parseKeywordOrIdentifierIf("MEDIUMTEXT"))
return parseDataTypeCollation(SQLDataType.CLOB);
break;
case 'N':
if (parseKeywordOrIdentifierIf("NCHAR"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.NCHAR));
else if (parseKeywordOrIdentifierIf("NCLOB"))
return parseDataTypeCollation(SQLDataType.NCLOB);
else if (parseKeywordOrIdentifierIf("NUMBER") ||
parseKeywordOrIdentifierIf("NUMERIC"))
return parseDataTypePrecisionScaleIf(SQLDataType.NUMERIC);
else if (parseKeywordOrIdentifierIf("NVARCHAR") ||
parseKeywordOrIdentifierIf("NVARCHAR2"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.NVARCHAR));
break;
case 'O':
if (parseKeywordOrIdentifierIf("OTHER"))
return SQLDataType.OTHER;
break;
case 'R':
if (parseKeywordOrIdentifierIf("REAL"))
return parseAndIgnoreDataTypePrecisionScaleIf(SQLDataType.REAL);
break;
case 'S':
if (parseKeywordOrIdentifierIf("SERIAL4") ||
parseKeywordOrIdentifierIf("SERIAL"))
return SQLDataType.INTEGER.identity(true);
else if (parseKeywordOrIdentifierIf("SERIAL8"))
return SQLDataType.BIGINT.identity(true);
else if (parseKeywordOrIdentifierIf("SET"))
return parseDataTypeCollation(parseDataTypeEnum());
else if (parseKeywordOrIdentifierIf("SMALLINT"))
return parseUnsigned(parseAndIgnoreDataTypeLength(SQLDataType.SMALLINT));
else if (parseKeywordOrIdentifierIf("SMALLSERIAL") ||
parseKeywordOrIdentifierIf("SERIAL2"))
return SQLDataType.SMALLINT.identity(true);
break;
case 'T':
if (parseKeywordOrIdentifierIf("TEXT"))
return parseDataTypeCollation(parseAndIgnoreDataTypeLength(SQLDataType.CLOB));
else if (parseKeywordOrIdentifierIf("TIMESTAMPTZ"))
return parseDataTypePrecisionIf(SQLDataType.TIMESTAMPWITHTIMEZONE);
else if (parseKeywordOrIdentifierIf("TIMESTAMP")) {
Integer precision = parseDataTypePrecisionIf();
if (parseKeywordOrIdentifierIf("WITH TIME ZONE"))
return precision == null ? SQLDataType.TIMESTAMPWITHTIMEZONE : SQLDataType.TIMESTAMPWITHTIMEZONE(precision);
else if (parseKeywordOrIdentifierIf("WITHOUT TIME ZONE") || true)
return precision == null ? SQLDataType.TIMESTAMP : SQLDataType.TIMESTAMP(precision);
}
else if (parseKeywordOrIdentifierIf("TIMETZ"))
return parseDataTypePrecisionIf(SQLDataType.TIMEWITHTIMEZONE);
else if (parseKeywordOrIdentifierIf("TIME")) {
Integer precision = parseDataTypePrecisionIf();
if (parseKeywordOrIdentifierIf("WITH TIME ZONE"))
return precision == null ? SQLDataType.TIMEWITHTIMEZONE : SQLDataType.TIMEWITHTIMEZONE(precision);
else if (parseKeywordOrIdentifierIf("WITHOUT TIME ZONE") || true)
return precision == null ? SQLDataType.TIME : SQLDataType.TIME(precision);
}
else if (parseKeywordOrIdentifierIf("TINYBLOB"))
return SQLDataType.BLOB;
else if (parseKeywordOrIdentifierIf("TINYINT"))
return parseUnsigned(parseAndIgnoreDataTypeLength(SQLDataType.TINYINT));
else if (parseKeywordOrIdentifierIf("TINYTEXT"))
return parseDataTypeCollation(SQLDataType.CLOB);
break;
case 'U':
if (parseKeywordOrIdentifierIf("UUID"))
return SQLDataType.UUID;
else if (parseKeywordOrIdentifierIf("UNIQUEIDENTIFIER"))
return SQLDataType.UUID;
break;
case 'V':
if (parseKeywordOrIdentifierIf("VARCHAR") ||
parseKeywordOrIdentifierIf("VARCHAR2") ||
// [#5934] [#10291] TODO: support as actual data type as well
parseKeywordOrIdentifierIf("VARCHAR_IGNORECASE"))
return parseDataTypeCollation(parseDataTypeLength(SQLDataType.VARCHAR));
else if (parseKeywordOrIdentifierIf("VARBINARY"))
return parseDataTypeLength(SQLDataType.VARBINARY);
break;
case 'X':
if (parseKeywordOrIdentifierIf("XML"))
return SQLDataType.XML;
break;
}
return new DefaultDataType(dsl.dialect(), Object.class, parseName());
}
private final boolean parseKeywordOrIdentifierIf(String keyword) {
int p = position();
char quoteEnd = parseQuote(false);
boolean result = parseKeywordIf(keyword);
if (!result)
position(p);
else if (quoteEnd != 0)
parse(quoteEnd);
return result;
}
private final DataType<?> parseUnsigned(DataType result) {
if (parseKeywordIf("UNSIGNED"))
if (result == SQLDataType.TINYINT)
return SQLDataType.TINYINTUNSIGNED;
else if (result == SQLDataType.SMALLINT)
return SQLDataType.SMALLINTUNSIGNED;
else if (result == SQLDataType.INTEGER)
return SQLDataType.INTEGERUNSIGNED;
else if (result == SQLDataType.BIGINT)
return SQLDataType.BIGINTUNSIGNED;
return result;
}
private final DataType<?> parseAndIgnoreDataTypeLength(DataType<?> result) {
if (parseIf('(')) {
parseUnsignedInteger();
parse(')');
}
return result;
}
private final DataType<?> parseDataTypeLength(DataType<?> in) {
DataType<?> result = in;
if (parseIf('(')) {
if (!parseKeywordIf("MAX"))
result = result.length((int) (long) parseUnsignedInteger());
if (in == SQLDataType.VARCHAR || in == SQLDataType.CHAR)
if (!parseKeywordIf("BYTE"))
parseKeywordIf("CHAR");
parse(')');
}
return result;
}
private final DataType<?> parseDataTypeCollation(DataType<?> result) {
CharacterSet cs = parseCharacterSetSpecificationIf();
if (cs != null)
result = result.characterSet(cs);
Collation col = parseCollateSpecificationIf();
if (col != null)
result = result.collation(col);
return result;
}
private final CharacterSet parseCharacterSetSpecificationIf() {
if (parseKeywordIf("CHARACTER SET") || parseKeywordIf("CHARSET")) {
parseIf('=');
return parseCharacterSet();
}
return null;
}
private final Collation parseCollateSpecificationIf() {
if (parseKeywordIf("COLLATE")) {
parseIf('=');
return parseCollation();
}
return null;
}
private final DataType<?> parseAndIgnoreDataTypePrecisionScaleIf(DataType<?> result) {
if (parseIf('(')) {
parseUnsignedInteger();
if (parseIf(','))
parseUnsignedInteger();
parse(')');
}
return result;
}
private final Integer parseDataTypePrecisionIf() {
Integer precision = null;
if (parseIf('(')) {
precision = (int) (long) parseUnsignedInteger();
parse(')');
}
return precision;
}
private final DataType<?> parseDataTypePrecisionIf(DataType<?> result) {
if (parseIf('(')) {
int precision = (int) (long) parseUnsignedInteger();
result = result.precision(precision);
parse(')');
}
return result;
}
private final DataType<?> parseDataTypePrecisionScaleIf(DataType<?> result) {
if (parseIf('(')) {
int precision = parseIf('*') ? 38 : (int) (long) parseUnsignedInteger();
if (parseIf(','))
result = result.precision(precision, (int) (long) parseSignedInteger());
else
result = result.precision(precision);
parse(')');
}
return result;
}
private final DataType<?> parseDataTypeEnum() {
parse('(');
List<String> literals = new ArrayList<>();
int length = 0;
do {
String literal = parseStringLiteral();
if (literal != null)
length = Math.max(length, literal.length());
literals.add(literal);
}
while (parseIf(','));
parse(')');
// [#7025] TODO, replace this by a dynamic enum data type encoding, once available
String className = "GeneratedEnum" + (literals.hashCode() & 0x7FFFFFF);
StringBuilder content = new StringBuilder();
content.append(
"package org.jooq.impl;\n"
+ "enum ").append(className).append(" implements org.jooq.EnumType {\n");
for (int i = 0; i < literals.size(); i++)
content.append(" E").append(i).append("(\"").append(literals.get(i).replace("\"", "\\\"")).append("\"),\n");
content.append(
" ;\n"
+ " final String literal;\n"
+ " private ").append(className).append("(String literal) { this.literal = literal; }\n"
+ " @Override\n"
+ " public String getName() {\n"
+ " return getClass().getName();\n"
+ " }\n"
+ " @Override\n"
+ " public String getLiteral() {\n"
+ " return literal;\n"
+ " }\n"
+ "}");
return VARCHAR(length).asEnumDataType(Reflect.compile("org.jooq.impl." + className, content.toString()).get());
}
// -----------------------------------------------------------------------------------------------------------------
// Literal parsing
// -----------------------------------------------------------------------------------------------------------------
private final char parseCharacterLiteral() {
parse('\'', false);
char c = character();
// TODO MySQL string escaping...
if (c == '\'')
parse('\'', false);
positionInc();
parse('\'');
return c;
}
private final Field<?> parseBindVariable() {
// [#11074] Bindings can be Param or even Field types
Object binding = nextBinding();
String paramName;
switch (character()) {
case '?':
parse('?');
paramName = "" + bindIndex;
break;
case ':':
parse(':', false);
Name identifier = parseIdentifier();
paramName = identifier.last();
break;
default:
throw exception("Illegal bind variable character");
}
if (binding instanceof Field)
return (Field<?>) binding;
Param<?> param = DSL.param(paramName, binding);
if (bindParamListener != null)
bindParams.put(paramName, param);
return param;
}
private final Comment parseComment() {
return DSL.comment(parseStringLiteral());
}
private final String parseStringLiteral(String literal) {
String value = parseStringLiteral();
if (!literal.equals(value))
throw expected("String literal: '" + literal + "'");
return value;
}
private final String parseStringLiteral() {
String result = parseStringLiteralIf();
if (result == null)
throw expected("String literal");
return result;
}
private final String parseStringLiteralIf() {
if (parseIf('q', '\'', false) || parseIf('Q', '\'', false))
return parseOracleQuotedStringLiteral();
else if (parseIf('e', '\'', false) || parseIf('E', '\'', false))
return parseUnquotedStringLiteral(true, '\'');
else if (peek('\''))
return parseUnquotedStringLiteral(false, '\'');
else if (parseIf('n', '\'', false) || parseIf('N', '\'', false))
return parseUnquotedStringLiteral(true, '\'');
else if (peek('$'))
return parseDollarQuotedStringLiteralIf();
else
return null;
}
private final Boolean parseBitLiteralIf() {
if (parseIf("B'", false) || parseIf("b'", false)) {
boolean result = false;
if (!parseIf('0') && parseIf('1'))
result = true;
if (parseIf('0') || parseIf('1'))
throw exception("Currently, only BIT(1) literals are supported");
parse('\'');
return result;
}
return null;
}
private final byte[] parseBinaryLiteralIf() {
if (parseIf("X'", false) || parseIf("x'", false)) {
if (parseIf('\''))
return EMPTY_BYTE;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
char c1 = 0;
char c2 = 0;
do {
while (hasMore()) {
c1 = character();
if (c1 == ' ')
positionInc();
else
break;
}
c2 = characterNext();
if (c1 == '\'')
break;
if (c2 == '\'')
throw exception("Unexpected token: \"'\"");
try {
buffer.write(Integer.parseInt("" + c1 + c2, 16));
}
catch (NumberFormatException e) {
throw exception("Illegal character for binary literal");
}
positionInc(2);
}
while (hasMore());
if (c1 == '\'') {
positionInc();
parseWhitespaceIf();
return buffer.toByteArray();
}
throw exception("Binary literal not terminated");
}
return null;
}
private final String parseOracleQuotedStringLiteral() {
parse('\'', false);
char start = character();
char end;
switch (start) {
case '[' : end = ']'; positionInc(); break;
case '{' : end = '}'; positionInc(); break;
case '(' : end = ')'; positionInc(); break;
case '<' : end = '>'; positionInc(); break;
case ' ' :
case '\t':
case '\r':
case '\n': throw exception("Illegal quote string character");
default : end = start; positionInc(); break;
}
StringBuilder sb = new StringBuilder();
for (int i = position(); i < sql.length; i++) {
char c = character(i);
if (c == end)
if (character(i + 1) == '\'') {
position(i + 2);
parseWhitespaceIf();
return sb.toString();
}
else {
i++;
}
sb.append(c);
}
throw exception("Quoted string literal not terminated");
}
private final String parseDollarQuotedStringLiteralIf() {
int previous = position();
if (!parseIf('$'))
return null;
int openTokenStart = previous;
int openTokenEnd = previous;
int closeTokenStart = -1;
int closeTokenEnd = -1;
tokenLoop:
for (int i = position(); i < sql.length; i++) {
char c = character(i);
// "Good enough" approximation of PostgreSQL's syntax requirements
// for dollar quoted tokens. If formal definition is known, improve.
// No definition is available from this documentation:
// https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING
if (!Character.isJavaIdentifierPart(c))
return null;
openTokenEnd++;
if (c == '$')
break tokenLoop;
}
position(openTokenEnd + 1);
literalLoop:
for (int i = position(); i < sql.length; i++) {
char c = character(i);
if (c == '$')
if (closeTokenStart == -1)
closeTokenStart = i;
else if (openTokenEnd - openTokenStart == (closeTokenEnd = i) - closeTokenStart)
break literalLoop;
else
closeTokenStart = closeTokenEnd;
else if (closeTokenStart > -1 && character(i) != character(i - (closeTokenStart - openTokenStart)))
closeTokenStart = -1;
}
if (closeTokenEnd != -1) {
position(closeTokenEnd + 1);
return substring(openTokenEnd + 1, closeTokenStart);
}
position(previous);
return null;
}
private final String parseUnquotedStringLiteral(boolean postgresEscaping, char delim) {
parse(delim, false);
StringBuilder sb = new StringBuilder();
characterLoop:
for (int i = position(); i < sql.length; i++) {
char c1 = character(i);
// TODO MySQL string escaping...
switch (c1) {
case '\\': {
if (!postgresEscaping)
break;
i++;
char c2 = character(i);
switch (c2) {
// Escaped whitespace characters
case 'b':
c1 = '\b';
break;
case 'n':
c1 = '\n';
break;
case 't':
c1 = '\t';
break;
case 'r':
c1 = '\r';
break;
case 'f':
c1 = '\f';
break;
// Hexadecimal byte value
case 'x': {
char c3 = character(i + 1);
char c4 = character(i + 2);
int d3;
if ((d3 = Character.digit(c3, 16)) != -1) {
i++;
int d4;
if ((d4 = Character.digit(c4, 16)) != -1) {
c1 = (char) (0x10 * d3 + d4);
i++;
}
else
c1 = (char) d3;
}
else
throw exception("Illegal hexadecimal byte value");
break;
}
// Unicode character value UTF-16
case 'u':
c1 = (char) Integer.parseInt(new String(sql, i + 1, 4), 16);
i += 4;
break;
// Unicode character value UTF-32
case 'U':
sb.appendCodePoint(Integer.parseInt(new String(sql, i + 1, 8), 16));
i += 8;
continue characterLoop;
default:
// Octal byte value
if (Character.digit(c2, 8) != -1) {
char c3 = character(i + 1);
if (Character.digit(c3, 8) != -1) {
i++;
char c4 = character(i + 1);
if (Character.digit(c4, 8) != -1) {
i++;
c1 = (char) Integer.parseInt("" + c2 + c3 + c4, 8);
}
else {
c1 = (char) Integer.parseInt("" + c2 + c3, 8);
}
}
else {
c1 = (char) Integer.parseInt("" + c2, 8);
}
}
// All other characters
else {
c1 = c2;
}
break;
}
break;
}
case '\'': {
if (character(i + 1) != delim) {
position(i + 1);
parseWhitespaceIf();
return sb.toString();
}
i++;
break;
}
}
sb.append(c1);
}
throw exception("String literal not terminated");
}
private final Field<Number> parseFieldUnsignedNumericLiteral(Sign sign) {
Field<Number> result = parseFieldUnsignedNumericLiteralIf(sign);
if (result == null)
throw expected("Unsigned numeric literal");
return result;
}
private final Field<Number> parseFieldUnsignedNumericLiteralIf(Sign sign) {
Number r = parseUnsignedNumericLiteralIf(sign);
return r == null ? null : inline(r);
}
private final Number parseUnsignedNumericLiteralIf(Sign sign) {
int p = position();
char c;
for (;;) {
c = character();
if (c >= '0' && c <= '9') {
positionInc();
}
else
break;
}
if (c == '.') {
positionInc();
}
else {
if (p == position())
return null;
String s = substring(p, position());
parseWhitespaceIf();
try {
return sign == Sign.MINUS
? -Long.valueOf(s)
: Long.valueOf(s);
}
catch (Exception e1) {
return sign == Sign.MINUS
? new BigInteger(s).negate()
: new BigInteger(s);
}
}
for (;;) {
c = character();
if (c >= '0' && c <= '9') {
positionInc();
}
else
break;
}
if (p == position())
return null;
String s = substring(p, position());
parseWhitespaceIf();
return sign == Sign.MINUS
? new BigDecimal(s).negate()
: new BigDecimal(s);
// TODO add floating point support
}
private final Field<Integer> parseZeroOne() {
if (parseIf('0'))
return zero();
else if (parseIf('1'))
return one();
else
throw expected("0 or 1");
}
private final Field<Integer> parseZeroOneDefault() {
if (parseIf('0'))
return zero();
else if (parseIf('1'))
return one();
else if (parseKeywordIf("DEFAULT"))
return defaultValue(INTEGER);
else
throw expected("0 or 1");
}
private final Long parseSignedInteger() {
Long result = parseSignedIntegerIf();
if (result == null)
throw expected("Signed integer");
return result;
}
private final Long parseSignedIntegerIf() {
Sign sign = parseSign();
Long unsigned;
if (sign == Sign.MINUS)
unsigned = parseUnsignedInteger();
else
unsigned = parseUnsignedIntegerIf();
return unsigned == null
? null
: sign == Sign.MINUS
? -unsigned
: unsigned;
}
private final <T> Param<T> requireParam(Field<T> field) {
if (field instanceof Param)
return (Param<T>) field;
else
throw expected("Bind parameter or constant");
}
private final <T> T parseParenthesised(Supplier<T> supplier) {
parse('(');
T result = supplier.get();
parse(')');
return result;
}
private final Field<Long> parseParenthesisedUnsignedIntegerOrBindVariable() {
Field<Long> result;
int parens;
for (parens = 0; parseIf('('); parens++);
result = parseUnsignedIntegerOrBindVariable();
for (; parens > 0 && parse(')'); parens--);
return result;
}
private final Field<Long> parseUnsignedIntegerOrBindVariable() {
Long i = parseUnsignedIntegerIf();
return i != null ? DSL.inline(i) : (Field<Long>) parseBindVariable();
}
private final Long parseUnsignedInteger() {
Long result = parseUnsignedIntegerIf();
if (result == null)
throw expected("Unsigned integer");
return result;
}
private final Long parseUnsignedIntegerIf() {
int p = position();
for (;;) {
char c = character();
if (c >= '0' && c <= '9')
positionInc();
else
break;
}
if (p == position())
return null;
String s = substring(p, position());
parseWhitespaceIf();
return Long.valueOf(s);
}
private final JoinType parseJoinTypeIf() {
if (parseKeywordIf("CROSS")) {
if (parseKeywordIf("JOIN"))
return JoinType.CROSS_JOIN;
else if (parseKeywordIf("APPLY"))
return JoinType.CROSS_APPLY;
}
else if (parseKeywordIf("INNER") && parseKeyword("JOIN"))
return JoinType.JOIN;
else if (parseKeywordIf("JOIN"))
return JoinType.JOIN;
else if (parseKeywordIf("LEFT")) {
if (parseKeywordIf("SEMI") && parseKeyword("JOIN"))
return JoinType.LEFT_SEMI_JOIN;
else if (parseKeywordIf("ANTI") && parseKeyword("JOIN"))
return JoinType.LEFT_ANTI_JOIN;
else if ((parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
return JoinType.LEFT_OUTER_JOIN;
}
else if (parseKeywordIf("RIGHT") && (parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
return JoinType.RIGHT_OUTER_JOIN;
else if (parseKeywordIf("FULL") && (parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
return JoinType.FULL_OUTER_JOIN;
else if (parseKeywordIf("OUTER APPLY"))
return JoinType.OUTER_APPLY;
else if (parseKeywordIf("NATURAL")) {
if (parseKeywordIf("LEFT") && (parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
return JoinType.NATURAL_LEFT_OUTER_JOIN;
else if (parseKeywordIf("RIGHT") && (parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
return JoinType.NATURAL_RIGHT_OUTER_JOIN;
else if (parseKeywordIf("FULL") && (parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
return JoinType.NATURAL_FULL_OUTER_JOIN;
else if ((parseKeywordIf("INNER") || true) && parseKeyword("JOIN"))
return JoinType.NATURAL_JOIN;
}
else if (parseKeywordIf("STRAIGHT_JOIN"))
return JoinType.STRAIGHT_JOIN;
return null;
// TODO partitioned join
}
private final TruthValue parseTruthValueIf() {
if (parseKeywordIf("TRUE"))
return TruthValue.T_TRUE;
else if (parseKeywordIf("FALSE"))
return TruthValue.T_FALSE;
else if (parseKeywordIf("NULL"))
return TruthValue.T_NULL;
return null;
}
private final CombineOperator parseCombineOperatorIf(boolean intersectOnly) {
if (!intersectOnly && parseKeywordIf("UNION"))
if (parseKeywordIf("ALL"))
return CombineOperator.UNION_ALL;
else if (parseKeywordIf("DISTINCT"))
return CombineOperator.UNION;
else
return CombineOperator.UNION;
else if (!intersectOnly && (parseKeywordIf("EXCEPT") || parseKeywordIf("MINUS")))
if (parseKeywordIf("ALL"))
return CombineOperator.EXCEPT_ALL;
else if (parseKeywordIf("DISTINCT"))
return CombineOperator.EXCEPT;
else
return CombineOperator.EXCEPT;
else if (intersectOnly && parseKeywordIf("INTERSECT"))
if (parseKeywordIf("ALL"))
return CombineOperator.INTERSECT_ALL;
else if (parseKeywordIf("DISTINCT"))
return CombineOperator.INTERSECT;
else
return CombineOperator.INTERSECT;
return null;
}
private final ComputationalOperation parseComputationalOperationIf() {
switch (characterUpper()) {
case 'A':
if (parseFunctionNameIf("ANY"))
return ComputationalOperation.ANY;
else if (parseFunctionNameIf("AVG"))
return ComputationalOperation.AVG;
break;
case 'B':
if (parseFunctionNameIf("BOOL_AND", "BOOLAND_AGG"))
return ComputationalOperation.EVERY;
else if (parseFunctionNameIf("BOOL_OR", "BOOLOR_AGG"))
return ComputationalOperation.ANY;
break;
case 'E':
if (parseFunctionNameIf("EVERY"))
return ComputationalOperation.EVERY;
break;
case 'L':
if (parseFunctionNameIf("LOGICAL_AND"))
return ComputationalOperation.EVERY;
else if (parseFunctionNameIf("LOGICAL_OR"))
return ComputationalOperation.ANY;
break;
case 'M':
if (parseFunctionNameIf("MAX"))
return ComputationalOperation.MAX;
else if (parseFunctionNameIf("MEDIAN"))
return ComputationalOperation.MEDIAN;
else if (parseFunctionNameIf("MIN"))
return ComputationalOperation.MIN;
break;
case 'P':
if (parseFunctionNameIf("PRODUCT"))
return ComputationalOperation.PRODUCT;
break;
case 'S':
if (parseFunctionNameIf("SUM"))
return ComputationalOperation.SUM;
else if (parseFunctionNameIf("SOME"))
return ComputationalOperation.ANY;
else if (parseFunctionNameIf("STDDEV_POP", "STDEVP"))
return ComputationalOperation.STDDEV_POP;
else if (parseFunctionNameIf("STDDEV_SAMP", "STDEV", "STDEV_SAMP"))
return ComputationalOperation.STDDEV_SAMP;
break;
case 'V':
if (parseFunctionNameIf("VAR_POP", "VARIANCE", "VARP"))
return ComputationalOperation.VAR_POP;
else if (parseFunctionNameIf("VAR_SAMP", "VARIANCE_SAMP", "VAR"))
return ComputationalOperation.VAR_SAMP;
break;
}
return null;
}
private final BinarySetFunctionType parseBinarySetFunctionTypeIf() {
// TODO speed this up
for (BinarySetFunctionType type : BinarySetFunctionType.values())
if (parseFunctionNameIf(type.name()))
return type;
return null;
}
private final Comparator parseComparatorIf() {
if (parseIf("="))
return Comparator.EQUALS;
else if (parseIf("!=") || parseIf("<>") || parseIf("^="))
return Comparator.NOT_EQUALS;
else if (parseIf(">="))
return Comparator.GREATER_OR_EQUAL;
else if (parseIf(">"))
return Comparator.GREATER;
// MySQL DISTINCT operator
else if (parseIf("<=>"))
return Comparator.IS_NOT_DISTINCT_FROM;
else if (parseIf("<="))
return Comparator.LESS_OR_EQUAL;
else if (parseIf("<"))
return Comparator.LESS;
return null;
}
private static enum TSQLOuterJoinComparator {
LEFT, RIGHT
}
private final TSQLOuterJoinComparator parseTSQLOuterJoinComparatorIf() {
if (parseIf("*="))
return TSQLOuterJoinComparator.LEFT;
else if (parseIf("=*"))
return TSQLOuterJoinComparator.RIGHT;
else
return null;
}
// -----------------------------------------------------------------------------------------------------------------
// Other tokens
// -----------------------------------------------------------------------------------------------------------------
private final String parseUntilEOL() {
String result = parseUntilEOLIf();
if (result == null)
throw expected("Content before EOL");
return result;
}
private final String parseUntilEOLIf() {
int start = position();
int stop = start;
for (; stop < sql.length; stop++) {
char c = character(stop);
if (c == '\r') {
if (character(stop + 1) == '\n')
stop++;
break;
}
else if (c == '\n')
break;
}
if (start == stop)
return null;
position(stop);
parseWhitespaceIf();
return substring(start, stop);
}
private final boolean parseTokens(char... tokens) {
boolean result = parseTokensIf(tokens);
if (!result)
throw expected(new String(tokens));
return result;
}
private final boolean parseTokensIf(char... tokens) {
int p = position();
for (char token : tokens) {
if (!parseIf(token)) {
position(p);
return false;
}
}
return true;
}
private final boolean peekTokens(char... tokens) {
int p = position();
for (char token : tokens) {
if (!parseIf(token)) {
position(p);
return false;
}
}
position(p);
return true;
}
private final boolean parse(String string) {
boolean result = parseIf(string);
if (!result)
throw expected(string);
return result;
}
private final boolean parseIf(String string) {
return parseIf(string, true);
}
private final boolean parseIf(String string, boolean skipAfterWhitespace) {
boolean result = peek(string);
if (result) {
positionInc(string.length());
if (skipAfterWhitespace)
parseWhitespaceIf();
}
return result;
}
private final boolean parse(char c) {
return parse(c, true);
}
private final boolean parse(char c, boolean skipAfterWhitespace) {
if (!parseIf(c, skipAfterWhitespace))
throw expected("Token '" + c + "'");
return true;
}
private final boolean parseIf(char c) {
return parseIf(c, true);
}
private final boolean parseIf(char c, boolean skipAfterWhitespace) {
boolean result = peek(c);
if (result) {
positionInc();
if (skipAfterWhitespace)
parseWhitespaceIf();
}
return result;
}
private final boolean parseIf(char c, char peek, boolean skipAfterWhitespace) {
if (character() != c)
return false;
if (characterNext() != peek)
return false;
positionInc();
if (skipAfterWhitespace)
parseWhitespaceIf();
return true;
}
private final boolean parseFunctionNameIf(String name) {
return peekKeyword(name, true, false, true);
}
private final boolean parseFunctionNameIf(String name1, String name2) {
return parseFunctionNameIf(name1) || parseFunctionNameIf(name2);
}
private final boolean parseFunctionNameIf(String name1, String name2, String name3) {
return parseFunctionNameIf(name1) || parseFunctionNameIf(name2) || parseFunctionNameIf(name3);
}
private final boolean parseFunctionNameIf(String... names) {
for (String name : names)
if (parseFunctionNameIf(name))
return true;
return false;
}
private final boolean parseOperator(String operator) {
if (!parseOperatorIf(operator))
throw expected("Operator '" + operator + "'");
return true;
}
private final boolean parseOperatorIf(String operator) {
return peekOperator(operator, true);
}
private final boolean peekOperator(String operator) {
return peekOperator(operator, false);
}
private final boolean peekOperator(String operator, boolean updatePosition) {
int length = operator.length();
int p = position();
if (sql.length < p + length)
return false;
int pos = afterWhitespace(p, false);
for (int i = 0; i < length; i++, pos++)
if (sql[pos] != operator.charAt(i))
return false;
// [#9888] An operator that is followed by a special character is very likely another, more complex operator
if (isOperatorPart(pos))
return false;
if (updatePosition) {
position(pos);
parseWhitespaceIf();
}
return true;
}
private final boolean parseKeyword(String keyword) {
if (!parseKeywordIf(keyword))
throw expected("Keyword '" + keyword + "'");
return true;
}
private final boolean parseKeywordIf(String keyword) {
return peekKeyword(keyword, true, false, false);
}
private final boolean parseKeywordIf(String... keywords) {
for (String keyword : keywords)
if (parseKeywordIf(keyword))
return true;
return false;
}
private final boolean parseKeyword(String... keywords) {
if (parseKeywordIf(keywords))
return true;
throw expected(keywords);
}
private final Keyword parseAndGetKeyword(String... keywords) {
Keyword result = parseAndGetKeywordIf(keywords);
if (result == null)
throw expected(keywords);
return result;
}
private final Keyword parseAndGetKeywordIf(String... keywords) {
for (String keyword : keywords)
if (parseKeywordIf(keyword))
return keyword(keyword.toLowerCase());
return null;
}
private final Keyword parseAndGetKeywordIf(String keyword) {
if (parseKeywordIf(keyword))
return keyword(keyword.toLowerCase());
return null;
}
private final boolean peek(char c) {
if (character() != c)
return false;
return true;
}
private final boolean peek(String string) {
return peek(string, position());
}
private final boolean peek(String string, int p) {
int length = string.length();
if (sql.length < p + length)
return false;
for (int i = 0; i < length; i++)
if (sql[p + i] != string.charAt(i))
return false;
return true;
}
private final boolean peekKeyword(String... keywords) {
for (String keyword : keywords)
if (peekKeyword(keyword))
return true;
return false;
}
private final boolean peekKeyword(String keyword) {
return peekKeyword(keyword, false, false, false);
}
private final boolean peekKeyword(String keyword, boolean updatePosition, boolean peekIntoParens, boolean requireFunction) {
int length = keyword.length();
int p = position();
if (sql.length < p + length)
return false;
int skip = afterWhitespace(p, peekIntoParens) - p;
for (int i = 0; i < length; i++) {
char c = keyword.charAt(i);
int pos = p + i + skip;
switch (c) {
case ' ':
if (!Character.isWhitespace(sql[pos]))
return false;
skip = skip + (afterWhitespace(pos) - pos - 1);
break;
default:
if (upper(sql[pos]) != c)
return false;
break;
}
}
int pos = p + length + skip;
// [#8806] A keyword that is followed by a period is very likely an identifier
if (isIdentifierPart(pos) || character(pos) == '.')
return false;
if (requireFunction)
if (character(afterWhitespace(pos)) != '(')
return false;
if (updatePosition) {
positionInc(length + skip);
parseWhitespaceIf();
}
return true;
}
private final boolean parseWhitespaceIf() {
int p = position();
position(afterWhitespace(p));
return p != position();
}
private final int afterWhitespace(int p) {
return afterWhitespace(p, false);
}
private final int afterWhitespace(int p, boolean peekIntoParens) {
// [#8074] The SQL standard and some implementations (e.g. PostgreSQL,
// SQL Server) support nesting block comments
int blockCommentNestLevel = 0;
boolean ignoreComment = false;
final String ignoreCommentStart = settings().getParseIgnoreCommentStart();
final String ignoreCommentStop = settings().getParseIgnoreCommentStop();
final boolean checkIgnoreComment = !FALSE.equals(settings().isParseIgnoreComments());
loop:
for (int i = p; i < sql.length; i++) {
switch (sql[i]) {
case ' ':
case '\t':
case '\r':
case '\n':
p = i + 1;
continue loop;
case '(':
if (peekIntoParens)
continue loop;
else
break loop;
case '/':
if (i + 1 < sql.length && sql[i + 1] == '*') {
i = i + 2;
blockCommentNestLevel++;
while (i < sql.length) {
if (!(ignoreComment = peekIgnoreComment(ignoreComment, ignoreCommentStart, ignoreCommentStop, checkIgnoreComment, i))) {
switch (sql[i]) {
case '/':
if (i + 1 < sql.length && sql[i + 1] == '*') {
i = i + 2;
blockCommentNestLevel++;
}
break;
case '+':
if (!ignoreHints() && i + 1 < sql.length && ((sql[i + 1] >= 'A' && sql[i + 1] <= 'Z') || (sql[i + 1] >= 'a' && sql[i + 1] <= 'z'))) {
blockCommentNestLevel = 0;
break loop;
}
break;
case '*':
if (i + 1 < sql.length && sql[i + 1] == '/') {
p = (i = i + 1) + 1;
if (--blockCommentNestLevel == 0)
continue loop;
}
break;
}
}
i++;
}
}
// [#9651] H2 and Snowflake's c-style single line comments
else if (i + 1 < sql.length && sql[i + 1] == '/') {
i = i + 2;
while (i < sql.length) {
if (!(ignoreComment = peekIgnoreComment(ignoreComment, ignoreCommentStart, ignoreCommentStop, checkIgnoreComment, i))) {
switch (sql[i]) {
case '\r':
case '\n':
p = i + 1;
continue loop;
}
}
i++;
}
p = i;
}
break loop;
case '-':
case '#':
if (sql[i] == '-' && i + 1 < sql.length && sql[i + 1] == '-' ||
sql[i] == '#' && SUPPORTS_HASH_COMMENT_SYNTAX.contains(dialect())) {
if (sql[i] == '-')
i = i + 2;
else
i++;
while (i < sql.length) {
if (!(ignoreComment = peekIgnoreComment(ignoreComment, ignoreCommentStart, ignoreCommentStop, checkIgnoreComment, i))) {
switch (sql[i]) {
case '\r':
case '\n':
p = i + 1;
continue loop;
}
}
i++;
}
p = 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.
default:
p = i;
break loop;
}
}
if (blockCommentNestLevel > 0)
throw exception("Nested block comment not properly closed");
return p;
}
private final boolean peekIgnoreComment(
boolean ignoreComment,
String ignoreCommentStart,
String ignoreCommentStop,
boolean checkIgnoreComment,
int i
) {
if (checkIgnoreComment)
if (!ignoreComment)
ignoreComment = peek(ignoreCommentStart, i);
else
ignoreComment = !peek(ignoreCommentStop, i);
return ignoreComment;
}
private final char upper(char c) {
return c >= 'a' && c <= 'z' ? (char) (c - ('a' - 'A')) : c;
}
private static enum TruthValue {
T_TRUE,
T_FALSE,
T_NULL;
}
private static enum ComputationalOperation {
AVG,
MAX,
MIN,
SUM,
PRODUCT,
EVERY,
ANY,
SOME,
COUNT,
STDDEV_POP,
STDDEV_SAMP,
VAR_POP,
VAR_SAMP,
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[] KEYWORDS_IN_STATEMENTS = {
"ALTER",
"BEGIN",
"COMMENT",
"CREATE",
"DECLARE",
"DELETE",
"DROP",
"END", // In T-SQL, semicolons are optional, so a T-SQL END clause might appear
"GO", // The T-SQL statement batch delimiter, not a SELECT keyword
"GRANT",
"INSERT",
"MERGE",
"RENAME",
"REVOKE",
"SELECT",
"SET",
"TRUNCATE",
"UPDATE",
"USE",
"VALUES",
"WITH",
};
private static final String[] KEYWORDS_IN_SELECT = {
"CONNECT BY",
"EXCEPT",
"FETCH FIRST",
"FETCH NEXT",
"FOR JSON",
"FOR KEY SHARE",
"FOR NO KEY UPDATE",
"FOR SHARE",
"FOR UPDATE",
"FOR XML",
"FROM",
"GROUP BY",
"HAVING",
"INTERSECT",
"INTO",
"LIMIT",
"MINUS",
"OFFSET",
"ON",
"ORDER BY",
"PARTITION BY",
"QUALIFY",
"RETURNING",
"ROWS",
"START WITH",
"UNION",
"WHERE",
"WINDOW",
};
private static final String[] KEYWORDS_IN_FROM = {
"CONNECT BY",
"CREATE",
"CROSS APPLY",
"CROSS JOIN",
"EXCEPT",
"FETCH FIRST",
"FETCH NEXT",
"FOR JSON",
"FOR KEY SHARE",
"FOR NO KEY UPDATE",
"FOR SHARE",
"FOR UPDATE",
"FOR XML",
"FULL JOIN",
"FULL OUTER JOIN",
"GROUP BY",
"HAVING",
"INNER JOIN",
"INTERSECT",
"INTO",
"JOIN",
"LEFT ANTI JOIN",
"LEFT JOIN",
"LEFT OUTER JOIN",
"LEFT SEMI JOIN",
"LIMIT",
"MINUS",
"NATURAL FULL JOIN",
"NATURAL FULL OUTER JOIN",
"NATURAL INNER JOIN",
"NATURAL JOIN",
"NATURAL LEFT JOIN",
"NATURAL LEFT OUTER JOIN",
"NATURAL RIGHT JOIN",
"NATURAL RIGHT OUTER JOIN",
"OFFSET",
"ON",
"ORDER BY",
"OUTER APPLY",
"PARTITION BY",
"QUALIFY",
"RETURNING",
"RIGHT ANTI JOIN",
"RIGHT JOIN",
"RIGHT OUTER JOIN",
"RIGHT SEMI JOIN",
"ROWS",
"START WITH",
"STRAIGHT_JOIN",
"UNION",
"USING",
"WHERE",
"WINDOW",
};
private static final String[] PIVOT_KEYWORDS = {
"FOR"
};
private static final DDLQuery IGNORE = Reflect.on(DSL.query("/* ignored */")).as(DDLQuery.class, QueryPartInternal.class);
private static final Query IGNORE_NO_DELIMITER = Reflect.on(DSL.query("/* ignored */")).as(Query.class, QueryPartInternal.class);
private final DSLContext dsl;
private final Locale locale;
private final Meta meta;
private final char[] sql;
private final ParseWithMetaLookups metaLookups;
private boolean metaLookupsForceIgnore;
private final Consumer<Param<?>> bindParamListener;
private int position = 0;
private boolean ignoreHints = true;
private final Object[] bindings;
private int bindIndex = 0;
private final Map<String, Param<?>> bindParams = new LinkedHashMap<>();
private String delimiter = ";";
private final ScopeStack<Name, Table<?>> tableScope = new ScopeStack<>(null);
private final ScopeStack<Name, Field<?>> fieldScope = new ScopeStack<>(null);
private final ScopeStack<Name, FieldProxy<?>> lookupFields = new ScopeStack<>(null);
private boolean scopeClear = false;
ParserContext(
DSLContext dsl,
Meta meta,
ParseWithMetaLookups metaLookups,
String sqlString,
Object[] bindings
) {
this.dsl = dsl;
this.locale = parseLocale(dsl.settings());
this.meta = meta;
this.metaLookups = metaLookups;
this.sql = sqlString != null ? sqlString.toCharArray() : new char[0];
this.bindings = bindings;
// [#8722] This is an undocumented flag that allows for collecting parameters from the parser
// Do not rely on this flag. It will change incompatibly in the future.
this.bindParamListener = (Consumer<Param<?>>) dsl.configuration().data("org.jooq.parser.param-collector");
parseWhitespaceIf();
}
private final Configuration configuration() {
return dsl.configuration();
}
private final Settings settings() {
return configuration().settings();
}
private final SQLDialect dialect() {
SQLDialect result = settings().getParseDialect();
if (result == null)
result = SQLDialect.DEFAULT;
return result;
}
private final SQLDialect family() {
return dialect().family();
}
private final boolean metaLookupsForceIgnore() {
return this.metaLookupsForceIgnore;
}
private final ParserContext metaLookupsForceIgnore(boolean m) {
this.metaLookupsForceIgnore = m;
return this;
}
private final boolean proEdition() {
return configuration().commercial();
}
private final boolean requireProEdition() {
if (!proEdition())
throw exception("Feature only supported in pro edition");
return true;
}
private final boolean requireUnsupportedSyntax() {
if (dsl.configuration().settings().getParseUnsupportedSyntax() == ParseUnsupportedSyntax.FAIL)
throw exception("Syntax not supported");
return true;
}
private final String substring(int startPosition, int endPosition) {
return new String(sql, startPosition, endPosition - startPosition);
}
private final ParserException internalError() {
return exception("Internal Error");
}
private final ParserException expected(String object) {
return init(new ParserException(mark(), object + " expected"));
}
private final ParserException expected(String... objects) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < objects.length; i++)
if (i == 0)
sb.append(objects[i]);
// [#10169] Correct application of Oxford comma 🧐
else if (i == 1 && objects.length == 2)
sb.append(" or ").append(objects[i]);
else if (i == objects.length - 1)
sb.append(", or ").append(objects[i]);
else
sb.append(", ").append(objects[i]);
return init(new ParserException(mark(), sb.toString() + " expected"));
}
private final ParserException notImplemented(String feature) {
return notImplemented(feature, "https://github.com/jOOQ/jOOQ/issues/10171");
}
private final ParserException notImplemented(String feature, String link) {
return init(new ParserException(mark(), feature + " not yet implemented. If you're interested in this feature, please comment on " + link));
}
private final ParserException unsupportedClause() {
return init(new ParserException(mark(), "Unsupported clause"));
}
private final ParserException exception(String message) {
return init(new ParserException(mark(), message));
}
private final ParserException init(ParserException e) {
int[] line = line();
return e.position(position).line(line[0]).column(line[1]);
}
private final Object nextBinding() {
if (bindIndex++ < bindings.length)
return bindings[bindIndex - 1];
else if (bindings.length == 0)
return null;
else
throw exception("No binding provided for bind index " + bindIndex);
}
private final int[] line() {
int line = 1;
int column = 1;
for (int i = 0; i < position; i++) {
if (sql[i] == '\r') {
line++;
column = 1;
if (i + 1 < sql.length && sql[i + 1] == '\n')
i++;
}
else if (sql[i] == '\n') {
line++;
column = 1;
}
else {
column++;
}
}
return new int[] { line, column };
}
private final char characterUpper() {
return Character.toUpperCase(character());
}
private final char character() {
return character(position);
}
private final char character(int pos) {
return pos >= 0 && pos < sql.length ? sql[pos] : ' ';
}
private final char characterNextUpper() {
return Character.toUpperCase(characterNext());
}
private final char characterNext() {
return character(position + 1);
}
private final int position() {
return position;
}
private final boolean position(int newPosition) {
position = newPosition;
return true;
}
private final boolean positionInc() {
return positionInc(1);
}
private final boolean positionInc(int inc) {
return position(position + inc);
}
private final String delimiter() {
return delimiter;
}
private final void delimiter(String newDelimiter) {
delimiter = newDelimiter;
}
private final boolean ignoreHints() {
return ignoreHints;
}
private final void ignoreHints(boolean newIgnoreHints) {
ignoreHints = newIgnoreHints;
}
private final boolean isOperatorPart(int pos) {
return isOperatorPart(character(pos));
}
private final boolean isOperatorPart(char character) {
// Obtain all distinct, built-in PostgreSQL operator characters:
// select distinct regexp_split_to_table(oprname, '') from pg_catalog.pg_operator order by 1;
switch (character) {
case '!':
case '#':
case '%':
case '&':
case '*':
case '+':
case '-':
case '/':
case ':':
case '<':
case '=':
case '>':
case '?':
case '@':
case '^':
case '|':
case '~':
return true;
}
return false;
}
private final boolean isIdentifierPart() {
return isIdentifierPart(character());
}
private final boolean isIdentifierPart(int pos) {
return isIdentifierPart(character(pos));
}
private final boolean isIdentifierPart(char character) {
return Character.isJavaIdentifierPart(character)
|| ((character == '@'
|| character == '#')
&& character != delimiter.charAt(0));
}
private final boolean hasMore() {
return position < sql.length;
}
private final boolean hasMore(int offset) {
return position + offset < sql.length;
}
private final boolean done() {
return position >= sql.length && (bindings.length == 0 || bindings.length == bindIndex);
}
private final <Q extends QueryPart> Q done(String message, Q result) {
if (done())
return notify(result);
else
throw exception(message);
}
private final <Q extends QueryPart> Q notify(Q result) {
if (bindParamListener != null) {
final Map<String, Param<?>> params = new LinkedHashMap<>();
// [#8722] TODO Replace this by a public SPI
// [#11054] Use a VisitListener to find actual Params in the expression tree,
// which may have more refined DataTypes attached to them, from context
dsl.configuration().derive(new DefaultVisitListener() {
@Override
public void visitStart(VisitContext context) {
if (context.queryPart() instanceof Param) {
Param<?> p = (Param<?>) context.queryPart();
if (!params.containsKey(p.getParamName()))
params.put(p.getParamName(), p);
}
}
}).dsl().render(result);
for (String name : bindParams.keySet())
bindParamListener.accept(params.get(name));
}
return result;
}
private final String mark() {
int[] line = line();
return "[" + line[0] + ":" + line[1] + "] "
+ (position > 50 ? "..." : "")
+ substring(Math.max(0, position - 50), position)
+ "[*]"
+ substring(position, Math.min(sql.length, position + 80))
+ (sql.length > position + 80 ? "..." : "");
}
private final void scope(Table<?> table) {
tableScope.set(table.getQualifiedName(), table);
}
private final void scope(Field<?> field) {
fieldScope.set(field.getQualifiedName(), field);
}
private final void scopeStart() {
tableScope.scopeStart();
fieldScope.scopeStart();
lookupFields.scopeStart();
lookupFields.setAll(null);
}
private final void scopeEnd() {
List<FieldProxy<?>> retain = new ArrayList<>();
for (FieldProxy<?> lookup : lookupFields) {
Value<Field<?>> found = null;
for (Field<?> f : fieldScope) {
if (f.getName().equals(lookup.getName())) {
if (found != null) {
position(lookup.position());
throw exception("Ambiguous field identifier");
}
// TODO: Does this instance of "found" really interact with the one below?
found = new Value<>(0, f);
}
}
found = resolveInTableScope(tableScope.valueIterable(), lookup.getQualifiedName(), lookup, found);
if (found != null)
lookup.delegate((AbstractField) found.value);
else
retain.add(lookup);
}
lookupFields.scopeEnd();
tableScope.scopeEnd();
fieldScope.scopeEnd();
for (FieldProxy<?> r : retain)
if (lookupFields.get(r.getQualifiedName()) == null)
if (lookupFields.inScope())
lookupFields.set(r.getQualifiedName(), r);
else
unknownField(r);
}
private final Value<Field<?>> resolveInTableScope(Iterable<Value<Table<?>>> tables, Name lookupName, FieldProxy<?> lookup, Value<Field<?>> found) {
tableScopeLoop:
for (Value<Table<?>> t : tables) {
Value<Field<?>> f;
if (t.value instanceof JoinTable) {
found = resolveInTableScope(
asList(
new Value<>(t.scopeLevel, ((JoinTable) t.value).lhs),
new Value<>(t.scopeLevel, ((JoinTable) t.value).rhs)
),
lookupName, lookup, found
);
}
else if (lookupName.qualified()) {
// Additional tests:
// - More complex search paths
// - Ambiguities from multiple search paths, when S1.T and S2.T conflict
// - Test fully qualified column names vs partially qualified column names
Name q = lookupName.qualifier();
boolean x = q.qualified();
if (x && q.equals(t.value.getQualifiedName()) || !x && q.last().equals(t.value.getName()))
if ((found = Value.of(t.scopeLevel, t.value.field(lookup.getName()))) != null)
break tableScopeLoop;
}
else if ((f = Value.of(t.scopeLevel, t.value.field(lookup.getName()))) != null) {
if (found == null || found.scopeLevel < f.scopeLevel) {
found = f;
}
else {
position(lookup.position());
throw exception("Ambiguous field identifier");
}
}
}
return found;
}
private final void scopeClear() {
scopeClear = true;
}
private final void scopeResolve() {
if (!lookupFields.isEmpty())
unknownField(lookupFields.iterator().next());
}
private final void unknownField(FieldProxy<?> field) {
if (!scopeClear && !metaLookupsForceIgnore && metaLookups == THROW_ON_FAILURE) {
position(field.position());
throw exception("Unknown field identifier");
}
}
private final Table<?> lookupTable(Name name) {
if (meta != null) {
List<Table<?>> tables;
// [#8616] If name is not qualified, names reported by meta must be
// unqualified as well
if (!(tables = meta.getTables(name)).isEmpty())
for (Table<?> table : tables)
if (table.getQualifiedName().qualified() == name.qualified())
return tables.get(0);
// [#8616] If name is not qualified, try the search path as well
if (!name.qualified())
for (ParseSearchSchema schema : settings().getParseSearchPath())
if ((tables = meta.getTables(name(schema.getCatalog(), schema.getSchema()).append(name))).size() == 1)
return tables.get(0);
}
if (!metaLookupsForceIgnore && metaLookups == THROW_ON_FAILURE)
throw exception("Unknown table identifier");
return table(name);
}
private final Field<?> lookupField(Name name) {
if (metaLookups == ParseWithMetaLookups.OFF)
return field(name);
FieldProxy<?> field = lookupFields.get(name);
if (field == null)
lookupFields.set(name, field = new FieldProxy<>((AbstractField<Object>) field(name), position));
return field;
}
@Override
public String toString() {
return mark();
}
}