diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 726a1696c46..5f2274ea738 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -60,7 +60,7 @@ jobs: SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' SLACK_MESSAGE: "${{ github.job }}: ${{ job.status }} @here" SLACK_USERNAME: "liquibot" - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_WEBHOOK: ${{ secrets.SNYK_LIQUIBASE_SLACK_WEBHOOK }} MSG_MINIMAL: actions url SLACK_ICON_EMOJI: ':liquibase:' SLACK_LINK_NAMES: true diff --git a/liquibase-core/pom.xml b/liquibase-core/pom.xml index 9755ec4aaf5..0ba213325d6 100644 --- a/liquibase-core/pom.xml +++ b/liquibase-core/pom.xml @@ -57,13 +57,13 @@ 1.3 test - + org.osgi org.osgi.core provided - 4.3.1 - + 5.0.0 + org.springframework @@ -155,10 +155,11 @@ org.apache.felix maven-bundle-plugin - 3.3.0 + 5.1.2 org.liquibase.core + liquibase.osgi.Activator javax.activation*;resolution:=optional, javax.servlet.*;version="[2.6,4)";resolution:=optional, @@ -167,6 +168,66 @@ org.yaml.snakeyaml.*, *;resolution:=optional + + osgi.serviceloader; osgi.serviceloader=liquibase.serializer.ChangeLogSerializer, + osgi.serviceloader; osgi.serviceloader=liquibase.parser.NamespaceDetails, + osgi.serviceloader; osgi.serviceloader=liquibase.database.Database, + osgi.serviceloader; osgi.serviceloader=liquibase.change.Change, + osgi.serviceloader; osgi.serviceloader=liquibase.database.DatabaseConnection, + osgi.serviceloader; osgi.serviceloader=liquibase.precondition.Precondition, + osgi.serviceloader; osgi.serviceloader=liquibase.serializer.SnapshotSerializer, + osgi.serviceloader; osgi.serviceloader=liquibase.configuration.AutoloadedConfigurations, + osgi.serviceloader; osgi.serviceloader=liquibase.diff.DiffGenerator, + osgi.serviceloader; osgi.serviceloader=liquibase.lockservice.LockService, + osgi.serviceloader; osgi.serviceloader=liquibase.changelog.ChangeLogHistoryService, + osgi.serviceloader; osgi.serviceloader=liquibase.datatype.LiquibaseDataType, + osgi.serviceloader; osgi.serviceloader=liquibase.configuration.ConfigurationValueProvider, + osgi.serviceloader; osgi.serviceloader=liquibase.logging.LogService, + osgi.serviceloader; osgi.serviceloader=liquibase.snapshot.SnapshotGenerator, + osgi.serviceloader; osgi.serviceloader=liquibase.parser.ChangeLogParser, + osgi.serviceloader; osgi.serviceloader=liquibase.servicelocator.ServiceLocator, + osgi.serviceloader; osgi.serviceloader=liquibase.diff.compare.DatabaseObjectComparator, + osgi.serviceloader; osgi.serviceloader=liquibase.command.LiquibaseCommand, + osgi.serviceloader; osgi.serviceloader=liquibase.license.LicenseService, + osgi.serviceloader; osgi.serviceloader=liquibase.diff.output.changelog.ChangeGenerator, + osgi.serviceloader; osgi.serviceloader=liquibase.executor.Executor, + osgi.serviceloader; osgi.serviceloader=liquibase.structure.DatabaseObject, + osgi.serviceloader; osgi.serviceloader=liquibase.parser.SnapshotParser, + osgi.serviceloader; osgi.serviceloader=liquibase.hub.HubService, + osgi.serviceloader; osgi.serviceloader=liquibase.command.CommandStep, + osgi.serviceloader; osgi.serviceloader=liquibase.sqlgenerator.SqlGenerator + + + osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)", + osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)", + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.serializer.ChangeLogSerializer)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.NamespaceDetails)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.database.Database)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.change.Change)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.database.DatabaseConnection)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.precondition.Precondition)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.serializer.SnapshotSerializer)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.configuration.AutoloadedConfigurations)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.DiffGenerator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.lockservice.LockService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.changelog.ChangeLogHistoryService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.datatype.LiquibaseDataType)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.configuration.ConfigurationValueProvider)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.logging.LogService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.snapshot.SnapshotGenerator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.ChangeLogParser)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.servicelocator.ServiceLocator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.compare.DatabaseObjectComparator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.command.LiquibaseCommand)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.license.LicenseService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.output.changelog.ChangeGenerator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.executor.Executor)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.structure.DatabaseObject)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.SnapshotParser)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.hub.HubService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.command.CommandStep)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.sqlgenerator.SqlGenerator)"; cardinality:=multiple + diff --git a/liquibase-core/src/main/java/liquibase/Scope.java b/liquibase-core/src/main/java/liquibase/Scope.java index 25a5aa3adcb..0910d57134c 100644 --- a/liquibase-core/src/main/java/liquibase/Scope.java +++ b/liquibase-core/src/main/java/liquibase/Scope.java @@ -11,6 +11,7 @@ import liquibase.logging.Logger; import liquibase.logging.core.JavaLogService; import liquibase.logging.core.LogServiceFactory; +import liquibase.osgi.Activator; import liquibase.resource.ClassLoaderResourceAccessor; import liquibase.resource.ResourceAccessor; import liquibase.servicelocator.ServiceLocator; @@ -55,6 +56,7 @@ public enum Attr { fileEncoding, databaseChangeLog, changeSet, + osgiPlatform } private static ScopeManager scopeManager; @@ -95,6 +97,7 @@ public static Scope getCurrentScope() { } rootScope.values.put(Attr.serviceLocator.name(), serviceLocator); + rootScope.values.put(Attr.osgiPlatform.name(), Activator.OSGIContainerChecker.isOsgiPlatform()); } return scopeManager.getCurrentScope(); } diff --git a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java index c236ab86c12..04bd19a2891 100755 --- a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java @@ -3,15 +3,19 @@ import liquibase.change.*; import liquibase.database.Database; import liquibase.database.core.DB2Database; -import liquibase.database.core.SQLiteDatabase; +import liquibase.database.core.PostgresDatabase; import liquibase.database.core.SQLiteDatabase.AlterTableVisitor; -import liquibase.exception.DatabaseException; +import liquibase.datatype.DataTypeFactory; +import liquibase.datatype.LiquibaseDataType; +import liquibase.datatype.core.BooleanType; +import liquibase.statement.DatabaseFunction; import liquibase.statement.SqlStatement; import liquibase.statement.core.ReorganizeTableStatement; import liquibase.statement.core.SetNullableStatement; import liquibase.statement.core.UpdateStatement; import liquibase.structure.core.Column; import liquibase.structure.core.Index; +import liquibase.util.BooleanUtil; import liquibase.util.StringUtil; import java.util.ArrayList; @@ -135,9 +139,41 @@ public void setConstraintName(String constraintName) { public SqlStatement[] generateStatements(Database database) { List statements = new ArrayList<>(); - if (defaultNullValue != null) { + if (defaultNullValue != null && !defaultNullValue.equalsIgnoreCase("null")) { + final String columnDataType = this.getColumnDataType(); + + Object finalDefaultNullValue = defaultNullValue; + if (columnDataType != null) { + final LiquibaseDataType datatype = DataTypeFactory.getInstance().fromDescription(columnDataType, database); + if (datatype instanceof BooleanType) { + //need to detect a boolean or bit type and handle it correctly sometimes or it is not converted to the correct datatype + finalDefaultNullValue = datatype.objectToSql(finalDefaultNullValue, database); + if (finalDefaultNullValue.equals("0")) { + finalDefaultNullValue = 0; + } else if (finalDefaultNullValue.equals("1")) { + finalDefaultNullValue = 1; + } + + if (columnDataType.toLowerCase().contains("bit")) { + if (BooleanUtil.parseBoolean(finalDefaultNullValue.toString())) { + finalDefaultNullValue = 1; + } else { + finalDefaultNullValue = 0; + } + } + + if (database instanceof PostgresDatabase) { + if (finalDefaultNullValue.equals(0)) { + finalDefaultNullValue = new DatabaseFunction( "B'0'"); + } else if (finalDefaultNullValue.equals(1)) { + finalDefaultNullValue = new DatabaseFunction( "B'1'"); + } + } + } + } + statements.add(new UpdateStatement(getCatalogName(), getSchemaName(), getTableName()) - .addNewColumnValue(getColumnName(), defaultNullValue) + .addNewColumnValue(getColumnName(), finalDefaultNullValue) .setWhereClause(database.escapeObjectName(getColumnName(), Column.class) + " IS NULL")); } diff --git a/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java b/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java index 1f4e4f94d9a..f86c0b68025 100644 --- a/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java +++ b/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java @@ -14,6 +14,7 @@ import liquibase.util.ObjectUtil; import java.util.*; +import liquibase.util.OsgiUtil; /** * Adapts CustomChange implementations to the standard change system used by Liquibase. @@ -69,16 +70,21 @@ public CustomChangeWrapper setClass(String className) throws CustomChangeExcepti return this; } this.className = className; - try { + try { + Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class); + if (Boolean.TRUE.equals(osgiPlatform)) { + customChange = (CustomChange)OsgiUtil.loadClass(className).getConstructor().newInstance(); + } else { + try { + customChange = (CustomChange) Class.forName(className, true, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance(); + } catch (ClassCastException e) { //fails in Ant in particular try { - customChange = (CustomChange) Class.forName(className, true, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance(); - } catch (ClassCastException e) { //fails in Ant in particular - try { - customChange = (CustomChange) Thread.currentThread().getContextClassLoader().loadClass(className).getConstructor().newInstance(); - } catch (ClassNotFoundException e1) { - customChange = (CustomChange) Class.forName(className).getConstructor().newInstance(); - } + customChange = (CustomChange) Thread.currentThread().getContextClassLoader().loadClass(className).getConstructor().newInstance(); + } catch (ClassNotFoundException e1) { + customChange = (CustomChange) Class.forName(className).getConstructor().newInstance(); } + } + } } catch (Exception e) { throw new CustomChangeException(e); } @@ -316,7 +322,12 @@ public void customLoadLogic(ParsedNode parsedNode, ResourceAccessor resourceAcce CustomChange customChange = null; try { - customChange = (CustomChange) Class.forName(className, false, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance(); + Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class); + if (Boolean.TRUE.equals(osgiPlatform)) { + customChange = (CustomChange)OsgiUtil.loadClass(className).getConstructor().newInstance(); + } else { + customChange = (CustomChange) Class.forName(className, false, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance(); + } } catch (Exception e) { throw new UnexpectedLiquibaseException(e); } diff --git a/liquibase-core/src/main/java/liquibase/osgi/Activator.java b/liquibase-core/src/main/java/liquibase/osgi/Activator.java new file mode 100644 index 00000000000..673a53cebe1 --- /dev/null +++ b/liquibase-core/src/main/java/liquibase/osgi/Activator.java @@ -0,0 +1,108 @@ +package liquibase.osgi; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import liquibase.osgi.Activator.LiquibaseBundle; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; + +public class Activator implements BundleActivator, BundleTrackerCustomizer { + + private static final String LIQUIBASE_CUSTOM_CHANGE_WRAPPER_PACKAGES = "Liquibase-Custom-Change-Packages"; + private BundleTracker bundleTracker; + private static final List liquibaseBundles = new CopyOnWriteArrayList<>(); + + @Override + public void start(final BundleContext bc) throws Exception { + OSGIContainerChecker.osgiPlatform(); + bundleTracker = new BundleTracker<>(bc, Bundle.ACTIVE, this); + bundleTracker.open(); + } + + @Override + public void stop(BundleContext context) throws Exception { + bundleTracker.close(); + liquibaseBundles.clear(); + } + + public static List getLiquibaseBundles() { + return Collections.unmodifiableList(liquibaseBundles); + } + + @Override + public LiquibaseBundle addingBundle(Bundle bundle, BundleEvent event) { + if (bundle.getBundleId() == 0) { + return null; + } + String customWrapperPackages = (String) bundle.getHeaders().get(LIQUIBASE_CUSTOM_CHANGE_WRAPPER_PACKAGES); + if (customWrapperPackages != null) { + LiquibaseBundle lb = new LiquibaseBundle(bundle, customWrapperPackages); + liquibaseBundles.add(lb); + return lb; + } + return null; + } + + @Override + public void modifiedBundle(Bundle bundle, BundleEvent event, LiquibaseBundle liquibaseBundle) { + // nothing to do + } + + @Override + public void removedBundle(Bundle bundle, BundleEvent event, LiquibaseBundle liquibaseBundle) { + if (liquibaseBundle != null) { + liquibaseBundles.remove(liquibaseBundle); + } + } + + public static class LiquibaseBundle { + + private final Bundle bundle; + private final List allowedPackages; + + public LiquibaseBundle(Bundle bundle, String allowedPackages) { + if (bundle == null) { + throw new IllegalArgumentException("bundle cannot be empty"); + } + if (allowedPackages == null || allowedPackages.isEmpty()) { + throw new IllegalArgumentException("packages cannot be empty"); + } + this.bundle = bundle; + this.allowedPackages = Collections.unmodifiableList(Arrays.asList(allowedPackages.split(","))); + } + + public Bundle getBundle() { + return bundle; + } + + public boolean allowedAllPackages() { + return allowedPackages.size() == 1 + && "*".equals(allowedPackages.get(0)); + } + + public List getAllowedPackages() { + return allowedPackages; + } + + } + + public static class OSGIContainerChecker { + + private static volatile boolean osgiPlatform = false; + + public static boolean isOsgiPlatform() { + return osgiPlatform; + } + + static void osgiPlatform() { + osgiPlatform = true; + } + } + +} diff --git a/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java b/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java index 8b56d2ca485..de89b1868c8 100644 --- a/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java +++ b/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java @@ -1,6 +1,8 @@ package liquibase.util; import liquibase.Scope; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; import java.io.IOException; import java.io.InputStream; @@ -54,19 +56,37 @@ public static String getBuildNumber() { // the jar file. private static String getBuildInfo(String propertyId) { if (liquibaseBuildProperties == null) { - try { - liquibaseBuildProperties = new Properties(); - final Enumeration propertiesUrls = Scope.getCurrentScope().getClassLoader().getResources("liquibase.build.properties"); - while (propertiesUrls.hasMoreElements()) { - final URL url = propertiesUrls.nextElement(); - try (InputStream buildProperties = url.openStream()) { + Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class); + if (Boolean.TRUE.equals(osgiPlatform)) { + Bundle bundle = FrameworkUtil.getBundle(LiquibaseUtil.class); + URL propURL = bundle.getEntry("liquibase.build.properties"); + if (propURL == null) { + Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties"); + } else { + try (InputStream buildProperties = propURL.openStream()) { + liquibaseBuildProperties = new Properties(); if (buildProperties != null) { liquibaseBuildProperties.load(buildProperties); } + } catch (IOException e) { + Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties", e); } } - } catch (IOException e) { - Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties", e); + } else { + try { + liquibaseBuildProperties = new Properties(); + final Enumeration propertiesUrls = Scope.getCurrentScope().getClassLoader().getResources("liquibase.build.properties"); + while (propertiesUrls.hasMoreElements()) { + final URL url = propertiesUrls.nextElement(); + try (InputStream buildProperties = url.openStream()) { + if (buildProperties != null) { + liquibaseBuildProperties.load(buildProperties); + } + } + } + } catch (IOException e) { + Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties", e); + } } } diff --git a/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java b/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java new file mode 100644 index 00000000000..71a39077f77 --- /dev/null +++ b/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java @@ -0,0 +1,65 @@ +package liquibase.util; + +import java.util.List; +import java.util.stream.Collectors; +import liquibase.osgi.Activator; +import liquibase.osgi.Activator.LiquibaseBundle; + +public final class OsgiUtil { + + private OsgiUtil() { + } + + /** + * try to load a class under OSGI environment. It will try to load the class + * from all liquibase bundles registered via + * {@link Activator Activator} + * + * @param + * @param className name of class + * @return + * @throws ClassNotFoundException + */ + public static Class loadClass(String className) throws ClassNotFoundException { + List liquibaseBundles = Activator.getLiquibaseBundles(); + for (LiquibaseBundle lb : liquibaseBundles) { + try { + Class clazz = (Class) lb.getBundle().loadClass(className); + if (!isClassAllowed(lb, clazz)) { + throw new ClassNotFoundException("Class is not allowed to load, class:" + className + " bundles:" + + liquibaseBundles.stream().map(i -> i.getBundle().getSymbolicName()) + .collect(Collectors.joining(","))); + } + return clazz; + } catch (ClassNotFoundException ex) { + // nothing to do + } + } + throw new ClassNotFoundException("Cannot find class:" + className + " bundles:" + + liquibaseBundles.stream().map(i -> i.getBundle().getSymbolicName()) + .collect(Collectors.joining(","))); + } + + /** + * + * @param clazz + * @return true is a class is allowed + * @throws java.lang.ClassNotFoundException + */ + private static boolean isClassAllowed(LiquibaseBundle liquibaseBundle, Class clazz) { + if (liquibaseBundle.allowedAllPackages()) { + return true; + } + for (String allowedPackage : liquibaseBundle.getAllowedPackages()) { + Package pkg = clazz.getPackage(); + if (pkg != null) { + String pkgName = pkg.getName(); + if (allowedPackage.equals(pkgName) || allowedPackage.startsWith(pkgName + ".")) { + return true; + } + } + } + return false; + } + +} diff --git a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy index 69289e5573a..411389c361d 100644 --- a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy @@ -1,6 +1,9 @@ package liquibase.change.core import liquibase.change.StandardChangeTest +import liquibase.database.core.MySQLDatabase +import liquibase.statement.core.SetNullableStatement +import liquibase.statement.core.UpdateStatement public class AddNotNullConstraintChangeTest extends StandardChangeTest { @@ -28,4 +31,80 @@ public class AddNotNullConstraintChangeTest extends StandardChangeTest { reverses[0].getTableName() == "TABLE_NAME" reverses[0].getColumnName() == "COL_HERE" } -} \ No newline at end of file + + def should_generateStatements_add_update_statement_before_not_null_constraint() { + given: + def change = new AddNotNullConstraintChange() + change.setTableName("table_name") + change.setColumnName("column_name") + change.setColumnDataType("varchar(20)") + change.setDefaultNullValue("Hello World!") + + def database = new MySQLDatabase() + + when: + def output = change.generateStatements(database) + + then: + output.length == 2 + output[0] instanceof UpdateStatement + def update = (UpdateStatement) output[0] + update.getTableName() == "table_name" + update.getNewColumnValues().size() == 1 + update.getNewColumnValues().get("column_name") == "Hello World!" + update.getWhereClause() == "column_name IS NULL" + + output[1] instanceof SetNullableStatement + } + + def should_generateStatements_update_statement_handle_boolean_type() { + given: + def change = new AddNotNullConstraintChange() + change.setTableName("FOO") + change.setColumnName("BAR") + change.setColumnDataType("BOOLEAN") + change.setDefaultNullValue("false") + + def database = new MySQLDatabase() + + when: + def output = change.generateStatements(database) + + then: + output.length == 2 + output[0] instanceof UpdateStatement + def update = (UpdateStatement) output[0] + update.getTableName() == "FOO" + update.getNewColumnValues().size() == 1 + update.getNewColumnValues().get("BAR") == 0 + update.getWhereClause() == "BAR IS NULL" + + output[1] instanceof SetNullableStatement + } + + def should_generateStatements_update_statement_handle_bit_1_type() { + given: + def change = new AddNotNullConstraintChange() + change.setTableName("xxx") + change.setColumnName("col_name") + change.setColumnDataType("BIT(1)") + change.setDefaultNullValue("1") + + def database = new MySQLDatabase() + + when: + def output = change.generateStatements(database) + + then: + output.length == 2 + output[0] instanceof UpdateStatement + def update = (UpdateStatement) output[0] + update.getTableName() == "xxx" + update.getNewColumnValues().size() == 1 + update.getNewColumnValues().get("col_name") == 1 + update.getWhereClause() == "col_name IS NULL" + + output[1] instanceof SetNullableStatement + } + +}