package er.extensions.migration;
import java.sql.SQLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.eoaccess.EOAdaptor;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOSQLExpression;
import com.webobjects.eoaccess.EOSchemaSynchronization;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.jdbcadaptor.JDBCAdaptor;
import er.extensions.jdbc.ERXJDBCUtilities;
import er.extensions.jdbc.ERXSQLHelper;
/**
* ERXMigrationColumn is conceptually equivalent to an EOAttribute in the
* ERXMigrationXxx model. To obtain an ERXMigrationColumn, call
* ERXMigrationTable.newXxxColumn(..).
*
* @author mschrag
*/
public class ERXMigrationColumn {
private static final Logger log = LoggerFactory.getLogger(ERXMigrationDatabase.class);
public static final String NULL_VALUE_TYPE = "___NULL_VALUE_TYPE___";
/**
* Constant for use with ERXMigrationTable.newXxxColumn AllowsNull columns.
*/
public static final boolean AllowsNull = true;
/**
* Constant for use with ERXMigrationTable.newXxxColumn NotNull columns.
*/
public static final boolean NotNull = false;
private ERXMigrationTable _table;
private String _name;
private int _jdbcType;
private int _width;
private int _precision;
private int _scale;
private boolean _allowsNull;
private Object _defaultValue;
private boolean _new;
private String _overrideValueType;
private String _overrideExternalType;
private boolean _primaryKey;
/**
* Constructs a new ERXMigrationColumn.
*
* @param table
* the parent table
* @param name
* the name of the column to create
* @param jdbcType
* the JDBC type of the column (see java.sql.Types)
* @param width
* the width of the column (or 0 for unspecified)
* @param precision
* the precision of the column (or 0 for unspecified)
* @param scale
* the scale of the column (or 0 for unspecified)
* @param allowsNull
* if true, the column will allow null values
* @param overrideValueType
* value type associated with the underlying attribute (or <code>null</code> for autoselect)
* @param defaultValue
* this will set the "Default" hint in the EOAttribute's userInfo
* dictionary (your plugin must support this)
*/
protected ERXMigrationColumn(ERXMigrationTable table, String name, int jdbcType, int width, int precision, int scale, boolean allowsNull, String overrideValueType, Object defaultValue) {
_table = table;
_name = name;
_jdbcType = jdbcType;
_width = width;
_precision = precision;
_scale = scale;
_allowsNull = allowsNull;
_overrideValueType = overrideValueType;
_defaultValue = defaultValue;
_new = true;
}
/**
* Returns the parent ERXMigrationTable of this column.
*
* @return the parent ERXMigrationTable of this column
*/
public ERXMigrationTable table() {
return _table;
}
/**
* Sets the name of this column. This does not perform a column rename
* operation.
*
* @param name
* the name of this column
*/
public void _setName(String name) {
_name = name;
}
/**
* Returns the name of this column.
*
* @return the name of this column
*/
public String name() {
return _name;
}
/**
* Sets the width of this column. This does not perform a column resize
* operation.
*
* @param width
* the width of this column
*/
public void _setWidth(int width) {
_width = width;
}
/**
* Returns the width of this column.
*
* @return the width of this column
*/
public int width() {
return _width;
}
/**
* Sets whether or not this column allows nulls. This does not perform a
* column change operation.
*
* @param allowsNull
* if true, this column allows nulls
*/
public void _setAllowsNull(boolean allowsNull) {
_allowsNull = allowsNull;
}
/**
* Returns the width of this column.
*
* @return the width of this column
*/
public boolean allowsNull() {
return _allowsNull;
}
/**
* Sets the precision of this column. This does not perform a column change
* operation.
*
* @param precision
* the precision of this column
*/
public void _setPrecision(int precision) {
_precision = precision;
}
/**
* Returns the precision of this column.
*
* @return the precision of this column
*/
public int precision() {
return _precision;
}
/**
* Sets the scale of this column. This does not perform a column change
* operation.
*
* @param scale
* the scale of this column
*/
public void _setScale(int scale) {
_scale = scale;
}
/**
* Returns the scale of this column.
*
* @return the scale of this column
*/
public int scale() {
return _scale;
}
/**
* Overrides the external type of this column.
*
* @param overrideExternalType
* the external type to override
*/
public void _setOverrideExternalType(String overrideExternalType) {
_overrideExternalType = overrideExternalType;
}
/**
* Returns the external type of this column (or null if there is no
* override).
*
* @return the external type of this column
*/
public String overrideExternalType() {
return _overrideExternalType;
}
/**
* Sets the value type for the underlying attribute for this column.
*
* @param overrideValueType
* the value type for the underlying attribute for this column
*/
public void _setOverrideValueType(String overrideValueType) {
_overrideValueType = overrideValueType;
}
/**
* Returns the value type associated with the underlying attribute (or null
* to have this autoselected).
*
* @return the value type associated with the underlying attribute
*/
public String overrideValueType() {
return _overrideValueType;
}
/**
* Sets the default value of this column.
*
* @param defaultValue
* the default value of this column
*/
public void setDefaultValue(Object defaultValue) {
_defaultValue = defaultValue;
}
/**
* Returns the default value of this column.
*
* @return the default value of this column
*/
public Object defaultValue() {
return _defaultValue;
}
/**
* Sets whether or not this column is a primary key.
*
* @param primaryKey whether or not this column is a primary key
*/
public void _setPrimaryKey(boolean primaryKey) {
_primaryKey = primaryKey;
}
/**
* Returns whether or not this column is a primary key (note this
* is only valid if you told migrations that this column is a
* primary key).
*
* @return whether or not this column is a primary key
*/
public boolean isPrimaryKey() {
return _primaryKey;
}
/**
* Returns true if this column has not yet been created in the database.
*
* @return if this column has not yet been created in the database
*/
public boolean isNew() {
return _new;
}
/**
* Sets whether or not this column has been created in the database.
*
* @param isNew
* if true, the column has been created
*/
public void _setNew(boolean isNew) {
_new = isNew;
}
/**
* Returns an EOAttribute with all of its fields filled in based on the
* properties of this ERXMigrationColumn. The attribute is attached to a
* table._blankEntity().
*
* @return an EOAttribute with all of its fields filled in
*/
public EOAttribute _newAttribute() {
return _newAttribute(_table._blankEntity());
}
/**
* Returns an EOAttribute with all of its fields filled in based on the
* properties of this ERXMigrationColumn. The attribute is attached to the
* given entity.
*
* @param entity
* the entity to add the attribute to
* @return an EOAttribute with all of its fields filled in
*/
@SuppressWarnings("unchecked")
public EOAttribute _newAttribute(EOEntity entity) {
EOAdaptor eoAdaptor = _table.database().adaptor();
// MS: Hack to make Memory adaptor migrations "work"
if (!(eoAdaptor instanceof JDBCAdaptor)) {
EOAttribute nonJdbcAttribute = new EOAttribute();
nonJdbcAttribute.setName(_name);
nonJdbcAttribute.setColumnName(_name);
nonJdbcAttribute.setExternalType("nonJdbcAttribute");
entity.addAttribute(nonJdbcAttribute);
return nonJdbcAttribute;
}
JDBCAdaptor adaptor = (JDBCAdaptor)_table.database().adaptor();
ERXSQLHelper sqlHelper = ERXSQLHelper.newSQLHelper(adaptor);
String externalType = sqlHelper.externalTypeForJDBCType(adaptor, _jdbcType);
if (externalType == null) {
externalType = "IF_YOU_ARE_SEEING_THIS_SOMETHING_WENT_WRONG_WITH_EXTERNAL_TYPES";
}
EOAttribute attribute = adaptor.createAttribute(_name, _name, _jdbcType, externalType, _precision, _scale, _allowsNull ? 1 : 0);
if (_width > 0) {
attribute.setWidth(_width);
}
if (_defaultValue != null) {
NSDictionary userInfo = attribute.userInfo();
NSMutableDictionary mutableUserInfo;
if (userInfo == null) {
mutableUserInfo = new NSMutableDictionary();
}
else {
mutableUserInfo = userInfo.mutableClone();
}
mutableUserInfo.setObjectForKey(_defaultValue, "default");
attribute.setUserInfo(mutableUserInfo);
}
if (_overrideValueType != null) {
if (ERXMigrationColumn.NULL_VALUE_TYPE.equals(_overrideValueType)) {
attribute.setValueType(null);
}
else {
attribute.setValueType(_overrideValueType);
}
if (sqlHelper.reassignExternalTypeForValueTypeOverride(attribute)) {
adaptor.assignExternalTypeForAttribute(attribute);
}
}
if (_overrideExternalType != null) {
attribute.setExternalType(_overrideExternalType);
}
entity.addAttribute(attribute);
return attribute;
}
/**
* Returns an array of EOSQLExpressions for creating this column.
*
* @return an array of EOSQLExpressions for creating this column
*/
@SuppressWarnings("unchecked")
public NSArray<EOSQLExpression> _createExpressions() {
EOSchemaSynchronization schemaSynchronization = _table.database().synchronizationFactory();
NSArray<EOSQLExpression> expressions = schemaSynchronization.statementsToInsertColumnForAttribute(_newAttribute(), NSDictionary.EmptyDictionary);
ERXMigrationDatabase._ensureNotEmpty(expressions, "add column", true);
return expressions;
}
/**
* Executes the SQL operations to create this column.
*
* @throws SQLException
* if the creation fails
*/
public void create() throws SQLException {
if (_new) {
ERXJDBCUtilities.executeUpdateScript(_table.database().adaptorChannel(), ERXMigrationDatabase._stringsForExpressions(_createExpressions()));
_new = false;
}
else {
log.warn("You called .create() on the column '{}', but it was already created.", _name);
}
}
/**
* Returns an array of EOSQLExpressions for deleting this column.
*
* @return an array of EOSQLExpressions for deleting this column
*/
@SuppressWarnings("unchecked")
public NSArray<EOSQLExpression> _deleteExpressions() {
EOSchemaSynchronization schemaSynchronization = _table.database().synchronizationFactory();
NSArray<EOSQLExpression> expressions = schemaSynchronization.statementsToDeleteColumnNamed(name(), _table.name(), NSDictionary.EmptyDictionary);
ERXMigrationDatabase._ensureNotEmpty(expressions, "delete column", true);
return expressions;
}
/**
* Executes the SQL operations to delete this column.
*
* @throws SQLException
* if the delete fails
*/
public void delete() throws SQLException {
ERXJDBCUtilities.executeUpdateScript(_table.database().adaptorChannel(), ERXMigrationDatabase._stringsForExpressions(_deleteExpressions()));
_table._columnDeleted(this);
}
/**
* Returns an array of EOSQLExpressions for renaming this column.
*
* @param newName
* the new name of this column
* @return an array of EOSQLExpressions for renaming this column
*/
@SuppressWarnings("unchecked")
public NSArray<EOSQLExpression> _renameToExpressions(String newName) {
EOSchemaSynchronization schemaSynchronization = _table.database().synchronizationFactory();
NSArray<EOSQLExpression> expressions = schemaSynchronization.statementsToRenameColumnNamed(name(), _table.name(), newName, NSDictionary.EmptyDictionary);
ERXMigrationDatabase._ensureNotEmpty(expressions, "rename column", true);
_setName(newName);
return expressions;
}
/**
* Executes the SQL operations to rename this column.
*
* @param newName
* the new name of this column
* @throws SQLException
* if the rename fails
*/
public void renameTo(String newName) throws SQLException {
ERXJDBCUtilities.executeUpdateScript(_table.database().adaptorChannel(), ERXMigrationDatabase._stringsForExpressions(_renameToExpressions(newName)));
}
/**
* Changes the "allows null" state of this column.
*
* @param allowsNull
* if true, this column allows nulls
* @throws SQLException
* if the change fails
*/
@SuppressWarnings("unchecked")
public void setAllowsNull(boolean allowsNull) throws SQLException {
EOSchemaSynchronization schemaSynchronization = _table.database().synchronizationFactory();
NSArray<EOSQLExpression> expressions = schemaSynchronization.statementsToModifyColumnNullRule(name(), _table.name(), allowsNull, NSDictionary.EmptyDictionary);
ERXMigrationDatabase._ensureNotEmpty(expressions, "modify allows null", true);
ERXJDBCUtilities.executeUpdateScript(_table.database().adaptorChannel(), ERXMigrationDatabase._stringsForExpressions(expressions));
}
/**
* Changes the data type of this column.
*
* @param jdbcType
* the new JDBC type of the column (see java.sql.Types)
* @param scale
* the new scale
* @param precision
* the new precision
* @param width
* the new width
* @param options
* the options to use for conversion (or null)
* @throws SQLException
* if the change fails
*/
@SuppressWarnings("unchecked")
public void setDataType(int jdbcType, int scale, int precision, int width, NSDictionary options) throws SQLException {
JDBCAdaptor adaptor = (JDBCAdaptor) _table.database().adaptor();
String externalType = ERXSQLHelper.newSQLHelper(adaptor).externalTypeForJDBCType(adaptor, jdbcType);
EOSchemaSynchronization schemaSynchronization = _table.database().synchronizationFactory();
NSArray<EOSQLExpression> expressions = schemaSynchronization.statementsToConvertColumnType(_name, _table.name(), null, new _ColumnType(externalType, scale, precision, width), options);
ERXMigrationDatabase._ensureNotEmpty(expressions, "convert column type", true);
ERXJDBCUtilities.executeUpdateScript(_table.database().adaptorChannel(), ERXMigrationDatabase._stringsForExpressions(expressions));
_jdbcType = jdbcType;
_scale = scale;
_precision = precision;
_width = width;
}
/**
* Changes the data type of this column to a type that has a width.
*
* @param jdbcType
* the new JDBC type of the column (see java.sql.Types)
* @param width
* the new width
* @param options
* the options to use for conversion (or null)
* @throws SQLException
* if the change fails
*/
public void setWidthType(int jdbcType, int width, NSDictionary options) throws SQLException {
setDataType(jdbcType, 0, 0, width, options);
}
/**
* Changes the data type of this column to a new numeric type.
*
* @param jdbcType
* the new JDBC type of the column (see java.sql.Types)
* @param scale
* the new scale
* @param precision
* the new precision
* @param options
* the options to use for conversion (or null)
* @throws SQLException
* if the change fails
*/
public void setNumericType(int jdbcType, int scale, int precision, NSDictionary options) throws SQLException {
setDataType(jdbcType, scale, precision, 0, options);
}
/**
* Implements EOSchemaSynchronization.ColumnTypes
*
* @author mschrag
*/
public static class _ColumnType implements EOSchemaSynchronization.ColumnTypes {
private String _name;
private int _scale;
private int _precision;
private int _width;
public _ColumnType(String name, int scale, int precision, int width) {
_name = name;
_scale = scale;
_precision = precision;
_width = width;
}
public String name() {
return _name;
}
public int precision() {
return _precision;
}
public int scale() {
return _scale;
}
public int width() {
return _width;
}
}
}