diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index c239c95eaf..784b95f6d8 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -5016,7 +5016,7 @@ Execute OracleImplicitCall(): name=ColumnIdentifier() { call.setName(name.getNames()); call.setExecType(Execute.ExecType.IMPLICIT); requireDdlSyntax(!Arrays.asList("RAISE", "RETURN", "GOTO", "EXIT", "CONTINUE", "LOOP", "WHILE", "FOR") .contains(call.getName().toUpperCase(java.util.Locale.ROOT)), "Unsupported PL/SQL control statement"); } - [ LOOKAHEAD("(") arguments=ExpressionList() { call.setExprList(arguments); } ] + [ LOOKAHEAD(1, "(") arguments=ExpressionList() { call.setExprList(arguments); } ] { return call; } } @@ -5267,7 +5267,7 @@ LockStatement LockStatement(): { } { { statement.setUseTableKeyword(false); } - [ LOOKAHEAD() { statement.setUseTableKeyword(true); } ] + [ LOOKAHEAD(1, ) { statement.setUseTableKeyword(true); } ] target=LockTarget() { statement.getTargets().add(target); } ( "," target=LockTarget() { statement.getTargets().add(target); } )* [ mode=TableLockMode() { statement.setLockMode(mode); } ] @@ -5489,7 +5489,6 @@ DBMSDestination DBMSDestination() #DBMSDestination: { connectionDefinition = ConnectionDefinition() { dbmsDestination.setConnectionDefinition(connectionDefinition); } ( - LOOKAHEAD(3) table = Table() { dbmsDestination.setTable(table); } [ LOOKAHEAD(2) columns = ParenthesedColumnList() { dbmsDestination.setColumns(columns); } ] [ LOOKAHEAD(2) dbmsTableDestinationOptions = DBMSTableDestinationOptionList() { dbmsDestination.setDBMSTableDestinationOptions(dbmsTableDestinationOptions); } ] @@ -8714,9 +8713,9 @@ Select Select() #Select: [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { select.setInterpolate(interpolateElements); } ] [ LOOKAHEAD(2, { !(select instanceof Values) }) optionClause = OptionClause() { select.setOption(optionClause); } ] - [ LOOKAHEAD() limit=LimitWithOffset() {select.setLimit(limit);} ] - [ LOOKAHEAD() offset = Offset() { select.setOffset(offset);} ] - [ LOOKAHEAD() fetch = Fetch() { select.setFetch(fetch);} ] + [ LOOKAHEAD(1, ) limit=LimitWithOffset() {select.setLimit(limit);} ] + [ LOOKAHEAD(1, ) offset = Offset() { select.setOffset(offset);} ] + [ LOOKAHEAD(1, ) fetch = Fetch() { select.setFetch(fetch);} ] [ LOOKAHEAD( ) withIsolation = WithIsolation() { select.setIsolation(withIsolation);} ] ) ) @@ -8765,8 +8764,8 @@ PlainSelect FromFirstSelect() #FromFirstSelect: [ LOOKAHEAD(2) qualify=Qualify() { plainSelect.setQualify(qualify); } ] [ LOOKAHEAD( ) orderByElements=OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ] - [ LOOKAHEAD() limit=LimitWithOffset() { plainSelect.setLimit(limit); } ] - [ LOOKAHEAD() offset=Offset() { plainSelect.setOffset(offset); } ] + [ LOOKAHEAD(1, ) limit=LimitWithOffset() { plainSelect.setLimit(limit); } ] + [ LOOKAHEAD(1, ) offset=Offset() { plainSelect.setOffset(offset); } ] { linkAST(plainSelect, jjtThis); return plainSelect; @@ -9170,8 +9169,8 @@ TableStatement TableStatement(): table = Table() { tableStatement.setTable(table); } [ LOOKAHEAD( ) orderByElements = OrderByElements() { tableStatement.setOrderByElements(orderByElements); } ] - [ LOOKAHEAD() limit = LimitWithOffset() { tableStatement.setLimit(limit);} ] - [ LOOKAHEAD() offset = Offset() { tableStatement.setOffset(offset);} ] + [ LOOKAHEAD(1, ) limit = LimitWithOffset() { tableStatement.setLimit(limit);} ] + [ LOOKAHEAD(1, ) offset = Offset() { tableStatement.setOffset(offset);} ] { return tableStatement; } /* Support operationList */ } @@ -9505,7 +9504,7 @@ PlainSelect PlainSelect() #PlainSelect: selectItems=SelectItemsList() ) - [ LOOKAHEAD() + [ LOOKAHEAD(1, ) ( LOOKAHEAD({ isMySqlSelectIntoAhead() }) mySqlSelectIntoClause = MySqlSelectIntoClause(MySqlSelectIntoClause.Position.BEFORE_FROM) @@ -9567,8 +9566,8 @@ PlainSelect PlainSelect() #PlainSelect: // optionally attach ClickHouse's `LIMIT ... BY ...`. Checking BY right after the limit // expression avoids a numeric LOOKAHEAD, which cannot see past a long parenthesized // subquery and would wrongly commit to LIMIT BY (issue #2359). - [ LOOKAHEAD() limit = LimitWithOffset() - [ LOOKAHEAD() expressionList = ExpressionList() { limit.setByExpressions(expressionList); } ] + [ LOOKAHEAD(1, ) limit = LimitWithOffset() + [ LOOKAHEAD(1, ) expressionList = ExpressionList() { limit.setByExpressions(expressionList); } ] { if (limit.getByExpressions() != null) { plainSelect.setLimitBy(limit); @@ -9576,13 +9575,13 @@ PlainSelect PlainSelect() #PlainSelect: plainSelect.setLimit(limit); } } ] - [ LOOKAHEAD() offset = Offset() { plainSelect.setOffset(offset); } ] + [ LOOKAHEAD(1, ) offset = Offset() { plainSelect.setOffset(offset); } ] [ LOOKAHEAD(, { limit==null }) limit = LimitWithOffset() { plainSelect.setLimit(limit); } ] [ LOOKAHEAD( ) mySqlProcedureAnalyse = MySqlProcedureAnalyse() { plainSelect.setMySqlProcedureAnalyse(mySqlProcedureAnalyse); } ] - [ LOOKAHEAD() fetch = Fetch() { plainSelect.setFetch(fetch); } ] + [ LOOKAHEAD(1, ) fetch = Fetch() { plainSelect.setFetch(fetch); } ] [ LOOKAHEAD( ) withIsolation = WithIsolation() { plainSelect.setIsolation(withIsolation); } ] [ LOOKAHEAD(2) @@ -9599,7 +9598,7 @@ PlainSelect PlainSelect() #PlainSelect: ( LOOKAHEAD(2) "," updateTable = Table() { updateTables.add(updateTable); } )* { plainSelect.setForUpdateTables(updateTables); } ] - [ LOOKAHEAD() wait = Wait() { plainSelect.setWait(wait); } ] + [ LOOKAHEAD(1, ) wait = Wait() { plainSelect.setWait(wait); } ] [ LOOKAHEAD(2) ( { plainSelect.setNoWait(true); } | { plainSelect.setSkipLocked(true); }) ] [ LOOKAHEAD( ) orderByElements = OrderByElements() @@ -9617,7 +9616,7 @@ PlainSelect PlainSelect() #PlainSelect: { plainSelect.setMySqlSelectIntoClause(mySqlSelectIntoClause); } ] [ LOOKAHEAD(2) settings = UpdateSets() { plainSelect.setSettings(settings); } ] - [ LOOKAHEAD() optimize = OptimizeFor() { plainSelect.setOptimizeFor(optimize); } ] + [ LOOKAHEAD(1, ) optimize = OptimizeFor() { plainSelect.setOptimizeFor(optimize); } ] [ LOOKAHEAD(3) intoTempTable = Table() { plainSelect.setIntoTempTable(intoTempTable);} ] [ LOOKAHEAD(3) { plainSelect.setUseWithNoLog(true); } ] { @@ -9690,10 +9689,10 @@ Select SetOperationList(Select select) #SetOperationList: { [ LOOKAHEAD(2) orderByElements=OrderByElements() {list.setOrderByElements(orderByElements);} ] [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { list.setInterpolate(interpolateElements); } ] - [ LOOKAHEAD() limit = LimitWithOffset() { list.setLimit(limit); } ] - [ LOOKAHEAD() offset = Offset() { list.setOffset(offset); } ] + [ LOOKAHEAD(1, ) limit = LimitWithOffset() { list.setLimit(limit); } ] + [ LOOKAHEAD(1, ) offset = Offset() { list.setOffset(offset); } ] [ LOOKAHEAD(, { limit==null }) limit = LimitWithOffset() { list.setLimit(limit); } ] - [ LOOKAHEAD() fetch = Fetch() { list.setFetch(fetch); } ] + [ LOOKAHEAD(1, ) fetch = Fetch() { list.setFetch(fetch); } ] [ LOOKAHEAD( ) withIsolation = WithIsolation() { list.setIsolation(withIsolation); } ] { @@ -10173,7 +10172,7 @@ ColumnsTransformer ColumnsTransformer(): | { transformer = new ColumnsTransformer(ColumnsTransformer.ColumnsTransformerType.RENAME); } ( - LOOKAHEAD("(") "(" replaceItems = SelectItemsList() ")" + LOOKAHEAD(1, "(") "(" replaceItems = SelectItemsList() ")" | item = SelectItem() { @@ -10646,21 +10645,21 @@ void MySqlSelectIntoOutfileTail(MySqlSelectIntoClause intoClause): (token= | token=) { intoClause.setCharacterSet(token.image); } ( - LOOKAHEAD(( | )) + LOOKAHEAD(1, ( | )) MySqlSelectIntoFieldsClause(intoClause) - [ LOOKAHEAD() MySqlSelectIntoLinesClause(intoClause) ] + [ LOOKAHEAD(1, ) MySqlSelectIntoLinesClause(intoClause) ] | - LOOKAHEAD() + LOOKAHEAD(1, ) MySqlSelectIntoLinesClause(intoClause) | { } ) | - LOOKAHEAD(( | )) + LOOKAHEAD(1, ( | )) MySqlSelectIntoFieldsClause(intoClause) - [ LOOKAHEAD() MySqlSelectIntoLinesClause(intoClause) ] + [ LOOKAHEAD(1, ) MySqlSelectIntoLinesClause(intoClause) ] | - LOOKAHEAD() + LOOKAHEAD(1, ) MySqlSelectIntoLinesClause(intoClause) | { } @@ -10681,7 +10680,7 @@ void MySqlSelectIntoFieldsClause(MySqlSelectIntoClause intoClause): token= { intoClause.setFieldsTerminatedBy(stringValue(token)); } ( - LOOKAHEAD(( | )) + LOOKAHEAD(1, ( | )) [ { intoClause.setFieldsOptionallyEnclosed(true); } ] token= { intoClause.setFieldsEnclosedBy(stringValue(token)); } @@ -10697,7 +10696,7 @@ void MySqlSelectIntoFieldsClause(MySqlSelectIntoClause intoClause): { } ) | - LOOKAHEAD(( | )) + LOOKAHEAD(1, ( | )) [ { intoClause.setFieldsOptionallyEnclosed(true); } ] token= { intoClause.setFieldsEnclosedBy(stringValue(token)); } @@ -11667,7 +11666,7 @@ OrderByElement OrderByElement(): } { columnReference = Expression() - [ LOOKAHEAD() (collateToken= | collateToken=) { columnReference = new CollateExpression(columnReference, collateToken.image); } ] + [ LOOKAHEAD(1, ) (collateToken= | collateToken=) { columnReference = new CollateExpression(columnReference, collateToken.image); } ] [ LOOKAHEAD(2) ( | ( { orderByElement.setAsc(false); } )) { orderByElement.setAscDescPresent(true); } ] [ LOOKAHEAD(2) [ LOOKAHEAD(2) ( @@ -11766,9 +11765,9 @@ OptionHint OptionHint(): name = OptionHintName() { optionHint.setName(name); } [ ( - LOOKAHEAD("=") "=" value = Expression() { optionHint.setValue(value); optionHint.setUseEquals(true); } + LOOKAHEAD(1, "=") "=" value = Expression() { optionHint.setValue(value); optionHint.setUseEquals(true); } | - LOOKAHEAD("(") "(" parameter = OptionHintParameter() { optionHint.addParameter(parameter); } + LOOKAHEAD(1, "(") "(" parameter = OptionHintParameter() { optionHint.addParameter(parameter); } ( "," parameter = OptionHintParameter() { optionHint.addParameter(parameter); } )* ")" | @@ -12617,7 +12616,7 @@ Expression IsJsonExpression(Expression leftExpression): [ LOOKAHEAD(2) ( { result.setUniqueKeys(IsJsonExpression.UniqueKeys.WITH); } | { result.setUniqueKeys(IsJsonExpression.UniqueKeys.WITHOUT); } ) { result.setUseKeysKeyword(false); } - [ LOOKAHEAD() { result.setUseKeysKeyword(true); } ] ] + [ LOOKAHEAD(1, ) { result.setUseKeysKeyword(true); } ] ] { result.setLeftExpression(leftExpression); return result; } } @@ -13304,7 +13303,7 @@ Expression PrimaryExpression() #PrimaryExpression: { retval = new FunctionAllColumns(unwrapParenthesedFunction(retval)); } ] // Share field and method navigation with the other primary expressions. - ( LOOKAHEAD(".") nxt=NavigationStep(retval) { retval = nxt; } )* + ( LOOKAHEAD(1, ".") nxt=NavigationStep(retval) { retval = nxt; } )* ) ) @@ -15050,7 +15049,7 @@ ExpressionList ExecuteArguments(): { } { ( - LOOKAHEAD("(") arguments=ExpressionList() + LOOKAHEAD(1, "(") arguments=ExpressionList() | argument=ExecuteArgument() { arguments.add(argument); } ( LOOKAHEAD(2) "," argument=ExecuteArgument() { arguments.add(argument); } )* @@ -16204,7 +16203,7 @@ String PostgreSqlStorageParameterName(): { String name; String part; } { name=RelObjectName() - ( LOOKAHEAD(".") "." part=RelObjectNameExt() { name += "." + part; } )* + ( LOOKAHEAD(1, ".") "." part=RelObjectNameExt() { name += "." + part; } )* { return name; } } @@ -16474,15 +16473,15 @@ ColumnOption ColumnDefinitionOption(): { | LOOKAHEAD( ) { option = ColumnOption.nullability(false); } | - LOOKAHEAD() { option = ColumnOption.nullability(true); } + LOOKAHEAD(1, ) { option = ColumnOption.nullability(true); } | - LOOKAHEAD() { option = ColumnOption.visibility(true); } + LOOKAHEAD(1, ) { option = ColumnOption.visibility(true); } | - LOOKAHEAD() { option = ColumnOption.visibility(false); } + LOOKAHEAD(1, ) { option = ColumnOption.visibility(false); } | - LOOKAHEAD() { option = ColumnOption.autoIncrement(); } + LOOKAHEAD(1, ) { option = ColumnOption.autoIncrement(); } | - LOOKAHEAD() ( tk= | tk= | tk= ) + LOOKAHEAD(1, ) ( tk= | tk= | tk= ) { option = ColumnOption.storage(accessEnum(ColumnOption.Storage.class, tk.image)); } | LOOKAHEAD({ isKeywordAhead("COMPRESSION") }) ContextualKeyword("COMPRESSION") compression=RelObjectName() @@ -16508,12 +16507,12 @@ ColumnOption ColumnDefinitionOption(): { option.setUseEquals(useEquals); } | - LOOKAHEAD() + LOOKAHEAD(1, ) ( tk= { collation = tk.image; } | collationNames=RelObjectNames() { collation = String.join(".", collationNames.getNames()); } ) { option = ColumnOption.collate(collation); } | - LOOKAHEAD() tk= + LOOKAHEAD(1, ) tk= { option = ColumnOption.comment(stringValue(tk)); } | LOOKAHEAD( ) defaultExpression=Expression() @@ -16546,7 +16545,7 @@ ColumnOption ColumnDefinitionOption(): { LOOKAHEAD( ) { option = ColumnOption.raw("DEFAULT", "ON", "NULL"); } | - LOOKAHEAD() { option = ColumnOption.raw("DEFAULT"); } + LOOKAHEAD(1, ) { option = ColumnOption.raw("DEFAULT"); } // Db2 permits an omitted default value. Keep these implicit defaults raw. [ LOOKAHEAD(2, { !(getToken(1).kind == K_NOT && getToken(2).kind == K_NULL) }) defaultExpression=ColumnDefaultValue() { option = ColumnOption.defaultValue(defaultExpression); } ] @@ -18303,7 +18302,7 @@ CreateTable CreateTable(boolean isUsingOrReplace): tk.image.toUpperCase(Locale.ROOT))); } ] [ { createTable.setUseAsKeyword(true); } ] ( - LOOKAHEAD() execute=Execute() { + LOOKAHEAD(1, ) execute=Execute() { requireDdlSyntax(createTable.isUseAsKeyword() && createTable.getDuplicateHandling() == null, "CREATE TABLE EXECUTE requires AS and cannot use duplicate handling"); createTable.setExecute(execute); @@ -18381,7 +18380,7 @@ ForeignDataOption ForeignDataOption(): } { ( - LOOKAHEAD() name=RelObjectName() + LOOKAHEAD(1, ) name=RelObjectName() { option = new ForeignDataOption(name, null); option.setAction(ForeignDataOption.Action.DROP); } | [ LOOKAHEAD(1) ( { action = ForeignDataOption.Action.ADD; } @@ -18396,7 +18395,7 @@ TableElement CreateTableElement(boolean typed): { TableElement element; } { ( - LOOKAHEAD() element=LikeClause() + LOOKAHEAD(1, ) element=LikeClause() | LOOKAHEAD(3) element=CreateTableConstraint() | element=CreateTableColumnDefinition(typed) ) @@ -18856,7 +18855,7 @@ String LargeObjectTypeName(): Token object; } { - [ LOOKAHEAD() national= ] + [ LOOKAHEAD(1, ) national= ] ( type= | type= | type= | type= ) large= object= { return (national == null ? "" : national.image + " ") @@ -19037,7 +19036,7 @@ ColDataType ColDataType(): ( LOOKAHEAD(1) typeModifier=MySqlTypeModifier() { colDataType.addTypeModifier(typeModifier); } )* ( - LOOKAHEAD() { tk = null; } + LOOKAHEAD(1, ) { tk = null; } [ LOOKAHEAD(2) "[" tk= "]" ] { array.add(tk == null ? null : Integer.valueOf(tk.image)); colDataType.setArrayData(array); @@ -19677,7 +19676,7 @@ void AlterExpressionUsingIndex(AlterExpression alterExp): { alterExp.addParameters("USING"); } [ LOOKAHEAD(2) { alterExp.addParameters("INDEX"); } ] ( - LOOKAHEAD(2, ) sk4=RelObjectName() + LOOKAHEAD(1, ) sk4=RelObjectName() { alterExp.addParameters("TABLESPACE"); alterExp.addParameters(sk4); } | sk4=RelObjectName() { alterExp.addParameters(sk4); } @@ -21731,7 +21730,7 @@ Statement Alter(): | LOOKAHEAD(AlterTrigger() ( | )) statement = AlterTrigger() | - LOOKAHEAD() statement = AlterPolicy() + LOOKAHEAD(1, ) statement = AlterPolicy() | LOOKAHEAD({ isMySqlAlterUserAhead() }) statement = AlterUser() | @@ -21747,11 +21746,11 @@ Statement Alter(): // Other dialects keep unmodelled ALTER DATABASE forms as unsupported statements. LOOKAHEAD(AlterDatabase()) statement = AlterDatabase() | - LOOKAHEAD() statement = AlterSchema() + LOOKAHEAD(1, ) statement = AlterSchema() | - LOOKAHEAD() statement = AlterType() + LOOKAHEAD(1, ) statement = AlterType() | - LOOKAHEAD() statement = AlterDomain() + LOOKAHEAD(1, ) statement = AlterDomain() | LOOKAHEAD({ isKeywordAhead("EXTENSION") }) statement = AlterExtension() | @@ -22111,7 +22110,7 @@ RelationAlterAction PostgreSqlRelationAction(): action.setStatistics(statistics); } ) | - LOOKAHEAD("(") options=PostgreSqlIndexOptions() { + LOOKAHEAD(1, "(") options=PostgreSqlIndexOptions() { for (IndexOption option : options) { requireDdlSyntax(option.getValue() != null && option.isUseEquals(), "Column attributes require a value and an equals sign"); @@ -23526,9 +23525,9 @@ Statement Create(): | LOOKAHEAD({ isPostgreSqlTriggerAhead() }) statement = PostgreSqlTrigger(isUsingOrReplace) | - LOOKAHEAD() { requireTypeDdlSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE TYPE"); } statement = CreateType() + LOOKAHEAD(1, ) { requireTypeDdlSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE TYPE"); } statement = CreateType() | - LOOKAHEAD() { requireTypeDdlSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE DOMAIN"); } statement = CreateDomain() + LOOKAHEAD(1, ) { requireTypeDdlSyntax(!isUsingOrReplace, "OR REPLACE is not supported for CREATE DOMAIN"); } statement = CreateDomain() | LOOKAHEAD({ isKeywordAhead("EXTENSION") && "REPOSITORY".equalsIgnoreCase(getToken(2).image) }) @@ -25286,7 +25285,7 @@ CreateRule CreateRule(boolean orReplace): ( { statement.setNothing(true); } | LOOKAHEAD(Select()) action=Select() { statement.getActions().add(action); } - | LOOKAHEAD("(") "(" { statement.setUseParentheses(true); } + | LOOKAHEAD(1, "(") "(" { statement.setUseParentheses(true); } [ LOOKAHEAD({ getToken(1).kind != ST_SEMICOLON && !")".equals(getToken(1).image) }) action=PostgreSqlRuleAction() { statement.getActions().add(action); } ] ( diff --git a/src/test/java/net/sf/jsqlparser/parser/LookaheadCleanupTest.java b/src/test/java/net/sf/jsqlparser/parser/LookaheadCleanupTest.java new file mode 100644 index 0000000000..079c8c1f74 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/parser/LookaheadCleanupTest.java @@ -0,0 +1,163 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.parser; + +import static org.junit.jupiter.api.Assertions.*; +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.statement.ConnectionDefinition; +import net.sf.jsqlparser.statement.CloudConnectionDefinition; +import net.sf.jsqlparser.statement.export.DBMSDestination; +import org.junit.jupiter.api.Test; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.MySqlSelectIntoClause; +import net.sf.jsqlparser.statement.execute.Execute; +import net.sf.jsqlparser.statement.lock.LockStatement; +import net.sf.jsqlparser.statement.create.rule.CreateRule; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.notify.NotifyStatement; +import net.sf.jsqlparser.expression.LongValue; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class LookaheadCleanupTest { + @ParameterizedTest + @ValueSource(strings = {"EXA AT conn TABLE t", "ORA AT conn TABLE schemaName.tableName", + "JDBC DRIVER = 'driverName' AT conn TABLE \"t\"", + "EXA AT conn TABLE t (c1, c2) REPLACE TRUNCATE"}) + void dispatchesTableDestinationsWithCompleteConsumption(String sql) throws Exception { + CCJSqlParser parser = CCJSqlParserUtil.newParser(sql); + DBMSDestination destination = parser.DBMSDestination(); + assertNotNull(destination.getTable()); + assertNull(destination.getStatement()); + assertNotNull(destination.getConnectionDefinition()); + assertEquals(CCJSqlParserConstants.EOF, parser.getToken(1).kind); + assertSqlCanBeParsedAndDeparsed("EXPORT sourceTable INTO " + sql, true, + p -> p.withDialect(Dialect.EXASOL)); + } + + @ParameterizedTest + @ValueSource(strings = {"EXA AT conn STATEMENT 'insert into t values (?)'", + "ORA AT conn STATEMENT 'insert into t values (?)'"}) + void preservesStatementDestinations(String sql) throws Exception { + CCJSqlParser parser = CCJSqlParserUtil.newParser(sql); + DBMSDestination destination = parser.DBMSDestination(); + assertNull(destination.getTable()); + assertEquals("insert into t values (?)", destination.getStatement().getValue()); + assertEquals(CCJSqlParserConstants.EOF, parser.getToken(1).kind); + assertSqlCanBeParsedAndDeparsed("EXPORT sourceTable INTO " + sql, true, + p -> p.withDialect(Dialect.EXASOL)); + } + + @ParameterizedTest + @ValueSource(strings = {"EXA AT conn TABLE", "ORA AT conn STATEMENT", "EXA AT conn SELECT t"}) + void rejectsIncompleteAndWrongDestinationPrefixes(String sql) { + CCJSqlParser parser = CCJSqlParserUtil.newParser(sql); + assertThrows(ParseException.class, parser::DBMSDestination); + if (sql.endsWith("TABLE")) { + assertEquals(CCJSqlParserConstants.K_TABLE, parser.getToken(0).kind); + assertEquals(CCJSqlParserConstants.EOF, parser.getToken(1).kind); + } + } + + @ParameterizedTest + @ValueSource(strings = {"AT conn", "AT '127.0.0.1'", "AT CLOUD NONE conn", + "AT CLOUD AZURE BLOBSTORAGE conn"}) + void preservesCloudAndOrdinaryConnections(String sql) throws Exception { + CCJSqlParser parser = CCJSqlParserUtil.newParser(sql); + ConnectionDefinition connection = parser.ConnectionOrCloudConnectionDefinition(); + if (sql.startsWith("AT CLOUD")) { + assertInstanceOf(CloudConnectionDefinition.class, connection); + } else { + assertEquals(ConnectionDefinition.class, connection.getClass()); + } + assertEquals(CCJSqlParserConstants.EOF, parser.getToken(1).kind); + assertSqlCanBeParsedAndDeparsed("EXPORT sourceTable INTO CSV " + sql + " FILE 'file.csv'", + true, p -> p.withDialect(Dialect.EXASOL)); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT id FROM t LIMIT 7 OFFSET 2", + "SELECT id FROM t UNION ALL SELECT id FROM u LIMIT 7 OFFSET 2"}) + void boundedSelectTailsKeepTheirAstAndFollowingStatement(String sql) throws Exception { + Select select = (Select) complete(sql); + assertEquals(7, ((LongValue) select.getLimit().getRowCount()).getValue()); + assertEquals(2, ((LongValue) select.getOffset().getOffset()).getValue()); + assertEquals("SELECT 42", CCJSqlParserUtil.parseStatements(sql + "; SELECT 42").get(1) + .toString()); + } + + @Test + void boundedFetchKeepsItsCount() throws Exception { + Select select = (Select) complete("SELECT id FROM t OFFSET 2 ROWS FETCH NEXT 7 ROWS ONLY"); + assertEquals(2, ((LongValue) select.getOffset().getOffset()).getValue()); + assertEquals(7, ((LongValue) select.getFetch().getExpression()).getValue()); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT a INTO OUTFILE '/tmp/result.txt' FIELDS TERMINATED BY ',' " + + "OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY 'end' FROM t", + "SELECT a FROM t INTO OUTFILE '/tmp/result.txt' COLUMNS TERMINATED BY ',' " + + "OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY 'end'"}) + void boundedOutfileOptionsKeepTheirOrderingAndValues(String sql) throws Exception { + MySqlSelectIntoClause into = ((Select) complete(sql)).getPlainSelect() + .getMySqlSelectIntoClause(); + assertEquals(MySqlSelectIntoClause.Type.OUTFILE, into.getType()); + assertEquals("/tmp/result.txt", into.getFileName().getValue()); + assertEquals(",", into.getFieldsTerminatedBy().getValue()); + assertTrue(into.isFieldsOptionallyEnclosed()); + assertEquals("end", into.getLinesTerminatedBy().getValue()); + } + + @Test + void boundedParenthesesKeepExecutionArgumentsAndRuleActions() throws Exception { + Execute execute = (Execute) complete("CALL p (:a, :b)"); + assertEquals("p", execute.getName()); + assertEquals(2, execute.getExprList().size()); + assertEquals(":a", execute.getExprList().get(0).toString()); + assertEquals(":b", execute.getExprList().get(1).toString()); + CreateRule rule = (CreateRule) complete("CREATE RULE r AS ON INSERT TO t DO " + + "(INSERT INTO log VALUES (9); NOTIFY ch, 'changed')"); + assertEquals(2, rule.getActions().size()); + assertEquals("log", ((Insert) rule.getActions().get(0)).getTable().getName()); + assertEquals("changed", + ((NotifyStatement) rule.getActions().get(1)).getPayload().getValue()); + } + + @ParameterizedTest + @ValueSource(strings = {"LOCK TABLE a IN SHARE MODE WAIT 5", "LOCK a IN SHARE MODE WAIT 5"}) + void boundedTableKeywordKeepsLockTargetAndWait(String sql) throws Exception { + LockStatement lock = (LockStatement) complete(sql); + assertEquals(sql.startsWith("LOCK TABLE"), lock.isUseTableKeyword()); + assertEquals("a", lock.getTable().getName()); + assertEquals("SHARE", lock.getLockMode().getValue()); + assertEquals(5L, lock.getWaitSeconds()); + } + + @ParameterizedTest + @ValueSource(strings = {"SELECT id FROM t LIMIT", "SELECT id FROM t OFFSET", + "SELECT id FROM t FETCH NEXT", "CALL p (", "CALL p (:a,)", "LOCK TABLE", + "CREATE RULE r AS ON INSERT TO t DO (NOTIFY ch", "SELECT a FROM t INTO OUTFILE 'x' " + + "LINES TERMINATED BY 'end' FIELDS TERMINATED BY ','"}) + void boundedGuardsRejectIncompleteAndReorderedNeighbors(String sql) { + assertThrows(net.sf.jsqlparser.JSQLParserException.class, + () -> CCJSqlParserUtil.parse(sql)); + } + + private static Statement complete(String sql) throws Exception { + CCJSqlParser parser = CCJSqlParserUtil.newParser(sql); + Statement statement = parser.Statement(); + assertEquals(CCJSqlParserConstants.EOF, parser.getToken(1).kind); + assertEquals(statement.toString(), CCJSqlParserUtil.parse(statement.toString()).toString()); + return statement; + } + +}