Skip to content

Commit 1d7c916

Browse files
committed
Validate CTE bodies through the existing statement dispatch
1 parent b2115ac commit 1d7c916

2 files changed

Lines changed: 98 additions & 1 deletion

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ public <S> Void visit(WithItem<?> withItem, S context) {
390390
if (isNotEmpty(withItem.getWithItemList())) {
391391
withItem.getWithItemList().forEach(wi -> wi.accept(this, context));
392392
}
393-
withItem.getSelect().accept((SelectVisitor<?>) this, context);
393+
withItem.accept(getValidator(StatementValidator.class), context);
394394
return null;
395395
}
396396

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.util.validation;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
import java.util.List;
15+
import java.util.stream.Stream;
16+
import net.sf.jsqlparser.parser.feature.Feature;
17+
import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.params.ParameterizedTest;
20+
import org.junit.jupiter.params.provider.Arguments;
21+
import org.junit.jupiter.params.provider.MethodSource;
22+
import org.junit.jupiter.params.provider.ValueSource;
23+
24+
class DataModifyingCteValidationTest {
25+
static Stream<Arguments> modifyingBodies() {
26+
return Stream.of(
27+
Arguments.of("DELETE FROM foo RETURNING id", Feature.delete),
28+
Arguments.of("UPDATE foo SET id = 1 RETURNING id", Feature.update),
29+
Arguments.of("INSERT INTO foo VALUES (1) RETURNING id", Feature.insert));
30+
}
31+
32+
@ParameterizedTest
33+
@MethodSource("modifyingBodies")
34+
void reportsDisallowedDmlInsteadOfCastingToSelect(String body, Feature operation) {
35+
String sql = "WITH x AS (" + body + ") SELECT * FROM x";
36+
for (FeaturesAllowed allowed : List.of(FeaturesAllowed.SELECT,
37+
FeaturesAllowed.SELECT.copy().add(Feature.withItem))) {
38+
List<ValidationError> errors = Validation.validate(List.of(allowed), sql);
39+
assertThat(errors).hasSize(1);
40+
assertThat(errors.get(0).getParsedStatement()).isNotNull();
41+
assertThat(errors.get(0).getErrors()).extracting(Throwable::getMessage)
42+
.contains(operation + " not allowed.");
43+
}
44+
}
45+
46+
@ParameterizedTest
47+
@MethodSource("modifyingBodies")
48+
void allowsDmlWhenItsFeaturesAreEnabled(String body, Feature operation) {
49+
String sql = "WITH x AS (" + body + ") SELECT * FROM x";
50+
List<ValidationError> errors =
51+
Validation.validate(List.of(new FeaturesAllowed(Feature.values())), sql);
52+
assertThat(errors).as("allowed %s CTE", operation).isEmpty();
53+
}
54+
55+
@ParameterizedTest
56+
@ValueSource(strings = {
57+
"WITH x AS (SELECT id FROM foo) SELECT * FROM x",
58+
"WITH RECURSIVE x(id) AS (SELECT 1 UNION ALL SELECT id + 1 FROM x WHERE id < 3) SELECT * FROM x",
59+
"WITH x AS (WITH y AS (SELECT id FROM foo) SELECT * FROM y) SELECT * FROM x"})
60+
void keepsSelectAndRecursiveCteValidation(String sql) {
61+
assertThat(Validation.validate(List.of(FeaturesAllowed.SELECT.copy()
62+
.add(Feature.withItem, Feature.withItemRecursive, Feature.setOperation,
63+
Feature.setOperationUnion)),
64+
sql)).isEmpty();
65+
}
66+
67+
@Test
68+
void collectsErrorsFromMultipleBodiesAndFollowingStatements() {
69+
String sql = "WITH x AS (DELETE FROM foo RETURNING id), "
70+
+ "y AS (UPDATE bar SET id = 2 RETURNING id) SELECT * FROM x; SELECT 1";
71+
Validation validation = new Validation(List.of(FeaturesAllowed.SELECT.copy()
72+
.add(Feature.withItem)), sql);
73+
List<ValidationError> errors = validation.validate();
74+
assertThat(validation.getParsedStatements()).hasSize(2);
75+
assertThat(errors).hasSize(1);
76+
assertThat(errors.get(0).getErrors()).extracting(Throwable::getMessage)
77+
.contains("delete not allowed.", "update not allowed.");
78+
}
79+
80+
@Test
81+
void visitsNestedBodiesAndReturningExpressions() {
82+
String sql = "WITH x AS (WITH y AS (DELETE FROM foo RETURNING id) "
83+
+ "SELECT id FROM y) SELECT * FROM x";
84+
List<ValidationError> errors = Validation.validate(List.of(FeaturesAllowed.SELECT.copy()
85+
.add(Feature.withItem)), sql);
86+
assertThat(errors).hasSize(1);
87+
assertThat(errors.get(0).getErrors()).extracting(Throwable::getMessage)
88+
.contains("delete not allowed.");
89+
90+
FeaturesAllowed allowed = new FeaturesAllowed(Feature.values()).remove(Feature.function);
91+
errors = Validation.validate(List.of(allowed),
92+
"WITH x AS (DELETE FROM foo RETURNING upper(id)) SELECT * FROM x");
93+
assertThat(errors).hasSize(1);
94+
assertThat(errors.get(0).getErrors()).extracting(Throwable::getMessage)
95+
.containsExactly("function not allowed.");
96+
}
97+
}

0 commit comments

Comments
 (0)