Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>liquibase</groupId>
<artifactId>netezza</artifactId>
<version>5.0.2</version>
<version>5.0.3</version>
<name>Liquibase Netezza Database Integration</name>
<description>Liquibase extension for Netezza database</description>
<url>http://www.liquibase.org</url>
Expand Down Expand Up @@ -139,7 +139,7 @@
<configuration>
<archive>
<manifestEntries>
<Liquibase-Package>liquibase.ext.netezza.database,liquibase.ext.netezza.snapshot</Liquibase-Package>
<Liquibase-Package>liquibase.ext.netezza.sqlgenerator,liquibase.ext.netezza.datatype,liquibase.ext.netezza.change,liquibase.ext.netezza.database,liquibase.ext.netezza.snapshot,liquibase.ext.netezza.statement,liquibase.ext.netezza.diff</Liquibase-Package>
</manifestEntries>
<manifest>
<addClasspath>true</addClasspath>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package liquibase.ext.netezza.change;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't there be a copyright?


import liquibase.change.DatabaseChange;
import liquibase.change.core.ModifyDataTypeChange;
import liquibase.database.Database;
import liquibase.database.core.DB2Database;
import liquibase.ext.netezza.statement.ModifyColumnDataTypeStatementNetezza;
import liquibase.statement.SqlStatement;
import liquibase.statement.core.ModifyDataTypeStatement;
import liquibase.statement.core.ReorganizeTableStatement;

/**
* Netezza only allows changing length(by incrementing but not decrementing) and precision(by incrementing but not decrementing) of the data type.
* So we need to override the default implementation of ModifyDataTypeChange to generate the correct SQL
* for Netezza. If the change is not safe, we will throw an exception to prevent the change from being executed.
* This class is used to mark the change as destructive, to make it different from the default implementation of ModifyDataTypeChange which is not destructive.
*/
@DatabaseChange(
name = "modifyDataTypeDestructive",
description = "Modify the data type of a column, by recreating",
priority = 5,
appliesTo = {"column"}
)
public class ModifyDataTypeChangeDestructiveNetezza extends ModifyDataTypeChange {

@Override
public SqlStatement[] generateStatements(Database database) {
ModifyColumnDataTypeStatementNetezza modifyDataTypeStatement = new ModifyColumnDataTypeStatementNetezza(this.getCatalogName(), this.getSchemaName(), this.getTableName(), this.getColumnName(), this.getNewDataType());
return new SqlStatement[] {modifyDataTypeStatement};
}

@Override
public String getCatalogName() {
return super.getCatalogName();
}

@Override
public String getSchemaName() {
return super.getSchemaName();
}

@Override
public String getTableName() {
return super.getTableName();
}

@Override
public String getColumnName() {
return super.getColumnName();
}

@Override
public String getNewDataType() {
return super.getNewDataType();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package liquibase.ext.netezza.datatype;

import liquibase.database.Database;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.core.NumberType;
import liquibase.ext.netezza.database.NetezzaDatabase;

public class NetezzaNumberType extends NumberType {
public int getPriority() {
return 5;
}

public boolean supports(Database database) {
return database instanceof NetezzaDatabase;
}

public DatabaseDataType toDatabaseDataType(Database database) {
return new DatabaseDataType("NUMERIC", this.getParameters());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package liquibase.ext.netezza.diff.output.changelog;

import liquibase.change.Change;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.diff.Difference;
import liquibase.diff.ObjectDifferences;
import liquibase.diff.output.DiffOutputControl;
import liquibase.diff.output.changelog.core.ChangedColumnChangeGenerator;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.ext.netezza.change.ModifyDataTypeChangeDestructiveNetezza;
import liquibase.ext.netezza.database.NetezzaDatabase;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Column;
import liquibase.structure.core.DataType;
import liquibase.structure.core.Schema;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ChangedColumnChangeGeneratorNetezza extends ChangedColumnChangeGenerator {

public static final Pattern LENGTH_PATTERN = Pattern.compile("VARCHAR\\((\\d+)\\s*(?:BYTE)?\\)");


@Override
public int getPriority(Class<? extends DatabaseObject> objectType, Database database) {
if (database instanceof NetezzaDatabase) {
return Column.class.isAssignableFrom(objectType) ? 2 : -1;
} else {
return PRIORITY_NONE;
}
}

@Override
protected void handleTypeDifferences(
Column column,
ObjectDifferences differences,
DiffOutputControl control,
List<Change> changes,
Database referenceDatabase,
Database comparisonDatabase
) {
Difference typeDifference = differences.getDifference("type");
if (typeDifference == null) {
return;
}
String table = column.getRelation().getName();
String columnName = column.getName();
Schema schema = column.getRelation().getSchema();
boolean isSafe = isSafeExpansion(
typeDifference.getComparedValue().toString(),
typeDifference.getReferenceValue().toString()
);
if (!isSafe && isDistributionKey(referenceDatabase, schema, table, columnName)) {
throw new UnexpectedLiquibaseException(
"Column '" + columnName + "' is a distribution key. " +
"Full table rebuild required."
);
}

if (!isSafe && isIndexed(referenceDatabase, schema, table, columnName)) {
throw new UnexpectedLiquibaseException(
String.format(
"Column '%s.%s.%s' is indexed and cannot be safely modified in Netezza.",
schema, table, columnName
)
);
}
if (isSafe) {
super.handleTypeDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase);
} else {
ModifyDataTypeChangeDestructiveNetezza modifyDataTypeChangeDestructiveNetezza = new ModifyDataTypeChangeDestructiveNetezza();
modifyDataTypeChangeDestructiveNetezza.setSchemaName(schema.getName());
modifyDataTypeChangeDestructiveNetezza.setTableName(table);
modifyDataTypeChangeDestructiveNetezza.setColumnName(columnName);
DataType referenceType = (DataType)typeDifference.getReferenceValue();
modifyDataTypeChangeDestructiveNetezza.setNewDataType(referenceType.toString());
changes.add(modifyDataTypeChangeDestructiveNetezza);
}
}

private boolean isSafeExpansion(String oldType, String newType) {
if (oldType == null) return false;

oldType = oldType.toUpperCase();
newType = newType.toUpperCase();

// VARCHAR(n) -> VARCHAR(m) where m >= n
if (oldType.startsWith("VARCHAR") && newType.startsWith("VARCHAR")) {
int oldLen = extractSingleNumber(oldType);
int newLen = extractSingleNumber(newType);
return newLen >= oldLen;
}

return false;
}

private int extractSingleNumber(String type) {
Matcher m = LENGTH_PATTERN.matcher(type);
return m.find() ? Integer.parseInt(m.group(1)) : 0;
}

private boolean isDistributionKey(Database referenceDatabase, Schema schema, String table, String columnName) {
String sql =
"SELECT 1\n"
+ "FROM _v_table_dist_map dm\n"
+ "WHERE dm.OWNER = ?\n"
+ " AND dm.TABLENAME = ?\n"
+ " AND dm.attname = ?";
try (PreparedStatement ps = ((JdbcConnection) referenceDatabase.getConnection()).prepareStatement(sql)) {
ps.setString(1, schema.getName().toUpperCase());
ps.setString(2, table.toUpperCase());
ps.setString(3, columnName.toUpperCase());

try (ResultSet rs = ps.executeQuery()) {
return rs.next();
}

} catch (Exception e) {
throw new RuntimeException(
"Failed to check distribution key for " + table + "." + columnName, e
);
}
}

protected boolean isIndexed(Database database, Schema schema, String table, String column) {
String sql =
"SELECT 1\n"
+ "FROM DEFINITION_SCHEMA._V_RELATION_KEYDATA\n"
+ "WHERE \"SCHEMA\" = ?\n"
+ " AND RELATION = ?\n"
+ " AND ATTNAME = ?";

try (PreparedStatement ps =
((JdbcConnection) database.getConnection()).prepareStatement(sql)) {

ps.setString(1, table.toUpperCase());
ps.setString(2, schema.getName().toUpperCase());
ps.setString(3, column.toUpperCase());

try (ResultSet rs = ps.executeQuery()) {
return rs.next();
}

} catch (Exception e) {
throw new RuntimeException(
"Failed to check index usage for " + schema + "." + table + "." + column, e
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package liquibase.ext.netezza.sqlgenerator;

import liquibase.database.Database;
import liquibase.ext.netezza.database.NetezzaDatabase;
import liquibase.sql.Sql;
import liquibase.sql.UnparsedSql;
import liquibase.sqlgenerator.SqlGeneratorChain;
import liquibase.sqlgenerator.core.DropColumnGenerator;
import liquibase.statement.core.DropColumnStatement;
import liquibase.structure.DatabaseObject;

public class DropColumnGeneratorNetezza extends DropColumnGenerator {
public int getPriority() {
return 5;
}

public boolean supports(DropColumnStatement statement, Database database) {
return database instanceof NetezzaDatabase;
}

public Sql[] generateSql(DropColumnStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
Sql[] sqls = new Sql[1];
String tableName = database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName());
sqls[0] = new UnparsedSql(
"ALTER TABLE " + tableName + " DROP " + database.escapeColumnName(
statement.getCatalogName(),
statement.getSchemaName(),
statement.getTableName(),
statement.getColumnName()
) + " RESTRICT", new DatabaseObject[0]
);
return sqls;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package liquibase.ext.netezza.sqlgenerator;

import liquibase.database.Database;
import liquibase.datatype.DataTypeFactory;
import liquibase.datatype.DatabaseDataType;
import liquibase.exception.Warnings;
import liquibase.ext.netezza.database.NetezzaDatabase;
import liquibase.ext.netezza.statement.ModifyColumnDataTypeStatementNetezza;
import liquibase.sql.Sql;
import liquibase.sql.UnparsedSql;
import liquibase.sqlgenerator.SqlGeneratorChain;
import liquibase.sqlgenerator.core.ModifyDataTypeGenerator;
import liquibase.statement.core.ModifyDataTypeStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Relation;

/**
* Netezza only allows changing length(by incrementing but not decrementing) and precision(by incrementing but not decrementing) of the data type.
* So we need to override the default implementation of ModifyDataTypeGenerator to generate the correct SQL for Netezza.
*/
public class ModifyDataTypeGeneratorNetezza extends ModifyDataTypeGenerator {
public ModifyDataTypeGeneratorNetezza() {
super();
}

@Override
public boolean supports(ModifyDataTypeStatement statement, Database database) {
if (statement instanceof ModifyColumnDataTypeStatementNetezza) {
return database instanceof NetezzaDatabase;
} else {
return false;
}
}

@Override
public Sql[] generateSql(ModifyDataTypeStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
String table = database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName());;
String column = database.escapeColumnName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName(), statement.getColumnName());
DatabaseDataType newDataType = DataTypeFactory.getInstance().fromDescription(statement.getNewDataType(), database).toDatabaseDataType(database);
// fallback: recreate column
String tempColumn = statement.getColumnName() + "_TMP_" + System.currentTimeMillis();
Relation affectedTable = this.getAffectedTable(statement);
return new Sql[] {
new UnparsedSql(String.format(
"ALTER TABLE %s ADD COLUMN %s %s",
table, tempColumn, newDataType
), affectedTable
),
new UnparsedSql(String.format(
"UPDATE %s SET %s = %s",
table, tempColumn, column
), affectedTable
),
new UnparsedSql(String.format(
"ALTER TABLE %s DROP COLUMN %s RESTRICT",
table, column
), affectedTable
),
new UnparsedSql(String.format(
"ALTER TABLE %s RENAME COLUMN %s TO %s",
table, tempColumn, column
), affectedTable
)
};

}

@Override
public Warnings warn(ModifyDataTypeStatement modifyDataTypeStatement, Database database, SqlGeneratorChain sqlGeneratorChain) {
return new Warnings().addWarning(
"Changing data type " +
" to " + modifyDataTypeStatement.getNewDataType() + " may cause data loss. Netezza does not support shrinking data types nor changing the type itself. Migration will attempt to recreate the column, but it may fail if there are constraints or indexes on the column."
);
}


@Override
public int getPriority() {
return PRIORITY_DATABASE;
}
}
Loading
Loading