Skip to content

Commit 02b9d94

Browse files
authored
Clarify PostgreSQL DO support and verify the reported block (#2633)
* Clarify PostgreSQL DO support and verify the reported block * Keep DO round-trip assertions independent of fixture license comments
1 parent 4b88a44 commit 02b9d94

4 files changed

Lines changed: 98 additions & 14 deletions

File tree

src/site/sphinx/unsupported.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ Missing syntax is added on demand — please `open an issue <https://github.com/
1111
Procedural SQL
1212
=======================================
1313

14-
This is the one substantial gap. JSQLParser parses **statements**, not stored programs. Anonymous blocks with declarations, cursors, exception handlers and loops are outside its scope:
14+
Procedural-language support is partial. Accepting a block or routine definition does not necessarily mean that its body is parsed into a statement tree. Select the relevant dialect as described in :ref:`Choose a Dialect`.
1515

1616
.. code-block:: sql
17-
:caption: Oracle PL/SQL — not supported
17+
:caption: Oracle anonymous block — supported with Dialect.ORACLE
1818
1919
DECLARE
2020
num NUMBER;
@@ -23,21 +23,25 @@ This is the one substantial gap. JSQLParser parses **statements**, not stored pr
2323
dbms_output.put_line('The number is ' || num);
2424
END;
2525
26-
.. code-block:: sql
27-
:caption: PostgreSQL anonymous block — not supported
26+
.. code-block:: postgresql
27+
:caption: PostgreSQL DO — supported with Dialect.POSTGRESQL; body remains opaque
2828
2929
DO $$
3030
BEGIN
3131
RAISE NOTICE 'hello';
3232
END
3333
$$;
3434
35-
Specifically not parsed: typed local variable declarations, ``CURSOR`` declarations and ``OPEN`` / ``FETCH`` / ``CLOSE``, ``EXCEPTION`` handlers, ``WHILE`` and ``FOR`` loops, ``ELSIF``, and assignment (``:=``).
35+
The PostgreSQL example produces a ``DoStatement`` whose ``getCode()`` is a ``StringValue``. The body, quotes and dollar tag are preserved, and statements following the block are parsed separately. PL/pgSQL declarations, conditions and statements inside that literal are not exposed as child AST nodes. Table discovery therefore rejects the opaque body rather than reporting an incomplete table list.
36+
37+
Full procedural-language coverage, including cursors, loops and ``ELSIF``, remains outside the supported subset.
3638

3739
What *is* supported:
3840

3941
- ``BEGIN .. END`` blocks and ``IF .. ELSE`` around ordinary statements (``Block``, ``IfElseStatement``)
4042
- ``DECLARE @variable`` in the T-SQL sense (``DeclareStatement``)
43+
- PostgreSQL ``DO`` wrappers with the body preserved as a string literal (``DoStatement``)
44+
- Selected Oracle anonymous blocks, including variable declarations, assignments and exception handlers (``OracleBlock``); see :ref:`Oracle anonymous blocks`
4145

4246
Routine and trigger definitions
4347
---------------------------------------
@@ -81,4 +85,4 @@ If you hit one, you do not have to abandon the parse:
8185
8286
The offending statement comes back as an ``UnsupportedStatement`` holding its original text, and the rest of the script parses normally. See :ref:`Handle Parse Errors`.
8387

84-
Before concluding something is unsupported, check the parser features: square brackets, backslash escapes, double-quoted strings and hash comments are all **off by default** and are the most common cause of a "not supported" report that is really a dialect setting. See :ref:`Choose a Dialect`.
88+
Before concluding something is unsupported, check the parser features: square brackets, backslash escapes, double-quoted strings and hash comments are all **off by default** and are the most common cause of a "not supported" report that is really a dialect setting. See :ref:`Choose a Dialect`.

src/site/sphinx/usage.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,22 @@ Expression visitors can inspect or replace the body literal. Feature analysis
783783
reports ``OPAQUE``; table discovery rejects this statement because the body's
784784
table accesses are unknown. Validation checks the ``doStatement`` capability,
785785
without validating the procedural language inside the literal.
786+
787+
Enable the PostgreSQL dialect when parsing a script containing a ``DO`` block:
788+
789+
.. code-block:: java
790+
791+
Statements statements = CCJSqlParserUtil.parseStatements(
792+
"DO $$BEGIN RAISE NOTICE 'hello'; END$$; SELECT 1;",
793+
parser -> parser.withDialect(Dialect.POSTGRESQL));
794+
DoStatement block = (DoStatement) statements.get(0);
795+
String body = block.getCode().getValue();
796+
// body: BEGIN RAISE NOTICE 'hello'; END
797+
// statements.get(1) is the following SELECT.
798+
799+
Semicolons and SQL statements inside the body remain part of its string literal;
800+
they do not split the surrounding script into additional statements.
801+
786802
With ``Dialect.POSTGRESQL``, ``#`` terminates an unquoted identifier, so JSON
787803
operators such as ``js#>>'{a}'`` and ``js#>'{a}'`` work without surrounding
788804
spaces. Quote identifiers containing ``#``, for example ``"js#"``. Other

src/test/java/net/sf/jsqlparser/statement/DoStatementTest.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
1313
import static org.junit.jupiter.api.Assertions.*;
1414

15+
import java.io.InputStream;
16+
import java.nio.charset.StandardCharsets;
1517
import java.util.ArrayList;
1618
import java.util.List;
1719
import net.sf.jsqlparser.JSQLParserException;
@@ -56,14 +58,35 @@ void roundTripsBodyAndLanguagePosition(String sql) throws Exception {
5658

5759
@Test
5860
void preservesProceduralBodyAndFollowingStatementsIssue1946() throws Exception {
59-
String body = "$$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM comm.permission_operation) THEN\n"
60-
+ " INSERT INTO comm.permission_operation (permission_operation_id) VALUES (1) "
61-
+ "ON CONFLICT (permission_operation_id) DO NOTHING;\n END IF;\nEND $$";
62-
Statements statements = CCJSqlParserUtil.parseStatements("DO " + body + "; SELECT 1;",
63-
p -> p.withDialect(Dialect.POSTGRESQL));
64-
assertEquals(2, statements.size());
65-
assertEquals(body, ((DoStatement) statements.get(0)).getCode().toString());
66-
assertEquals("SELECT 1", statements.get(1).toString());
61+
String sql;
62+
try (InputStream input = getClass().getResourceAsStream("/postgresql/do-issue1946.sql")) {
63+
assertNotNull(input);
64+
sql = new String(input.readAllBytes(), StandardCharsets.UTF_8).strip();
65+
}
66+
String body = sql.substring(sql.indexOf("$$"), sql.lastIndexOf("$$") + 2);
67+
Statements statements =
68+
CCJSqlParserUtil.parseStatements("SELECT 0;\n" + sql + "\nSELECT 1;",
69+
p -> p.withDialect(Dialect.POSTGRESQL).withUnsupportedStatements(false));
70+
assertEquals(3, statements.size());
71+
assertEquals("SELECT 0", statements.get(0).toString());
72+
DoStatement block = assertInstanceOf(DoStatement.class, statements.get(1));
73+
assertEquals(body, block.getCode().toString());
74+
assertEquals(body.substring(2, body.length() - 2), block.getCode().getValue());
75+
assertEquals("SELECT 1", statements.get(2).toString());
76+
77+
StringBuilder output = new StringBuilder();
78+
for (Statement statement : statements) {
79+
statement.accept(new StatementDeParser(output), null);
80+
output.append(";\n");
81+
}
82+
assertEquals("SELECT 0;\nDO " + body + ";\nSELECT 1;\n", output.toString());
83+
Statements reparsed = CCJSqlParserUtil.parseStatements(output.toString(),
84+
p -> p.withDialect(Dialect.POSTGRESQL).withUnsupportedStatements(false));
85+
assertEquals(3, reparsed.size());
86+
assertEquals(body,
87+
assertInstanceOf(DoStatement.class, reparsed.get(1)).getCode().toString());
88+
assertEquals("SELECT 0", reparsed.get(0).toString());
89+
assertEquals("SELECT 1", reparsed.get(2).toString());
6790
}
6891

6992
@Test
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
DO $$
11+
BEGIN
12+
IF NOT EXISTS( select 1 from comm.permission_operation where permission_operation_code = 'ecg_report_time_modify') and EXISTS( select 1 from comm.permission where permission_code = 'data_modify')
13+
THEN
14+
INSERT INTO comm.permission_operation
15+
(permission_operation_id,
16+
permission_id,
17+
permission_operation_code,
18+
permission_operation_name,
19+
"type",
20+
"version",
21+
his_org_id,
22+
his_creater_id,
23+
his_creater_name,
24+
his_create_time,
25+
his_updater_id,
26+
his_update_time)
27+
VALUES
28+
((select max(permission_operation_id) + 1 from comm.permission_operation),
29+
(select permission_id from comm.permission where permission_code = 'data_modify' limit 1),
30+
'ecg_report_time_modify',
31+
'心电报告时间修改',
32+
'1',
33+
0,
34+
(select his_org_id from comm.hospital limit 1),
35+
1,
36+
'系统管理员',
37+
now(),
38+
1,
39+
now()) on conflict(permission_operation_id) do nothing;
40+
END IF;
41+
END $$;

0 commit comments

Comments
 (0)