All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jooq.impl.ParserImpl Maven / Gradle / Ivy

There is a newer version: 3.19.15
Show newest version
/*
 * 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
 * Apache-2.0 license 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.DatePart.DAY;
import static org.jooq.DatePart.HOUR;
import static org.jooq.DatePart.MINUTE;
import static org.jooq.DatePart.MONTH;
import static org.jooq.DatePart.SECOND;
import static org.jooq.DatePart.YEAR;
import static org.jooq.JoinType.JOIN;
// ...
// ...
// ...
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
// ...
// ...
// ...
// ...
// ...
import static org.jooq.VisitListener.onVisitStart;
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.acosh;
import static org.jooq.impl.DSL.acoth;
import static org.jooq.impl.DSL.all;
import static org.jooq.impl.DSL.any;
import static org.jooq.impl.DSL.anyValue;
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.asinh;
import static org.jooq.impl.DSL.asterisk;
import static org.jooq.impl.DSL.atan;
import static org.jooq.impl.DSL.atanh;
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.bitAndAgg;
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.bitNandAgg;
import static org.jooq.impl.DSL.bitNor;
import static org.jooq.impl.DSL.bitNorAgg;
import static org.jooq.impl.DSL.bitNot;
import static org.jooq.impl.DSL.bitOr;
import static org.jooq.impl.DSL.bitOrAgg;
import static org.jooq.impl.DSL.bitXNor;
import static org.jooq.impl.DSL.bitXNorAgg;
import static org.jooq.impl.DSL.bitXor;
import static org.jooq.impl.DSL.bitXorAgg;
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.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.dateAdd;
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.dual;
import static org.jooq.impl.DSL.epoch;
import static org.jooq.impl.DSL.every;
import static org.jooq.impl.DSL.excluded;
// ...
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.finalTable;
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.isnull;
import static org.jooq.impl.DSL.isoDayOfWeek;
import static org.jooq.impl.DSL.jsonArray;
import static org.jooq.impl.DSL.jsonArrayAgg;
import static org.jooq.impl.DSL.jsonExists;
import static org.jooq.impl.DSL.jsonObject;
import static org.jooq.impl.DSL.jsonObjectAgg;
import static org.jooq.impl.DSL.jsonTable;
import static org.jooq.impl.DSL.jsonValue;
import static org.jooq.impl.DSL.jsonbArray;
import static org.jooq.impl.DSL.jsonbArrayAgg;
import static org.jooq.impl.DSL.jsonbObject;
import static org.jooq.impl.DSL.jsonbObjectAgg;
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.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.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.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.multisetAgg;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.newTable;
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.oldTable;
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.reverse;
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.rtrim;
import static org.jooq.impl.DSL.schema;
import static org.jooq.impl.DSL.second;
import static org.jooq.impl.DSL.select;
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.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.systemName;
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.toDate;
import static org.jooq.impl.DSL.toHex;
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.user;
import static org.jooq.impl.DSL.uuid;
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.widthBucket;
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.xmlserializeContent;
import static org.jooq.impl.DSL.xmlserializeDocument;
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.DefaultParseContext.FunctionKeyword.FK_AND;
import static org.jooq.impl.DefaultParseContext.FunctionKeyword.FK_IN;
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.Names.N_DUAL;
import static org.jooq.impl.QOM.JSONOnNull.ABSENT_ON_NULL;
import static org.jooq.impl.QOM.JSONOnNull.NULL_ON_NULL;
// ...
// ...
// ...
// ...
import static org.jooq.impl.QOM.XMLPassingMechanism.BY_REF;
import static org.jooq.impl.QOM.XMLPassingMechanism.BY_VALUE;
import static org.jooq.impl.SQLDataType.BIGINT;
import static org.jooq.impl.SQLDataType.BINARY;
import static org.jooq.impl.SQLDataType.BIT;
import static org.jooq.impl.SQLDataType.BLOB;
import static org.jooq.impl.SQLDataType.BOOLEAN;
import static org.jooq.impl.SQLDataType.CHAR;
import static org.jooq.impl.SQLDataType.CLOB;
import static org.jooq.impl.SQLDataType.DATE;
import static org.jooq.impl.SQLDataType.DECIMAL;
import static org.jooq.impl.SQLDataType.DOUBLE;
import static org.jooq.impl.SQLDataType.FLOAT;
import static org.jooq.impl.SQLDataType.GEOGRAPHY;
import static org.jooq.impl.SQLDataType.GEOMETRY;
import static org.jooq.impl.SQLDataType.INTEGER;
import static org.jooq.impl.SQLDataType.INTERVAL;
import static org.jooq.impl.SQLDataType.INTERVALDAYTOSECOND;
import static org.jooq.impl.SQLDataType.INTERVALYEARTOMONTH;
import static org.jooq.impl.SQLDataType.JSON;
import static org.jooq.impl.SQLDataType.JSONB;
import static org.jooq.impl.SQLDataType.LONGNVARCHAR;
import static org.jooq.impl.SQLDataType.LONGVARBINARY;
import static org.jooq.impl.SQLDataType.LONGVARCHAR;
import static org.jooq.impl.SQLDataType.NCHAR;
import static org.jooq.impl.SQLDataType.NCLOB;
import static org.jooq.impl.SQLDataType.NUMERIC;
import static org.jooq.impl.SQLDataType.NVARCHAR;
import static org.jooq.impl.SQLDataType.OTHER;
import static org.jooq.impl.SQLDataType.REAL;
import static org.jooq.impl.SQLDataType.SMALLINT;
import static org.jooq.impl.SQLDataType.TIME;
import static org.jooq.impl.SQLDataType.TIMESTAMP;
import static org.jooq.impl.SQLDataType.TIMESTAMPWITHTIMEZONE;
import static org.jooq.impl.SQLDataType.TIMEWITHTIMEZONE;
import static org.jooq.impl.SQLDataType.TINYINT;
import static org.jooq.impl.SQLDataType.VARBINARY;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.SQLDataType.XML;
import static org.jooq.impl.SelectQueryImpl.EMULATE_SELECT_INTO_AS_CTAS;
import static org.jooq.impl.SelectQueryImpl.NO_SUPPORT_FOR_UPDATE_OF_FIELDS;
import static org.jooq.impl.Tools.CONFIG;
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.EMPTY_STRING;
import static org.jooq.impl.Tools.EMPTY_TABLE;
import static org.jooq.impl.Tools.aliased;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.impl.Tools.asInt;
import static org.jooq.impl.Tools.deleteQueryImpl;
import static org.jooq.impl.Tools.normaliseNameCase;
import static org.jooq.impl.Tools.selectQueryImpl;
import static org.jooq.impl.Tools.updateQueryImpl;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_PARSE_ON_CONFLICT;
import static org.jooq.impl.Transformations.transformAppendMissingTableReferences;
import static org.jooq.tools.StringUtils.defaultIfNull;

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.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
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.TreeSet;
import java.util.function.BiFunction;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
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.AlterIndexStep;
import org.jooq.AlterSchemaStep;
import org.jooq.AlterSequenceFlagsStep;
import org.jooq.AlterSequenceStep;
import org.jooq.AlterTableAddStep;
import org.jooq.AlterTableDropStep;
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.Context;
import org.jooq.CreateDomainConstraintStep;
import org.jooq.CreateDomainDefaultStep;
// ...
// ...
// ...
import org.jooq.CreateIndexIncludeStep;
import org.jooq.CreateIndexStep;
import org.jooq.CreateIndexWhereStep;
// ...
// ...
// ...
import org.jooq.CreateSequenceFlagsStep;
import org.jooq.CreateTableAsStep;
import org.jooq.CreateTableCommentStep;
import org.jooq.CreateTableElementListStep;
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.DropIndexOnStep;
import org.jooq.DropSchemaStep;
import org.jooq.DropTableStep;
import org.jooq.DropTypeStep;
import org.jooq.Field;
import org.jooq.FieldOrRow;
// ...
// ...
import org.jooq.Function1;
import org.jooq.Function2;
import org.jooq.Function3;
import org.jooq.Function4;
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.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.LanguageContext;
import org.jooq.LikeEscapeStep;
// ...
import org.jooq.Merge;
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.ParseContext;
// ...
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.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.SelectField;
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.TableElement;
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.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.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.impl.QOM.DocumentOrContent;
import org.jooq.impl.QOM.JSONOnNull;
// ...
import org.jooq.impl.QOM.UEmpty;
import org.jooq.impl.QOM.XMLPassingMechanism;
import org.jooq.impl.ScopeStack.Value;
import org.jooq.impl.Tools.BooleanDataKey;
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 DefaultParseContext ctx(String sql, Object... bindings) {
        return new DefaultParseContext(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 DefaultParseContext extends AbstractScope implements ParseContext {







    static final Set         SUPPORTS_HASH_COMMENT_SYNTAX  = SQLDialect.supportedBy(MARIADB, MYSQL);

    final Queries parse() {
        return wrap(() -> {
            List result = new ArrayList<>();
            Query query;
            int p = positionBeforeWhitespace;

            do {
                parseDelimiterSpecifications();

                while (parseDelimiterIf(false))
                    p = positionBeforeWhitespace;

                retainComments(result, p);
                query = patchParsedQuery(parseQuery(false, false));
                if (query == IGNORE.get() || query == IGNORE_NO_DELIMITER.get())
                    continue;
                if (query != null)
                    result.add(query);
            }
            while (parseDelimiterIf(true) && (p = positionBeforeWhitespace) >= 0 && !done());

            if (query != null)
                retainComments(result, p);

            return done("Unexpected token or missing query delimiter", dsl.queries(result));
        });
    }

    private final void retainComments(List result, int p) {
        if (TRUE.equals(settings().isParseRetainCommentsBetweenQueries()) && p < position) {
            for (int i = p; i < position; i++) {
                if (character(i) != ' ') {
                    result.add(new IgnoreQuery(substring(p, position)));
                    break;
                }
            }
        }
    }

    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 string =
                configuration().deriveSettings(s -> s
                    .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(string);
                String schema;
                if (matcher.find())
                    if (!StringUtils.isBlank(schema = matcher.group(2)))
                        return configuration().dsl().setSchema(schema);
                    else
                        return IGNORE.get();
            }
        }

        return query;
    }

    final Query parseQuery0() {
        return wrap(() -> done("Unexpected clause", parseQuery(false, false)));
    }

    final Statement parseStatement0() {
        return wrap(() -> done("Unexpected content", parseStatementAndSemicolonIf()));
    }



















    final ResultQuery parseResultQuery0() {
        return wrap(() -> done("Unexpected content after end of query input", (ResultQuery) parseQuery(true, false)));
    }

    final Select parseSelect0() {
        return wrap(() -> done("Unexpected content after end of query input", (Select) parseQuery(true, true)));
    }

    final Table parseTable0() {
        return wrap(() -> done("Unexpected content after end of table input", parseTable()));
    }

    final Field parseField0() {
        return wrap(() -> done("Unexpected content after end of field input", parseField()));
    }

    final Row parseRow0() {
        return wrap(() -> done("Unexpected content after end of row input", parseRow()));
    }

    final Condition parseCondition0() {
        return wrap(() -> done("Unexpected content after end of condition input", parseCondition()));
    }

    final Name parseName0() {
        return wrap(() -> 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;

        scope.scopeStart();
        boolean previousMetaLookupsForceIgnore = metaLookupsForceIgnore();
        Query result = null;
        LanguageContext previous = languageContext;

        try {
            languageContext = LanguageContext.QUERY;

            switch (characterUpper()) {
                case 'A':
                    if (!parseResultQuery && peekKeyword("ALTER"))
                        return result = metaLookupsForceIgnore(true).parseAlter();

                    break;

                case 'B':
                    if (!parseResultQuery && peekKeyword("BEGIN")) {
                        languageContext = previous;
                        return result = parseBlock(false);
                    }

                    break;

                case 'C':
                    if (!parseResultQuery && peekKeyword("CREATE"))
                        return result = metaLookupsForceIgnore(true).parseCreate();
                    else if (!parseResultQuery && peekKeyword("COMMENT ON"))
                        return result = metaLookupsForceIgnore(true).parseCommentOn();
                    else if (!parseResultQuery && parseKeywordIf("CT"))
                        return result = metaLookupsForceIgnore(true).parseCreateTable(false);
                    else if (!parseResultQuery && parseKeywordIf("CV"))
                        return result = metaLookupsForceIgnore(true).parseCreateView(false);
                    else if (!ignoreProEdition() && peekKeyword("CALL") && requireProEdition())



                        ;
                    else if (parseKeywordIf("COMMIT"))
                        throw notImplemented("COMMIT");
                    else if (parseKeywordIf("CONNECT"))
                        throw notImplemented("CONNECT");

                    break;

                case 'D':
                    if (!parseResultQuery && !ignoreProEdition() && peekKeyword("DECLARE") && requireProEdition())
                        return result = parseBlock(true);
                    else if (!parseSelect && (peekKeyword("DELETE") || peekKeyword("DEL")))
                        return result = parseDelete(null, parseResultQuery);
                    else if (!parseResultQuery && peekKeyword("DROP"))
                        return result = metaLookupsForceIgnore(true).parseDrop();
                    else if (!parseResultQuery && peekKeyword("DO"))
                        return result = parseDo();

                    break;

                case 'E':
                    if (!parseResultQuery && peekKeyword("EXECUTE BLOCK AS"))
                        return result = parseBlock(true);
                    else if (!parseResultQuery && peekKeyword("EXEC"))
                        return result = parseExec();
                    else if (!ignoreProEdition() && peekKeyword("EXECUTE PROCEDURE") && requireProEdition())



                        ;

                    break;

                case 'G':
                    if (!parseResultQuery && peekKeyword("GRANT"))
                        return result = metaLookupsForceIgnore(true).parseGrant();

                    break;

                case 'I':
                    if (!parseSelect && (peekKeyword("INSERT") || peekKeyword("INS")))
                        return result = parseInsert(null, parseResultQuery);

                    break;

                case 'L':
                    if (parseKeywordIf("LOAD"))
                        throw notImplemented("LOAD");

                    break;

                case 'M':
                    if (!parseResultQuery && peekKeyword("MERGE"))
                        return result = parseMerge(null);

                    break;

                case 'O':
                    if (!parseResultQuery && peekKeyword("OPEN"))
                        return result = parseOpen();

                    break;

                case 'R':
                    if (!parseResultQuery && peekKeyword("RENAME"))
                        return result = metaLookupsForceIgnore(true).parseRename();
                    else if (!parseResultQuery && peekKeyword("REVOKE"))
                        return result = metaLookupsForceIgnore(true).parseRevoke();
                    else if (parseKeywordIf("REPLACE"))
                        throw notImplemented("REPLACE");
                    else if (parseKeywordIf("ROLLBACK"))
                        throw notImplemented("ROLLBACK");

                    break;

                case 'S':
                    if (peekSelect(false))
                        return result = parseSelect();
                    else if (!parseResultQuery && peekKeyword("SET"))
                        return result = parseSet();
                    else if (parseKeywordIf("SAVEPOINT"))
                        throw notImplemented("SAVEPOINT");

                    break;

                case 'T':
                    if (!parseSelect && peekKeyword("TABLE"))
                        return result = parseSelect();
                    else if (!parseResultQuery && peekKeyword("TRUNCATE"))
                        return result = parseTruncate();

                    break;

                case 'U':
                    if (!parseSelect && (peekKeyword("UPDATE") || peekKeyword("UPD")))
                        return result = parseUpdate(null, parseResultQuery);
                    else if (!parseResultQuery && peekKeyword("USE"))
                        return result = parseUse();
                    else if (parseKeywordIf("UPSERT"))
                        throw notImplemented("UPSERT");

                    break;

                case 'V':
                    if (!parseSelect && peekKeyword("VALUES"))
                        return result = parseSelect();

                case 'W':
                    if (peekKeyword("WITH"))
                        return result = parseWith(parseSelect);

                    break;

                case '(':

                    // TODO are there other possible statement types?
                    if (peekKeyword("WITH", false, true, false))
                        return result = parseWith(true);
                    else
                        return result = parseSelect();

                case '{':
                    if (!ignoreProEdition() && peekKeyword("{ CALL") && requireProEdition())



                        ;

                    break;

                default:
                    break;
            }

            throw exception("Unsupported query type");
        }
        catch (ParserException e) {

            // [#9061] Don't hide this pre-existing exceptions in scopeResolve()
            scope.scopeClear();
            throw e;
        }
        finally {
            scope.scopeEnd(result);
            scope.scopeResolve();
            metaLookupsForceIgnore(previousMetaLookupsForceIgnore);
            languageContext = previous;
        }
    }

    // -----------------------------------------------------------------------------------------------------------------
    // 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> cte = new ArrayList<>();
        do {
            Name name = parseIdentifier();
            DerivedColumnList dcl = null;

            if (parseIf('(')) {
                List columnNames = parseIdentifiers();
                parse(')');
                dcl = name.fields(columnNames.toArray(EMPTY_NAME));
            }

            parseKeyword("AS");
            boolean materialized = parseKeywordIf("MATERIALIZED");
            boolean notMaterialized = !materialized && parseKeywordIf("NOT MATERIALIZED");
            parse('(');
            ResultQuery resultQuery = (ResultQuery) parseQuery(true, false);
            parse(')');

            cte.add(dcl != null
                ? materialized
                    ? dcl.asMaterialized(resultQuery)
                    : notMaterialized
                    ? dcl.asNotMaterialized(resultQuery)
                    : dcl.as(resultQuery)
                : materialized
                    ? name.asMaterialized(resultQuery)
                    : notMaterialized
                    ? name.asNotMaterialized(resultQuery)
                    : name.as(resultQuery)
            );
        }
        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, false);
        else if (!parseSelect && (peekKeyword("INSERT") || peekKeyword("INS")))
            result = parseInsert(with, false);
        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, false);
        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 Field parseScalarSubqueryIf() {
        int p = position();

        try {
            if (peekSelectOrWith(true)) {
                parse('(');
                SelectQueryImpl select = parseWithOrSelect();
                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;
        }

        return null;
    }

    private final SelectQueryImpl parseWithOrSelect() {
        return parseWithOrSelect(null);
    }

    private final SelectQueryImpl parseWithOrSelect(Integer degree) {
        return peekKeyword("WITH") ? (SelectQueryImpl) parseWith(true, degree) : parseSelect(degree, null);
    }

    private final SelectQueryImpl parseSelect() {
        return parseSelect(null, null);
    }

    private final SelectQueryImpl parseSelect(Integer degree, WithImpl with) {
        scope.scopeStart();
        SelectQueryImpl result = parseQueryExpressionBody(degree, with, null);
        List> orderBy = null;

        for (Field field : result.getSelect())
            if (aliased(field) != null)
                scope.scope(field);

        if (parseKeywordIf("ORDER")) {
            if (!ignoreProEdition() && parseKeywordIf("SIBLINGS BY") && requireProEdition()) {




            }
            else if (parseKeywordIf("BY"))
                result.addOrderBy(orderBy = parseList(',', c -> c.parseSortField()));
            else
                throw expected("SIBLINGS BY", "BY");
        }

        if (orderBy != null && parseKeywordIf("SEEK")) {
            boolean before = parseKeywordIf("BEFORE");
            if (!before)
                parseKeywordIf("AFTER");

            List> seek = parseList(',', c -> c.parseField());
            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 (!ignoreProEdition() && parseKeywordIf("XML") && requireProEdition()) {













































            }
            else if (!ignoreProEdition() && (jsonb = parseKeywordIf("JSONB") || parseKeywordIf("JSON")) && requireProEdition()) {





























            }
            else
                throw expected("UPDATE", "NO KEY UPDATE", "SHARE", "KEY SHARE", "XML", "JSON");

            if (parseKeywordIf("OF"))
                if (NO_SUPPORT_FOR_UPDATE_OF_FIELDS.contains(parseDialect()))
                    result.setForUpdateOf(parseList(',', t -> t.parseTable()).toArray(EMPTY_TABLE));
                else
                    result.setForUpdateOf(parseList(',', c -> c.parseField()));

            if (parseKeywordIf("NOWAIT"))
                result.setForUpdateNoWait();
            else if (!ignoreProEdition() && parseKeywordIf("WAIT") && requireProEdition())



                ;
            else if (parseKeywordIf("SKIP LOCKED"))
                result.setForUpdateSkipLocked();
        }

        scope.scopeEnd(result);
        return result;
    }

    private final void parseLimit(SelectQueryImpl result, boolean offset) {
        boolean offsetStandard = false;
        boolean offsetPostgres = false;

        if (offset && parseKeywordIf("OFFSET")) {
            result.addOffset((Field) parseField());

            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")) {
            Field limit = (Field) parseField();

            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, (Field) parseField());
            }
            else {
                if (parseKeywordIf("PERCENT"))
                    result.setLimitPercent(true);

                if (parseKeywordIf("WITH TIES"))
                    result.setWithTies(true);

                if (offset && parseKeywordIf("OFFSET"))
                    result.addLimit((Field) parseField(), 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((Field) parseField());

                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 = parseUnsignedIntegerLiteral();

            if (parseKeywordIf("TO")) {
                Long to = parseUnsignedIntegerLiteral();
                result.addLimit(to - from);
                result.addOffset(from - 1);
            }
            else {
                result.addLimit(from);
            }
        }
    }

    private final SelectQueryImpl parseQueryExpressionBody(Integer degree, WithImpl with, SelectQueryImpl prefix) {
        SelectQueryImpl lhs = parseQueryTerm(degree, with, prefix);
        SelectQueryImpl local = lhs;

        CombineOperator combine;
        while ((combine = parseCombineOperatorIf(false)) != null) {
            scope.scopeEnd(local);
            scope.scopeStart();

            if (degree == null)
                degree = Tools.degree(lhs);

            SelectQueryImpl rhs = local = degreeCheck(degree, parseQueryTerm(degree, null, null));
            switch (combine) {
                case UNION:
                    lhs = lhs.union(rhs);
                    break;
                case UNION_ALL:
                    lhs = lhs.unionAll(rhs);
                    break;
                case EXCEPT:
                    lhs = lhs.except(rhs);
                    break;
                case EXCEPT_ALL:
                    lhs = lhs.exceptAll(rhs);
                    break;
                default:
                    throw internalError();
            }
        }

        return lhs;
    }

    private final SelectQueryImpl parseQueryTerm(Integer degree, WithImpl with, SelectQueryImpl prefix) {
        SelectQueryImpl lhs = prefix != null ? prefix : parseQueryPrimary(degree, with);
        SelectQueryImpl local = lhs;

        CombineOperator combine;
        while ((combine = parseCombineOperatorIf(true)) != null) {
            scope.scopeEnd(local);
            scope.scopeStart();

            if (degree == null)
                degree = Tools.degree(lhs);

            SelectQueryImpl rhs = local = degreeCheck(degree, parseQueryPrimary(degree, null));
            switch (combine) {
                case INTERSECT:
                    lhs = lhs.intersect(rhs);
                    break;
                case INTERSECT_ALL:
                    lhs = lhs.intersectAll(rhs);
                    break;
                default:
                    throw internalError();
            }
        }

        return lhs;
    }

    private final SelectQueryImpl degreeCheck(int expected, SelectQueryImpl 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 parseQueryPrimary(Integer degree, WithImpl with) {
        if (parseIf('(')) {
            SelectQueryImpl result = parseSelect(degree, with);
            parse(')');
            return result;
        }

        if (peekKeyword("VALUES"))
            return (SelectQueryImpl) dsl.selectQuery(parseTableValueConstructor());
        else if (peekKeyword("TABLE"))
            return (SelectQueryImpl) dsl.selectQuery(parseExplicitTable());

        ignoreHints(false);
        parseKeyword("SELECT", "SEL");
        String hints = parseHints();
        boolean distinct = parseKeywordIf("DISTINCT") || parseKeywordIf("UNIQUE");
        List> distinctOn = null;

        if (distinct) {
            if (parseKeywordIf("ON")) {
                parse('(');
                distinctOn = parseList(',', c -> c.parseField());
                parse(')');
            }
        }
        else
            parseKeywordIf("ALL");

        Field limit = null;
        Field offset = null;
        boolean percent = false;
        boolean withTies = false;

        // T-SQL style TOP .. START AT
        if (parseKeywordIf("TOP")) {
            limit = (Field) parseField();
            percent = !ignoreProEdition() && parseKeywordIf("PERCENT") && requireProEdition();

            if (parseKeywordIf("START AT"))
                offset = (Field) parseField();
            else if (parseKeywordIf("WITH TIES"))
                withTies = true;
        }

        // Informix style SKIP .. FIRST
        else if (parseKeywordIf("SKIP")) {
            offset = (Field) parseField();

            if (parseKeywordIf("FIRST"))
                limit = (Field) parseField();
        }
        else if (parseKeywordIf("FIRST")) {
            limit = (Field) parseField();
        }

        List 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> from = null;

        if (parseKeywordIf("INTO")) {
            if (proEdition()) {



























            }
            else
                intoTable = parseTableName();
        }

        if (parseKeywordIf("FROM")) {
            from = parseList(',', ParseContext::parseTable);

            // [#16762] No explicit DUAL tables should be present at the top level, by default
            if (from.size() == 1)
                from.removeIf(t -> t instanceof Dual);
        }


        // [#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.scope(table);

        SelectQueryImpl 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 result) {
        if (parseKeywordIf("WHERE")) {
            result.addConditions(parseCondition());
            return true;
        }
        else
            return false;
    }

    private final boolean parseQueryPrimaryHaving(SelectQueryImpl result) {
        if (parseKeywordIf("HAVING")) {
            result.addHaving(parseCondition());
            return true;
        }
        else
            return false;
    }

    private final boolean parseQueryPrimaryGroupBy(SelectQueryImpl result) {
        List groupBy;

        if (parseKeywordIf("GROUP BY")) {
            if (!parseKeywordIf("ALL") && parseKeywordIf("DISTINCT"))
                result.setGroupByDistinct(true);

            if (parseIf('(', ')', true)) {
                parse(')');
                result.addGroupBy();
            }
            else if (parseKeywordIf("ROLLUP")) {
                parse('(');
                result.addGroupBy(rollup(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD)));
                parse(')');
            }
            else if (parseKeywordIf("CUBE")) {
                parse('(');
                result.addGroupBy(cube(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD)));
                parse(')');
            }
            else if (parseKeywordIf("GROUPING SETS")) {
                parse('(');
                List>> fieldSets = parseList(',', c -> parseFieldsOrEmptyOptionallyParenthesised(false));
                parse(')');
                result.addGroupBy(groupingSets(fieldSets.toArray((Collection[]) EMPTY_COLLECTION)));
            }
            else {
                groupBy = parseList(',', c -> c.parseField());

                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 result) {
        if (!ignoreProEdition() && parseKeywordIf("CONNECT BY") && requireProEdition()) {







            return true;
        }
        else
            return false;
    }

    private final boolean parseQueryPrimaryStartWith(SelectQueryImpl result) {
        if (!ignoreProEdition() && parseKeywordIf("START WITH") && requireProEdition()) {



            return true;
        }
        else
            return false;
    }

    private final List parseWindowDefinitions() {
        return parseList(',', c -> {
            Name name = parseIdentifier();
            parseKeyword("AS");
            parse('(');
            WindowDefinition result = name.as(parseWindowSpecificationIf(null, true));
            parse(')');
            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(parseList(',', c -> c.parseField()))
            : null;

        if (parseKeywordIf("ORDER BY"))
            if (orderByAllowed)
                s2 = s1 == null
                    ? orderBy(parseList(',', c -> c.parseSortField()))
                    : s1.orderBy(parseList(',', c -> c.parseSortField()));
            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 = parseUnsignedIntegerLiteralIf()) != 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 (asTrue(n = parseUnsignedIntegerLiteral()))
                    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 (asTrue(n = parseUnsignedIntegerLiteral()))
                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 Query parseDelete(WithImpl with, boolean parseResultQuery) {
        parseKeyword("DELETE", "DEL");
        Field limit = null;

        // T-SQL style TOP .. START AT
        if (parseKeywordIf("TOP")) {
            limit = (Field) parseField();

            // [#8623] TODO Support this
            // percent = parseKeywordIf("PERCENT") && requireProEdition();
        }

        parseKeywordIf("FROM");
        Table table = scope.scope(parseTable(() -> peekKeyword(KEYWORDS_IN_DELETE_FROM)));
        DeleteUsingStep s1 = with == null ? dsl.delete(table) : with.delete(table);
        DeleteWhereStep s2 = parseKeywordIf("USING", "FROM") ? s1.using(parseList(',', t -> scope.scope(parseTable(() -> peekKeyword(KEYWORDS_IN_DELETE_FROM))))) : s1;
        DeleteOrderByStep s3 = parseKeywordIf("ALL")
            ? s2
            : parseKeywordIf("WHERE")
            ? s2.where(parseCondition())
            : s2;
        DeleteLimitStep s4 = parseKeywordIf("ORDER BY") ? s3.orderBy(parseList(',', c -> c.parseSortField())) : s3;
        DeleteReturningStep s5 = (limit != null || parseKeywordIf("LIMIT"))
            ? s4.limit(limit != null ? limit : (Field) parseField())
            : s4;
        return (parseResultQuery ? parseKeyword("RETURNING") : parseKeywordIf("RETURNING"))
            ? s5.returning(parseSelectList())
            : s5;
    }

    private final Query parseInsert(WithImpl with, boolean parseResultQuery) {
        scope.scopeStart();
        parseKeyword("INSERT", "INS");
        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.scope(table);

        InsertSetStep s1 = (with == null ? dsl.insertInto(table) : with.insertInto(table));
        Field[] fields = null;

        if (!peekSelectOrWith(true) && parseIf('(') && !parseIf(')')) {
            fields = parseList(',', c -> parseField()).toArray(EMPTY_FIELD);
            parse(')');
        }

        InsertOnDuplicateStep onDuplicate;
        InsertReturningStep returning;

        try {
            // [#11821] The Teradata INSERT INTO t (1, 2) syntax can be recognised:
            //          When there are non-references fields
            boolean hasExpressions = anyMatch(fields, f -> !(f instanceof TableField));

            if (hasExpressions || parseKeywordIf("VALUES")) {
                List>> allValues = new ArrayList<>();

                if (hasExpressions) {
                    allValues.add(asList(fields));
                    fields = null;
                }

                valuesLoop:
                do {
                    if (hasExpressions && !parseIf(','))
                        break valuesLoop;

                    parse('(');

                    // [#6936] MySQL treats an empty VALUES() clause as the same thing as the standard DEFAULT VALUES
                    if (fields == null && parseIf(')'))
                        break valuesLoop;

                    List> values = parseList(',', c -> c.parseKeywordIf("DEFAULT") ? default_() : c.parseField());

                    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> values : allValues)
                    step2 = step2.values(values);

                returning = onDuplicate = step2;
            }
            else if (parseKeywordIf("SET")) {
                Map, Object> map = parseSetClauseList();

                returning = onDuplicate =  s1.set(map);
            }
            else if (peekSelectOrWith(true)) {
                Field[] f = fields;

                // [#13503] The SELECT in INSERT .. SELECT has its own, independent scope
                returning = onDuplicate = newScope(() -> {
                    Select select = parseWithOrSelect();

                    return (f == null)
                        ? s1.select(select)
                        : s1.columns(f).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");

                    // Cast is necessary, see https://github.com/eclipse-jdt/eclipse.jdt.core/issues/99
                    InsertOnConflictWhereStep where = parseKeywordIf("ALL TO EXCLUDED")
                        ? onDuplicate.onDuplicateKeyUpdate().setAllToExcluded()
                        : onDuplicate.onDuplicateKeyUpdate().set((Map) data(DATA_PARSE_ON_CONFLICT, true, c -> c.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(parseList(',', c -> parseFieldName()));
                        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")) {

                        // Cast is necessary, see https://github.com/eclipse-jdt/eclipse.jdt.core/issues/99
                        InsertOnConflictWhereStep where = parseKeywordIf("ALL TO EXCLUDED")
                            ? doUpdate.doUpdate().setAllToExcluded()
                            : doUpdate.doUpdate().set((Map) data(DATA_PARSE_ON_CONFLICT, true, c -> c.parseSetClauseList()));

                        if (parseKeywordIf("WHERE"))
                            returning = where.where(parseCondition());
                        else
                            returning = where;
                    }
                    else
                        throw expected("NOTHING", "UPDATE");
                }
                else
                    throw expected("CONFLICT", "DUPLICATE");
            }

            return (parseResultQuery ? parseKeyword("RETURNING") : parseKeywordIf("RETURNING"))
                ? returning.returning(parseSelectList())
                : returning;
        }
        finally {
            scope.scopeEnd(((InsertImpl) s1).getDelegate());
        }
    }

    private final Query parseUpdate(WithImpl with, boolean parseResultQuery) {
        parseKeyword("UPDATE", "UPD");
        Field limit = null;

        // T-SQL style TOP .. START AT
        if (parseKeywordIf("TOP")) {
            limit = (Field) parseField();

            // [#8623] TODO Support this
            // percent = parseKeywordIf("PERCENT") && requireProEdition();
        }

        Table table = scope.scope(parseTable(() -> peekKeyword(KEYWORDS_IN_UPDATE_FROM)));
        UpdateSetFirstStep s1 = (with == null ? dsl.update(table) : with.update(table));
        List> from = parseKeywordIf("FROM") ? parseList(',', t -> scope.scope(parseTable(() -> peekKeyword(KEYWORDS_IN_UPDATE_FROM)))) : 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, Object> map = parseSetClauseList();
            s2 = s1.set(map);
        }

        UpdateWhereStep s3 = from != null
            ? s2.from(from)
            : parseKeywordIf("FROM")
            ? s2.from(parseList(',', t -> parseTable(() -> peekKeyword(KEYWORDS_IN_UPDATE_FROM))))
            : s2;
        UpdateOrderByStep s4 = parseKeywordIf("ALL")
            ? s3
            : parseKeywordIf("WHERE")
            ? s3.where(parseCondition())
            : s3;
        UpdateLimitStep s5 = parseKeywordIf("ORDER BY") ? s4.orderBy(parseList(',', c -> c.parseSortField())) : s4;
        UpdateReturningStep s6 = (limit != null || parseKeywordIf("LIMIT"))
            ? s5.limit(limit != null ? limit : (Field) parseField())
            : s5;
        return (parseResultQuery ? parseKeyword("RETURNING") : parseKeywordIf("RETURNING"))
            ? s6.returning(parseSelectList())
            : s6;
    }

    private final Map, Object> parseSetClauseList() {
        Map, 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;
    }

    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 = parseCorrelationNameIf(table != null ? table : using, () -> peekKeyword("ON"));
        parseKeyword("ON");
        Condition on = parseCondition();
        boolean update = false;
        boolean insert = false;
        Field[] insertColumns = null;
        List> insertValues = null;
        Condition insertWhere = null;
        Map, Object> updateSet;
        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 = parseUniqueList("identifier", ',', c -> parseFieldName()).toArray(EMPTY_FIELD);
                parse(')');
                parseKeyword("VALUES");
                parse('(');
                insertValues = parseList(',', c -> c.parseKeywordIf("DEFAULT") ? default_() : c.parseField());
                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
        Merge s3 = insert
            ? insertWhere != null
                ? s2.whenNotMatchedThenInsert(insertColumns).values(insertValues).where(insertWhere)
                : s2.whenNotMatchedThenInsert(insertColumns).values(insertValues)
            : s2;

        return s3;
    }

    private final Query parseOpen() {
        parseKeyword("OPEN");
        parseKeyword("SCHEMA");

        return parseSetSchema();
    }

    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 = parseSignedIntegerLiteralIf();
            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.get();
        }
    }

    private final Query parseSetCatalog() {
        return dsl.setCatalog(parseCatalogName());
    }

    private final Query parseUse() {
        parseKeyword("USE");
        parseKeywordIf("DATABASE");
        return dsl.setCatalog(parseCatalogName());
    }

    private final Query parseSetSchema() {
        parseIf('=');
        return peek('\'') ? dsl.setSchema(parseStringLiteral()) : 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")) {
            Table table = parseTableName();

            if (parseIf('(')) {
                s1 = dsl.commentOnColumn(table.getQualifiedName().append(parseIdentifier()));
                parseKeyword("IS");
                DDLQuery s2 = s1.is(parseStringLiteral());
                parse(')');
                return s2;
            }
            else
                s1 = dsl.commentOnTable(table);
        }
        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.get();
        }

        // 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");
            parseName();
            parseKeyword("IS");
            parseStringLiteral();
            return IGNORE.get();
        }
        else if (parseAndGetKeywordIf(
            "POLICY",
            "RULE",
            "TRIGGER"
        ) != null) {
            parseIdentifier();
            parseKeyword("ON");
            parseIdentifier();
            parseKeyword("IS");
            parseStringLiteral();
            return IGNORE.get();
        }
        else if (parseKeywordIf("TRANSFORM FOR")) {
            parseIdentifier();
            parseKeyword("LANGUAGE");
            parseIdentifier();
            parseKeyword("IS");
            parseStringLiteral();
            return IGNORE.get();
        }
        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 (!ignoreProEdition() && 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 (!ignoreProEdition() && parseKeywordIf("TRIGGER") && requireProEdition())



                        ;
                    else if (parseKeywordIf("VIEW", "FORCE VIEW"))
                        return parseCreateView(true);
                    else if (!ignoreProEdition() && parseKeywordIf("FUNCTION") && requireProEdition())



                        ;
                    else if (parseKeywordIf("PACKAGE"))
                        throw notImplemented("CREATE PACKAGE", "https://github.com/jOOQ/jOOQ/issues/9190");
                    else if (!ignoreProEdition() && 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 (!ignoreProEdition() && 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 (!ignoreProEdition() && 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 parseCascadeRestrictIf(
                        parseIfExists(this::parseDomainName, dsl::dropDomainIfExists, dsl::dropDomain),
                        DropDomainCascadeStep::cascade,
                        DropDomainCascadeStep::restrict
                    );

                break;

            case 'E':
                if (parseKeywordIf("EXTENSION"))
                    return parseDropExtension();

                break;

            case 'F':
                if (!ignoreProEdition() && 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 (!ignoreProEdition() && 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 parseCascadeRestrictIf(
                        parseIfExists(this::parseSchemaName, dsl::dropSchemaIfExists, dsl::dropSchema),
                        DropSchemaStep::cascade,
                        DropSchemaStep::restrict
                    );

                break;

            case 'T':
                if (parseKeywordIf("TABLE"))
                    return parseDropTable(false);
                else if (parseKeywordIf("TEMPORARY TABLE"))
                    return parseDropTable(true);
                else if (!ignoreProEdition() && parseKeywordIf("TRIGGER") && requireProEdition())



                    ;
                else if (parseKeywordIf("TYPE"))
                    return parseCascadeRestrictIf(
                        parseIfExists(this::parseIdentifiers, dsl::dropTypeIfExists, dsl::dropType),
                        DropTypeStep::cascade,
                        DropTypeStep::restrict
                    );
                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");
        parseKeywordIf("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;

        return cascade
            ? step2.cascade()
            : restrict
            ? step2.restrict()
            : step2;
    }

    private final DDLQuery parseGrant() {
        parseKeyword("GRANT");
        Privilege privilege = parsePrivilege();
        List 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 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() {
        if (parseKeywordIf("EXEC 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 {
            if (!ignoreProEdition() && requireProEdition()) {



            }

            throw unsupportedClause();
        }
    }

    private final Block parseBlock(boolean allowDeclareSection) {
        LanguageContext previous = languageContext;

        try {
            if (languageContext == LanguageContext.QUERY)
                languageContext = LanguageContext.BLOCK;

            List statements = new ArrayList<>();





            if (allowDeclareSection && !ignoreProEdition() && parseKeywordIf("DECLARE") && requireProEdition())



                ;
            else
                parseKeywordIf("EXECUTE BLOCK AS");

            parseKeyword("BEGIN");
            parseKeywordIf("ATOMIC", "NOT ATOMIC");
            statements.addAll(parseStatementsAndPeek("END"));
            parseKeyword("END");



            parseIf(';');





            return dsl.begin(statements);
        }
        finally {
            languageContext = previous;
        }
    }

    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 parseStatements(boolean peek, String... keywords) {
        List statements = new ArrayList<>();

        for (;;) {
            if (peek && peekKeyword(keywords) || !peek && parseKeywordIf(keywords))
                break;

            Statement parsed;
            Statement stored;




            stored = parsed = parseStatement();

            if (parsed == null)
                break;






            statements.add(stored);
            parseSemicolonAfterNonBlocks(parsed);
        }

        return statements;
    }

    private final List parseStatementsAndPeek(String... keywords) {
        return parseStatements(true, keywords);
    }

    private final List parseStatementsAndKeyword(String... keywords) {
        return parseStatements(false, keywords);
    }



































































    private final Block parseDo() {
        parseKeyword("DO");
        return (Block) dsl.parser().parseQuery(parseStringLiteral());
    }

    private final Statement parseStatement() {
        switch (characterUpper()) {
            case 'C':
                if (!ignoreProEdition() && peekKeyword("CALL") && requireProEdition())



                ;
                else if (!ignoreProEdition() && peekKeyword("CONTINUE") && requireProEdition())



                ;

                break;

            case 'D':
                if (!ignoreProEdition() && peekKeyword("DECLARE") && requireProEdition())







                ;
                else if (!ignoreProEdition() && peekKeyword("DEFINE") && requireProEdition())



                ;

                break;

            case 'E':
                if (!ignoreProEdition() && peekKeyword("EXECUTE PROCEDURE", "EXEC") && requireProEdition())



                ;
                if (!ignoreProEdition() && peekKeyword("EXECUTE") && !peekKeyword("EXECUTE BLOCK") && requireProEdition())



                ;
                else if (!ignoreProEdition() && peekKeyword("EXIT") && requireProEdition())



                ;

                break;

            case 'F':
                if (!ignoreProEdition() && peekKeyword("FOR") && requireProEdition())



                ;

                break;

            case 'G':
                if (!ignoreProEdition() && peekKeyword("GOTO") && requireProEdition())



                ;

                break;

            case 'I':
                if (!ignoreProEdition() && peekKeyword("IF") && requireProEdition())



                ;
                else if (!ignoreProEdition() && peekKeyword("ITERATE") && requireProEdition())



                ;

                break;

            case 'L':
                if (!ignoreProEdition() && peekKeyword("LEAVE") && requireProEdition())



                ;
                else if (!ignoreProEdition() && peekKeyword("LET") && requireProEdition())



                ;
                else if (!ignoreProEdition() && peekKeyword("LOOP") && requireProEdition())



                ;

                break;

            case 'N':
                if (peekKeyword("NULL"))
                    return parseNullStatement();

                break;

            case 'R':
                if (!ignoreProEdition() && peekKeyword("REPEAT") && requireProEdition())



                ;
                else if (!ignoreProEdition() && peekKeyword("RETURN") && requireProEdition())



                ;
                else if (!ignoreProEdition() && peekKeyword("RAISE") && requireProEdition())



                ;

                break;

            case 'S':
                if (!ignoreProEdition() && peekKeyword("SET") && requireProEdition())



                ;
                else if (!ignoreProEdition() && peekKeyword("SIGNAL") && requireProEdition())



                ;

                break;

            case 'W':
                if (!ignoreProEdition() && 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 = parseList(',', c -> parseFieldName()).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.get();
    }

    private final DDLQuery parseDropExtension() {
        boolean ifExists = parseKeywordIf("IF EXISTS");
        parseIdentifiers();
        ifExists = ifExists || parseKeywordIf("IF EXISTS");
        if (!parseKeywordIf("CASCADE"))
            parseKeywordIf("RESTRICT");
        return IGNORE.get();
    }

    private final DDLQuery parseAlterView() {
        boolean ifExists = parseKeywordIf("IF EXISTS");
        Table oldName = parseTableName();

        if (parseKeywordIf("RENAME")) {
            parseKeyword("AS", "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.get();
        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() {
        return parseIfExists(this::parseTableName, dsl::dropViewIfExists, dsl::dropView);
    }

    private final DDLQuery parseCreateSequence() {
        boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
        Sequence schemaName = parseSequenceName();

        CreateSequenceFlagsStep s = ifNotExists
            ? dsl.createSequenceIfNotExists(schemaName)
            : dsl.createSequence(schemaName);

        boolean startWith = false;
        boolean incrementBy = false;
        boolean minvalue = false;
        boolean maxvalue = false;
        boolean cycle = false;
        boolean cache = false;

        for (;;) {
            Field 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")) {
            parseKeyword("AS", "TO");
            return s.renameTo(parseSequenceName());
        }
        else if (parseKeywordIf("OWNER TO") && parseUser() != null) {
            return IGNORE.get();
        }
        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 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 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 parseSequenceMaxvalueIf() {
        return parseKeywordIf("MAXVALUE") && (parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
    }

    private final boolean parseSequenceNoMinvalueIf() {
        return parseKeywordIf("NO MINVALUE") || parseKeywordIf("NOMINVALUE");
    }

    private final Field parseSequenceMinvalueIf() {
        return parseKeywordIf("MINVALUE") && (parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
    }

    private final Field parseSequenceIncrementByIf() {
        return parseKeywordIf("INCREMENT") && (parseKeywordIf("BY") || parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
    }

    private final Field 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(parseUnsignedIntegerLiteral());
    }

    private final DDLQuery parseDropSequence() {
        boolean ifExists = parseKeywordIf("IF EXISTS");
        Sequence sequenceName = parseSequenceName();
        ifExists = ifExists || parseKeywordIf("IF EXISTS");
        parseKeywordIf("RESTRICT");

        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();

        CreateTableOnCommitStep onCommitStep;
        CreateTableCommentStep commentStep;

        List> fields = new ArrayList<>();
        List constraints = new ArrayList<>();
        List indexes = new ArrayList<>();
        boolean primary = false;
        boolean identity = false;
        boolean readonly = false;
        boolean ctas = false;

        if (!peekSelectOrWith(true) && 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] [#12712]
                    // Look ahead if the next tokens indicate a MySQL index definition
                    if (parseIf('(') || (
                            parseDataTypeIf(false) == null
                         && 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();
                boolean skipType = peek(',') || peek(')');

                // If only we had multiple return values or destructuring...
                ParseInlineConstraints inlineConstraints = parseInlineConstraints(
                    fieldName,
                    !skipType ? parseDataType() : SQLDataType.OTHER,
                    constraints,
                    primary,
                    identity,
                    readonly
                );

                primary = inlineConstraints.primary;
                identity = inlineConstraints.identity;

                fields.add(field(fieldName, inlineConstraints.type, inlineConstraints.fieldComment));
            }
            while (parseIf(','));

            if (fields.isEmpty())
                throw expected("At least one column");

            parse(')');
        }
        else
            ctas = true;

        CreateTableElementListStep elementListStep = ifNotExists
            ? temporary
                ? dsl.createTemporaryTableIfNotExists(tableName)
                : dsl.createTableIfNotExists(tableName)
            : temporary
                ? dsl.createTemporaryTable(tableName)
                : dsl.createTable(tableName);

        if (!fields.isEmpty())
            elementListStep = elementListStep.columns(fields);

        CreateTableElementListStep constraintStep = constraints.isEmpty()
            ? elementListStep
            : elementListStep.constraints(constraints);
        CreateTableAsStep asStep = indexes.isEmpty()
            ? constraintStep
            : constraintStep.indexes(indexes);

        // [#6133] Historically, the jOOQ API places the ON COMMIT clause after
        // the AS clause, which doesn't correspond to dialect implementations
        Function onCommit;
        if (temporary && parseKeywordIf("ON COMMIT")) {
            if (parseKeywordIf("DELETE ROWS"))
                onCommit = CreateTableOnCommitStep::onCommitDeleteRows;
            else if (parseKeywordIf("DROP"))
                onCommit = CreateTableOnCommitStep::onCommitDrop;
            else if (parseKeywordIf("PRESERVE ROWS"))
                onCommit = CreateTableOnCommitStep::onCommitPreserveRows;
            else
                throw unsupportedClause();
        }
        else
            onCommit = s -> s;

        // [#12888] To avoid ambiguities with T-SQL's support for statement batches
        //          without statement separators, let's accept MySQL's optional AS
        //          keyword only for empty field lists
        if (parseKeywordIf("AS") || fields.isEmpty() && peekSelectOrWith(true)) {
            boolean previousMetaLookupsForceIgnore = metaLookupsForceIgnore();
            CreateTableWithDataStep withDataStep = asStep.as((Select) metaLookupsForceIgnore(false).parseQuery(true, true));
            metaLookupsForceIgnore(previousMetaLookupsForceIgnore);
            onCommitStep =
                  parseKeywordIf("WITH DATA")
                ? withDataStep.withData()
                : parseKeywordIf("WITH NO DATA")
                ? withDataStep.withNoData()
                : withDataStep;
        }
        else if (ctas) {
            throw expected("AS, WITH, SELECT, or (");
        }
        else {
            onCommitStep = asStep;

            // [#14631] [#14690] SQLite optional keywords
            if (parseKeywordIf("STRICT", "WITHOUT ROWID") && parseIf(','))
                parseKeyword("STRICT", "WITHOUT ROWID");
        }

        commentStep = onCommit.apply(onCommitStep);

        List 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")) {
                if (!parseIf('='))
                    parseKeywordIf("IS");
                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 ','");
        }

        CreateTableStorageStep storageStep = comment != null
            ? commentStep.comment(comment)
            : commentStep;

        if (storage.size() > 0)
            return storageStep.storage(new SQLConcatenationImpl(storage.toArray(EMPTY_QUERYPART)));
        else
            return storageStep;
    }

    private static final record ParseInlineConstraints(DataType type, Comment fieldComment, boolean primary, boolean identity, boolean readonly) {}

    private final ParseInlineConstraints parseInlineConstraints(
        Name fieldName,
        DataType type,
        List constraints,
        boolean primary,
        boolean identity,
        boolean readonly
    ) {
        boolean nullable = false;
        boolean defaultValue = false;
        boolean computed = false;
        boolean onUpdate = false;
        boolean unique = false;
        Constraint uniqueConstraint = null;
        boolean comment = false;
        boolean compress = false;
        Comment fieldComment = null;

        identity |= type.identity();
        readonly |= type.readonly();

        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('(')) {
                        parseSignedIntegerLiteral();
                        parse(',');
                        parseSignedIntegerLiteral();
                        parse(')');
                    }

                    type = type.identity(true);
                    defaultValue = true;
                    identity = true;
                    continue;
                }
                else if (!ignoreProEdition() && parseKeywordIf("READONLY") && requireProEdition()) {




                }
                else if (parseKeywordIf("DEFAULT")) {

                    // [#10963] Special case nextval('_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()));

                        // TODO: [#10115] Support this clause also in the jOOQ API
                        parseKeywordIf("WITH VALUES");

                        defaultValue = true;
                    }

                    continue;
                }
                else if (!computed
                        && !ignoreProEdition()
                        && (parseKeywordIf("AS")
                            || parseKeywordIf("COMPUTED") && (parseKeywordIf("BY") || true))
                        && requireProEdition()) {






                }
                else if ((!identity || !computed) && parseKeywordIf("GENERATED")) {
                    boolean always;
                    if (!(always = parseKeywordIf("ALWAYS"))) {
                        parseKeyword("BY DEFAULT");

                        // TODO: Ignored keyword from Oracle
                        parseKeywordIf("ON NULL");
                    }

                    if (always ? parseKeywordIf("AS IDENTITY") : parseKeyword("AS IDENTITY")) {
                        parseIdentityOptionIf();
                        type = type.identity(true);
                        identity = true;
                    }
                    else if (!ignoreProEdition() && parseKeyword("AS") && requireProEdition()) {





                    }

                    defaultValue = true;
                    continue;
                }
            }

            if (!onUpdate) {
                if (parseKeywordIf("ON UPDATE")) {

                    // [#6132] TODO: Support this feature in the jOOQ DDL API
                    parseConcat();
                    onUpdate = true;
                    continue;
                }
            }

            ConstraintTypeStep inlineConstraint = parseConstraintNameSpecification();

            if (!primary && parsePrimaryKeyClusteredNonClusteredKeywordIf()) {
                constraints.add(parseConstraintEnforcementIf(inlineConstraint == null
                    ? primaryKey(fieldName)
                    : inlineConstraint.primaryKey(fieldName)));

                // [#13880] Remove all lexically preceding inline UNIQUE KEYs as
                //          soon as a PRIMARY KEY is encountered
                if (uniqueConstraint != null)
                    constraints.remove(uniqueConstraint);

                primary = true;
                unique = true;
                continue;
            }
            else if (parseKeywordIf("UNIQUE")) {
                if (!parseKeywordIf("KEY"))
                    parseKeywordIf("INDEX");

                if (!unique)
                    constraints.add(uniqueConstraint = parseConstraintEnforcementIf(inlineConstraint == null
                        ? unique(fieldName)
                        : inlineConstraint.unique(fieldName)));

                unique = true;
                continue;
            }

            if (parseKeywordIf("CHECK")) {
                constraints.add(parseCheckSpecification(inlineConstraint));
                continue;
            }

            if (parseKeywordIf("FOREIGN KEY REFERENCES", "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")) {
                    if (!parseIf('='))
                        parseKeywordIf("IS");
                    fieldComment = parseComment();
                    comment = true;
                    continue;
                }
                else if (peekKeyword("OPTIONS")) {
                    fieldComment = parseOptionsDescription();
                    comment = true;
                    continue;
                }
            }


            if (!compress) {
                if (!ignoreProEdition() && parseKeywordIf("NO COMPRESS") && requireProEdition()) {




                }
                else if (!ignoreProEdition() && parseKeywordIf("COMPRESS") && requireProEdition()) {











                }
            }

            break;
        }

        return new ParseInlineConstraints(type, fieldComment, primary, identity, readonly);
    }

















    private final void parseIdentityOptionIf() {

        // 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;
                }
                else if (parseSignedIntegerLiteralIf() != null) {
                    identityOption = true;
                    continue;
                }

                if (identityOption)
                    break;
                else
                    throw unsupportedClause();
            }

            parse(')');
        }
    }

    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");

        if (!parseKeywordIf("ASC"))
            parseKeywordIf("DESC");

        return true;
    }

    private final DDLQuery parseCreateType() {
        Name name = parseName();
        parseKeyword("AS ENUM");
        List values;
        parse('(');

        if (!parseIf(')')) {
            values = parseList(',', ParseContext::parseStringLiteral);
            parse(')');
        }
        else
            values = new ArrayList<>();

        return dsl.createType(name).asEnum(values);
    }

    private final Index parseIndexSpecification(Table table) {
        Name name = parseIdentifierIf();
        parseUsingIndexTypeIf();
        return Internal.createIndex(name == null ? NO_NAME : name, table, parseParenthesisedSortSpecification(), false);
    }

    private final boolean parseConstraintConflictClauseIf() {
        return parseKeywordIf("ON CONFLICT") && parseKeyword("ROLLBACK", "ABORT", "FAIL", "IGNORE", "REPLACE");
    }

    private final Constraint parseConstraintEnforcementIf(ConstraintEnforcementStep e) {
        boolean onConflict = false;
        boolean deferrable = false;
        boolean initially = false;

        while ((!onConflict && (onConflict = parseConstraintConflictClauseIf()))
            || (!deferrable && (deferrable = parseConstraintDeferrableIf()))
            || (!initially && (initially = parseConstraintInitiallyIf())))
            ;

        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() {
        SortField[] fieldExpressions = parseParenthesisedSortSpecification();
        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;
        if ((constraintName = parseIdentifierIf()) != null)
            if (constraint == null)
                constraint = constraint(constraintName);

        parse('(');
        Field[] referencing = parseList(',', c -> parseFieldName()).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 = parseList(',', c -> parseFieldName()).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 final  S parseIfExists(
        Supplier part,
        Function stepIfExists,
        Function step
    ) {
        boolean ifExists = parseKeywordIf("IF EXISTS");
        O q = part.get();
        ifExists = ifExists || parseKeywordIf("IF EXISTS");

        return ifExists ? stepIfExists.apply(q) : step.apply(q);
    }

    private final  S2 parseCascadeRestrictIf(
        S1 step,
        Function stepCascade,
        Function stepRestrict
    ) {
        boolean cascade = parseKeywordIf("CASCADE");
        boolean restrict = !cascade && parseKeywordIf("RESTRICT");

        return cascade
            ? stepCascade.apply(step)
            : restrict
            ? stepRestrict.apply(step)
            : step;
    }

    private static final Set 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")) {
                    if (!parseIf('='))
                        parseKeywordIf("IS");
                    return dsl.commentOnTable(tableName).is(parseStringLiteral());
                }

                break;

            case 'D':
                if (parseKeywordIf("DROP")) {
                    if (parseKeywordIf("CONSTRAINT")) {
                        return parseCascadeRestrictIf(
                            parseIfExists(this::parseIdentifier, s1::dropConstraintIfExists, s1::dropConstraint),
                            AlterTableDropStep::cascade,
                            AlterTableDropStep::restrict
                        );
                    }
                    else if (parseKeywordIf("UNIQUE")) {
                        return parseCascadeRestrictIf(
                            s1.dropUnique(
                                  peek('(')
                                ? unique(parseKeyColumnList())
                                : constraint(parseIdentifier())
                            ),
                            AlterTableDropStep::cascade,
                            AlterTableDropStep::restrict
                        );
                    }
                    else if (parseKeywordIf("PRIMARY KEY")) {
                        Name identifier = parseIdentifierIf();
                        return parseCascadeRestrictIf(
                            identifier == null ? s1.dropPrimaryKey() : s1.dropPrimaryKey(identifier),
                            AlterTableDropStep::cascade,
                            AlterTableDropStep::restrict
                        );
                    }
                    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> fields = null;

                        if (!ifColumnExists) {
                            while (parseIf(',')
                                && (parseKeywordIf("DROP") || true) && (parseKeywordIf("COLUMN") || true)
                                || 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),
                            AlterTableDropStep::cascade,
                            AlterTableDropStep::restrict
                        );
                    }
                }

                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.get();

                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();
                        parseKeyword("AS", "TO");
                        Name newName = parseIdentifier();

                        return s1.renameColumn(oldName).to(newName);
                    }
                    else if (parseKeywordIf("INDEX")) {
                        Name oldName = parseIdentifier();
                        parseKeyword("AS", "TO");
                        Name newName = parseIdentifier();

                        return s1.renameIndex(oldName).to(newName);
                    }
                    else if (parseKeywordIf("CONSTRAINT")) {
                        Name oldName = parseIdentifier();
                        parseKeyword("AS", "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 DDLQuery parseAlterTableAdd(AlterTableStep s1, Table tableName) {
        List list = new ArrayList<>();

        if (parseIndexOrKeyIf()) {
            Name name = parseIdentifierIf();

            return name == null
                ? dsl.createIndex().on(tableName, parseParenthesisedSortSpecification())
                : dsl.createIndex(name).on(tableName, parseParenthesisedSortSpecification());
        }

        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 c)
                return s1.add(c);
            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 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 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 = parseDataTypeIf(true);
        if (type == null)
            type = SQLDataType.OTHER;

        int p = list == null ? -1 : list.size();

        ParseInlineConstraints inline = parseInlineConstraints(fieldName, type, list, false, 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()));
            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)
            && (parseConstraintConflictClauseIf() || 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.get();
        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();
                    parseKeyword("AS", "TO");
                    return dsl.alterTable(oldName.getTable()).renameColumn(oldName).to(parseFieldName());
                }

                break;

            case 'D':
                if (parseKeywordIf("DATABASE")) {
                    Catalog oldName = parseCatalogName();
                    parseKeyword("AS", "TO");
                    return dsl.alterDatabase(oldName).renameTo(parseCatalogName());
                }

                break;

            case 'I':
                if (parseKeywordIf("INDEX")) {
                    Name oldName = parseIndexName();
                    parseKeyword("AS", "TO");
                    return dsl.alterIndex(oldName).renameTo(parseIndexName());
                }

                break;

            case 'S':
                if (parseKeywordIf("SCHEMA")) {
                    Schema oldName = parseSchemaName();
                    parseKeyword("AS", "TO");
                    return dsl.alterSchema(oldName).renameTo(parseSchemaName());
                }
                else if (parseKeywordIf("SEQUENCE")) {
                    Sequence oldName = parseSequenceName();
                    parseKeyword("AS", "TO");
                    return dsl.alterSequence(oldName).renameTo(parseSequenceName());
                }

                break;

            case 'V':
                if (parseKeywordIf("VIEW")) {
                    Table oldName = parseTableName();
                    parseKeyword("AS", "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();
        parseKeyword("AS", "TO");
        return dsl.alterTable(oldName).renameTo(parseTableName());
    }

    private final DDLQuery parseDropTable(boolean temporary) {
        boolean ifExists = parseKeywordIf("IF EXISTS");
        Table tableName = parseTableName();
        ifExists = ifExists || parseKeywordIf("IF EXISTS");
        boolean cascade = parseKeywordIf("CASCADE") && (parseKeywordIf("CONSTRAINTS") || true);
        boolean restrict = !cascade && parseKeywordIf("RESTRICT");

        DropTableStep s1;

        s1 = ifExists
           ? dsl.dropTableIfExists(tableName)
           : temporary
           ? dsl.dropTemporaryTable(tableName)
           : dsl.dropTable(tableName);

        return cascade
           ? s1.cascade()
           : restrict
           ? s1.restrict()
           : s1;
    }






























































































































































































































































































































































    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 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);
                parseKeyword("AS", "TO");
                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.get();
        }
        else
            throw expected("ADD", "DROP", "RENAME", "SET", "OWNER TO");
    }

    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")) {
            parseKeyword("AS", "TO");
            return s1.renameTo(parseCatalogName());
        }
        else if (parseKeywordIf("OWNER TO") && parseUser() != null)
            return IGNORE.get();
        else if (parseAlterDatabaseFlags(true))
            return IGNORE.get();
        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() {
        return parseIfExists(this::parseCatalogName, dsl::dropDatabaseIfExists, dsl::dropDatabase);
    }

    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")) {
            parseKeyword("AS", "TO");
            return s1.renameTo(parseSchemaName());
        }
        else if (parseKeywordIf("OWNER TO") && parseUser() != null)
            return IGNORE.get();
        else if (parseAlterDatabaseFlags(false))
            return IGNORE.get();
        else
            throw expected("OWNER TO", "RENAME TO");
    }

    private final DDLQuery parseCreateIndex(boolean unique) {
        boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
        Name indexName = parseIndexNameIf();
        parseUsingIndexTypeIf();
        SortField[] fields = null;
        if (peek('('))
            fields = parseParenthesisedSortSpecification();
        parseKeyword("ON");
        Table tableName = parseTableName();
        parseUsingIndexTypeIf();
        if (fields == null)
            fields = parseParenthesisedSortSpecification();
        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;

        return condition != null
            ? s3.where(condition)
            : excludeNullKeys
            ? s3.excludeNullKeys()
            : s3;
    }

    private SortField[] parseParenthesisedSortSpecification() {
        parse('(');
        SortField[] fields = parseList(',', c -> c.parseSortField()).toArray(EMPTY_SORTFIELD);
        parse(')');

        return fields;
    }

    private final boolean parseUsingIndexTypeIf() {
        if (parseKeywordIf("USING"))
            parseIdentifier();

        return true;
    }

    private final DDLQuery parseAlterIndex() {
        boolean ifExists = parseKeywordIf("IF EXISTS");
        Name indexName = parseIndexName();
        parseKeyword("RENAME");
        parseKeyword("AS", "TO");
        Name newName = parseIndexName();

        AlterIndexStep s1 = ifExists
            ? dsl.alterIndexIfExists(indexName)
            : dsl.alterIndex(indexName);
        return s1.renameTo(newName);
    }

    private final DDLQuery parseDropIndex() {
        boolean ifExists = parseKeywordIf("IF EXISTS");
        Name indexName = parseIndexName();
        ifExists = ifExists || parseKeywordIf("IF EXISTS");
        boolean on = parseKeywordIf("ON");
        Table onTable = on ? parseTableName() : null;

        DropIndexOnStep s1;
        DropIndexCascadeStep s2;

        s1 = ifExists
            ? dsl.dropIndexIfExists(indexName)
            : dsl.dropIndex(indexName);

        s2 = on
            ? s1.on(onTable)
            : s1;

        return parseKeywordIf("CASCADE")
            ? s2.cascade()
            : parseKeywordIf("RESTRICT")
            ? s2.restrict()
            : s2;
    }

    // -----------------------------------------------------------------------------------------------------------------
    // QueryPart parsing
    // -----------------------------------------------------------------------------------------------------------------

    @Override
    public 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 (!forbidden.contains(FK_AND) && parseKeywordIf("AND"))
            condition = toCondition(condition).and(toCondition(parseNot()));

        return condition;
    }

    private final QueryPart parseNot() {
        int not = parseNot0();
        QueryPart condition = parsePredicate();

        for (int i = 0; i < not; i++)
            condition = toCondition(condition).not();

        return condition;
    }

    private final int parseNot0() {
        int not = 0;

        while (parseKeywordIf("NOT"))
            not++;

        return not;
    }

    private final QueryPart parsePredicate() {
        Condition condition;







        switch (characterUpper()) {
            case 'D':






                break;

            case 'E':
                if (parseKeywordIf("EXISTS"))
                    return exists(parseParenthesised(c -> parseWithOrSelect()));

                break;

            case 'I':






                break;

            case 'J':
                if ((condition = parsePredicateJSONExistsIf()) != null)
                    return condition;

                break;

            case 'R':
                if (parseKeywordIf("REGEXP_LIKE"))
                    return parseFunctionArgs2(Field::likeRegex);

                break;

            case 'S':
                if (!ignoreProEdition() && parseFunctionNameIf("ST_CONTAINS", "SDO_CONTAINS") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_CROSSES") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_DISJOINT") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_EQUALS", "SDO_EQUAL") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_INTERSECTS") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_ISCLOSED") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_ISEMPTY") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_OVERLAPS", "SDO_OVERLAPS") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_TOUCHES", "SDO_TOUCH") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_WITHIN", "SDO_INSIDE") && requireProEdition()) {



                }

                break;

            case 'U':
                if (parseKeywordIf("UNIQUE"))
                    // javac can't infer this (?)
                    return unique(this.>parseParenthesised(c -> parseWithOrSelect()));







                break;

            case 'X':
                if ((condition = parsePredicateXMLExistsIf()) != null)
                    return condition;

                break;
        }

        FieldOrRow left;
        Comparator comp;
        TSQLOuterJoinComparator outer;
        boolean not;
        boolean notOp = false;

        left = parseConcat();
        int p = position();
        not = parseKeywordIf("NOT");
        boolean isField = left instanceof Field;


        if (!not && !ignoreProEdition() && ((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
                ? isField
                    ? peekSelectOrWith(true)
                        ? ((Field) left).compare(comp, DSL.all(parseWithOrSelect(1)))
                        : ((Field) left).compare(comp, DSL.all(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD)))

                    // TODO: Support quantifiers also for rows
                    : new RowSubqueryCondition((Row) left, DSL.all(parseWithOrSelect(((Row) left).size())), comp)

                : any
                ? isField
                    ? peekSelectOrWith(true)
                        ? ((Field) left).compare(comp, DSL.any(parseWithOrSelect(1)))
                        : ((Field) left).compare(comp, DSL.any(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD)))

                    // TODO: Support quantifiers also for rows
                    : new RowSubqueryCondition((Row) left, DSL.any(parseWithOrSelect(((Row) left).size())), comp)

                : isField
                    ? ((Field) left).compare(comp, toField(parseConcat()))
                    : 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
                    ? isField
                        ? ((Field) left).isNotNull()
                        : ((Row) left).isNotNull()
                    : isField
                        ? ((Field) left).isNull()
                        : ((Row) left).isNull();
            else if (isField && parseKeywordIf("JSON"))
                return not
                    ? ((Field) left).isNotJson()
                    : ((Field) left).isJson();
            else if (isField && parseKeywordIf("DOCUMENT"))
                return not
                    ? ((Field) left).isNotDocument()
                    : ((Field) left).isDocument();

            not = parseKeywordIf("DISTINCT FROM") == not;
            if (left instanceof Field f) {
                Field right = toField(parseConcat());
                return not ? f.isNotDistinctFrom(right) : f.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()));
        }
        else if (!forbidden.contains(FK_IN) && parseKeywordIf("IN")) {
            Condition result;

            // [#12691] Some dialects support A IN B syntax without parentheses for single element in lists
            if (isField && !peek('(')) {
                result = not
                    ? ((Field) left).notIn(parseConcat())
                    : ((Field) left).in(parseConcat());
            }
            else {
                parse('(');

                if (peek(')'))
                    result = not
                        ? isField
                            ? ((Field) left).notIn(EMPTY_FIELD)
                            : new RowInCondition((Row) left, new QueryPartList<>(), true)
                        : isField
                            ? ((Field) left).in(EMPTY_FIELD)
                            : new RowInCondition((Row) left, new QueryPartList<>(), false);
                else if (peekSelectOrWith(true))
                    result = not
                        ? isField
                            ? ((Field) left).notIn(parseWithOrSelect(1))
                            : new RowSubqueryCondition((Row) left, parseWithOrSelect(((Row) left).size()), NOT_IN)
                        : isField
                            ? ((Field) left).in(parseWithOrSelect(1))
                            : new RowSubqueryCondition((Row) left, parseWithOrSelect(((Row) left).size()), IN);
                else
                    result = not
                        ? isField
                            ? ((Field) left).notIn(parseList(',', c -> c.parseField()))
                            : new RowInCondition((Row) left, new QueryPartList<>(parseList(',', c -> parseRow(((Row) left).size()))), true)
                        : isField
                            ? ((Field) left).in(parseList(',', c -> c.parseField()))
                            : new RowInCondition((Row) left, new QueryPartList<>(parseList(',', c -> parseRow(((Row) left).size()))), false);

                parse(')');
            }

            return result;
        }
        else if (parseKeywordIf("BETWEEN")) {
            boolean symmetric = !parseKeywordIf("ASYMMETRIC") && parseKeywordIf("SYMMETRIC");
            FieldOrRow r1 = isField
                ? parseConcat()
                : parseRow(((Row) left).size());
            parseKeyword("AND");
            FieldOrRow r2 = isField
                ? parseConcat()
                : parseRow(((Row) left).size());

            return symmetric
                ? not
                    ? isField
                        ? ((Field) left).notBetweenSymmetric((Field) r1, (Field) r2)
                        : new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2)
                    : isField
                        ? ((Field) left).betweenSymmetric((Field) r1, (Field) r2)
                        : new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2)
                : not
                    ? isField
                        ? ((Field) left).notBetween((Field) r1, (Field) r2)
                        : new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2)
                    : isField
                        ? ((Field) left).between((Field) r1, (Field) r2)
                        : new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2);
        }
        else if (isField && (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> fields;
                    if (parseIf(')')) {
                        fields = emptyList();
                    }
                    else {
                        fields = parseList(',', c -> toField(parseConcat()));
                        parse(')');
                    }
                    Field[] fieldArray = fields.toArray(new Field[0]);
                    LikeEscapeStep result = (not ^ notOp) ? ((Field) left).notLike(any(fieldArray)) : ((Field) 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> fields;
                    if (parseIf(')')) {
                        fields = emptyList();
                    }
                    else {
                        fields = parseList(',', c -> toField(parseConcat()));
                        parse(')');
                    }
                    Field[] fieldArray = fields.toArray(new Field[0]);
                    LikeEscapeStep result = (not ^ notOp) ? ((Field) left).notLike(all(fieldArray)) : ((Field) left).like(all(fieldArray));
                    return parseEscapeClauseIf(result);
                }
            }
            else {
                Field right = toField(parseConcat());
                LikeEscapeStep like = (not ^ notOp) ? ((Field) left).notLike(right) : ((Field) left).like(right);
                return parseEscapeClauseIf(like);
            }
        }
        else if (isField && (parseKeywordIf("ILIKE") || parseOperatorIf("~~*") || (notOp = parseOperatorIf("!~~*")))) {
            Field right = toField(parseConcat());
            LikeEscapeStep like = (not ^ notOp) ? ((Field) left).notLikeIgnoreCase(right) : ((Field) left).likeIgnoreCase(right);
            return parseEscapeClauseIf(like);
        }
        else if (isField && (parseKeywordIf("REGEXP")
                                        || parseKeywordIf("RLIKE")
                                        || parseKeywordIf("LIKE_REGEX")
                                        || parseOperatorIf("~")
                                        || (notOp = parseOperatorIf("!~")))) {
            Field right = toField(parseConcat());
            return (not ^ notOp)
                    ? ((Field) left).notLikeRegex(right)
                    : ((Field) left).likeRegex(right);
        }
        else if (isField && parseKeywordIf("SIMILAR TO")) {
            Field right = toField(parseConcat());
            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 {
            position(p);
            return left;
        }
    }

    private final Condition parsePredicateXMLExistsIf() {
        if (parseKeywordIf("XMLEXISTS")) {
            parse('(');
            Field xpath = (Field) parseField();
            XMLPassingMechanism m = parseXMLPassingMechanism();
            Field xml = (Field) 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);
        }

        return null;
    }

    private final Condition parsePredicateJSONExistsIf() {
        if (parseKeywordIf("JSON_EXISTS")) {
            parse('(');
            Field json = parseField();
            parse(',');
            Field path = (Field) parseField();
            JSONExists.Behaviour b = parseJSONExistsOnErrorBehaviourIf();
            parse(')');












            return jsonExists(json, path);
        }

        return null;
    }

    private final QueryPart parseEscapeClauseIf(LikeEscapeStep like) {
        return parseKeywordIf("ESCAPE") ? like.escape(parseCharacterLiteral()) : like;
    }

    @Override
    public final Table parseTable() {
        return parseTable(() -> peekKeyword(KEYWORDS_IN_SELECT_FROM) || (!delimiterRequired && peekKeyword(KEYWORDS_IN_STATEMENTS)));
    }

    private final Table parseTable(BooleanSupplier forbiddenKeywords) {
        Table result = parseLateral(forbiddenKeywords);

        for (;;) {
            Table joined = parseJoinedTableIf(result, forbiddenKeywords);

            if (joined == null)
                return result;
            else
                result = joined;
        }
    }

    private final Table parseLateral(BooleanSupplier forbiddenKeywords) {
        if (parseKeywordIf("LATERAL"))
            return lateral(parseTableFactor(forbiddenKeywords));
        else
            return parseTableFactor(forbiddenKeywords);
    }

    private final  Table t(TableLike table) {
        return t(table, false);
    }

    private final  Table t(TableLike table, boolean dummyAlias) {
        return
            table instanceof Table t
          ? t
          : dummyAlias
          ? table.asTable("x")
          : table.asTable();
    }

    private final Table parseTableFactor(BooleanSupplier forbiddenKeywords) {

        // [#7982] Postpone turning Select into a Table in case there is an alias
        TableLike result;






        // TODO ONLY ( table primary )
        if (parseFunctionNameIf("OLD TABLE")) {
            parse('(');
            Query query = parseQuery(false, false);
            parse(')');

            if (query instanceof Merge q)
                result = oldTable(q);
            else if (query instanceof Update q)
                result = oldTable(q);
            else if (query instanceof Delete q)
                result = oldTable(q);
            else
                throw expected("UPDATE", "DELETE", "MERGE");
        }
        else if (parseFunctionNameIf("NEW TABLE")) {
            parse('(');
            Query query = parseQuery(false, false);
            parse(')');

            if (query instanceof Merge q)
                result = newTable(q);
            else if (query instanceof Insert q)
                result = newTable(q);
            else if (query instanceof Update q)
                result = newTable(q);
            else
                throw expected("INSERT", "UPDATE", "MERGE");
        }
        else if (parseFunctionNameIf("FINAL TABLE")) {
            parse('(');
            Query query = parseQuery(false, false);
            parse(')');

            if (query instanceof Merge q)
                result = finalTable(q);
            else if (query instanceof Insert q)
                result = finalTable(q);
            else if (query instanceof Update q)
                result = finalTable(q);
            else
                throw expected("INSERT", "UPDATE", "MERGE");
        }
        else if (parseFunctionNameIf("UNNEST", "TABLE")) {
            parse('(');

            if (parseFunctionNameIf("GENERATOR")) {
                parse('(');
                Field tl = parseFunctionArgumentIf("TIMELIMIT");
                Field rc = parseFunctionArgumentIf("ROWCOUNT");

                if (tl == null)
                    tl = parseFunctionArgumentIf("TIMELIMIT");

                parse(')');
                result = generateSeries(one(), (Field) rc);
            }
            else {
                Field f = parseField();

                // Work around a missing feature in unnest()
                if (!f.getType().isArray())
                    f = f.coerce(f.getDataType().getArrayType());

                result = unnest(f);
            }

            parse(')');
        }
        else if (parseFunctionNameIf("GENERATE_SERIES", "SYSTEM_RANGE")) {
            parse('(');
            Field from = toField(parseConcat());
            parse(',');
            Field to = toField(parseConcat());

            Field step = parseIf(',')
                ? toField(parseConcat())
                : 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());
            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 (peekFunctionNameIf("VALUES")) {
            result = parseTableValueConstructor();
        }
        else if (parseFunctionNameIf("XMLTABLE")) {
            parse('(');

            XMLTablePassingStep s1 = xmltable((Field) toField(parseConcat()));
            XMLPassingMechanism m = parseXMLPassingMechanismIf();
            Field passing = m == null ? null : (Field) 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", "SEL", "WITH")) {
                SelectQueryImpl select = parseWithOrSelect();
                parse(')');
                result = parseQueryExpressionBody(null, null, select);
            }
            else if (peekKeyword("VALUES")) {
                result = parseTableValueConstructor();
                parse(')');
            }
            else {
                result = parseJoinedTable(forbiddenKeywords);
                parse(')');
            }
        }
        else {
            result = parseTableName();

            // TODO Sample clause
        }

        if (!ignoreProEdition() && parseKeywordIf("VERSIONS BETWEEN") && requireProEdition()) {






























        }
        else if (!ignoreProEdition()
            && 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 (!ignoreProEdition() && parseKeywordIf("AS OF") && requireProEdition()) {










        }

        if (!ignoreProEdition() && parseKeywordIf("PIVOT") && requireProEdition()) {















































        }

        // TODO UNPIVOT
        result = parseCorrelationNameIf(result, forbiddenKeywords);

        int p = position();
        if (parseKeywordIf("WITH")) {
            if (!ignoreProEdition() && 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 Field parseFunctionArgumentIf(String parameterName) {
        if (parseKeywordIf(parameterName) && parse("=>"))
            return parseField();
        else
            return null;
    }

    private final TableLike parseCorrelationNameIf(TableLike result, BooleanSupplier forbiddenKeywords) {
        Name alias = null;
        List columnAliases = null;

        if (parseKeywordIf("AS"))
            alias = parseIdentifier();
        else if (!forbiddenKeywords.getAsBoolean())
            alias = parseIdentifierIf();

        if (alias != null) {
            if (parseIf('(')) {
                columnAliases = parseIdentifiers();
                parse(')');
            }

            if (columnAliases != null)
                result = t(result, true).as(alias, columnAliases);
            else
                result = t(result, true).as(alias);
        }

        return result;
    }

































































    private final Row parseTableValueConstructorRow(Integer degree) {
        if (parseKeywordIf("ROW"))
            return parseTuple(degree);

        Field r = null;

        if (degree == null || degree == 1)
            r = parseScalarSubqueryIf();

        if (r != null)
            return row(r);
        else if (peek('('))
            return parseTuple(degree);
        else if (degree == null || degree == 1)
            return row(parseField());
        else
            throw exception("Expected row of degree: " + degree);
    }

    private final Table parseTableValueConstructor() {
        parseKeyword("VALUES");

        List rows = new ArrayList<>();
        Integer degree = null;
        do {
            Row row = parseTableValueConstructorRow(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 fieldsOrRows;

        if (allowDoubleParens)
            fieldsOrRows = parseList(',', c -> parseFieldOrRow());
        else
            fieldsOrRows = parseList(',', c -> c.parseField());

        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(BooleanSupplier forbiddenKeywords) {
        Table result = parseLateral(forbiddenKeywords);

        for (;;) {
            Table joined = parseJoinedTableIf(result, forbiddenKeywords);

            if (joined == null)
                return result;
            else
                result = joined;
        }
    }

    private final Table parseJoinedTableIf(Table left, BooleanSupplier forbiddenKeywords) {
        JoinType joinType = parseJoinTypeIf();

        if (joinType == null)
            return null;

        Table right = joinType.qualified() ? parseTable(forbiddenKeywords) : parseLateral(forbiddenKeywords);

        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 (!ignoreProEdition() && 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 parseSelectList() {
        List result = new ArrayList<>();

        do {
            QualifiedAsterisk qa;

            if (parseIf('*')) {
                if (parseKeywordIf("EXCEPT")) {
                    parse('(');
                    result.add(DSL.asterisk().except(parseList(',', c -> parseFieldName()).toArray(EMPTY_FIELD)));
                    parse(')');
                }
                else
                    result.add(DSL.asterisk());
            }
            else if ((qa = parseQualifiedAsteriskIf()) != null) {
                if (parseKeywordIf("EXCEPT")) {
                    parse('(');
                    result.add(qa.except(parseList(',', c -> parseFieldName()).toArray(EMPTY_FIELD)));
                    parse(')');
                }
                else
                    result.add(qa);
            }
            else {
                Name alias = null;
                SelectField field = null;














                if (field == null) {
                    field = parseSelectField();

                    if (parseKeywordIf("AS"))
                        alias = parseIdentifier(true, false);
                    else if (!peekKeyword(KEYWORDS_IN_SELECT) && (delimiterRequired || !peekKeyword(KEYWORDS_IN_STATEMENTS)))
                        alias = parseIdentifierIf(true, false);
                }

                result.add(alias == null ? field : field.as(alias));
            }
        }
        while (parseIf(','));

        return result;
    }

    @Override
    public 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> parseFieldsOrEmptyParenthesised() {
        parse('(');

        if (parseIf(')')) {
            return emptyList();
        }
        else {
            List> result = parseList(',', c -> c.parseField());
            parse(')');
            return result;
        }
    }

    private final List> parseFieldsOrEmptyOptionallyParenthesised(boolean allowUnparenthesisedLists) {
        if (peek('('))
            return parseFieldsOrEmptyParenthesised();




        else if (allowUnparenthesisedLists)
            return parseList(',', c -> c.parseField());
        else
            return asList(parseField());
    }

    private final SelectField parseSelectField() {
        return (SelectField) parseFieldOrRow();
    }

    private final Row parseRow() {
        return parseRow(null);
    }

    private final Row parseRowIf() {
        return parseRowIf(null);
    }

    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);
    }

    public final FieldOrRow parseFieldOrRow() {
        return toFieldOrRow(parseOr());
    }

    @Override
    public final Field parseField() {
        return toField(parseOr());
    }

    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 c)
            return c;
        else if (part instanceof Field f) {
            DataType dataType = f.getDataType();
            Class type = dataType.getType();

            if (type == Boolean.class)
                return condition(f);

            // [#11631] [#12394] Numeric expressions are booleans in MySQL
            else if (dataType.isNumeric())
                return f.ne(zero());

            // [#7266] Support parsing column references as predicates
            else if (type == Object.class && (part instanceof TableFieldImpl || part instanceof Val))
                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 f)
            return f;
        else if (part instanceof Row r)
            return r;
        else
            throw expected("Field or row");
    }

    private final Field toField(QueryPart part) {
        if (part == null)
            return null;
        else if (part instanceof Field f)
            return f;
        else
            throw expected("Field");
    }

    private final FieldOrRow parseConcat() {
        FieldOrRow r = parseCollated();

        if (r instanceof Field)
            while (parseIf("||"))
                r = concat((Field) r, toField(parseCollated()));

        return r;
    }

    private final FieldOrRow parseCollated() {
        FieldOrRow r = parseNumericOp();

        if (r instanceof Field) {
            if (parseKeywordIf("COLLATE"))
                r = ((Field) r).collate(parseCollation());























        }

        return r;
    }

    private final Field parseFieldNumericOpParenthesised() {
        parse('(');
        Field r = toField(parseNumericOp());
        parse(')');

        return r;
    }

    private final Field parseFieldParenthesised() {
        parse('(');
        Field r = parseField();
        parse(')');

        return r;
    }

    private final  Q parseFunctionArgs1(Function1 finisher) {
        parse('(');
        Field f1 = parseField();
        parse(')');

        return finisher.apply(f1);
    }

    private final  Q parseFunctionArgs2(
        Function1 finisher1,
        Function2 finisher2
    ) {
        parse('(');
        Field f1 = parseField();
        Field f2 = parseIf(',') ? parseField() : null;
        parse(')');

        return f2 == null ? finisher1.apply(f1) : finisher2.apply(f1, f2);
    }

    private final  Q parseFunctionArgs2(Function2 finisher) {
        return parseFunctionArgs2(this::parseField, finisher);
    }

    private final  Q parseFunctionArgs2(
        Supplier> argument,
        Function2 finisher
    ) {
        parse('(');
        Field f1 = argument.get();
        parse(',');
        Field f2 = argument.get();
        parse(')');

        return finisher.apply(f1, f2);
    }

    private final  Q parseFunctionArgs3(
        Function2 finisher2,
        Function3 finisher3
    ) {
        parse('(');
        Field f1 = parseField();
        parse(',');
        Field f2 = parseField();
        Field f3 = parseIf(',') ? parseField() : null;
        parse(')');

        return f3 == null ? finisher2.apply(f1, f2) : finisher3.apply(f1, f2, f3);
    }

    private final  Q parseFunctionArgs3(Function3 finisher) {
        parse('(');
        Field f1 = parseField();
        parse(',');
        Field f2 = parseField();
        parse(',');
        Field f3 = parseField();
        parse(')');

        return finisher.apply(f1, f2, f3);
    }

    private final  Q parseFunctionArgs4(Function4 finisher) {
        parse('(');
        Field f1 = parseField();
        parse(',');
        Field f2 = parseField();
        parse(',');
        Field f3 = parseField();
        parse(',');
        Field f4 = parseField();
        parse(')');

        return finisher.apply(f1, f2, f3, f4);
    }

    private final boolean parseEmptyParens() {
        return parse('(') && parse(')');
    }

    private final boolean parseEmptyParensIf() {
        return parseIf('(') && parse(')') || true;
    }

    // Any numeric operator of low precedence
    // See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-PRECEDENCE
    private final FieldOrRow parseNumericOp() {
        FieldOrRow r = parseSum();

        if (r instanceof Field)
            for (;;)
                if (parseIf("<<"))
                    r = ((Field) r).shl((Field) parseSum());
                else if (parseIf(">>"))
                    r = ((Field) r).shr((Field) parseSum());
                else
                    break;

        return r;
    }

    private final FieldOrRow parseSum() {
        FieldOrRow r = parseFactor();

        if (r instanceof Field)
            for (;;)
                if (parseIf('+'))
                    r = parseSumRightOperand(r, true);
                else if (parseIf('-'))
                    r = parseSumRightOperand(r, false);
                else
                    break;

        return r;
    }

    private final Field parseSumRightOperand(FieldOrRow r, boolean add) {
        Field rhs = (Field) parseFactor();
        DatePart part;

        if (!ignoreProEdition() && (parseKeywordIf("YEAR") || parseKeywordIf("YEARS")) && requireProEdition())
            part = DatePart.YEAR;
        else if (!ignoreProEdition() && (parseKeywordIf("MONTH") || parseKeywordIf("MONTHS")) && requireProEdition())
            part = DatePart.MONTH;
        else if (!ignoreProEdition() && (parseKeywordIf("DAY") || parseKeywordIf("DAYS")) && requireProEdition())
            part = DatePart.DAY;
        else if (!ignoreProEdition() && (parseKeywordIf("HOUR") || parseKeywordIf("HOURS")) && requireProEdition())
            part = DatePart.HOUR;
        else if (!ignoreProEdition() && (parseKeywordIf("MINUTE") || parseKeywordIf("MINUTES")) && requireProEdition())
            part = DatePart.MINUTE;
        else if (!ignoreProEdition() && (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() {
        FieldOrRow r = parseExp();

        if (r instanceof Field)
            for (;;)
                if (!peek("*=") && parseIf('*'))
                    r = ((Field) r).mul((Field) parseExp());
                else if (parseIf('/'))
                    r = ((Field) r).div((Field) parseExp());
                else if (parseIf('%'))
                    r = ((Field) r).mod((Field) parseExp());






                else
                    break;

        return r;
    }

    private final FieldOrRow parseExp() {
        FieldOrRow r = parseUnaryOps();

        if (r instanceof Field)
            for (;;)
                if (!peek("^=") && parseIf('^') || parseIf("**"))
                    r = ((Field) r).pow(toField(parseUnaryOps()));
                else
                    break;

        return r;
    }

    private final FieldOrRow parseUnaryOps() {
        if (!ignoreProEdition() && parseKeywordIf("CONNECT_BY_ROOT") && requireProEdition()) {



        }

        if (parseIf('~'))
            return toField(parseUnaryOps()).bitNot();

        FieldOrRow r;
        Sign sign = parseSign();

        if (sign == Sign.NONE)
            r = parseTerm();
        else if (sign == Sign.PLUS)
            r = toField(parseTerm());
        else if ((r = parseFieldUnsignedNumericLiteralIf(Sign.MINUS)) == null)
            r = toField(parseTerm()).neg();

        if (!ignoreProEdition() && 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
        int p = position();
        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());
            parse(']');
        }

        r = parseMethodCallIf(r);
        return r;
    }

    private final FieldOrRow parseMethodCallIf(FieldOrRow r) {








        return r;
    }

    private final FieldOrRow parseMethodCallIf0(FieldOrRow r) {



















































































































        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 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() {
        FieldOrRow field;
        Object value;



















        switch (characterUpper()) {

            // [#8821] Known prefixes so far:
            case ':':
            case '@':
            case '?':
                if ((field = parseBindVariableIf()) != null)
                    return field;

                break;









            case '\'':
                return inline(parseStringLiteral());

            case '$':
                if ((field = parseBindVariableIf()) != null)
                    return field;
                else if ((value = parseDollarQuotedStringLiteralIf()) != null)
                    return inline((String) value);

                break;

            case 'A':
                if (parseFunctionNameIf("ABS"))
                    return abs((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("ASC", "ASCII", "ASCII_VAL"))
                    return ascii((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("ACOS"))
                    return acos((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("ACOSH"))
                    return acosh((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("ACOTH"))
                    return acoth((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("ASIN"))
                    return asin((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("ASINH"))
                    return asinh((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("ATAN", "ATN"))
                    return atan((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("ATANH"))
                    return atanh((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("ATN2", "ATAN2"))
                    return parseFunctionArgs2(() -> toField(parseNumericOp()), DSL::atan2);

                else if (parseFunctionNameIf("ASCII_CHAR"))
                    return chr((Field) parseFieldParenthesised());

                else if ((field = parseArrayValueConstructorIf()) != null)
                    return field;

                else if (parseFunctionNameIf("ADD_YEARS"))
                    return parseFieldAddDatePart(YEAR);
                else if (parseFunctionNameIf("ADD_MONTHS"))
                    return parseFieldAddDatePart(MONTH);
                else if (parseFunctionNameIf("ADD_DAYS"))
                    return parseFieldAddDatePart(DAY);
                else if (parseFunctionNameIf("ADD_HOURS"))
                    return parseFieldAddDatePart(HOUR);
                else if (parseFunctionNameIf("ADD_MINUTES"))
                    return parseFieldAddDatePart(MINUTE);
                else if (parseFunctionNameIf("ADD_SECONDS"))
                    return parseFieldAddDatePart(SECOND);
                else if (parseFunctionNameIf("ARRAY_GET"))
                    return parseFunctionArgs2((f1, f2) -> arrayGet(f1, f2));

                break;

            case 'B':
                if (parseFunctionNameIf("BIT_LENGTH"))
                    return bitLength((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("BITCOUNT", "BIT_COUNT"))
                    return bitCount((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("BYTE_LENGTH"))
                    return octetLength((Field) parseFieldParenthesised());
                else if ((field = parseFieldBitwiseFunctionIf()) != null)
                    return field;

                else if ((value = parseBitLiteralIf()) != null)
                    return DSL.inline((Boolean) value);

                break;

            case 'C':
                if ((field = parseFieldConcatIf()) != null)
                    return field;
                else if ((parseFunctionNameIf("CURRENT_CATALOG") && parseEmptyParens()))
                    return currentCatalog();
                else if ((parseFunctionNameIf("CURRENT_DATABASE") && parseEmptyParens()))
                    return currentCatalog();
                else if ((parseKeywordIf("CURRENT_SCHEMA", "CURRENT SCHEMA")) && parseEmptyParensIf())
                    return currentSchema();
                else if ((parseKeywordIf("CURRENT_USER", "CURRENT USER", "CURRENTUSER")) && parseEmptyParensIf())
                    return currentUser();
                else if (parseFunctionNameIf("CHR", "CHAR"))
                    return chr((Field) parseFieldParenthesised());

                else if (parseFunctionNameIf("CHARINDEX"))
                    return parseFunctionArgs3((f1, f2) -> DSL.position(f2, f1), (f1, f2, f3) -> DSL.position(f2, f1, f3));
                else if (parseFunctionNameIf("CHAR_LENGTH"))
                    return charLength((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("CARDINALITY"))
                    return cardinality((Field) parseFieldParenthesised());
                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());

                else if ((parseKeywordIf("CURRENT_DATE") || parseKeywordIf("CURRENT DATE")) && parseEmptyParensIf())
                    return currentDate();
                else if (parseKeywordIf("CURRENT_TIMESTAMP") || parseKeywordIf("CURRENT TIMESTAMP")) {
                    Field precision = null;
                    if (parseIf('('))
                        if (!parseIf(')')) {
                            precision = (Field) parseField();
                            parse(')');
                        }
                    return precision != null ? currentTimestamp(precision) : currentTimestamp();
                }
                else if ((parseKeywordIf("CURRENT_TIME") || parseKeywordIf("CURRENT TIME")) && parseEmptyParensIf())
                    return currentTime();
                else if (parseFunctionNameIf("CURDATE") && parseEmptyParens())
                    return currentDate();
                else if (parseFunctionNameIf("CURTIME") && parseEmptyParens())
                    return currentTime();

                else 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 (!ignoreProEdition() && parseKeywordIf("CONNECT_BY_ISCYCLE") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseKeywordIf("CONNECT_BY_ISLEAF") && requireProEdition()) {



                }

                break;

            case 'D':
                if ((parseFunctionNameIf("DATABASE") && parseEmptyParens()))
                    return currentCatalog();
                else if ((parseFunctionNameIf("DB_NAME") && parseEmptyParens()))
                    return currentCatalog();
                else if ((parseFunctionNameIf("DBINFO") && parse('(') && parseStringLiteral("dbname") != null && parse(')')))
                    return currentCatalog();
                else if (parseFunctionNameIf("DIGITS"))
                    return digits((Field) parseFieldParenthesised());

                else 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;

                else if ((field = parseFieldDenseRankIf()) != null)
                    return field;
                else if (parseFunctionNameIf("DECADE"))
                    return decade(parseFieldParenthesised());
                else if (parseFunctionNameIf("DAY")
                      || parseFunctionNameIf("DAYOFMONTH"))
                    return day(parseFieldParenthesised());
                // 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());
                else if (parseFunctionNameIf("DAYOFWEEK")
                      || parseFunctionNameIf("DAY_OF_WEEK"))
                    return dayOfWeek(parseFieldParenthesised());
                else if (parseFunctionNameIf("DAYOFYEAR")
                      || parseFunctionNameIf("DAY_OF_YEAR"))
                    return dayOfYear(parseFieldParenthesised());
                else if (parseFunctionNameIf("DEGREES")
                      || parseFunctionNameIf("DEGREE")
                      || parseFunctionNameIf("DEG"))
                    return deg((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("DATALENGTH"))
                    return octetLength((Field) parseFieldParenthesised());

                else if ((field = parseFieldDecodeIf()) != null)
                    return field;
                else if (parseKeywordIf("DEFAULT"))
                    return default_();

                break;

            case 'E':

                // [#6704] PostgreSQL E'...' escaped string literals
                if (characterNext() == '\'')
                    return inline(parseStringLiteral());

                else if ((field = parseFieldExtractIf()) != null)
                    return field;
                else if (parseFunctionNameIf("EXP"))
                    return exp((Field) parseFieldNumericOpParenthesised());

                else if (parseFunctionNameIf("EPOCH"))
                    return epoch(parseFieldParenthesised());

                break;

            case 'F':
                if (parseFunctionNameIf("FLOOR"))
                    return floor((Field) parseFieldNumericOpParenthesised());

                else if ((field = parseFieldFirstValueIf()) != null)
                    return field;
                else if ((field = parseFieldFieldIf()) != null)
                    return field;

                break;

            case 'G':
                if (parseKeywordIf("GETDATE") && parseEmptyParens())
                    return currentTimestamp();

                else if (parseFunctionNameIf("GENGUID", "GENERATE_UUID", "GEN_RANDOM_UUID") && parseEmptyParens())
                    return uuid();

                else if ((field = parseFieldGreatestIf()) != null)
                    return field;
                else if (!ignoreProEdition() && parseFunctionNameIf("GROUP_ID") && requireProEdition() && parseEmptyParens()) {



                }
                else if ((field = parseFieldGroupingIdIf()) != null)
                    return field;
                else if (parseFunctionNameIf("GROUPING"))
                    return grouping(parseFieldParenthesised());
                else if (!ignoreProEdition() && (parseFunctionNameIf("GEOMETRY::STGEOMFROMWKB") || parseFunctionNameIf("GEOGRAPHY::STGEOMFROMWKB")) && requireProEdition()) {



                }
                else if (!ignoreProEdition() && (parseFunctionNameIf("GEOMETRY::STGEOMFROMTEXT") || parseFunctionNameIf("GEOGRAPHY::STGEOMFROMTEXT")) && requireProEdition()) {



                }
                else
                    break;

            case 'H':
                if (parseFunctionNameIf("HOUR"))
                    return hour(parseFieldParenthesised());

                else if (parseFunctionNameIf("HASH_MD5"))
                    return md5((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("HEX"))
                    return toHex((Field) parseFieldParenthesised());

                break;

            case 'I':
                if ((field = parseFieldIntervalLiteralIf()) != null)
                    return field;
                else if (parseFunctionNameIf("ISO_DAY_OF_WEEK"))
                    return isoDayOfWeek(parseFieldParenthesised());
                else if (parseFunctionNameIf("INSTR"))
                    return parseFunctionArgs3(DSL::position, DSL::position);
                else if (parseFunctionNameIf("INSERT"))
                    return parseFunctionArgs4(DSL::insert);
                else if (parseFunctionNameIf("IFNULL"))
                    return parseFunctionArgs2((f1, f2) -> ifnull((Field) f1, (Field) f2));
                else if (parseFunctionNameIf("ISNULL"))
                    return parseFunctionArgs2(f -> f.isNull(), (f1, f2) -> isnull((Field) f1, (Field) f2));
                else if ((field = parseFieldIfIf()) != null)
                    return field;
                else
                    break;

            case 'J':
                if ((field = parseFieldJSONArrayConstructorIf()) != null)
                    return field;
                else if ((field = parseFieldJSONObjectConstructorIf()) != null)
                    return field;
                else if ((field = parseFieldJSONValueIf()) != null)
                    return field;
                else if ((field = parseFieldJSONLiteralIf()) != null)
                    return field;

                break;

            case 'L':
                if (parseFunctionNameIf("LOWER", "LCASE"))
                    return lower((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("LPAD"))
                    return parseFunctionArgs3(DSL::lpad, DSL::lpad);
                else if (parseFunctionNameIf("LTRIM"))
                    return parseFunctionArgs2(DSL::ltrim, DSL::ltrim);
                else if (parseFunctionNameIf("LEFT"))
                    return parseFunctionArgs2(DSL::left);
                else if (parseFunctionNameIf("LENGTH", "LEN"))
                    return length((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("LENGTHB"))
                    return octetLength((Field) parseFieldParenthesised());
                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 (!ignoreProEdition() && parseKeywordIf("LEVEL") && requireProEdition()) {



                }
                else if (parseKeywordIf("LSHIFT"))
                    return parseFunctionArgs2(() -> toField(parseNumericOp()), (f1, f2) -> shl(f1, f2));
                else if ((field = parseFieldLeastIf()) != null)
                    return field;
                else if ((field = parseFieldLeadLagIf()) != null)
                    return field;
                else if ((field = parseFieldLastValueIf()) != null)
                    return field;

                break;

            case 'M':
                if (parseFunctionNameIf("MOD"))
                    return parseFunctionArgs2(Field::mod);
                else if (parseFunctionNameIf("MICROSECOND"))
                    return microsecond(parseFieldParenthesised());
                else if (parseFunctionNameIf("MILLENNIUM"))
                    return millennium(parseFieldParenthesised());
                else if (parseFunctionNameIf("MILLISECOND"))
                    return millisecond(parseFieldParenthesised());
                else if (parseFunctionNameIf("MINUTE"))
                    return minute(parseFieldParenthesised());
                else if (parseFunctionNameIf("MONTH"))
                    return month(parseFieldParenthesised());
                else if (parseFunctionNameIf("MID"))
                    return parseFunctionArgs3(DSL::mid);
                else if (parseFunctionNameIf("MD5"))
                    return md5((Field) parseFieldParenthesised());

                else if ((field = parseMultisetValueConstructorIf()) != null)
                    return field;

                else if ((field = parseFieldGreatestIf()) != null)
                    return field;
                else if ((field = parseFieldLeastIf()) != null)
                    return field;
                else if ((field = parseFieldDecodeIf()) != null)
                    return field;

                break;

            case 'N':

                // [#9540] N'...' NVARCHAR literals
                if (characterNext() == '\'')
                    return inline(parseStringLiteral(), NVARCHAR);
                else if ((field = parseFieldNewIdIf()) != null)
                    return field;
                else if (parseFunctionNameIf("NVL2"))
                    return parseFunctionArgs3((f1, f2, f3) -> nvl2((Field) f1, (Field) f2, (Field) f3));
                else if (parseFunctionNameIf("NVL"))
                    return parseFunctionArgs2((f1, f2) -> nvl((Field) f1, (Field) f2));
                else if (parseFunctionNameIf("NULLIF"))
                    return parseFunctionArgs2((f1, f2) -> nullif((Field) f1, (Field) f2));
                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 precision = (Field) parseField();
                    parse(')');
                    return now(precision);
                }

                break;

            case 'O':
                if (parseFunctionNameIf("OREPLACE"))
                    return parseFunctionArgs3(DSL::replace, DSL::replace);
                else if ((field = parseFieldOverlayIf()) != null)
                    return field;
                else if ((field = parseFieldTranslateIf()) != null)
                    return field;

                else if (parseFunctionNameIf("OCTET_LENGTH"))
                    return octetLength((Field) parseFieldParenthesised());

                break;

            case 'P':
                if ((field = parseFieldPositionIf()) != null)
                    return field;
                else if ((field = parseFieldPercentRankIf()) != null)
                    return field;
                else if (parseFunctionNameIf("POWER", "POW"))
                    return parseFunctionArgs2(() -> toField(parseNumericOp()), DSL::power);
                else if (parseFunctionNameIf("PI") && parseEmptyParens())
                    return pi();

                else if (!ignoreProEdition() && parseKeywordIf("PRIOR") && requireProEdition()) {



                }

                break;

            case 'Q':
                if (characterNext() == '\'')
                    return inline(parseStringLiteral());

                else if (parseFunctionNameIf("QUARTER"))
                    return quarter(parseFieldParenthesised());

            case 'R':
                if (parseFunctionNameIf("REPLACE"))
                    return parseFunctionArgs3(DSL::replace, DSL::replace);
                else if ((field = parseFieldRegexpReplaceIf()) != null)
                    return field;
                else if (parseFunctionNameIf("REPEAT", "REPLICATE"))
                    return parseFunctionArgs2(DSL::repeat);
                else if (parseFunctionNameIf("REVERSE"))
                    return reverse((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("RPAD"))
                    return parseFunctionArgs3(DSL::rpad, DSL::rpad);
                else if (parseFunctionNameIf("RTRIM"))
                    return parseFunctionArgs2(DSL::rtrim, DSL::rtrim);
                else if (parseFunctionNameIf("RIGHT"))
                    return parseFunctionArgs2(DSL::right);
                else if (parseFunctionNameIf("RANDOM_UUID") && parseEmptyParens())
                    return uuid();

                else if (parseFunctionNameIf("ROW_NUMBER", "ROWNUMBER") && parseEmptyParens())
                    return parseWindowFunction(null, null, rowNumber());
                else if ((field = parseFieldRankIf()) != null)
                    return field;
                else if ((field = parseFieldRoundIf()) != null)
                    return field;
                else if (!ignoreProEdition() && parseKeywordIf("ROWNUM") && requireProEdition()) {



                }
                else if (parseFunctionNameIf("RADIANS")
                      || parseFunctionNameIf("RADIAN")
                      || parseFunctionNameIf("RAD"))
                    return rad((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("RAND", "RANDOM") && parseEmptyParens())
                    return rand();

                else if (parseFunctionNameIf("RATIO_TO_REPORT"))
                    return parseFunctionArgs1(f -> parseWindowFunction(null, null, ratioToReport(f)));
                else if (parseKeywordIf("RSHIFT"))
                    return parseFunctionArgs2(() -> toField(parseNumericOp()), (f1, f2) -> shr(f1, f2));
                else if (parseFunctionNameIf("ROW"))
                    return parseTuple();

                break;

            case 'S':
                if ((field = parseFieldSubstringIf()) != null)
                    return field;
                else if (parseFunctionNameIf("SUBSTRING_INDEX"))
                    return parseFunctionArgs3(DSL::substringIndex);
                else if (parseFunctionNameIf("SPACE"))
                    return space((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("SPLIT_PART"))
                    return parseFunctionArgs3(DSL::splitPart);
                else if (parseFunctionNameIf("STR_REPLACE"))
                    return parseFunctionArgs3(DSL::replace, DSL::replace);
                else if (parseFunctionNameIf("SCHEMA") && parseEmptyParensIf())
                    return currentSchema();
                else if (parseFunctionNameIf("STRREVERSE"))
                    return reverse((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("SYSUUID") && parseEmptyParensIf())
                    return uuid();

                else if (parseFunctionNameIf("SECOND"))
                    return second(parseFieldParenthesised());
                else if (!ignoreProEdition() && parseFunctionNameIf("SEQ4", "SEQ8") && parseEmptyParens() && requireProEdition()) {



                }
                else if (parseFunctionNameIf("SIGN", "SGN"))
                    return sign((Field) parseFieldParenthesised());
                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 (parseKeywordIf("SHL", "SHIFTLEFT"))
                    return parseFunctionArgs2(() -> toField(parseNumericOp()), (f1, f2) -> shl(f1, f2));
                else if (parseKeywordIf("SHR", "SHIFTRIGHT"))
                    return parseFunctionArgs2(() -> toField(parseNumericOp()), (f1, f2) -> shr(f1, f2));
                else if ((field = parseFieldSysConnectByPathIf()) != null)
                    return field;
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_AREA") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("SDO_GEOM.SDO_AREA") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_ASBINARY") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_ASTEXT") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_CENTROID") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("SDO_GEOM.SDO_CENTROID") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_DIFFERENCE") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("SDO_GEOM.SDO_DIFFERENCE") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_DISTANCE") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("SDO_GEOM.SDO_DISTANCE") && requireProEdition()) {



                }
                else if (parseProFunctionNameIf("ST_ENDPOINT", "SDO_LRS.GEOM_SEGMENT_END_PT")) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_EXTERIORRING") && requireProEdition()) {



                }
                else if (parseProFunctionNameIf("ST_GEOMETRYN", "SDO_UTIL.EXTRACT")) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_GEOMETRYTYPE") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_GEOMFROMWKB") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_GEOMFROMTEXT", "SDO_GEOMETRY") && requireProEdition()) {







                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_INTERIORRINGN") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_INTERSECTION") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("SDO_GEOM.SDO_INTERSECTION") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_LENGTH") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("SDO_GEOM.SDO_LENGTH") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_NUMINTERIORRING", "ST_NUMINTERIORRINGS") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_NUMGEOMETRIES", "SDO_UTIL.GETNUMELEM") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_NUMPOINTS") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_POINTN") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_SRID") && requireProEdition()) {



                }
                else if (parseProFunctionNameIf("ST_STARTPOINT", "SDO_LRS.GEOM_SEGMENT_START_PT")) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_UNION") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("SDO_GEOM.SDO_UNION") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_X") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_Y") && requireProEdition()) {



                }
                else if (!ignoreProEdition() && parseFunctionNameIf("ST_Z") && requireProEdition()) {



                }

                break;

            case 'T':
                if ((field = parseBooleanValueExpressionIf()) != null)
                    return field;

                else if ((field = parseFieldTrimIf()) != null)
                    return field;
                else if ((field = parseFieldTranslateIf()) != null)
                    return field;
                else if (parseFunctionNameIf("TO_CHAR"))
                    return parseFunctionArgs2(DSL::toChar, DSL::toChar);
                else if (parseFunctionNameIf("TO_HEX"))
                    return toHex((Field) parseFieldParenthesised());

                else if (parseFunctionNameIf("TANH"))
                    return tanh((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("TAN"))
                    return tan((Field) parseFieldNumericOpParenthesised());
                else if (parseFunctionNameIf("TO_NUMBER"))
                    return parseFunctionArgs1(f -> cast(f, NUMERIC));
                else if (parseFunctionNameIf("TIMEZONE_HOUR"))
                    return timezoneHour(parseFieldParenthesised());
                else if (parseFunctionNameIf("TIMEZONE_MINUTE"))
                    return timezoneMinute(parseFieldParenthesised());
                else if (parseFunctionNameIf("TIMEZONE"))
                    return timezone(parseFieldParenthesised());

                else if ((field = parseFieldTimestampLiteralIf()) != null)
                    return field;
                else if ((field = parseFieldTimeLiteralIf()) != null)
                    return field;
                else if (parseFunctionNameIf("TO_DATE"))
                    return parseFunctionArgs2(f1 -> toDate(f1, inline(settings().getParseDateFormat())), DSL::toDate);
                else if (parseFunctionNameIf("TO_TIMESTAMP"))
                    return parseFunctionArgs2(f1 -> toTimestamp(f1, inline(settings().getParseTimestampFormat())), DSL::toTimestamp);
                else if (parseFunctionNameIf("TIMESTAMPDIFF"))
                    return parseFunctionArgs2((f1, f2) -> DSL.timestampDiff(f1, f2));
                else if ((field = parseFieldTruncIf()) != null)
                    return field;

                break;

            case 'U':
                if (parseFunctionNameIf("UPPER", "UCASE"))
                    return DSL.upper((Field) parseFieldParenthesised());
                else if (parseFunctionNameIf("UUID", "UUID_GENERATE", "UUID_STRING") && parseEmptyParens())
                    return uuid();

                else if (parseFunctionNameIf("UNIX_TIMESTAMP"))
                    return epoch(parseFieldParenthesised());

                break;

            case 'V':
                if (TRUE.equals(data(DATA_PARSE_ON_CONFLICT)) && (parseFunctionNameIf("VALUES") || parseFunctionNameIf("VALUE")))
                    return excluded(parseFieldParenthesised());

            case 'W':
                if (parseFunctionNameIf("WIDTH_BUCKET"))
                    return parseFunctionArgs4((f1, f2, f3, f4) -> widthBucket(f1, f2, f3, f4));
                else if (parseFunctionNameIf("WEEK"))
                    return week(parseFieldParenthesised());

                break;

            case 'X':
                if ((value = parseBinaryLiteralIf()) != null)
                    return inline((byte[]) value);

                else if (parseFunctionNameIf("XMLCOMMENT"))
                    return xmlcomment((Field) parseField());
                else if (parseFunctionNameIf("XMLTYPE"))
                    return cast((Field) parseField(), XML);
                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;
                else if ((field = parseFieldXMLSerializeIf()) != null)
                    return field;

                break;

            case 'Y':
                if (parseFunctionNameIf("YEAR"))
                    return year(parseFieldParenthesised());

                break;

            case 'Z':
                if (parseFunctionNameIf("ZEROIFNULL"))
                    return coalesce(parseFieldParenthesised(), zero());

                break;

            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '-':
            case '.':
                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();
                        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();
                EnumSet fk = forbidden;

                try {
                    if (!forbidden.isEmpty())
                        forbidden = EnumSet.noneOf(FunctionKeyword.class);

                    FieldOrRow r = parseScalarSubqueryIf();
                    if (r != null)
                        return r;

                    parse('(');
                    r = parseFieldOrRow();
                    List> list = null;

                    if (r instanceof Field f) {
                        while (parseIf(',')) {
                            if (list == null) {
                                list = new ArrayList<>();
                                list.add(f);
                            }

                            // TODO Allow for nesting ROWs
                            list.add(parseField());
                        }
                    }

                    parse(')');
                    return list != null ? row(list) : r;
                }
                finally {
                    forbidden = fk;
                }
        }

        if ((field = parseAggregateFunctionIf()) != null)
            return field;

        else if ((field = parseBooleanValueExpressionIf()) != null)
            return field;

        else
            return parseFieldNameOrSequenceExpression();
    }

    private final Field parseFieldAddDatePart(DatePart part) {
        return parseFunctionArgs2((f1, f2) -> dateAdd(f1, f2, part));
    }

    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 parseFieldSysConnectByPathIf() {
        if (!ignoreProEdition() && 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);
        boolean agg = false;

        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") ||
            (agg = parseKeywordIf("BIT_AND_AGG")) ||
            (agg = parseKeywordIf("BITAND_AGG")) ||
            (agg = parseKeywordIf("BIN_AND_AGG"))) {
            parse('(');
            Field x = toField(parseNumericOp());

            if (agg && parse(')') || parseIf(')'))
                return parseAggregateFunctionIf(false, bitAndAgg((Field) x));

            parse(',');
            Field y = toField(parseNumericOp());
            parse(')');

            return bitAnd((Field) x, (Field) y);
        }
        else if (parseKeywordIf("BIT_NAND") ||
            parseKeywordIf("BITNAND") ||
            parseKeywordIf("BIN_NAND") ||
            (agg = parseKeywordIf("BIT_NAND_AGG")) ||
            (agg = parseKeywordIf("BITNAND_AGG")) ||
            (agg = parseKeywordIf("BIN_NAND_AGG"))) {
            parse('(');
            Field x = toField(parseNumericOp());

            if (agg && parse(')') || parseIf(')'))
                return parseAggregateFunctionIf(false, bitNandAgg((Field) x));

            parse(',');
            Field y = toField(parseNumericOp());
            parse(')');

            return bitNand((Field) x, (Field) y);
        }
        else if (parseKeywordIf("BIT_OR") ||
            parseKeywordIf("BITOR") ||
            parseKeywordIf("BIN_OR") ||
            (agg = parseKeywordIf("BIT_OR_AGG")) ||
            (agg = parseKeywordIf("BITOR_AGG")) ||
            (agg = parseKeywordIf("BIN_OR_AGG"))) {
            parse('(');
            Field x = toField(parseNumericOp());

            if (agg && parse(')') || parseIf(')'))
                return parseAggregateFunctionIf(false, bitOrAgg((Field) x));

            parse(',');
            Field y = toField(parseNumericOp());
            parse(')');

            return bitOr((Field) x, (Field) y);
        }
        else if (parseKeywordIf("BIT_NOR") ||
            parseKeywordIf("BITNOR") ||
            parseKeywordIf("BIN_NOR") ||
            (agg = parseKeywordIf("BIT_NOR_AGG")) ||
            (agg = parseKeywordIf("BITNOR_AGG")) ||
            (agg = parseKeywordIf("BIN_NOR_AGG"))) {
            parse('(');
            Field x = toField(parseNumericOp());

            if (agg && parse(')') || parseIf(')'))
                return parseAggregateFunctionIf(false, bitNorAgg((Field) x));

            parse(',');
            Field y = toField(parseNumericOp());
            parse(')');

            return bitNor((Field) x, (Field) y);
        }
        else if (parseKeywordIf("BIT_XOR") ||
            parseKeywordIf("BITXOR") ||
            parseKeywordIf("BIN_XOR") ||
            (agg = parseKeywordIf("BIT_XOR_AGG")) ||
            (agg = parseKeywordIf("BITXOR_AGG")) ||
            (agg = parseKeywordIf("BIN_XOR_AGG"))) {
            parse('(');
            Field x = toField(parseNumericOp());

            if (agg && parse(')') || parseIf(')'))
                return parseAggregateFunctionIf(false, bitXorAgg((Field) x));

            parse(',');
            Field y = toField(parseNumericOp());
            parse(')');

            return bitXor((Field) x, (Field) y);
        }
        else if (parseKeywordIf("BIT_XNOR") ||
            parseKeywordIf("BITXNOR") ||
            parseKeywordIf("BIN_XNOR") ||
            (agg = parseKeywordIf("BIT_XNOR_AGG")) ||
            (agg = parseKeywordIf("BITXNOR_AGG")) ||
            (agg = parseKeywordIf("BIN_XNOR_AGG"))) {
            parse('(');
            Field x = toField(parseNumericOp());

            if (agg && parse(')') || parseIf(')'))
                return parseAggregateFunctionIf(false, bitXNorAgg((Field) x));

            parse(',');
            Field y = toField(parseNumericOp());
            parse(')');

            return bitXNor((Field) x, (Field) y);
        }
        else if (parseKeywordIf("BIT_NOT") || parseKeywordIf("BITNOT") || parseKeywordIf("BIN_NOT")) {
            parse('(');
            Field x = toField(parseNumericOp());
            parse(')');

            return bitNot((Field) x);
        }
        else if (parseKeywordIf("BIN_SHL", "BITSHIFTLEFT")) {
            parse('(');
            Field x = toField(parseNumericOp());
            parse(',');
            Field y = toField(parseNumericOp());
            parse(')');

            return shl((Field) x, (Field) y);
        }
        else if (parseKeywordIf("BIN_SHR", "BITSHIFTRIGHT")) {
            parse('(');
            Field x = toField(parseNumericOp());
            parse(',');
            Field y = toField(parseNumericOp());
            parse(')');

            return shr((Field) x, (Field) y);
        }

        return null;
    }

    private final Field parseFieldNewIdIf() {
        if (parseFunctionNameIf("NEWID")) {
            parse('(');
            Long l = parseSignedIntegerLiteralIf();

            if (l != null && l != -1L)
                throw expected("No argument or -1 expected");

            parse(')');
            return uuid();
        }

        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 enum SequenceMethod {
        NEXTVAL,
        CURRVAL
    }

    private final Field parseFieldXMLSerializeIf() {
        if (parseFunctionNameIf("XMLSERIALIZE")) {
            parse('(');
            boolean content = parseKeywordIf("CONTENT");
            if (!content)
                parseKeywordIf("DOCUMENT");

            Field value = (Field) parseField();
            parseKeyword("AS");
            DataType type = parseCastDataType();
            parse(')');

            return content ? xmlserializeContent(value, type) : xmlserializeDocument(value, type);
        }

        return null;
    }

    private final Field parseFieldXMLConcatIf() {
        if (parseFunctionNameIf("XMLCONCAT")) {
            parse('(');
            List> fields = parseList(',', c -> c.parseField());
            parse(')');

            return xmlconcat(fields);
        }

        return null;
    }

    private final Field parseFieldXMLElementIf() {
        if (parseFunctionNameIf("XMLELEMENT")) {
            parse('(');
            parseKeywordIf("NAME");

            if (parseIf(')'))
                return xmlelement(systemName("NAME"));

            Name name = parseIdentifier();
            XMLAttributes attr = null;
            List> content = new ArrayList<>();

            while (parseIf(',')) {
                if (attr == null && parseKeywordIf("XMLATTRIBUTES")) {
                    parse('(');
                    List> 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 (!ignoreProEdition() && 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> 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 xml = (Field) parseField();
            parse(')');

            return documentOrContent == DocumentOrContent.DOCUMENT
                 ? xmlparseDocument(xml)
                 : xmlparseContent(xml);
        }

        return null;
    }

    private final Field parseFieldXMLQueryIf() {
        if (parseFunctionNameIf("XMLQUERY")) {
            parse('(');
            Field xpath = (Field) parseField();
            XMLPassingMechanism m = parseXMLPassingMechanism();
            Field xml = (Field) 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> parseAliasedXMLContent() {
        List> result = new ArrayList<>();

        do {
            Field field = parseField();

            if (parseKeywordIf("AS"))
                field = field.as(parseIdentifier(true, false));

            result.add(field);
        }
        while (parseIf(','));
        return result;
    }

    private final AggregateFilterStep parseXMLAggFunctionIf() {
        if (parseFunctionNameIf("XMLAGG")) {
            XMLAggOrderByStep s1;
            AggregateFilterStep s2;

            parse('(');
            s2 = s1 = xmlagg((Field) parseField());

            if (parseKeywordIf("ORDER BY"))
                s2 = s1.orderBy(parseList(',', c -> c.parseSortField()));

            parse(')');
            return s2;
        }

        return null;
    }

    private final Field parseFieldJSONValueIf() {
        if (parseFunctionNameIf("JSON_VALUE")) {
            parse('(');
            Field json = parseField();
            parse(',');
            Field path = (Field) 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 (!ignoreProEdition() && parseKeywordIf("ERROR") && requireProEdition())
            return JSONValue.Behaviour.ERROR;
        else if (!ignoreProEdition() && parseKeywordIf("NULL") && requireProEdition())
            return JSONValue.Behaviour.NULL;
        else if (!ignoreProEdition() && parseKeywordIf("DEFAULT") && requireProEdition())
            return JSONValue.Behaviour.DEFAULT;
        else
            return null;
    }

    private final JSONExists.Behaviour parseJSONExistsOnErrorBehaviourIf() {
        if (!ignoreProEdition() && parseKeywordIf("ERROR") && parseKeyword("ON ERROR") && requireProEdition())
            return JSONExists.Behaviour.ERROR;
        else if (!ignoreProEdition() && parseKeywordIf("TRUE") && parseKeyword("ON ERROR") && requireProEdition())
            return JSONExists.Behaviour.TRUE;
        else if (!ignoreProEdition() && parseKeywordIf("FALSE") && parseKeyword("ON ERROR") && requireProEdition())
            return JSONExists.Behaviour.FALSE;
        else if (!ignoreProEdition() && 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 parseFieldJSONLiteralIf() {
        if (parseKeywordIf("JSON")) {
            if (parseIf('{')) {
                if (parseIf('}'))
                    return jsonObject();

                List> entries = parseList(',', ctx -> {
                    Field key = parseField();
                    parse(':');
                    return key(key).value(parseField());
                });

                parse('}');
                return jsonObject(entries);
            }
            else if (parseIf('[')) {
                if (parseIf(']'))
                    return jsonArray();

                List> fields = parseList(',', c -> parseField());
                parse(']');
                return jsonArray(fields);
            }
            else
                throw expected("[", "{");
        }

        return null;
    }

    private final Field parseFieldJSONArrayConstructorIf() {
        boolean jsonb = false;

        if (parseFunctionNameIf("JSON_ARRAY", "JSON_BUILD_ARRAY") || (jsonb = parseFunctionNameIf("JSONB_BUILD_ARRAY"))) {
            parse('(');
            if (parseIf(')'))
                return jsonb ? jsonbArray() : jsonArray();

            List> result = null;
            JSONOnNull onNull = parseJSONNullTypeIf();
            DataType returning = parseJSONReturningIf();

            if (onNull == null && returning == null) {
                result = parseList(',', c -> c.parseField());
                onNull = parseJSONNullTypeIf();
                returning = parseJSONReturningIf();
            }

            parse(')');

            JSONArrayNullStep s1 = result == null
                ? jsonb ? jsonbArray() : jsonArray()
                : jsonb ? jsonbArray(result) : 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() {
        boolean jsonb = false;

        if (parseFunctionNameIf("JSON_ARRAYAGG", "JSON_AGG", "JSON_GROUP_ARRAY") || (jsonb = parseFunctionNameIf("JSONB_AGG"))) {
            AggregateFilterStep result;
            JSONArrayAggOrderByStep s1;
            JSONArrayAggNullStep s2;
            JSONArrayAggReturningStep s3;
            JSONOnNull onNull;
            DataType returning;

            parse('(');
            result = s3 = s2 = s1 = jsonb ? jsonbArrayAgg(parseField()) : jsonArrayAgg(parseField());

            if (parseKeywordIf("ORDER BY"))
                result = s3 = s2 = s1.orderBy(parseList(',', c -> c.parseSortField()));

            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() {
        boolean jsonb = false;

        if (parseFunctionNameIf("JSON_OBJECT", "JSON_BUILD_OBJECT") || (jsonb = parseFunctionNameIf("JSONB_BUILD_OBJECT"))) {
            parse('(');
            if (parseIf(')'))
                return jsonb ? jsonbObject() : jsonObject();

            List> result;
            JSONOnNull onNull = parseJSONNullTypeIf();
            DataType returning = parseJSONReturningIf();

            if (onNull == null && returning == null) {
                result = parseList(',', c -> parseJSONEntry());
                onNull = parseJSONNullTypeIf();
                returning = parseJSONReturningIf();
            }
            else
                result = new ArrayList<>();

            parse(')');

            JSONObjectNullStep s1 = jsonb ? jsonbObject(result) : 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() {
        boolean jsonb = false;

        if (parseFunctionNameIf("JSON_OBJECTAGG", "JSON_OBJECT_AGG", "JSON_GROUP_OBJECT") || (jsonb = parseFunctionNameIf("JSONB_OBJECT_AGG"))) {
            AggregateFilterStep result;
            JSONObjectAggNullStep s1;
            JSONObjectAggReturningStep s2;
            JSONOnNull onNull;
            DataType returning;

            parse('(');
            result = s2 = s1 = jsonb ? jsonbObjectAgg(parseJSONEntry()) : 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 key = (Field) parseField();
        if (parseKeywordIf("VALUE"))
            ;
        else if (valueRequired)
            throw expected("VALUE");
        else
            parse(',');

        return key(key).value(parseField());
    }

    private final Field parseArrayValueConstructorIf() {
        if (parseKeywordIf("ARRAY")) {
            if (parseIf('[')) {
                List> fields;

                if (parseIf(']')) {
                    fields = emptyList();
                }
                else {
                    fields = parseList(',', c -> c.parseField());
                    parse(']');
                }

                // Prevent "wrong" javac method bind
                return DSL.array((Collection) fields);
            }
            else if (parseIf('(')) {
                SelectQueryImpl select = parseWithOrSelect(1);
                parse(')');

                return DSL.array(select);
            }
            else
                throw expected("[", "(");
        }

        return null;
    }

    private final Field parseMultisetValueConstructorIf() {
        if (parseKeywordIf("MULTISET")) {
            if (parseIf('(')) {
                SelectQueryImpl select = parseWithOrSelect();
                parse(')');

                return DSL.multiset(select);
            }
            else
                throw expected("(");
        }

        return null;
    }

    private final Field parseFieldLogIf() {
        if (parseFunctionNameIf("LOG")) {
            parse('(');
            Field f1 = toField(parseNumericOp());
            Field f2 = parseIf(',') ? toField(parseNumericOp()) : null;
            parse(')');

            switch (parseFamily()) {











                case POSTGRES:
                case SQLITE:
                case YUGABYTEDB:
                    return f2 == null ? log10(f1) : log(f2, f1);

                default:
                    return f2 == null ? ln(f1) : log(f2, f1);
            }
        }

        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());
                    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")) {
            parse('(');
            Field arg1 = toField(parseNumericOp());
            Field arg2 = parseIf(',') ? toField(parseNumericOp()) : null;
            parse(')');

            return arg2 == null ? round(arg1) : round(arg1, arg2);
        }

        return null;
    }

    private final Field parseFieldLeastIf() {
        if (parseFunctionNameIf("LEAST", "MINVALUE")) {
            parse('(');
            List> fields = parseList(',', c -> c.parseField());
            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> fields = parseList(',', c -> c.parseField());
            parse(')');

            return greatest(fields.get(0), fields.size() > 1 ? fields.subList(1, fields.size()).toArray(EMPTY_FIELD) : EMPTY_FIELD);
        }

        return null;
    }

    private final Field parseFieldGroupingIdIf() {
        if (!ignoreProEdition() && 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 (parseKeywordIf("WITH TIME ZONE")) {
                return inline(parseTimestampTZLiteral());
            }
            else if (parseIf('(')) {
                Field f = parseField();
                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 OffsetDateTime parseTimestampTZLiteral() {
        OffsetDateTime timestamp = Convert.convert(parseStringLiteral(), OffsetDateTime.class);

        if (timestamp == null)
            throw exception("Illegal timestamp literal");

        return timestamp;
    }

    private final Field parseFieldTimeLiteralIf() {
        int p = position();

        if (parseKeywordIf("TIME")) {
            if (parseKeywordIf("WITHOUT TIME ZONE")) {
                return inline(parseTimeLiteral());
            }
            else if (parseKeywordIf("WITH TIME ZONE")) {
                return inline(parseTimeTZLiteral());
            }
            else if (parseIf('(')) {
                Field f = parseField();
                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 OffsetTime parseTimeTZLiteral() {
        OffsetTime time = Convert.convert(parseStringLiteral(), OffsetTime.class);

        if (time == null)
            throw exception("Illegal time literal");

        return time;
    }

    private final Field parseFieldIntervalLiteralIf() {
        int p = position();

        if (parseKeywordIf("INTERVAL")) {
            if (peek('\'')) {
                return inline(parseIntervalLiteral());
            }
            else {
                Long interval = parseUnsignedIntegerLiteralIf();

                if (interval != null) {
                    DatePart part = parseIntervalDatePart();
                    long l = interval;
                    int i = asInt(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, asInt(l / 1000), (int) (l % 1000 * 1000000)));
                        case MICROSECOND:
                            return inline(new DayToSecond(0, 0, 0, asInt(l / 1000000), (int) (l % 1000000 * 1000)));
                        case NANOSECOND:
                            return inline(new DayToSecond(0, 0, 0, asInt(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 : asInt((long) (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, asInt((long) seconds), asInt((long) ((seconds % 1.0) * 1000000000)))
            );
        }

        position(p);
        return null;
    }

    private final boolean parseIntervalPrecisionKeywordIf(String keyword) {
        if (parseKeywordIf(keyword)) {
            if (parseIf('(')) {
                parseUnsignedIntegerLiteral();
                parse(')');
            }

            return true;
        }

        return false;
    }

    private final Interval parseIntervalLiteral() {
        Interval result = parsePostgresIntervalLiteralIf();
        if (result != null)
            return result;

        String string = parseStringLiteral();
        String message = "Illegal interval literal";

        if (parseIntervalPrecisionKeywordIf("YEAR"))
            if (parseKeywordIf("TO") && parseIntervalPrecisionKeywordIf("MONTH"))
                return requireNotNull(YearToMonth.yearToMonth(string), message);
            else
                return requireNotNull(YearToMonth.year(string), message);
        else if (parseIntervalPrecisionKeywordIf("MONTH"))
            return requireNotNull(YearToMonth.month(string), message);
        else if (parseIntervalPrecisionKeywordIf("DAY"))
            if (parseKeywordIf("TO"))
                if (parseIntervalPrecisionKeywordIf("SECOND"))
                    return requireNotNull(DayToSecond.dayToSecond(string), message);
                else if (parseIntervalPrecisionKeywordIf("MINUTE"))
                    return requireNotNull(DayToSecond.dayToMinute(string), message);
                else if (parseIntervalPrecisionKeywordIf("HOUR"))
                    return requireNotNull(DayToSecond.dayToHour(string), message);
                else
                    throw expected("HOUR", "MINUTE", "SECOND");
            else
                return requireNotNull(DayToSecond.day(string), message);
        else if (parseIntervalPrecisionKeywordIf("HOUR"))
            if (parseKeywordIf("TO"))
                if (parseIntervalPrecisionKeywordIf("SECOND"))
                    return requireNotNull(DayToSecond.hourToSecond(string), message);
                else if (parseIntervalPrecisionKeywordIf("MINUTE"))
                    return requireNotNull(DayToSecond.hourToMinute(string), message);
                else
                    throw expected("MINUTE", "SECOND");
            else
                return requireNotNull(DayToSecond.hour(string), message);
        else if (parseIntervalPrecisionKeywordIf("MINUTE"))
            if (parseKeywordIf("TO") && parseIntervalPrecisionKeywordIf("SECOND"))
                return requireNotNull(DayToSecond.minuteToSecond(string), message);
            else
                return requireNotNull(DayToSecond.minute(string), message);
        else if (parseIntervalPrecisionKeywordIf("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 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();
                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", "DATETIME_TRUNC")) {
            parse('(');

            Field field;
            DatePart part;

            switch (parseFamily()) {







                default:
                    part = parseDatePart();
                    parse(',');
                    field = parseField();
                    break;
            }

            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 interval = (Field) parseField();
            parse(',');
            Field date = (Field) parseField();
            parse(')');

            return DSL.dateAdd(date, interval, part);
        }

        // MySQL style
        else if (parseFunctionNameIf("DATE_ADD") || (sub = parseFunctionNameIf("DATE_SUB"))) {
            parse('(');
            Field d = parseField();

            // [#12025] In the absence of meta data, assume TIMESTAMP
            Field date = d.getDataType().isDateTime() ? d : d.coerce(TIMESTAMP);
            parse(',');

            // [#8792] TODO: Support parsing interval expressions
            Field interval = parseFieldIntervalLiteralIf();
            parse(')');

            return sub ? DSL.dateSub((Field) date, (Field) interval) : DSL.dateAdd((Field) date, (Field) interval);
        }

        return null;
    }

    private final Field parseFieldDateDiffIf() {
        if (parseFunctionNameIf("DATEDIFF")) {
            parse('(');
            DatePart datePart = parseDatePartIf();

            if (datePart != null)
                parse(',');

            Field d1 = (Field) parseField();

            if (parseIf(',')) {
                Field d2 = (Field) parseField();
                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() {
        int p = position();
        boolean string = parseIf('\'');

        DatePart result = parseDatePartIf0();

        if (result == null)
            position(p);
        else if (string)
            parse('\'');

        // [#12645] In PostgreSQL, function based indexes tend to cast the
        //          date part to a type explicitly
        if (parseIf("::"))
            parseDataType();

        return result;
    }

    private final DatePart parseDatePartIf0() {
        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("WEEK_OF_YEAR") ||
                    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 result = concat(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD));
            parse(')');
            return result;
        }

        return null;
    }

    private final Field parseFieldOverlayIf() {
        if (parseFunctionNameIf("OVERLAY")) {
            parse('(');
            Field f1 = (Field) parseField();
            parseKeyword("PLACING");
            Field f2 = (Field) parseField();
            parseKeyword("FROM");
            Field f3 = (Field) parseField();
            Field f4 =
                parseKeywordIf("FOR")
              ? (Field) parseField()




              : null;
            parse(')');

            return f4 == null ? overlay(f1, f2, f3) : overlay(f1, f2, f3, f4);
        }

        return null;
    }

    private final Field parseFieldPositionIf() {
        if (parseFunctionNameIf("POSITION")) {
            parse('(');
            forbidden.add(FK_IN);
            Field f1 = (Field) parseField();
            parseKeyword("IN");
            forbidden.remove(FK_IN);
            Field f2 = (Field) parseField();
            parse(')');
            return DSL.position(f2, f1);
        }

        return null;
    }

    private final Field parseFieldLocateIf() {
        boolean locate = parseFunctionNameIf("LOCATE");
        if (locate || parseFunctionNameIf("LOCATE_IN_STRING")) {
            parse('(');
            Field f1 = (Field) parseField();
            parse(',');
            Field f2 = (Field) parseField();
            Field f3 = (Field) (parseIf(',') ? parseField() : null);
            parse(')');












            if (locate)
                return f3 == null ? DSL.position(f2, f1) : DSL.position(f2, f1, f3);
            else
                return f3 == null ? DSL.position(f1, f2) : DSL.position(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();
            parse(',');
            Field pattern = parseField();
            Field replacement = parseIf(',') ? parseField() : null;
            Long i1;
            Long i2;

            if (replacement == null) {
                replacement = inline("");
            }
            else if (ifx) {
                if (parseIf(','))
                    if (1L == parseUnsignedIntegerLiteral())
                        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 = parseUnsignedIntegerLiteral();
                        parse(',');
                        i2 = parseUnsignedIntegerLiteral();

                        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 (parseFamily()) {



                    case POSTGRES:
                    case YUGABYTEDB:
                        first = true;
                        break;
                }
            }

            parse(')');
            return first
                ? regexpReplaceFirst(field, pattern, replacement)
                : regexpReplaceAll(field, pattern, replacement);
        }
        else if (parseFunctionNameIf("REPLACE_REGEXPR")) {
            parse('(');
            forbidden.add(FK_IN);
            Field pattern = parseField();
            parseKeyword("IN");
            forbidden.remove(FK_IN);
            Field field = parseField();
            Field replacement = parseKeywordIf("WITH") ? parseField() : inline("");
            first = parseKeywordIf("OCCURRENCE") && !parseKeywordIf("ALL") && parse("1");

            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 f1 = (Field) parseField();
            if (substr || !(keywords = parseKeywordIf("FROM")))
                parse(',');
            Field f2 = toField(parseNumericOp());
            Field f3 =
                    ((keywords && parseKeywordIf("FOR")) || (!keywords && parseIf(',')))
                ? (Field) toField(parseNumericOp())
                : 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(',') || parseIf(')')) {
                    position(p);
                }
                else if (parseKeywordIf("FROM")) {
                    Field f = (Field) parseField();
                    parse(')');

                    return leading ? ltrim(f)
                         : trailing ? rtrim(f)
                         : trim(f);
                }
            }

            if (parseKeywordIf("FROM")) {
                if (parseIf(',') || parseIf(')')) {
                    position(p);
                }
                else {
                    Field f = (Field) parseField();
                    parse(')');
                    return trim(f);
                }
            }

            Field f1 = (Field) parseField();

            if (parseKeywordIf("FROM")) {
                Field f2 = (Field) parseField();
                parse(')');

                return leading ? ltrim(f2, f1)
                     : trailing ? rtrim(f2, f1)
                     : trim(f2, f1);
            }
            else {
                Field f2 = parseIf(',') ? (Field) parseField() : null;
                parse(')');

                return f2 == null ? trim(f1) : trim(f1, f2);
            }
        }

        return null;
    }

    private final Field parseFieldTranslateIf() {
        if (parseFunctionNameIf("TRANSLATE", "OTRANSLATE")) {





            return parseFunctionArgs3(DSL::translate);
        }

        return null;
    }

    private final Field parseFieldDecodeIf() {
        if (parseFunctionNameIf("DECODE", "MAP")) {
            parse('(');
            List> fields = parseList(',', c -> c.parseField());
            int size = fields.size();
            if (size < 3)
                throw expected("At least three arguments to DECODE()");

            parse(')');
            return DSL.decode(
                (Field)   fields.get(0),
                (Field)   fields.get(1),
                (Field)   fields.get(2),
                (Field[]) (size == 3 ? EMPTY_FIELD : fields.subList(3, size).toArray(EMPTY_FIELD))
            );
        }

        return null;
    }

    private final Field parseFieldChooseIf() {
        if (parseFunctionNameIf("CHOOSE")) {
            parse('(');
            Field index = (Field) parseField();
            parse(',');
            List> fields = parseList(',', c -> c.parseField());
            parse(')');

            return DSL.choose(index, fields.toArray(EMPTY_FIELD));
        }

        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 parseFieldCoalesceIf() {
        if (parseFunctionNameIf("COALESCE")) {
            parse('(');
            List> fields = parseList(',', c -> c.parseField());
            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  Field parseFieldFieldIf() {
        if (parseFunctionNameIf("FIELD")) {
            parse('(');
            Field f1 = parseField();
            parse(',');
            List> f2 = parseList(',', c -> c.parseField());
            parse(')');

            return DSL.field((Field) f1, (Field[]) 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 (!ignoreProEdition() && parseIf(',') && requireProEdition())
                style = parseUnsignedIntegerLiteral();
            parse(')');

            if (style == null)
                return cast(field, type);




        }

        return null;
    }

    private final Field parseBooleanValueExpressionIf() {
        TruthValue truth = parseTruthValueIf();

        if (truth != null) {
            switch (truth) {
                case T_TRUE:
                    return inline(true);
                case T_FALSE:
                    return inline(false);

                // [#16368] We cannot decide the data type at this point
                case T_NULL:
                    return inline(null, OTHER);
                default:
                    throw exception("Truth value not supported: " + truth);
            }
        }

        return null;
    }

    private final Field parseAggregateFunctionIf() {
        return parseAggregateFunctionIf(false);
    }

    private final Field parseAggregateFunctionIf(boolean basic) {
        return parseAggregateFunctionIf(basic, null);
    }

    private final Field parseAggregateFunctionIf(boolean basic, AggregateFunction f) {
        AggregateFunction agg = null;
        AggregateFilterStep filter = null;
        WindowBeforeOverStep over = null;
        Object keep = null;
        Field result = null;
        Condition condition = null;

        keep = over = filter = agg = f != null ? f : 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 = parseMultisetAggFunctionIf();
        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 = parseCountIfIf();

        if (filter == null && over == null)
            if (!basic)
                return parseSpecialAggregateFunctionIf();
            else
                return null;

        if (keep != null && filter != null && !basic && !ignoreProEdition() && parseKeywordIf("KEEP")) {
            requireProEdition();


















        }
        else if (filter != null && !basic && parseKeywordIf("FILTER")) {
            result = over = parseAggregateFilter(filter);
        }
        else if (filter != null)
            result = filter;
        else
            result = over;

        if (!basic && parseKeywordIf("OVER")) {
            Object nameOrSpecification = parseWindowNameOrSpecification(filter != null);

            if (nameOrSpecification instanceof Name n)
                result = over.over(n);
            else if (nameOrSpecification instanceof WindowSpecification w)
                result = over.over(w);
            else
                result = over.over();
        }

        return result;
    }

    private final Field parseAggregateFilterIf(AggregateFilterStep filter) {
        return parseKeywordIf("FILTER") ? parseAggregateFilter(filter) : filter;
    }

    private final WindowBeforeOverStep parseAggregateFilter(AggregateFilterStep filter) {
        parse('(');
        parseKeyword("WHERE");
        Condition condition = parseCondition();
        parse(')');

        return filter.filterWhere(condition);
    }

    private final Field parseSpecialAggregateFunctionIf() {
        if (parseFunctionNameIf("GROUP_CONCAT")) {
            parse('(');

            GroupConcatOrderByStep s1;
            GroupConcatSeparatorStep s2;
            AggregateFunction s3;

            if (parseKeywordIf("DISTINCT"))
                s1 = DSL.groupConcatDistinct(parseField());
            else
                s1 = DSL.groupConcat(parseField());

            if (parseKeywordIf("ORDER BY"))
                s2 = s1.orderBy(parseList(',', c -> c.parseSortField()));
            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> args = parseList(',', c -> c.parseField());
            parse(')');
            return parseAggregateFilterIf(rank(args).withinGroupOrderBy(parseWithinGroupN()));
        }

        return null;
    }

    private final Field parseFieldDenseRankIf() {
        if (parseFunctionNameIf("DENSE_RANK", "DENSERANK")) {
            parse('(');

            if (parseIf(')'))
                return parseWindowFunction(null, null, denseRank());

            // Hypothetical set function
            List> args = parseList(',', c -> c.parseField());
            parse(')');
            return parseAggregateFilterIf(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> args = parseList(',', c -> c.parseField());
            parse(')');
            return parseAggregateFilterIf(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> args = parseList(',', c -> c.parseField());
            parse(')');
            return parseAggregateFilterIf(cumeDist(args).withinGroupOrderBy(parseWithinGroupN()));
        }

        return null;
    }

    private final Field parseFieldNtileIf() {
        if (parseFunctionNameIf("NTILE")) {
            parse('(');
            int number = asInt(parseUnsignedIntegerLiteral());
            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 f1 = (Field) parseField();
            Integer f2 = null;
            Field f3 = null;

            if (parseIf(',')) {
                f2 = asInt(parseUnsignedIntegerLiteral());

                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 arg = (Field) parseField();
            WindowIgnoreNullsStep 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 arg = (Field) parseField();
            WindowIgnoreNullsStep 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(',');
            Field f2 = parseField();
            WindowFromFirstLastStep s1 = nthValue(f1, (Field) 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);

        return nameOrSpecification instanceof Name n
            ? s3.over(n)
            : nameOrSpecification instanceof WindowSpecification w
            ? s3.over(w)
            : s3.over();
    }

    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() {
        switch (characterUpper()) {
            case 'C':
                if (parseFunctionNameIf("CORR"))
                    return parseBindarySetFunction(DSL::corr);
                else if (parseFunctionNameIf("COVAR_POP"))
                    return parseBindarySetFunction(DSL::covarPop);
                else if (parseFunctionNameIf("COVAR_SAMP"))
                    return parseBindarySetFunction(DSL::covarSamp);

                break;

            case 'R':
                if (parseFunctionNameIf("REGR_AVGX"))
                    return parseBindarySetFunction(DSL::regrAvgX);
                else if (parseFunctionNameIf("REGR_AVGY"))
                    return parseBindarySetFunction(DSL::regrAvgY);
                else if (parseFunctionNameIf("REGR_COUNT"))
                    return parseBindarySetFunction(DSL::regrCount);
                else if (parseFunctionNameIf("REGR_INTERCEPT"))
                    return parseBindarySetFunction(DSL::regrIntercept);
                else if (parseFunctionNameIf("REGR_R2"))
                    return parseBindarySetFunction(DSL::regrR2);
                else if (parseFunctionNameIf("REGR_SLOPE"))
                    return parseBindarySetFunction(DSL::regrSlope);
                else if (parseFunctionNameIf("REGR_SXX"))
                    return parseBindarySetFunction(DSL::regrSXX);
                else if (parseFunctionNameIf("REGR_SXY"))
                    return parseBindarySetFunction(DSL::regrSXY);
                else if (parseFunctionNameIf("REGR_SYY"))
                    return parseBindarySetFunction(DSL::regrSYY);

                break;
        }

        return null;
    }

    private final AggregateFunction parseBindarySetFunction(BiFunction, ? super Field, ? extends AggregateFunction> function) {
        parse('(');
        Field arg1 = (Field) parseField();
        parse(',');
        Field arg2 = (Field) parseField();
        parse(')');

        return function.apply(arg1, arg2);
    }

    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> sort = null;

            if (parseKeywordIf("ORDER BY"))
                sort = parseList(',', c -> c.parseSortField());

            parse(')');

            ArrayAggOrderByStep s1 = distinct
                ? arrayAggDistinct(a1)
                : arrayAgg(a1);

            return sort == null ? s1 : s1.orderBy(sort);
        }

        return null;
    }

    private final AggregateFilterStep parseMultisetAggFunctionIf() {
        if (parseKeywordIf("MULTISET_AGG")) {
            parse('(');

            List> fields = parseList(',', c -> c.parseField());
            List> sort = null;

            if (parseKeywordIf("ORDER BY"))
                sort = parseList(',', c -> c.parseSortField());

            parse(')');
            ArrayAggOrderByStep s1 = multisetAgg(fields);
            return sort == null ? s1 : s1.orderBy(sort);
        }

        return null;
    }

    private final List> parseWithinGroupN() {
        return parseWithinGroupN(false);
    }

    private final List> parseWithinGroupN(boolean optional) {
        if (optional) {
            if (!parseKeywordIf("WITHIN GROUP"))
                return emptyList();
        }
        else
            parseKeyword("WITHIN GROUP");

        parse('(');
        parseKeyword("ORDER BY");
        List> result = parseList(',', c -> c.parseSortField());
        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(parseList(',', c -> c.parseField()));
            parse(')');
        }
        else if (parseFunctionNameIf("DENSE_RANK")) {
            parse('(');
            ordered = denseRank(parseList(',', c -> c.parseField()));
            parse(')');
        }
        else if (parseFunctionNameIf("PERCENT_RANK")) {
            parse('(');
            ordered = percentRank(parseList(',', c -> c.parseField()));
            parse(')');
        }
        else if (parseFunctionNameIf("CUME_DIST")) {
            parse('(');
            ordered = cumeDist(parseList(',', c -> c.parseField()));
            parse(')');
        }
        else
            ordered = null;

        return ordered;
    }

    private final OrderedAggregateFunction parseInverseDistributionFunctionIf() {
        OrderedAggregateFunction ordered;

        if (parseFunctionNameIf("PERCENTILE_CONT")) {
            parse('(');
            ordered = percentileCont((Field) parseField());
            parse(')');
        }
        else if (parseFunctionNameIf("PERCENTILE_DISC")) {
            parse('(');
            ordered = percentileDisc((Field) parseField());
            parse(')');
        }
        else
            ordered = null;

        return ordered;
    }

    private final OrderedAggregateFunction parseListaggFunctionIf() {
        OrderedAggregateFunction 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;
        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> fields = parseList(',', c -> c.parseField());
                    parse(')');

                    return operation == ComputationalOperation.MAX ? greatest(arg, fields.toArray(EMPTY_FIELD)) : least(arg, fields.toArray(EMPTY_FIELD));
                }
            }
        }

        parse(')');

        switch (operation) {
            case ANY_VALUE:
                return anyValue(arg);
            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
                        ? parseList(',', c -> c.parseField()).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 WindowBeforeOverStep parseCountIfIf() {
        if (parseFunctionNameIf("COUNTIF", "COUNT_IF")) {
            parse('(');
            Condition condition = parseCondition();
            parse(')');
            return count().filterWhere(condition);
        }

        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(position(), parseName());
    }

    private final Table parseTableNameIf() {
        int positionBeforeName = position();
        Name name = parseNameIf();

        if (name == null)
            return null;

        return lookupTable(positionBeforeName, name);
    }

    private final Field parseFieldNameOrSequenceExpression() {
        int positionBeforeName = position();
        Name name = parseName();

        if (name.qualified()) {
            String first = name.first();
            String last = name.last();

            if ("NEXTVAL".equalsIgnoreCase(last))
                return sequence(name.qualifier()).nextval();
            else if ("CURRVAL".equalsIgnoreCase(last))
                return sequence(name.qualifier()).currval();
            else if (TRUE.equals(data(DATA_PARSE_ON_CONFLICT)) && "EXCLUDED".equalsIgnoreCase(first))
                return excluded(field(name.unqualifiedName()));
        }

        unknownFunctions:
        if (dsl.settings().getParseUnknownFunctions() == ParseUnknownFunctions.IGNORE && peek('(') && !peekTokens('(', '+', ')')) {
            int p = position();
            List> arguments;

            parse('(');
            if (!parseIf(')')) {













                arguments = parseList(',', c -> c.parseField());
                parse(')');
            }
            else
                arguments = new ArrayList<>();

            return function(name, Object.class, arguments.toArray(EMPTY_FIELD));
        }










        return lookupField(positionBeforeName, name);
    }

    private final TableField parseFieldName() {
        return (TableField) lookupField(position, parseName());
    }

    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());
    }

    @Override
    public final Name parseName() {
        Name result = parseNameIf();

        if (result == null)
            throw expected("Identifier");

        return result;
    }

    @Override
    public 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(".."))
            return parseNameQualified('.', identifier);




        else
            return identifier;
    }

    private final Name parseNameQualified(char separator, Name firstPart) {
        List result = new ArrayList<>();
        result.add(firstPart);

        while (parseIf(separator))
            result.add(parseIdentifier());

        return DSL.name(result.toArray(EMPTY_NAME));
    }

    private final QualifiedAsterisk parseQualifiedAsteriskIf() {
        int positionBeforeName = position();
        Name i1 = parseIdentifierIf();

        if (i1 == null)
            return null;

        if (parseIf('.')) {
            List result = null;
            Name i2;

            do {
                if ((i2 = parseIdentifierIf()) != null) {
                    if (result == null) {
                        result = new ArrayList<>();
                        result.add(i1);
                    }

                    result.add(i2);
                }
                else {
                    parse('*');
                    return lookupQualifiedAsterisk(positionBeforeName, result == null ? i1 : DSL.name(result.toArray(EMPTY_NAME)));
                }
            }
            while (parseIf('.'));
        }

        position(positionBeforeName);
        return null;
    }

    private final List parseIdentifiers() {
        return parseUniqueList("identifier", ',', c -> parseIdentifier());
    }

    @Override
    public final Name parseIdentifier() {
        return parseIdentifier(false, false);
    }

    private final Name parseIdentifier(boolean allowAposQuotes, boolean allowPartAsStart) {
        Name result = parseIdentifierIf(allowAposQuotes, allowPartAsStart);

        if (result == null)
            throw expected("Identifier");

        return result;
    }

    @Override
    public final Name parseIdentifierIf() {
        return parseIdentifierIf(false, false);
    }

    private final Name parseIdentifierIf(boolean allowAposQuotes, boolean allowPartAsStart) {
        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 {
            if (allowPartAsStart ? isIdentifierPart() : isIdentifierStart()) {
                do {
                    sb.append(character());
                    positionInc();
                }
                while (isIdentifierPart() && hasMore());
            }
        }

        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 + ". Start of identifier: " + StringUtils.abbreviate(sb.toString(), 30));

            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();
    }

    @Override
    public final DataType parseDataType() {
        DataType result = parseDataTypeIf(true);

        if (result == null)
            throw expected("Data type");

        return result;
    }

    private final DataType parseDataTypeIf(boolean parseUnknownTypes) {
        DataType result = parseDataTypePrefixIf(parseUnknownTypes);

        if (result != null) {
            boolean array = parseKeywordIf("ARRAY");

            if (parseIf('[')) {
                parseUnsignedIntegerLiteralIf();
                parse(']');

                array = true;
            }

            if (array)
                result = result.getArrayDataType();
        }

        return result;
    }

    private final DataType parseDataTypePrefixIf(boolean parseUnknownTypes) {
        char character = characterUpper();

        if (character == '[' || character == '"' || character == '`')
            character = characterNextUpper();

        switch (character) {
            case 'A':
                if (parseKeywordOrIdentifierIf("ARRAY")) {
                    if (peek('('))
                        return parseParenthesised(c -> parseDataTypeIf(parseUnknownTypes).getArrayDataType());
                    else if (peek('<'))
                        return parseParenthesised('<', c -> parseDataTypeIf(parseUnknownTypes).getArrayDataType(), '>');
                    else
                        return OTHER.getArrayDataType();
                }
                else if (parseKeywordIf("AUTO_INCREMENT")) {
                    parseDataTypeIdentityArgsIf();
                    return INTEGER.identity(true);
                }

                break;

            case 'B':
                if (parseKeywordOrIdentifierIf("BIGINT"))
                    return parseUnsigned(parseAndIgnoreDataTypeLength(BIGINT));
                else if (parseKeywordOrIdentifierIf("BIGSERIAL"))
                    return BIGINT.identity(true);
                else if (parseKeywordOrIdentifierIf("BINARY"))
                    if (parseKeywordIf("VARYING"))
                        return parseDataTypeLength(VARBINARY);
                    else
                        return parseDataTypeLength(BINARY);
                else if (parseKeywordOrIdentifierIf("BIT"))
                    return parseDataTypeLength(BIT);
                else if (parseKeywordOrIdentifierIf("BLOB"))
                    if (parseKeywordIf("SUB_TYPE"))
                        if (parseKeywordIf("0", "BINARY"))
                            return parseDataTypeLength(BLOB);
                        else if (parseKeywordIf("1", "TEXT"))
                            return parseDataTypeLength(CLOB);
                        else
                            throw expected("0", "BINARY", "1", "TEXT");
                    else
                        return parseDataTypeLength(BLOB);
                else if (parseKeywordOrIdentifierIf("BOOLEAN") ||
                         parseKeywordOrIdentifierIf("BOOL"))
                    return BOOLEAN;
                else if (parseKeywordOrIdentifierIf("BYTEA"))
                    return BLOB;

                break;

            case 'C':
                if (parseKeywordOrIdentifierIf("CHAR") ||
                    parseKeywordOrIdentifierIf("CHARACTER"))
                    if (parseKeywordIf("VARYING"))
                        return parseDataTypeCollation(parseDataTypeLength(VARCHAR, VARBINARY, () -> parseKeywordIf("FOR BIT DATA")));
                    else if (parseKeywordIf("LARGE OBJECT"))
                        return parseDataTypeCollation(parseDataTypeLength(CLOB));
                    else
                        return parseDataTypeCollation(parseDataTypeLength(CHAR, BINARY, () -> parseKeywordIf("FOR BIT DATA")));

                // [#5934] [#10291] TODO: support as actual data type as well
                else if (parseKeywordOrIdentifierIf("CITEXT"))
                    return parseDataTypeCollation(parseAndIgnoreDataTypeLength(CLOB));
                else if (parseKeywordOrIdentifierIf("CLOB"))
                    return parseDataTypeCollation(parseDataTypeLength(CLOB));

                break;

            case 'D':
                if (parseKeywordOrIdentifierIf("DATE"))
                    return DATE;
                else if (parseKeywordOrIdentifierIf("DATETIME"))
                    return parseDataTypePrecisionIf(TIMESTAMP);
                else if (parseKeywordOrIdentifierIf("DECIMAL") ||
                         parseKeywordOrIdentifierIf("DEC"))
                    return parseDataTypePrecisionScaleIf(DECIMAL);
                else if (parseKeywordOrIdentifierIf("DOUBLE PRECISION") ||
                         parseKeywordOrIdentifierIf("DOUBLE"))
                    return parseAndIgnoreDataTypePrecisionScaleIf(DOUBLE);

                break;

            case 'E':
                if (parseKeywordOrIdentifierIf("ENUM"))
                    return parseDataTypeCollation(parseDataTypeEnum());

                break;

            case 'F':
                if (parseKeywordOrIdentifierIf("FLOAT"))
                    return parseAndIgnoreDataTypePrecisionScaleIf(FLOAT);

                break;

            case 'G':
                if (!ignoreProEdition()
                    && (parseKeywordOrIdentifierIf("GEOMETRY") || parseKeywordOrIdentifierIf("SDO_GEOMETRY"))
                    && requireProEdition()
                ) {



                }
                else if (!ignoreProEdition() && parseKeywordOrIdentifierIf("GEOGRAPHY") && requireProEdition()) {



                }

                break;

            case 'I':
                if (parseKeywordOrIdentifierIf("INTEGER") ||
                    parseKeywordOrIdentifierIf("INT") ||
                    parseKeywordOrIdentifierIf("INT4"))
                    return parseUnsigned(parseAndIgnoreDataTypeLength(INTEGER));
                else if (parseKeywordOrIdentifierIf("INT2"))
                    return SMALLINT;
                else if (parseKeywordOrIdentifierIf("INT8"))
                    return BIGINT;
                else if (parseKeywordIf("INTERVAL")) {
                    if (parseKeywordIf("YEAR")) {
                        parseDataTypePrecisionIf();
                        parseKeyword("TO MONTH");
                        return INTERVALYEARTOMONTH;
                    }
                    else if (parseKeywordIf("DAY")) {
                        parseDataTypePrecisionIf();
                        parseKeyword("TO SECOND");
                        parseDataTypePrecisionIf();
                        return INTERVALDAYTOSECOND;
                    }
                    else
                        return INTERVAL;
                }
                else if (parseKeywordIf("IDENTITY")) {
                    parseDataTypeIdentityArgsIf();
                    return INTEGER.identity(true);
                }

                break;

            case 'J':
                if (parseKeywordOrIdentifierIf("JSON"))
                    return JSON;
                else if (parseKeywordOrIdentifierIf("JSONB"))
                    return JSONB;

                break;

            case 'L':
                if (parseKeywordOrIdentifierIf("LONGBLOB"))
                    return BLOB;
                else if (parseKeywordOrIdentifierIf("LONGTEXT"))
                    return parseDataTypeCollation(CLOB);
                else if (parseKeywordOrIdentifierIf("LONG NVARCHAR"))
                    return parseDataTypeCollation(parseDataTypeLength(LONGNVARCHAR));
                else if (parseKeywordOrIdentifierIf("LONG VARBINARY") ||
                         parseKeywordOrIdentifierIf("LONGVARBINARY"))
                    return parseDataTypeCollation(parseDataTypeLength(LONGVARBINARY));
                else if (parseKeywordOrIdentifierIf("LONG VARCHAR") ||
                         parseKeywordOrIdentifierIf("LONGVARCHAR"))
                    return parseDataTypeCollation(parseDataTypeLength(LONGVARCHAR, LONGVARBINARY, () -> parseKeywordIf("FOR BIT DATA")));

                break;

            case 'M':
                if (parseKeywordOrIdentifierIf("MEDIUMBLOB"))
                    return BLOB;
                else if (parseKeywordOrIdentifierIf("MEDIUMINT"))
                    return parseUnsigned(parseAndIgnoreDataTypeLength(INTEGER));
                else if (parseKeywordOrIdentifierIf("MEDIUMTEXT"))
                    return parseDataTypeCollation(CLOB);

                break;

            case 'N':
                if (parseKeywordIf("NATIONAL CHARACTER") ||
                    parseKeywordIf("NATIONAL CHAR"))
                    if (parseKeywordIf("VARYING"))
                        return parseDataTypeCollation(parseDataTypeLength(NVARCHAR));
                    else if (parseKeywordIf("LARGE OBJECT"))
                        return parseDataTypeCollation(parseDataTypeLength(NCLOB));
                    else
                        return parseDataTypeCollation(parseDataTypeLength(NCHAR));
                else if (parseKeywordOrIdentifierIf("NCHAR"))
                    if (parseKeywordIf("VARYING"))
                        return parseDataTypeCollation(parseDataTypeLength(NVARCHAR));
                    else if (parseKeywordIf("LARGE OBJECT"))
                        return parseDataTypeCollation(parseDataTypeLength(NCLOB));
                    else
                        return parseDataTypeCollation(parseDataTypeLength(NCHAR));
                else if (parseKeywordOrIdentifierIf("NCLOB"))
                    return parseDataTypeCollation(parseDataTypeLength(NCLOB));
                else if (parseKeywordOrIdentifierIf("NUMBER") ||
                         parseKeywordOrIdentifierIf("NUMERIC"))
                    return parseDataTypePrecisionScaleIf(NUMERIC);
                else if (parseKeywordOrIdentifierIf("NVARCHAR") ||
                         parseKeywordOrIdentifierIf("NVARCHAR2"))
                    return parseDataTypeCollation(parseDataTypeLength(NVARCHAR));

                break;

            case 'O':
                if (parseKeywordOrIdentifierIf("OTHER"))
                    return OTHER;

                break;

            case 'R':
                if (parseKeywordOrIdentifierIf("REAL"))
                    return parseAndIgnoreDataTypePrecisionScaleIf(REAL);

                break;

            case 'S':
                if (parseKeywordOrIdentifierIf("SERIAL4") ||
                    parseKeywordOrIdentifierIf("SERIAL"))
                    return INTEGER.identity(true);
                else if (parseKeywordOrIdentifierIf("SERIAL8"))
                    return BIGINT.identity(true);
                else if (parseKeywordOrIdentifierIf("SET"))
                    return parseDataTypeCollation(parseDataTypeEnum());
                else if (parseKeywordOrIdentifierIf("SMALLINT"))
                    return parseUnsigned(parseAndIgnoreDataTypeLength(SMALLINT));
                else if (parseKeywordOrIdentifierIf("SMALLSERIAL") ||
                         parseKeywordOrIdentifierIf("SERIAL2"))
                    return SMALLINT.identity(true);

                break;

            case 'T':
                if (parseKeywordOrIdentifierIf("TEXT"))
                    return parseDataTypeCollation(parseAndIgnoreDataTypeLength(CLOB));
                else if (parseKeywordOrIdentifierIf("TIMESTAMPTZ"))
                    return parseDataTypePrecisionIf(TIMESTAMPWITHTIMEZONE);
                else if (parseKeywordOrIdentifierIf("TIMESTAMP")) {
                    Integer precision = parseDataTypePrecisionIf();

                    if (parseKeywordOrIdentifierIf("WITH TIME ZONE"))
                        return precision == null ? TIMESTAMPWITHTIMEZONE : TIMESTAMPWITHTIMEZONE(precision);
                    else if (parseKeywordOrIdentifierIf("WITHOUT TIME ZONE") || true)
                        return precision == null ? TIMESTAMP : TIMESTAMP(precision);
                }
                else if (parseKeywordOrIdentifierIf("TIMETZ"))
                    return parseDataTypePrecisionIf(TIMEWITHTIMEZONE);
                else if (parseKeywordOrIdentifierIf("TIME")) {
                    Integer precision = parseDataTypePrecisionIf();

                    if (parseKeywordOrIdentifierIf("WITH TIME ZONE"))
                        return precision == null ? TIMEWITHTIMEZONE : SQLDataType.TIMEWITHTIMEZONE(precision);
                    else if (parseKeywordOrIdentifierIf("WITHOUT TIME ZONE") || true)
                        return precision == null ? TIME : TIME(precision);
                }
                else if (parseKeywordOrIdentifierIf("TINYBLOB"))
                    return BLOB;
                else if (parseKeywordOrIdentifierIf("TINYINT"))
                    return parseUnsigned(parseAndIgnoreDataTypeLength(TINYINT));
                else if (parseKeywordOrIdentifierIf("TINYTEXT"))
                    return parseDataTypeCollation(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(VARCHAR, VARBINARY, () -> parseKeywordIf("FOR BIT DATA")));
                else if (parseKeywordOrIdentifierIf("VARBINARY"))
                    return parseDataTypeLength(VARBINARY);

                break;

            case 'X':
                if (parseKeywordOrIdentifierIf("XML"))
                    return SQLDataType.XML;

                break;
        }

        Name name;
        if (parseUnknownTypes && (name = parseNameIf()) != null)
            return parseDataTypeLength(new DefaultDataType(dsl.dialect(), Object.class, name));
        else
            return null;
    }

    private final void parseDataTypeIdentityArgsIf() {
        if (parseIf('(')) {
            parseList(',', c -> c.parseField());
            parse(')');
        }
    }

    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('(')) {
            parseUnsignedIntegerLiteral();
            parse(')');
        }

        return result;
    }

    private final DataType parseDataTypeLength(DataType in) {
        return parseDataTypeLength(in, in, () -> false);
    }

    private final DataType parseDataTypeLength(DataType in, DataType alternative, BooleanSupplier alternativeIfTrue) {
        Integer length = null;

        if (parseIf('(')) {
            if (!parseKeywordIf("MAX"))
                length = asInt(parseUnsignedIntegerLiteral());

            if (in == VARCHAR || in == CHAR)
                if (!parseKeywordIf("BYTE"))
                    parseKeywordIf("CHAR");

            parse(')');
        }

        DataType result = alternativeIfTrue.getAsBoolean() ? alternative : in;
        return length == null ? result : result.length(length);
    }

    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('(')) {
            parseUnsignedIntegerLiteral();

            if (parseIf(','))
                parseUnsignedIntegerLiteral();

            parse(')');
        }

        return result;
    }

    private final Integer parseDataTypePrecisionIf() {
        Integer precision = null;

        if (parseIf('(')) {
            precision = asInt(parseUnsignedIntegerLiteral());
            parse(')');
        }

        return precision;
    }

    private final DataType parseDataTypePrecisionIf(DataType result) {
        if (parseIf('(')) {
            int precision = asInt(parseUnsignedIntegerLiteral());
            result = result.precision(precision);
            parse(')');
        }

        return result;
    }

    private final DataType parseDataTypePrecisionScaleIf(DataType result) {
        if (parseIf('(')) {
            int precision = parseIf('*') ? 38 : asInt(parseUnsignedIntegerLiteral());

            if (parseIf(','))
                result = result.precision(precision, asInt(parseSignedIntegerLiteral()));
            else
                result = result.precision(precision);

            parse(')');
        }

        return result;
    }

    private final DataType parseDataTypeEnum() {
        parse('(');
        List literals = new ArrayList<>();
        int length = 0;

        do {
            String literal = parseStringLiteral();
            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 parseBindVariableIf() {
        int p = position();
        String paramName;

        switch (character()) {
            case '?':
                parse('?');
                paramName = null;
                break;

            default:
                String prefix = defaultIfNull(settings().getParseNamedParamPrefix(), ":");

                if (parseIf(prefix, false)) {
                    Name identifier = parseIdentifier(false, true);
                    paramName = identifier.last();

                    // [#8821] Avoid conflicts with dollar quoted string literals
                    if ("$".equals(prefix) && paramName.endsWith("$")) {
                        position(p);
                        return null;
                    }

                    break;
                }
                else
                    return null;
        }

        // [#11074] Bindings can be Param or even Field types
        Object binding = nextBinding();

        if (binding instanceof Field f)
            return f;

        Param param = DSL.param(paramName, binding);

        if (bindParamListener != null)
            bindParams.put(paramName != null ? paramName : ("" + bindIndex), 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;
    }

    @Override
    public final String parseStringLiteral() {
        String result = parseStringLiteralIf();

        if (result == null)
            throw expected("String literal");

        return result;
    }

    @Override
    public 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 = !parseIf('0') && parseIf('1');

            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;

            do {
                while (hasMore()) {
                    c1 = character();

                    if (c1 == ' ')
                        positionInc();
                    else
                        break;
                }

                c2 = characterNext();

                if (c1 == '\'')
                    break;
                if (c2 == '\'')
                    throw exception("Unexpected token: \"'\"");

                try {
                    buffer.write(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 (!peek('$'))
            return null;
        else
            parse('$');

        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) parseInt(substring(i + 1, i + 5), 16);
                            i += 4;
                            break;

                        // Unicode character value UTF-32
                        case 'U':
                            sb.appendCodePoint(parseInt(substring(i + 1, i + 9), 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) parseInt("" + c2 + c3 + c4, 8);
                                    }
                                    else {
                                        c1 = (char) parseInt("" + c2 + c3, 8);
                                    }
                                }
                                else {
                                    c1 = (char) 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 parseFieldUnsignedNumericLiteral(Sign sign) {
        Field result = parseFieldUnsignedNumericLiteralIf(sign);

        if (result == null)
            throw expected("Unsigned numeric literal");

        return result;
    }

    private final Field parseFieldUnsignedNumericLiteralIf(Sign sign) {
        Number r = parseUnsignedNumericLiteralIf(sign);
        return r == null ? null : inline(r);
    }

    private final Number parseUnsignedNumericLiteralIf(Sign sign) {
        int p = position();
        boolean decimal = false;
        parseDigits();

        if (decimal |= parseIf('.', false))
            parseDigits();

        if (p == position())
            return null;

        if (parseIf('e', false) || parseIf('E', false)) {
            parseIf('-', false);
            int p0 = position();
            parseDigits();

            // [#16330] Support implicit 0 exponents (e.g. 0e or 0.0e as supported by SQL Server)
            String s = substring(p, position());
            if (position() == p0)
                s = s + "0";

            parseWhitespaceIf();
            return sign == Sign.MINUS ? -parseDouble(s) : parseDouble(s);
        }
        else {
            String s = substring(p, position());
            parseWhitespaceIf();

            if (decimal)
                return sign == Sign.MINUS ? new BigDecimal(s).negate() : new BigDecimal(s);

            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);
            }
        }
    }

    private void parseDigits() {
        for (;;) {
            char c = character();

            if (c >= '0' && c <= '9')
                positionInc();
            else
                break;
        }
    }

    private final Field parseZeroOne() {
        if (parseIf('0'))
            return zero();
        else if (parseIf('1'))
            return one();
        else
            throw expected("0 or 1");
    }

    private final Field 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");
    }

    @Override
    public final Long parseSignedIntegerLiteral() {
        Long result = parseSignedIntegerLiteralIf();

        if (result == null)
            throw expected("Signed integer");

        return result;
    }

    @Override
    public final Long parseSignedIntegerLiteralIf() {
        Sign sign = parseSign();
        Long unsigned;

        if (sign == Sign.MINUS)
            unsigned = parseUnsignedIntegerLiteral();
        else
            unsigned = parseUnsignedIntegerLiteralIf();

        return unsigned == null
             ? null
             : sign == Sign.MINUS
             ? -unsigned
             : unsigned;
    }

    private final  List parseList(char separator, Function element) {
        return parseList(c -> c.parseIf(separator), element);
    }

    @Override
    public final  List parseList(String separator, Function element) {
        return parseList(c -> c.parseIf(separator), element);
    }

    @Override
    public final  List parseList(Predicate separator, Function element) {
        List result = new ArrayList<>();

        do
            result.add(element.apply(this));
        while (separator.test(this));

        return result;
    }

    private final  List parseUniqueList(String objectType, char separator, Function element) {
        return parseUniqueList(objectType, c -> c.parseIf(separator), element);
    }

    private final  List parseUniqueList(String objectType, String separator, Function element) {
        return parseUniqueList(objectType, c -> c.parseIf(separator), element);
    }

    private final  List parseUniqueList(String objectType, Predicate separator, Function element) {
        Set result = new LinkedHashSet<>();

        do
            if (!result.add(element.apply(this)))
                throw exception("Duplicate " + objectType + " encountered: ");
        while (separator.test(this));

        return new ArrayList<>(result);
    }

    @Override
    public final  T parseParenthesised(Function content) {
        return parseParenthesised('(', content, ')');
    }

    @Override
    public final  T parseParenthesised(char open, Function content, char close) {
        parse(open);
        T result = content.apply(this);
        parse(close);

        return result;
    }

    @Override
    public final  T parseParenthesised(String open, Function content, String close) {
        parse(open);
        T result = content.apply(this);
        parse(close);

        return result;
    }

    private final Field parseUnsignedIntegerOrBindVariable() {
        Long i = parseUnsignedIntegerLiteralIf();

        if (i != null)
            return DSL.inline(i);

        Field f = parseBindVariableIf();
        if (f != null)
            return (Field) f;

        throw expected("Unsigned integer or bind variable");
    }

    @Override
    public final Long parseUnsignedIntegerLiteral() {
        Long result = parseUnsignedIntegerLiteralIf();

        if (result == null)
            throw expected("Unsigned integer literal");

        return result;
    }

    @Override
    public final Long parseUnsignedIntegerLiteralIf() {
        int p = position();
        parseDigits();

        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("ANY_VALUE"))
                    return ComputationalOperation.ANY_VALUE;
                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;
                else if (parseFunctionNameIf("MUL"))
                    return ComputationalOperation.PRODUCT;

                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", "STDEVP", "STDDEV_POP"))
                    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 Comparator parseComparatorIf() {










        if (parseIf("=") || parseKeywordIf("EQ"))
            return Comparator.EQUALS;
        else if (parseIf("!=") || parseIf("<>") || parseIf("^=") || parseKeywordIf("NE"))
            return Comparator.NOT_EQUALS;
        else if (parseIf(">=") || parseKeywordIf("GE"))
            return Comparator.GREATER_OR_EQUAL;
        else if (parseIf(">") || parseKeywordIf("GT"))
            return Comparator.GREATER;

        // MySQL DISTINCT operator
        else if (parseIf("<=>"))
            return Comparator.IS_NOT_DISTINCT_FROM;
        else if (parseIf("<=") || parseKeywordIf("LE"))
            return Comparator.LESS_OR_EQUAL;
        else if (parseIf("<") || parseKeywordIf("LT"))
            return Comparator.LESS;

        return null;
    }

    private 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;
    }

    @Override
    public final boolean parse(String string) {
        boolean result = parseIf(string);

        if (!result)
            throw expected(string);

        return result;
    }

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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 peekFunctionNameIf(String name) {
        return peekKeyword(name, false, false, true);
    }

    private final boolean parseProFunctionNameIf(String name) {
        return !ignoreProEdition() && parseFunctionNameIf(name) && requireProEdition();
    }

    private final boolean parseProFunctionNameIf(String name1, String name2) {
        return !ignoreProEdition() && parseFunctionNameIf(name1, name2) && requireProEdition();
    }

    private final boolean parseProFunctionNameIf(String name1, String name2, String name3) {
        return !ignoreProEdition() && parseFunctionNameIf(name1, name2, name3) && requireProEdition();
    }

    private final boolean parseProFunctionNameIf(String... names) {
        return !ignoreProEdition() && parseFunctionNameIf(names) && requireProEdition();
    }

    @Override
    public 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);
    }

    @Override
    public final boolean parseFunctionNameIf(String... names) {
        return anyMatch(names, n -> parseFunctionNameIf(n));
    }

    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;
    }

    @Override
    public final boolean parseKeyword(String keyword) {
        if (!parseKeywordIf(keyword))
            throw expected("Keyword '" + keyword + "'");

        return true;
    }

    private final boolean parseKeyword(String keyword1, String keyword2) {
        if (parseKeywordIf(keyword1, keyword2))
            return true;

        throw expected(keyword1, keyword2);
    }

    private final boolean parseKeyword(String keyword1, String keyword2, String keyword3) {
        if (parseKeywordIf(keyword1, keyword2, keyword3))
            return true;

        throw expected(keyword1, keyword2, keyword3);
    }

    @Override
    public final boolean parseKeywordIf(String keyword) {
        return peekKeyword(keyword, true, false, false);
    }

    private final boolean parseKeywordIf(String keyword1, String keyword2) {
        return parseKeywordIf(keyword1) || parseKeywordIf(keyword2);
    }

    private final boolean parseKeywordIf(String keyword1, String keyword2, String keyword3) {
        return parseKeywordIf(keyword1) || parseKeywordIf(keyword2) || parseKeywordIf(keyword3);
    }

    @Override
    public final boolean parseKeywordIf(String... keywords) {
        return anyMatch(keywords, k -> parseKeywordIf(k));
    }

    @Override
    public 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) {
        return Tools.findAny(keywords, k -> parseKeywordIf(k), k -> keyword(k.toLowerCase()));
    }

    private final Keyword parseAndGetKeywordIf(String keyword) {
        if (parseKeywordIf(keyword))
            return keyword(keyword.toLowerCase());

        return null;
    }

    @Override
    public final boolean peek(char c) {
        return character() == c;
    }

    @Override
    public 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;
    }

    @Override
    public final boolean peekKeyword(String... keywords) {
        return anyMatch(keywords, k -> peekKeyword(k));
    }

    @Override
    public final boolean peekKeyword(String keyword) {
        return peekKeyword(keyword, false, false, false);
    }

    private final boolean peekKeyword(String keyword1, String keyword2) {
        return peekKeyword(keyword1) || peekKeyword(keyword2);
    }

    private final boolean peekKeyword(String keyword1, String keyword2, String keyword3) {
        return peekKeyword(keyword1) || peekKeyword(keyword2) || peekKeyword(keyword3);
    }

    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(character(pos)))
                        return false;

                    skip = skip + (afterWhitespace(pos) - pos - 1);
                    break;

                default:
                    if (upper(character(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() {
        positionBeforeWhitespace = position();
        position(afterWhitespace(positionBeforeWhitespace));
        return positionBeforeWhitespace != 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(parseDialect())) {

                        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 enum TruthValue {
        T_TRUE,
        T_FALSE,
        T_NULL
    }

    private enum ComputationalOperation {
        ANY_VALUE,
        AVG,
        MAX,
        MIN,
        SUM,
        PRODUCT,
        EVERY,
        ANY,
        SOME,
        COUNT,
        STDDEV_POP,
        STDDEV_SAMP,
        VAR_POP,
        VAR_SAMP,
        MEDIAN,
//        COLLECT,
//        FUSION,
//        INTERSECTION;
    }

    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 = {
        "CROSS APPLY",
        "CROSS JOIN",
        "FULL JOIN",
        "FULL OUTER JOIN",
        "INNER JOIN",
        "JOIN",
        "LEFT ANTI JOIN",
        "LEFT JOIN",
        "LEFT OUTER JOIN",
        "LEFT SEMI JOIN",
        "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",
        "ON",
        "OUTER APPLY",
        "PARTITION BY",
        "RIGHT ANTI JOIN",
        "RIGHT JOIN",
        "RIGHT OUTER JOIN",
        "RIGHT SEMI JOIN",
        "STRAIGHT_JOIN",
        "USING"
    };

    private static final String[] KEYWORDS_IN_SELECT_FROM;

    static {
        Set set = new TreeSet<>(asList(KEYWORDS_IN_FROM));

        set.addAll(asList(
            "CONNECT BY",
            "CREATE",
            "EXCEPT",
            "FETCH FIRST",
            "FETCH NEXT",
            "FOR JSON",
            "FOR KEY SHARE",
            "FOR NO KEY UPDATE",
            "FOR SHARE",
            "FOR UPDATE",
            "FOR XML",
            "GROUP BY",
            "HAVING",
            "INTERSECT",
            "INTO",
            "LIMIT",
            "MINUS",
            "OFFSET",
            "ORDER BY",
            "QUALIFY",
            "RETURNING",
            "ROWS",
            "START WITH",
            "UNION",
            "WHERE",
            "WINDOW"
        ));

        KEYWORDS_IN_SELECT_FROM = set.toArray(EMPTY_STRING);
    }

    private static final String[] KEYWORDS_IN_UPDATE_FROM;

    static {
        Set set = new TreeSet<>(asList(KEYWORDS_IN_FROM));
        set.addAll(asList("FROM", "SET", "WHERE", "ORDER BY", "LIMIT", "RETURNING"));
        KEYWORDS_IN_UPDATE_FROM = set.toArray(EMPTY_STRING);
    }

    private static final String[] KEYWORDS_IN_DELETE_FROM;

    static {
        Set set = new TreeSet<>(asList(KEYWORDS_IN_FROM));
        set.addAll(asList("FROM", "USING", "ALL", "WHERE", "ORDER BY", "LIMIT", "RETURNING"));
        set.addAll(asList(KEYWORDS_IN_STATEMENTS));
        KEYWORDS_IN_DELETE_FROM = set.toArray(EMPTY_STRING);
    }

    private static final String[] PIVOT_KEYWORDS      = {
        "FOR"
    };

    private static final Lazy IGNORE              = Lazy.of(() -> new IgnoreQuery());
    private static final Lazy    IGNORE_NO_DELIMITER = Lazy.of(() -> new IgnoreQuery());

    static final class IgnoreQuery extends AbstractDDLQuery implements UEmpty {
        final String sql;

        IgnoreQuery() {
            this("/* ignored */");
        }

        IgnoreQuery(String sql) {
            super(CONFIG.get());

            this.sql = sql;
        }

        @Override
        public void accept(Context ctx) {
            ctx.sql(sql);
        }
    }



    private final DSLContext            dsl;
    private final Locale                locale;
    private final Meta                  meta;
    private char[]                      sql;
    private final ParseWithMetaLookups  metaLookups;
    private boolean                     metaLookupsForceIgnore;
    private final Consumer>    bindParamListener;
    private int                         positionBeforeWhitespace;
    private int                         position        = 0;
    private boolean                     ignoreHints     = true;
    private final Object[]              bindings;
    private int                         bindIndex       = 0;
    private final Map> bindParams      = new LinkedHashMap<>();
    private String                      delimiter       = ";";
    private boolean                     delimiterRequired = false;
    private LanguageContext             languageContext = LanguageContext.QUERY;
    private EnumSet    forbidden       = EnumSet.noneOf(FunctionKeyword.class);
    private ParseScope                  scope           = new ParseScope();





    /**
     * Keywords that can appear as syntactic tokens in functions are forbidden
     * in non-parenthesised expressions passed as function arguments.
     */
    enum FunctionKeyword {
        FK_AND,
        FK_IN
    }

    DefaultParseContext(
        DSLContext dsl,
        Meta meta,
        ParseWithMetaLookups metaLookups,
        String sqlString,
        Object[] bindings
    ) {
        super(dsl.configuration());

        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>) dsl.configuration().data("org.jooq.parser.param-collector");
        this.delimiterRequired = TRUE.equals(dsl.configuration().data("org.jooq.parser.delimiter-required"));




        parseWhitespaceIf();
    }

    @Override
    public final SQLDialect parseDialect() {
        SQLDialect result = settings().getParseDialect();

        if (result == null)
            result = SQLDialect.DEFAULT;

        return result;
    }

    @Override
    public final SQLDialect parseFamily() {
        return parseDialect().family();
    }

    @Override
    public final LanguageContext languageContext() {
        return languageContext;
    }

    private final ParseWithMetaLookups metaLookups() {
        if (metaLookupsForceIgnore())
            return ParseWithMetaLookups.OFF;






        else
            return this.metaLookups;
    }

    private final boolean metaLookupsForceIgnore() {
        return this.metaLookupsForceIgnore;
    }

    private final DefaultParseContext metaLookupsForceIgnore(boolean m) {
        this.metaLookupsForceIgnore = m;
        return this;
    }

    private final boolean proEdition() {
        return configuration().commercial();
    }

    private final boolean ignoreProEdition() {
        return !proEdition() && TRUE.equals(settings().isParseIgnoreCommercialOnlyFeatures());
    }

    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 double parseDouble(String string) {
        try {
            return Double.parseDouble(string);
        }
        catch (NumberFormatException e) {
            throw expected("Double literal");
        }
    }

    private final int parseInt(String string) {
        try {
            return Integer.parseInt(string);
        }
        catch (NumberFormatException e) {
            throw expected("Integer literal");
        }
    }

    private final int parseInt(String string, int base) {
        try {
            return Integer.parseInt(string, base);
        }
        catch (NumberFormatException e) {
            throw expected("Integer literal of base: " + base);
        }
    }

    private final String substring(int startPosition, int endPosition) {
        startPosition = Math.max(0, startPosition);
        endPosition = Math.min(sql.length, endPosition);
        return new String(sql, startPosition, Math.min(endPosition - startPosition, sql.length - 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"));
    }

    @Override
    public 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());
    }

    @Override
    public final char character() {
        return character(position);
    }

    @Override
    public 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);
    }

    @Override
    public final char[] characters() {
        return sql;
    }

    @Override
    public final ParseContext characters(char[] newCharacters) {
        this.sql = newCharacters;
        return this;
    }

    @Override
    public final int position() {
        return position;
    }

    @Override
    public 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 isIdentifierStart() {
        return isIdentifierStart(character());
    }

    private final boolean isIdentifierStart(int pos) {
        return isIdentifierStart(character(pos));
    }

    private final boolean isIdentifierStart(char character) {
        return Character.isJavaIdentifierStart(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 done(String message, Q result) {
        if (done())
            return notify(result);
        else
            throw exception(message);
    }

    private final  Q wrap(Supplier supplier) {
        ParserException suppressed = null;

        try {
            return supplier.get();
        }
        catch (ParserException e) {
            throw suppressed = e;
        }
        finally {











        }
    }

    private final  Q notify(Q result) {
        if (bindParamListener != null) {
            final Map> 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().deriveAppending(onVisitStart(ctx -> {
                if (ctx.queryPart() instanceof Param p) {
                    if (!p.isInline()) {
                        String name = p.getParamName();

                        if (name == null)
                            name = "" + ctx.context().peekIndex();

                        if (!params.containsKey(name))
                            params.put(name, p);
                    }
                }
            })).dsl().render(result);

            for (String name : bindParams.keySet())
                bindParamListener.accept(params.get(name));
        }

        return result;
    }

    @SuppressWarnings("unused")
    private final boolean asTrue(Object o) {
        return true;
    }

    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  T newScope(Supplier scoped) {
        ParseScope old = scope;

        try {
            scope = new ParseScope();
            return scoped.get();
        }
        finally {
            scope = old;
        }
    }

    private class ParseScope {
        private boolean                                        scopeClear               = false;
        private final ScopeStack>               tableScope               = new ScopeStack<>();
        private final ScopeStack>               fieldScope               = new ScopeStack<>();
        private final ScopeStack lookupQualifiedAsterisks = new ScopeStack<>();
        private final ScopeStack>          lookupFields             = new ScopeStack<>();





        private final Table scope(Table table) {
            tableScope.set(table.getQualifiedName(), table);
            return table;
        }

        private final Field scope(Field field) {
            fieldScope.set(field.getQualifiedName(), field);
            return field;
        }

        private final void scopeResolve() {
            if (!lookupFields.isEmpty())
                unknownField(lookupFields.iterator().next());
            if (!lookupQualifiedAsterisks.isEmpty())
                unknownTable(lookupQualifiedAsterisks.iterator().next());
        }

        private final void scopeStart() {
            tableScope.scopeStart();
            fieldScope.scopeStart();
            lookupFields.scopeStart();
            lookupFields.setAll(null);
            lookupQualifiedAsterisks.scopeStart();
            lookupQualifiedAsterisks.setAll(null);
        }

        private final void scopeEnd(Query scopeOwner) {
            List> fields = new ArrayList<>();

            // [#14372] Avoid looking up tables at a higher scope level
            lookupLoop:
            for (QualifiedAsteriskProxy lookup : scope.lookupQualifiedAsterisks.iterableAtScopeLevel()) {
                for (Table t : scope.tableScope) {

                    // [#15056] TODO: Could there be an ambiguity, as with fields?
                    if (t.getName().equals(lookup.$table().getName())) {
                        lookup.delegate((QualifiedAsteriskImpl) t.asterisk());
                        continue lookupLoop;
                    }
                }

                // [#15056] TODO: Should we support references to higher scopes?
                unknownTable(lookup);
            }

            // [#14372] Avoid looking up fields at a higher scope level
            for (FieldProxy lookup : scope.lookupFields.iterableAtScopeLevel()) {
                Value> found = null;

                for (Field f : scope.fieldScope) {
                    if (f.getName().equals(lookup.getName())) {
                        if (found != null) {
                            position(lookup.position());
                            throw exception("Ambiguous field identifier");
                        }

                        // TODO: Does this instance of "found" really interact with the one below?
                        found = new Value<>(0, f);
                    }
                }

                found = resolveInTableScope(scope.tableScope.valueIterable(), lookup.getQualifiedName(), lookup, found);

                if (found != null && !(found.value() instanceof FieldProxy)) {
                    lookup.delegate((AbstractField) found.value());
                }
                else {
                    lookup.scopeOwner(scopeOwner);
                    fields.add(lookup);
                }
            }

            scope.lookupQualifiedAsterisks.scopeEnd();
            scope.lookupFields.scopeEnd();
            scope.tableScope.scopeEnd();
            scope.fieldScope.scopeEnd();

            for (FieldProxy r : fields)
                if (scope.lookupFields.get(r.getQualifiedName()) == null)
                    if (scope.lookupFields.inScope())
                        scope.lookupFields.set(r.getQualifiedName(), r);
                    else
                        unknownField(r);
        }

        private final void scopeClear() {
            scopeClear = true;
        }

        private final void unknownField(FieldProxy field) {
            if (!scopeClear) {






























                if (metaLookups() == THROW_ON_FAILURE) {
                    position(field.position());
                    throw exception("Unknown field identifier");
                }
            }
        }

        private final void unknownTable(QualifiedAsteriskProxy asterisk) {
            if (!scopeClear) {
                if (metaLookups() == THROW_ON_FAILURE) {
                    position(asterisk.position());
                    throw exception("Unknown table identifier");
                }
            }
        }
    }

    private final Value> resolveInTableScope(Iterable>> tables, Name lookupName, FieldProxy lookup, Value> found) {

        tableScopeLoop:
        for (Value> t : tables) {
            Value> f;

            if (t.value() instanceof JoinTable j) {
                found = resolveInTableScope(
                    asList(
                        new Value<>(t.scopeLevel(), j.lhs),
                        new Value<>(t.scopeLevel(), j.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 Table lookupTable(int positionBeforeName, Name name) {
        if (meta != null) {
            List> 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);
        }

        // [#16762] It should always be possible to lookup the DUAL pseudo table
        if (Dual.isDual(name))
            return dual();

        if (metaLookups() == THROW_ON_FAILURE) {
            position(positionBeforeName);
            throw exception("Unknown table identifier");
        }

        return table(name);
    }

    private final QualifiedAsterisk lookupQualifiedAsterisk(int positionBeforeName, Name name) {
        if (metaLookups() == ParseWithMetaLookups.OFF || scope.lookupQualifiedAsterisks.scopeLevel() < 0)
            return table(name).asterisk();

        QualifiedAsteriskProxy asterisk = scope.lookupQualifiedAsterisks.get(name);
        if (asterisk == null)
            scope.lookupQualifiedAsterisks.set(name, asterisk = new QualifiedAsteriskProxy((QualifiedAsteriskImpl) table(name).asterisk(), positionBeforeName));

        return asterisk;
    }

    private final Field lookupField(int positionBeforeName, Name name) {
        if (metaLookups() == ParseWithMetaLookups.OFF || scope.lookupFields.scopeLevel() < 0)
            return field(name);

        FieldProxy field = scope.lookupFields.get(name);
        if (field == null)
            scope.lookupFields.set(name, field = new FieldProxy<>((AbstractField) field(name), positionBeforeName));

        return field;
    }

    @Override
    public String toString() {
        return mark();
    }

    public final  T data(Object key, Object value, Function function) {
        Object previous = data(key, value);

        try {
            return function.apply(this);
        }
        finally {
            if (previous == null)
                data().remove(key);
            else
                data(key, previous);
        }
    }
}