Skip to content

Commit 7278d46

Browse files
Merge branch 'master' into fix/sqlserver-xml-subquery-methods
2 parents 14aedc0 + 1bc9424 commit 7278d46

14 files changed

Lines changed: 515 additions & 36 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ public enum Feature {
457457
*/
458458
oracleBlock,
459459

460-
execute, executeExec, executeCall, executeExecute,
460+
execute, executeExec, executeCall, executeExecute, doStatement,
461461

462462
/**
463463
* SQL "EXECUTE" statement is allowed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import java.util.function.Consumer;
13+
import net.sf.jsqlparser.expression.StringValue;
14+
15+
/** A PostgreSQL anonymous routine. The language-specific body remains a string literal. */
16+
public class DoStatement implements Statement {
17+
private StringValue code;
18+
private String language;
19+
private boolean languageBeforeCode;
20+
21+
public StringValue getCode() {
22+
return code;
23+
}
24+
25+
public void setCode(StringValue code) {
26+
this.code = code;
27+
}
28+
29+
public DoStatement withCode(StringValue code) {
30+
setCode(code);
31+
return this;
32+
}
33+
34+
/** Returns the explicit language, or null when omitted. */
35+
public String getLanguage() {
36+
return language;
37+
}
38+
39+
public void setLanguage(String language) {
40+
this.language = language;
41+
}
42+
43+
public DoStatement withLanguage(String language) {
44+
setLanguage(language);
45+
return this;
46+
}
47+
48+
public boolean isLanguageBeforeCode() {
49+
return languageBeforeCode;
50+
}
51+
52+
public void setLanguageBeforeCode(boolean languageBeforeCode) {
53+
this.languageBeforeCode = languageBeforeCode;
54+
}
55+
56+
public StringBuilder appendTo(StringBuilder builder, Consumer<StringValue> codePrinter) {
57+
builder.append("DO ");
58+
if (languageBeforeCode && language != null) {
59+
builder.append("LANGUAGE ").append(language).append(' ');
60+
}
61+
codePrinter.accept(code);
62+
if (!languageBeforeCode && language != null) {
63+
builder.append(" LANGUAGE ").append(language);
64+
}
65+
return builder;
66+
}
67+
68+
@Override
69+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
70+
return visitor.visit(this, context);
71+
}
72+
73+
@Override
74+
public String toString() {
75+
StringBuilder builder = new StringBuilder();
76+
return appendTo(builder, value -> builder.append(value)).toString();
77+
}
78+
}

‎src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,13 @@ public <S> Void visit(Execute execute, S context) {
598598
return null;
599599
}
600600

601+
@Override
602+
public <S> Void visit(DoStatement statement, S context) {
603+
analysis.claimTopLevel();
604+
analysis.opaque("DO");
605+
return null;
606+
}
607+
601608
@Override
602609
public <S> Void visit(CreateFunctionalStatement createFunctionalStatement, S context) {
603610
analysis.claimTopLevel();

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ default void visit(Statements statements) {
210210

211211
<S> T visit(Execute execute, S context);
212212

213+
default <S> T visit(DoStatement statement, S context) {
214+
return null;
215+
}
216+
217+
default void visit(DoStatement statement) {
218+
this.visit(statement, null);
219+
}
220+
213221
default void visit(Execute execute) {
214222
this.visit(execute, null);
215223
}

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,12 @@ public <S> T visit(Execute execute, S context) {
423423
return null;
424424
}
425425

426+
@Override
427+
public <S> T visit(DoStatement statement, S context) {
428+
expressionVisitor.visitExpression(statement.getCode(), context);
429+
return null;
430+
}
431+
426432
@Override
427433
public <S> T visit(LockStatement lock, S context) {
428434

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import net.sf.jsqlparser.statement.CreateFunctionalStatement;
9292
import net.sf.jsqlparser.statement.DeclareStatement;
9393
import net.sf.jsqlparser.statement.DescribeStatement;
94+
import net.sf.jsqlparser.statement.DoStatement;
9495
import net.sf.jsqlparser.statement.ExplainStatement;
9596
import net.sf.jsqlparser.statement.IfElseStatement;
9697
import net.sf.jsqlparser.statement.OutputClause;
@@ -1564,6 +1565,12 @@ public <S> Void visit(CreateIndex createIndex, S context) {
15641565
return null;
15651566
}
15661567

1568+
@Override
1569+
public <S> Void visit(DoStatement statement, S context) {
1570+
throwUnsupported(statement);
1571+
return null;
1572+
}
1573+
15671574
@Override
15681575
public void visit(CreateIndex createIndex) {
15691576
StatementVisitor.super.visit(createIndex);

‎src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import net.sf.jsqlparser.statement.CreateFunctionalStatement;
3939
import net.sf.jsqlparser.statement.DeclareStatement;
4040
import net.sf.jsqlparser.statement.DescribeStatement;
41+
import net.sf.jsqlparser.statement.DoStatement;
4142
import net.sf.jsqlparser.statement.ExplainStatement;
4243
import net.sf.jsqlparser.statement.IfElseStatement;
4344
import net.sf.jsqlparser.statement.PurgeStatement;
@@ -151,6 +152,11 @@ public <S> StringBuilder visit(CreateIndex createIndex, S context) {
151152
return builder;
152153
}
153154

155+
@Override
156+
public <S> StringBuilder visit(DoStatement statement, S context) {
157+
return statement.appendTo(builder, code -> code.accept(expressionDeParser, context));
158+
}
159+
154160
@Override
155161
public <S> StringBuilder visit(CreateTable createTable, S context) {
156162
CreateTableDeParser createTableDeParser = new CreateTableDeParser(this, builder);

‎src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public enum PostgresqlVersion implements Version {
2727
EnumSet.of(// supported if used with jdbc
2828
Feature.jdbcParameter,
2929
Feature.jdbcNamedParameter, // expressions
30+
Feature.doStatement,
3031
Feature.exprLike,
3132
Feature.exprSimilarTo, // https://www.postgresql.org/docs/current/sql-select.html
3233
Feature.select,

‎src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import net.sf.jsqlparser.statement.Block;
3636
import net.sf.jsqlparser.statement.Commit;
3737
import net.sf.jsqlparser.statement.CreateFunctionalStatement;
38+
import net.sf.jsqlparser.statement.DoStatement;
3839
import net.sf.jsqlparser.statement.DeclareStatement;
3940
import net.sf.jsqlparser.statement.DescribeStatement;
4041
import net.sf.jsqlparser.statement.ExplainStatement;
@@ -232,6 +233,12 @@ public <S> Void visit(Execute execute, S context) {
232233
return null;
233234
}
234235

236+
@Override
237+
public <S> Void visit(DoStatement statement, S context) {
238+
validateFeature(Feature.doStatement);
239+
return null;
240+
}
241+
235242
@Override
236243
public <S> Void visit(SetStatement set, S context) {
237244
getValidator(SetStatementValidator.class).validate(set);

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

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,6 +1769,14 @@ TOKEN_MGR_DECLS : {
17691769
token.endColumn = input_stream.getEndColumn();
17701770
}
17711771

1772+
/** Splits a longest-match identifier without losing token positions. */
1773+
private void truncateIdentifierToken(Token token, int length) {
1774+
input_stream.backup(token.image.length() - length);
1775+
token.image = token.image.substring(0, length);
1776+
token.endLine = input_stream.getEndLine();
1777+
token.endColumn = input_stream.getEndColumn();
1778+
}
1779+
17721780
/**
17731781
* Consumes the body of a block comment after the opening delimiter has been matched,
17741782
* honouring nesting, up to and including the outermost closing delimiter. Then backs
@@ -2510,8 +2518,19 @@ TOKEN:
25102518
&& Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments))) {
25112519
int hashIndex = matchedToken.image.indexOf('#');
25122520
if (hashIndex > 0) {
2513-
input_stream.backup(matchedToken.image.length() - hashIndex);
2514-
matchedToken.image = matchedToken.image.substring(0, hashIndex);
2521+
truncateIdentifierToken(matchedToken, hashIndex);
2522+
}
2523+
}
2524+
// PostgreSQL does not allow # in unquoted identifiers. Re-lex it as an
2525+
// operator, including #> and #>>, even when it touches the left operand.
2526+
if (matchedToken.kind == S_IDENTIFIER
2527+
&& AbstractJSqlParser.Dialect.POSTGRESQL.name().equals(configuration.getValue(Feature.dialect))) {
2528+
int hashIndex = matchedToken.image.indexOf('#');
2529+
if (hashIndex > 0) {
2530+
truncateIdentifierToken(matchedToken, hashIndex);
2531+
} else if (hashIndex == 0) {
2532+
truncateIdentifierToken(matchedToken, 1);
2533+
matchedToken.kind = S_HASH_OPERATOR;
25152534
}
25162535
}
25172536
}
@@ -2532,8 +2551,7 @@ TOKEN:
25322551
&& Boolean.TRUE.equals(configuration.getValue(Feature.allowHashLineComments))) {
25332552
int hashIndex = matchedToken.image.indexOf('#');
25342553
if (hashIndex > 0) {
2535-
input_stream.backup(matchedToken.image.length() - hashIndex);
2536-
matchedToken.image = matchedToken.image.substring(0, hashIndex);
2554+
truncateIdentifierToken(matchedToken, hashIndex);
25372555
}
25382556
}
25392557
}
@@ -2764,6 +2782,9 @@ Statement SingleStatement() :
27642782
|
27652783
stm = Execute()
27662784
|
2785+
LOOKAHEAD({ getToken(1).kind == K_DO && Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) })
2786+
stm = DoStatement()
2787+
|
27672788
stm = Set()
27682789
|
27692790
stm = Reset()
@@ -2883,6 +2904,26 @@ OracleBlock.ExceptionHandler OracleExceptionHandler():
28832904
<K_THEN> body=OracleBlockStatements() { handler.setStatements(body); return handler; }
28842905
}
28852906

2907+
DoStatement DoStatement():
2908+
{
2909+
DoStatement statement = new DoStatement();
2910+
String language;
2911+
Token code;
2912+
}
2913+
{
2914+
<K_DO>
2915+
(
2916+
LOOKAHEAD({ isKeywordAhead("LANGUAGE") })
2917+
AccessKeyword("LANGUAGE") language=RelObjectName() code=<S_CHAR_LITERAL>
2918+
{ statement.setLanguage(language); statement.setLanguageBeforeCode(true); }
2919+
|
2920+
code=<S_CHAR_LITERAL>
2921+
[ LOOKAHEAD({ isKeywordAhead("LANGUAGE") }) AccessKeyword("LANGUAGE")
2922+
language=RelObjectName() { statement.setLanguage(language); } ]
2923+
)
2924+
{ statement.setCode(new StringValue(code.image)); return statement; }
2925+
}
2926+
28862927
Block Block() #Block : {
28872928
Statements stmts = new Statements();
28882929
List<Statement> list = new ArrayList<Statement>();
@@ -13050,15 +13091,12 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1305013091
{
1305113092
CreateTable createTable = new CreateTable();
1305213093
Table table = null;
13053-
List<ColumnDefinition> columnDefinitions = new ArrayList<ColumnDefinition>();
1305413094
List<TableElement> tableElements = new ArrayList<TableElement>();
13095+
TableElement element;
1305513096
List<String> tableOptions = new ArrayList<String>();
1305613097
List<TableOption> typedTableOptions = new ArrayList<TableOption>();
1305713098
List<String> createOptions = new ArrayList<String>();
1305813099
Token tk = null;
13059-
ColumnDefinition coldef = null;
13060-
List<Index> indexes = new ArrayList<Index>();
13061-
Index index = null;
1306213100
List<String> parameter = new ArrayList<String>();
1306313101
TableOption tableOption = null;
1306413102
SpannerInterleaveIn interleaveIn = null;
@@ -13071,7 +13109,6 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1307113109
Table partitionOfTable = null;
1307213110
PartitionBound partitionBound = null;
1307313111
ColDataType ofType = null;
13074-
LikeClause likeClause = null;
1307513112
}
1307613113
{
1307713114
{ createTable.setOrReplace(isUsingOrReplace);}
@@ -13094,33 +13131,12 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1309413131
)
1309513132
|
1309613133
(
13097-
"("
13098-
(
13099-
LOOKAHEAD(<K_LIKE>) likeClause=LikeClause() { tableElements.add(likeClause); }
13100-
|
13101-
LOOKAHEAD(3) index = CreateTableConstraint()
13102-
{ indexes.add(index); tableElements.add(index); }
13103-
|
13104-
coldef = CreateTableColumnDefinition(ofType != null)
13105-
{ columnDefinitions.add(coldef); tableElements.add(coldef); }
13106-
)
13107-
13108-
(
13109-
","
13110-
(
13111-
LOOKAHEAD(<K_LIKE>) likeClause=LikeClause() { tableElements.add(likeClause); }
13112-
|
13113-
LOOKAHEAD(3) (
13114-
index = CreateTableConstraint()
13115-
{ indexes.add(index); tableElements.add(index); }
13116-
)
13117-
|
13118-
(
13119-
coldef = CreateTableColumnDefinition(ofType != null)
13120-
{ columnDefinitions.add(coldef); tableElements.add(coldef); }
13121-
)
13122-
)
13134+
"(" element=CreateTableElement(ofType != null) { tableElements.add(element); }
13135+
( LOOKAHEAD(2) "," element=CreateTableElement(ofType != null)
13136+
{ tableElements.add(element); }
1312313137
)*
13138+
[ LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect))
13139+
&& getToken(1).kind == K_COMMA && getToken(2).kind == CLOSING_BRACKET }) "," ]
1312413140

1312513141
")"
1312613142
)
@@ -13169,6 +13185,17 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1316913185
}
1317013186
}
1317113187

13188+
TableElement CreateTableElement(boolean typed):
13189+
{ TableElement element; }
13190+
{
13191+
(
13192+
LOOKAHEAD(<K_LIKE>) element=LikeClause()
13193+
| LOOKAHEAD(3) element=CreateTableConstraint()
13194+
| element=CreateTableColumnDefinition(typed)
13195+
)
13196+
{ return element; }
13197+
}
13198+
1317213199
ColumnDefinition CreateTableColumnDefinition(boolean typed):
1317313200
{
1317413201
ColumnDefinition column = null;

0 commit comments

Comments
 (0)