Skip to content

Commit f63ace3

Browse files
authored
fix(feature): classify metadata DDL schema effects (#2754)
Signed-off-by: 付典 <fudianchn@gmail.com>
1 parent 6d1ec1d commit f63ace3

2 files changed

Lines changed: 217 additions & 37 deletions

File tree

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

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,70 +9,86 @@
99
*/
1010
package net.sf.jsqlparser.statement;
1111

12-
import net.sf.jsqlparser.statement.create.accessmethod.CreateAccessMethod;
13-
14-
import net.sf.jsqlparser.statement.oracle.OracleBlock;
15-
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
16-
import net.sf.jsqlparser.statement.oracle.OracleNullStatement;
17-
18-
import net.sf.jsqlparser.statement.role.CreateRole;
19-
import net.sf.jsqlparser.statement.role.AlterRole;
20-
import net.sf.jsqlparser.statement.grant.Revoke;
21-
import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges;
22-
import net.sf.jsqlparser.statement.create.trigger.CreateTrigger;
23-
import net.sf.jsqlparser.statement.create.type.CreateType;
24-
import net.sf.jsqlparser.statement.alter.AlterType;
25-
import net.sf.jsqlparser.statement.create.domain.CreateDomain;
26-
import net.sf.jsqlparser.statement.alter.AlterDomain;
27-
import net.sf.jsqlparser.statement.create.extension.CreateExtension;
28-
import net.sf.jsqlparser.statement.alter.AlterExtension;
29-
import net.sf.jsqlparser.statement.create.publication.CreatePublication;
30-
import net.sf.jsqlparser.statement.alter.AlterPublication;
31-
import net.sf.jsqlparser.statement.create.subscription.CreateSubscription;
32-
import net.sf.jsqlparser.statement.create.subscription.SubscriptionOption;
33-
import net.sf.jsqlparser.statement.alter.AlterSubscription;
34-
12+
import java.util.ArrayList;
13+
import java.util.EnumSet;
14+
import java.util.LinkedHashSet;
15+
import java.util.List;
16+
import java.util.Locale;
17+
import java.util.Set;
18+
import java.util.function.Predicate;
3519
import net.sf.jsqlparser.JSQLParserException;
3620
import net.sf.jsqlparser.expression.AnalyticExpression;
37-
import net.sf.jsqlparser.expression.TranscodingFunction;
3821
import net.sf.jsqlparser.expression.Expression;
3922
import net.sf.jsqlparser.expression.ExpressionVisitor;
4023
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
4124
import net.sf.jsqlparser.expression.Function;
25+
import net.sf.jsqlparser.expression.TranscodingFunction;
4226
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
4327
import net.sf.jsqlparser.statement.alter.Alter;
28+
import net.sf.jsqlparser.statement.alter.AlterCollation;
29+
import net.sf.jsqlparser.statement.alter.AlterDomain;
30+
import net.sf.jsqlparser.statement.alter.AlterExtension;
31+
import net.sf.jsqlparser.statement.alter.AlterPublication;
4432
import net.sf.jsqlparser.statement.alter.AlterSession;
33+
import net.sf.jsqlparser.statement.alter.AlterStatistics;
34+
import net.sf.jsqlparser.statement.alter.AlterSubscription;
4535
import net.sf.jsqlparser.statement.alter.AlterSystemStatement;
36+
import net.sf.jsqlparser.statement.alter.AlterTablespaceMove;
37+
import net.sf.jsqlparser.statement.alter.AlterTextSearchConfiguration;
38+
import net.sf.jsqlparser.statement.alter.AlterType;
4639
import net.sf.jsqlparser.statement.alter.RenameTableStatement;
40+
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
41+
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
4742
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
4843
import net.sf.jsqlparser.statement.analyze.Analyze;
4944
import net.sf.jsqlparser.statement.comment.Comment;
45+
import net.sf.jsqlparser.statement.create.accessmethod.CreateAccessMethod;
5046
import net.sf.jsqlparser.statement.create.database.CreateDatabase;
47+
import net.sf.jsqlparser.statement.create.domain.CreateDomain;
48+
import net.sf.jsqlparser.statement.create.extension.CreateExtension;
49+
import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
5150
import net.sf.jsqlparser.statement.create.index.CreateIndex;
51+
import net.sf.jsqlparser.statement.create.macro.CreateMacro;
5252
import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
53+
import net.sf.jsqlparser.statement.create.publication.CreatePublication;
5354
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
5455
import net.sf.jsqlparser.statement.create.sequence.CreateSequence;
56+
import net.sf.jsqlparser.statement.create.subscription.CreateSubscription;
57+
import net.sf.jsqlparser.statement.create.subscription.SubscriptionOption;
5558
import net.sf.jsqlparser.statement.create.synonym.CreateSynonym;
5659
import net.sf.jsqlparser.statement.create.table.CreateTable;
60+
import net.sf.jsqlparser.statement.create.textsearch.CreateTextSearchConfiguration;
61+
import net.sf.jsqlparser.statement.create.trigger.CreateTrigger;
62+
import net.sf.jsqlparser.statement.create.type.CreateType;
63+
import net.sf.jsqlparser.statement.create.user.CreateUser;
5764
import net.sf.jsqlparser.statement.create.view.AlterView;
5865
import net.sf.jsqlparser.statement.create.view.CreateView;
5966
import net.sf.jsqlparser.statement.delete.Delete;
6067
import net.sf.jsqlparser.statement.drop.Drop;
6168
import net.sf.jsqlparser.statement.execute.Execute;
6269
import net.sf.jsqlparser.statement.export.Export;
70+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
71+
import net.sf.jsqlparser.statement.grant.AlterDefaultPrivileges;
6372
import net.sf.jsqlparser.statement.grant.Grant;
73+
import net.sf.jsqlparser.statement.grant.Revoke;
6474
import net.sf.jsqlparser.statement.imprt.Import;
6575
import net.sf.jsqlparser.statement.insert.Insert;
6676
import net.sf.jsqlparser.statement.insert.InsertBulk;
77+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
6778
import net.sf.jsqlparser.statement.lock.LockStatement;
6879
import net.sf.jsqlparser.statement.merge.Merge;
80+
import net.sf.jsqlparser.statement.oracle.OracleAssignment;
81+
import net.sf.jsqlparser.statement.oracle.OracleBlock;
82+
import net.sf.jsqlparser.statement.oracle.OracleNullStatement;
6983
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
84+
import net.sf.jsqlparser.statement.role.AlterRole;
85+
import net.sf.jsqlparser.statement.role.CreateRole;
7086
import net.sf.jsqlparser.statement.select.FromItemVisitor;
7187
import net.sf.jsqlparser.statement.select.FromItemVisitorAdapter;
88+
import net.sf.jsqlparser.statement.select.MySqlSelectIntoClause;
7289
import net.sf.jsqlparser.statement.select.PivotVisitor;
7390
import net.sf.jsqlparser.statement.select.PivotVisitorAdapter;
7491
import net.sf.jsqlparser.statement.select.PlainSelect;
75-
import net.sf.jsqlparser.statement.select.MySqlSelectIntoClause;
7692
import net.sf.jsqlparser.statement.select.Select;
7793
import net.sf.jsqlparser.statement.select.SelectItem;
7894
import net.sf.jsqlparser.statement.select.SelectItemVisitor;
@@ -88,18 +104,6 @@
88104
import net.sf.jsqlparser.statement.update.Update;
89105
import net.sf.jsqlparser.statement.upsert.Upsert;
90106

91-
import java.util.ArrayList;
92-
import java.util.EnumSet;
93-
import java.util.LinkedHashSet;
94-
import java.util.List;
95-
import java.util.Locale;
96-
import java.util.Set;
97-
import java.util.function.Predicate;
98-
import net.sf.jsqlparser.statement.create.macro.CreateMacro;
99-
import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
100-
import net.sf.jsqlparser.statement.export.ExportDataStatement;
101-
import net.sf.jsqlparser.statement.load.LoadDataStatement;
102-
103107
/**
104108
* Derives a {@link StatementFeatures} verdict from a statement tree.
105109
*
@@ -1159,4 +1163,45 @@ public <S> Void visit(OracleNullStatement statement, S context) {
11591163
public <S> Void visit(CreateAccessMethod statement, S context) {
11601164
return schemaOnly();
11611165
}
1166+
1167+
@Override
1168+
public <S> Void visit(AlterCollation statement, S context) {
1169+
return schemaOnly();
1170+
}
1171+
1172+
@Override
1173+
public <S> Void visit(AlterDatabase statement, S context) {
1174+
return schemaOnly();
1175+
}
1176+
1177+
@Override
1178+
public <S> Void visit(AlterSchema statement, S context) {
1179+
return schemaOnly();
1180+
}
1181+
1182+
@Override
1183+
public <S> Void visit(AlterStatistics statement, S context) {
1184+
return schemaOnly();
1185+
}
1186+
1187+
@Override
1188+
public <S> Void visit(AlterTablespaceMove statement, S context) {
1189+
return schemaOnly();
1190+
}
1191+
1192+
@Override
1193+
public <S> Void visit(AlterTextSearchConfiguration statement, S context) {
1194+
return schemaOnly();
1195+
}
1196+
1197+
@Override
1198+
public <S> Void visit(CreateTextSearchConfiguration statement, S context) {
1199+
return schemaOnly();
1200+
}
1201+
1202+
@Override
1203+
public <S> Void visit(CreateUser statement, S context) {
1204+
return schemaOnly();
1205+
}
1206+
11621207
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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 static org.junit.jupiter.api.Assertions.*;
13+
14+
import java.util.EnumSet;
15+
import java.util.List;
16+
import java.util.Set;
17+
import java.util.stream.Stream;
18+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
19+
import net.sf.jsqlparser.statement.alter.AlterCollation;
20+
import net.sf.jsqlparser.statement.alter.AlterStatistics;
21+
import net.sf.jsqlparser.statement.alter.AlterTablespaceMove;
22+
import net.sf.jsqlparser.statement.alter.AlterTextSearchConfiguration;
23+
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
24+
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
25+
import net.sf.jsqlparser.statement.create.textsearch.CreateTextSearchConfiguration;
26+
import net.sf.jsqlparser.statement.create.user.CreateUser;
27+
import net.sf.jsqlparser.test.TestUtils;
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.params.provider.Arguments;
31+
import org.junit.jupiter.params.provider.MethodSource;
32+
import org.junit.jupiter.params.provider.ValueSource;
33+
34+
class StatementFeatureVisitorMetadataDdlTest {
35+
static Stream<Arguments> definitions() {
36+
return Stream.of(
37+
Arguments.of("ALTER COLLATION c REFRESH VERSION", AlterCollation.class),
38+
Arguments.of("ALTER COLLATION c RENAME TO c2", AlterCollation.class),
39+
Arguments.of("ALTER DATABASE d READ ONLY = 0", AlterDatabase.class),
40+
Arguments.of("ALTER DATABASE d CHARACTER SET utf8mb4 COLLATE utf8mb4_bin",
41+
AlterDatabase.class),
42+
Arguments.of("ALTER SCHEMA s RENAME TO s2", AlterSchema.class),
43+
Arguments.of("ALTER SCHEMA s OWNER TO CURRENT_USER", AlterSchema.class),
44+
Arguments.of("ALTER STATISTICS s RENAME TO s2", AlterStatistics.class),
45+
Arguments.of("ALTER STATISTICS s SET STATISTICS 500", AlterStatistics.class),
46+
Arguments.of("ALTER TABLE ALL IN TABLESPACE old_space SET TABLESPACE new_space",
47+
AlterTablespaceMove.class),
48+
Arguments.of(
49+
"ALTER INDEX ALL IN TABLESPACE old_space SET TABLESPACE new_space NOWAIT",
50+
AlterTablespaceMove.class),
51+
Arguments.of("CREATE TEXT SEARCH CONFIGURATION c (COPY = pg_catalog.simple)",
52+
CreateTextSearchConfiguration.class),
53+
Arguments.of("CREATE TEXT SEARCH CONFIGURATION c (PARSER = p)",
54+
CreateTextSearchConfiguration.class),
55+
Arguments.of("ALTER TEXT SEARCH CONFIGURATION c ADD MAPPING FOR word WITH simple",
56+
AlterTextSearchConfiguration.class),
57+
Arguments.of("ALTER TEXT SEARCH CONFIGURATION c DROP MAPPING FOR word",
58+
AlterTextSearchConfiguration.class),
59+
Arguments.of("CREATE USER u", CreateUser.class),
60+
Arguments.of("CREATE USER u IDENTIFIED BY 'password'", CreateUser.class));
61+
}
62+
63+
@ParameterizedTest
64+
@MethodSource("definitions")
65+
void metadataDefinitionsHaveOnlySchemaEffectsAcrossEntryPoints(String sql, Class<?> type)
66+
throws Exception {
67+
Statement statement = TestUtils.assertSqlCanBeParsedAndDeparsed(sql);
68+
assertInstanceOf(type, statement);
69+
assertSchemaOnly(statement.getFeatures());
70+
assertSchemaOnly(StatementFeatureVisitor.analyse(statement));
71+
assertSchemaOnly(StatementFeatureVisitor.analyse(sql));
72+
assertSchemaOnly(StatementFeatureVisitor.analyse(statement, name -> true));
73+
Statements script = CCJSqlParserUtil.parseStatements(sql);
74+
assertSchemaOnly(StatementFeatureVisitor.analyse(script));
75+
List<StatementFeatures> each = StatementFeatureVisitor.analyseEach(script);
76+
assertEquals(1, each.size());
77+
assertSchemaOnly(each.get(0));
78+
}
79+
80+
@ParameterizedTest
81+
@MethodSource("definitions")
82+
void classificationDoesNotSuppressFollowingExecutedSql(String sql, Class<?> type)
83+
throws Exception {
84+
Statements script = CCJSqlParserUtil.parseStatements(sql + "; SELECT f(id) FROM t");
85+
assertInstanceOf(type, script.getStatements().get(0));
86+
List<StatementFeatures> each = StatementFeatureVisitor.analyseEach(script);
87+
assertSchemaOnly(each.get(0));
88+
assertEquals(Set.of("f"), each.get(1).getUnresolvedReferences());
89+
assertEquals(EnumSet.of(StmtFeature.READS_DATA, StmtFeature.RETURNS_RESULT_SET),
90+
each.get(1).getCertain());
91+
StatementFeatures union = StatementFeatureVisitor.analyse(script);
92+
assertEquals(EnumSet.of(StmtFeature.MODIFIES_SCHEMA, StmtFeature.READS_DATA,
93+
StmtFeature.RETURNS_RESULT_SET), union.getCertain());
94+
assertEquals(Set.of("f"), union.getUnresolvedReferences());
95+
assertTrue(union.mayModifyData());
96+
}
97+
98+
@ParameterizedTest
99+
@ValueSource(
100+
strings = {"CREATE ROLE r", "CREATE TRIGGER tr AFTER INSERT ON t EXECUTE FUNCTION f()",
101+
"CREATE POLICY p ON t USING (f(id) > 0)"})
102+
void existingStoredDefinitionsRemainSchemaOnly(String sql) throws Exception {
103+
assertSchemaOnly(TestUtils.assertSqlCanBeParsedAndDeparsed(sql).getFeatures());
104+
}
105+
106+
@Test
107+
void unknownAndUnsupportedStatementsRemainConservative() throws Exception {
108+
Statement unknown = new Statement() {
109+
@Override
110+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
111+
return null;
112+
}
113+
};
114+
assertOpaque(unknown.getFeatures());
115+
assertOpaque(StatementFeatureVisitor.analyse(new Statements()));
116+
StatementFeatures unsupported = CCJSqlParserUtil
117+
.parse("garbage", p -> p.withUnsupportedStatements(true)).getFeatures();
118+
assertOpaque(unsupported);
119+
assertEquals(Set.of("unsupported"), unsupported.getUnresolvedReferences());
120+
}
121+
122+
private static void assertSchemaOnly(StatementFeatures features) {
123+
assertEquals(EnumSet.of(StmtFeature.MODIFIES_SCHEMA), features.getCertain());
124+
assertTrue(features.getUncertain().isEmpty());
125+
assertTrue(features.getUnresolvedReferences().isEmpty());
126+
}
127+
128+
private static void assertOpaque(StatementFeatures features) {
129+
assertEquals(EnumSet.of(StmtFeature.OPAQUE), features.getCertain());
130+
assertEquals(
131+
EnumSet.of(StmtFeature.READS_DATA, StmtFeature.RETURNS_RESULT_SET,
132+
StmtFeature.MODIFIES_DATA, StmtFeature.MODIFIES_SCHEMA),
133+
features.getUncertain());
134+
}
135+
}

0 commit comments

Comments
 (0)