Skip to content

Commit 9ab1ebd

Browse files
authored
feat: Support unknown keyword (#2141)
1 parent df66569 commit 9ab1ebd

File tree

13 files changed

+200
-4
lines changed

13 files changed

+200
-4
lines changed

src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression;
4343
import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression;
4444
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
45+
import net.sf.jsqlparser.expression.operators.relational.IsUnknownExpression;
4546
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
4647
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
4748
import net.sf.jsqlparser.expression.operators.relational.Matches;
@@ -267,6 +268,12 @@ default void visit(IsBooleanExpression isBooleanExpression) {
267268
this.visit(isBooleanExpression, null);
268269
}
269270

271+
<S> T visit(IsUnknownExpression isUnknownExpression, S context);
272+
273+
default void visit(IsUnknownExpression isUnknownExpression) {
274+
this.visit(isUnknownExpression, null);
275+
}
276+
270277
<S> T visit(LikeExpression likeExpression, S context);
271278

272279
default void visit(LikeExpression likeExpression) {

src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression;
4343
import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression;
4444
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
45+
import net.sf.jsqlparser.expression.operators.relational.IsUnknownExpression;
4546
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
4647
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
4748
import net.sf.jsqlparser.expression.operators.relational.Matches;
@@ -263,6 +264,11 @@ public <S> T visit(IsBooleanExpression isBooleanExpression, S context) {
263264
return isBooleanExpression.getLeftExpression().accept(this, context);
264265
}
265266

267+
@Override
268+
public <S> T visit(IsUnknownExpression isUnknownExpression, S context) {
269+
return isUnknownExpression.getLeftExpression().accept(this, context);
270+
}
271+
266272
@Override
267273
public <S> T visit(LikeExpression likeExpression, S context) {
268274
return visitBinaryExpression(likeExpression, context);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2025 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.expression.operators.relational;
11+
12+
import net.sf.jsqlparser.expression.Expression;
13+
import net.sf.jsqlparser.expression.ExpressionVisitor;
14+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
15+
16+
public class IsUnknownExpression extends ASTNodeAccessImpl implements Expression {
17+
18+
private Expression leftExpression;
19+
private boolean isNot = false;
20+
21+
public Expression getLeftExpression() {
22+
return leftExpression;
23+
}
24+
25+
public void setLeftExpression(Expression expression) {
26+
leftExpression = expression;
27+
}
28+
29+
public boolean isNot() {
30+
return isNot;
31+
}
32+
33+
public void setNot(boolean isNot) {
34+
this.isNot = isNot;
35+
}
36+
37+
@Override
38+
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
39+
return expressionVisitor.visit(this, context);
40+
}
41+
42+
@Override
43+
public String toString() {
44+
return leftExpression + " IS" + (isNot ? " NOT" : "") + " UNKNOWN";
45+
}
46+
47+
public IsUnknownExpression withLeftExpression(Expression leftExpression) {
48+
this.setLeftExpression(leftExpression);
49+
return this;
50+
}
51+
52+
public IsUnknownExpression withNot(boolean isNot) {
53+
this.setNot(isNot);
54+
return this;
55+
}
56+
57+
public <E extends Expression> E getLeftExpression(Class<E> type) {
58+
return type.cast(getLeftExpression());
59+
}
60+
}

src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ public class ParserKeywordsUtils {
133133
{"UNBOUNDED", RESTRICTED_JSQLPARSER},
134134
{"UNION", RESTRICTED_SQL2016},
135135
{"UNIQUE", RESTRICTED_SQL2016},
136+
{"UNKNOWN", RESTRICTED_SQL2016},
136137
{"UNPIVOT", RESTRICTED_JSQLPARSER},
137138
{"USE", RESTRICTED_JSQLPARSER},
138139
{"USING", RESTRICTED_SQL2016},

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression;
102102
import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression;
103103
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
104+
import net.sf.jsqlparser.expression.operators.relational.IsUnknownExpression;
104105
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
105106
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
106107
import net.sf.jsqlparser.expression.operators.relational.Matches;
@@ -530,6 +531,12 @@ public <S> Void visit(IsBooleanExpression isBooleanExpression, S context) {
530531
return null;
531532
}
532533

534+
@Override
535+
public <S> Void visit(IsUnknownExpression isUnknownExpression, S context) {
536+
537+
return null;
538+
}
539+
533540
@Override
534541
public <S> Void visit(JdbcParameter jdbcParameter, S context) {
535542

src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression;
102102
import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression;
103103
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
104+
import net.sf.jsqlparser.expression.operators.relational.IsUnknownExpression;
104105
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
105106
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
106107
import net.sf.jsqlparser.expression.operators.relational.Matches;
@@ -428,6 +429,17 @@ public <S> StringBuilder visit(IsBooleanExpression isBooleanExpression, S contex
428429
return buffer;
429430
}
430431

432+
@Override
433+
public <S> StringBuilder visit(IsUnknownExpression isUnknownExpression, S context) {
434+
isUnknownExpression.getLeftExpression().accept(this, context);
435+
if (isUnknownExpression.isNot()) {
436+
buffer.append(" IS NOT UNKNOWN");
437+
} else {
438+
buffer.append(" IS UNKNOWN");
439+
}
440+
return buffer;
441+
}
442+
431443
@Override
432444
public <S> StringBuilder visit(JdbcParameter jdbcParameter, S context) {
433445
buffer.append(jdbcParameter.getParameterCharacter());
@@ -514,6 +526,10 @@ public void visit(IsBooleanExpression isBooleanExpression) {
514526
visit(isBooleanExpression, null);
515527
}
516528

529+
public void visit(IsUnknownExpression isUnknownExpression) {
530+
visit(isUnknownExpression, null);
531+
}
532+
517533
public void visit(JdbcParameter jdbcParameter) {
518534
visit(jdbcParameter, null);
519535
}

src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression;
103103
import net.sf.jsqlparser.expression.operators.relational.IsDistinctExpression;
104104
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
105+
import net.sf.jsqlparser.expression.operators.relational.IsUnknownExpression;
105106
import net.sf.jsqlparser.expression.operators.relational.JsonOperator;
106107
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
107108
import net.sf.jsqlparser.expression.operators.relational.Matches;
@@ -289,6 +290,12 @@ public <S> Void visit(IsBooleanExpression isBooleanExpression, S context) {
289290
return null;
290291
}
291292

293+
@Override
294+
public <S> Void visit(IsUnknownExpression isUnknownExpression, S context) {
295+
isUnknownExpression.getLeftExpression().accept(this, context);
296+
return null;
297+
}
298+
292299
@Override
293300
public <S> Void visit(JdbcParameter jdbcParameter, S context) {
294301
validateFeature(Feature.jdbcParameter);
@@ -383,6 +390,10 @@ public void visit(IsBooleanExpression isBooleanExpression) {
383390
visit(isBooleanExpression, null); // Call the parametrized visit method with null context
384391
}
385392

393+
public void visit(IsUnknownExpression isUnknownExpression) {
394+
visit(isUnknownExpression, null); // Call the parametrized visit method with null context
395+
}
396+
386397
public void visit(JdbcParameter jdbcParameter) {
387398
visit(jdbcParameter, null); // Call the parametrized visit method with null context
388399
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
496496
| <K_UNBOUNDED: "UNBOUNDED">
497497
| <K_UNION:"UNION">
498498
| <K_UNIQUE:"UNIQUE">
499+
| <K_UNKNOWN:"UNKNOWN">
499500
| <K_UNLOGGED: "UNLOGGED">
500501
| <K_UNPIVOT:"UNPIVOT">
501502
| <K_UPDATE:"UPDATE">
@@ -4153,6 +4154,8 @@ Expression SQLCondition():
41534154
|
41544155
LOOKAHEAD(IsBooleanExpression()) result=IsBooleanExpression(left)
41554156
|
4157+
LOOKAHEAD(IsUnknownExpression()) result=IsUnknownExpression(left)
4158+
|
41564159
LOOKAHEAD(2) result=LikeExpression(left)
41574160
|
41584161
LOOKAHEAD(IsDistinctExpression()) result=IsDistinctExpression(left)
@@ -4366,6 +4369,21 @@ Expression IsBooleanExpression(Expression leftExpression):
43664369
}
43674370
}
43684371

4372+
Expression IsUnknownExpression(Expression leftExpression):
4373+
{
4374+
IsUnknownExpression result = new IsUnknownExpression();
4375+
}
4376+
{
4377+
(
4378+
<K_IS> [<K_NOT> { result.setNot(true); } ] <K_UNKNOWN>
4379+
)
4380+
4381+
{
4382+
result.setLeftExpression(leftExpression);
4383+
return result;
4384+
}
4385+
}
4386+
43694387
Expression ExistsExpression():
43704388
{
43714389
ExistsExpression result = new ExistsExpression();

src/site/sphinx/keywords.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ The following Keywords are **restricted** in JSQLParser-|JSQLPARSER_VERSION| and
7777
+----------------------+-------------+-----------+
7878
| QUALIFY | Yes | |
7979
+----------------------+-------------+-----------+
80-
| HAVING | Yes | Yes |
80+
| HAVING | Yes | Yes |
8181
+----------------------+-------------+-----------+
8282
| IF | Yes | Yes |
8383
+----------------------+-------------+-----------+
@@ -97,7 +97,7 @@ The following Keywords are **restricted** in JSQLParser-|JSQLPARSER_VERSION| and
9797
+----------------------+-------------+-----------+
9898
| INTERVAL | Yes | Yes |
9999
+----------------------+-------------+-----------+
100-
| INTO | Yes | Yes |
100+
| INTO | Yes | Yes |
101101
+----------------------+-------------+-----------+
102102
| IS | Yes | Yes |
103103
+----------------------+-------------+-----------+
@@ -109,7 +109,7 @@ The following Keywords are **restricted** in JSQLParser-|JSQLPARSER_VERSION| and
109109
+----------------------+-------------+-----------+
110110
| LIKE | Yes | Yes |
111111
+----------------------+-------------+-----------+
112-
| LIMIT | Yes | Yes |
112+
| LIMIT | Yes | Yes |
113113
+----------------------+-------------+-----------+
114114
| MINUS | Yes | Yes |
115115
+----------------------+-------------+-----------+
@@ -139,7 +139,9 @@ The following Keywords are **restricted** in JSQLParser-|JSQLPARSER_VERSION| and
139139
+----------------------+-------------+-----------+
140140
| OPTIMIZE | Yes | Yes |
141141
+----------------------+-------------+-----------+
142-
| PIVOT | Yes | Yes |
142+
| OVERWRITE | Yes | Yes |
143+
+----------------------+-------------+-----------+
144+
| PIVOT | Yes | Yes |
143145
+----------------------+-------------+-----------+
144146
| PREFERRING | Yes | Yes |
145147
+----------------------+-------------+-----------+
@@ -181,6 +183,8 @@ The following Keywords are **restricted** in JSQLParser-|JSQLPARSER_VERSION| and
181183
+----------------------+-------------+-----------+
182184
| UNIQUE | Yes | Yes |
183185
+----------------------+-------------+-----------+
186+
| UNKNOWN | Yes | Yes |
187+
+----------------------+-------------+-----------+
184188
| UNPIVOT | Yes | Yes |
185189
+----------------------+-------------+-----------+
186190
| USE | Yes | Yes |
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2023 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.expression.operators.relational;
11+
12+
import net.sf.jsqlparser.schema.Column;
13+
import net.sf.jsqlparser.test.TestUtils;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.params.ParameterizedTest;
16+
import org.junit.jupiter.params.provider.ValueSource;
17+
18+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
19+
20+
class IsUnknownExpressionTest {
21+
22+
@ParameterizedTest
23+
@ValueSource(strings = {
24+
"SELECT * FROM mytable WHERE 1 IS UNKNOWN",
25+
"SELECT * FROM mytable WHERE 1 IS NOT UNKNOWN",
26+
})
27+
public void testIsUnknownExpression(String sqlStr) {
28+
assertDoesNotThrow(() -> TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr));
29+
}
30+
31+
@Test
32+
void testStringConstructor() {
33+
Column column = new Column("x");
34+
35+
IsUnknownExpression defaultIsUnknownExpression =
36+
new IsUnknownExpression().withLeftExpression(column);
37+
TestUtils.assertExpressionCanBeDeparsedAs(defaultIsUnknownExpression, "x IS UNKNOWN");
38+
39+
IsUnknownExpression isUnknownExpression =
40+
new IsUnknownExpression().withLeftExpression(column).withNot(false);
41+
TestUtils.assertExpressionCanBeDeparsedAs(isUnknownExpression, "x IS UNKNOWN");
42+
43+
IsUnknownExpression isNotUnknownExpression =
44+
new IsUnknownExpression().withLeftExpression(column).withNot(true);
45+
TestUtils.assertExpressionCanBeDeparsedAs(isNotUnknownExpression, "x IS NOT UNKNOWN");
46+
}
47+
}

src/test/java/net/sf/jsqlparser/statement/builder/ReflectionModelTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public class ReflectionModelTest {
110110
new net.sf.jsqlparser.expression.operators.relational.InExpression(),
111111
new net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression(),
112112
new net.sf.jsqlparser.expression.operators.relational.IsNullExpression(),
113+
new net.sf.jsqlparser.expression.operators.relational.IsUnknownExpression(),
113114
new net.sf.jsqlparser.expression.operators.relational.JsonOperator("@>"),
114115
new net.sf.jsqlparser.expression.operators.relational.LikeExpression(),
115116
new net.sf.jsqlparser.expression.operators.relational.Matches(),

src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2271,6 +2271,18 @@ public void testIsNotFalse() throws JSQLParserException {
22712271
assertSqlCanBeParsedAndDeparsed(statement);
22722272
}
22732273

2274+
@Test
2275+
public void testIsUnknown() throws JSQLParserException {
2276+
String statement = "SELECT col FROM tbl WHERE col IS UNKNOWN";
2277+
assertSqlCanBeParsedAndDeparsed(statement);
2278+
}
2279+
2280+
@Test
2281+
public void testIsNotUnknown() throws JSQLParserException {
2282+
String statement = "SELECT col FROM tbl WHERE col IS NOT UNKNOWN";
2283+
assertSqlCanBeParsedAndDeparsed(statement);
2284+
}
2285+
22742286
@Test
22752287
public void testTSQLJoin() throws JSQLParserException {
22762288
String stmt = "SELECT * FROM tabelle1, tabelle2 WHERE tabelle1.a *= tabelle2.b";

src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ public void testIsNull() {
150150
validateNoErrors("SELECT * FROM tab t WHERE t.col IS NOT NULL", 1, EXPRESSIONS);
151151
}
152152

153+
@Test
154+
public void testIsUnknown() {
155+
validateNoErrors("SELECT * FROM tab t WHERE t.col IS UNKNOWN", 1, EXPRESSIONS);
156+
validateNoErrors("SELECT * FROM tab t WHERE t.col IS NOT UNKNOWN", 1, EXPRESSIONS);
157+
}
158+
153159
@Test
154160
public void testLike() {
155161
validateNoErrors("SELECT * FROM tab t WHERE t.col LIKE '%search for%'", 1, EXPRESSIONS);

0 commit comments

Comments
 (0)