Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add force option to dropAll DAT-16418 #5692

Merged
merged 9 commits into from
Mar 21, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ package liquibase.extension.testing.command
import liquibase.change.ColumnConfig
import liquibase.change.core.CreateTableChange
import liquibase.change.core.TagDatabaseChange
import liquibase.exception.CommandExecutionException
import liquibase.exception.CommandValidationException
import liquibase.extension.testing.setup.SetupCleanResources
import liquibase.extension.testing.setup.SetupEnvironmentVariableProvider

CommandTests.define {
command = ["dropAll"]
signature = """
Short Description: Drop all database objects owned by the user
Long Description: NOT SET
Required Args:
force (Boolean) Argument to allow use of dropAll with values of 'true' or 'false'. The default is 'false'.
requireForce (Boolean) Argument to require user of dropAll to supply a 'force' argument, with values of 'true' or 'false'. The default is 'false'.
url (String) The JDBC database connection URL
OBFUSCATED
Optional Args:
Expand Down Expand Up @@ -70,10 +75,157 @@ Optional Args:
]

expectedUI = [
"All objects dropped from LBUSER@jdbc:h2:mem:lbcat"
"INFO: The drop-all command may result in unrecoverable destructive changes to objects at",
"To protect against unwanted drops, set --requireForce=true, which will require a --force=true flag on the command.",
"Learn more at https://docs.liquibase.com/dropall."
]
}

run "Happy path with explicit requireDropAllForce=false", {
arguments = [
url : { it.url },
username : { it.username },
password : { it.password }
]
setup {
def add = [ LIQUIBASE_DROP_ALL_REQUIRE_FORCE:"false" ]
String[] remove = [:]
run(
new SetupEnvironmentVariableProvider(add, remove)
)
database = [
new CreateTableChange(
tableName: "FirstTable",
columns: [
ColumnConfig.fromName("FirstColumn")
.setType("VARCHAR(255)")
]
),
new CreateTableChange(
tableName: "SecondTable",
columns: [
ColumnConfig.fromName("SecondColumn")
.setType("VARCHAR(255)")
]
),
new TagDatabaseChange(
tag: "version_2.0"
),
new CreateTableChange(
tableName: "liquibaseRunInfo",
columns: [
ColumnConfig.fromName("timesRan")
.setType("INT")
]
),
]
}

expectedResults = [
statusCode : 0,
]

expectedUI = [
"INFO: The drop-all command may result in unrecoverable destructive changes to objects at",
"To protect against unwanted drops, set --requireForce=true, which will require a --force=true flag on the command.",
"Learn more at https://docs.liquibase.com/dropall."
]
}

run "Happy path with requireDropAllForce=true and force=true", {
arguments = [
url : { it.url },
username : { it.username },
password : { it.password },
force: { true }
]
setup {
def add = [ LIQUIBASE_COMMAND_DROP_ALL_REQUIRE_FORCE:"true" ]
String[] remove = [:]
run(
new SetupEnvironmentVariableProvider(add, remove)
)
database = [
new CreateTableChange(
tableName: "FirstTable",
columns: [
ColumnConfig.fromName("FirstColumn")
.setType("VARCHAR(255)")
]
),
new CreateTableChange(
tableName: "SecondTable",
columns: [
ColumnConfig.fromName("SecondColumn")
.setType("VARCHAR(255)")
]
),
new TagDatabaseChange(
tag: "version_2.0"
),
new CreateTableChange(
tableName: "liquibaseRunInfo",
columns: [
ColumnConfig.fromName("timesRan")
.setType("INT")
]
),
]
}

expectedResults = [
statusCode : 0,
]

expectedUI = [
"All objects dropped from"
]
}

run "Run with require dropAll flag set to true", {
arguments = [
url : { it.url },
username : { it.username },
password : { it.password },
requireForce: { true }
]
setup {
cleanResources(SetupCleanResources.CleanupMode.CLEAN_ON_BOTH, "liquibase.flowfile.yaml")
database = [
new CreateTableChange(
tableName: "FirstTable",
columns: [
ColumnConfig.fromName("FirstColumn")
.setType("VARCHAR(255)")
]
),
new CreateTableChange(
tableName: "SecondTable",
columns: [
ColumnConfig.fromName("SecondColumn")
.setType("VARCHAR(255)")
]
),
new TagDatabaseChange(
tag: "version_2.0"
),
new CreateTableChange(
tableName: "liquibaseRunInfo",
columns: [
ColumnConfig.fromName("timesRan")
.setType("INT")
]
),
]
}

expectedException = CommandExecutionException.class
expectedExceptionMessage =
"""
The drop-all command may result in unrecoverable destructive changes by dropping all the objects at database
"""
}

run "Run without a URL should throw an exception", {
arguments = [
url : "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public class GlobalConfiguration implements AutoloadedConfigurations {
.setDescription("Complete list of Location(s) to search for files such as changelog files in. Multiple paths can be specified by separating them with commas.")
.build();

ALWAYS_DROP_INSTEAD_OF_REPLACE = builder.define("alwaysDropInsteadOfReplace", Boolean.class)
ALWAYS_DROP_INSTEAD_OF_REPLACE = builder.define("alwaysDropInsteadOfReplace", Boolean.class)
.setDescription("If true, drop and recreate a view instead of replacing it.")
.setDefaultValue(false)
.setValueHandler(ValueHandlerUtil::booleanValueHandler)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package liquibase.command.core;

import liquibase.CatalogAndSchema;
import liquibase.GlobalConfiguration;
import liquibase.Scope;
import liquibase.changelog.ChangeLogHistoryServiceFactory;
import liquibase.command.*;
import liquibase.configuration.LiquibaseConfiguration;
import liquibase.database.Database;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
Expand All @@ -13,6 +15,7 @@
import liquibase.logging.Logger;
import liquibase.snapshot.SnapshotControl;
import liquibase.util.StringUtil;
import liquibase.util.ValueHandlerUtil;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -28,12 +31,28 @@ public class DropAllCommandStep extends AbstractCommandStep {
public static final CommandArgumentDefinition<String> SCHEMAS_ARG;
public static final CommandArgumentDefinition<CatalogAndSchema[]> CATALOG_AND_SCHEMAS_ARG;

public static final CommandArgumentDefinition<Boolean> REQUIRE_FORCE_ARG;

public static final CommandArgumentDefinition<Boolean> FORCE_ARG;

static {
CommandBuilder builder = new CommandBuilder(COMMAND_NAME);
SCHEMAS_ARG = builder.argument("schemas", String.class).description("Schemas to include in drop").build();
CATALOG_AND_SCHEMAS_ARG = builder.argument("catalogAndSchemas", CatalogAndSchema[].class)
.description("Catalog and schemas to include in drop. It has precedence over SCHEMAS_ARG").supersededBy(SCHEMAS_ARG).hidden().build();
SCHEMAS_ARG.setSupersededBy(CATALOG_AND_SCHEMAS_ARG);
REQUIRE_FORCE_ARG = builder.argument("requireForce", Boolean.class)
.description("Argument to require user of dropAll to supply a 'force' argument, with values of 'true' or 'false'. The default is 'false'.")
.setValueHandler(ValueHandlerUtil::booleanValueHandler)
.required()
.defaultValue(false)
.build();
FORCE_ARG = builder.argument("force", Boolean.class)
.description("Argument to allow use of dropAll with values of 'true' or 'false'. The default is 'false'.")
.setValueHandler(ValueHandlerUtil::booleanValueHandler)
.required()
.defaultValue(false)
.build();
}

@Override
Expand All @@ -55,6 +74,23 @@ public List<Class<?>> requiredDependencies() {
public void run(CommandResultsBuilder resultsBuilder) throws Exception {
CommandScope commandScope = resultsBuilder.getCommandScope();
Database database = (Database) commandScope.getDependency(Database.class);
if (Boolean.FALSE.equals(commandScope.getArgumentValue(REQUIRE_FORCE_ARG))) {
String noRequirementForForceMessage =
String.format("The drop-all command may result in unrecoverable destructive changes to objects at '%s'.%n" +
"To protect against unwanted drops, set --requireForce=true, which " +
"will require a --force=true flag on the command.%nLearn more at https://docs.liquibase.com/dropall.%n",
database.getConnection().getURL());
Scope.getCurrentScope().getUI().sendMessage("INFO: " + noRequirementForForceMessage);
} else {
boolean force = commandScope.getArgumentValue(FORCE_ARG);
if (!force) {
String message =
"The drop-all command may result in unrecoverable destructive changes by dropping all the objects at database '" +
database.getConnection().getURL() +
"'. This message can be suppressed and the drop-all command executed by adding the --force flag.";
throw new LiquibaseException(message);
}
}
LockService lockService = LockServiceFactory.getInstance().getLockService(database);
lockService.waitForLock();

Expand Down