diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index ba4af64f6..b5dd337a2 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1879,6 +1879,13 @@ public class CCJSqlParser extends AbstractJSqlParser { // AS always starts an alias if (kind == K_AS) return true; + // SAMPLE followed by a size or BLOCK starts a sampling clause. + if (kind == K_SAMPLE && (getToken(2).kind == OPENING_BRACKET + || getToken(2).kind == K_BLOCK || getToken(2).kind == S_LONG + || getToken(2).kind == S_DOUBLE)) { + return false; + } + // String-literal alias: SELECT col 'myAlias' if (kind == S_CHAR_LITERAL) return true; @@ -3038,6 +3045,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -3231,6 +3239,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -3366,7 +3375,8 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */ | | | /* Salesforce SOQL */ -| +| +| | | | @@ -3420,7 +3430,7 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */ | | | -| +| | | | diff --git a/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java b/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java index 9eb002853..ff582d27a 100644 --- a/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java @@ -11,6 +11,10 @@ import java.util.stream.Stream; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.TernaryExpression; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.statement.piped.FromQuery; +import net.sf.jsqlparser.statement.piped.SelectPipeOperator; import net.sf.jsqlparser.expression.NotExpression; import net.sf.jsqlparser.expression.MapExpression; import net.sf.jsqlparser.expression.JdbcNamedParameter; @@ -24,6 +28,7 @@ import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.SampleClause; import net.sf.jsqlparser.statement.select.WithItem; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -77,6 +82,26 @@ public void testGroupsAsIdentifier() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("CREATE TABLE mytable (groups INT)", true); } + // sample and extend are non-reserved in the common dialects and reach the + // parser as ordinary table and column names in downstream rewriting tools. + @Test + public void testSampleExtendAsIdentifier() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT sample, extend FROM mytable", true); + assertSqlCanBeParsedAndDeparsed("SELECT mytable.extend FROM mytable", true); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE extend = 'x'", true); + assertSqlCanBeParsedAndDeparsed("UPDATE mytable SET sample = 1, extend = 'x' WHERE id = 1", + true); + assertSqlCanBeParsedAndDeparsed("INSERT INTO mytable (sample, extend) VALUES (1, 'x')", + true); + assertSqlCanBeParsedAndDeparsed("SELECT * FROM sample", true); + assertSqlCanBeParsedAndDeparsed("DELETE FROM sample WHERE extend = 'x'", true); + assertSqlCanBeParsedAndDeparsed("INSERT INTO sample (extend) VALUES ('x')", true); + assertSqlCanBeParsedAndDeparsed("SELECT a AS sample, b AS extend FROM mytable", true); + } + + @ParameterizedTest + @ValueSource(strings = {"sample", "extend", "SaMpLe", "ExTeNd"}) + void testSampleExtendImplicitAliases(String keyword) throws JSQLParserException { // exists is a non-reserved keyword in the common dialects and reaches the // parser as an ordinary column name in downstream rewriting tools. @Test @@ -116,7 +141,7 @@ void testExistsImplicitAliases(String keyword) throws JSQLParserException { } @ParameterizedTest - @ValueSource(strings = {"exists", "ExIsTs"}) + @ValueSource(strings = {"exists", "ExIsTs", "sample", "extend", "SaMpLe", "ExTeNd"}) void testExistsCte(String keyword) throws JSQLParserException { PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed( "WITH " + keyword + "(x) AS (SELECT 1) SELECT x FROM " + keyword, true); @@ -130,7 +155,7 @@ void testExistsCte(String keyword) throws JSQLParserException { } @ParameterizedTest - @ValueSource(strings = {"exists", "ExIsTs"}) + @ValueSource(strings = {"exists", "ExIsTs", "sample", "extend", "SaMpLe", "ExTeNd"}) void testExistsExpressionContexts(String keyword) throws JSQLParserException { PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( "SELECT ALL " + keyword + " FROM mytable"); @@ -142,7 +167,7 @@ void testExistsExpressionContexts(String keyword) throws JSQLParserException { } @ParameterizedTest - @ValueSource(strings = {"exists", "ExIsTs"}) + @ValueSource(strings = {"exists", "ExIsTs", "sample", "extend", "SaMpLe", "ExTeNd"}) void testExistsTernaryOperand(String keyword) throws JSQLParserException { PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed( "SELECT flag ? " + keyword + " : 0 FROM mytable", true); @@ -152,6 +177,38 @@ void testExistsTernaryOperand(String keyword) throws JSQLParserException { .getColumnName()); } + @ParameterizedTest + @ValueSource(strings = {"SAMPLE (10)", "SAMPLE BLOCK (10) SEED (1)", + "SAMPLE 0.1 OFFSET 1000", "USING SAMPLE 10 PERCENT (BERNOULLI)", + "TABLESAMPLE SYSTEM (10)"}) + void testSampleClauseStaysClause(String clause) throws JSQLParserException { + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed( + "SELECT * FROM mytable " + clause, true); + Table table = assertInstanceOf(Table.class, select.getFromItem()); + assertNull(table.getAlias()); + SampleClause sample = table.getSampleClause(); + assertNotNull(sample); + assertEquals(" " + clause, sample.toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"sample", "extend", "SaMpLe", "ExTeNd"}) + void testSampleExtendPipeExpression(String keyword) throws JSQLParserException { + FromQuery query = (FromQuery) assertSqlCanBeParsedAndDeparsed( + "FROM " + keyword + " |> EXTEND " + keyword + " AS x", true); + SelectPipeOperator operator = assertInstanceOf(SelectPipeOperator.class, + query.getPipeOperators().get(0)); + assertEquals("EXTEND", operator.getOperatorName()); + assertEquals(keyword, assertInstanceOf(Column.class, + operator.getSelectItems().get(0).getExpression()).getColumnName()); + assertSqlCanBeParsedAndDeparsed( + "FROM mytable |> EXTEND " + keyword + "(x) AS x", true); + PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse( + "SELECT ALL " + keyword + "(x) FROM mytable"); + assertEquals(keyword, assertInstanceOf(Function.class, + select.getSelectItem(0).getExpression()).getName()); + } + @Test void testExistsColumnWithOracleJoin() throws JSQLParserException { PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed( @@ -253,6 +310,8 @@ void testExistsBeforeNestedTernary() throws JSQLParserException { assertEquals("exists", assertInstanceOf(Column.class, map.getEntries().get(0).getKey()) .getColumnName()); assertInstanceOf(TernaryExpression.class, map.getEntries().get(0).getValue()); + } + // ERRORS introduces Exasol's IMPORT/EXPORT error clauses but is an ordinary name elsewhere, // e.g. a DuckDB column holding the errors of an upstream step. @Test