Skip to content

Commit 7c4ca96

Browse files
fix: support SAMPLE and EXTEND as unquoted identifiers (#2774)
Signed-off-by: 付典 <fudianchn@gmail.com> Co-authored-by: manticore-projects <andreas@manticore-projects.com>
1 parent a6c8e89 commit 7c4ca96

2 files changed

Lines changed: 74 additions & 5 deletions

File tree

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,6 +1879,13 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
18791879
// AS always starts an alias
18801880
if (kind == K_AS) return true;
18811881

1882+
// SAMPLE followed by a size or BLOCK starts a sampling clause.
1883+
if (kind == K_SAMPLE && (getToken(2).kind == OPENING_BRACKET
1884+
|| getToken(2).kind == K_BLOCK || getToken(2).kind == S_LONG
1885+
|| getToken(2).kind == S_DOUBLE)) {
1886+
return false;
1887+
}
1888+
18821889
// String-literal alias: SELECT col 'myAlias'
18831890
if (kind == S_CHAR_LITERAL) return true;
18841891

@@ -3038,6 +3045,7 @@ String NonReservedWord() :
30383045
| tk=<K_EXECUTE: "EXECUTE">
30393046
| tk=<K_EXPLAIN:"EXPLAIN">
30403047
| tk=<K_EXPLICIT:"EXPLICIT">
3048+
| tk=<K_EXTEND:"EXTEND">
30413049
| tk=<K_EXISTS:"EXISTS">
30423050
| tk=<K_EXTENDED:"EXTENDED">
30433051
| tk=<K_EXTRACT:"EXTRACT">
@@ -3231,6 +3239,7 @@ String NonReservedWord() :
32313239
| tk=<K_RTRIM: "RTRIM">
32323240
| tk=<K_SAFE_CAST: "SAFE_CAST">
32333241
| tk=<K_SAFE_CONVERT: "SAFE_CONVERT">
3242+
| tk=<K_SAMPLE:"SAMPLE">
32343243
| tk=<K_SAVEPOINT: "SAVEPOINT">
32353244
| tk=<K_SCHEDULE: "SCHEDULE">
32363245
| tk=<K_SCHEMA: "SCHEMA">
@@ -3366,7 +3375,8 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */
33663375
| <K_ELSE:"ELSE">
33673376
| <K_EXCEPT: "EXCEPT">
33683377
| <K_EXCLUDES: "EXCLUDES"> /* Salesforce SOQL */
3369-
| <K_EXTEND:"EXTEND">
3378+
| <K_EXISTS:"EXISTS">
3379+
| <K_FBV:"FBV">
33703380
| <K_FETCH:"FETCH">
33713381
| <K_FINAL: "FINAL">
33723382
| <K_FALSE: "FALSE">
@@ -3420,7 +3430,7 @@ TOKEN: /* Reserved SQL Keywords and structural tokens */
34203430
| <K_QUALIFY: "QUALIFY">
34213431
| <K_RETURNING: "RETURNING">
34223432
| <K_RIGHT:"RIGHT">
3423-
| <K_SAMPLE: "SAMPLE">
3433+
| <K_SCRIPT: "SCRIPT">
34243434
| <K_SELECT: ("SELECT" | "SEL")>
34253435
| <K_SEMI : "SEMI">
34263436
| <K_SET:"SET">

‎src/test/java/net/sf/jsqlparser/statement/KeywordsTest.java‎

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
import java.util.stream.Stream;
1313
import net.sf.jsqlparser.JSQLParserException;
14+
import net.sf.jsqlparser.expression.TernaryExpression;
15+
import net.sf.jsqlparser.expression.Function;
16+
import net.sf.jsqlparser.statement.piped.FromQuery;
17+
import net.sf.jsqlparser.statement.piped.SelectPipeOperator;
1418
import net.sf.jsqlparser.expression.NotExpression;
1519
import net.sf.jsqlparser.expression.MapExpression;
1620
import net.sf.jsqlparser.expression.JdbcNamedParameter;
@@ -24,6 +28,7 @@
2428
import net.sf.jsqlparser.schema.Column;
2529
import net.sf.jsqlparser.schema.Table;
2630
import net.sf.jsqlparser.statement.select.PlainSelect;
31+
import net.sf.jsqlparser.statement.select.SampleClause;
2732
import net.sf.jsqlparser.statement.select.WithItem;
2833
import org.junit.jupiter.api.Test;
2934
import org.junit.jupiter.params.ParameterizedTest;
@@ -77,6 +82,26 @@ public void testGroupsAsIdentifier() throws JSQLParserException {
7782
assertSqlCanBeParsedAndDeparsed("CREATE TABLE mytable (groups INT)", true);
7883
}
7984

85+
// sample and extend are non-reserved in the common dialects and reach the
86+
// parser as ordinary table and column names in downstream rewriting tools.
87+
@Test
88+
public void testSampleExtendAsIdentifier() throws JSQLParserException {
89+
assertSqlCanBeParsedAndDeparsed("SELECT sample, extend FROM mytable", true);
90+
assertSqlCanBeParsedAndDeparsed("SELECT mytable.extend FROM mytable", true);
91+
assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE extend = 'x'", true);
92+
assertSqlCanBeParsedAndDeparsed("UPDATE mytable SET sample = 1, extend = 'x' WHERE id = 1",
93+
true);
94+
assertSqlCanBeParsedAndDeparsed("INSERT INTO mytable (sample, extend) VALUES (1, 'x')",
95+
true);
96+
assertSqlCanBeParsedAndDeparsed("SELECT * FROM sample", true);
97+
assertSqlCanBeParsedAndDeparsed("DELETE FROM sample WHERE extend = 'x'", true);
98+
assertSqlCanBeParsedAndDeparsed("INSERT INTO sample (extend) VALUES ('x')", true);
99+
assertSqlCanBeParsedAndDeparsed("SELECT a AS sample, b AS extend FROM mytable", true);
100+
}
101+
102+
@ParameterizedTest
103+
@ValueSource(strings = {"sample", "extend", "SaMpLe", "ExTeNd"})
104+
void testSampleExtendImplicitAliases(String keyword) throws JSQLParserException {
80105
// exists is a non-reserved keyword in the common dialects and reaches the
81106
// parser as an ordinary column name in downstream rewriting tools.
82107
@Test
@@ -116,7 +141,7 @@ void testExistsImplicitAliases(String keyword) throws JSQLParserException {
116141
}
117142

118143
@ParameterizedTest
119-
@ValueSource(strings = {"exists", "ExIsTs"})
144+
@ValueSource(strings = {"exists", "ExIsTs", "sample", "extend", "SaMpLe", "ExTeNd"})
120145
void testExistsCte(String keyword) throws JSQLParserException {
121146
PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(
122147
"WITH " + keyword + "(x) AS (SELECT 1) SELECT x FROM " + keyword, true);
@@ -130,7 +155,7 @@ void testExistsCte(String keyword) throws JSQLParserException {
130155
}
131156

132157
@ParameterizedTest
133-
@ValueSource(strings = {"exists", "ExIsTs"})
158+
@ValueSource(strings = {"exists", "ExIsTs", "sample", "extend", "SaMpLe", "ExTeNd"})
134159
void testExistsExpressionContexts(String keyword) throws JSQLParserException {
135160
PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(
136161
"SELECT ALL " + keyword + " FROM mytable");
@@ -142,7 +167,7 @@ void testExistsExpressionContexts(String keyword) throws JSQLParserException {
142167
}
143168

144169
@ParameterizedTest
145-
@ValueSource(strings = {"exists", "ExIsTs"})
170+
@ValueSource(strings = {"exists", "ExIsTs", "sample", "extend", "SaMpLe", "ExTeNd"})
146171
void testExistsTernaryOperand(String keyword) throws JSQLParserException {
147172
PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(
148173
"SELECT flag ? " + keyword + " : 0 FROM mytable", true);
@@ -152,6 +177,38 @@ void testExistsTernaryOperand(String keyword) throws JSQLParserException {
152177
.getColumnName());
153178
}
154179

180+
@ParameterizedTest
181+
@ValueSource(strings = {"SAMPLE (10)", "SAMPLE BLOCK (10) SEED (1)",
182+
"SAMPLE 0.1 OFFSET 1000", "USING SAMPLE 10 PERCENT (BERNOULLI)",
183+
"TABLESAMPLE SYSTEM (10)"})
184+
void testSampleClauseStaysClause(String clause) throws JSQLParserException {
185+
PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(
186+
"SELECT * FROM mytable " + clause, true);
187+
Table table = assertInstanceOf(Table.class, select.getFromItem());
188+
assertNull(table.getAlias());
189+
SampleClause sample = table.getSampleClause();
190+
assertNotNull(sample);
191+
assertEquals(" " + clause, sample.toString());
192+
}
193+
194+
@ParameterizedTest
195+
@ValueSource(strings = {"sample", "extend", "SaMpLe", "ExTeNd"})
196+
void testSampleExtendPipeExpression(String keyword) throws JSQLParserException {
197+
FromQuery query = (FromQuery) assertSqlCanBeParsedAndDeparsed(
198+
"FROM " + keyword + " |> EXTEND " + keyword + " AS x", true);
199+
SelectPipeOperator operator = assertInstanceOf(SelectPipeOperator.class,
200+
query.getPipeOperators().get(0));
201+
assertEquals("EXTEND", operator.getOperatorName());
202+
assertEquals(keyword, assertInstanceOf(Column.class,
203+
operator.getSelectItems().get(0).getExpression()).getColumnName());
204+
assertSqlCanBeParsedAndDeparsed(
205+
"FROM mytable |> EXTEND " + keyword + "(x) AS x", true);
206+
PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(
207+
"SELECT ALL " + keyword + "(x) FROM mytable");
208+
assertEquals(keyword, assertInstanceOf(Function.class,
209+
select.getSelectItem(0).getExpression()).getName());
210+
}
211+
155212
@Test
156213
void testExistsColumnWithOracleJoin() throws JSQLParserException {
157214
PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(
@@ -253,6 +310,8 @@ void testExistsBeforeNestedTernary() throws JSQLParserException {
253310
assertEquals("exists", assertInstanceOf(Column.class, map.getEntries().get(0).getKey())
254311
.getColumnName());
255312
assertInstanceOf(TernaryExpression.class, map.getEntries().get(0).getValue());
313+
}
314+
256315
// ERRORS introduces Exasol's IMPORT/EXPORT error clauses but is an ordinary name elsewhere,
257316
// e.g. a DuckDB column holding the errors of an upstream step.
258317
@Test

0 commit comments

Comments
 (0)