Skip to content

Commit e5cbbad

Browse files
fix: walk WITH items of TableStatement in adapter and validator
SelectVisitorAdapter.visit(TableStatement) visited nothing and SelectValidator only reported tableStatement, so a data-modifying CTE in front of TABLE was invisible to StatementFeatures and to Validation: WITH d AS (DELETE FROM t RETURNING a) TABLE d gave mayModifyData() == false and passed FeaturesAllowed.SELECT. The adapter now walks the WITH items, ORDER BY and LIMIT/OFFSET/FETCH of TABLE like its VALUES visit, and SelectValidator validates the WITH list of TABLE and VALUES.
1 parent f925239 commit e5cbbad

5 files changed

Lines changed: 50 additions & 1 deletion

File tree

‎src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ public <S> T visit(LateralSubSelect lateralSubSelect, S context) {
347347

348348
@Override
349349
public <S> T visit(TableStatement tableStatement, S context) {
350+
visitWithItems(tableStatement.getWithItemsList(), context);
351+
expressionVisitor.visitOrderBy(tableStatement.getOrderByElements(), context);
352+
visitLimitOffsetFetch(tableStatement, context);
350353
return null;
351354
}
352355
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,10 @@ public <S> Void visit(LateralSubSelect lateralSubSelect, S context) {
503503

504504
@Override
505505
public <S> Void visit(TableStatement tableStatement, S context) {
506+
if (isNotEmpty(tableStatement.getWithItemsList())) {
507+
tableStatement.getWithItemsList()
508+
.forEach(withItem -> withItem.accept((SelectVisitor<Void>) this, context));
509+
}
506510
getValidator(TableStatementValidator.class).validate(tableStatement);
507511
return null;
508512
}
@@ -525,6 +529,10 @@ public <S> Void visit(ParenthesedFromItem parenthesis, S context) {
525529

526530
@Override
527531
public <S> Void visit(Values values, S context) {
532+
if (isNotEmpty(values.getWithItemsList())) {
533+
values.getWithItemsList()
534+
.forEach(withItem -> withItem.accept((SelectVisitor<Void>) this, context));
535+
}
528536
getValidator(ValuesStatementValidator.class).validate(values);
529537
return null;
530538
}

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,17 @@ void dataModifyingCteAtTopLevel() throws JSQLParserException {
315315
assertThat(f.modifiesData()).isTrue();
316316
}
317317

318+
@ParameterizedTest(name = "{0}")
319+
@ValueSource(strings = {"WITH c AS (DELETE FROM t RETURNING *) TABLE c",
320+
"WITH c AS (UPDATE t SET a = 1 RETURNING *) TABLE u",
321+
"WITH c AS (INSERT INTO t VALUES (1) RETURNING *) TABLE c ORDER BY a LIMIT 1"})
322+
@DisplayName("data-modifying CTE in front of TABLE: TABLE returns rows, the CTE still mutates")
323+
void dataModifyingCteBeforeTable(String sql) throws JSQLParserException {
324+
StatementFeatures f = analyse(sql);
325+
assertThat(f.returnsResultSet()).isTrue();
326+
assertThat(f.modifiesData()).isTrue();
327+
}
328+
318329
@Test
319330
@DisplayName("RETURNING inside a nested CTE of an INSERT returns nothing to the client")
320331
void nestedReturningIsNotAResultSet() throws JSQLParserException {
@@ -666,7 +677,12 @@ private StatementFeatures analyseAllPureExcept(String sql, String impure)
666677
"SELECT a[1:length(pg_read_file('x'))] FROM t",
667678
"SELECT a FROM t WHERE a[length(pg_read_file('x'))] = 1",
668679
"SELECT 1 UNION SELECT 2 LIMIT length(pg_read_file('x'))",
669-
"SELECT 1 UNION SELECT 2 FETCH FIRST length(pg_read_file('x')) ROWS ONLY"})
680+
"SELECT 1 UNION SELECT 2 FETCH FIRST length(pg_read_file('x')) ROWS ONLY",
681+
"WITH c AS (SELECT pg_read_file('x')) TABLE c",
682+
"TABLE t ORDER BY pg_read_file('x')",
683+
"TABLE t LIMIT length(pg_read_file('x'))",
684+
"TABLE t OFFSET length(pg_read_file('x'))",
685+
"TABLE t FETCH FIRST length(pg_read_file('x')) ROWS ONLY"})
670686
void functionIsChecked(String sql) throws JSQLParserException {
671687
StatementFeatures impure = analyseAllPureExcept(sql, "pg_read_file");
672688
assertThat(impure.getUnresolvedReferences()).containsExactly("pg_read_file");

‎src/test/java/net/sf/jsqlparser/util/validation/validator/TableStatementValidatorTest.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,17 @@ public void testValidationSelectNotAllowed() throws JSQLParserException {
3131
validateNotAllowed(sql, 1, 1, FeaturesAllowed.DDL, Feature.select, Feature.tableStatement);
3232
}
3333

34+
@Test
35+
public void testValidationWithItemNotAllowed() throws JSQLParserException {
36+
String sql = "WITH d AS (DELETE FROM t RETURNING a) TABLE d";
37+
validateNotAllowed(sql, 1, 1, FeaturesAllowed.SELECT, Feature.withItem, Feature.delete,
38+
Feature.deleteReturningExpressionList);
39+
}
40+
41+
@Test
42+
public void testValidationWithItemAllowed() throws JSQLParserException {
43+
String sql = "WITH d AS (DELETE FROM t RETURNING a) TABLE d";
44+
validateNoErrors(sql, 1, PostgresqlVersion.V14);
45+
}
46+
3447
}

‎src/test/java/net/sf/jsqlparser/util/validation/validator/ValuesStatementValidatorTest.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
*/
1010
package net.sf.jsqlparser.util.validation.validator;
1111

12+
import net.sf.jsqlparser.parser.feature.Feature;
1213
import net.sf.jsqlparser.util.validation.ValidationTestAsserts;
1314
import net.sf.jsqlparser.util.validation.feature.DatabaseType;
15+
import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
1416
import org.junit.jupiter.api.Test;
1517

1618
public class ValuesStatementValidatorTest extends ValidationTestAsserts {
@@ -22,4 +24,11 @@ public void testValidateValuesStatement() {
2224
1, DatabaseType.SQLSERVER);
2325
}
2426

27+
@Test
28+
public void testValidateWithItemsOfValues() {
29+
validateNotAllowed("WITH d AS (UPDATE t SET a = 1 RETURNING a) VALUES (1)", 1, 1,
30+
FeaturesAllowed.SELECT.copy().add(Feature.values), Feature.withItem,
31+
Feature.update, Feature.updateReturning);
32+
}
33+
2534
}

0 commit comments

Comments
 (0)