From 4d8a512191a4a1b9a28d8eff0f00330deafebd4d Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Wed, 12 Jul 2023 14:25:16 +0700 Subject: [PATCH] feat: SQL:2016 TABLESAMPLE clause - supports SQL:2016 - supports Oracle dialect - fixes #1826 - fixes #1593 Signed-off-by: Andreas Reichel --- .../jsqlparser/parser/CCJSqlParserUtil.java | 2 +- .../parser/ParserKeywordsUtils.java | 6 +- .../sf/jsqlparser/parser/feature/Feature.java | 9 +- .../java/net/sf/jsqlparser/schema/Table.java | 60 +++++++-- .../jsqlparser/statement/ReturningClause.java | 6 +- .../jsqlparser/statement/select/FromItem.java | 1 + .../sf/jsqlparser/statement/select/Join.java | 8 +- .../statement/select/SampleClause.java | 120 +++++++++++++++++ .../util/deparser/SelectDeParser.java | 7 + .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 125 +++++++++++++----- src/site/sphinx/keywords.rst | 4 + .../parser/CCJSqlParserUtilTest.java | 5 +- .../statement/ConditionalKeywordsTest.java | 10 +- .../statement/select/SampleClauseTest.java | 35 +++++ .../statement/select/SelectTest.java | 18 +-- .../statement/select/oracle-tests/pivot01.sql | 3 +- .../statement/select/oracle-tests/pivot11.sql | 3 +- .../select/oracle-tests/sample01.sql | 4 +- .../select/oracle-tests/simple05.sql | 3 +- 19 files changed, 346 insertions(+), 83 deletions(-) create mode 100644 src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java create mode 100644 src/test/java/net/sf/jsqlparser/statement/select/SampleClauseTest.java diff --git a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java index 1ae8ff081..5ea6af47f 100644 --- a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java +++ b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java @@ -461,7 +461,7 @@ public static int getUnbalancedPosition(String text) { return i; // Return position of unbalanced closing bracket } char top = stack.pop(); - if ((c == ')' && top != '(') || (c == ']' && top != '[') || (c == '}' && top != '{')) { + if (c == ')' && top != '(' || c == ']' && top != '[' || c == '}' && top != '{') { return i; // Return position of unbalanced closing bracket } } diff --git a/src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java b/src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java index 53e49d93a..9306e01da 100644 --- a/src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java +++ b/src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java @@ -77,7 +77,8 @@ public class ParserKeywordsUtils { {"PIVOT", RESTRICTED_JSQLPARSER}, {"PROCEDURE", RESTRICTED_ALIAS}, {"PUBLIC", RESTRICTED_ALIAS}, {"RECURSIVE", RESTRICTED_SQL2016}, {"REGEXP", RESTRICTED_SQL2016}, {"RETURNING", RESTRICTED_JSQLPARSER}, - {"RIGHT", RESTRICTED_SQL2016}, {"SEL", RESTRICTED_ALIAS}, {"SELECT", RESTRICTED_ALIAS}, + {"RIGHT", RESTRICTED_SQL2016}, {"SAMPLE", RESTRICTED_ALIAS}, {"SEL", RESTRICTED_ALIAS}, + {"SELECT", RESTRICTED_ALIAS}, {"SEMI", RESTRICTED_JSQLPARSER}, {"SET", RESTRICTED_JSQLPARSER}, {"SOME", RESTRICTED_JSQLPARSER}, {"START", RESTRICTED_JSQLPARSER}, {"TABLES", RESTRICTED_ALIAS}, {"TOP", RESTRICTED_SQL2016}, @@ -86,7 +87,8 @@ public class ParserKeywordsUtils { {"UNPIVOT", RESTRICTED_JSQLPARSER}, {"USE", RESTRICTED_JSQLPARSER}, {"USING", RESTRICTED_SQL2016}, {"SQL_CACHE", RESTRICTED_JSQLPARSER}, {"SQL_CALC_FOUND_ROWS", RESTRICTED_JSQLPARSER}, {"SQL_NO_CACHE", RESTRICTED_JSQLPARSER}, - {"STRAIGHT_JOIN", RESTRICTED_JSQLPARSER}, {"VALUE", RESTRICTED_JSQLPARSER}, + {"STRAIGHT_JOIN", RESTRICTED_JSQLPARSER}, {"TABLESAMPLE", RESTRICTED_ALIAS}, + {"VALUE", RESTRICTED_JSQLPARSER}, {"VALUES", RESTRICTED_SQL2016}, {"VARYING", RESTRICTED_JSQLPARSER}, {"WHEN", RESTRICTED_SQL2016}, {"WHERE", RESTRICTED_SQL2016}, {"WINDOW", RESTRICTED_SQL2016}, {"WITH", RESTRICTED_SQL2016}, 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 e52487454..7d7e94a72 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -292,7 +292,7 @@ public enum Feature { /** * "RETURNING expr(, expr)*" * - * @see SelectExpressionItem + * @see net.sf.jsqlparser.expression.operators.relational.ExpressionList */ insertReturningExpressionList, @@ -326,7 +326,7 @@ public enum Feature { /** * "RETURNING expr(, expr)*" * - * @see SelectExpressionItem + * @see net.sf.jsqlparser.statement.select.SelectItem */ updateReturning, /** @@ -354,7 +354,7 @@ public enum Feature { /** * "RETURNING expr(, expr)*" * - * @see SelectExpressionItem + * @see net.sf.jsqlparser.statement.select.SelectItem */ deleteReturningExpressionList, @@ -436,7 +436,6 @@ public enum Feature { /** * SQL "REPLACE" statement is allowed * - * @see Replace */ @Deprecated replace, @@ -644,7 +643,7 @@ public enum Feature { lateralSubSelect, /** - * @see ValuesList + * @see net.sf.jsqlparser.statement.select.Values */ valuesList, /** diff --git a/src/main/java/net/sf/jsqlparser/schema/Table.java b/src/main/java/net/sf/jsqlparser/schema/Table.java index 0d248910f..744010ca4 100644 --- a/src/main/java/net/sf/jsqlparser/schema/Table.java +++ b/src/main/java/net/sf/jsqlparser/schema/Table.java @@ -20,6 +20,7 @@ import net.sf.jsqlparser.statement.select.FromItemVisitor; import net.sf.jsqlparser.statement.select.IntoTableVisitor; import net.sf.jsqlparser.statement.select.Pivot; +import net.sf.jsqlparser.statement.select.SampleClause; import net.sf.jsqlparser.statement.select.UnPivot; /** @@ -42,6 +43,8 @@ public class Table extends ASTNodeAccessImpl implements FromItem, MultiPartName private Alias alias; + private SampleClause sampleClause; + private Pivot pivot; private UnPivot unpivot; @@ -50,8 +53,7 @@ public class Table extends ASTNodeAccessImpl implements FromItem, MultiPartName private SQLServerHints sqlServerHints; - public Table() { - } + public Table() {} public Table(String name) { setName(name); @@ -104,10 +106,10 @@ public void setSchemaName(String schemaName) { public String getName() { String name = getIndex(NAME_IDX); - if (name!=null && name.contains("@")) { + if (name != null && name.contains("@")) { int pos = name.lastIndexOf('@'); - if (pos>0) { - name = name.substring(0, pos ); + if (pos > 0) { + name = name.substring(0, pos); } } return name; @@ -115,10 +117,10 @@ public String getName() { public String getDBLinkName() { String name = getIndex(NAME_IDX); - if (name!=null && name.contains("@")) { + if (name != null && name.contains("@")) { int pos = name.lastIndexOf('@'); - if (pos>0 && name.length()>1) { - name = name.substring(pos+1); + if (pos > 0 && name.length() > 1) { + name = name.substring(pos + 1); } } return name; @@ -232,12 +234,46 @@ public void setSqlServerHints(SQLServerHints sqlServerHints) { this.sqlServerHints = sqlServerHints; } + public SampleClause getSampleClause() { + return sampleClause; + } + + public Table setSampleClause(SampleClause sampleClause) { + this.sampleClause = sampleClause; + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append(getFullyQualifiedName()); + if (alias != null) { + builder.append(alias); + } + + if (sampleClause != null) { + sampleClause.appendTo(builder); + } + + if (pivot != null) { + builder.append(" ").append(pivot); + } + + if (unpivot != null) { + builder.append(" ").append(unpivot); + } + + if (mysqlHints != null) { + builder.append(mysqlHints); + } + + if (sqlServerHints != null) { + builder.append(sqlServerHints); + } + return builder; + } + @Override public String toString() { - return getFullyQualifiedName() + ((alias != null) ? alias.toString() : "") - + ((pivot != null) ? " " + pivot : "") + ((unpivot != null) ? " " + unpivot : "") - + ((mysqlHints != null) ? mysqlHints.toString() : "") - + ((sqlServerHints != null) ? sqlServerHints.toString() : ""); + return appendTo(new StringBuilder()).toString(); } @Override diff --git a/src/main/java/net/sf/jsqlparser/statement/ReturningClause.java b/src/main/java/net/sf/jsqlparser/statement/ReturningClause.java index 6d42ffdfc..3c7525b46 100644 --- a/src/main/java/net/sf/jsqlparser/statement/ReturningClause.java +++ b/src/main/java/net/sf/jsqlparser/statement/ReturningClause.java @@ -15,9 +15,9 @@ import java.util.List; /** - * RETURNING clause according to - * {@see https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/DELETE.html#GUID-156845A5-B626-412B-9F95-8869B988ABD7 - * } Part of UPDATE, INSERT, DELETE statements + * RETURNING clause according to Part of UPDATE, INSERT, DELETE statements */ public class ReturningClause extends ArrayList> { diff --git a/src/main/java/net/sf/jsqlparser/statement/select/FromItem.java b/src/main/java/net/sf/jsqlparser/statement/select/FromItem.java index b43d7b33b..eb2ee84c4 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/FromItem.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItem.java @@ -43,4 +43,5 @@ default FromItem withUnPivot(UnPivot unpivot) { void setUnPivot(UnPivot unpivot); + } diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Join.java b/src/main/java/net/sf/jsqlparser/statement/select/Join.java index dd34e9c06..efaa3e6c7 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/Join.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/Join.java @@ -86,7 +86,7 @@ public Join withInner(boolean b) { /** * - * @return Sets the INNER keyword and switches off any contradicting qualifiers automatically. + * Sets the INNER keyword and switches off any contradicting qualifiers automatically. */ public void setInner(boolean b) { if (b) { @@ -128,7 +128,7 @@ public Join withOuter(boolean b) { /** * - * @return Sets the OUTER keyword and switches off any contradicting qualifiers automatically. + * Sets the OUTER keyword and switches off any contradicting qualifiers automatically. */ public void setOuter(boolean b) { if (b) { @@ -184,7 +184,7 @@ public Join withLeft(boolean b) { /** * - * @return Sets the LEFT keyword and switches off any contradicting qualifiers automatically. + * Sets the LEFT keyword and switches off any contradicting qualifiers automatically. */ public void setLeft(boolean b) { if (b) { @@ -210,7 +210,7 @@ public Join withRight(boolean b) { /** * - * @return Sets the RIGHT keyword and switches off any contradicting qualifiers automatically. + * Sets the RIGHT keyword and switches off any contradicting qualifiers automatically. */ public void setRight(boolean b) { if (b) { diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java b/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java new file mode 100644 index 000000000..d405f9b7e --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java @@ -0,0 +1,120 @@ +package net.sf.jsqlparser.statement.select; + +public class SampleClause { + public enum SampleKeyword { + SAMPLE, TABLESAMPLE; + + public static SampleKeyword from(String sampleKeyword) { + return Enum.valueOf(SampleKeyword.class, sampleKeyword.toUpperCase()); + } + } + + public enum SampleMethod { + BERNOULLI, SYSTEM, BLOCK; + + public static SampleMethod from(String sampleMethod) { + return Enum.valueOf(SampleMethod.class, sampleMethod.toUpperCase()); + } + } + + private SampleKeyword keyword; + private SampleMethod method; + private Number percentageArgument; + private Number repeatArgument; + + // Oracle Specific + private Number seedArgument; + + public SampleClause(String keyword, String method, Number percentageArgument, + Number repeatArgument, Number seedArgument) { + this.keyword = SampleKeyword.from(keyword); + this.method = method == null || method.length() == 0 ? null : SampleMethod.from(method); + this.percentageArgument = percentageArgument; + this.repeatArgument = repeatArgument; + this.seedArgument = seedArgument; + } + + public SampleClause() { + this(SampleKeyword.TABLESAMPLE.toString(), null, null, null, null); + } + + public SampleClause(String keyword) { + this(keyword, null, null, null, null); + } + + public SampleKeyword getKeyword() { + return keyword; + } + + public SampleClause setKeyword(SampleKeyword keyword) { + this.keyword = keyword; + return this; + } + + public Number getPercentageArgument() { + return percentageArgument; + } + + public SampleClause setPercentageArgument(Number percentageArgument) { + this.percentageArgument = percentageArgument; + return this; + } + + public Number getRepeatArgument() { + return repeatArgument; + } + + public SampleClause setRepeatArgument(Number repeatArgument) { + this.repeatArgument = repeatArgument; + return this; + } + + public Number getSeedArgument() { + return seedArgument; + } + + public SampleClause setSeedArgument(Number seedArgument) { + this.seedArgument = seedArgument; + return this; + } + + public SampleMethod getMethod() { + return method; + } + + public SampleClause setMethod(SampleMethod method) { + this.method = method; + return this; + } + + public SampleClause setMethod(String method) { + this.method = method == null || method.length() == 0 ? null : SampleMethod.from(method); + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + + builder.append(" ").append(keyword); + if (method != null) { + builder.append(" ").append(method); + } + + if (percentageArgument != null) { + builder.append(" (").append(percentageArgument).append(")"); + } + + if (repeatArgument != null) { + builder.append(" REPEATABLE (").append(repeatArgument).append(")"); + } + + if (seedArgument != null) { + builder.append(" SEED (").append(seedArgument).append(")"); + } + + return builder; + } + + public String toString() { + return appendTo(new StringBuilder()).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java index c5f6636e3..0ba071794 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -211,6 +211,13 @@ public void visit(PlainSelect plainSelect) { if (plainSelect.getFromItem() != null) { buffer.append(" FROM "); plainSelect.getFromItem().accept(this); + + if (plainSelect.getFromItem() instanceof Table) { + Table table = (Table) plainSelect.getFromItem(); + if (table.getSampleClause() != null) { + table.getSampleClause().appendTo(buffer); + } + } } if (plainSelect.getLateralViews() != null) { diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 71f224213..60a3473c4 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -150,9 +150,11 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | +| | | | @@ -363,6 +365,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -377,8 +380,10 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | +| | | | @@ -402,6 +407,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -1740,7 +1746,7 @@ String RelObjectNameWithoutValue() : { Token tk = null; } { ( tk= | tk= | tk= | tk= | tk= | tk= | tk= - | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="ALGORITHM" | tk="ALTER" | tk="ANALYZE" | tk="APPLY" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="BEGIN" | tk="BINARY" | tk="BIT" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMIT" | tk="CONFLICT" | tk="CONVERT" | tk="COSTS" | tk="CS" | tk="CYCLE" | tk="DATABASE" | tk="DDL" | tk="DECLARE" | tk="DEFAULT" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="ENABLE" | tk="END" | tk="ESCAPE" | tk="EXCLUDE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXTENDED" | tk="EXTRACT" | tk="FALSE" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GLOBAL" | tk="GRANT" | tk="GUARD" | tk="HISTORY" | tk="HOPPING" | tk="INCLUDE" | tk="INCREMENT" | tk="INDEX" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="ISNULL" | tk="JSON" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="LAST" | tk="LEADING" | tk="LINK" | tk="LOCAL" | tk="LOCKED" | tk="LOG" | tk="MATCH" | tk="MATCHED" | tk="MATERIALIZED" | tk="MAXVALUE" | tk="MEMBER" | tk="MERGE" | tk="MINVALUE" | tk="MODIFY" | tk="MOVEMENT" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="OVER" | tk="OVERLAPS" | tk="PARALLEL" | tk="PARENT" | tk="PARTITION" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PRECEDING" | tk="PRECISION" | tk="PRIMARY" | tk="PRIOR" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGISTER" | tk="RENAME" | tk="REPLACE" | tk="RESET" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RLIKE" | tk="ROLLBACK" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="STORED" | tk="STRING" | tk="SUSPEND" | tk="SWITCH" | tk="SYNONYM" | tk="SYSTEM" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TO" | tk="TRIGGER" | tk="TRUE" | tk="TRUNCATE" | tk="TUMBLING" | tk="TYPE" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VERBOSE" | tk="VIEW" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) + | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="ALGORITHM" | tk="ALTER" | tk="ANALYZE" | tk="APPLY" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOCK" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMIT" | tk="CONFLICT" | tk="CONVERT" | tk="COSTS" | tk="CS" | tk="CYCLE" | tk="DATABASE" | tk="DDL" | tk="DECLARE" | tk="DEFAULT" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="ENABLE" | tk="END" | tk="ESCAPE" | tk="EXCLUDE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXTENDED" | tk="EXTRACT" | tk="FALSE" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GLOBAL" | tk="GRANT" | tk="GUARD" | tk="HISTORY" | tk="HOPPING" | tk="INCLUDE" | tk="INCREMENT" | tk="INDEX" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="ISNULL" | tk="JSON" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="LAST" | tk="LEADING" | tk="LINK" | tk="LOCAL" | tk="LOCKED" | tk="LOG" | tk="MATCH" | tk="MATCHED" | tk="MATERIALIZED" | tk="MAXVALUE" | tk="MEMBER" | tk="MERGE" | tk="MINVALUE" | tk="MODIFY" | tk="MOVEMENT" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="OVER" | tk="OVERLAPS" | tk="PARALLEL" | tk="PARENT" | tk="PARTITION" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PRECEDING" | tk="PRECISION" | tk="PRIMARY" | tk="PRIOR" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGISTER" | tk="RENAME" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RLIKE" | tk="ROLLBACK" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="STORED" | tk="STRING" | tk="SUSPEND" | tk="SWITCH" | tk="SYNONYM" | tk="SYSTEM" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TO" | tk="TRIGGER" | tk="TRUE" | tk="TRUNCATE" | tk="TUMBLING" | tk="TYPE" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VERBOSE" | tk="VIEW" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) { return tk.image; } } @@ -1825,10 +1831,64 @@ Table TableWithAlias(): Alias alias = null; } { - table=Table() [ LOOKAHEAD(2) alias=Alias() { table.setAlias(alias); }] + table=Table() + [ LOOKAHEAD(2) alias=Alias() { table.setAlias(alias); }] { return table; } } +Number Number(): +{ + Token token; + Number number; +} +{ + ( + token = { number = Double.valueOf(token.image); } + | + token = { number = Long.valueOf(token.image); } + ) + + { + return number; + } +} + +SampleClause SampleClause(): +{ + Token token; + SampleClause sampleClause; + String keyword; + String method=null; + Number percentageArgument; + Number repeatArgument=null; + Number seedArgument=null; +} +{ + ( + ( + // Oracle + token= { keyword = token.image; } + [ token= { method = token.image; } ] + ) + | + ( + // SQL:2016 compliant + token = { keyword = token.image; } + ( token = | token = ) { method = token.image; } + ) + ) + + "(" percentageArgument = Number() ")" + + [ LOOKAHEAD(2) "(" repeatArgument = Number() ")" ] + + [ LOOKAHEAD(2) "(" seedArgument = Number() ")" ] + + { + return new SampleClause(keyword, method, percentageArgument, repeatArgument, seedArgument); + } +} + Select SelectWithWithItems( List withItems): { Select select; @@ -2546,6 +2606,7 @@ FromItem FromItem(): { FromItem fromItem = null; FromItem fromItem2 = null; + SampleClause sampleClause; Pivot pivot = null; UnPivot unpivot = null; Alias alias = null; @@ -2555,41 +2616,39 @@ FromItem FromItem(): } { ( + LOOKAHEAD( TableFunction() ) fromItem=TableFunction() + | + LOOKAHEAD(3) fromItem=Table() + | + LOOKAHEAD( ParenthesedFromItem() ) fromItem = ParenthesedFromItem() + | + LOOKAHEAD(3) ( + fromItem=ParenthesedSelect() + [ LOOKAHEAD(2) pivot=Pivot() { fromItem.setPivot(pivot); } ] + [ LOOKAHEAD(2) unpivot=UnPivot() { fromItem.setUnPivot(unpivot); } ] + ) + | + fromItem=LateralSubSelect() + ) + [ LOOKAHEAD(2) alias=Alias() { fromItem.setAlias(alias); } ] + [ LOOKAHEAD(2) sampleClause = SampleClause() { ((Table) fromItem).setSampleClause(sampleClause); } ] + [ LOOKAHEAD(2) unpivot=UnPivot() { fromItem.setUnPivot(unpivot); } ] + [ LOOKAHEAD(2) ( LOOKAHEAD(2) pivot=PivotXml() | pivot=Pivot() ) { fromItem.setPivot(pivot); } ] + [ + LOOKAHEAD(2) ( - LOOKAHEAD( TableFunction() ) fromItem=TableFunction() - | - LOOKAHEAD(3) fromItem=Table() - | - LOOKAHEAD( ParenthesedFromItem() ) fromItem = ParenthesedFromItem() - | - LOOKAHEAD(3) ( - fromItem=ParenthesedSelect() - [ LOOKAHEAD(2) pivot=Pivot() { fromItem.setPivot(pivot); } ] - [ LOOKAHEAD(2) unpivot=UnPivot() { fromItem.setUnPivot(unpivot); } ] - ) + indexHint = MySQLIndexHint() { + if (fromItem instanceof Table) + ((Table) fromItem).setHint(indexHint); + } | - fromItem=LateralSubSelect() + sqlServerHints = SQLServerHints() { + if (fromItem instanceof Table) + ((Table) fromItem).setSqlServerHints(sqlServerHints); + } ) - - [ LOOKAHEAD(2) alias=Alias() { fromItem.setAlias(alias); } ] - [ LOOKAHEAD(2) unpivot=UnPivot() { fromItem.setUnPivot(unpivot); } ] - [ LOOKAHEAD(2) ( LOOKAHEAD(2) pivot=PivotXml() | pivot=Pivot() ) { fromItem.setPivot(pivot); } ] - [ - LOOKAHEAD(2) - ( - indexHint = MySQLIndexHint() { - if (fromItem instanceof Table) - ((Table) fromItem).setHint(indexHint); - } - | - sqlServerHints = SQLServerHints() { - if (fromItem instanceof Table) - ((Table) fromItem).setSqlServerHints(sqlServerHints); - } - ) - ] - ) + ] { return fromItem; } diff --git a/src/site/sphinx/keywords.rst b/src/site/sphinx/keywords.rst index feb1fcf52..40778c18d 100644 --- a/src/site/sphinx/keywords.rst +++ b/src/site/sphinx/keywords.rst @@ -141,6 +141,8 @@ The following Keywords are **restricted** in JSQLParser-|JSQLPARSER_VERSION| and +----------------------+-------------+-----------+ | RIGHT | Yes | Yes | +----------------------+-------------+-----------+ +| SAMPLE | Yes | | ++----------------------+-------------+-----------+ | SEL | Yes | | +----------------------+-------------+-----------+ | SELECT | Yes | | @@ -179,6 +181,8 @@ The following Keywords are **restricted** in JSQLParser-|JSQLPARSER_VERSION| and +----------------------+-------------+-----------+ | STRAIGHT_JOIN | Yes | Yes | +----------------------+-------------+-----------+ +| TABLESAMPLE | Yes | | ++----------------------+-------------+-----------+ | VALUE | Yes | Yes | +----------------------+-------------+-----------+ | VALUES | Yes | Yes | diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java index b72de5f2b..0b78f60d0 100644 --- a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java +++ b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java @@ -496,8 +496,9 @@ public void execute() throws Throwable { void testUnbalancedPosition() { String sqlStr = "SELECT * from ( test "; sqlStr = "select\n" + - " concat('{','\"dffs\":\"',if(dffs is null,'',cast(dffs as string),'\",\"djr\":\"',if(djr is null,'',cast(djr as string),'\",\"djrq\":\"',if(djrq is null,'',cast(djrq as string),'\",\"thjssj\":\"',if(thjssj is null,'',cast(thjssj as string),'\",\"thkssj\":\"',if(thkssj is null,'',cast(thkssj as string),'\",\"sjc\":\"',if(sjc is null,'',cast(sjc as string),'\",\"ldhm\":\"',if(ldhm is null,'',cast(ldhm as string),'\",\"lxdh\":\"',if(lxdh is null,'',cast(lxdh as string),'\",\"md\":\"',if(md is null,'',cast(md as string),'\",\"nr\":\"',if(nr is null,'',cast(nr as string),'\",\"nrfl\":\"',if(nrfl is null,'',cast(nrfl as string),'\",\"nrwjid\":\"',if(nrwjid is null,'',cast(nrwjid as string),'\",\"sfbm\":\"',if(sfbm is null,'',cast(sfbm as string),'\",\"sjly\":\"',if(sjly is null,'',cast(sjly as string),'\",\"wtsd\":\"',if(wtsd is null,'',cast(wtsd as string),'\",\"xb\":\"',if(xb is null,'',cast(xb as string),'\",\"xfjbh\":\"',if(xfjbh is null,'',cast(xfjbh as string),'\",\"xfjid\":\"',if(xfjid is null,'',cast(xfjid as string),'\",\"xm\":\"',if(xm is null,'',cast(xm as string),'\",\"zhut\":\"',if(zhut is null,'',cast(zhut as string),'\",\"zt\":\"',if(zt is null,'',cast(zt as string),'\"}')\n" + + " concat('{','\"dffs\":\"',if(dffs is null,'',cast(dffs as string),'\",\"djr\":\"',if(djr is null,'',cast(djr as string),'\",\"djrq\":\"',if(djrq is null,'',cast(djrq as string),'\",\"thjssj\":\"',if(thjssj is null,'',cast(thjssj as string),'\",\"thkssj\":\"',if(thkssj is null,'',cast(thkssj as string),'\",\"sjc\":\"',if(sjc is null,'',cast(sjc as string),'\",\"ldhm\":\"',if(ldhm is null,'',cast(ldhm as string),'\",\"lxdh\":\"',if(lxdh is null,'',cast(lxdh as string),'\",\"md\":\"',if(md is null,'',cast(md as string),'\",\"nr\":\"',if(nr is null,'',cast(nr as string),'\",\"nrfl\":\"',if(nrfl is null,'',cast(nrfl as string),'\",\"nrwjid\":\"',if(nrwjid is null,'',cast(nrwjid as string),'\",\"sfbm\":\"',if(sfbm is null,'',cast(sfbm as string),'\",\"sjly\":\"',if(sjly is null,'',cast(sjly as string),'\",\"wtsd\":\"',if(wtsd is null,'',cast(wtsd as string),'\",\"xb\":\"',if(xb is null,'',cast(xb as string),'\",\"xfjbh\":\"',if(xfjbh is null,'',cast(xfjbh as string),'\",\"xfjid\":\"',if(xfjid is null,'',cast(xfjid as string),'\",\"xm\":\"',if(xm is null,'',cast(xm as string),'\",\"zhut\":\"',if(zhut is null,'',cast(zhut as string),'\",\"zt\":\"',if(zt is null,'',cast(zt as string),'\"}')\n" + + " from tab"; - assertEquals(-1 , CCJSqlParserUtil.getUnbalancedPosition(sqlStr)); + assertEquals(1122, CCJSqlParserUtil.getUnbalancedPosition(sqlStr)); } } diff --git a/src/test/java/net/sf/jsqlparser/statement/ConditionalKeywordsTest.java b/src/test/java/net/sf/jsqlparser/statement/ConditionalKeywordsTest.java index ae7599812..fadae1256 100644 --- a/src/test/java/net/sf/jsqlparser/statement/ConditionalKeywordsTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/ConditionalKeywordsTest.java @@ -35,12 +35,11 @@ public static Stream keyWords() { List keywords = new ArrayList<>(); try { try { - keywords.addAll( ParserKeywordsUtils.getAllKeywordsUsingRegex(file) ); - for (String reserved: ParserKeywordsUtils.getReservedKeywords( + keywords.addAll(ParserKeywordsUtils.getAllKeywordsUsingRegex(file)); + for (String reserved : ParserKeywordsUtils.getReservedKeywords( // get all PARSER RESTRICTED without the ALIAS RESTRICTED ParserKeywordsUtils.RESTRICTED_JSQLPARSER - & ~ParserKeywordsUtils.RESTRICTED_ALIAS - )) { + | ParserKeywordsUtils.RESTRICTED_ALIAS)) { keywords.remove(reserved); } } catch (Exception ex) { @@ -55,7 +54,8 @@ public static Stream keyWords() { @ParameterizedTest(name = "Keyword {0}") @MethodSource("keyWords") public void testRelObjectNameExt(String keyword) throws JSQLParserException { - String sqlStr = String.format("SELECT %1$s.%1$s.%1$s AS \"%1$s\" from %1$s ORDER BY %1$s ", keyword); + String sqlStr = String.format( + "SELECT %1$s.%1$s.%1$s \"%1$s\" from %1$s \"%1$s\" ORDER BY %1$s ", keyword); assertSqlCanBeParsedAndDeparsed(sqlStr, true); } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SampleClauseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SampleClauseTest.java new file mode 100644 index 000000000..722b599cd --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/select/SampleClauseTest.java @@ -0,0 +1,35 @@ +package net.sf.jsqlparser.statement.select; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.test.TestUtils; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + + +class SampleClauseTest { + + @ParameterizedTest + @ValueSource(strings = { + "SELECT * FROM fact_halllogin_detail TABLESAMPLE BERNOULLI (10) where dt>=20220710 limit 10", + "SELECT * FROM fact_halllogin_detail TABLESAMPLE BERNOULLI (10.1) where dt>=20220710 limit 10", + "SELECT * FROM fact_halllogin_detail TABLESAMPLE SYSTEM (10) where dt>=20220710 limit 10", + "SELECT * FROM fact_halllogin_detail TABLESAMPLE SYSTEM (10) REPEATABLE (10) where dt>=20220710 limit 10", + "SELECT * FROM fact_halllogin_detail TABLESAMPLE SYSTEM (10.0) REPEATABLE (10.1) where dt>=20220710 limit 10" + }) + void standardTestIssue1593(String sqlStr) throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @ParameterizedTest + @ValueSource(strings = { + "SELECT * from table_name SAMPLE(99)", "SELECT * from table_name SAMPLE(99.1)", + "SELECT * from table_name SAMPLE BLOCK (99)", + "SELECT * from table_name SAMPLE BLOCK (99.1)", + "SELECT * from table_name SAMPLE BLOCK (99) SEED (10) ", + "SELECT * from table_name SAMPLE BLOCK (99.1) SEED (10.1)" + }) + void standardOracleIssue1826() throws JSQLParserException { + String sqlStr = "SELECT * from table_name SAMPLE(99)"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } +} diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index 694f6b13b..498c21949 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -5726,23 +5726,17 @@ public void testNotIsNullInFilter() throws JSQLParserException { void testBackSlashQuotationIssue1812() throws JSQLParserException { String sqlStr = "SELECT ('\\'', 'a')"; Statement stmt2 = CCJSqlParserUtil.parse( - sqlStr - , parser -> parser - .withBackslashEscapeCharacter(true) - ); + sqlStr, parser -> parser + .withBackslashEscapeCharacter(true)); sqlStr = "INSERT INTO recycle_record (a,f) VALUES ('\\'anything', 'abc');"; stmt2 = CCJSqlParserUtil.parse( - sqlStr - , parser -> parser - .withBackslashEscapeCharacter(true) - ); + sqlStr, parser -> parser + .withBackslashEscapeCharacter(true)); sqlStr = "INSERT INTO recycle_record (a,f) VALUES ('\\'','83653692186728700711687663398101');"; stmt2 = CCJSqlParserUtil.parse( - sqlStr - , parser -> parser - .withBackslashEscapeCharacter(true) - ); + sqlStr, parser -> parser + .withBackslashEscapeCharacter(true)); } } diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/pivot01.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/pivot01.sql index 0ab76d6d8..84954082d 100644 --- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/pivot01.sql +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/pivot01.sql @@ -14,4 +14,5 @@ select * from pivot_table ---@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 3, 2021, 7:20:08 AM \ No newline at end of file +--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 3, 2021, 7:20:08 AM +--@FAILURE: select*from pivot_tableunpivot(yearly_total for order_mode in(store as 'direct',internet as 'online'))order by year,order_mode recorded first on Jul 12, 2023, 12:58:42 PM \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/pivot11.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/pivot11.sql index edad4d55d..8ed570271 100644 --- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/pivot11.sql +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/pivot11.sql @@ -28,4 +28,5 @@ join d using(c) where d_t = 'p' ---@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 3, 2021, 7:20:08 AM \ No newline at end of file +--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 3, 2021, 7:20:08 AM +--@FAILURE: select*from spivot(max(c_c_p)as max_ccp,max(d_c_p)max_dcp,max(d_x_p)dxp,count(1)cnt for(i,p)in((1,1)as one_one,(1,2)as one_two,(1,3)as one_three,(2,1)as two_one,(2,2)as two_two,(2,3)as two_three))join d using(c)where d_t='p' recorded first on Jul 12, 2023, 12:58:42 PM \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/sample01.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/sample01.sql index 116ee9025..96a881ecc 100644 --- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/sample01.sql +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/sample01.sql @@ -12,4 +12,6 @@ select * from select 1 as c1 from "sys"."obj$" sample block (14.285714 , 1) seed (1) "o" ) samplesub ---@FAILURE: Encountered unexpected token: "block" recorded first on Aug 3, 2021, 7:20:08 AM \ No newline at end of file +--@FAILURE: Encountered unexpected token: "block" recorded first on Aug 3, 2021, 7:20:08 AM +--@FAILURE: Encountered unexpected token: "block" "BLOCK" recorded first on Jul 12, 2023, 12:58:42 PM +--@FAILURE: Encountered unexpected token: "," "," recorded first on Jul 12, 2023, 1:30:58 PM \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/simple05.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/simple05.sql index c210af03f..0c7d2b161 100644 --- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/simple05.sql +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/simple05.sql @@ -17,4 +17,5 @@ select * from a ) ---@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 3, 2021, 7:20:08 AM \ No newline at end of file +--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 3, 2021, 7:20:08 AM +--@FAILURE: select*from(select*from aunpivot(value for value_type in(dummy))) recorded first on Jul 12, 2023, 12:58:42 PM \ No newline at end of file