From c067ce1c7f65fdb0862746bec273f9cfc20a3cb0 Mon Sep 17 00:00:00 2001 From: filipe Date: Mon, 4 Sep 2023 16:05:31 -0300 Subject: [PATCH 1/4] Implements a better check for TableIsEmpty using SQL ansi + tests --- .../common/common.tests.changelog.xml | 12 ++++ .../core/TableIsEmptyPrecondition.java | 60 +++++++++++++++++-- .../core/TableIsEmptyGenerator.java | 40 +++++++++++++ .../statement/core/TableIsEmptyStatement.java | 14 +++++ .../liquibase.sqlgenerator.SqlGenerator | 1 + 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java create mode 100644 liquibase-standard/src/main/java/liquibase/statement/core/TableIsEmptyStatement.java diff --git a/liquibase-integration-tests/src/test/resources/changelogs/common/common.tests.changelog.xml b/liquibase-integration-tests/src/test/resources/changelogs/common/common.tests.changelog.xml index 0c0bcfe7631..5856d051d04 100644 --- a/liquibase-integration-tests/src/test/resources/changelogs/common/common.tests.changelog.xml +++ b/liquibase-integration-tests/src/test/resources/changelogs/common/common.tests.changelog.xml @@ -484,8 +484,20 @@ + + + + + + + + + + + + diff --git a/liquibase-standard/src/main/java/liquibase/precondition/core/TableIsEmptyPrecondition.java b/liquibase-standard/src/main/java/liquibase/precondition/core/TableIsEmptyPrecondition.java index 02b18d2fe70..6a057cbb15d 100644 --- a/liquibase-standard/src/main/java/liquibase/precondition/core/TableIsEmptyPrecondition.java +++ b/liquibase-standard/src/main/java/liquibase/precondition/core/TableIsEmptyPrecondition.java @@ -1,14 +1,64 @@ package liquibase.precondition.core; -public class TableIsEmptyPrecondition extends RowCountPrecondition { +import liquibase.Scope; +import liquibase.changelog.ChangeSet; +import liquibase.changelog.DatabaseChangeLog; +import liquibase.changelog.visitor.ChangeExecListener; +import liquibase.database.Database; +import liquibase.exception.PreconditionErrorException; +import liquibase.exception.PreconditionFailedException; +import liquibase.exception.ValidationErrors; +import liquibase.exception.Warnings; +import liquibase.executor.ExecutorService; +import liquibase.precondition.AbstractPrecondition; +import liquibase.statement.core.TableIsEmptyStatement; +import lombok.Getter; +import lombok.Setter; - public TableIsEmptyPrecondition() { - this.setExpectedRows(0); +@Setter +@Getter +public class TableIsEmptyPrecondition extends AbstractPrecondition { + + private String catalogName; + + private String schemaName; + + private String tableName; + + @Override + public void check(Database database, DatabaseChangeLog changeLog, ChangeSet changeSet, ChangeExecListener changeExecListener) throws PreconditionFailedException, PreconditionErrorException { + try { + TableIsEmptyStatement statement = new TableIsEmptyStatement(getCatalogName(), getSchemaName(), getTableName()); + + int result = Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", database).queryForInt(statement); + if (result > 0) { + throw new PreconditionFailedException("Table " + getTableName() + " is not empty.", changeLog, this); + } + + } catch (PreconditionFailedException e) { + throw e; + } catch (Exception e) { + throw new PreconditionErrorException(e, changeLog, this); + } } @Override - protected String getFailureMessage(int result, int expectedRows) { - return "Table "+getTableName()+" is not empty. Contains "+result+" rows"; + public Warnings warn(Database database) { + return new Warnings(); + } + + @Override + public ValidationErrors validate(Database database) { + ValidationErrors validationErrors = new ValidationErrors(); + validationErrors.checkRequiredField("tableName", tableName); + + return validationErrors; + } + + + @Override + public String getSerializedObjectNamespace() { + return STANDARD_CHANGELOG_NAMESPACE; } @Override diff --git a/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java b/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java new file mode 100644 index 00000000000..75f013def9f --- /dev/null +++ b/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java @@ -0,0 +1,40 @@ +package liquibase.sqlgenerator.core; + +import liquibase.database.Database; +import liquibase.exception.ValidationErrors; +import liquibase.sql.Sql; +import liquibase.sql.UnparsedSql; +import liquibase.sqlgenerator.SqlGeneratorChain; +import liquibase.statement.core.TableIsEmptyStatement; + +public class TableIsEmptyGenerator extends AbstractSqlGenerator { + + @Override + public int getPriority() { + return PRIORITY_DEFAULT; + } + + @Override + public boolean supports(TableIsEmptyStatement statement, Database database) { + return true; + } + + @Override + public ValidationErrors validate(TableIsEmptyStatement tableIsEmptyStatement, Database database, SqlGeneratorChain sqlGeneratorChain) { + ValidationErrors validationErrors = new ValidationErrors(); + validationErrors.checkRequiredField("tableName", tableIsEmptyStatement.getTableName()); + return validationErrors; + } + + protected String generateCountSql(TableIsEmptyStatement statement, Database database) { + return String.format("SELECT COUNT(1) WHERE EXISTS (SELECT * FROM %s)", database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName())); + } + + + @Override + public Sql[] generateSql(TableIsEmptyStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) { + return new Sql[] { new UnparsedSql(generateCountSql(statement, database)) }; + } + + +} diff --git a/liquibase-standard/src/main/java/liquibase/statement/core/TableIsEmptyStatement.java b/liquibase-standard/src/main/java/liquibase/statement/core/TableIsEmptyStatement.java new file mode 100644 index 00000000000..e1aebd62222 --- /dev/null +++ b/liquibase-standard/src/main/java/liquibase/statement/core/TableIsEmptyStatement.java @@ -0,0 +1,14 @@ +package liquibase.statement.core; + +import liquibase.statement.AbstractSqlStatement; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class TableIsEmptyStatement extends AbstractSqlStatement { + + private final String catalogName; + private final String schemaName; + private final String tableName; +} diff --git a/liquibase-standard/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator b/liquibase-standard/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator index 98b8a0cc376..4516bea418d 100644 --- a/liquibase-standard/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator +++ b/liquibase-standard/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator @@ -98,6 +98,7 @@ liquibase.sqlgenerator.core.SetTableRemarksGenerator liquibase.sqlgenerator.core.SetViewRemarksGenerator liquibase.sqlgenerator.core.StoredProcedureGenerator liquibase.sqlgenerator.core.TableRowCountGenerator +liquibase.sqlgenerator.core.TableIsEmptyGenerator liquibase.sqlgenerator.core.TagDatabaseGenerator liquibase.sqlgenerator.core.UnlockDatabaseChangeLogGenerator liquibase.sqlgenerator.core.UpdateChangeSetChecksumGenerator From 0f0f07f8a8bf3777709c91f5f112b1cb7b43b00a Mon Sep 17 00:00:00 2001 From: filipe Date: Mon, 4 Sep 2023 16:15:09 -0300 Subject: [PATCH 2/4] Fix order. --- .../META-INF/services/liquibase.sqlgenerator.SqlGenerator | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liquibase-standard/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator b/liquibase-standard/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator index 4516bea418d..123271239fc 100644 --- a/liquibase-standard/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator +++ b/liquibase-standard/src/main/resources/META-INF/services/liquibase.sqlgenerator.SqlGenerator @@ -97,8 +97,8 @@ liquibase.sqlgenerator.core.SetNullableGenerator liquibase.sqlgenerator.core.SetTableRemarksGenerator liquibase.sqlgenerator.core.SetViewRemarksGenerator liquibase.sqlgenerator.core.StoredProcedureGenerator -liquibase.sqlgenerator.core.TableRowCountGenerator liquibase.sqlgenerator.core.TableIsEmptyGenerator +liquibase.sqlgenerator.core.TableRowCountGenerator liquibase.sqlgenerator.core.TagDatabaseGenerator liquibase.sqlgenerator.core.UnlockDatabaseChangeLogGenerator liquibase.sqlgenerator.core.UpdateChangeSetChecksumGenerator From 47acb9f6538124e3cfba51104dfb63b7be999016 Mon Sep 17 00:00:00 2001 From: filipe Date: Mon, 4 Sep 2023 17:05:18 -0300 Subject: [PATCH 3/4] Adding support for more databases. --- .../sqlgenerator/core/TableIsEmptyGenerator.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java b/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java index 75f013def9f..87b1e335457 100644 --- a/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java +++ b/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java @@ -1,6 +1,8 @@ package liquibase.sqlgenerator.core; import liquibase.database.Database; +import liquibase.database.core.HsqlDatabase; +import liquibase.database.core.OracleDatabase; import liquibase.exception.ValidationErrors; import liquibase.sql.Sql; import liquibase.sql.UnparsedSql; @@ -27,7 +29,13 @@ public ValidationErrors validate(TableIsEmptyStatement tableIsEmptyStatement, Da } protected String generateCountSql(TableIsEmptyStatement statement, Database database) { - return String.format("SELECT COUNT(1) WHERE EXISTS (SELECT * FROM %s)", database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName())); + String tableName = database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName()); + if (database instanceof HsqlDatabase) { + return String.format("SELECT COUNT(1) FROM (VALUES(0)) WHERE EXISTS (SELECT * FROM %s)", tableName); + } else if (database instanceof OracleDatabase) { + return String.format("SELECT COUNT(1) FROM DUAL WHERE EXISTS (SELECT * FROM %s)", tableName); + } + return String.format("SELECT COUNT(1) WHERE EXISTS (SELECT * FROM %s)", tableName); } From b9bae5654b24118e7ed5253d63961f63d48dc6d3 Mon Sep 17 00:00:00 2001 From: filipe Date: Mon, 4 Sep 2023 17:24:34 -0300 Subject: [PATCH 4/4] Fix Mysql family tests. --- .../liquibase/sqlgenerator/core/TableIsEmptyGenerator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java b/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java index 87b1e335457..bc7cbe7f215 100644 --- a/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java +++ b/liquibase-standard/src/main/java/liquibase/sqlgenerator/core/TableIsEmptyGenerator.java @@ -2,6 +2,7 @@ import liquibase.database.Database; import liquibase.database.core.HsqlDatabase; +import liquibase.database.core.MySQLDatabase; import liquibase.database.core.OracleDatabase; import liquibase.exception.ValidationErrors; import liquibase.sql.Sql; @@ -32,7 +33,7 @@ protected String generateCountSql(TableIsEmptyStatement statement, Database data String tableName = database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName()); if (database instanceof HsqlDatabase) { return String.format("SELECT COUNT(1) FROM (VALUES(0)) WHERE EXISTS (SELECT * FROM %s)", tableName); - } else if (database instanceof OracleDatabase) { + } else if (database instanceof OracleDatabase || database instanceof MySQLDatabase) { return String.format("SELECT COUNT(1) FROM DUAL WHERE EXISTS (SELECT * FROM %s)", tableName); } return String.format("SELECT COUNT(1) WHERE EXISTS (SELECT * FROM %s)", tableName);