16372 lines
482 KiB
Java
16372 lines
482 KiB
Java
/*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* Other licenses:
|
|
* -----------------------------------------------------------------------------
|
|
* Commercial licenses for this work are available. These replace the above
|
|
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
|
|
* database integrations.
|
|
*
|
|
* For more information, please visit: https://www.jooq.org/legal/licensing
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
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.JoinType.JOIN;
|
|
// ...
|
|
// ...
|
|
// ...
|
|
// ...
|
|
// ...
|
|
// ...
|
|
// ...
|
|
import static org.jooq.SQLDialect.MARIADB;
|
|
// ...
|
|
import static org.jooq.SQLDialect.MYSQL;
|
|
// ...
|
|
// ...
|
|
import static org.jooq.SQLDialect.SQLITE;
|
|
// ...
|
|
// ...
|
|
// ...
|
|
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.and;
|
|
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.arrayAppend;
|
|
import static org.jooq.impl.DSL.arrayConcat;
|
|
import static org.jooq.impl.DSL.arrayGet;
|
|
import static org.jooq.impl.DSL.arrayOverlap;
|
|
import static org.jooq.impl.DSL.arrayPrepend;
|
|
import static org.jooq.impl.DSL.arrayRemove;
|
|
import static org.jooq.impl.DSL.arrayReplace;
|
|
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.binaryBitLength;
|
|
import static org.jooq.impl.DSL.binaryLength;
|
|
import static org.jooq.impl.DSL.binaryLtrim;
|
|
import static org.jooq.impl.DSL.binaryMd5;
|
|
import static org.jooq.impl.DSL.binaryOctetLength;
|
|
import static org.jooq.impl.DSL.binaryOverlay;
|
|
import static org.jooq.impl.DSL.binaryRtrim;
|
|
import static org.jooq.impl.DSL.binaryTrim;
|
|
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.case_;
|
|
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.falseCondition;
|
|
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.jsonArrayAggDistinct;
|
|
import static org.jooq.impl.DSL.jsonExists;
|
|
import static org.jooq.impl.DSL.jsonGetAttribute;
|
|
import static org.jooq.impl.DSL.jsonGetAttributeAsText;
|
|
import static org.jooq.impl.DSL.jsonGetElement;
|
|
import static org.jooq.impl.DSL.jsonGetElementAsText;
|
|
import static org.jooq.impl.DSL.jsonKeyExists;
|
|
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.jsonbArrayAggDistinct;
|
|
import static org.jooq.impl.DSL.jsonbGetAttribute;
|
|
import static org.jooq.impl.DSL.jsonbGetAttributeAsText;
|
|
import static org.jooq.impl.DSL.jsonbGetElement;
|
|
import static org.jooq.impl.DSL.jsonbGetElementAsText;
|
|
import static org.jooq.impl.DSL.jsonbKeyExists;
|
|
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.lambda;
|
|
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.listAggDistinct;
|
|
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.maxBy;
|
|
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.minBy;
|
|
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.or;
|
|
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.trueCondition;
|
|
import static org.jooq.impl.DSL.trunc;
|
|
import static org.jooq.impl.DSL.tryCast;
|
|
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.xor;
|
|
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.Internal.iadd;
|
|
import static org.jooq.impl.Internal.isub;
|
|
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.map;
|
|
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.CreateSequenceAsStep;
|
|
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.JSON;
|
|
import org.jooq.JSONArrayAggNullStep;
|
|
import org.jooq.JSONArrayAggOrderByStep;
|
|
import org.jooq.JSONArrayAggReturningStep;
|
|
import org.jooq.JSONArrayNullStep;
|
|
import org.jooq.JSONArrayReturningStep;
|
|
import org.jooq.JSONEntry;
|
|
import org.jooq.JSONObjectAggNullStep;
|
|
import org.jooq.JSONObjectAggReturningStep;
|
|
import org.jooq.JSONObjectNullStep;
|
|
import org.jooq.JSONObjectReturningStep;
|
|
import org.jooq.JSONTableColumnPathStep;
|
|
import org.jooq.JSONTableColumnsStep;
|
|
import org.jooq.JSONValueDefaultStep;
|
|
import org.jooq.JSONValueOnStep;
|
|
import org.jooq.JoinType;
|
|
import org.jooq.Keyword;
|
|
// ...
|
|
import org.jooq.Lambda1;
|
|
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.OptionallyOrderedAggregateFunction;
|
|
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.SQLDialectCategory;
|
|
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.TableOuterJoinStep;
|
|
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.JoinHint;
|
|
// ...
|
|
import org.jooq.impl.QOM.UEmpty;
|
|
import org.jooq.impl.QOM.XMLPassingMechanism;
|
|
import org.jooq.impl.ScopeStack.Value;
|
|
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<SQLDialect> SUPPORTS_HASH_COMMENT_SYNTAX = SQLDialect.supportedBy(MARIADB, MYSQL);
|
|
|
|
final Queries parse() {
|
|
return wrap(() -> {
|
|
List<Query> 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<Query> 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 (isDDLDatabase()) {
|
|
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;
|
|
}
|
|
|
|
private boolean isDDLDatabase() {
|
|
return TRUE.equals(configuration().data("org.jooq.ddl.parse-for-ddldatabase"));
|
|
}
|
|
|
|
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 WORK", "BEGIN TRANSACTION", "BEGIN TRAN"))
|
|
return result = parseStartTransaction();
|
|
else if (!parseResultQuery && parseKeywordIf("BT"))
|
|
return dsl.startTransaction();
|
|
else 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, false);
|
|
else if (!ignoreProEdition() && peekKeyword("CALL") && requireProEdition())
|
|
|
|
|
|
|
|
;
|
|
else if (!parseResultQuery && peekKeyword("COMMIT"))
|
|
return result = parseCommit();
|
|
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", "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())
|
|
|
|
|
|
|
|
;
|
|
else if (!parseResultQuery && parseKeywordIf("ET", "END TRANSACTION"))
|
|
return dsl.commit();
|
|
|
|
break;
|
|
|
|
case 'G':
|
|
if (!parseResultQuery && peekKeyword("GRANT"))
|
|
return result = metaLookupsForceIgnore(true).parseGrant();
|
|
|
|
break;
|
|
|
|
case 'I':
|
|
if (!parseSelect && (peekKeyword("INSERT", "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 (!parseResultQuery && peekKeyword("RELEASE"))
|
|
return result = parseReleaseSavepoint();
|
|
else if (!parseResultQuery && peekKeyword("ROLLBACK"))
|
|
return result = parseRollback();
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
if (peekSelect(false))
|
|
return result = parseSelect();
|
|
else if (!parseResultQuery && peekKeyword("SET"))
|
|
return result = parseSet();
|
|
else if (!parseResultQuery && peekKeyword("SAVE", "SAVEPOINT"))
|
|
return result = parseSavepoint();
|
|
else if (!parseResultQuery && peekKeyword("START"))
|
|
return result = parseStartTransaction();
|
|
|
|
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", "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<CommonTableExpression<?>> cte = new ArrayList<>();
|
|
do {
|
|
if (parseKeywordIf("FUNCTION"))
|
|
throw notImplemented("WITH FUNCTION");
|
|
|
|
Name name = parseIdentifier();
|
|
DerivedColumnList dcl = null;
|
|
|
|
if (parseIf('(')) {
|
|
List<Name> columnNames = parseIdentifiers();
|
|
parse(')');
|
|
dcl = name.fields(columnNames.toArray(EMPTY_NAME));
|
|
}
|
|
|
|
parseKeyword("AS");
|
|
boolean materialized = parseKeywordIf("MATERIALIZED");
|
|
boolean notMaterialized = !materialized && parseKeywordIf("NOT MATERIALIZED");
|
|
parse('(');
|
|
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", "DEL")))
|
|
result = parseDelete(with, false);
|
|
else if (!parseSelect && (peekKeyword("INSERT", "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", "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<Record> 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<Record> parseWithOrSelect() {
|
|
return parseWithOrSelect(null);
|
|
}
|
|
|
|
private final SelectQueryImpl<Record> parseWithOrSelect(Integer degree) {
|
|
return peekKeyword("WITH") ? (SelectQueryImpl<Record>) parseWith(true, degree) : parseSelect(degree, null);
|
|
}
|
|
|
|
private final SelectQueryImpl<Record> parseSelect() {
|
|
return parseSelect(null, null);
|
|
}
|
|
|
|
private final SelectQueryImpl<Record> parseSelect(Integer degree, WithImpl with) {
|
|
scope.scopeStart();
|
|
SelectQueryImpl<Record> result = parseQueryExpressionBody(degree, with, null);
|
|
List<SortField<?>> orderBy = null;
|
|
|
|
for (Field<?> field : result.getSelect())
|
|
if (aliased(field) != null)
|
|
scope.scope(field);
|
|
|
|
if (parseKeywordIf("ORDER")) {
|
|
if (parseProKeywordIf("SIBLINGS BY")) {
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (parseKeywordIf("BY"))
|
|
result.addOrderBy(orderBy = parseList(',', c -> c.parseSortField()));
|
|
else
|
|
throw expected("SIBLINGS BY", "BY");
|
|
}
|
|
|
|
boolean limit = false;
|
|
boolean for_ = false;
|
|
boolean offset = false;
|
|
|
|
if (orderBy != null && parseKeywordIf("SEEK")) {
|
|
boolean before = parseKeywordIf("BEFORE");
|
|
if (!before)
|
|
parseKeywordIf("AFTER");
|
|
|
|
List<Field<?>> 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);
|
|
|
|
offset = true;
|
|
}
|
|
|
|
while ((!limit && (limit = parseSelectLimit(result, offset)))
|
|
|| (!for_ && (for_ = parseSelectFor(result))))
|
|
;
|
|
|
|
if (parseKeywordIf("WITH CHECK OPTION"))
|
|
result.setWithCheckOption();
|
|
else if (parseKeywordIf("WITH READ ONLY"))
|
|
result.setWithReadOnly();
|
|
|
|
scope.scopeEnd(result);
|
|
return result;
|
|
}
|
|
|
|
private final boolean parseSelectLimit(SelectQueryImpl<Record> result, boolean offset) {
|
|
boolean limit = result.getLimit().isApplicable();
|
|
|
|
if (!limit)
|
|
parseLimit(result, !offset);
|
|
|
|
return limit;
|
|
}
|
|
|
|
private final boolean parseSelectFor(SelectQueryImpl<Record> result) {
|
|
boolean for_;
|
|
|
|
forClause:
|
|
if (for_ = 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 (parseProKeywordIf("XML")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (!ignoreProEdition() && (jsonb = parseKeywordIf("JSONB", "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 (parseProKeywordIf("WAIT"))
|
|
|
|
|
|
|
|
;
|
|
else if (parseKeywordIf("SKIP LOCKED"))
|
|
result.setForUpdateSkipLocked();
|
|
}
|
|
|
|
return for_;
|
|
}
|
|
|
|
private final void parseLimit(SelectQueryImpl<Record> result, boolean allowOffset) {
|
|
boolean offsetStandard = false;
|
|
boolean offsetPostgres = false;
|
|
|
|
if (allowOffset && parseKeywordIf("OFFSET")) {
|
|
result.addOffset((Field) parseField());
|
|
|
|
if (parseKeywordIf("ROWS", "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<Long> limit = (Field) parseField();
|
|
|
|
if (offsetPostgres) {
|
|
result.addLimit(limit);
|
|
|
|
if (parseKeywordIf("PERCENT"))
|
|
result.setLimitPercent(true);
|
|
|
|
if (parseKeywordIf("WITH TIES"))
|
|
result.setWithTies(true);
|
|
}
|
|
else if (allowOffset && parseIf(',')) {
|
|
result.addLimit(limit, (Field) parseField());
|
|
}
|
|
else {
|
|
if (parseKeywordIf("PERCENT"))
|
|
result.setLimitPercent(true);
|
|
|
|
if (parseKeywordIf("WITH TIES"))
|
|
result.setWithTies(true);
|
|
|
|
if (allowOffset && 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<Record> parseQueryExpressionBody(Integer degree, WithImpl with, SelectQueryImpl<Record> prefix) {
|
|
SelectQueryImpl<Record> lhs = parseQueryTerm(degree, with, prefix);
|
|
SelectQueryImpl<Record> local = lhs;
|
|
|
|
CombineOperator combine;
|
|
while ((combine = parseCombineOperatorIf(false)) != null) {
|
|
scope.scopeEnd(local);
|
|
scope.scopeStart();
|
|
|
|
if (degree == null)
|
|
degree = Tools.degree(lhs);
|
|
|
|
SelectQueryImpl<Record> 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<Record> parseQueryTerm(Integer degree, WithImpl with, SelectQueryImpl<Record> prefix) {
|
|
SelectQueryImpl<Record> lhs = prefix != null ? prefix : parseQueryPrimary(degree, with);
|
|
SelectQueryImpl<Record> local = lhs;
|
|
|
|
CombineOperator combine;
|
|
while ((combine = parseCombineOperatorIf(true)) != null) {
|
|
scope.scopeEnd(local);
|
|
scope.scopeStart();
|
|
|
|
if (degree == null)
|
|
degree = Tools.degree(lhs);
|
|
|
|
SelectQueryImpl<Record> 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<Record> degreeCheck(int expected, SelectQueryImpl<Record> s) {
|
|
if (expected == 0)
|
|
return s;
|
|
|
|
int actual = Tools.degree(s);
|
|
if (actual == 0)
|
|
return s;
|
|
|
|
if (expected != actual)
|
|
throw exception("Select list must contain " + expected + " columns. Got: " + actual);
|
|
|
|
return s;
|
|
}
|
|
|
|
private final SelectQueryImpl<Record> parseQueryPrimary(Integer degree, WithImpl with) {
|
|
if (parseIf('(')) {
|
|
SelectQueryImpl<Record> result = parseSelect(degree, with);
|
|
parse(')');
|
|
return result;
|
|
}
|
|
|
|
if (peekKeyword("VALUES"))
|
|
return (SelectQueryImpl<Record>) dsl.selectQuery(parseTableValueConstructor());
|
|
else if (peekKeyword("TABLE"))
|
|
return (SelectQueryImpl<Record>) dsl.selectQuery(parseExplicitTable());
|
|
|
|
ignoreHints(false);
|
|
parseKeywordUndocumentedAlternatives("SELECT", "SEL");
|
|
String hints = parseHints();
|
|
boolean distinct = parseKeywordIf("DISTINCT", "UNIQUE");
|
|
List<Field<?>> distinctOn = null;
|
|
|
|
if (distinct) {
|
|
if (parseKeywordIf("ON")) {
|
|
parse('(');
|
|
distinctOn = parseList(',', c -> c.parseField());
|
|
parse(')');
|
|
}
|
|
}
|
|
else
|
|
parseKeywordIf("ALL");
|
|
|
|
Field<Long> limit = null;
|
|
Field<Long> offset = null;
|
|
boolean percent = false;
|
|
boolean withTies = false;
|
|
|
|
// T-SQL style TOP .. START AT
|
|
if (parseKeywordIf("TOP")) {
|
|
limit = (Field) parseField();
|
|
percent = parseProKeywordIf("PERCENT");
|
|
|
|
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<SelectFieldOrAsterisk> select = parseSelectList();
|
|
|
|
degreeCheck:
|
|
if (degree != null && !degree.equals(0) && !degree.equals(select.size())) {
|
|
for (SelectFieldOrAsterisk s : select)
|
|
if (!(s instanceof Field<?>))
|
|
break degreeCheck;
|
|
|
|
throw exception("Select list must contain " + degree + " columns. Got: " + select.size());
|
|
}
|
|
|
|
Table<?> intoTable = null;
|
|
|
|
|
|
|
|
List<Table<?>> from = null;
|
|
|
|
if (parseKeywordIf("INTO")) {
|
|
if (proEdition()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else
|
|
intoTable = parseTableName();
|
|
}
|
|
|
|
if (parseKeywordIf("FROM")) {
|
|
from = 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<Record> result = new SelectQueryImpl<>(dsl.configuration(), with);
|
|
|
|
if (hints != null)
|
|
result.addHint(hints);
|
|
|
|
if (distinct)
|
|
result.setDistinct(distinct);
|
|
|
|
if (distinctOn != null)
|
|
result.addDistinctOn(distinctOn);
|
|
|
|
if (!select.isEmpty())
|
|
result.addSelect(select);
|
|
|
|
if (intoTable != null)
|
|
result.setInto(intoTable);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (from != null)
|
|
result.addFrom(from);
|
|
|
|
// [#10638] [#11403] Oracle and Teradata seem to support (but not document)
|
|
// arbitrary ordering between these clauses
|
|
boolean where = false;
|
|
boolean connectBy = false;
|
|
boolean startWith = false;
|
|
boolean groupBy = false;
|
|
boolean having = false;
|
|
|
|
while ((!where && (where = parseQueryPrimaryWhere(result)))
|
|
|| (!connectBy && (connectBy = parseQueryPrimaryConnectBy(result)))
|
|
|| (!startWith && (startWith = parseQueryPrimaryStartWith(result)))
|
|
|| (!groupBy && (groupBy = parseQueryPrimaryGroupBy(result)))
|
|
|| (!having && (having = parseQueryPrimaryHaving(result))))
|
|
;
|
|
|
|
if (startWith && !connectBy)
|
|
throw expected("CONNECT BY");
|
|
|
|
if (parseKeywordIf("WINDOW"))
|
|
result.addWindow(parseWindowDefinitions());
|
|
|
|
if (parseKeywordIf("QUALIFY"))
|
|
result.addQualify(parseCondition());
|
|
|
|
if (limit != null)
|
|
if (offset != null)
|
|
result.addLimit(offset, limit);
|
|
else
|
|
result.addLimit(limit);
|
|
|
|
if (percent)
|
|
|
|
|
|
|
|
;
|
|
|
|
if (withTies)
|
|
result.setWithTies(true);
|
|
|
|
return result;
|
|
}
|
|
|
|
private final boolean parseQueryPrimaryWhere(SelectQueryImpl<Record> result) {
|
|
if (parseKeywordIf("WHERE")) {
|
|
result.addConditions(parseCondition());
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
private final boolean parseQueryPrimaryHaving(SelectQueryImpl<Record> result) {
|
|
if (parseKeywordIf("HAVING")) {
|
|
result.addHaving(parseCondition());
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
private final boolean parseQueryPrimaryGroupBy(SelectQueryImpl<Record> result) {
|
|
List<GroupField> groupBy;
|
|
|
|
if (parseKeywordIf("GROUP BY")) {
|
|
if (!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<List<Field<?>>> fieldSets = parseList(',', c -> parseFieldsOrEmptyOptionallyParenthesised(false));
|
|
parse(')');
|
|
result.addGroupBy(groupingSets(fieldSets.toArray((Collection[]) EMPTY_COLLECTION)));
|
|
}
|
|
else {
|
|
groupBy = parseOrdinaryGroupingSets();
|
|
|
|
if (parseKeywordIf("WITH ROLLUP"))
|
|
result.addGroupBy(rollup(groupBy.toArray(EMPTY_FIELD)));
|
|
else
|
|
result.addGroupBy(groupBy);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
private final List<GroupField> parseOrdinaryGroupingSets() {
|
|
List<GroupField> result = new ArrayList<>();
|
|
|
|
do {
|
|
|
|
// [#14159] Explicit ROW expressions are actual RowAsFields.
|
|
// Other parenthesised expressions are grouping column reference lists
|
|
if (peekKeyword("ROW")) {
|
|
result.add(parseField());
|
|
}
|
|
else {
|
|
FieldOrRow fr = parseFieldOrRow();
|
|
|
|
if (fr instanceof Field<?> f)
|
|
result.add(f);
|
|
else
|
|
result.addAll(asList(((Row) fr).fields()));
|
|
}
|
|
}
|
|
while (parseIf(','));
|
|
|
|
return result;
|
|
}
|
|
|
|
private final boolean parseQueryPrimaryConnectBy(SelectQueryImpl<Record> result) {
|
|
if (parseProKeywordIf("CONNECT BY")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
private final boolean parseQueryPrimaryStartWith(SelectQueryImpl<Record> result) {
|
|
if (parseProKeywordIf("START WITH")) {
|
|
|
|
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
private final List<WindowDefinition> 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<Long> 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(parseJoinedTable(() -> peekKeyword(KEYWORD_LOOKUP_IN_DELETE_FROM)));
|
|
DeleteUsingStep<?> s1 = with == null ? dsl.delete(table) : with.delete(table);
|
|
DeleteWhereStep<?> s2 = parseKeywordIf("USING", "FROM") ? s1.using(parseList(',', t -> scope.scope(parseJoinedTable(() -> peekKeyword(KEYWORD_LOOKUP_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();
|
|
parseKeywordUndocumentedAlternatives("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<List<Field<?>>> 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<Field<?>> 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<Field<?>> values : allValues)
|
|
step2 = step2.values(values);
|
|
|
|
returning = onDuplicate = step2;
|
|
}
|
|
else if (parseKeywordIf("SET")) {
|
|
Map<Field<?>, Object> map = parseSetClauseList();
|
|
|
|
returning = onDuplicate = s1.set(map);
|
|
}
|
|
else if (peekSelectOrWith(true)) {
|
|
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) {
|
|
parseKeywordUndocumentedAlternatives("UPDATE", "UPD");
|
|
Field<Long> 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(parseJoinedTable(() -> peekKeyword(KEYWORD_LOOKUP_IN_UPDATE_FROM)));
|
|
UpdateSetFirstStep<?> s1 = (with == null ? dsl.update(table) : with.update(table));
|
|
List<Table<?>> from = parseKeywordIf("FROM") ? parseList(',', t -> scope.scope(parseJoinedTable(() -> peekKeyword(KEYWORD_LOOKUP_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<Field<?>, Object> map = parseSetClauseList();
|
|
s2 = s1.set(map);
|
|
}
|
|
|
|
UpdateWhereStep<?> s3 = from != null
|
|
? s2.from(from)
|
|
: parseKeywordIf("FROM")
|
|
? s2.from(parseList(',', t -> parseJoinedTable(() -> peekKeyword(KEYWORD_LOOKUP_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<Field<?>, Object> parseSetClauseList() {
|
|
Map<Field<?>, Object> map = new LinkedHashMap<>();
|
|
|
|
do {
|
|
Field<?> field = parseFieldName();
|
|
|
|
if (map.containsKey(field))
|
|
throw exception("Duplicate column in set clause list: " + field);
|
|
|
|
parse('=');
|
|
|
|
Field<?> value = parseKeywordIf("DEFAULT") ? default_() : parseField();
|
|
map.put(field, value);
|
|
}
|
|
while (parseIf(','));
|
|
|
|
return map;
|
|
}
|
|
|
|
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;
|
|
List<Field<?>> insertColumns = null;
|
|
List<Field<?>> insertValues = null;
|
|
Condition insertWhere = null;
|
|
Map<Field<?>, 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 = Tools.fieldsByName(parseIdentifiers().toArray(EMPTY_NAME));
|
|
parse(')');
|
|
parseKeyword("VALUES");
|
|
parse('(');
|
|
insertValues = parseList(',', c -> c.parseKeywordIf("DEFAULT") ? default_() : c.parseField());
|
|
parse(')');
|
|
|
|
if (insertColumns.size() != insertValues.size())
|
|
throw exception("Insert column size (" + insertColumns.size() + ") 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());
|
|
}
|
|
else if (parseKeywordIf("MATERIALIZED VIEW")) {
|
|
s1 = dsl.commentOnMaterializedView(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",
|
|
"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 'C':
|
|
if (parseKeywordIf("CACHED TABLE"))
|
|
return parseCreateTable(false);
|
|
|
|
break;
|
|
|
|
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, false);
|
|
else if (parseKeywordIf("FORCE MATERIALIZED VIEW"))
|
|
return parseCreateView(false, true);
|
|
else if (parseKeywordIf("FULLTEXT INDEX") && requireUnsupportedSyntax())
|
|
return parseCreateIndex(false);
|
|
else if (parseProKeywordIf("FUNCTION"))
|
|
|
|
|
|
|
|
;
|
|
|
|
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 'M':
|
|
if (parseKeywordIf("MEMORY TABLE"))
|
|
return parseCreateTable(false);
|
|
else if (parseKeywordIf("MATERIALIZED VIEW"))
|
|
return parseCreateView(false, true);
|
|
|
|
break;
|
|
|
|
case 'O':
|
|
if (parseKeywordIf("OR")) {
|
|
parseKeyword("REPLACE", "ALTER");
|
|
|
|
if (parseProKeywordIf("TRIGGER"))
|
|
|
|
|
|
|
|
;
|
|
else if (parseKeywordIf("VIEW", "FORCE VIEW"))
|
|
return parseCreateView(true, false);
|
|
else if (parseKeywordIf("MATERIALIZED VIEW"))
|
|
return parseCreateView(true, true);
|
|
else if (parseProKeywordIf("FUNCTION"))
|
|
|
|
|
|
|
|
;
|
|
else if (parseKeywordIf("PACKAGE"))
|
|
throw notImplemented("CREATE PACKAGE", "https://github.com/jOOQ/jOOQ/issues/9190");
|
|
else if (parseProKeywordIf("PROC", "PROCEDURE"))
|
|
|
|
|
|
|
|
;
|
|
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 (parseProKeywordIf("PROC", "PROCEDURE"))
|
|
|
|
|
|
|
|
;
|
|
|
|
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 (parseProKeywordIf("TRIGGER"))
|
|
|
|
|
|
|
|
;
|
|
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, 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 'M':
|
|
if (parseKeywordIf("MATERIALIZED VIEW"))
|
|
return parseAlterView(true);
|
|
|
|
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(false);
|
|
|
|
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 (parseProKeywordIf("FUNCTION"))
|
|
|
|
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 'G':
|
|
if (parseKeywordIf("GENERATOR"))
|
|
return parseDropSequence();
|
|
|
|
break;
|
|
|
|
case 'I':
|
|
if (parseKeywordIf("INDEX"))
|
|
return parseDropIndex();
|
|
|
|
break;
|
|
|
|
case 'M':
|
|
if (parseKeywordIf("MATERIALIZED VIEW"))
|
|
return parseDropView(true);
|
|
|
|
break;
|
|
|
|
case 'P':
|
|
if (parseKeywordIf("PACKAGE"))
|
|
throw notImplemented("DROP PACKAGE", "https://github.com/jOOQ/jOOQ/issues/9190");
|
|
else if (parseProKeywordIf("PROC", "PROCEDURE"))
|
|
|
|
|
|
|
|
;
|
|
|
|
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 (parseProKeywordIf("TRIGGER"))
|
|
|
|
|
|
|
|
;
|
|
else if (parseKeywordIf("TYPE")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return parseCascadeRestrictIf(
|
|
parseIfExists(this::parseIdentifiers,
|
|
n -> dsl.dropTypeIfExists(n.toArray(EMPTY_NAME)),
|
|
n -> dsl.dropType(n.toArray(EMPTY_NAME))
|
|
),
|
|
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(false);
|
|
|
|
break;
|
|
}
|
|
|
|
throw expected(
|
|
"GENERATOR",
|
|
"FUNCTION",
|
|
"INDEX",
|
|
"PROCEDURE",
|
|
"SCHEMA",
|
|
"SEQUENCE",
|
|
"TABLE",
|
|
"TEMPORARY TABLE",
|
|
"TRIGGER",
|
|
"TYPE",
|
|
"VIEW"
|
|
);
|
|
}
|
|
|
|
private final Truncate<?> parseTruncate() {
|
|
parseKeyword("TRUNCATE");
|
|
parseKeywordIf("TABLE");
|
|
List<Table<?>> table = parseList(',', ctx -> 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<Privilege> privileges = null;
|
|
|
|
while (parseIf(',')) {
|
|
if (privileges == null) {
|
|
privileges = new ArrayList<>();
|
|
privileges.add(privilege);
|
|
}
|
|
|
|
privileges.add(parsePrivilege());
|
|
}
|
|
|
|
parseKeyword("ON");
|
|
parseKeywordIf("TABLE");
|
|
Table<?> table = parseTableName();
|
|
|
|
parseKeyword("TO");
|
|
User user = parseKeywordIf("PUBLIC") ? null : parseUser();
|
|
|
|
GrantOnStep s1 = privileges == null ? dsl.grant(privilege) : dsl.grant(privileges);
|
|
GrantToStep s2 = s1.on(table);
|
|
GrantWithGrantOptionStep s3 = user == null ? s2.toPublic() : s2.to(user);
|
|
|
|
return parseKeywordIf("WITH GRANT OPTION")
|
|
? s3.withGrantOption()
|
|
: s3;
|
|
}
|
|
|
|
private final DDLQuery parseRevoke() {
|
|
parseKeyword("REVOKE");
|
|
boolean grantOptionFor = parseKeywordIf("GRANT OPTION FOR");
|
|
Privilege privilege = parsePrivilege();
|
|
List<Privilege> privileges = null;
|
|
|
|
while (parseIf(',')) {
|
|
if (privileges == null) {
|
|
privileges = new ArrayList<>();
|
|
privileges.add(privilege);
|
|
}
|
|
|
|
privileges.add(parsePrivilege());
|
|
}
|
|
|
|
parseKeyword("ON");
|
|
parseKeywordIf("TABLE");
|
|
Table<?> table = parseTableName();
|
|
|
|
RevokeOnStep s1 = grantOptionFor
|
|
? privileges == null
|
|
? dsl.revokeGrantOptionFor(privilege)
|
|
: dsl.revokeGrantOptionFor(privileges)
|
|
: privileges == null
|
|
? dsl.revoke(privilege)
|
|
: dsl.revoke(privileges);
|
|
|
|
parseKeyword("FROM");
|
|
User user = parseKeywordIf("PUBLIC") ? null : parseUser();
|
|
|
|
RevokeFromStep s2 = s1.on(table);
|
|
return user == null ? s2.fromPublic() : s2.from(user);
|
|
}
|
|
|
|
private final Query parseExec() {
|
|
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<Statement> statements = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
if (allowDeclareSection && parseProKeywordIf("DECLARE"))
|
|
|
|
|
|
|
|
;
|
|
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<Statement> parseStatements(boolean peek, String... keywords) {
|
|
List<Statement> 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<Statement> parseStatementsAndPeek(String... keywords) {
|
|
return parseStatements(true, keywords);
|
|
}
|
|
|
|
private final List<Statement> parseStatementsAndKeyword(String... keywords) {
|
|
return parseStatements(false, keywords);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final Query parseStartTransaction() {
|
|
parseKeyword("START", "BEGIN");
|
|
parseKeyword("WORK", "TRAN", "TRANSACTION");
|
|
parseKeywordIf("READ WRITE");
|
|
return dsl.startTransaction();
|
|
}
|
|
|
|
private final Query parseSavepoint() {
|
|
if (parseKeywordIf("SAVEPOINT")) {
|
|
Name n = parseIdentifier();
|
|
parseKeywordIf("UNIQUE");
|
|
parseKeywordIf("ON ROLLBACK RETAIN CURSORS");
|
|
return dsl.savepoint(n);
|
|
}
|
|
|
|
parseKeyword("SAVE");
|
|
parseKeyword("TRAN", "TRANSACTION");
|
|
return dsl.savepoint(parseIdentifier());
|
|
}
|
|
|
|
private final Query parseReleaseSavepoint() {
|
|
parseKeyword("RELEASE");
|
|
parseKeywordIf("TO");
|
|
parseKeywordIf("SAVEPOINT");
|
|
return dsl.releaseSavepoint(parseIdentifier());
|
|
}
|
|
|
|
private final Query parseCommit() {
|
|
parseKeyword("COMMIT");
|
|
parseKeywordIf("WORK", "TRAN", "TRANSACTION");
|
|
return dsl.commit();
|
|
}
|
|
|
|
private final Query parseRollback() {
|
|
parseKeyword("ROLLBACK");
|
|
|
|
if (parseKeywordIf(
|
|
"TRAN",
|
|
"TRANSACTION TO SAVEPOINT",
|
|
"TRANSACTION TO",
|
|
"TRANSACTION",
|
|
"WORK TO SAVEPOINT",
|
|
"TO SAVEPOINT",
|
|
"TO"
|
|
))
|
|
return dsl.rollback().toSavepoint(parseIdentifier());
|
|
|
|
parseKeywordIf("WORK");
|
|
return dsl.rollback();
|
|
}
|
|
|
|
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 (peekProKeyword("EXIT"))
|
|
|
|
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 'F':
|
|
if (peekProKeyword("FOR"))
|
|
|
|
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 'G':
|
|
if (peekProKeyword("GOTO"))
|
|
|
|
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 'I':
|
|
if (peekProKeyword("IF"))
|
|
|
|
|
|
|
|
;
|
|
else if (peekProKeyword("ITERATE"))
|
|
|
|
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 'L':
|
|
if (peekProKeyword("LEAVE"))
|
|
|
|
|
|
|
|
;
|
|
else if (peekProKeyword("LET"))
|
|
|
|
|
|
|
|
;
|
|
else if (peekProKeyword("LOOP"))
|
|
|
|
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 'N':
|
|
if (peekKeyword("NULL"))
|
|
return parseNullStatement();
|
|
|
|
break;
|
|
|
|
case 'R':
|
|
if (peekProKeyword("REPEAT"))
|
|
|
|
|
|
|
|
;
|
|
else if (peekProKeyword("RETURN"))
|
|
|
|
|
|
|
|
;
|
|
else if (peekProKeyword("RAISE"))
|
|
|
|
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
if (peekProKeyword("SET"))
|
|
|
|
|
|
|
|
;
|
|
else if (peekProKeyword("SIGNAL"))
|
|
|
|
|
|
|
|
;
|
|
|
|
break;
|
|
|
|
case 'W':
|
|
if (peekProKeyword("WHILE"))
|
|
|
|
|
|
|
|
;
|
|
|
|
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 materialized) {
|
|
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
|
|
? materialized
|
|
? dsl.createMaterializedViewIfNotExists(view, fields)
|
|
: dsl.createViewIfNotExists(view, fields)
|
|
: orReplace
|
|
? materialized
|
|
? dsl.createOrReplaceMaterializedView(view, fields)
|
|
: dsl.createOrReplaceView(view, fields)
|
|
: materialized
|
|
? dsl.createMaterializedView(view, fields)
|
|
: 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 materialized) {
|
|
boolean ifExists = parseKeywordIf("IF EXISTS");
|
|
Table<?> oldName = parseTableName();
|
|
Field<?>[] fields = EMPTY_FIELD;
|
|
|
|
if (parseIf('(')) {
|
|
fields = parseList(',', c -> parseFieldName()).toArray(fields);
|
|
parse(')');
|
|
}
|
|
|
|
if (parseKeywordIf("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 + ")");
|
|
|
|
if (fields.length == 0)
|
|
return dsl.alterView(oldName).as(select);
|
|
else
|
|
return dsl.alterView(oldName, fields).as(select);
|
|
}
|
|
else if (fields.length > 0)
|
|
throw expected("AS");
|
|
else if (parseKeywordIf("RENAME")) {
|
|
parseKeyword("AS", "TO");
|
|
Table<?> newName = parseTableName();
|
|
|
|
return (
|
|
ifExists
|
|
? materialized
|
|
? dsl.alterMaterializedViewIfExists(oldName)
|
|
: dsl.alterViewIfExists(oldName)
|
|
: materialized
|
|
? dsl.alterMaterializedView(oldName)
|
|
: dsl.alterView(oldName)
|
|
).renameTo(newName);
|
|
}
|
|
else if (parseKeywordIf("OWNER TO") && parseUser() != null)
|
|
return IGNORE.get();
|
|
else if (parseKeywordIf("SET"))
|
|
return (materialized
|
|
? dsl.alterMaterializedView(oldName)
|
|
: dsl.alterView(oldName)).comment(parseOptionsDescription());
|
|
else
|
|
throw expected("AS", "OWNER TO", "RENAME", "SET");
|
|
}
|
|
|
|
private final Comment parseOptionsDescription() {
|
|
parseKeyword("OPTIONS");
|
|
parse('(');
|
|
parseKeyword("DESCRIPTION");
|
|
parse('=');
|
|
Comment comment = parseComment();
|
|
parse(')');
|
|
|
|
return comment;
|
|
}
|
|
|
|
private final DDLQuery parseDropView(boolean materialized) {
|
|
return materialized
|
|
? parseIfExists(this::parseTableName, dsl::dropMaterializedViewIfExists, dsl::dropMaterializedView)
|
|
: 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 as = false;
|
|
boolean startWith = false;
|
|
boolean incrementBy = false;
|
|
boolean minvalue = false;
|
|
boolean maxvalue = false;
|
|
boolean cycle = false;
|
|
boolean cache = false;
|
|
|
|
for (;;) {
|
|
Field field;
|
|
DataType type = null;
|
|
|
|
if (!as && (as |= (parseKeywordIf("AS") && (type = parseDataType()) != null)))
|
|
s = ((CreateSequenceAsStep) s).as(type);
|
|
else 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<Long> field;
|
|
|
|
if (!startWith && (startWith |= (field = parseSequenceStartWithIf()) != null))
|
|
s1 = s1.startWith(field);
|
|
else if (!incrementBy && (incrementBy |= (field = parseSequenceIncrementByIf()) != null))
|
|
s1 = s1.incrementBy(field);
|
|
else if (!minvalue && (minvalue |= (field = parseSequenceMinvalueIf()) != null))
|
|
s1 = s1.minvalue(field);
|
|
else if (!minvalue && (minvalue |= parseSequenceNoMinvalueIf()))
|
|
s1 = s1.noMinvalue();
|
|
else if (!maxvalue && (maxvalue |= (field = parseSequenceMaxvalueIf()) != null))
|
|
s1 = s1.maxvalue(field);
|
|
else if (!maxvalue && (maxvalue |= parseSequenceNoMaxvalueIf()))
|
|
s1 = s1.noMaxvalue();
|
|
else if (!cycle && (cycle |= parseKeywordIf("CYCLE")))
|
|
s1 = s1.cycle();
|
|
else if (!cycle && (cycle |= parseSequenceNoCycleIf()))
|
|
s1 = s1.noCycle();
|
|
else if (!cache && (cache |= (field = parseSequenceCacheIf()) != null))
|
|
s1 = s1.cache(field);
|
|
else if (!cache && (cache |= parseSequenceNoCacheIf()))
|
|
s1 = s1.noCache();
|
|
else if (!restart && (restart |= parseKeywordIf("RESTART"))) {
|
|
if (parseKeywordIf("WITH"))
|
|
s1 = s1.restartWith(parseUnsignedIntegerOrBindVariable());
|
|
else
|
|
s1 = s1.restart();
|
|
}
|
|
else
|
|
break;
|
|
|
|
found = true;
|
|
}
|
|
|
|
if (!found)
|
|
throw expected(
|
|
"CACHE",
|
|
"CYCLE",
|
|
"INCREMENT BY",
|
|
"MAXVALUE",
|
|
"MINVALUE",
|
|
"NO CACHE",
|
|
"NO CYCLE",
|
|
"NO MAXVALUE",
|
|
"NO MINVALUE",
|
|
"OWNER TO",
|
|
"RENAME TO",
|
|
"RESTART",
|
|
"START WITH"
|
|
);
|
|
|
|
return s1;
|
|
}
|
|
}
|
|
|
|
private final boolean parseSequenceNoCacheIf() {
|
|
return parseKeywordIf("NO CACHE", "NOCACHE");
|
|
}
|
|
|
|
private final Field<Long> parseSequenceCacheIf() {
|
|
return parseKeywordIf("CACHE") && (parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
|
|
}
|
|
|
|
private final boolean parseSequenceNoCycleIf() {
|
|
return parseKeywordIf("NO CYCLE", "NOCYCLE");
|
|
}
|
|
|
|
private final boolean parseSequenceNoMaxvalueIf() {
|
|
return parseKeywordIf("NO MAXVALUE", "NOMAXVALUE");
|
|
}
|
|
|
|
private final Field<Long> parseSequenceMaxvalueIf() {
|
|
return parseKeywordIf("MAXVALUE") && (parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
|
|
}
|
|
|
|
private final boolean parseSequenceNoMinvalueIf() {
|
|
return parseKeywordIf("NO MINVALUE", "NOMINVALUE");
|
|
}
|
|
|
|
private final Field<Long> parseSequenceMinvalueIf() {
|
|
return parseKeywordIf("MINVALUE") && (parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
|
|
}
|
|
|
|
private final Field<Long> parseSequenceIncrementByIf() {
|
|
return parseKeywordIf("INCREMENT") && (parseKeywordIf("BY") || parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
|
|
}
|
|
|
|
private final Field<Long> parseSequenceStartWithIf() {
|
|
return parseKeywordIf("START") && (parseKeywordIf("WITH") || parseIf("=") || true) ? parseUnsignedIntegerOrBindVariable() : null;
|
|
}
|
|
|
|
private final Query parseAlterSession() {
|
|
parseKeyword("SET CURRENT_SCHEMA");
|
|
parse('=');
|
|
return dsl.setSchema(parseSchemaName());
|
|
}
|
|
|
|
private final DDLQuery parseSetGenerator() {
|
|
Sequence<?> sequenceName = parseSequenceName();
|
|
parseKeyword("TO");
|
|
return dsl.alterSequence((Sequence) sequenceName).restartWith(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<Field<?>> fields = new ArrayList<>();
|
|
List<Constraint> constraints = new ArrayList<>();
|
|
List<Index> indexes = new ArrayList<>();
|
|
boolean primary = false;
|
|
boolean identity = false;
|
|
boolean hidden = false;
|
|
boolean readonly = false;
|
|
boolean ctas = false;
|
|
|
|
if (!peekSelectOrWith(true) && parseIf('(')) {
|
|
|
|
columnLoop:
|
|
do {
|
|
int p = position();
|
|
|
|
ConstraintTypeStep constraint = parseConstraintNameSpecificationIf();
|
|
|
|
if (parsePrimaryKeyClusteredNonClusteredKeywordIf()) {
|
|
if (primary)
|
|
throw exception("Duplicate primary key specification");
|
|
|
|
primary = true;
|
|
PrimaryKeySpecification pk = parsePrimaryKeySpecification(constraint, true);
|
|
constraints.add(pk.constraint());
|
|
if (pk.identity()) {
|
|
ConstraintImpl c = (ConstraintImpl) pk.constraint();
|
|
|
|
replacement:
|
|
if (c.$primaryKey().length == 1) {
|
|
for (int i = 0; i < fields.size(); i++) {
|
|
Field<?> f = fields.get(i);
|
|
|
|
if (f.getName().equalsIgnoreCase(c.$primaryKey()[0].getName())) {
|
|
fields.set(i, field(f.getQualifiedName(), f.getDataType().identity(true)));
|
|
break replacement;
|
|
}
|
|
}
|
|
|
|
throw expected("Column not found: " + c.$primaryKey()[0].getName());
|
|
}
|
|
else
|
|
throw expected("Single column primary key with inline identity");
|
|
}
|
|
|
|
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,
|
|
hidden,
|
|
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<CreateTableOnCommitStep, CreateTableCommentStep> 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<Record>) 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<SQL> storage = new ArrayList<>();
|
|
Comment comment = null;
|
|
|
|
storageLoop:
|
|
for (boolean first = true;; first = false) {
|
|
boolean optional = first || !parseIf(',');
|
|
Keyword keyword = null;
|
|
|
|
// MySQL storage clauses (see: https://dev.mysql.com/doc/refman/5.7/en/create-table.html)
|
|
if ((keyword = parseAndGetKeywordIf("AUTO_INCREMENT")) != null) {
|
|
parseIf('=');
|
|
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(Sign.NONE)));
|
|
}
|
|
else if ((keyword = parseAndGetKeywordIf("AVG_ROW_LENGTH")) != null) {
|
|
parseIf('=');
|
|
storage.add(sql("{0} {1}", keyword, parseFieldUnsignedNumericLiteral(Sign.NONE)));
|
|
}
|
|
else if ((keyword = parseAndGetKeywordIf("CHARACTER SET")) != null) {
|
|
parseIf('=');
|
|
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
|
|
}
|
|
else if ((keyword = parseAndGetKeywordIf("DEFAULT CHARACTER SET")) != null
|
|
|| (keyword = parseAndGetKeywordIf("DEFAULT CHARSET")) != null) {
|
|
parseIf('=');
|
|
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
|
|
}
|
|
else if ((keyword = parseAndGetKeywordIf("CHECKSUM")) != null) {
|
|
parseIf('=');
|
|
storage.add(sql("{0} {1}", keyword, parseZeroOne()));
|
|
}
|
|
else if ((keyword = parseAndGetKeywordIf("COLLATE")) != null) {
|
|
parseIf('=');
|
|
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
|
|
}
|
|
else if ((keyword = parseAndGetKeywordIf("DEFAULT COLLATE")) != null) {
|
|
parseIf('=');
|
|
storage.add(sql("{0} {1}", keyword, parseIdentifier()));
|
|
}
|
|
|
|
// [#10164] In a statement batch, this could already be the next statement
|
|
else if (!peekKeyword("COMMENT ON") && parseKeywordIf("COMMENT")) {
|
|
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 hidden, boolean readonly) {}
|
|
|
|
private final ParseInlineConstraints parseInlineConstraints(
|
|
Name fieldName,
|
|
DataType<?> type,
|
|
List<? super Constraint> constraints,
|
|
boolean primary,
|
|
boolean identity,
|
|
boolean hidden,
|
|
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;
|
|
boolean sparse = false;
|
|
Comment fieldComment = null;
|
|
|
|
identity |= type.identity();
|
|
hidden |= type.hidden();
|
|
readonly |= type.readonly();
|
|
|
|
for (;;) {
|
|
ConstraintTypeStep inlineConstraint = parseConstraintNameSpecificationIf();
|
|
|
|
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 (parseKeywordIf("NOT HIDDEN", "VISIBLE")) {
|
|
continue;
|
|
}
|
|
else if (parseKeywordIf("IMPLICITLY HIDDEN", "HIDDEN", "NOT VISIBLE", "INVISIBLE")) {
|
|
type = type.hidden(true);
|
|
continue;
|
|
}
|
|
else if (parseProKeywordIf("READONLY")) {
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (parseKeywordIf("DEFAULT")) {
|
|
|
|
// [#10963] Special case nextval('<id>_seq'::regclass)
|
|
if (parseSerialIf()) {
|
|
type = type.identity(true);
|
|
}
|
|
else {
|
|
|
|
// TODO: [#10116] Support this clause also in the jOOQ API
|
|
parseKeywordIf("ON NULL");
|
|
|
|
type = type.defaultValue((Field) toField(parseConcat()));
|
|
|
|
// 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)
|
|
|| parseKeywordIf("COMPUTE"))
|
|
&& requireProEdition()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (!computed && parseProKeywordIf("ALIAS")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (!computed && parseProKeywordIf("MATERIALIZED")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
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 (!primary && parsePrimaryKeyClusteredNonClusteredKeywordIf()) {
|
|
constraints.add(parseConstraintEnforcementIf(inlineConstraint == null
|
|
? primaryKey(fieldName)
|
|
: inlineConstraint.primaryKey(fieldName)));
|
|
|
|
parseUniqueIndexStorageClausesIf();
|
|
|
|
// [#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");
|
|
|
|
parseUniqueIndexStorageClausesIf();
|
|
|
|
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 (inlineConstraint != null)
|
|
throw expected("CHECK", "DEFAULT", "NOT NULL", "NULL", "PRIMARY KEY", "REFERENCES", "UNIQUE");
|
|
|
|
if (!onUpdate) {
|
|
if (parseKeywordIf("ON UPDATE")) {
|
|
|
|
// [#6132] TODO: Support this feature in the jOOQ DDL API
|
|
parseConcat();
|
|
onUpdate = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
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 (parseProKeywordIf("NO COMPRESS")) {
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (parseProKeywordIf("COMPRESS")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
if (!sparse) {
|
|
if (parseProKeywordIf("SPARSE")) {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return new ParseInlineConstraints(type, fieldComment, primary, identity, hidden, 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 boolean parseUniqueIndexStorageClausesIf() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final DDLQuery parseCreateType() {
|
|
boolean ifNotExists = parseKeywordIf("IF NOT EXISTS");
|
|
Name name = parseName();
|
|
|
|
if (parseKeywordIf("AS")) {
|
|
if (parseKeywordIf("ENUM")) {
|
|
List<String> values;
|
|
parse('(');
|
|
|
|
if (!parseIf(')')) {
|
|
values = parseList(',', ParseContext::parseStringLiteral);
|
|
parse(')');
|
|
}
|
|
else
|
|
values = new ArrayList<>();
|
|
|
|
return (ifNotExists ? dsl.createTypeIfNotExists(name) : dsl.createType(name))
|
|
.asEnum(values.toArray(EMPTY_STRING));
|
|
}
|
|
else {
|
|
parseKeywordIf("OBJECT", "STRUCT");
|
|
parse('(');
|
|
List<Field<?>> fields = parseList(',', ctx -> DSL.field(parseIdentifier(), parseDataType()));
|
|
parse(')');
|
|
return (ifNotExists ? dsl.createTypeIfNotExists(name) : dsl.createType(name))
|
|
.as(fields);
|
|
}
|
|
}
|
|
else if (parseKeywordIf("FROM")) {
|
|
return (ifNotExists ? dsl.createDomainIfNotExists(name) : dsl.createDomain(name))
|
|
.as(parseDataType());
|
|
}
|
|
else
|
|
throw expected("AS", "FROM");
|
|
}
|
|
|
|
private final Index parseIndexSpecification(Table<?> table) {
|
|
Name name = parseIdentifierIf();
|
|
parseUsingIndexTypeIf();
|
|
return Internal.createIndex(name == null ? NO_NAME : name, table, parseParenthesisedSortSpecification(false).fields(), 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", "ENFORCED")))
|
|
return e.enforced();
|
|
else if ((parseKeywordIf("DISABLE", "NOT ENFORCED")))
|
|
return e.notEnforced();
|
|
else
|
|
return e;
|
|
}
|
|
|
|
private final boolean parseConstraintDeferrableIf() {
|
|
return parseKeywordIf("DEFERRABLE", "NOT DEFERRABLE");
|
|
}
|
|
|
|
private final boolean parseConstraintInitiallyIf() {
|
|
return parseKeywordIf("INITIALLY") && parseKeyword("DEFERRED", "IMMEDIATE");
|
|
}
|
|
|
|
private static final record PrimaryKeySpecification(Constraint constraint, boolean identity) {}
|
|
|
|
private final PrimaryKeySpecification parsePrimaryKeySpecification(ConstraintTypeStep constraint, boolean allowIdentity) {
|
|
parseUsingIndexTypeIf();
|
|
KeyColumnList k = parseKeyColumnList(allowIdentity);
|
|
|
|
ConstraintEnforcementStep e = constraint == null
|
|
? primaryKey(k.fields())
|
|
: constraint.primaryKey(k.fields());
|
|
|
|
parseUsingIndexTypeIf();
|
|
parseUniqueIndexStorageClausesIf();
|
|
return new PrimaryKeySpecification(parseConstraintEnforcementIf(e), k.identity());
|
|
}
|
|
|
|
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(false).fields();
|
|
|
|
ConstraintEnforcementStep e = constraint == null
|
|
? unique(fieldNames)
|
|
: constraint.unique(fieldNames);
|
|
|
|
parseUsingIndexTypeIf();
|
|
parseUniqueIndexStorageClausesIf();
|
|
return parseConstraintEnforcementIf(e);
|
|
}
|
|
|
|
private static final record KeyColumnList(Field<?>[] fields, boolean identity) {}
|
|
|
|
private final KeyColumnList parseKeyColumnList(boolean allowIdentity) {
|
|
SortSpecification s = parseParenthesisedSortSpecification(allowIdentity);
|
|
Field<?>[] fieldNames = new Field[s.fields().length];
|
|
|
|
for (int i = 0; i < s.fields().length; i++)
|
|
if (s.fields()[i].$sortOrder() != SortOrder.DESC)
|
|
fieldNames[i] = s.fields()[i].$field();
|
|
|
|
// [#7899] TODO: Support this in jOOQ
|
|
else
|
|
throw notImplemented("DESC sorting in constraints");
|
|
|
|
return new KeyColumnList(fieldNames, s.identity());
|
|
}
|
|
|
|
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 <O, S extends QueryPart> S parseIfExists(
|
|
Supplier<? extends O> part,
|
|
Function<? super O, ? extends S> stepIfExists,
|
|
Function<? super O, ? extends S> 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 extends QueryPart, S1 extends S2> S2 parseCascadeRestrictIf(
|
|
S1 step,
|
|
Function<? super S1, ? extends S2> stepCascade,
|
|
Function<? super S1, ? extends S2> stepRestrict
|
|
) {
|
|
boolean cascade = parseKeywordIf("CASCADE");
|
|
boolean restrict = !cascade && parseKeywordIf("RESTRICT");
|
|
|
|
return cascade
|
|
? stepCascade.apply(step)
|
|
: restrict
|
|
? stepRestrict.apply(step)
|
|
: step;
|
|
}
|
|
|
|
private static final Set<String> ALTER_KEYWORDS = new HashSet<>(Arrays.asList("ADD", "ALTER", "COMMENT", "DROP", "MODIFY", "RENAME"));
|
|
|
|
private final DDLQuery parseAlterTable() {
|
|
boolean ifTableExists = parseKeywordIf("IF EXISTS");
|
|
Table<?> tableName;
|
|
|
|
if (peekKeyword("ONLY")) {
|
|
|
|
// [#7751] ONLY is only supported by PostgreSQL. In other RDBMS, it
|
|
// corresponds to a table name.
|
|
Name only = parseIdentifier();
|
|
int p = position();
|
|
|
|
if ((tableName = parseTableNameIf()) == null || (
|
|
!tableName.getQualifiedName().qualified()
|
|
&& tableName.getUnqualifiedName().quoted() == Quoted.UNQUOTED
|
|
&& ALTER_KEYWORDS.contains(tableName.getName().toUpperCase()))) {
|
|
tableName = table(only);
|
|
position(p);
|
|
}
|
|
}
|
|
else {
|
|
tableName = parseTableName();
|
|
}
|
|
|
|
AlterTableStep s1 = ifTableExists
|
|
? dsl.alterTableIfExists(tableName)
|
|
: dsl.alterTable(tableName);
|
|
|
|
switch (characterUpper()) {
|
|
case 'A':
|
|
if (parseKeywordIf("ADD"))
|
|
return parseAlterTableAdd(s1, tableName, false);
|
|
else if (parseKeywordIf("ALTER"))
|
|
if (parseKeywordIf("CONSTRAINT"))
|
|
return parseAlterTableAlterConstraint(s1);
|
|
else if ((parseKeywordIf("COLUMN") || true))
|
|
return parseAlterTableAlterColumn(tableName, s1);
|
|
|
|
break;
|
|
|
|
case 'C':
|
|
|
|
// TODO: support all of the storageLoop from the CREATE TABLE statement
|
|
if (parseKeywordIf("COMMENT")) {
|
|
if (parseKeywordIf("COLUMN")) {
|
|
return dsl.commentOnColumn(tableName.getQualifiedName().append(parseIdentifier()))
|
|
.is(parseStringLiteral());
|
|
}
|
|
else {
|
|
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(false).fields())
|
|
: 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<Field<?>> 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("COMMENT"))
|
|
return s1.comment(parseComment());
|
|
else if ((parseKeywordIf("COLUMN") || true))
|
|
return parseAlterTableAlterColumn(tableName, s1);
|
|
|
|
break;
|
|
|
|
case 'O':
|
|
if (parseKeywordIf("OWNER TO") && parseUser() != null)
|
|
return IGNORE.get();
|
|
|
|
break;
|
|
|
|
case 'R':
|
|
if (parseKeywordIf("RENAME")) {
|
|
if (parseKeywordIf("AS", "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;
|
|
|
|
case 'W':
|
|
if (parseKeywordIf("WITH CHECK ADD"))
|
|
return parseAlterTableAdd(s1, tableName, true);
|
|
|
|
break;
|
|
}
|
|
|
|
throw expected("ADD", "ALTER", "COMMENT", "DROP", "MODIFY", "OWNER TO", "RENAME", "SET", "WITH");
|
|
}
|
|
|
|
private final DDLQuery parseAlterTableAdd(AlterTableStep s1, Table<?> tableName, boolean constraintOnly) {
|
|
List<TableElement> list = new ArrayList<>();
|
|
|
|
if (!constraintOnly && parseIndexOrKeyIf()) {
|
|
Name name = parseIdentifierIf();
|
|
|
|
return name == null
|
|
? dsl.createIndex().on(tableName, parseParenthesisedSortSpecification(false).fields())
|
|
: dsl.createIndex(name).on(tableName, parseParenthesisedSortSpecification(false).fields());
|
|
}
|
|
|
|
if (parseIf('(')) {
|
|
do
|
|
parseAlterTableAddFieldsOrConstraints(list, constraintOnly);
|
|
while (parseIf(','));
|
|
|
|
parse(')');
|
|
}
|
|
else if (!constraintOnly && (
|
|
parseKeywordIf("COLUMN IF NOT EXISTS")
|
|
|| parseKeywordIf("IF NOT EXISTS"))) {
|
|
return parseAlterTableAddFieldFirstBeforeLast(s1.addColumnIfNotExists(parseAlterTableAddField(null)));
|
|
}
|
|
else {
|
|
do
|
|
parseAlterTableAddFieldsOrConstraints(list, constraintOnly);
|
|
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<TableElement> list, boolean constraintOnly) {
|
|
ConstraintTypeStep constraint = parseConstraintNameSpecificationIf();
|
|
|
|
if (parsePrimaryKeyClusteredNonClusteredKeywordIf())
|
|
list.add(parsePrimaryKeySpecification(constraint, false).constraint());
|
|
else if (parseKeywordIf("UNIQUE") && (parseKeywordIf("KEY", "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 (!constraintOnly && (parseKeywordIf("COLUMN") || true))
|
|
parseAlterTableAddField(list);
|
|
}
|
|
|
|
private final ConstraintTypeStep parseConstraintNameSpecificationIf() {
|
|
if (parseKeywordIf("CONSTRAINT") && !peekKeyword("PRIMARY KEY", "UNIQUE", "FOREIGN KEY", "CHECK"))
|
|
return constraint(parseIdentifier());
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseAlterTableAddField(List<TableElement> 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, false);
|
|
Field<?> result = field(fieldName, inline.type, inline.fieldComment);
|
|
|
|
if (list != null)
|
|
list.add(p, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
private final DDLQuery parseAlterTableAlterColumn(Table<?> table, 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", "SET NULL", "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", "DEFAULT"))
|
|
return s1.alter(field).default_((Field) toField(parseConcat()));
|
|
else if (peekKeyword("SET OPTIONS") && parseKeywordIf("SET"))
|
|
return dsl.commentOnColumn(field(table.getQualifiedName().append(field.getUnqualifiedName()))).is(parseOptionsDescription());
|
|
else if (parseKeywordIf("TO", "RENAME TO", "RENAME AS"))
|
|
return s1.renameColumn(field).to(parseFieldName());
|
|
else if (parseKeywordIf("TYPE", "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<Constraint> constraints = new ArrayList<>();
|
|
|
|
constraintLoop:
|
|
for (;;) {
|
|
ConstraintTypeStep constraint = parseConstraintNameSpecificationIf();
|
|
|
|
// 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 = parseConstraintNameSpecificationIf();
|
|
|
|
// 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", "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();
|
|
parseMySQLCreateDatabaseFlagsIf();
|
|
|
|
return ifNotExists
|
|
? dsl.createDatabaseIfNotExists(catalogName)
|
|
: dsl.createDatabase(catalogName);
|
|
}
|
|
|
|
private final void parseMySQLCreateDatabaseFlagsIf() {
|
|
for (;;) {
|
|
if (parseKeywordIf("DEFAULT CHARACTER SET", "CHARACTER SET") && (parseIf("=") || true))
|
|
parseCharacterSet();
|
|
else if (parseKeywordIf("DEFAULT COLLATE", "COLLATE") && (parseIf("=") || true))
|
|
parseCollation();
|
|
else if (parseKeywordIf("DEFAULT ENCRYPTION", "ENCRYPTION") && (parseIf("=") || true))
|
|
parseCharacterLiteral();
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
parseMySQLCreateDatabaseFlagsIf();
|
|
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(false).fields();
|
|
parseKeyword("ON");
|
|
Table<?> tableName = parseTableName();
|
|
parseUsingIndexTypeIf();
|
|
if (fields == null)
|
|
fields = parseParenthesisedSortSpecification(false).fields();
|
|
parseUsingIndexTypeIf();
|
|
|
|
Name[] include = null;
|
|
if (parseKeywordIf("INCLUDE", "COVERING", "STORING")) {
|
|
parse('(');
|
|
include = parseIdentifiers().toArray(EMPTY_NAME);
|
|
parse(')');
|
|
}
|
|
|
|
parseKeywordIf("VISIBLE");
|
|
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 static final record SortSpecification(SortField<?>[] fields, boolean identity) {}
|
|
|
|
private SortSpecification parseParenthesisedSortSpecification(boolean allowIdentity) {
|
|
parse('(');
|
|
SortField<?>[] fields = parseList(',', c -> c.parseSortField()).toArray(EMPTY_SORTFIELD);
|
|
boolean identity = fields.length == 1 && allowIdentity && parseKeywordIf("AUTOINCREMENT", "AUTO_INCREMENT");
|
|
parse(')');
|
|
|
|
return new SortSpecification(fields, identity);
|
|
}
|
|
|
|
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 = parseXor();
|
|
|
|
while (parseKeywordIf("OR"))
|
|
condition = toCondition(condition).or(toCondition(parseXor()));
|
|
|
|
return condition;
|
|
}
|
|
|
|
private final QueryPart parseXor() {
|
|
QueryPart condition = parseAnd();
|
|
|
|
while (parseKeywordIf("XOR"))
|
|
condition = toCondition(condition).xor(toCondition(parseAnd()));
|
|
|
|
return condition;
|
|
}
|
|
|
|
private final QueryPart parseAnd() {
|
|
QueryPart condition = parseNot();
|
|
|
|
while (!forbidden.contains(FK_AND) && parseKeywordIf("AND") || parseCategory() == SQLDialectCategory.MYSQL && parseIf("&&"))
|
|
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") || parseCategory() == SQLDialectCategory.MYSQL && parseIf('!'))
|
|
not++;
|
|
|
|
return not;
|
|
}
|
|
|
|
private final QueryPart parsePredicate() {
|
|
int p1 = position();
|
|
Condition condition;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch (characterUpper()) {
|
|
case 'D':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
case 'E':
|
|
if (parseFunctionNameIf("EQUAL_NULL"))
|
|
return parseEqualNull();
|
|
else if (parseKeywordIf("EXISTS"))
|
|
return exists(parseParenthesised(c -> parseWithOrSelect()));
|
|
|
|
break;
|
|
|
|
case 'I':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
case 'J':
|
|
if ((condition = parsePredicateJSONExistsIf()) != null)
|
|
return condition;
|
|
|
|
break;
|
|
|
|
case 'P':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
case 'R':
|
|
if (parseKeywordIf("REGEXP_LIKE"))
|
|
return parseFunctionArgs2(Field::likeRegex);
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
if (parseProFunctionNameIf("ST_CONTAINS", "SDO_CONTAINS")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_COVEREDBY", "SDO_COVEREDBY")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_COVERS", "SDO_COVERS")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_CROSSES")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_DISJOINT")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_EQUALS", "SDO_EQUAL")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_INTERSECTS")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ISCLOSED")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ISEMPTY")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ISSIMPLE")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ISVALID")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_OVERLAPS", "SDO_OVERLAPS")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_TOUCHES", "SDO_TOUCH")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_WITHIN", "SDO_INSIDE")) {
|
|
|
|
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'U':
|
|
if (parseKeywordIf("UNIQUE"))
|
|
// javac can't infer this (?)
|
|
return unique(this.<Select<?>>parseParenthesised(c -> parseWithOrSelect()));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
case 'X':
|
|
if ((condition = parsePredicateXMLExistsIf()) != null)
|
|
return condition;
|
|
|
|
break;
|
|
}
|
|
|
|
boolean notOp = false;
|
|
FieldOrRow left = parseConcat();
|
|
int p2 = position();
|
|
boolean not = parseKeywordIf("NOT");
|
|
boolean isField = left instanceof Field;
|
|
Comparator comp;
|
|
TSQLOuterJoinComparator outer;
|
|
|
|
|
|
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", "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()))
|
|
: AbstractRow.compare((Row) left, comp, parseRow(((Row) left).size(), true));
|
|
|
|
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<Field<?>> fields;
|
|
if (parseIf(')')) {
|
|
fields = emptyList();
|
|
}
|
|
else {
|
|
fields = parseList(',', c -> toField(parseConcat()));
|
|
parse(')');
|
|
}
|
|
Field<String>[] fieldArray = fields.toArray(new Field[0]);
|
|
LikeEscapeStep result = (not ^ notOp) ? ((Field<String>) left).notLike(any(fieldArray)) : ((Field<String>) left).like(any(fieldArray));
|
|
return parseEscapeClauseIf(result);
|
|
}
|
|
}
|
|
else if (parseKeywordIf("ALL")) {
|
|
parse('(');
|
|
if (peekSelectOrWith(true)) {
|
|
Select<?> select = parseWithOrSelect();
|
|
parse(')');
|
|
LikeEscapeStep result = (not ^ notOp) ? ((Field) left).notLike(all(select)) : ((Field) left).like(all(select));
|
|
return parseEscapeClauseIf(result);
|
|
}
|
|
else {
|
|
List<Field<?>> fields;
|
|
if (parseIf(')')) {
|
|
fields = emptyList();
|
|
}
|
|
else {
|
|
fields = parseList(',', c -> toField(parseConcat()));
|
|
parse(')');
|
|
}
|
|
Field<String>[] fieldArray = fields.toArray(new Field[0]);
|
|
LikeEscapeStep result = (not ^ notOp) ? ((Field<String>) left).notLike(all(fieldArray)) : ((Field<String>) left).like(all(fieldArray));
|
|
return parseEscapeClauseIf(result);
|
|
}
|
|
}
|
|
else {
|
|
Field right = toField(parseConcat());
|
|
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(p2);
|
|
return left;
|
|
}
|
|
}
|
|
|
|
private final Condition parseEqualNull() {
|
|
Condition result;
|
|
|
|
parse('(');
|
|
FieldOrRow left = parseConcat();
|
|
parse(',');
|
|
|
|
if (left instanceof Field f)
|
|
result = f.isNotDistinctFrom((Field) toField(parseConcat()));
|
|
else
|
|
result = new RowIsDistinctFrom((Row) left, parseRow(((Row) left).size(), true), true);
|
|
|
|
parse(')');
|
|
return result;
|
|
}
|
|
|
|
private final Condition parsePredicateXMLExistsIf() {
|
|
if (parseKeywordIf("XMLEXISTS")) {
|
|
parse('(');
|
|
Field<String> xpath = (Field<String>) parseField();
|
|
XMLPassingMechanism m = parseXMLPassingMechanism();
|
|
Field<XML> xml = (Field<XML>) parseField();
|
|
parse(')');
|
|
|
|
if (m == BY_REF)
|
|
return xmlexists(xpath).passingByRef(xml);
|
|
else if (m == BY_VALUE)
|
|
return xmlexists(xpath).passingByValue(xml);
|
|
else
|
|
return xmlexists(xpath).passing(xml);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Condition parsePredicateJSONExistsIf() {
|
|
if (parseKeywordIf("JSON_EXISTS")) {
|
|
parse('(');
|
|
Field json = parseField();
|
|
parse(',');
|
|
Field<String> path = (Field<String>) 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 parseJoinedTable(() -> peekKeyword(KEYWORD_LOOKUP_IN_SELECT_FROM));
|
|
}
|
|
|
|
private final Table<?> parseLateral(BooleanSupplier forbiddenKeywords) {
|
|
if (parseKeywordIf("LATERAL"))
|
|
return lateral(parseTableFactor(forbiddenKeywords));
|
|
else
|
|
return parseTableFactor(forbiddenKeywords);
|
|
}
|
|
|
|
private final <R extends Record> Table<R> t(TableLike<R> table) {
|
|
return t(table, false);
|
|
}
|
|
|
|
private final <R extends Record> Table<R> t(TableLike<R> table, boolean dummyAlias) {
|
|
return
|
|
table instanceof Table<R> 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<Integer>) rc);
|
|
}
|
|
else {
|
|
Field<?> f = parseField();
|
|
|
|
// Work around a missing feature in unnest()
|
|
if (!f.getType().isArray())
|
|
f = f.coerce(f.getDataType().getArrayDataType());
|
|
|
|
result = unnest(f);
|
|
}
|
|
|
|
parse(')');
|
|
}
|
|
else if (parseFunctionNameIf("GENERATE_SERIES", "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("NUMBERS")) {
|
|
parse('(');
|
|
Field f1 = toField(parseConcat());
|
|
Field f2 = parseIf(',') ? toField(parseConcat()) : null;
|
|
parse(')');
|
|
|
|
result = f2 == null
|
|
? generateSeries(zero(), isub(f1, one()))
|
|
: generateSeries(f1, isub(iadd(f1, f2), one()));
|
|
}
|
|
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 (parseFunctionNameIf("OPENJSON") && requireProEdition()) {
|
|
result = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (peekFunctionNameIf("VALUES")) {
|
|
result = parseTableValueConstructor();
|
|
}
|
|
else if (parseFunctionNameIf("XMLTABLE")) {
|
|
parse('(');
|
|
|
|
XMLTablePassingStep s1 = xmltable((Field) toField(parseConcat()));
|
|
XMLPassingMechanism m = parseXMLPassingMechanismIf();
|
|
Field<XML> passing = m == null ? null : (Field<XML>) parseField();
|
|
|
|
XMLTableColumnsStep s2 = (XMLTableColumnsStep) (
|
|
m == BY_REF
|
|
? s1.passingByRef(passing)
|
|
: m == BY_VALUE
|
|
? s1.passingByValue(passing)
|
|
: m == XMLPassingMechanism.DEFAULT
|
|
? s1.passing(passing)
|
|
: s1
|
|
);
|
|
|
|
parseKeyword("COLUMNS");
|
|
|
|
do {
|
|
Name fieldName = parseIdentifier();
|
|
|
|
if (parseKeywordIf("FOR ORDINALITY")) {
|
|
s2 = s2.column(fieldName).forOrdinality();
|
|
}
|
|
else {
|
|
XMLTableColumnPathStep s3 = s2.column(fieldName, parseDataType());
|
|
s2 = parseKeywordIf("PATH") ? s3.path(parseStringLiteral()) : s3;
|
|
}
|
|
}
|
|
while (parseIf(','));
|
|
|
|
parse(')');
|
|
result = s2;
|
|
}
|
|
else if (parseIf('(')) {
|
|
|
|
// A table factor parenthesis can mark the beginning of any of:
|
|
// - A derived table: E.g. (select 1)
|
|
// - A derived table with nested set ops: E.g. ((select 1) union (select 2))
|
|
// - A values derived table: E.g. (values (1))
|
|
// - A joined table: E.g. ((a join b on p) right join c on q)
|
|
// - A combination of the above: E.g. ((a join (select 1) on p) right join (((select 1)) union (select 2)) on q)
|
|
if (peekKeyword("SELECT", "SEL", "WITH")) {
|
|
SelectQueryImpl<Record> 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 (parseProKeywordIf("VERSIONS BETWEEN")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (!ignoreProEdition() && parseForPeriodIf() && requireProEdition()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (parseProKeywordIf("AS OF")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
if (parseKeywordIf("WITH ORDINALITY"))
|
|
result = t(result).withOrdinality();
|
|
|
|
if (parseProKeywordIf("PIVOT")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
// TODO UNPIVOT
|
|
result = parseCorrelationNameIf(result, forbiddenKeywords);
|
|
|
|
int p = position();
|
|
if (!peekKeyword("WITH CHECK OPTION", "WITH READ ONLY") && parseKeywordIf("WITH")) {
|
|
if (!ignoreProEdition() && parseIf('(') && requireProEdition()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
// [#10164] Without parens, WITH is part of the next statement in delimiter free statement batches
|
|
else
|
|
position(p);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else {
|
|
for (;;) {
|
|
if (parseKeywordIf("USE KEY", "USE INDEX")) {
|
|
if (parseKeywordIf("FOR JOIN"))
|
|
result = t(result).useIndexForJoin(parseParenthesisedIdentifiers());
|
|
else if (parseKeywordIf("FOR ORDER BY"))
|
|
result = t(result).useIndexForOrderBy(parseParenthesisedIdentifiers());
|
|
else if (parseKeywordIf("FOR GROUP BY"))
|
|
result = t(result).useIndexForGroupBy(parseParenthesisedIdentifiers());
|
|
else
|
|
result = t(result).useIndex(parseParenthesisedIdentifiers());
|
|
}
|
|
else if (parseKeywordIf("FORCE KEY", "FORCE INDEX")) {
|
|
if (parseKeywordIf("FOR JOIN"))
|
|
result = t(result).forceIndexForJoin(parseParenthesisedIdentifiers());
|
|
else if (parseKeywordIf("FOR ORDER BY"))
|
|
result = t(result).forceIndexForOrderBy(parseParenthesisedIdentifiers());
|
|
else if (parseKeywordIf("FOR GROUP BY"))
|
|
result = t(result).forceIndexForGroupBy(parseParenthesisedIdentifiers());
|
|
else
|
|
result = t(result).forceIndex(parseParenthesisedIdentifiers());
|
|
}
|
|
else if (parseKeywordIf("IGNORE KEY", "IGNORE INDEX")) {
|
|
if (parseKeywordIf("FOR JOIN"))
|
|
result = t(result).ignoreIndexForJoin(parseParenthesisedIdentifiers());
|
|
else if (parseKeywordIf("FOR ORDER BY"))
|
|
result = t(result).ignoreIndexForOrderBy(parseParenthesisedIdentifiers());
|
|
else if (parseKeywordIf("FOR GROUP BY"))
|
|
result = t(result).ignoreIndexForGroupBy(parseParenthesisedIdentifiers());
|
|
else
|
|
result = t(result).ignoreIndex(parseParenthesisedIdentifiers());
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
return t(result);
|
|
}
|
|
|
|
private final boolean parseForPeriodIf() {
|
|
return 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");
|
|
}
|
|
|
|
private final String[] parseParenthesisedIdentifiers() {
|
|
return parseParenthesised(c -> map(parseIdentifiers(), Name::last, String[]::new));
|
|
}
|
|
|
|
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<Name> 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<Row> 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<? extends FieldOrRow> 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) {
|
|
int p = position();
|
|
if (parseProKeywordIf("PARTITION BY")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
Join join = parseJoinTypeIf();
|
|
|
|
if (join == null)
|
|
return null;
|
|
|
|
Table<?> right = join.type.qualified() ? parseJoinedTable(forbiddenKeywords) : parseLateral(forbiddenKeywords);
|
|
|
|
TableOptionalOnStep<?> s0;
|
|
TablePartitionByStep<?> s1;
|
|
TableOnStep<?> s2;
|
|
s2 = s1 = (TablePartitionByStep<?>) (s0 = left.join(right, join.type, join.hint));
|
|
|
|
switch (join.type) {
|
|
case LEFT_OUTER_JOIN:
|
|
case FULL_OUTER_JOIN:
|
|
case RIGHT_OUTER_JOIN:
|
|
if (parseProKeywordIf("PARTITION BY")) {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
// 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 (join.type == JOIN)
|
|
return s0;
|
|
else
|
|
throw expected("ON", "USING");
|
|
|
|
case CROSS_JOIN:
|
|
|
|
// [#9476] MySQL treats INNER JOIN and CROSS JOIN as the same
|
|
if (parseKeywordIf("ON"))
|
|
return left.join(right).on(parseCondition());
|
|
else if (parseKeywordIf("USING"))
|
|
return parseJoinUsing(left.join(right));
|
|
|
|
// No break
|
|
|
|
default:
|
|
return s0;
|
|
}
|
|
}
|
|
|
|
private final Table<?> parseJoinUsing(TableOnStep<?> join) {
|
|
Table<?> result;
|
|
|
|
parse('(');
|
|
|
|
if (parseIf(')')) {
|
|
result = join.using();
|
|
}
|
|
else {
|
|
result = join.using(Tools.fieldsByName(parseIdentifiers().toArray(EMPTY_NAME)));
|
|
parse(')');
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private final List<SelectFieldOrAsterisk> parseSelectList() {
|
|
List<SelectFieldOrAsterisk> result = new ArrayList<>();
|
|
|
|
do {
|
|
QualifiedAsterisk qa;
|
|
|
|
if (parseIf('*')) {
|
|
if (parseKeywordIf("EXCEPT")) {
|
|
parse('(');
|
|
result.add(DSL.asterisk().except(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(KEYWORD_LOOKUP_IN_SELECT) && !peekKeyword(KEYWORD_LOOKUP_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<Field<?>> parseFieldsOrEmptyParenthesised() {
|
|
parse('(');
|
|
|
|
if (parseIf(')')) {
|
|
return emptyList();
|
|
}
|
|
else {
|
|
List<Field<?>> result = parseList(',', c -> c.parseField());
|
|
parse(')');
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private final List<Field<?>> 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();
|
|
|
|
if (dataType.isBoolean())
|
|
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 (dataType.isOther() && (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 = concatOperator((Field) r, toField(parseCollated()));
|
|
|
|
return r;
|
|
}
|
|
|
|
private final Field<?> concatOperator(Field<?> a1, Field<?> a2) {
|
|
if (a1.getDataType().isArray() && a2.getDataType().isArray())
|
|
return DSL.arrayConcat((Field) a1, (Field) a2);
|
|
else if (a1.getDataType().isBinary() && a2.getDataType().isBinary())
|
|
return DSL.binaryConcat((Field) a1, (Field) a2);
|
|
else
|
|
return DSL.concat(a1, a2);
|
|
}
|
|
|
|
private final FieldOrRow parseCollated() {
|
|
FieldOrRow r = parseOp();
|
|
|
|
if (r instanceof Field) {
|
|
if (parseKeywordIf("COLLATE"))
|
|
r = ((Field) r).collate(parseCollation());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
private final Field<?> parseFieldNumericOpParenthesised() {
|
|
parse('(');
|
|
Field<?> r = toField(parseOp());
|
|
parse(')');
|
|
|
|
return r;
|
|
}
|
|
|
|
private final Field<?> parseFieldParenthesised() {
|
|
parse('(');
|
|
Field<?> r = parseField();
|
|
parse(')');
|
|
|
|
return r;
|
|
}
|
|
|
|
private final <Q extends QueryPart> Q parseFunctionArgs1(Function1<? super Field, ? extends Q> finisher) {
|
|
parse('(');
|
|
Field<?> f1 = parseField();
|
|
parse(')');
|
|
|
|
return finisher.apply(f1);
|
|
}
|
|
|
|
private final <Q extends QueryPart> Q parseFunctionArgs2(
|
|
Function1<? super Field, ? extends Q> finisher1,
|
|
Function2<? super Field, ? super Field, ? extends Q> finisher2
|
|
) {
|
|
parse('(');
|
|
Field<?> f1 = parseField();
|
|
Field<?> f2 = parseIf(',') ? parseField() : null;
|
|
parse(')');
|
|
|
|
return f2 == null ? finisher1.apply(f1) : finisher2.apply(f1, f2);
|
|
}
|
|
|
|
private final <Q extends QueryPart> Q parseFunctionArgs2(Function2<? super Field, ? super Field, ? extends Q> finisher) {
|
|
return parseFunctionArgs2(this::parseField, finisher);
|
|
}
|
|
|
|
private final <Q extends QueryPart> Q parseFunctionArgs2(
|
|
Supplier<? extends Field<?>> argument,
|
|
Function2<? super Field, ? super Field, ? extends Q> finisher
|
|
) {
|
|
parse('(');
|
|
Field<?> f1 = argument.get();
|
|
parse(',');
|
|
Field<?> f2 = argument.get();
|
|
parse(')');
|
|
|
|
return finisher.apply(f1, f2);
|
|
}
|
|
|
|
private final <Q extends QueryPart> Q parseFunctionArgs3(
|
|
Function2<? super Field, ? super Field, ? extends Q> finisher2,
|
|
Function3<? super Field, ? super Field, ? super Field, ? extends Q> 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 extends QueryPart> Q parseFunctionArgs3(Function3<? super Field, ? super Field, ? super Field, ? extends Q> finisher) {
|
|
parse('(');
|
|
Field<?> f1 = parseField();
|
|
parse(',');
|
|
Field<?> f2 = parseField();
|
|
parse(',');
|
|
Field<?> f3 = parseField();
|
|
parse(')');
|
|
|
|
return finisher.apply(f1, f2, f3);
|
|
}
|
|
|
|
private final <Q extends QueryPart> Q parseFunctionArgs4(Function4<? super Field, ? super Field, ? super Field, ? super Field, ? extends Q> 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 parseEmptyParensOr(Predicate<? super ParseContext> p) {
|
|
return parse('(') && (parseIf(')') || p.test(this) && 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 parseOp() {
|
|
FieldOrRow l = parseSum();
|
|
|
|
if (l instanceof Field)
|
|
for (;;)
|
|
if (parseIf("<<"))
|
|
l = ((Field) l).shl((Field) parseSum());
|
|
else if (parseIf(">>"))
|
|
l = ((Field) l).shr((Field) parseSum());
|
|
else if (parseIf("->>")) {
|
|
Field r = (Field) parseSum();
|
|
|
|
// [#10018] We cannot really know reliably whether this is a
|
|
// index or attribute access. Let's default to the
|
|
// more popular attribute access for now. Also,
|
|
// JSONB is likely more popular than JSON.
|
|
if (r.getDataType().isNumeric())
|
|
if (((Field) l).getType() == JSON.class)
|
|
l = jsonGetElementAsText((Field) l, r);
|
|
else
|
|
l = jsonbGetElementAsText((Field) l, r);
|
|
else
|
|
if (((Field) l).getType() == JSON.class)
|
|
l = jsonGetAttributeAsText((Field) l, r);
|
|
else
|
|
l = jsonbGetAttributeAsText((Field) l, r);
|
|
}
|
|
else if (parseIf("->")) {
|
|
Field r = (Field) parseSum();
|
|
|
|
// [#10018] We cannot really know reliably whether this is a
|
|
// index or attribute access. Let's default to the
|
|
// more popular attribute access for now. Also,
|
|
// JSONB is likely more popular than JSON.
|
|
if (r.getDataType().isNumeric())
|
|
if (((Field) l).getType() == JSON.class)
|
|
l = jsonGetElement((Field) l, r);
|
|
else
|
|
l = jsonbGetElement((Field) l, r);
|
|
else
|
|
if (((Field) l).getType() == JSON.class)
|
|
l = jsonGetAttribute((Field) l, r);
|
|
else
|
|
l = jsonbGetAttribute((Field) l, r);
|
|
}
|
|
else if (parseIf("??") || parseIf("?"))
|
|
if (((Field) l).getType() == JSON.class)
|
|
return jsonKeyExists((Field) l, (Field) parseSum());
|
|
else
|
|
return jsonbKeyExists((Field) l, (Field) parseSum());
|
|
else
|
|
break;
|
|
|
|
return l;
|
|
}
|
|
|
|
private final FieldOrRow parseSum() {
|
|
FieldOrRow r = parseFactor();
|
|
|
|
if (r instanceof Field)
|
|
for (;;)
|
|
if (parseIf('+'))
|
|
r = parseSumRightOperand(r, true);
|
|
else if (!peek("->") && parseIf('-'))
|
|
r = parseSumRightOperand(r, false);
|
|
else
|
|
break;
|
|
|
|
return r;
|
|
}
|
|
|
|
private final Field parseSumRightOperand(FieldOrRow r, boolean add) {
|
|
Field rhs = (Field) parseFactor();
|
|
DatePart part;
|
|
|
|
if (parseProKeywordIf("YEAR", "YEARS"))
|
|
part = DatePart.YEAR;
|
|
else if (parseProKeywordIf("MONTH", "MONTHS"))
|
|
part = DatePart.MONTH;
|
|
else if (parseProKeywordIf("DAY", "DAYS"))
|
|
part = DatePart.DAY;
|
|
else if (parseProKeywordIf("HOUR", "HOURS"))
|
|
part = DatePart.HOUR;
|
|
else if (parseProKeywordIf("MINUTE", "MINUTES"))
|
|
part = DatePart.MINUTE;
|
|
else if (parseProKeywordIf("SECOND", "SECONDS"))
|
|
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 (parseProKeywordIf("CONNECT_BY_ROOT")) {
|
|
|
|
|
|
|
|
}
|
|
|
|
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(parseOp()), 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(DatePart.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_APPEND", "arrayPushBack"))
|
|
return parseFunctionArgs2((f1, f2) -> arrayAppend((Field<Void[]>) f1, (Field<Void>) f2));
|
|
else if (parseFunctionNameIf("ARRAY_CAT", "ARRAY_CONCAT", "arrayConcat"))
|
|
return parseFunctionArgs2((f1, f2) -> arrayConcat(f1, f2));
|
|
else if (parseFunctionNameIf("ARRAY_GET", "arrayElement"))
|
|
return parseFunctionArgs2((f1, f2) -> arrayGet(f1, f2));
|
|
else if (parseFunctionNameIf("ARRAY_FILTER", "arrayFilter"))
|
|
return parseArrayLambdaFunction(DSL::arrayFilter);
|
|
else if (parseFunctionNameIf("ARRAY_MAP", "arrayMap", "ARRAY_TRANSFORM"))
|
|
return parseArrayLambdaFunction(DSL::arrayMap);
|
|
else if (parseFunctionNameIf("ARRAY_ALL_MATCH", "arrayAll", "ALL_MATCH"))
|
|
return parseArrayLambdaFunction(DSL::arrayAllMatch);
|
|
else if (parseFunctionNameIf("ARRAY_ANY_MATCH", "arrayExists", "ANY_MATCH"))
|
|
return parseArrayLambdaFunction(DSL::arrayAnyMatch);
|
|
else if (parseFunctionNameIf("ARRAY_NONE_MATCH"))
|
|
return parseArrayLambdaFunction(DSL::arrayNoneMatch);
|
|
else if (parseFunctionNameIf("ARRAY_MAP", "arrayMap", "ARRAY_TRANSFORM"))
|
|
return parseArrayLambdaFunction(DSL::arrayMap);
|
|
else if (parseFunctionNameIf("ARRAY_OVERLAP", "ARRAYS_OVERLAP"))
|
|
return parseFunctionArgs2((f1, f2) -> arrayOverlap((Field<Void[]>) f1, (Field<Void[]>) f2));
|
|
else if (parseFunctionNameIf("ARRAY_PREPEND"))
|
|
return parseFunctionArgs2((f1, f2) -> arrayPrepend((Field<Void>) f1, (Field<Void[]>) f2));
|
|
else if (parseFunctionNameIf("arrayPushFront"))
|
|
return parseFunctionArgs2((f1, f2) -> arrayPrepend((Field<Void>) f2, (Field<Void[]>) f1));
|
|
else if (parseFunctionNameIf("ARRAY_REMOVE"))
|
|
return parseFunctionArgs2((f1, f2) -> arrayRemove((Field<Void[]>) f1, (Field<Void>) f2));
|
|
else if (parseFunctionNameIf("ARRAY_REPLACE"))
|
|
return parseFunctionArgs3((f1, f2, f3) -> arrayReplace((Field<Void[]>) f1, (Field<Void>) f2, (Field<Void>) f3));
|
|
else if ((field = parseFieldArrayConstructIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("ADD"))
|
|
return parseFunctionArgs2(Field::add);
|
|
else if (parseFunctionNameIf("AND"))
|
|
return parseFunctionArgs2((f1, f2) -> and(condition(f1), condition(f2)));
|
|
|
|
break;
|
|
|
|
case 'B':
|
|
if (parseFunctionNameIf("BIT_LENGTH"))
|
|
return parseFunctionArgs1(f -> binary(f) ? binaryBitLength(f) : bitLength(f));
|
|
else if (parseFunctionNameIf("BITGET", "BIT_GET", "bitTest"))
|
|
return parseFunctionArgs2(DSL::bitGet);
|
|
else if (parseFunctionNameIf("BITSET", "BIT_SET"))
|
|
return parseFunctionArgs3(DSL::bitSet, DSL::bitSet);
|
|
else if (parseFunctionNameIf("BITCOUNT", "BIT_COUNT"))
|
|
return bitCount((Field) parseFieldNumericOpParenthesised());
|
|
else if (parseKeywordIf("BIT_LSHIFT"))
|
|
return parseFunctionArgs2(() -> toField(parseOp()), (f1, f2) -> shl(f1, f2));
|
|
else if (parseKeywordIf("BIT_RSHIFT"))
|
|
return parseFunctionArgs2(() -> toField(parseOp()), (f1, f2) -> shr(f1, f2));
|
|
else if (parseFunctionNameIf("BYTE_LENGTH"))
|
|
return octetLength((Field) parseFieldParenthesised());
|
|
else if ((field = parseFieldBitwiseFunctionIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("BIN_TO_UUID"))
|
|
return parseFunctionArgs1(DSL::binToUuid);
|
|
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", "currentDatabase") && 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) -> binary(f1, f2) ? DSL.binaryPosition(f2, f1) : DSL.position(f2, f1),
|
|
(f1, f2, f3) -> binary(f1, f2) ? DSL.binaryPosition(f2, f1, 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 (parseFunctionNameIf("CBRT"))
|
|
return parseFunctionArgs1(DSL::cbrt);
|
|
else if (parseFunctionNameIf("CONTAINS"))
|
|
return parseFunctionArgs2((f1, f2) -> f1.contains(f2));
|
|
else if ((field = parseNextvalCurrvalIf(SequenceMethod.CURRVAL)) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("CENTURY"))
|
|
return century(parseFieldParenthesised());
|
|
|
|
else if (parseKeywordIf("CURRENT_DATE", "CURRENT DATE") && parseEmptyParensIf())
|
|
return currentDate();
|
|
else if (parseKeywordIf("CURRENT_TIMESTAMP", "CURRENT TIMESTAMP")) {
|
|
Field<Integer> precision = null;
|
|
if (parseIf('('))
|
|
if (!parseIf(')')) {
|
|
precision = (Field<Integer>) parseField();
|
|
parse(')');
|
|
}
|
|
return precision != null ? currentTimestamp(precision) : currentTimestamp();
|
|
}
|
|
else if (parseKeywordIf("CURRENT_TIME", "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 (parseProKeywordIf("CONNECT_BY_ISCYCLE")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProKeywordIf("CONNECT_BY_ISLEAF")) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 (parseFunctionNameIf("DATE_PART_YEAR"))
|
|
return year(parseFieldParenthesised());
|
|
|
|
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_();
|
|
|
|
else if ((field = parseFieldSubstringIf()) != null)
|
|
return field;
|
|
else if (parseProFunctionNameIf("DBMS_LOB.INSTR")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("DBMS_LOB.GETLENGTH")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseFunctionNameIf("DIV", "DIVIDE"))
|
|
return parseFunctionArgs2(Field::div);
|
|
|
|
break;
|
|
|
|
case 'E':
|
|
|
|
// [#6704] PostgreSQL E'...' escaped string literals
|
|
if (characterNext() == '\'')
|
|
return inline(parseStringLiteral());
|
|
else if ((field = parseFieldExtractIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("ELEMENT_AT"))
|
|
return parseFunctionArgs2(DSL::arrayGet);
|
|
else if (parseFunctionNameIf("ENDS_WITH", "endsWith"))
|
|
return parseFunctionArgs2((f1, f2) -> f1.endsWith(f2));
|
|
else if (parseFunctionNameIf("EXP"))
|
|
return exp((Field) parseFieldNumericOpParenthesised());
|
|
else if (parseFunctionNameIf("EPOCH"))
|
|
return epoch(parseFieldParenthesised());
|
|
else if ((field = parseFieldChooseIf()) != null)
|
|
return field;
|
|
|
|
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 (parseFunctionNameIf("generateUUIDv4") && parseEmptyParensOr(c -> c.parseField() != null))
|
|
return uuid();
|
|
else if (parseFunctionNameIf("GET_BIT", "GETBIT"))
|
|
return parseFunctionArgs2(DSL::bitGet);
|
|
|
|
else if ((field = parseFieldGreatestIf()) != null)
|
|
return field;
|
|
else if (parseProFunctionNameIf("GROUP_ID") && parseEmptyParens()) {
|
|
|
|
|
|
|
|
}
|
|
else if ((field = parseFieldGroupingIdIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("GROUPING"))
|
|
return grouping(parseFieldParenthesised());
|
|
else if (parseProFunctionNameIf("GEOMETRY::STGEOMFROMWKB", "GEOGRAPHY::STGEOMFROMWKB")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("GEOMETRY::STGEOMFROMTEXT", "GEOGRAPHY::STGEOMFROMTEXT")) {
|
|
|
|
|
|
|
|
}
|
|
else if ((field = parseFieldBitwiseFunctionIf()) != null)
|
|
return field;
|
|
|
|
break;
|
|
|
|
case 'H':
|
|
if (parseFunctionNameIf("HOUR"))
|
|
return hour(parseFieldParenthesised());
|
|
|
|
else if (parseFunctionNameIf("HASH_MD5"))
|
|
return parseFunctionArgs1(f -> binary(f) ? binaryMd5(f) : md5(f));
|
|
else if (parseFunctionNameIf("HEX"))
|
|
return toHex((Field) parseFieldParenthesised());
|
|
|
|
break;
|
|
|
|
case 'I':
|
|
|
|
// [#8792] TODO: Support parsing interval expressions
|
|
if ((field = parseFieldIntervalLiteralIf(true)) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("ISO_DAY_OF_WEEK"))
|
|
return isoDayOfWeek(parseFieldParenthesised());
|
|
else if (parseFunctionNameIf("INSTR"))
|
|
return parseFunctionArgs3(
|
|
(f1, f2) -> binary(f1, f2) ? DSL.binaryPosition(f1, f2) : DSL.position(f1, f2),
|
|
(f1, f2, f3) -> binary(f1, f2) ? DSL.binaryPosition(f1, f2, f3) : DSL.position(f1, f2, f3)
|
|
);
|
|
else if (parseFunctionNameIf("INSERT"))
|
|
return parseFunctionArgs4(DSL::insert);
|
|
else if (parseFunctionNameIf("IFNULL"))
|
|
return parseFunctionArgs2((f1, f2) -> ifnull((Field<?>) f1, (Field<?>) f2));
|
|
else if (parseFunctionNameIf("ISJSON"))
|
|
return parseFunctionArgs1(f -> case_(f.isJson()).when(trueCondition(), one()).when(falseCondition(), zero()));
|
|
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;
|
|
else if (parseFunctionNameIf("JSON_ARRAY_LENGTH", "JSON_LENGTH", "JSONARRAYLENGTH"))
|
|
return parseFunctionArgs1(DSL::jsonArrayLength);
|
|
else if (parseFunctionNameIf("JSON_KEYS", "JSONExtractKeys"))
|
|
return parseFunctionArgs1(DSL::jsonKeys);
|
|
else if (parseFunctionNameIf("JSON_KEY_EXISTS"))
|
|
return parseFunctionArgs2(DSL::jsonKeyExists);
|
|
else if (parseFunctionNameIf("JSON_INSERT"))
|
|
return parseFunctionArgs3(DSL::jsonInsert);
|
|
else if (parseFunctionNameIf("JSON_REMOVE"))
|
|
return parseFunctionArgs2(DSL::jsonRemove);
|
|
else if (parseFunctionNameIf("JSON_REPLACE"))
|
|
return parseFunctionArgs3(DSL::jsonReplace);
|
|
else if (parseFunctionNameIf("JSON_SET"))
|
|
return parseFunctionArgs3(DSL::jsonSet);
|
|
else if (parseFunctionNameIf("JSON_VALID"))
|
|
return parseFunctionArgs1(f -> case_(f.isJson()).when(trueCondition(), one()).when(falseCondition(), zero()));
|
|
else if (parseFunctionNameIf("JSONB_ARRAY_LENGTH"))
|
|
return parseFunctionArgs1(DSL::jsonbArrayLength);
|
|
else if (parseFunctionNameIf("JSONB_KEYS"))
|
|
return parseFunctionArgs1(DSL::jsonbKeys);
|
|
else if (parseFunctionNameIf("JSONB_KEY_EXISTS"))
|
|
return parseFunctionArgs2(DSL::jsonbKeyExists);
|
|
else if (parseFunctionNameIf("JSONB_INSERT"))
|
|
return parseFunctionArgs3(DSL::jsonbInsert);
|
|
else if (parseFunctionNameIf("JSONB_REMOVE"))
|
|
return parseFunctionArgs2(DSL::jsonbRemove);
|
|
else if (parseFunctionNameIf("JSONB_REPLACE"))
|
|
return parseFunctionArgs3(DSL::jsonbReplace);
|
|
else if (parseFunctionNameIf("JSONB_SET"))
|
|
return parseFunctionArgs3(DSL::jsonbSet);
|
|
|
|
break;
|
|
|
|
case 'L':
|
|
if (parseFunctionNameIf("LOWER", "LCASE"))
|
|
return lower((Field) parseFieldParenthesised());
|
|
else if (parseFunctionNameIf("LPAD", "leftPad"))
|
|
return parseFunctionArgs3(DSL::lpad, DSL::lpad);
|
|
else if (parseFunctionNameIf("LTRIM"))
|
|
return parseFunctionArgs2(DSL::ltrim, (f1, f2) -> binary(f1, f2) ? binaryLtrim(f1, f2) : ltrim(f1, f2));
|
|
else if (parseFunctionNameIf("LEFT"))
|
|
return parseFunctionArgs2(DSL::left);
|
|
else if (parseFunctionNameIf("LENGTH", "LEN"))
|
|
return parseFunctionArgs1(f -> binary(f) ? binaryLength(f) : length(f));
|
|
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 (parseProKeywordIf("LEVEL")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseKeywordIf("LSHIFT", "LEFT_SHIFT"))
|
|
return parseFunctionArgs2(() -> toField(parseOp()), (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", "MODULO"))
|
|
return parseFunctionArgs2(Field::mod);
|
|
else if (parseFunctionNameIf("MULTIPLY"))
|
|
return parseFunctionArgs2(Field::mul);
|
|
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 parseFunctionArgs1(f -> binary(f) ? binaryMd5(f) : md5(f));
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else if (parseFunctionNameIf("MINUS"))
|
|
return parseFunctionArgs2(Field::sub);
|
|
|
|
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<Integer> precision = (Field<Integer>) parseField();
|
|
parse(')');
|
|
return now(precision);
|
|
}
|
|
else if (parseFunctionNameIf("NEG", "NEGATE"))
|
|
return parseFunctionArgs1(DSL::neg);
|
|
else if (parseFunctionNameIf("NONE_MATCH"))
|
|
return parseArrayLambdaFunction(DSL::arrayNoneMatch);
|
|
|
|
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 parseFunctionArgs1(f -> binary(f) ? binaryOctetLength(f) : octetLength(f));
|
|
else if ((field = parseFieldObjectConstructIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("OBJECT_KEYS"))
|
|
return parseFunctionArgs1(DSL::jsonKeys);
|
|
else if (parseFunctionNameIf("OR"))
|
|
return parseFunctionArgs2((f1, f2) -> or(condition(f1), condition(f2)));
|
|
|
|
break;
|
|
|
|
case 'P':
|
|
if ((field = parseFieldPositionIf()) != null)
|
|
return field;
|
|
else if ((field = parseFieldPercentRankIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("POWER", "POW"))
|
|
return parseFunctionArgs2(() -> toField(parseOp()), DSL::power);
|
|
else if (parseFunctionNameIf("PI") && parseEmptyParens())
|
|
return pi();
|
|
|
|
else if (parseProKeywordIf("PRIOR")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseFunctionNameIf("PLUS"))
|
|
return parseFunctionArgs2(Field::add);
|
|
|
|
break;
|
|
|
|
case 'Q':
|
|
if (characterNext() == '\'')
|
|
return inline(parseStringLiteral());
|
|
|
|
else if (parseFunctionNameIf("QUARTER"))
|
|
return quarter(parseFieldParenthesised());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case 'R':
|
|
if (parseFunctionNameIf("REPLACE", "replaceAll"))
|
|
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", "rightPad"))
|
|
return parseFunctionArgs3(DSL::rpad, DSL::rpad);
|
|
else if (parseFunctionNameIf("RTRIM"))
|
|
return parseFunctionArgs2(DSL::rtrim, (f1, f2) -> binary(f1, f2) ? binaryRtrim(f1, f2) : rtrim(f1, f2));
|
|
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 (parseProKeywordIf("ROWNUM")) {
|
|
|
|
|
|
|
|
}
|
|
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", "RIGHT_SHIFT"))
|
|
return parseFunctionArgs2(() -> toField(parseOp()), (f1, f2) -> shr(f1, f2));
|
|
else if (parseFunctionNameIf("ROOT"))
|
|
return parseFunctionArgs2(DSL::sqrt, DSL::root);
|
|
else if (parseFunctionNameIf("ROW"))
|
|
return parseTuple();
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
if ((field = parseFieldSubstringIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("SUBSTRING_INDEX", "substringIndex"))
|
|
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("STARTS_WITH", "startsWith"))
|
|
return parseFunctionArgs2((f1, f2) -> f1.startsWith(f2));
|
|
else if (parseFunctionNameIf("SCHEMA") && parseEmptyParensIf())
|
|
return currentSchema();
|
|
else if (parseFunctionNameIf("STRREVERSE"))
|
|
return reverse((Field) parseFieldParenthesised());
|
|
else if (parseFunctionNameIf("STRTOK"))
|
|
return parseFunctionArgs3(DSL::splitPart);
|
|
else if (parseFunctionNameIf("SYSUUID") && parseEmptyParensIf())
|
|
return uuid();
|
|
else if (parseFunctionNameIf("SET_BIT", "SETBIT"))
|
|
return parseFunctionArgs3(DSL::bitSet, DSL::bitSet);
|
|
|
|
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(parseOp()), (f1, f2) -> shl(f1, f2));
|
|
else if (parseKeywordIf("SHR", "SHIFTRIGHT"))
|
|
return parseFunctionArgs2(() -> toField(parseOp()), (f1, f2) -> shr(f1, f2));
|
|
else if ((field = parseFieldSysConnectByPathIf()) != null)
|
|
return field;
|
|
else if ((field = parseFieldCastIf()) != null)
|
|
return field;
|
|
else if (parseProFunctionNameIf("ST_AREA")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_AREA")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ASBINARY", "ST_ASWKB")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ASTEXT")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_CENTROID")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_CENTROID")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_DIFFERENCE")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_DIFFERENCE")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_DISTANCE")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_DISTANCE")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ENDPOINT")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_EXTERIORRING")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_GEOMETRYN")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_GEOMETRYTYPE")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_GEOMFROMWKB")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_GEOMFROMTEXT", "SDO_GEOMETRY")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_INTERIORRINGN")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_INTERSECTION")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_INTERSECTION")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_LENGTH")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_LENGTH")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_NUMINTERIORRING", "ST_NUMINTERIORRINGS", "ST_NINTERIORRINGS")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_NUMGEOMETRIES", "SDO_UTIL.GETNUMELEM")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_NPOINTS", "ST_NUMPOINTS")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_PERIMETER")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_POINTN")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_SRID")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_STARTPOINT")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_UNION")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_UNION")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_MIN_MBR_ORDINATE")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("SDO_GEOM.SDO_MAX_MBR_ORDINATE")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_X")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_XMIN")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_XMAX")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_Y")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_YMIN")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_YMAX")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_Z")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ZMIN")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseProFunctionNameIf("ST_ZMAX")) {
|
|
|
|
|
|
|
|
}
|
|
else if (parseFunctionNameIf("SUB", "SUBTRACT"))
|
|
return parseFunctionArgs2(Field::sub);
|
|
|
|
break;
|
|
|
|
case 'T':
|
|
if ((field = parseBooleanValueExpressionIf()) != null)
|
|
return field;
|
|
|
|
else if ((field = parseFieldTrimIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("trimBoth"))
|
|
return parseFunctionArgs1(DSL::trim);
|
|
else if (parseFunctionNameIf("trimLeft"))
|
|
return parseFunctionArgs1(DSL::ltrim);
|
|
else if (parseFunctionNameIf("trimRight"))
|
|
return parseFunctionArgs1(DSL::rtrim);
|
|
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;
|
|
else if ((field = parseFieldCastIf()) != null)
|
|
return field;
|
|
else if (parseFunctionNameIf("TRANSFORM"))
|
|
return parseArrayLambdaFunction(DSL::arrayMap);
|
|
else if (parseDialect() == SQLITE && parseFunctionNameIf("TOTAL"))
|
|
return coalesce(
|
|
parseAggregateFunctionIf(false,
|
|
(AggregateFunction<?>) parseGeneralSetFunctionIf(ComputationalOperation.SUM)
|
|
),
|
|
inline(BigDecimal.ZERO)
|
|
);
|
|
|
|
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("UUID_TO_BIN", "UUIDStringToNum"))
|
|
return parseFunctionArgs1(DSL::uuidToBin);
|
|
else if (parseFunctionNameIf("UUIDNumToString"))
|
|
return parseFunctionArgs1(DSL::binToUuid);
|
|
|
|
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", "widthBucket"))
|
|
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;
|
|
else if (parseFunctionNameIf("XOR"))
|
|
return parseFunctionArgs2((f1, f2) -> xor(condition(f1), condition(f2)));
|
|
|
|
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<Field<?>> 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 (parseProFunctionNameIf("SYS_CONNECT_BY_PATH")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldBitwiseFunctionIf() {
|
|
int p = position();
|
|
|
|
char c0 = characterUpper();
|
|
char c1 = character(p + 1);
|
|
char c2 = character(p + 2);
|
|
boolean agg = false;
|
|
|
|
if (c0 == 'B') {
|
|
if (c1 != 'I' && c1 != 'i')
|
|
return null;
|
|
if (c2 != 'T' && c2 != 't' && c2 != 'N' && c2 != 'n')
|
|
return null;
|
|
}
|
|
else {
|
|
if (c1 != 'r')
|
|
return null;
|
|
if (c2 != 'o')
|
|
return null;
|
|
}
|
|
|
|
if (parseKeywordIf("BIT_AND") ||
|
|
parseKeywordIf("BITWISE_AND") ||
|
|
parseKeywordIf("BITAND") ||
|
|
parseKeywordIf("BIN_AND") ||
|
|
(agg = parseKeywordIf("BIT_AND_AGG")) ||
|
|
(agg = parseKeywordIf("BITWISE_AND_AGG")) ||
|
|
(agg = parseKeywordIf("BITAND_AGG")) ||
|
|
(agg = parseKeywordIf("BIN_AND_AGG")) ||
|
|
(agg = parseFunctionNameIf("groupBitAnd"))) {
|
|
parse('(');
|
|
if (parseKeywordIf("DISTINCT", "ALL"))
|
|
agg = true;
|
|
Field<?> x = toField(parseOp());
|
|
|
|
if (agg && parse(')') || parseIf(')'))
|
|
return parseAggregateFunctionIf(false, bitAndAgg((Field) x));
|
|
|
|
parse(',');
|
|
Field<?> y = toField(parseOp());
|
|
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")) ||
|
|
(agg = parseFunctionNameIf("groupBitNand"))) {
|
|
parse('(');
|
|
if (parseKeywordIf("DISTINCT", "ALL"))
|
|
agg = true;
|
|
Field<?> x = toField(parseOp());
|
|
|
|
if (agg && parse(')') || parseIf(')'))
|
|
return parseAggregateFunctionIf(false, bitNandAgg((Field) x));
|
|
|
|
parse(',');
|
|
Field<?> y = toField(parseOp());
|
|
parse(')');
|
|
|
|
return bitNand((Field) x, (Field) y);
|
|
}
|
|
else if (parseKeywordIf("BIT_OR") ||
|
|
parseKeywordIf("BITWISE_OR") ||
|
|
parseKeywordIf("BITOR") ||
|
|
parseKeywordIf("BIN_OR") ||
|
|
(agg = parseKeywordIf("BIT_OR_AGG")) ||
|
|
(agg = parseKeywordIf("BITWISE_OR_AGG")) ||
|
|
(agg = parseKeywordIf("BITOR_AGG")) ||
|
|
(agg = parseKeywordIf("BIN_OR_AGG")) ||
|
|
(agg = parseKeywordIf("groupBitOr"))) {
|
|
parse('(');
|
|
if (parseKeywordIf("DISTINCT", "ALL"))
|
|
agg = true;
|
|
Field<?> x = toField(parseOp());
|
|
|
|
if (agg && parse(')') || parseIf(')'))
|
|
return parseAggregateFunctionIf(false, bitOrAgg((Field) x));
|
|
|
|
parse(',');
|
|
Field<?> y = toField(parseOp());
|
|
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")) ||
|
|
(agg = parseKeywordIf("groupBitNor"))) {
|
|
parse('(');
|
|
if (parseKeywordIf("DISTINCT", "ALL"))
|
|
agg = true;
|
|
Field<?> x = toField(parseOp());
|
|
|
|
if (agg && parse(')') || parseIf(')'))
|
|
return parseAggregateFunctionIf(false, bitNorAgg((Field) x));
|
|
|
|
parse(',');
|
|
Field<?> y = toField(parseOp());
|
|
parse(')');
|
|
|
|
return bitNor((Field) x, (Field) y);
|
|
}
|
|
else if (parseKeywordIf("BIT_XOR") ||
|
|
parseKeywordIf("BITWISE_XOR") ||
|
|
parseKeywordIf("BITXOR") ||
|
|
parseKeywordIf("BIN_XOR") ||
|
|
(agg = parseKeywordIf("BIT_XOR_AGG")) ||
|
|
(agg = parseKeywordIf("BITXOR_AGG")) ||
|
|
(agg = parseKeywordIf("BIN_XOR_AGG")) ||
|
|
(agg = parseKeywordIf("groupBitXor"))) {
|
|
parse('(');
|
|
Field<?> x = toField(parseOp());
|
|
|
|
if (agg && parse(')') || parseIf(')'))
|
|
return parseAggregateFunctionIf(false, bitXorAgg((Field) x));
|
|
|
|
parse(',');
|
|
Field<?> y = toField(parseOp());
|
|
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")) ||
|
|
(agg = parseKeywordIf("groupBitXnor"))) {
|
|
parse('(');
|
|
Field<?> x = toField(parseOp());
|
|
|
|
if (agg && parse(')') || parseIf(')'))
|
|
return parseAggregateFunctionIf(false, bitXNorAgg((Field) x));
|
|
|
|
parse(',');
|
|
Field<?> y = toField(parseOp());
|
|
parse(')');
|
|
|
|
return bitXNor((Field) x, (Field) y);
|
|
}
|
|
else if (parseKeywordIf("BIT_NOT", "BITNOT", "BIN_NOT", "BITWISE_NOT")) {
|
|
parse('(');
|
|
Field<?> x = toField(parseOp());
|
|
parse(')');
|
|
|
|
return bitNot((Field) x);
|
|
}
|
|
else if (parseKeywordIf("BIN_SHL", "BITSHIFTLEFT", "BITWISE_LEFT_SHIFT")) {
|
|
parse('(');
|
|
Field<?> x = toField(parseOp());
|
|
parse(',');
|
|
Field<?> y = toField(parseOp());
|
|
parse(')');
|
|
|
|
return shl((Field) x, (Field) y);
|
|
}
|
|
else if (parseKeywordIf("BIN_SHR", "BITSHIFTRIGHT", "BITWISE_RIGHT_SHIFT")) {
|
|
parse('(');
|
|
Field<?> x = toField(parseOp());
|
|
parse(',');
|
|
Field<?> y = toField(parseOp());
|
|
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<XML> value = (Field<XML>) 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<Field<?>> 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<Field<?>> content = new ArrayList<>();
|
|
|
|
while (parseIf(',')) {
|
|
if (attr == null && parseKeywordIf("XMLATTRIBUTES")) {
|
|
parse('(');
|
|
List<Field<?>> attrs = parseAliasedXMLContent();
|
|
parse(')');
|
|
attr = xmlattributes(attrs);
|
|
}
|
|
else
|
|
content.add(parseField());
|
|
}
|
|
parse(')');
|
|
|
|
return attr == null
|
|
? xmlelement(name, content)
|
|
: xmlelement(name, attr, content);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldXMLDocumentIf() {
|
|
if (parseProFunctionNameIf("XMLDOCUMENT")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldXMLPIIf() {
|
|
if (parseFunctionNameIf("XMLPI")) {
|
|
parse('(');
|
|
parseKeyword("NAME");
|
|
Name target = parseIdentifier();
|
|
Field<?> content = parseIf(',') ? parseField() : null;
|
|
parse(')');
|
|
return content == null ? xmlpi(target) : xmlpi(target, content);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldXMLForestIf() {
|
|
if (parseFunctionNameIf("XMLFOREST")) {
|
|
parse('(');
|
|
List<Field<?>> content = parseAliasedXMLContent();
|
|
parse(')');
|
|
|
|
return xmlforest(content);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldXMLParseIf() {
|
|
if (parseFunctionNameIf("XMLPARSE")) {
|
|
parse('(');
|
|
DocumentOrContent documentOrContent;
|
|
|
|
if (parseKeywordIf("DOCUMENT"))
|
|
documentOrContent = DocumentOrContent.DOCUMENT;
|
|
else if (parseKeywordIf("CONTENT"))
|
|
documentOrContent = DocumentOrContent.CONTENT;
|
|
else
|
|
throw expected("CONTENT", "DOCUMENT");
|
|
|
|
Field<String> xml = (Field<String>) parseField();
|
|
parse(')');
|
|
|
|
return documentOrContent == DocumentOrContent.DOCUMENT
|
|
? xmlparseDocument(xml)
|
|
: xmlparseContent(xml);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldXMLQueryIf() {
|
|
if (parseFunctionNameIf("XMLQUERY")) {
|
|
parse('(');
|
|
Field<String> xpath = (Field<String>) parseField();
|
|
XMLPassingMechanism m = parseXMLPassingMechanism();
|
|
Field<XML> xml = (Field<XML>) parseField();
|
|
parseKeywordIf("RETURNING CONTENT");
|
|
parse(')');
|
|
|
|
if (m == BY_REF)
|
|
return xmlquery(xpath).passingByRef(xml);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else
|
|
return xmlquery(xpath).passing(xml);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final XMLPassingMechanism parseXMLPassingMechanism() {
|
|
XMLPassingMechanism result = parseXMLPassingMechanismIf();
|
|
|
|
if (result == null)
|
|
throw expected("PASSING");
|
|
|
|
return result;
|
|
}
|
|
|
|
private final XMLPassingMechanism parseXMLPassingMechanismIf() {
|
|
if (!parseKeywordIf("PASSING"))
|
|
return null;
|
|
else if (!parseKeywordIf("BY"))
|
|
return XMLPassingMechanism.DEFAULT;
|
|
else if (parseKeywordIf("REF"))
|
|
return BY_REF;
|
|
else if (parseKeywordIf("VALUE"))
|
|
return BY_VALUE;
|
|
else
|
|
throw expected("REF", "VALUE");
|
|
}
|
|
|
|
private final List<Field<?>> parseAliasedXMLContent() {
|
|
List<Field<?>> result = new ArrayList<>();
|
|
|
|
do {
|
|
Field<?> field = parseField();
|
|
|
|
if (parseKeywordIf("AS"))
|
|
field = field.as(parseIdentifier(true, false));
|
|
|
|
result.add(field);
|
|
}
|
|
while (parseIf(','));
|
|
return result;
|
|
}
|
|
|
|
private final AggregateFilterStep<?> parseXMLAggFunctionIf() {
|
|
if (parseFunctionNameIf("XMLAGG")) {
|
|
XMLAggOrderByStep<?> s1;
|
|
AggregateFilterStep<?> s2;
|
|
|
|
parse('(');
|
|
parseKeywordIf("ALL");
|
|
s2 = s1 = xmlagg((Field<XML>) 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<String> path = (Field<String>) parseField();
|
|
|
|
JSONValueOnStep<?> s1 = jsonValue(json, path);
|
|
JSONValue.Behaviour behaviour = parseJSONValueBehaviourIf();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DataType<?> returning = parseJSONReturningIf();
|
|
parse(')');
|
|
return returning == null ? s1 : s1.returning(returning);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final JSONValue.Behaviour parseJSONValueBehaviourIf() {
|
|
if (parseProKeywordIf("ERROR"))
|
|
return JSONValue.Behaviour.ERROR;
|
|
else if (parseProKeywordIf("NULL"))
|
|
return JSONValue.Behaviour.NULL;
|
|
else if (parseProKeywordIf("DEFAULT"))
|
|
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<JSONEntry<?>> entries = parseList(',', ctx -> {
|
|
Field key = parseField();
|
|
parse(':');
|
|
return key(key).value(parseField());
|
|
});
|
|
|
|
parse('}');
|
|
return jsonObject(entries);
|
|
}
|
|
else if (parseIf('[')) {
|
|
if (parseIf(']'))
|
|
return jsonArray();
|
|
|
|
List<Field<?>> 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<Field<?>> 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('(');
|
|
boolean distinct = parseSetQuantifier();
|
|
result = s3 = s2 = s1 = jsonb
|
|
? distinct ? jsonbArrayAggDistinct(parseField()) : jsonbArrayAgg(parseField())
|
|
: distinct ? jsonArrayAggDistinct(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<?> parseArrayLambdaFunction(Function2<Field, Lambda1, Field> function) {
|
|
parse('(');
|
|
Field f;
|
|
Lambda1 l = parseLambdaIf(c -> c.parseField());
|
|
|
|
if (l != null && parse(',')) {
|
|
f = parseField();
|
|
}
|
|
else {
|
|
f = parseField();
|
|
parse(',');
|
|
l = parseLambda(c -> c.parseField());
|
|
}
|
|
|
|
parse(')');
|
|
return function.apply(f, l);
|
|
}
|
|
|
|
private final Lambda1<?, ?> parseLambda(Function<? super ParseContext, ? extends Field<?>> field) {
|
|
Lambda1<?, ?> l = parseLambdaIf(field);
|
|
|
|
if (l == null)
|
|
throw expected("Lambda");
|
|
|
|
return l;
|
|
}
|
|
|
|
private final Lambda1<?, ?> parseLambdaIf(Function<? super ParseContext, ? extends Field<?>> field) {
|
|
int p = position();
|
|
Name e = parseIdentifierIf();
|
|
|
|
if (e != null && parseIf("->"))
|
|
return lambda(field(e), (Field<?>) field.apply(this));
|
|
else
|
|
position(p);
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldArrayConstructIf() {
|
|
boolean absentOnNull = false;
|
|
|
|
if ((parseFunctionNameIf("ARRAY_CONSTRUCT") || (absentOnNull = parseFunctionNameIf("ARRAY_CONSTRUCT_COMPACT"))) && requireProEdition()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldObjectConstructIf() {
|
|
boolean nullOnNull = false;
|
|
|
|
if ((parseFunctionNameIf("OBJECT_CONSTRUCT") || (nullOnNull = parseFunctionNameIf("OBJECT_CONSTRUCT_KEEP_NULL"))) && requireProEdition()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
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<JSONEntry<?>> 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;
|
|
AggregateFilterStep<?> result;
|
|
|
|
if (parseFunctionNameIf("JSON_OBJECTAGG", "JSON_OBJECT_AGG", "JSON_GROUP_OBJECT") || (jsonb = parseFunctionNameIf("JSONB_OBJECT_AGG"))) {
|
|
JSONObjectAggNullStep<?> s1;
|
|
JSONObjectAggReturningStep<?> s2;
|
|
JSONOnNull onNull;
|
|
DataType<?> returning;
|
|
|
|
parse('(');
|
|
parseKeywordIf("ALL");
|
|
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;
|
|
}
|
|
else if (parseFunctionNameIf("OBJECT_AGG") && requireProEdition()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
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() {
|
|
return parseJSONEntry(true);
|
|
}
|
|
|
|
private final JSONEntry<?> parseJSONEntry(boolean supportKeyValue) {
|
|
boolean valueRequired = supportKeyValue && parseKeywordIf("KEY");
|
|
|
|
Field<String> key = (Field<String>) parseField();
|
|
if (supportKeyValue && 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<Field<?>> 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(parseOp());
|
|
Field f2 = parseIf(',') ? toField(parseOp()) : 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(parseOp());
|
|
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(parseOp());
|
|
Field arg2 = parseIf(',') ? toField(parseOp()) : null;
|
|
parse(')');
|
|
|
|
return arg2 == null ? round(arg1) : round(arg1, arg2);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldLeastIf() {
|
|
if (parseFunctionNameIf("LEAST", "MINVALUE")) {
|
|
parse('(');
|
|
List<Field<?>> 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<Field<?>> 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 (parseProFunctionNameIf("GROUPING_ID")) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
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() {
|
|
Timestamp timestamp = Convert.convert(parseStringLiteral(), Timestamp.class);
|
|
|
|
if (timestamp == null)
|
|
throw exception("Illegal timestamp literal");
|
|
|
|
return timestamp;
|
|
}
|
|
|
|
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() {
|
|
Time time = Convert.convert(parseStringLiteral(), Time.class);
|
|
|
|
if (time == null)
|
|
throw exception("Illegal time literal");
|
|
|
|
return time;
|
|
}
|
|
|
|
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(boolean parseUnknownSyntaxAsIdentifier) {
|
|
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);
|
|
|
|
if (parseUnknownSyntaxAsIdentifier)
|
|
return field(parseIdentifier());
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldMySQLIntervalLiteralIf(BiFunction<? super Field<?>, ? super DatePart, ? extends Field<?>> f) {
|
|
if (parseKeywordIf("INTERVAL")) {
|
|
Field<?> interval = parseField();
|
|
DatePart part = parseIntervalDatePart();
|
|
return f.apply(interval, part);
|
|
}
|
|
|
|
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 (parseKeywordIf("YEAR_MONTH"))
|
|
return requireNotNull(YearToMonth.yearToMonth(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 (parseKeywordIf("DAY_SECOND"))
|
|
return requireNotNull(DayToSecond.dayToSecond(string), message);
|
|
else if (parseKeywordIf("DAY_MINUTE"))
|
|
return requireNotNull(DayToSecond.dayToMinute(string), message);
|
|
else if (parseKeywordIf("DAY_HOUR"))
|
|
return requireNotNull(DayToSecond.dayToHour(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 (parseKeywordIf("HOUR_SECOND"))
|
|
return requireNotNull(DayToSecond.hourToSecond(string), message);
|
|
else if (parseKeywordIf("HOUR_MINUTE"))
|
|
return requireNotNull(DayToSecond.hourToMinute(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 (parseKeywordIf("MINUTE_SECOND"))
|
|
return requireNotNull(DayToSecond.minuteToSecond(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> 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<Number> interval = (Field<Number>) parseField();
|
|
parse(',');
|
|
Field<Date> date = (Field<Date>) parseField();
|
|
parse(')');
|
|
|
|
return DSL.dateAdd(date, interval, part);
|
|
}
|
|
|
|
// MySQL style
|
|
else if (parseFunctionNameIf("DATE_ADD") || (sub = parseFunctionNameIf("DATE_SUB"))) {
|
|
boolean s = 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(false);
|
|
|
|
if (interval == null) {
|
|
interval = parseFieldMySQLIntervalLiteralIf((i, p) -> s ? DSL.dateSub((Field) date, (Field) i, p) : DSL.dateAdd((Field) date, (Field) i, p));
|
|
|
|
if (interval != null) {
|
|
parse(')');
|
|
return interval;
|
|
}
|
|
}
|
|
else {
|
|
parse(')');
|
|
return s ? 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<Date> d1 = (Field<Date>) parseField();
|
|
|
|
if (parseIf(',')) {
|
|
Field<Date> d2 = (Field<Date>) 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", "DATE_PART")) {
|
|
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<String> result = concat(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD));
|
|
parse(')');
|
|
return result;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldOverlayIf() {
|
|
if (parseFunctionNameIf("OVERLAY")) {
|
|
parse('(');
|
|
Field f1 = parseField();
|
|
parseKeyword("PLACING");
|
|
Field f2 = parseField();
|
|
parseKeyword("FROM");
|
|
Field<Number> f3 = (Field) parseField();
|
|
Field<Number> f4 =
|
|
parseKeywordIf("FOR")
|
|
? (Field) parseField()
|
|
|
|
|
|
|
|
|
|
: null;
|
|
parse(')');
|
|
|
|
return f4 == null
|
|
? binary(f1, f2)
|
|
? binaryOverlay(f1, f2, f3)
|
|
: overlay(f1, f2, f3)
|
|
: binary(f1, f2)
|
|
? binaryOverlay(f1, f2, f3, f4)
|
|
: overlay(f1, f2, f3, f4);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final boolean binary(Field<?> f1) {
|
|
return f1.getDataType().isBinary();
|
|
}
|
|
|
|
private final boolean binary(Field<?> f1, Field<?> f2) {
|
|
return f1.getDataType().isBinary() || f2.getDataType().isBinary();
|
|
}
|
|
|
|
private final Field<?> parseFieldPositionIf() {
|
|
if (parseFunctionNameIf("POSITION")) {
|
|
parse('(');
|
|
forbidden.add(FK_IN);
|
|
Field f1 = parseField();
|
|
|
|
if (parseIf(',')) {
|
|
Field f2 = parseField();
|
|
Field f3 = parseIf(',') ? parseField() : null;
|
|
parse(')');
|
|
|
|
return f3 == null
|
|
? DSL.position(f1, f2)
|
|
: DSL.position(f1, f2, f3);
|
|
}
|
|
else {
|
|
parseKeyword("IN");
|
|
forbidden.remove(FK_IN);
|
|
Field f2 = parseField();
|
|
parse(')');
|
|
|
|
return binary(f1, f2)
|
|
? DSL.binaryPosition(f2, f1)
|
|
: DSL.position(f2, f1);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldLocateIf() {
|
|
boolean locate = parseFunctionNameIf("LOCATE");
|
|
if (locate || parseFunctionNameIf("LOCATE_IN_STRING")) {
|
|
parse('(');
|
|
Field f1 = parseField();
|
|
parse(',');
|
|
Field f2 = parseField();
|
|
Field<Integer> f3 = (Field) (parseIf(',') ? parseField() : null);
|
|
parse(')');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (locate)
|
|
return f3 == null
|
|
? binary(f1, f2)
|
|
? DSL.binaryPosition(f2, f1)
|
|
: DSL.position(f2, f1)
|
|
: binary(f1, f2)
|
|
? DSL.binaryPosition(f2, f1, f3)
|
|
: DSL.position(f2, f1, f3);
|
|
else
|
|
return f3 == null
|
|
? binary(f1, f2)
|
|
? DSL.binaryPosition(f1, f2)
|
|
: DSL.position(f1, f2)
|
|
: binary(f1, f2)
|
|
? DSL.binaryPosition(f1, f2, f3)
|
|
: DSL.position(f1, f2, f3);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldRegexpReplaceIf() {
|
|
boolean all = parseFunctionNameIf("REGEXP_REPLACE_ALL", "replaceRegexpAll");
|
|
boolean first = !all && parseFunctionNameIf("REGEXP_REPLACE_FIRST", "replaceRegexpOne");
|
|
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");
|
|
boolean dbmslob = !substr && parseProFunctionNameIf("DBMS_LOB.SUBSTR");
|
|
|
|
if (substring || substr) {
|
|
boolean keywords = !substr;
|
|
parse('(');
|
|
Field f1 = parseField();
|
|
if (substr || !(keywords = parseKeywordIf("FROM")))
|
|
parse(',');
|
|
Field f2 = toField(parseOp());
|
|
Field f3 =
|
|
((keywords && parseKeywordIf("FOR")) || (!keywords && parseIf(',')))
|
|
? (Field) toField(parseOp())
|
|
: null;
|
|
parse(')');
|
|
|
|
return f3 == null
|
|
? binary(f1)
|
|
? DSL.binarySubstring(f1, f2)
|
|
: DSL.substring(f1, f2)
|
|
: binary(f1)
|
|
? DSL.binarySubstring(f1, f2, f3)
|
|
: DSL.substring(f1, f2, f3);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldTrimIf() {
|
|
if (parseFunctionNameIf("TRIM")) {
|
|
parse('(');
|
|
int p = position();
|
|
|
|
boolean leading = parseKeywordIf("LEADING", "L");
|
|
boolean trailing = !leading && parseKeywordIf("TRAILING", "T");
|
|
boolean both = !leading && !trailing && parseKeywordIf("BOTH", "B");
|
|
|
|
if (leading || trailing || both) {
|
|
if (parseIf(',') || parseIf(')')) {
|
|
position(p);
|
|
}
|
|
else if (parseKeywordIf("FROM")) {
|
|
Field<String> f = (Field) parseField();
|
|
parse(')');
|
|
|
|
return leading
|
|
? ltrim(f)
|
|
: trailing
|
|
? rtrim(f)
|
|
: trim(f);
|
|
}
|
|
}
|
|
|
|
if (parseKeywordIf("FROM")) {
|
|
if (parseIf(',') || parseIf(')')) {
|
|
position(p);
|
|
}
|
|
else {
|
|
Field<String> f = (Field) parseField();
|
|
parse(')');
|
|
return trim(f);
|
|
}
|
|
}
|
|
|
|
Field f1 = parseField();
|
|
|
|
if (parseKeywordIf("FROM")) {
|
|
Field f2 = parseField();
|
|
parse(')');
|
|
|
|
return leading
|
|
? binary(f1, f2)
|
|
? binaryLtrim(f2, f1)
|
|
: ltrim(f2, f1)
|
|
: trailing
|
|
? binary(f1, f2)
|
|
? binaryRtrim(f2, f1)
|
|
: rtrim(f2, f1)
|
|
: binary(f1, f2)
|
|
? binaryTrim(f2, f1)
|
|
: trim(f2, f1);
|
|
}
|
|
else {
|
|
Field f2 = parseIf(',') ? parseField() : null;
|
|
parse(')');
|
|
|
|
return f2 == null
|
|
? trim(f1)
|
|
: binary(f1, f2)
|
|
? binaryTrim(f1, f2)
|
|
: 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", "DECODE_ORACLE", "MAP")) {
|
|
parse('(');
|
|
List<Field<?>> 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<Object>) fields.get(0),
|
|
(Field<Object>) fields.get(1),
|
|
(Field<Object>) fields.get(2),
|
|
(Field<Object>[]) (size == 3 ? EMPTY_FIELD : fields.subList(3, size).toArray(EMPTY_FIELD))
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldChooseIf() {
|
|
if (parseFunctionNameIf("CHOOSE", "ELT")) {
|
|
parse('(');
|
|
Field<Integer> index = (Field<Integer>) parseField();
|
|
parse(',');
|
|
List<Field<?>> 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<Field<?>> 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 <T> Field<?> parseFieldFieldIf() {
|
|
if (parseFunctionNameIf("FIELD")) {
|
|
parse('(');
|
|
Field<?> f1 = parseField();
|
|
parse(',');
|
|
List<Field<?>> f2 = parseList(',', c -> c.parseField());
|
|
parse(')');
|
|
|
|
return DSL.field((Field<T>) f1, (Field<T>[]) f2.toArray(EMPTY_FIELD));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldCaseIf() {
|
|
if (parseKeywordIf("CASE")) {
|
|
if (parseKeywordIf("WHEN")) {
|
|
CaseConditionStep step = null;
|
|
Field result;
|
|
|
|
do {
|
|
Condition condition = parseCondition();
|
|
parseKeyword("THEN");
|
|
Field value = parseField();
|
|
step = step == null ? when(condition, value) : step.when(condition, value);
|
|
}
|
|
while (parseKeywordIf("WHEN"));
|
|
|
|
if (parseKeywordIf("ELSE"))
|
|
result = step.otherwise(parseField());
|
|
else
|
|
result = step;
|
|
|
|
parseKeyword("END");
|
|
return result;
|
|
}
|
|
else {
|
|
CaseValueStep init = choose(parseField());
|
|
CaseWhenStep step = null;
|
|
Field result;
|
|
parseKeyword("WHEN");
|
|
|
|
do {
|
|
Field when = parseField();
|
|
parseKeyword("THEN");
|
|
Field then = parseField();
|
|
step = step == null ? init.when(when, then) : step.when(when, then);
|
|
}
|
|
while (parseKeywordIf("WHEN"));
|
|
|
|
if (parseKeywordIf("ELSE"))
|
|
result = step.otherwise(parseField());
|
|
else
|
|
result = step;
|
|
|
|
parseKeyword("END");
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldCastIf() {
|
|
boolean cast = parseFunctionNameIf("CAST");
|
|
boolean coerce = !cast && parseFunctionNameIf("COERCE");
|
|
boolean tryCast = !cast && !coerce && parseFunctionNameIf("TRY_CAST", "SAFE_CAST");
|
|
|
|
if (cast || coerce || tryCast) {
|
|
parse('(');
|
|
Field<?> field = parseField();
|
|
parseKeyword("AS");
|
|
DataType<?> type = parseCastDataType();
|
|
|
|
if (!tryCast)
|
|
tryCast = parseKeywordIf("DEFAULT NULL ON CONVERSION ERROR");
|
|
|
|
parse(')');
|
|
|
|
return tryCast
|
|
? tryCast(field, type)
|
|
: 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 = parseMinMaxByFunctionIf();
|
|
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<String> s3;
|
|
|
|
if (parseKeywordIf("DISTINCT"))
|
|
s1 = DSL.groupConcatDistinct(parseField());
|
|
else if (parseKeywordIf("ALL") || true)
|
|
s1 = DSL.groupConcat(parseField());
|
|
|
|
if (parseKeywordIf("ORDER BY"))
|
|
s2 = s1.orderBy(parseList(',', c -> c.parseSortField()));
|
|
else
|
|
s2 = s1;
|
|
|
|
if (parseKeywordIf("SEPARATOR"))
|
|
s3 = s2.separator((Field) parseField());
|
|
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());
|
|
else
|
|
parseKeywordIf("ALL");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Hypothetical set function
|
|
List<Field<?>> 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());
|
|
else
|
|
parseKeywordIf("ALL");
|
|
|
|
// Hypothetical set function
|
|
List<Field<?>> 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());
|
|
else
|
|
parseKeywordIf("ALL");
|
|
|
|
// Hypothetical set function
|
|
List<Field<?>> 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());
|
|
else
|
|
parseKeywordIf("ALL");
|
|
|
|
// Hypothetical set function
|
|
List<Field<?>> 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", "leadInFrame");
|
|
boolean lag = !lead && parseFunctionNameIf("LAG", "lagInFrame");
|
|
|
|
if (lead || lag) {
|
|
parse('(');
|
|
Field<Void> f1 = (Field) parseField();
|
|
Integer f2 = null;
|
|
Field<Void> 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<Void> arg = (Field) parseField();
|
|
WindowIgnoreNullsStep<Void> s1 = firstValue(arg);
|
|
WindowOverStep<?> s2 = parseWindowRespectIgnoreNulls(s1, s1);
|
|
parse(')');
|
|
return parseWindowFunction(null, s1, s2);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldLastValueIf() {
|
|
if (parseFunctionNameIf("LAST_VALUE")) {
|
|
parse('(');
|
|
Field<Void> arg = (Field) parseField();
|
|
WindowIgnoreNullsStep<Void> s1 = lastValue(arg);
|
|
WindowOverStep<?> s2 = parseWindowRespectIgnoreNulls(s1, s1);
|
|
parse(')');
|
|
return parseWindowFunction(null, s1, s2);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Field<?> parseFieldNthValueIf() {
|
|
if (parseFunctionNameIf("NTH_VALUE")) {
|
|
parse('(');
|
|
Field<?> f1 = parseField();
|
|
parse(',');
|
|
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 parseBinarySetFunction(DSL::corr);
|
|
else if (parseFunctionNameIf("COVAR_POP", "covarPop"))
|
|
return parseBinarySetFunction(DSL::covarPop);
|
|
else if (parseFunctionNameIf("COVAR_SAMP", "covarSamp"))
|
|
return parseBinarySetFunction(DSL::covarSamp);
|
|
|
|
break;
|
|
|
|
case 'R':
|
|
if (parseFunctionNameIf("REGR_AVGX"))
|
|
return parseBinarySetFunction(DSL::regrAvgX);
|
|
else if (parseFunctionNameIf("REGR_AVGY"))
|
|
return parseBinarySetFunction(DSL::regrAvgY);
|
|
else if (parseFunctionNameIf("REGR_COUNT"))
|
|
return parseBinarySetFunction(DSL::regrCount);
|
|
else if (parseFunctionNameIf("REGR_INTERCEPT"))
|
|
return parseBinarySetFunction(DSL::regrIntercept);
|
|
else if (parseFunctionNameIf("REGR_R2"))
|
|
return parseBinarySetFunction(DSL::regrR2);
|
|
else if (parseFunctionNameIf("REGR_SLOPE"))
|
|
return parseBinarySetFunction(DSL::regrSlope);
|
|
else if (parseFunctionNameIf("REGR_SXX"))
|
|
return parseBinarySetFunction(DSL::regrSXX);
|
|
else if (parseFunctionNameIf("REGR_SXY"))
|
|
return parseBinarySetFunction(DSL::regrSXY);
|
|
else if (parseFunctionNameIf("REGR_SYY"))
|
|
return parseBinarySetFunction(DSL::regrSYY);
|
|
|
|
break;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final AggregateFunction<?> parseBinarySetFunction(BiFunction<? super Field<? extends Number>, ? super Field<? extends Number>, ? extends AggregateFunction<?>> function) {
|
|
parse('(');
|
|
parseKeywordIf("ALL");
|
|
Field<? extends Number> arg1 = (Field) parseField();
|
|
parse(',');
|
|
Field<? extends Number> arg2 = (Field) parseField();
|
|
parse(')');
|
|
|
|
return function.apply(arg1, arg2);
|
|
}
|
|
|
|
private final AggregateFilterStep<?> parseOrderedSetFunctionIf() {
|
|
OrderedAggregateFunction<?> orderedN;
|
|
OrderedAggregateFunctionOfDeferredType ordered1;
|
|
boolean optionalWithinGroup = false;
|
|
|
|
orderedN = parseHypotheticalSetFunctionIf();
|
|
if (orderedN == null) {
|
|
InverseDistributionFunction idf = parseInverseDistributionFunctionIf();
|
|
|
|
if (idf != null)
|
|
if (idf.field() != null)
|
|
return idf.field();
|
|
else
|
|
orderedN = idf.ordered();
|
|
}
|
|
|
|
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<?> parseMinMaxByFunctionIf() {
|
|
boolean minBy = parseFunctionNameIf("MIN_BY", "ARG_MIN", "argMin");
|
|
boolean maxBy = !minBy && parseFunctionNameIf("MAX_BY", "ARG_MAX", "argMax");
|
|
|
|
if (minBy || maxBy) {
|
|
parse('(');
|
|
parseSetQuantifier();
|
|
Field<?> f1 = parseField();
|
|
parse(',');
|
|
Field<?> f2 = parseField();
|
|
List<SortField<?>> sort = parseAggregateOrderByIf();
|
|
parse(')');
|
|
|
|
OptionallyOrderedAggregateFunction<?> s1 = minBy ? minBy(f1, f2) : maxBy(f1, f2);
|
|
return sort == null ? s1 : s1.orderBy(sort);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final List<SortField<?>> parseAggregateOrderBy() {
|
|
List<SortField<?>> sort = parseAggregateOrderByIf();
|
|
|
|
if (sort == null)
|
|
throw expected("ORDER BY");
|
|
|
|
return sort;
|
|
}
|
|
|
|
private final List<SortField<?>> parseAggregateOrderByIf() {
|
|
List<SortField<?>> sort = null;
|
|
|
|
if (parseKeywordIf("ORDER BY"))
|
|
sort = parseList(',', c -> c.parseSortField());
|
|
|
|
return sort;
|
|
}
|
|
|
|
private final AggregateFilterStep<?> parseArrayAggFunctionIf() {
|
|
if (parseKeywordIf("ARRAY_AGG", "groupArray")) {
|
|
parse('(');
|
|
|
|
boolean distinct = parseSetQuantifier();
|
|
Field<?> a1 = parseField();
|
|
List<SortField<?>> sort = parseAggregateOrderByIf();
|
|
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('(');
|
|
parseKeywordIf("ALL");
|
|
List<Field<?>> fields = parseList(',', c -> c.parseField());
|
|
List<SortField<?>> sort = parseAggregateOrderByIf();
|
|
parse(')');
|
|
|
|
ArrayAggOrderByStep<?> s1 = multisetAgg(fields);
|
|
return sort == null ? s1 : s1.orderBy(sort);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final List<SortField<?>> parseWithinGroupN() {
|
|
return parseWithinGroupN(false);
|
|
}
|
|
|
|
private final List<SortField<?>> parseWithinGroupN(boolean optional) {
|
|
if (optional) {
|
|
if (!parseKeywordIf("WITHIN GROUP"))
|
|
return emptyList();
|
|
}
|
|
else
|
|
parseKeyword("WITHIN GROUP");
|
|
|
|
parse('(');
|
|
parseKeyword("ORDER BY");
|
|
List<SortField<?>> result = 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 static final record InverseDistributionFunction(OrderedAggregateFunction<BigDecimal> ordered, AggregateFilterStep<?> field) {}
|
|
|
|
private final InverseDistributionFunction parseInverseDistributionFunctionIf() {
|
|
if (parseFunctionNameIf("PERCENTILE_CONT"))
|
|
return parseInverseDistributionFunctionIf0(DSL::percentileCont);
|
|
else if (parseFunctionNameIf("PERCENTILE_DISC"))
|
|
return parseInverseDistributionFunctionIf0(DSL::percentileDisc);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
private final InverseDistributionFunction parseInverseDistributionFunctionIf0(Function<? super Field, ? extends OrderedAggregateFunction<BigDecimal>> f) {
|
|
parse('(');
|
|
parseKeywordIf("ALL");
|
|
Field f1 = parseField();
|
|
Field f2 = parseIf(',') ? parseField() : null;
|
|
|
|
if (f2 != null)
|
|
parseKeywordIf("IGNORE NULLS");
|
|
|
|
parse(')');
|
|
|
|
return f2 == null
|
|
? new InverseDistributionFunction(f.apply(f1), null)
|
|
: new InverseDistributionFunction(null, f.apply(f2).withinGroupOrderBy(f1));
|
|
}
|
|
|
|
private final OrderedAggregateFunction<String> parseListaggFunctionIf() {
|
|
OrderedAggregateFunction<String> ordered;
|
|
|
|
if (parseFunctionNameIf("LISTAGG", "STRING_AGG")) {
|
|
parse('(');
|
|
boolean distinct = parseSetQuantifier();
|
|
Field<?> field = parseField();
|
|
|
|
if (parseIf(','))
|
|
ordered = distinct ? listAggDistinct(field, (Field) parseField()) : listAgg(field, (Field) parseField());
|
|
else
|
|
ordered = distinct ? listAggDistinct(field) : listAgg(field);
|
|
|
|
if (parseKeywordIf("ORDER BY"))
|
|
ordered.withinGroupOrderBy(parseList(',', c -> c.parseSortField()));
|
|
|
|
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() {
|
|
return parseGeneralSetFunctionIf(parseComputationalOperationIf());
|
|
}
|
|
|
|
private final Field<?> parseGeneralSetFunctionIf(ComputationalOperation operation) {
|
|
boolean distinct;
|
|
Field arg;
|
|
|
|
if (operation == null)
|
|
return null;
|
|
|
|
parse('(');
|
|
|
|
switch (operation) {
|
|
case ANY:
|
|
case ANY_VALUE:
|
|
case AVG:
|
|
case EVERY:
|
|
case MAX:
|
|
case MIN:
|
|
case SUM:
|
|
case PRODUCT:
|
|
distinct = parseSetQuantifier();
|
|
break;
|
|
default:
|
|
parseKeywordIf("ALL");
|
|
distinct = false;
|
|
break;
|
|
}
|
|
|
|
arg = parseField();
|
|
|
|
switch (operation) {
|
|
case MAX:
|
|
case MIN: {
|
|
if (!distinct && parseIf(',')) {
|
|
List<Field<?>> fields = parseList(',', c -> c.parseField());
|
|
parse(')');
|
|
|
|
return operation == ComputationalOperation.MAX ? greatest(arg, fields.toArray(EMPTY_FIELD)) : least(arg, fields.toArray(EMPTY_FIELD));
|
|
}
|
|
}
|
|
|
|
case PRODUCT: {
|
|
if (!distinct && parseIf(',')) {
|
|
Field<?> result = arg.mul(parseField());
|
|
parse(')');
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
case ANY_VALUE: {
|
|
if (parseKeywordIf("HAVING")) {
|
|
boolean min = parseKeywordIf("MIN");
|
|
|
|
if (!min)
|
|
parseKeyword("MAX");
|
|
|
|
Field<?> f = parseField();
|
|
List<SortField<?>> sort = parseAggregateOrderByIf();
|
|
parse(')');
|
|
|
|
OptionallyOrderedAggregateFunction<?> s1 = min ? minBy(arg, f) : maxBy(arg, f);
|
|
return sort == null ? s1 : s1.orderBy(sort);
|
|
}
|
|
}
|
|
}
|
|
|
|
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('(');
|
|
if (parseIf(')'))
|
|
return count();
|
|
|
|
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<Integer> 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<Field<?>> arguments;
|
|
|
|
parse('(');
|
|
if (!parseIf(')')) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
arguments = parseList(',', c -> c.parseField());
|
|
parse(')');
|
|
}
|
|
else
|
|
arguments = new ArrayList<>();
|
|
|
|
// [#10107] Completely ignore functions in the DDLDatabase
|
|
return isDDLDatabase()
|
|
? inline((Object) null)
|
|
: function(name, Object.class, arguments);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(parseNameOrStringLiteral());
|
|
}
|
|
|
|
private final CharacterSet parseCharacterSet() {
|
|
return characterSet(parseNameOrStringLiteral());
|
|
}
|
|
|
|
public final Name parseNameOrStringLiteral() {
|
|
Name result = parseNameIf();
|
|
|
|
if (result == null)
|
|
return name(parseStringLiteral());
|
|
else
|
|
return result;
|
|
}
|
|
|
|
@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<Name> 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<Name> result = null;
|
|
Name i2;
|
|
|
|
do {
|
|
if ((i2 = parseIdentifierIf()) != null) {
|
|
if (result == null) {
|
|
result = new ArrayList<>();
|
|
result.add(i1);
|
|
}
|
|
|
|
result.add(i2);
|
|
}
|
|
else {
|
|
parse('*');
|
|
return lookupQualifiedAsterisk(positionBeforeName, result == null ? i1 : DSL.name(result.toArray(EMPTY_NAME)));
|
|
}
|
|
}
|
|
while (parseIf('.'));
|
|
}
|
|
|
|
position(positionBeforeName);
|
|
return null;
|
|
}
|
|
|
|
private final List<Name> parseIdentifiers() {
|
|
LinkedHashSet<Name> result = new LinkedHashSet<>();
|
|
|
|
do
|
|
if (!result.add(parseIdentifier()))
|
|
throw exception("Duplicate identifier encountered");
|
|
while (parseIf(','));
|
|
return new ArrayList<>(result);
|
|
}
|
|
|
|
@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;
|
|
|
|
do {
|
|
array = parseKeywordIf("ARRAY");
|
|
|
|
if (parseIf('[')) {
|
|
parseUnsignedIntegerLiteralIf();
|
|
parse(']');
|
|
|
|
array = true;
|
|
}
|
|
|
|
if (array)
|
|
result = result.getArrayDataType();
|
|
}
|
|
while (array);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private final DataType<?> parseDataTypePrefixIf(boolean parseUnknownTypes) {
|
|
char character = characterUpper();
|
|
|
|
if (character == '[' || character == '"' || character == '`')
|
|
character = characterNextUpper();
|
|
|
|
switch (character) {
|
|
case 'A':
|
|
if (parseKeywordOrIdentifierIf("ARRAY"))
|
|
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);
|
|
else if (parseKeywordOrIdentifierIf("STRING"))
|
|
return parseDataTypeCollation(parseDataTypeLength(VARCHAR));
|
|
|
|
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;
|
|
|
|
case 'Y':
|
|
if (parseKeywordOrIdentifierIf("YEAR"))
|
|
return parseDataTypeLength(SQLDataType.YEAR);
|
|
|
|
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", "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<String> 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 null;\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)) {
|
|
|
|
// [#14594] ": param" is a valid placeholder in Oracle
|
|
if (":".equals(prefix))
|
|
parseWhitespaceIf();
|
|
|
|
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();
|
|
Param<?> param;
|
|
|
|
if (binding instanceof Val<?> v) {
|
|
param = DSL.val0(
|
|
v.getValue(),
|
|
v.getDataType(),
|
|
v.inferredDataType,
|
|
bindIndex,
|
|
paramName
|
|
);
|
|
}
|
|
else if (binding instanceof Field<?> f) {
|
|
return f;
|
|
}
|
|
else
|
|
param = DSL.val0(
|
|
binding,
|
|
DSL.getDataType0((Class<?>) (binding != null ? binding.getClass() : Object.class)),
|
|
true,
|
|
bindIndex,
|
|
paramName
|
|
);
|
|
|
|
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<Number> parseFieldUnsignedNumericLiteral(Sign sign) {
|
|
Field<Number> result = parseFieldUnsignedNumericLiteralIf(sign);
|
|
|
|
if (result == null)
|
|
throw expected("Unsigned numeric literal");
|
|
|
|
return result;
|
|
}
|
|
|
|
private final Field<Number> parseFieldUnsignedNumericLiteralIf(Sign sign) {
|
|
Number r = parseUnsignedNumericLiteralIf(sign);
|
|
return r == null ? null : inline(r);
|
|
}
|
|
|
|
private final Number parseUnsignedNumericLiteralIf(Sign sign) {
|
|
int p = position();
|
|
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 final void parseDigits() {
|
|
for (;;) {
|
|
char c = character();
|
|
|
|
if (c >= '0' && c <= '9')
|
|
positionInc();
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
private final Field<Integer> parseZeroOne() {
|
|
if (parseIf('0'))
|
|
return zero();
|
|
else if (parseIf('1'))
|
|
return one();
|
|
else
|
|
throw expected("0 or 1");
|
|
}
|
|
|
|
private final Field<Integer> parseZeroOneDefault() {
|
|
if (parseIf('0'))
|
|
return zero();
|
|
else if (parseIf('1'))
|
|
return one();
|
|
else if (parseKeywordIf("DEFAULT"))
|
|
return defaultValue(INTEGER);
|
|
else
|
|
throw expected("0 or 1");
|
|
}
|
|
|
|
@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 <T> List<T> parseList(char separator, Function<? super ParseContext, ? extends T> element) {
|
|
return parseList(c -> c.parseIf(separator), element);
|
|
}
|
|
|
|
@Override
|
|
public final <T> List<T> parseList(String separator, Function<? super ParseContext, ? extends T> element) {
|
|
return parseList(c -> c.parseIf(separator), element);
|
|
}
|
|
|
|
@Override
|
|
public final <T> List<T> parseList(Predicate<? super ParseContext> separator, Function<? super ParseContext, ? extends T> element) {
|
|
List<T> result = new ArrayList<>();
|
|
|
|
do
|
|
result.add(element.apply(this));
|
|
while (separator.test(this));
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public final <T> T parseParenthesised(Function<? super ParseContext, ? extends T> content) {
|
|
return parseParenthesised('(', content, ')');
|
|
}
|
|
|
|
@Override
|
|
public final <T> T parseParenthesised(char open, Function<? super ParseContext, ? extends T> content, char close) {
|
|
parse(open);
|
|
T result = content.apply(this);
|
|
parse(close);
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public final <T> T parseParenthesised(String open, Function<? super ParseContext, ? extends T> content, String close) {
|
|
parse(open);
|
|
T result = content.apply(this);
|
|
parse(close);
|
|
|
|
return result;
|
|
}
|
|
|
|
private final Field<Long> parseUnsignedIntegerOrBindVariable() {
|
|
Long i = parseUnsignedIntegerLiteralIf();
|
|
|
|
if (i != null)
|
|
return DSL.inline(i);
|
|
|
|
Field<?> f = parseBindVariableIf();
|
|
if (f != null)
|
|
return (Field<Long>) 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 static final record Join(JoinType type, JoinHint hint) {}
|
|
|
|
private final Join parseJoinTypeIf() {
|
|
JoinHint hint;
|
|
|
|
if (parseKeywordIf("ANTI JOIN"))
|
|
return new Join(JoinType.LEFT_ANTI_JOIN, null);
|
|
else if (parseKeywordIf("CROSS")) {
|
|
if (parseKeywordIf("JOIN"))
|
|
return new Join(JoinType.CROSS_JOIN, null);
|
|
else if (parseKeywordIf("APPLY"))
|
|
return new Join(JoinType.CROSS_APPLY, null);
|
|
}
|
|
else if (parseKeywordIf("INNER") && asTrue(hint = parseJoinHintIf()) && parseKeyword("JOIN"))
|
|
return new Join(JoinType.JOIN, hint);
|
|
else if (parseKeywordIf("JOIN") && asTrue(hint = parseJoinHintIf()))
|
|
return new Join(JoinType.JOIN, hint);
|
|
else if (parseKeywordIf("LEFT")) {
|
|
if (parseKeywordIf("SEMI") && parseKeyword("JOIN"))
|
|
return new Join(JoinType.LEFT_SEMI_JOIN, null);
|
|
else if (parseKeywordIf("ANTI") && parseKeyword("JOIN"))
|
|
return new Join(JoinType.LEFT_ANTI_JOIN, null);
|
|
else if ((parseKeywordIf("OUTER") || true) && asTrue(hint = parseJoinHintIf()) && parseKeyword("JOIN"))
|
|
return new Join(JoinType.LEFT_OUTER_JOIN, hint);
|
|
}
|
|
else if (parseKeywordIf("RIGHT")) {
|
|
if (parseKeywordIf("ANTI JOIN"))
|
|
throw notImplemented("RIGHT ANTI JOIN");
|
|
else if (parseKeywordIf("SEMI JOIN"))
|
|
throw notImplemented("RIGHT SEMI JOIN");
|
|
else if ((parseKeywordIf("OUTER") || true) && asTrue(hint = parseJoinHintIf()) && parseKeyword("JOIN"))
|
|
return new Join(JoinType.RIGHT_OUTER_JOIN, hint);
|
|
}
|
|
else if (parseKeywordIf("FULL") && (parseKeywordIf("OUTER") || true) && asTrue(hint = parseJoinHintIf()) && parseKeyword("JOIN"))
|
|
return new Join(JoinType.FULL_OUTER_JOIN, hint);
|
|
else if (parseKeywordIf("OUTER APPLY"))
|
|
return new Join(JoinType.OUTER_APPLY, null);
|
|
else if (parseKeywordIf("NATURAL")) {
|
|
if (parseKeywordIf("LEFT") && (parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
|
|
return new Join(JoinType.NATURAL_LEFT_OUTER_JOIN, null);
|
|
else if (parseKeywordIf("RIGHT") && (parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
|
|
return new Join(JoinType.NATURAL_RIGHT_OUTER_JOIN, null);
|
|
else if (parseKeywordIf("FULL") && (parseKeywordIf("OUTER") || true) && parseKeyword("JOIN"))
|
|
return new Join(JoinType.NATURAL_FULL_OUTER_JOIN, null);
|
|
else if ((parseKeywordIf("INNER") || true) && parseKeyword("JOIN"))
|
|
return new Join(JoinType.NATURAL_JOIN, null);
|
|
}
|
|
else if (parseKeywordIf("SEMI JOIN"))
|
|
return new Join(JoinType.LEFT_SEMI_JOIN, null);
|
|
else if (parseKeywordIf("STRAIGHT_JOIN"))
|
|
return new Join(JoinType.STRAIGHT_JOIN, null);
|
|
|
|
return null;
|
|
// TODO partitioned join
|
|
}
|
|
|
|
private final JoinHint parseJoinHintIf() {
|
|
if (parseKeywordIf("HASH"))
|
|
return JoinHint.HASH;
|
|
else if (parseKeywordIf("LOOP", "LOOKUP"))
|
|
return JoinHint.LOOP;
|
|
else if (parseKeywordIf("MERGE"))
|
|
return JoinHint.MERGE;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
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", "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", "ARBITRARY"))
|
|
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", "MAXIMUM"))
|
|
return ComputationalOperation.MAX;
|
|
else if (parseFunctionNameIf("MEDIAN"))
|
|
return ComputationalOperation.MEDIAN;
|
|
else if (parseFunctionNameIf("MIN", "MINIMUM"))
|
|
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", "stddevPop"))
|
|
return ComputationalOperation.STDDEV_POP;
|
|
|
|
|
|
|
|
|
|
else if (parseFunctionNameIf("STDDEV_SAMP", "STDEV", "STDEV_SAMP", "stddevSamp"))
|
|
return ComputationalOperation.STDDEV_SAMP;
|
|
|
|
break;
|
|
|
|
case 'V':
|
|
if (parseFunctionNameIf("VAR_POP", "VARIANCE", "VARP", "varPop"))
|
|
return ComputationalOperation.VAR_POP;
|
|
|
|
|
|
|
|
|
|
else if (parseFunctionNameIf("VAR_SAMP", "VARIANCE_SAMP", "VAR", "varSamp"))
|
|
return ComputationalOperation.VAR_SAMP;
|
|
|
|
break;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private final Comparator parseComparatorIf() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (parseIf("==") || 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);
|
|
}
|
|
|
|
private final boolean parseKeywordUndocumentedAlternatives(String keyword, String undocumented1) {
|
|
if (parseKeywordIf(undocumented1))
|
|
return true;
|
|
else
|
|
return parseKeyword(keyword);
|
|
}
|
|
|
|
private final boolean parseProKeywordIf(String name) {
|
|
return !ignoreProEdition() && parseKeywordIf(name) && requireProEdition();
|
|
}
|
|
|
|
private final boolean parseProKeywordIf(String name1, String name2) {
|
|
return !ignoreProEdition() && parseKeywordIf(name1, name2) && requireProEdition();
|
|
}
|
|
|
|
private final boolean parseProKeywordIf(String name1, String name2, String name3) {
|
|
return !ignoreProEdition() && parseKeywordIf(name1, name2, name3) && requireProEdition();
|
|
}
|
|
|
|
private final boolean parseProKeywordIf(String... names) {
|
|
return !ignoreProEdition() && parseKeywordIf(names) && requireProEdition();
|
|
}
|
|
|
|
@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);
|
|
}
|
|
|
|
private final boolean parseKeywordIf(String keyword1, String keyword2, String keyword3, String keyword4) {
|
|
return parseKeywordIf(keyword1) || parseKeywordIf(keyword2) || parseKeywordIf(keyword3) || parseKeywordIf(keyword4);
|
|
}
|
|
|
|
@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;
|
|
}
|
|
|
|
private final boolean peekProKeyword(String... keywords) {
|
|
return !ignoreProEdition() && peekKeyword(keywords) && requireProEdition();
|
|
}
|
|
|
|
private final boolean peekProKeyword(String keyword) {
|
|
return !ignoreProEdition() && peekKeyword(keyword) && requireProEdition();
|
|
}
|
|
|
|
private final boolean peekProKeyword(String keyword1, String keyword2) {
|
|
return !ignoreProEdition() && peekKeyword(keyword1, keyword2) && requireProEdition();
|
|
}
|
|
|
|
private final boolean peekProKeyword(String keyword1, String keyword2, String keyword3) {
|
|
return !ignoreProEdition() && peekKeyword(keyword1, keyword2, keyword3) && requireProEdition();
|
|
}
|
|
|
|
@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) {
|
|
boolean caseSensitive =
|
|
Character.isLowerCase(keyword.charAt(0))
|
|
|| Character.isLowerCase(keyword.charAt(keyword.length() - 1));
|
|
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 (caseSensitive) {
|
|
if (character(pos) != c)
|
|
return false;
|
|
}
|
|
else 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 peekKeyword(KeywordLookup lookup) {
|
|
int pos = afterWhitespace(position(), false);
|
|
int p = lookup.lookup(sql, pos, i -> afterWhitespace(i, false));
|
|
|
|
if (p == pos)
|
|
return false;
|
|
|
|
// [#8806] A keyword that is followed by a period is very likely an identifier
|
|
if (isIdentifierPart(p) || character(p) == '.')
|
|
return false;
|
|
|
|
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 KeywordLookup KEYWORD_LOOKUP_IN_STATEMENTS = KeywordLookup.from(KEYWORDS_IN_STATEMENTS);
|
|
|
|
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 KeywordLookup KEYWORD_LOOKUP_IN_SELECT = KeywordLookup.from(KEYWORDS_IN_SELECT);
|
|
|
|
private static final String[] KEYWORDS_IN_FROM = {
|
|
"ANTI JOIN",
|
|
"CROSS APPLY",
|
|
"CROSS JOIN",
|
|
"FULL JOIN",
|
|
"FULL HASH JOIN",
|
|
"FULL LOOP JOIN",
|
|
"FULL LOOKUP JOIN",
|
|
"FULL MERGE JOIN",
|
|
"FULL OUTER JOIN",
|
|
"FULL OUTER HASH JOIN",
|
|
"FULL OUTER LOOP JOIN",
|
|
"FULL OUTER LOOKUP JOIN",
|
|
"FULL OUTER MERGE JOIN",
|
|
"INNER JOIN",
|
|
"INNER HASH JOIN",
|
|
"INNER LOOP JOIN",
|
|
"INNER LOOKUP JOIN",
|
|
"INNER MERGE JOIN",
|
|
"JOIN",
|
|
"LEFT ANTI JOIN",
|
|
"LEFT JOIN",
|
|
"LEFT HASH JOIN",
|
|
"LEFT LOOP JOIN",
|
|
"LEFT LOOKUP JOIN",
|
|
"LEFT MERGE JOIN",
|
|
"LEFT OUTER JOIN",
|
|
"LEFT OUTER HASH JOIN",
|
|
"LEFT OUTER LOOP JOIN",
|
|
"LEFT OUTER LOOKUP JOIN",
|
|
"LEFT OUTER MERGE 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 HASH JOIN",
|
|
"RIGHT LOOP JOIN",
|
|
"RIGHT LOOKUP JOIN",
|
|
"RIGHT MERGE JOIN",
|
|
"RIGHT OUTER JOIN",
|
|
"RIGHT OUTER HASH JOIN",
|
|
"RIGHT OUTER LOOP JOIN",
|
|
"RIGHT OUTER LOOKUP JOIN",
|
|
"RIGHT OUTER MERGE JOIN",
|
|
"RIGHT SEMI JOIN",
|
|
"SEMI JOIN",
|
|
"STRAIGHT_JOIN",
|
|
"USING"
|
|
};
|
|
|
|
private static final KeywordLookup KEYWORD_LOOKUP_IN_FROM = KeywordLookup.from(KEYWORDS_IN_FROM);
|
|
|
|
private static final String[] KEYWORDS_IN_SELECT_FROM;
|
|
|
|
static {
|
|
Set<String> set = new TreeSet<>(asList(KEYWORDS_IN_FROM));
|
|
set.addAll(asList(KEYWORDS_IN_STATEMENTS));
|
|
|
|
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",
|
|
"FORCE KEY",
|
|
"FORCE INDEX",
|
|
"GROUP BY",
|
|
"HAVING",
|
|
"IGNORE KEY",
|
|
"IGNORE INDEX",
|
|
"INTERSECT",
|
|
"INTO",
|
|
"LIMIT",
|
|
"MINUS",
|
|
"OFFSET",
|
|
"ORDER BY",
|
|
"QUALIFY",
|
|
"RETURNING",
|
|
"ROWS",
|
|
"START WITH",
|
|
"UNION",
|
|
"USE KEY",
|
|
"USE INDEX",
|
|
"WHERE",
|
|
"WINDOW"
|
|
));
|
|
|
|
KEYWORDS_IN_SELECT_FROM = set.toArray(EMPTY_STRING);
|
|
}
|
|
|
|
private static final KeywordLookup KEYWORD_LOOKUP_IN_SELECT_FROM = KeywordLookup.from(KEYWORDS_IN_SELECT_FROM);
|
|
|
|
private static final String[] KEYWORDS_IN_UPDATE_FROM;
|
|
|
|
static {
|
|
Set<String> 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 KeywordLookup KEYWORD_LOOKUP_IN_UPDATE_FROM = KeywordLookup.from(KEYWORDS_IN_UPDATE_FROM);
|
|
|
|
private static final String[] KEYWORDS_IN_DELETE_FROM;
|
|
|
|
static {
|
|
Set<String> 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 KeywordLookup KEYWORD_LOOKUP_IN_DELETE_FROM = KeywordLookup.from(KEYWORDS_IN_DELETE_FROM);
|
|
|
|
private static final String[] PIVOT_KEYWORDS = {
|
|
"FOR"
|
|
};
|
|
|
|
private static final Lazy<DDLQuery> IGNORE = Lazy.of(() -> new IgnoreQuery());
|
|
private static final Lazy<Query> 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<Param<?>> bindParamListener;
|
|
private int positionBeforeWhitespace;
|
|
private int position = 0;
|
|
private boolean ignoreHints = true;
|
|
private final Object[] bindings;
|
|
private int bindIndex = 0;
|
|
private final Map<String, Param<?>> bindParams = new LinkedHashMap<>();
|
|
private String delimiter = ";";
|
|
private LanguageContext languageContext = LanguageContext.QUERY;
|
|
private EnumSet<FunctionKeyword> 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<Param<?>>) dsl.configuration().data("org.jooq.parser.param-collector");
|
|
|
|
|
|
|
|
|
|
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 SQLDialectCategory parseCategory() {
|
|
return parseDialect().category();
|
|
}
|
|
|
|
@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/16487");
|
|
}
|
|
|
|
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 extends QueryPart> Q done(String message, Q result) {
|
|
if (done())
|
|
return notify(result);
|
|
else
|
|
throw exception(message);
|
|
}
|
|
|
|
private final <Q extends QueryPart> Q wrap(Supplier<Q> supplier) {
|
|
ParserException suppressed = null;
|
|
|
|
try {
|
|
return supplier.get();
|
|
}
|
|
catch (ParserException e) {
|
|
throw suppressed = e;
|
|
}
|
|
finally {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
private final <Q extends QueryPart> Q notify(Q result) {
|
|
if (bindParamListener != null) {
|
|
final Map<String, Param<?>> params = new LinkedHashMap<>();
|
|
|
|
// [#8722] TODO Replace this by a public SPI
|
|
// [#11054] Use a VisitListener to find actual Params in the expression tree,
|
|
// which may have more refined DataTypes attached to them, from context
|
|
dsl.configuration().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> T newScope(Supplier<T> scoped) {
|
|
ParseScope old = scope;
|
|
|
|
try {
|
|
scope = new ParseScope();
|
|
return scoped.get();
|
|
}
|
|
finally {
|
|
scope = old;
|
|
}
|
|
}
|
|
|
|
private class ParseScope {
|
|
private boolean scopeClear = false;
|
|
private final ScopeStack<Name, Table<?>> tableScope = new ScopeStack<>();
|
|
private final ScopeStack<Name, Field<?>> fieldScope = new ScopeStack<>();
|
|
private final ScopeStack<Name, QualifiedAsteriskProxy> lookupQualifiedAsterisks = new ScopeStack<>();
|
|
private final ScopeStack<Name, FieldProxy<?>> 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<FieldProxy<?>> 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<Field<?>> 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<Field<?>> resolveInTableScope(Iterable<Value<Table<?>>> tables, Name lookupName, FieldProxy<?> lookup, Value<Field<?>> found) {
|
|
|
|
tableScopeLoop:
|
|
for (Value<Table<?>> t : tables) {
|
|
Value<Field<?>> f;
|
|
|
|
if (t.value() instanceof JoinTable 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().fieldsIncludingHidden().field(lookup.getName()))) != null)
|
|
break tableScopeLoop;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else if ((f = Value.of(t.scopeLevel(), t.value().fieldsIncludingHidden().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<Table<?>> tables;
|
|
|
|
// [#8616] If name is not qualified, names reported by meta must be
|
|
// unqualified as well
|
|
if (!(tables = meta.getTables(name)).isEmpty())
|
|
for (Table<?> table : tables)
|
|
if (table.getQualifiedName().qualified() == name.qualified())
|
|
return tables.get(0);
|
|
|
|
// [#8616] If name is not qualified, try the search path as well
|
|
if (!name.qualified())
|
|
for (ParseSearchSchema schema : settings().getParseSearchPath())
|
|
if ((tables = meta.getTables(name(schema.getCatalog(), schema.getSchema()).append(name))).size() == 1)
|
|
return tables.get(0);
|
|
}
|
|
|
|
// [#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<Object>) field(name), positionBeforeName));
|
|
|
|
return field;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return mark();
|
|
}
|
|
|
|
public final <T> T data(Object key, Object value, Function<? super DefaultParseContext, ? extends T> function) {
|
|
Object previous = data(key, value);
|
|
|
|
try {
|
|
return function.apply(this);
|
|
}
|
|
finally {
|
|
if (previous == null)
|
|
data().remove(key);
|
|
else
|
|
data(key, previous);
|
|
}
|
|
}
|
|
} |