diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java index 8f4e7aa8f..14d04de65 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java @@ -25,6 +25,8 @@ import net.sf.jsqlparser.expression.TranscodingFunction; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.alter.Alter; +import net.sf.jsqlparser.statement.alter.AlterExpression; +import net.sf.jsqlparser.statement.alter.AlterOperation; import net.sf.jsqlparser.statement.alter.AlterCollation; import net.sf.jsqlparser.statement.alter.AlterRoutine; import net.sf.jsqlparser.statement.alter.AlterTrigger; @@ -171,6 +173,16 @@ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.TooManyMethods", "PMD.ExcessiveImports"}) public class StatementFeatureVisitor extends StatementVisitorAdapter { + /** + * ALTER TABLE actions that delete, swap or replace the rows of a table or partition. Like + * TRUNCATE and DROP they carry MODIFIES_DATA as well as MODIFIES_SCHEMA. + */ + private static final Set ALTER_ACTIONS_MODIFYING_DATA = + EnumSet.of(AlterOperation.DROP_PARTITION, AlterOperation.TRUNCATE_PARTITION, + AlterOperation.EXCHANGE_PARTITION, AlterOperation.DISCARD_PARTITION, + AlterOperation.DISCARD_TABLESPACE, AlterOperation.IMPORT_PARTITION, + AlterOperation.IMPORT_TABLESPACE); + private final Analysis analysis; // ---- construction ----------------------------------------------------------------------- @@ -672,7 +684,25 @@ public Void visit(PurgeStatement purgeStatement, S context) { @Override public Void visit(Alter alter, S context) { - return schemaOnly(); + schemaOnly(); + if (alter.getAlterExpressions() == null) { + return null; + } + for (AlterExpression action : alter.getAlterExpressions()) { + AlterOperation operation = action.getOperation(); + if (ALTER_ACTIONS_MODIFYING_DATA.contains(operation)) { + analysis.certain(StmtFeature.MODIFIES_DATA); + } else if (operation == AlterOperation.REPAIR_PARTITION) { + // like REPAIR TABLE, it may rewrite or discard rows + analysis.possible(StmtFeature.MODIFIES_DATA); + } else if (operation == AlterOperation.UNSPECIFIC) { + // an action the grammar does not model is kept as raw tokens and may write + // rows, e.g. ClickHouse's ALTER TABLE t DELETE WHERE .. + analysis.possible(StmtFeature.MODIFIES_DATA); + analysis.unresolved("unsupported"); + } + } + return null; } @Override diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index 250dda5b6..964b0e332 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -1209,7 +1209,7 @@ The features - ``SELECT``, ``DELETE .. RETURNING``, ``SHOW``, ``DESCRIBE``, ``EXPLAIN`` * - ``MODIFIES_DATA`` - rows are written or destroyed - - ``INSERT``, ``UPDATE``, ``DELETE``, ``MERGE``, ``UPSERT``, ``TRUNCATE``, ``DROP`` + - ``INSERT``, ``UPDATE``, ``DELETE``, ``MERGE``, ``UPSERT``, ``TRUNCATE``, ``DROP``, ``ALTER TABLE .. TRUNCATE PARTITION`` * - ``MODIFIES_SCHEMA`` - the catalogue changes - ``CREATE``, ``ALTER``, ``DROP``, ``TRUNCATE``, ``GRANT``, ``COMMENT`` diff --git a/src/test/java/net/sf/jsqlparser/statement/StatementFeatureVisitorTest.java b/src/test/java/net/sf/jsqlparser/statement/StatementFeatureVisitorTest.java index b8b310994..d62b829e2 100644 --- a/src/test/java/net/sf/jsqlparser/statement/StatementFeatureVisitorTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/StatementFeatureVisitorTest.java @@ -255,6 +255,45 @@ void destructiveDdlAlsoModifiesData() throws JSQLParserException { } } + /** + * The ALTER TABLE actions that delete, swap or replace rows are destructive DDL in the same + * sense: {@code TRUNCATE PARTITION ALL} empties a partitioned table just like TRUNCATE. + */ + @ParameterizedTest + @ValueSource(strings = {"ALTER TABLE t TRUNCATE PARTITION ALL", + "ALTER TABLE t TRUNCATE PARTITION p0", "ALTER TABLE t DROP PARTITION p0, p1", + "ALTER TABLE t ADD COLUMN c INT, DROP PARTITION p0", + "ALTER TABLE t EXCHANGE PARTITION p0 WITH TABLE u", + "ALTER TABLE t DISCARD TABLESPACE", "ALTER TABLE t DISCARD PARTITION p0 TABLESPACE", + "ALTER TABLE t IMPORT TABLESPACE", "ALTER TABLE t IMPORT PARTITION p0 TABLESPACE"}) + void destructiveAlterActionsAlsoModifyData(String sql) throws JSQLParserException { + StatementFeatures f = analyse(sql); + assertThat(f.modifiesSchema()).isTrue(); + assertThat(f.modifiesData()).isTrue(); + assertThat(f.returnsResultSet()).isFalse(); + } + + /** + * An ALTER TABLE action the grammar does not model, such as ClickHouse's DELETE and UPDATE + * mutations, is kept as raw text, so a write cannot be excluded. + */ + @ParameterizedTest + @ValueSource(strings = {"ALTER TABLE t DELETE WHERE id > 0", + "ALTER TABLE t UPDATE c = 1 WHERE id > 0"}) + void unmodelledAlterActionMayModifyData(String sql) throws JSQLParserException { + StatementFeatures f = analyse(sql); + assertThat(f.modifiesSchema()).isTrue(); + assertThat(f.mayModifyData()).isTrue(); + assertThat(f.getUnresolvedReferences()).containsExactly("unsupported"); + } + + @Test + void repairPartitionMayModifyDataLikeRepairTable() throws JSQLParserException { + StatementFeatures f = analyse("ALTER TABLE t REPAIR PARTITION p0"); + assertThat(f.modifiesData()).isFalse(); + assertThat(f.mayModifyData()).isTrue(); + } + // ---- the cases a flag union gets wrong -------------------------------------------------- @Test