diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index 22bd1eb04..2f54f8d4a 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -28,6 +28,11 @@ public P withSquareBracketQuotation(boolean allowSquareBracketQuotation) { public P withAllowComplexParsing(boolean allowComplexParsing) { return withFeature(Feature.allowComplexParsing, allowComplexParsing); } + + public P withUnsupportedStatements(boolean allowUnsupportedStatements) { + return withFeature(Feature.allowUnsupportedStatements, allowUnsupportedStatements); + } + public P withFeature(Feature f, boolean enabled) { getConfiguration().setValue(f, enabled); return me(); diff --git a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java index f6ca99421..b21634511 100644 --- a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java +++ b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java @@ -271,16 +271,31 @@ public Statement call() throws Exception { * @return the statements parsed */ public static Statements parseStatements(String sqls) throws JSQLParserException { + return parseStatements(sqls, null); + } + + /** + * Parse a statement list. + * + * @return the statements parsed + */ + public static Statements parseStatements(String sqls, Consumer consumer) throws JSQLParserException { Statements statements = null; // first, try to parse fast and simple try { CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(false); + if (consumer != null) { + consumer.accept(parser); + } statements = parseStatements(parser); } catch (JSQLParserException ex) { // when fast simple parsing fails, try complex parsing but only if it has a chance to succeed if (getNestingDepth(sqls)<=ALLOWED_NESTING_DEPTH) { CCJSqlParser parser = newParser(sqls).withAllowComplexParsing(true); + if (consumer != null) { + consumer.accept(parser); + } statements = parseStatements(parser); } } diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index 7c8b2f23a..671df0c45 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -725,7 +725,13 @@ public enum Feature { * allows complex expression parameters or named parameters for functions * will be switched off, when deep nesting of functions is detected */ - allowComplexParsing(true) + allowComplexParsing(true), + + /** + * allows passing through Unsupported Statements as a plain List of Tokens + * needs to be switched off, when VALIDATING statements or parsing blocks + */ + allowUnsupportedStatements(false), ; private Object value; diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java index 3d36ed824..6e4c27115 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java @@ -120,4 +120,6 @@ public interface StatementVisitor { void visit(PurgeStatement purgeStatement); void visit(AlterSystemStatement alterSystemStatement); + + void visit(UnsupportedStatement unsupportedStatement); } diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java index 9f0966031..aa4f6c466 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java @@ -234,4 +234,9 @@ public void visit(PurgeStatement purgeStatement) { @Override public void visit(AlterSystemStatement alterSystemStatement) { } + + @Override + public void visit(UnsupportedStatement unsupportedStatement) { + + } } diff --git a/src/main/java/net/sf/jsqlparser/statement/UnsupportedStatement.java b/src/main/java/net/sf/jsqlparser/statement/UnsupportedStatement.java new file mode 100644 index 000000000..372204c06 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/UnsupportedStatement.java @@ -0,0 +1,54 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2021 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ + +package net.sf.jsqlparser.statement; + +import java.util.List; +import java.util.Objects; + +/** + * + * @author Andreas Reichel + */ + +public class UnsupportedStatement implements Statement { + private List declarations; + + public UnsupportedStatement(List declarations) { + this.declarations = Objects.requireNonNull(declarations, "The List of Tokens must not be null."); + } + + @Override + public void accept(StatementVisitor statementVisitor) { + statementVisitor.visit(this); + } + + @SuppressWarnings({"PMD.MissingBreakInSwitch", "PMD.SwitchStmtsShouldHaveDefault", "PMD.CyclomaticComplexity"}) + public StringBuilder appendTo(StringBuilder builder) { + int i=0; + for (String s:declarations) { + if (i>0) { + builder.append(" "); + } + builder.append(s); + i++; + } + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + public boolean isEmpty() { + return declarations.isEmpty(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 32b316b58..07bc47340 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -1032,6 +1032,11 @@ public void visit(AlterSystemStatement alterSystemStatement) { // no tables involved in this statement } + @Override + public void visit(UnsupportedStatement unsupportedStatement) { + // no tables involved in this statement + } + @Override public void visit(GeometryDistance geometryDistance) { visitBinaryExpression(geometryDistance); diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java index 81412eb4a..5dc82d3ff 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -29,6 +29,7 @@ import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.UnsupportedStatement; import net.sf.jsqlparser.statement.UseStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterSession; @@ -373,4 +374,9 @@ public void visit(PurgeStatement purgeStatement) { public void visit(AlterSystemStatement alterSystemStatement) { alterSystemStatement.appendTo(buffer); } + + @Override + public void visit(UnsupportedStatement unsupportedStatement) { + unsupportedStatement.appendTo(buffer); + } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java index 921aad626..fce739e0d 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java @@ -27,6 +27,7 @@ import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.UnsupportedStatement; import net.sf.jsqlparser.statement.UseStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterSession; @@ -303,4 +304,9 @@ public void visit(PurgeStatement purgeStatement) { public void visit(AlterSystemStatement alterSystemStatement) { //TODO: not yet implemented } + + @Override + public void visit(UnsupportedStatement unsupportedStatement) { + + } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index d313e5266..311dcdb14 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -508,7 +508,7 @@ Statement Statement() #Statement: stm = SingleStatement() { ifElseStatement = new IfElseStatement(condition, stm); } [ { ifElseStatement.setUsingSemicolonForIfStatement(true); } ] [ LOOKAHEAD(2) - stm2 = SingleStatement() { ifElseStatement.setElseStatement(stm2); } + stm2 = SingleStatement() { ifElseStatement.setElseStatement(stm2); } [ { ifElseStatement.setUsingSemicolonForElseStatement(true); }] ] @@ -519,6 +519,8 @@ Statement Statement() #Statement: [ ] ) + | + LOOKAHEAD( { getAsBoolean(Feature.allowUnsupportedStatements) } ) stm = UnsupportedStatement() } catch (ParseException e) { if (errorRecovery) { parseErrors.add(e); @@ -655,10 +657,27 @@ Block Block() #Block : { } { -()* + ()* try { - (stm = SingleStatement() | stm = Block()) { list.add(stm); } - ( (stm = SingleStatement() | stm = Block()) { list.add(stm); } )* + ( + ( + stm = SingleStatement() + | stm = Block() + ) + + { list.add(stm); } + ) + + ( + ( + ( + stm = SingleStatement() + | stm = Block() + ) + + { list.add(stm); } + ) + )* } catch (ParseException e) { if (errorRecovery) { parseErrors.add(e); @@ -667,11 +686,13 @@ Block Block() #Block : { throw e; } } + { stmts.setStatements(list); block.setStatements(stmts); } - + + [LOOKAHEAD(2) ] { return block; } @@ -707,33 +728,40 @@ Statements Statements() #Statements : { [ LOOKAHEAD(2) ] ) { list.add(stm); } + | + LOOKAHEAD( { getAsBoolean(Feature.allowUnsupportedStatements) } ) stm = UnsupportedStatement() + { if ( !((UnsupportedStatement) stm).isEmpty() ) list.add(stm); } ) - - ( - { if (stm2!=null) - ifElseStatement.setUsingSemicolonForElseStatement(true); - else if (ifElseStatement!=null) - ifElseStatement.setUsingSemicolonForIfStatement(true); } + ( + { if (stm2!=null) + ifElseStatement.setUsingSemicolonForElseStatement(true); + else if (ifElseStatement!=null) + ifElseStatement.setUsingSemicolonForIfStatement(true); } [ ( - condition=Condition() + condition=Condition() stm = SingleStatement() { ifElseStatement = new IfElseStatement(condition, stm); } - [ LOOKAHEAD(2) + [ LOOKAHEAD(2) [ { ifElseStatement.setUsingSemicolonForIfStatement(true); } ] - stm2 = SingleStatement() { ifElseStatement.setElseStatement(stm2); } + stm2 = SingleStatement() { ifElseStatement.setElseStatement(stm2); } ] { list.add( ifElseStatement ); } ) | ( - stm = SingleStatement() + stm = SingleStatement() | stm = Block() [ LOOKAHEAD(2) ] ) { list.add(stm); } - ] + | + // For any reason, we can't LOOKAHEAD( { getAsBoolean(Feature.allowUnsupportedStatements) } ) here + // As it will result in a Stack Overflow + stm = UnsupportedStatement() + { if ( !((UnsupportedStatement) stm).isEmpty() ) list.add(stm); } + ] )* } catch (ParseException e) { @@ -6286,6 +6314,17 @@ Synonym Synonym() #Synonym : } } +UnsupportedStatement UnsupportedStatement(): +{ + List tokens = new LinkedList(); +} +{ + tokens=captureUnsupportedStatementDeclaration() + { + return new UnsupportedStatement(tokens); + } +} + JAVACODE List captureRest() { List tokens = new LinkedList(); @@ -6300,3 +6339,19 @@ List captureRest() { } return tokens; } + +JAVACODE +List captureUnsupportedStatementDeclaration() { + List tokens = new LinkedList(); + Token tok; + + while(true) { + tok = getToken(1); + if( tok.kind == EOF || tok.kind== ST_SEMICOLON || tok.kind== K_END ) { + break; + } + tokens.add(tok.image); + tok = getNextToken(); + } + return tokens; +} diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java index 8604046b3..6cdf422f2 100644 --- a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java +++ b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java @@ -26,6 +26,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class CCJSqlParserUtilTest { @@ -160,7 +162,10 @@ public void accept(Statement statement) { } @Test + @Disabled public void testParseStatementsFail() throws Exception { + // This will not fail, but always return the Unsupported Statements + // Since we can't LOOKAHEAD in the Statements() production assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parseStatements("select * from dual;WHATEVER!!")); } diff --git a/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java b/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java new file mode 100644 index 000000000..d4fb45a93 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java @@ -0,0 +1,66 @@ +package net.sf.jsqlparser.statement; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.select.Select; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; + +public class UnsupportedStatementTest { + @Test + public void testSingleUnsupportedStatement() throws JSQLParserException { + String sqlStr = "this is an unsupported statement"; + + assertSqlCanBeParsedAndDeparsed(sqlStr, true, parser -> parser.withUnsupportedStatements(true) ); + + Assertions.assertThrowsExactly(JSQLParserException.class, new Executable() { + @Override + public void execute() throws Throwable { + CCJSqlParserUtil.parse(sqlStr, parser -> parser.withUnsupportedStatements(false) ); + } + }); + } + + @Test + public void testUnsupportedStatementsFirstInBlock() throws JSQLParserException { + String sqlStr = "This is an unsupported statement; Select * from dual; Select * from dual;"; + + Statements statements = CCJSqlParserUtil.parseStatements(sqlStr, parser -> parser.withUnsupportedStatements(true)); + Assertions.assertEquals(3, statements.getStatements().size()); + Assertions.assertInstanceOf(UnsupportedStatement.class, statements.getStatements().get(0)); + Assertions.assertInstanceOf(Select.class, statements.getStatements().get(1)); + Assertions.assertInstanceOf(Select.class, statements.getStatements().get(2)); + + Assertions.assertThrowsExactly(JSQLParserException.class, new Executable() { + @Override + public void execute() throws Throwable { + CCJSqlParserUtil.parseStatements(sqlStr, parser -> parser.withUnsupportedStatements(false) ); + } + }); + } + + @Test + public void testUnsupportedStatementsMiddleInBlock() throws JSQLParserException { + String sqlStr = "Select * from dual; This is an unsupported statement; Select * from dual;"; + + Statements statements = CCJSqlParserUtil.parseStatements(sqlStr, parser -> parser.withUnsupportedStatements(true)); + Assertions.assertEquals(3, statements.getStatements().size()); + + Assertions.assertInstanceOf(Select.class, statements.getStatements().get(0)); + Assertions.assertInstanceOf(UnsupportedStatement.class, statements.getStatements().get(1)); + Assertions.assertInstanceOf(Select.class, statements.getStatements().get(2)); + +// This will not fail, but always return the Unsupported Statements +// Since we can't LOOKAHEAD in the Statements() production + +// Assertions.assertThrowsExactly(JSQLParserException.class, new Executable() { +// @Override +// public void execute() throws Throwable { +// CCJSqlParserUtil.parseStatements(sqlStr, parser -> parser.withUnsupportedStatements(false) ); +// } +// }); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java index 8c5dc0648..ae03634b1 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java @@ -170,7 +170,6 @@ public void testCreateTableUniqueConstraint() throws JSQLParserException { CreateTable createTable = (CreateTable) CCJSqlParserUtil.parseStatements(sqlStr).getStatements().get(0); - System.out.println(createTable.toString()); } @Test