From 5e3e282e2e95a349f18f1d372658db8251a72720 Mon Sep 17 00:00:00 2001 From: tanjiro Kamado Date: Thu, 29 Oct 2026 20:23:35 +0530 Subject: [PATCH] fix: report a feature for every statement in StatementValidator --- .../sf/jsqlparser/parser/feature/Feature.java | 79 ++++++++++++++- .../validation/feature/FeaturesAllowed.java | 6 +- .../util/validation/feature/H2Version.java | 79 ++++++--------- .../validation/feature/MariaDbVersion.java | 14 ++- .../util/validation/feature/MySqlVersion.java | 13 ++- .../validation/feature/OracleVersion.java | 12 ++- .../validation/feature/PostgresqlVersion.java | 16 +++- .../validation/feature/SqlServerVersion.java | 3 +- .../validation/validator/SelectValidator.java | 2 +- .../validator/StatementValidator.java | 76 ++++++++++----- .../validator/StatementValidatorTest.java | 96 +++++++++++++++++++ 11 files changed, 305 insertions(+), 91 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index 3ce49efd6a..de271c5b37 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -510,6 +510,13 @@ public enum Feature { executeStatementImmediate, executeUsing, + /** + * "PREPARE" and "DEALLOCATE" of a prepared statement are allowed + * + * @see net.sf.jsqlparser.statement.PrepareStatement + * @see net.sf.jsqlparser.statement.DeallocateStatement + */ + prepare, deallocate, /** * SQL "REPLACE" statement is allowed */ @@ -521,6 +528,12 @@ public enum Feature { * @see Drop */ drop, dropTable, dropIndex, dropView, dropSchema, dropSequence, dropTableIfExists, dropIndexIfExists, dropViewIfExists, dropSchemaIfExists, dropSequenceIfExists, + /** + * Oracle "PURGE" statement is allowed + * + * @see net.sf.jsqlparser.statement.PurgeStatement + */ + purge, /** * PostgreSQL "REASSIGN OWNED BY ... TO ..." statement is allowed * @@ -540,6 +553,13 @@ public enum Feature { * @see net.sf.jsqlparser.statement.alter.database.AlterDatabase */ alterDatabase, + /** + * "ALTER SESSION" and "ALTER SYSTEM" statements are allowed + * + * @see net.sf.jsqlparser.statement.alter.AlterSession + * @see net.sf.jsqlparser.statement.alter.AlterSystemStatement + */ + alterSession, alterSystem, /** * SQL "CREATE SCHEMA" statement is allowed * @@ -628,6 +648,11 @@ public enum Feature { * Structured type, domain and extension statements. */ createType, alterType, createDomain, alterDomain, createExtension, alterExtension, + /** + * PostgreSQL "CREATE POLICY", "ALTER POLICY", "CREATE STATISTICS" and "ALTER STATISTICS" + * statements are allowed + */ + createPolicy, alterPolicy, createStatistics, alterStatistics, /** * SQL "CREATE SYNONYM" statement is allowed * @@ -638,6 +663,13 @@ public enum Feature { * SQL "CREATE TRIGGER" statement is allowed */ createTrigger, + /** + * MySQL/MariaDB "CREATE EVENT" and "ALTER EVENT" statements are allowed + * + * @see net.sf.jsqlparser.statement.create.event.CreateEvent + * @see net.sf.jsqlparser.statement.create.event.AlterEvent + */ + createEvent, alterEvent, /** * PostgreSQL "CREATE EVENT TRIGGER" and "ALTER EVENT TRIGGER" statements are allowed * @@ -664,8 +696,12 @@ public enum Feature { * @see Commit */ commit, + /** SQL "ROLLBACK" statement is allowed */ + rollback, /** START TRANSACTION and dialect-specific BEGIN transaction statements. */ startTransaction, + /** SAVEPOINT statements. */ + savepoint, /** RELEASE [SAVEPOINT] statements. */ releaseSavepoint, /** Explicit AND [NO] CHAIN transaction completion. */ @@ -676,6 +712,12 @@ public enum Feature { transactionDeferrable, /** MySQL WITH CONSISTENT SNAPSHOT transaction characteristic. */ transactionConsistentSnapshot, + /** + * SQL "LOCK TABLE" statement is allowed + * + * @see net.sf.jsqlparser.statement.lock.LockStatement + */ + lock, /** * SQL "COMMENT ON" statement is allowed * @@ -784,16 +826,17 @@ public enum Feature { */ grant, revoke, createRole, alterRole, alterDefaultPrivileges, /** - * MySQL/MariaDB account management: "ALTER USER", "RENAME USER", "SET ROLE", "SET DEFAULT ROLE" - * and "SET PASSWORD" + * MySQL/MariaDB account management: "CREATE USER", "ALTER USER", "RENAME USER", "SET ROLE", + * "SET DEFAULT ROLE" and "SET PASSWORD" * + * @see net.sf.jsqlparser.statement.create.user.CreateUser * @see net.sf.jsqlparser.statement.alter.AlterUser * @see net.sf.jsqlparser.statement.alter.RenameUser * @see net.sf.jsqlparser.statement.SetRoleStatement * @see net.sf.jsqlparser.statement.SetDefaultRoleStatement * @see net.sf.jsqlparser.statement.SetPasswordStatement */ - alterUser, renameUser, setRole, setDefaultRole, setPassword, + createUser, alterUser, renameUser, setRole, setDefaultRole, setPassword, /** * @see Function */ @@ -1041,10 +1084,38 @@ public enum Feature { imprt, /** - * "EXPORT" + * "EXPORT" and BigQuery "EXPORT DATA" */ export, + /** + * PostgreSQL and DuckDB "COPY" and BigQuery "LOAD DATA" statements are allowed + * + * @see net.sf.jsqlparser.statement.CopyStatement + * @see net.sf.jsqlparser.statement.load.LoadDataStatement + */ + copy, loadData, + + /** + * DuckDB "ATTACH", "DETACH", "CONNECT", "DISCONNECT", "PRAGMA", "INSTALL", "LOAD", "CREATE + * MACRO" and "CREATE EXTENSION REPOSITORY" statements are allowed + */ + attach, detach, connect, disconnect, pragma, installExtension, loadExtension, createMacro, createExtensionRepository, + + /** + * BigQuery "ASSERT" statement is allowed + * + * @see net.sf.jsqlparser.statement.AssertStatement + */ + assertStatement, + + /** + * "SESSION" statement is allowed + * + * @see net.sf.jsqlparser.statement.SessionStatement + */ + session, + /** * MySQL allows a ',' as a separator between key and value entries. We allow that by default, * but it can be disabled here diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java index 3423673c17..0a855c46e8 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java @@ -112,7 +112,8 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS Feature.createTableCreateOptionStrings, Feature.createTableTableOptionStrings, Feature.createTableIfNotExists, Feature.createTableRowMovement, Feature.createTableFromSelect, - Feature.createTrigger, Feature.createEventTrigger, + Feature.createTrigger, Feature.createEventTrigger, Feature.createEvent, + Feature.createPolicy, Feature.createStatistics, Feature.createView).unmodifyable(); /** * all "ALTER" {@link Feature}'s @@ -122,7 +123,8 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS Feature.alterTextSearchConfiguration, Feature.alterCollation, Feature.alterTablespace, - Feature.alterEventTrigger, + Feature.alterEventTrigger, Feature.alterEvent, + Feature.alterPolicy, Feature.alterStatistics, Feature.alterView, Feature.alterIndex, Feature.alterSchema, Feature.alterForeignDataWrapper, Feature.alterServer, Feature.alterUserMapping) .unmodifyable(); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/H2Version.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/H2Version.java index 3984e7fe69..a81012872b 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/H2Version.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/H2Version.java @@ -24,17 +24,13 @@ */ public enum H2Version implements Version { V_1_4_200("1.4.200", - EnumSet.of( - // supported if used with jdbc + EnumSet.of(// supported if used with jdbc Feature.jdbcParameter, - Feature.jdbcNamedParameter, - // expressions - Feature.exprLike, - // http://h2database.com/html/commands.html#select + Feature.jdbcNamedParameter, // expressions + Feature.exprLike, // http://h2database.com/html/commands.html#select Feature.select, Feature.selectGroupBy, Feature.function, - Feature.selectHaving, - // https://h2database.com/html/grammar.html?#table_expression + Feature.selectHaving, // https://h2database.com/html/grammar.html?#table_expression // https://h2database.com/html/grammar.html?#join_specification Feature.join, Feature.joinSimple, @@ -71,42 +67,30 @@ public enum H2Version implements Version { Feature.setOperationMinus, // http://h2database.com/html/commands.html#create_sequence - Feature.createSequence, - // http://h2database.com/html/commands.html#alter_sequence - Feature.alterSequence, - // http://h2database.com/html/commands.html#create_schema - Feature.createSchema, - // http://h2database.com/html/commands.html#create_index - Feature.createIndex, - // http://h2database.com/html/commands.html#create_table + Feature.createSequence, // http://h2database.com/html/commands.html#alter_sequence + Feature.alterSequence, // http://h2database.com/html/commands.html#create_schema + Feature.createSchema, // http://h2database.com/html/commands.html#create_index + Feature.createIndex, // http://h2database.com/html/commands.html#create_table Feature.createTable, Feature.createTableCreateOptionStrings, Feature.createTableTableOptionStrings, Feature.createTableFromSelect, - Feature.createTableIfNotExists, - // http://h2database.com/html/commands.html#create_view + Feature.createTableIfNotExists, // http://h2database.com/html/commands.html#create_view Feature.createView, - Feature.createViewForce, Feature.createOrReplaceView, - // http://h2database.com/html/commands.html#alter_view_rename + Feature.createViewForce, Feature.createOrReplaceView, // http://h2database.com/html/commands.html#alter_view_rename // Feature.alterView, // http://h2database.com/html/commands.html#select - Feature.top, - // http://www.h2database.com/html/advanced.html?search=limit#result_sets + Feature.top, // http://www.h2database.com/html/advanced.html?search=limit#result_sets Feature.fetch, Feature.fetchFirst, // http://www.h2database.com/html/commands.html?highlight=DISTINCT&search=SELECT#firstFound - Feature.distinct, - // http://www.h2database.com/html/commands.html?highlight=DISTINCT%20ON&search=SELECT#firstFound - Feature.distinctOn, - // http://h2database.com/html/commands.html#insert + Feature.distinct, // http://www.h2database.com/html/commands.html?highlight=DISTINCT%20ON&search=SELECT#firstFound + Feature.distinctOn, // http://h2database.com/html/commands.html#insert Feature.insert, Feature.insertValues, Feature.values, - Feature.insertFromSelect, - // http://h2database.com/html/commands.html#update - Feature.update, - // http://h2database.com/html/commands.html#delete - Feature.delete, - // http://h2database.com/html/commands.html#truncate_table + Feature.insertFromSelect, // http://h2database.com/html/commands.html#update + Feature.update, // http://h2database.com/html/commands.html#delete + Feature.delete, // http://h2database.com/html/commands.html#truncate_table Feature.truncate, // http://www.h2database.com/html/commands.html#execute_immediate @@ -114,30 +98,23 @@ public enum H2Version implements Version { // http://h2database.com/html/commands.html#drop_table // http://h2database.com/html/commands.html#drop_index - Feature.drop, - // http://h2database.com/html/commands.html#drop_table - Feature.dropTable, - // http://h2database.com/html/commands.html#drop_index - Feature.dropIndex, - // http://h2database.com/html/commands.html#drop_view - Feature.dropView, - // http://h2database.com/html/commands.html#drop_schema - Feature.dropSchema, - // http://h2database.com/html/commands.html#drop_sequence + Feature.drop, // http://h2database.com/html/commands.html#drop_table + Feature.dropTable, // http://h2database.com/html/commands.html#drop_index + Feature.dropIndex, // http://h2database.com/html/commands.html#drop_view + Feature.dropView, // http://h2database.com/html/commands.html#drop_schema + Feature.dropSchema, // http://h2database.com/html/commands.html#drop_sequence Feature.dropSequence, Feature.dropTableIfExists, Feature.dropIndexIfExists, Feature.dropViewIfExists, - Feature.dropSchemaIfExists, Feature.dropSequenceIfExists, - // http://h2database.com/html/commands.html#alter_table_add + Feature.dropSchemaIfExists, Feature.dropSequenceIfExists, // http://h2database.com/html/commands.html#alter_table_add // http://h2database.com/html/commands.html#alter_table_add_constraint // ... - Feature.alterTable, - // http://www.h2database.com/html/commands.html#explain - Feature.explain, - // http://www.h2database.com/html/commands.html#grant_right + Feature.alterTable, // http://www.h2database.com/html/commands.html#explain + Feature.explain, // http://www.h2database.com/html/commands.html#grant_right // http://www.h2database.com/html/commands.html#grant_role - Feature.grant, - // http://h2database.com/html/commands.html#commit - Feature.commit)); + Feature.grant, // http://h2database.com/html/commands.html#commit + // http://h2database.com/html/commands.html#rollback + // http://h2database.com/html/commands.html#savepoint + Feature.commit, Feature.rollback, Feature.savepoint)); private Set features; private String versionString; diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java index f4ca890270..91fe21ebe5 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java @@ -84,12 +84,16 @@ public enum MariaDbVersion implements Version { Feature.tableMaintenance, // https://mariadb.com/kb/en/account-management-sql-statements/ - Feature.createRole, Feature.alterUser, Feature.renameUser, Feature.setRole, + Feature.createRole, Feature.createUser, Feature.alterUser, Feature.renameUser, + Feature.setRole, Feature.setDefaultRole, Feature.setPassword, // https://mariadb.com/kb/en/call/ Feature.execute, Feature.executeCall, + // https://mariadb.com/kb/en/deallocate-drop-prepare/ + Feature.deallocate, + // https://mariadb.com/kb/en/drop/ Feature.drop, // https://mariadb.com/kb/en/drop-index/ Feature.dropIndex, // https://mariadb.com/kb/en/drop-table/ @@ -121,6 +125,10 @@ public enum MariaDbVersion implements Version { Feature.createSchema, // https://mariadb.com/kb/en/create-trigger/ Feature.createTrigger, + // https://mariadb.com/kb/en/create-event/ + // https://mariadb.com/kb/en/alter-event/ + Feature.createEvent, Feature.alterEvent, + // https://mariadb.com/kb/en/create-database/ // https://mariadb.com/kb/en/alter-database/ Feature.createDatabase, Feature.alterDatabase, @@ -141,7 +149,9 @@ public enum MariaDbVersion implements Version { Feature.use, // https://mariadb.com/kb/en/grant/ Feature.grant, // https://mariadb.com/kb/en/revoke/ Feature.revoke, // https://mariadb.com/kb/en/commit/ - Feature.commit, // https://mariadb.com/kb/en/optimizer-hints/ + Feature.commit, // https://mariadb.com/kb/en/rollback/ + Feature.rollback, // https://mariadb.com/kb/en/savepoint/ + Feature.savepoint, // https://mariadb.com/kb/en/optimizer-hints/ Feature.mySqlHintStraightJoin, Feature.mysqlSelectModifiers, Feature.mysqlCalcFoundRows, Feature.mysqlSqlCacheFlag)), diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java index 17a1eae4d7..bcacc1a998 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java @@ -80,12 +80,16 @@ public enum MySqlVersion implements Version { Feature.tableMaintenance, // https://dev.mysql.com/doc/refman/8.0/en/account-management-statements.html - Feature.createRole, Feature.alterUser, Feature.renameUser, Feature.setRole, + Feature.createRole, Feature.createUser, Feature.alterUser, Feature.renameUser, + Feature.setRole, Feature.setDefaultRole, Feature.setPassword, // https://dev.mysql.com/doc/refman/8.0/en/call.html Feature.execute, Feature.executeCall, + // https://dev.mysql.com/doc/refman/8.0/en/deallocate-prepare.html + Feature.deallocate, + Feature.drop, // https://dev.mysql.com/doc/refman/8.0/en/drop-table.html Feature.dropTable, // https://dev.mysql.com/doc/refman/8.0/en/drop-index.html Feature.dropIndex, // https://dev.mysql.com/doc/refman/8.0/en/drop-view.html @@ -121,6 +125,10 @@ public enum MySqlVersion implements Version { Feature.createIndex, // https://dev.mysql.com/doc/refman/8.0/en/create-trigger.html Feature.createTrigger, + // https://dev.mysql.com/doc/refman/8.0/en/create-event.html + // https://dev.mysql.com/doc/refman/8.0/en/alter-event.html + Feature.createEvent, Feature.alterEvent, + // https://dev.mysql.com/doc/refman/8.0/en/describe.html Feature.describe, Feature.desc, // https://dev.mysql.com/doc/refman/8.0/en/explain.html @@ -134,7 +142,8 @@ public enum MySqlVersion implements Version { Feature.grant, // https://dev.mysql.com/doc/refman/8.0/en/revoke.html Feature.revoke, // https://dev.mysql.com/doc/refman/8.0/en/use.html Feature.use, // https://dev.mysql.com/doc/refman/8.0/en/commit.html - Feature.commit, Feature.startTransaction, Feature.releaseSavepoint, + Feature.commit, Feature.rollback, Feature.startTransaction, + Feature.savepoint, Feature.releaseSavepoint, Feature.transactionChain, Feature.transactionConsistentSnapshot, // Feature.mySqlHintStraightJoin, Feature.mysqlSqlCacheFlag, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java index 8d94ab72c1..6a607bb066 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/OracleVersion.java @@ -123,7 +123,17 @@ public enum OracleVersion implements Version { Feature.createTrigger, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-SCHEMA.html Feature.createSchema, - Feature.commit, + Feature.commit, Feature.rollback, Feature.savepoint, + + // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/LOCK-TABLE.html + Feature.lock, + + // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ALTER-SESSION.html + // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ALTER-SYSTEM.html + Feature.alterSession, Feature.alterSystem, + + // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/PURGE.html + Feature.purge, // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/COMMENT.html Feature.comment, Feature.commentOnTable, Feature.commentOnColumn, diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java index b0d8115d91..a0413a7101 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java @@ -141,6 +141,19 @@ public enum PostgresqlVersion implements Version { // https://www.postgresql.org/docs/current/sql-alterdatabase.html Feature.createDatabase, Feature.alterDatabase, + // https://www.postgresql.org/docs/current/sql-altersystem.html + Feature.alterSystem, + + // https://www.postgresql.org/docs/current/sql-createpolicy.html + // https://www.postgresql.org/docs/current/sql-createstatistics.html + Feature.createPolicy, Feature.alterPolicy, Feature.createStatistics, + Feature.alterStatistics, + + // https://www.postgresql.org/docs/current/sql-copy.html + // https://www.postgresql.org/docs/current/sql-prepare.html + // https://www.postgresql.org/docs/current/sql-lock.html + Feature.copy, Feature.prepare, Feature.deallocate, Feature.lock, + // https://www.postgresql.org/docs/current/sql-alterfunction.html // https://www.postgresql.org/docs/current/sql-alterprocedure.html // https://www.postgresql.org/docs/current/sql-alterroutine.html @@ -190,7 +203,8 @@ public enum PostgresqlVersion implements Version { Feature.securityLabel, // https://www.postgresql.org/docs/current/sql-set.html Feature.set, Feature.setAssignmentTo, // https://www.postgresql.org/docs/current/sql-reset.html Feature.reset, // https://www.postgresql.org/docs/current/sql-commit.html - Feature.commit, Feature.startTransaction, Feature.releaseSavepoint, + Feature.commit, Feature.rollback, Feature.startTransaction, + Feature.savepoint, Feature.releaseSavepoint, Feature.transactionIsolationLevel, Feature.transactionDeferrable)), V11("11", V10.copy().getFeatures()), V12("12", V11.copy() diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java index e7c11c9a7b..f028037341 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java @@ -106,7 +106,8 @@ public enum SqlServerVersion implements Version { Feature.createTrigger, // https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver15 Feature.merge, // https://docs.microsoft.com/en-us/sql/t-sql/statements/grant-transact-sql?view=sql-server-ver15 Feature.grant, // https://docs.microsoft.com/en-us/sql/t-sql/language-elements/commit-transaction-transact-sql?view=sql-server-ver15 - Feature.commit, // special sql-server features + Feature.commit, // https://docs.microsoft.com/en-us/sql/t-sql/language-elements/rollback-transaction-transact-sql?view=sql-server-ver15 + Feature.rollback, // special sql-server features // https://docs.microsoft.com/en-us/sql/relational-databases/xml/for-xml-sql-server?view=sql-server-ver15 Feature.selectForXmlPath, Feature.use, Feature.setIdentityInsert, Feature.allowSquareBracketQuotation, // diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java index b06c8a89f5..a042c50983 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java @@ -505,7 +505,7 @@ public Void visit(Values values, S context) { @Override public Void visit(Import imprt, S context) { - // TODO: not yet implemented + validateFeature(Feature.imprt); return null; } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java index e15bded789..55b80cdccd 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java @@ -11,7 +11,9 @@ import net.sf.jsqlparser.statement.alter.AlterRelation; import net.sf.jsqlparser.statement.alter.AlterTablespace; +import net.sf.jsqlparser.statement.alter.AlterTablespaceMove; import net.sf.jsqlparser.statement.alter.AlterPolicy; +import net.sf.jsqlparser.statement.alter.AlterStatistics; import net.sf.jsqlparser.statement.drop.DropPolicy; import net.sf.jsqlparser.statement.drop.DropUserMapping; import net.sf.jsqlparser.statement.create.statistics.CreateStatistics; @@ -173,9 +175,7 @@ public Void visit(CreateTable createTable, S context) { @Override public Void visit(CreateTrigger createTrigger, S context) { - if (createTrigger.getRoutine() != null) { - validateFeature(Feature.createTrigger); - } + validateFeature(Feature.createTrigger); createTrigger.visit(this::validateOptionalFromItem, this::validateOptionalExpression); if (createTrigger.getBody() != null) { @@ -261,6 +261,7 @@ public Void visit(ParenthesedDelete delete, S context) { @Override public Void visit(SessionStatement sessionStatement, S context) { + validateFeature(Feature.session); return null; } @@ -323,6 +324,7 @@ public Void visit(Alter alter, S context) { @Override public Void visit(AlterEvent alterEvent, S context) { + validateFeature(Feature.alterEvent); if (alterEvent.getBody() != null) { alterEvent.getBody().accept(this, context); } @@ -534,6 +536,7 @@ public Void visit(AlterDatabase statement, S context) { @Override public Void visit(CreateUser createUser, S context) { + validateFeature(Feature.createUser); return null; } @@ -570,6 +573,7 @@ public Void visit(SetPasswordStatement setPassword, S context) { @Override public Void visit(CreateEvent createEvent, S context) { + validateFeature(Feature.createEvent); if (createEvent.getBody() != null) { createEvent.getBody().accept(this, context); } @@ -641,12 +645,13 @@ public Void visit(TableMaintenanceStatement statement, S context) { @Override public Void visit(SavepointStatement savepointStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.savepoint); return null; } @Override public Void visit(RollbackStatement rollbackStatement, S context) { + validateFeature(Feature.rollback); if (rollbackStatement.getChain() != null) { validateFeature(Feature.transactionChain); } @@ -655,7 +660,7 @@ public Void visit(RollbackStatement rollbackStatement, S context) { @Override public Void visit(AlterSession alterSession, S context) { - // TODO: not yet implemented + validateFeature(Feature.alterSession); return null; } @@ -669,127 +674,131 @@ public Void visit(IfElseStatement ifElseStatement, S context) { } public Void visit(RenameTableStatement renameTableStatement, S context) { - // TODO: not yet implemented + // the same operation as ALTER TABLE ... RENAME TO + validateFeature(Feature.alterTable); return null; } @Override public Void visit(PragmaStatement pragmaStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.pragma); return null; } @Override public Void visit(AssertStatement assertStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.assertStatement); return null; } @Override public Void visit(ExtensionStatement extensionStatement, S context) { - // TODO: not yet implemented + validateFeature(extensionStatement.getOperation() == ExtensionStatement.Operation.LOAD + ? Feature.loadExtension + : Feature.installExtension); return null; } @Override public Void visit(ExportDataStatement exportDataStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.export); return null; } @Override public Void visit(AttachStatement attachStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.attach); return null; } @Override public Void visit(DetachStatement detachStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.detach); return null; } @Override public Void visit(ConnectStatement connectStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.connect); return null; } @Override public Void visit(DisconnectStatement disconnectStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.disconnect); return null; } @Override public Void visit(PrepareStatement prepareStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.prepare); return null; } @Override public Void visit(DeallocateStatement deallocateStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.deallocate); return null; } @Override public Void visit(CopyStatement copyStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.copy); return null; } @Override public Void visit(CreateMacro createMacro, S context) { - // TODO: not yet implemented + validateFeature(Feature.createMacro); return null; } @Override public Void visit(CreateExtensionRepository createExtensionRepository, S context) { - // TODO: not yet implemented + validateFeature(Feature.createExtensionRepository); return null; } @Override public Void visit(LoadDataStatement loadDataStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.loadData); return null; } @Override public Void visit(PurgeStatement purgeStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.purge); return null; } @Override public Void visit(AlterSystemStatement alterSystemStatement, S context) { - // TODO: not yet implemented + validateFeature(Feature.alterSystem); return null; } @Override public Void visit(UnsupportedStatement unsupportedStatement, S context) { - + // its tokens are not modelled, so nothing inside it can be validated + validateFeature(Feature.allowUnsupportedStatements); return null; } @Override public Void visit(Import imprt, S context) { - // TODO: not yet implemented + validateFeature(Feature.imprt); return null; } @Override public Void visit(Export export, S context) { - // TODO: not yet implemented + validateFeature(Feature.export); return null; } @Override public Void visit(LockStatement lock, S context) { - // TODO: not yet implemented + validateFeature(Feature.lock); return null; } @@ -1090,6 +1099,7 @@ public void visit(Export export) { @Override public Void visit(CreatePolicy createPolicy, S context) { + validateFeature(Feature.createPolicy); validateOptionalFromItem(createPolicy.getTable()); validateOptionalExpression(createPolicy.getUsingExpression()); validateOptionalExpression(createPolicy.getWithCheckExpression()); @@ -1400,8 +1410,15 @@ public Void visit(AlterTablespace statement, S context) { return null; } + @Override + public Void visit(AlterTablespaceMove statement, S context) { + validateFeature(Feature.alterTablespace); + return null; + } + @Override public Void visit(AlterPolicy statement, S context) { + validateFeature(Feature.alterPolicy); validateOptionalFromItem(statement.getTable()); if (statement.getNewName() == null) { statement.getOptions().visitExpressions(this::validateOptionalExpression); @@ -1418,9 +1435,16 @@ public Void visit(DropPolicy statement, S context) { @Override public Void visit(CreateStatistics statement, S context) { + validateFeature(Feature.createStatistics); validateOptionalFromItem(statement.getTable()); validateOptionalExpression(statement.getExpressions()); return null; } + @Override + public Void visit(AlterStatistics statement, S context) { + validateFeature(Feature.alterStatistics); + return null; + } + } diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/StatementValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/StatementValidatorTest.java index 7a7e52888e..51d4717635 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/StatementValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/StatementValidatorTest.java @@ -10,15 +10,111 @@ package net.sf.jsqlparser.util.validation.validator; import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; import net.sf.jsqlparser.parser.feature.Feature; +import net.sf.jsqlparser.parser.feature.FeatureConfiguration; +import net.sf.jsqlparser.util.validation.Validation; +import net.sf.jsqlparser.util.validation.ValidationError; import net.sf.jsqlparser.util.validation.ValidationTestAsserts; import net.sf.jsqlparser.util.validation.feature.DatabaseType; import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class StatementValidatorTest extends ValidationTestAsserts { + static Stream statementsOutsideSelect() { + return Stream.of( + Arguments.of("COPY t TO '/tmp/t.csv'", Feature.copy), + Arguments.of("LOAD DATA INTO ds.t FROM FILES (format = 'CSV')", Feature.loadData), + Arguments.of("EXPORT DATA OPTIONS (uri = 'gs://b/*.csv', format = 'CSV') " + + "AS SELECT a FROM t", Feature.export), + Arguments.of("ATTACH DATABASE '/tmp/x.db' AS x", Feature.attach), + Arguments.of("DETACH DATABASE x", Feature.detach), + Arguments.of("CONNECT 'postgres://localhost/db'", Feature.connect), + Arguments.of("DISCONNECT", Feature.disconnect), + Arguments.of("PRAGMA database_list", Feature.pragma), + Arguments.of("INSTALL httpfs", Feature.installExtension), + Arguments.of("LOAD httpfs", Feature.loadExtension), + Arguments.of("CREATE MACRO m(a) AS a + 1", Feature.createMacro), + Arguments.of("CREATE EXTENSION REPOSITORY my_repo", + Feature.createExtensionRepository), + Arguments.of("ASSERT (1 = 1) AS 'expected'", Feature.assertStatement), + Arguments.of("SESSION START", Feature.session), + Arguments.of("PREPARE p AS SELECT 1", Feature.prepare), + Arguments.of("DEALLOCATE PREPARE p", Feature.deallocate), + Arguments.of("SAVEPOINT s", Feature.savepoint), + Arguments.of("ROLLBACK", Feature.rollback), + Arguments.of("LOCK TABLE t IN EXCLUSIVE MODE", Feature.lock), + Arguments.of("ALTER SESSION SET CURRENT_SCHEMA = other", Feature.alterSession), + Arguments.of("ALTER SYSTEM SET archive_command = 'cp %p /tmp/%f'", + Feature.alterSystem), + Arguments.of("PURGE TABLE t", Feature.purge), + Arguments.of("RENAME TABLE a TO b", Feature.alterTable), + Arguments.of("ALTER TABLE ALL IN TABLESPACE a SET TABLESPACE b", + Feature.alterTablespace), + Arguments.of("CREATE USER 'app'@'%' REQUIRE SSL", Feature.createUser), + Arguments.of("CREATE POLICY p ON t", Feature.createPolicy), + Arguments.of("ALTER POLICY p ON t RENAME TO q", Feature.alterPolicy), + Arguments.of("CREATE STATISTICS s ON a, b FROM t", Feature.createStatistics), + Arguments.of("ALTER STATISTICS s RENAME TO s2", Feature.alterStatistics), + Arguments.of("CREATE EVENT e ON SCHEDULE EVERY 1 DAY DO SELECT 1", + Feature.createEvent), + Arguments.of("ALTER EVENT e DISABLE", Feature.alterEvent), + Arguments.of("CREATE TRIGGER trg BEFORE INSERT ON t FOR EACH ROW SELECT 1", + Feature.createTrigger), + Arguments.of("CREATE LANGUAGE plperlu", Feature.allowUnsupportedStatements)); + } + + @ParameterizedTest + @MethodSource("statementsOutsideSelect") + public void testValidateStatementNotAllowedForSelect(String sql, Feature feature) { + validateNotAllowed(sql, 1, 1, FeaturesAllowed.SELECT, feature); + } + + @Test + public void testValidateExasolImportExportNotAllowedForSelect() { + FeatureConfiguration exasol = + new FeatureConfiguration().setValue(Feature.dialect, Dialect.EXASOL.name()); + for (String sql : Arrays.asList("IMPORT INTO t FROM LOCAL CSV FILE 'file.csv'", + "SELECT * FROM (IMPORT FROM LOCAL CSV FILE 'file.csv')", + "EXPORT t INTO LOCAL CSV FILE 'file.csv'")) { + List errors = + new Validation(exasol, List.of(FeaturesAllowed.SELECT), sql).validate(); + assertErrorsSize(errors, 1); + assertNotAllowed(errors.get(0).getErrors(), + sql.startsWith("EXPORT") ? Feature.export : Feature.imprt); + } + } + + @Test + public void testValidateStatementsForSupportingDatabases() throws JSQLParserException { + validateNoErrors("ROLLBACK", 1, DatabaseType.DATABASES); + validateNoErrors("SAVEPOINT s", 1, DatabaseType.ORACLE, DatabaseType.MYSQL, + DatabaseType.MARIADB, DatabaseType.POSTGRESQL, DatabaseType.H2); + validateNoErrors("LOCK TABLE t IN EXCLUSIVE MODE", 1, DatabaseType.ORACLE, + DatabaseType.POSTGRESQL); + validateNoErrors("ALTER SYSTEM SET x = 1", 1, DatabaseType.ORACLE, + DatabaseType.POSTGRESQL); + validateNoErrors("ALTER SESSION SET CURRENT_SCHEMA = other", 1, DatabaseType.ORACLE); + validateNoErrors("DEALLOCATE PREPARE p", 1, DatabaseType.MYSQL, DatabaseType.MARIADB, + DatabaseType.POSTGRESQL); + for (String sql : Arrays.asList("COPY t FROM '/tmp/t.csv'", "PREPARE p AS SELECT 1", + "CREATE POLICY p ON t", "ALTER POLICY p ON t RENAME TO q", + "CREATE STATISTICS s ON a, b FROM t", "ALTER STATISTICS s RENAME TO s2")) { + validateNoErrors(sql, 1, DatabaseType.POSTGRESQL); + } + for (String sql : Arrays.asList("CREATE EVENT e ON SCHEDULE EVERY 1 DAY DO DELETE FROM t", + "ALTER EVENT e DISABLE")) { + validateNoErrors(sql, 1, DatabaseType.MYSQL, DatabaseType.MARIADB); + } + } + @Test public void testValidateCreateSchema() throws JSQLParserException { for (String sql : Arrays.asList("CREATE SCHEMA my_schema",