Skip to content

Commit 8ee6236

Browse files
committed
Merge master and preserve CYCLE and DML CTE validation
2 parents dd2d9d4 + 329ee6b commit 8ee6236

83 files changed

Lines changed: 17381 additions & 5367 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,6 @@
22

33
Changelog of JSqlParser.
44

5-
## jsqlparser-5.4 (2025-05-25)
6-
7-
### Features
8-
9-
- Session Statement ([7d2e6](https://github.com/JSQLParser/JSqlParser/commit/7d2e6b65324ce57) manticore-projects)
10-
- sync with Master ([e14d7](https://github.com/JSQLParser/JSqlParser/commit/e14d7eb1c4e9963) manticore-projects)
11-
- JavaCC 8 keyword utils ([cfe2d](https://github.com/JSQLParser/JSqlParser/commit/cfe2d8ccaf7c76d) manticore-projects)
12-
- Complete on JavaCC-8 ([1b7ed](https://github.com/JSQLParser/JSqlParser/commit/1b7ed2d7be000ce) manticore-projects)
13-
- Optimise performance ([e91c4](https://github.com/JSQLParser/JSqlParser/commit/e91c480b0bbe0a9) manticore-projects)
14-
- avoid looping through the tokens every single time ([b18fb](https://github.com/JSQLParser/JSqlParser/commit/b18fbca1f48e63e) manticore-projects)
15-
- avoid looping through the tokens every single time ([7ac6c](https://github.com/JSQLParser/JSqlParser/commit/7ac6cd0fa08d713) manticore-projects)
16-
- add proper JMH benchmarks ([21c98](https://github.com/JSQLParser/JSqlParser/commit/21c983fc1f4f3f2) manticore-projects)
17-
- JavaCC-8 ([9d144](https://github.com/JSQLParser/JSqlParser/commit/9d1442e9a4800e2) manticore-projects)
18-
- remove all semantic lookaheads ([5abca](https://github.com/JSQLParser/JSqlParser/commit/5abcaeaede27cfe) manticore-projects)
19-
20-
### Bug Fixes
21-
22-
- bring back `SYNTACTIC LOOKAHEAD` where it makes sense ([b3c5b](https://github.com/JSQLParser/JSqlParser/commit/b3c5b63344de193) manticore-projects)
23-
- the Quotes Token manipulation ([bad81](https://github.com/JSQLParser/JSqlParser/commit/bad818e0b872c6a) manticore-projects)
24-
25-
### Other changes
26-
27-
285
## jsqlparser-5.3 (2025-05-17)
296

307
### Features

‎README.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,23 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
138138
| | Statements |
139139
|---|---|
140140
| **Queries** | `SELECT` · `WITH …` · Piped SQL |
141+
| **ksqlDB windows** | JOIN `WITHIN`, window `GRACE PERIOD`, and `EMIT CHANGES`/`FINAL` |
141142
| **DML** | `INSERT` · `UPDATE` · `UPSERT` · `MERGE` · `DELETE` · `TRUNCATE TABLE` |
142143
| **DDL** | `CREATE …` · `ALTER …` · `DROP …` |
143144
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
145+
| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` |
144146
| **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` |
145147

146148
Beyond statement shapes, the grammar handles nested sub-selects, bind parameters (`?`,
147149
`:name`), window and analytic functions, Oracle hints, and the T-SQL square-bracket versus
148150
array-literal ambiguity. The complete reference is on the
149151
[syntax page](https://jsqlparser.github.io/JSqlParser/syntax.html).
150152

153+
PostgreSQL dollar-quoted strings, including `$tag$…$tag$`, retain their delimiter and
154+
literal body in `StringValue`. Tagged quotes are disabled by default to preserve
155+
identifier parsing. Enable them with `parser.withDialect(Dialect.POSTGRESQL)` or
156+
`parser.withDollarQuotedStringTags(true)`. Untagged `$$…$$` literals remain enabled.
157+
151158
## Statement classification
152159

153160
Any parsed statement can say what it actually does — no second parse, no visitor to write:

‎src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class AnalyticExpression extends ASTNodeAccessImpl implements Expression
5858
public AnalyticExpression() {}
5959

6060
public AnalyticExpression(Function function) {
61-
this.name = String.join(" ", function.getMultipartName());
61+
this.name = function.getName();
6262
this.allColumns = function.isAllColumns();
6363
this.distinct = function.isDistinct();
6464
this.unique = function.isUnique();

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package net.sf.jsqlparser.expression;
1111

1212
import java.util.List;
13+
import net.sf.jsqlparser.statement.execute.ExecuteArgument;
1314
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
1415
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
1516
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift;
@@ -72,6 +73,10 @@
7273

7374
public interface ExpressionVisitor<T> {
7475

76+
default <S> T visit(ExecuteArgument argument, S context) {
77+
return argument.getExpression().accept(this, context);
78+
}
79+
7580
default <S> T visitExpressions(ExpressionList<? extends Expression> expressions, S context) {
7681
if (expressions != null) {
7782
expressions.forEach(expression -> expression.accept(this, context));

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

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
import java.util.ArrayList;
1313
import java.util.Arrays;
1414
import java.util.Collection;
15+
import java.util.List;
1516
import java.util.Map;
16-
import java.util.Optional;
1717
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
1818
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
1919
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift;
@@ -119,11 +119,9 @@ public <S> T visit(Function function, S context) {
119119
if (function.getKeep() != null) {
120120
subExpressions.add(function.getKeep());
121121
}
122-
if (function.getOrderByElements() != null) {
123-
for (OrderByElement orderByElement : function.getOrderByElements()) {
124-
subExpressions.add(orderByElement.getExpression());
125-
}
126-
}
122+
addOrderByExpressions(subExpressions, function.getOrderByElements());
123+
addFunctionModifiers(subExpressions, function.getHavingClause(),
124+
function.getKeywordArguments(), function.getLimit());
127125
return visitExpressions(function, context, subExpressions);
128126
}
129127

@@ -419,29 +417,41 @@ public <S> T visit(AnalyticExpression analyticExpression, S context) {
419417
if (analyticExpression.getKeep() != null) {
420418
subExpressions.add(analyticExpression.getKeep());
421419
}
422-
if (analyticExpression.getFuncOrderBy() != null) {
423-
for (OrderByElement element : analyticExpression.getOrderByElements()) {
424-
subExpressions.add(element.getExpression());
420+
subExpressions.add(analyticExpression.getFilterExpression());
421+
addOrderByExpressions(subExpressions, analyticExpression.getFuncOrderBy());
422+
addFunctionModifiers(subExpressions, analyticExpression.getHavingClause(),
423+
analyticExpression.getKeywordArguments(), analyticExpression.getLimit());
424+
if (analyticExpression.getWindowDefinition() != null) {
425+
subExpressions.addAll(analyticExpression.getWindowDefinition().getAllExpressions());
426+
}
427+
return visitExpressions(analyticExpression, context, subExpressions);
428+
}
429+
430+
private static void addOrderByExpressions(List<Expression> expressions,
431+
List<OrderByElement> orderBy) {
432+
if (orderBy != null) {
433+
for (OrderByElement element : orderBy) {
434+
expressions.add(element.getExpression());
425435
}
426436
}
427-
if (analyticExpression.getWindowElement() != null) {
428-
/*
429-
* Visit expressions from the range and offset of the window element. Do this using
430-
* optional chains, because several things down the tree can be null e.g. the
431-
* expression. So, null-safe versions of e.g.:
432-
* analyticExpression.getWindowElement().getOffset().getExpression().accept(this,
433-
* parameters);
434-
*/
435-
Optional.ofNullable(analyticExpression.getWindowElement().getRange())
436-
.map(WindowRange::getStart)
437-
.map(WindowOffset::getExpression).ifPresent(subExpressions::add);
438-
Optional.ofNullable(analyticExpression.getWindowElement().getRange())
439-
.map(WindowRange::getEnd)
440-
.map(WindowOffset::getExpression).ifPresent(subExpressions::add);
441-
Optional.ofNullable(analyticExpression.getWindowElement().getOffset())
442-
.map(WindowOffset::getExpression).ifPresent(subExpressions::add);
437+
}
438+
439+
private static void addFunctionModifiers(List<Expression> expressions,
440+
Function.HavingClause having, List<Function.KeywordArgument> arguments,
441+
net.sf.jsqlparser.statement.select.Limit limit) {
442+
expressions.add(having);
443+
if (arguments != null) {
444+
for (Function.KeywordArgument argument : arguments) {
445+
expressions.add(argument.getExpression());
446+
}
447+
}
448+
if (limit != null) {
449+
expressions.add(limit.getOffset());
450+
expressions.add(limit.getRowCount());
451+
if (limit.getByExpressions() != null) {
452+
expressions.addAll(limit.getByExpressions());
453+
}
443454
}
444-
return visitExpressions(analyticExpression, context, subExpressions);
445455
}
446456

447457
@Override

‎src/main/java/net/sf/jsqlparser/expression/StringValue.java‎

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ public StringValue(String escapedValue) {
4242
value = escapedValue.substring(1, escapedValue.length() - 1);
4343
quoteStr = "\"";
4444
return;
45-
} else if (escapedValue.length() >= 4 && escapedValue.startsWith("$$")
46-
&& escapedValue.endsWith("$$")) {
47-
value = escapedValue.substring(2, escapedValue.length() - 2);
48-
quoteStr = "$$";
45+
}
46+
47+
String delimiter = getDollarQuoteDelimiter(escapedValue);
48+
if (delimiter != null && escapedValue.length() >= 2 * delimiter.length()
49+
&& escapedValue.endsWith(delimiter)) {
50+
quoteStr = delimiter;
51+
value = escapedValue.substring(delimiter.length(),
52+
escapedValue.length() - delimiter.length());
4953
return;
5054
}
5155

@@ -64,6 +68,32 @@ public StringValue(String escapedValue) {
6468
value = escapedValue;
6569
}
6670

71+
/**
72+
* Returns the opening PostgreSQL dollar-quote delimiter, or null if there is none. A tag
73+
* follows unquoted identifier rules, excluding dollar signs. This method does not require the
74+
* closing delimiter or inspect the body.
75+
*/
76+
public static String getDollarQuoteDelimiter(String text) {
77+
if (text == null || text.length() < 2 || text.charAt(0) != '$') {
78+
return null;
79+
}
80+
int end = text.indexOf('$', 1);
81+
if (end < 0) {
82+
return null;
83+
}
84+
for (int i = 1; i < end;) {
85+
int character = text.codePointAt(i);
86+
boolean valid =
87+
i == 1 ? Character.isUnicodeIdentifierStart(character) || character == '_'
88+
: Character.isUnicodeIdentifierPart(character);
89+
if (!valid) {
90+
return null;
91+
}
92+
i += Character.charCount(character);
93+
}
94+
return text.substring(0, end + 1);
95+
}
96+
6797
public String getValue() {
6898
return value;
6999
}
@@ -90,6 +120,9 @@ public StringValue setQuoteStr(String quoteStr) {
90120
}
91121

92122
public String getNotExcapedValue() {
123+
if (quoteStr != null && quoteStr.startsWith("$")) {
124+
return value;
125+
}
93126
StringBuilder buffer = new StringBuilder(value);
94127
int index = 0;
95128
int deletesNum = 0;

‎src/main/java/net/sf/jsqlparser/expression/WindowDefinition.java‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package net.sf.jsqlparser.expression;
1111

1212
import java.io.Serializable;
13+
import java.util.ArrayList;
1314
import java.util.List;
1415

1516
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
@@ -73,6 +74,30 @@ public WindowDefinition withWindowName(String windowName) {
7374
return this;
7475
}
7576

77+
/** Returns the partition, order and frame expressions for both inline and named windows. */
78+
public List<Expression> getAllExpressions() {
79+
List<Expression> expressions = new ArrayList<>(partitionBy);
80+
if (getOrderByElements() != null) {
81+
for (OrderByElement element : getOrderByElements()) {
82+
expressions.add(element.getExpression());
83+
}
84+
}
85+
if (windowElement != null) {
86+
if (windowElement.getRange() != null) {
87+
addOffsetExpression(expressions, windowElement.getRange().getStart());
88+
addOffsetExpression(expressions, windowElement.getRange().getEnd());
89+
}
90+
addOffsetExpression(expressions, windowElement.getOffset());
91+
}
92+
return expressions;
93+
}
94+
95+
private static void addOffsetExpression(List<Expression> expressions, WindowOffset offset) {
96+
if (offset != null && offset.getExpression() != null) {
97+
expressions.add(offset.getExpression());
98+
}
99+
}
100+
76101
@Override
77102
public String toString() {
78103
StringBuilder b = new StringBuilder();

‎src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,16 @@ public enum Dialect {
3737
Feature.allowHashLineComments,
3838
Feature.allowDoubleQuotedStrings), SQLSERVER(AdjacentStringLiterals.OFF,
3939
Feature.allowSquareBracketQuotation), POSTGRESQL(
40-
AdjacentStringLiterals.NEWLINE), H2, EXASOL, BIGQUERY(
40+
AdjacentStringLiterals.NEWLINE,
41+
Feature.allowDollarQuotedStringTags), H2, EXASOL, BIGQUERY(
4142
AdjacentStringLiterals.WHITESPACE,
4243
Feature.allowDoubleQuotedStrings,
4344
Feature.allowHashLineComments,
4445
Feature.allowBackslashEscapeCharacter), DATABRICKS(
4546
AdjacentStringLiterals.WHITESPACE,
4647
Feature.allowDoubleQuotedStrings,
4748
Feature.allowBackslashEscapeCharacter), SNOWFLAKE(
48-
Feature.allowBackslashEscapeCharacter);
49+
Feature.allowBackslashEscapeCharacter), INFORMIX;
4950

5051
private final Set<Feature> lexerFeatures;
5152
private final AdjacentStringLiterals adjacentStringLiterals;
@@ -143,6 +144,14 @@ public P withBackslashEscapeCharacter(boolean allowBackslashEscapeCharacter) {
143144
return withFeature(Feature.allowBackslashEscapeCharacter, allowBackslashEscapeCharacter);
144145
}
145146

147+
/**
148+
* Controls tagged dollar quotes; disabled by default, enabled by the PostgreSQL dialect preset.
149+
* False preserves dollar-containing identifier spellings.
150+
*/
151+
public P withDollarQuotedStringTags(boolean allowDollarQuotedStringTags) {
152+
return withFeature(Feature.allowDollarQuotedStringTags, allowDollarQuotedStringTags);
153+
}
154+
146155
public P withDoubleQuotedStrings() {
147156
return withFeature(Feature.allowDoubleQuotedStrings, true);
148157
}

‎src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,11 @@ public static Statements parseStatements(String sqls, Consumer<CCJSqlParser> con
429429
}
430430

431431
ExecutorService executorService = Executors.newSingleThreadExecutor();
432-
final Statements statements = parseStatements(sqls, executorService, consumer);
433-
executorService.shutdown();
434-
435-
return statements;
432+
try {
433+
return parseStatements(sqls, executorService, consumer);
434+
} finally {
435+
executorService.shutdown();
436+
}
436437
}
437438

438439
/**
@@ -447,7 +448,6 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe
447448
return null;
448449
}
449450

450-
Statements statements = null;
451451
CCJSqlParser parser = newParser(sqls);
452452
if (consumer != null) {
453453
consumer.accept(parser);
@@ -457,7 +457,7 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe
457457

458458
// first, try to parse fast and simple
459459
try {
460-
statements = parseStatements(parser.withAllowComplexParsing(false), executorService);
460+
return parseStatements(parser.withAllowComplexParsing(false), executorService);
461461
} catch (JSQLParserException ex) {
462462
// when fast simple parsing fails, try complex parsing but only if it has a chance to
463463
// succeed
@@ -468,10 +468,10 @@ public static Statements parseStatements(String sqls, ExecutorService executorSe
468468
if (consumer != null) {
469469
consumer.accept(parser);
470470
}
471-
statements = parseStatements(parser.withAllowComplexParsing(true), executorService);
471+
return parseStatements(parser.withAllowComplexParsing(true), executorService);
472472
}
473+
throw ex;
473474
}
474-
return statements;
475475
}
476476

477477
/**

‎src/main/java/net/sf/jsqlparser/parser/feature/Feature.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ public enum Feature {
643643
/**
644644
* @see Grant
645645
*/
646-
grant,
646+
grant, revoke, createRole, alterRole, alterDefaultPrivileges,
647647
/**
648648
* @see Function
649649
*/
@@ -670,6 +670,9 @@ public enum Feature {
670670
* @see DeclareStatement
671671
*/
672672
declare,
673+
674+
/** SQL Server local table variables in queries and DML targets. */
675+
tableVariable,
673676
/**
674677
* @see SetStatement
675678
*/
@@ -808,6 +811,12 @@ public enum Feature {
808811
*/
809812
allowDoubleQuotedStrings(false),
810813

814+
/**
815+
* Recognizes PostgreSQL $tag$...$tag$ literals; disabled by default to preserve unquoted
816+
* identifiers, enabled by the PostgreSQL dialect preset. Untagged $$ literals are unaffected.
817+
*/
818+
allowDollarQuotedStringTags(false),
819+
811820
/**
812821
* concatenates adjacent String Literals: NEWLINE when separated by whitespace with at least one
813822
* newline (the SQL standard and PostgreSQL), WHITESPACE across any whitespace (GoogleSQL,

0 commit comments

Comments
 (0)