blob: 9ec28027a37532fb40675ca80178b00a24d3f32e [file] [log] [blame]
package org.apache.ddlutils.io;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Types;
import java.util.List;
import junit.framework.TestSuite;
import org.apache.commons.beanutils.DynaBean;
import org.apache.ddlutils.DdlUtilsException;
import org.apache.ddlutils.PlatformFactory;
import org.apache.ddlutils.PlatformInfo;
import org.apache.ddlutils.TestDatabaseWriterBase;
import org.apache.ddlutils.dynabean.SqlDynaBean;
import org.apache.ddlutils.dynabean.SqlDynaClass;
import org.apache.ddlutils.dynabean.SqlDynaProperty;
import org.apache.ddlutils.model.CascadeActionEnum;
import org.apache.ddlutils.model.CloneHelper;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.ForeignKey;
import org.apache.ddlutils.model.Index;
import org.apache.ddlutils.model.IndexColumn;
import org.apache.ddlutils.model.Reference;
import org.apache.ddlutils.model.Table;
import org.apache.ddlutils.model.TypeMap;
import org.apache.ddlutils.platform.DefaultValueHelper;
/**
* Base class for database roundtrip (creation & reconstruction from the database).
*
* @version $Revision: 289996 $
*/
public abstract class RoundtripTestBase extends TestDatabaseWriterBase
{
/**
* Creates the test suite for the given test class which must be a sub class of
* {@link RoundtripTestBase}. If the platform supports it, it will be tested
* with both delimited and undelimited identifiers.
*
* @param testedClass The tested class
* @return The tests
*/
protected static TestSuite getTests(Class testedClass)
{
if (!RoundtripTestBase.class.isAssignableFrom(testedClass) ||
Modifier.isAbstract(testedClass.getModifiers()))
{
throw new DdlUtilsException("Cannot create parameterized tests for class "+testedClass.getName());
}
TestSuite suite = new TestSuite();
try
{
Method[] methods = testedClass.getMethods();
PlatformInfo info = null;
RoundtripTestBase newTest;
for (int idx = 0; (methods != null) && (idx < methods.length); idx++)
{
if (methods[idx].getName().startsWith("test") &&
((methods[idx].getParameterTypes() == null) || (methods[idx].getParameterTypes().length == 0)))
{
newTest = (RoundtripTestBase)testedClass.newInstance();
newTest.setName(methods[idx].getName());
newTest.setUseDelimitedIdentifiers(false);
suite.addTest(newTest);
if (info == null)
{
info = PlatformFactory.createNewPlatformInstance(newTest.getDatabaseName()).getPlatformInfo();
}
if (info.isDelimitedIdentifiersSupported())
{
newTest = (RoundtripTestBase)testedClass.newInstance();
newTest.setName(methods[idx].getName());
newTest.setUseDelimitedIdentifiers(true);
suite.addTest(newTest);
}
}
}
}
catch (Exception ex)
{
throw new DdlUtilsException(ex);
}
return suite;
}
/** Whether to use delimited identifiers for the test. */
private boolean _useDelimitedIdentifiers;
/**
* Specifies whether the test shall use delimited identifiers.
*
* @param useDelimitedIdentifiers Whether to use delimited identifiers
*/
protected void setUseDelimitedIdentifiers(boolean useDelimitedIdentifiers)
{
_useDelimitedIdentifiers = useDelimitedIdentifiers;
}
/**
* {@inheritDoc}
*/
protected void setUp() throws Exception
{
super.setUp();
getPlatform().setDelimitedIdentifierModeOn(_useDelimitedIdentifiers);
}
/**
* Inserts a row into the designated table.
*
* @param tableName The name of the table (case insensitive)
* @param columnValues The values for the columns in order of definition
*/
protected void insertRow(String tableName, Object[] columnValues)
{
Table table = getModel().findTable(tableName);
DynaBean bean = getModel().createDynaBeanFor(table);
for (int idx = 0; (idx < table.getColumnCount()) && (idx < columnValues.length); idx++)
{
Column column = table.getColumn(idx);
bean.set(column.getName(), columnValues[idx]);
}
getPlatform().insert(getModel(), bean);
}
/**
* Updates the row in the designated table.
*
* @param tableName The name of the table (case insensitive)
* @param oldBean The bean representing the current row
* @param columnValues The values for the columns in order of definition
*/
protected void updateRow(String tableName, DynaBean oldBean, Object[] columnValues)
{
Table table = getModel().findTable(tableName);
DynaBean bean = getModel().createDynaBeanFor(table);
for (int idx = 0; (idx < table.getColumnCount()) && (idx < columnValues.length); idx++)
{
Column column = table.getColumn(idx);
bean.set(column.getName(), columnValues[idx]);
}
getPlatform().update(getModel(), oldBean, bean);
}
/**
* Deletes the specified row from the table.
*
* @param tableName The name of the table (case insensitive)
* @param pkColumnValues The values for the pk columns in order of definition
*/
protected void deleteRow(String tableName, Object[] pkColumnValues)
{
Table table = getModel().findTable(tableName);
DynaBean bean = getModel().createDynaBeanFor(table);
Column[] pkColumns = table.getPrimaryKeyColumns();
for (int idx = 0; (idx < pkColumns.length) && (idx < pkColumnValues.length); idx++)
{
bean.set(pkColumns[idx].getName(), pkColumnValues[idx]);
}
getPlatform().delete(getModel(), bean);
}
/**
* Returns a "SELECT * FROM [table name]" statement. It also takes
* delimited identifier mode into account if enabled.
*
* @param table The table
* @param orderColumn The column to order the rows by (can be <code>null</code>)
* @return The statement
*/
protected String getSelectQueryForAllString(Table table, String orderColumn)
{
StringBuffer query = new StringBuffer();
query.append("SELECT * FROM ");
if (getPlatform().isDelimitedIdentifierModeOn())
{
query.append(getPlatformInfo().getDelimiterToken());
}
query.append(table.getName());
if (getPlatform().isDelimitedIdentifierModeOn())
{
query.append(getPlatformInfo().getDelimiterToken());
}
if (orderColumn != null)
{
query.append(" ORDER BY ");
if (getPlatform().isDelimitedIdentifierModeOn())
{
query.append(getPlatformInfo().getDelimiterToken());
}
query.append(orderColumn);
if (getPlatform().isDelimitedIdentifierModeOn())
{
query.append(getPlatformInfo().getDelimiterToken());
}
}
return query.toString();
}
/**
* Retrieves all rows from the given table.
*
* @param tableName The table
* @return The rows
*/
protected List getRows(String tableName)
{
Table table = getModel().findTable(tableName, getPlatform().isDelimitedIdentifierModeOn());
return getPlatform().fetch(getModel(),
getSelectQueryForAllString(table, null),
new Table[] { table });
}
/**
* Retrieves all rows from the given table.
*
* @param tableName The table
* @param orderColumn The column to order the rows by
* @return The rows
*/
protected List getRows(String tableName, String orderColumn)
{
Table table = getModel().findTable(tableName, getPlatform().isDelimitedIdentifierModeOn());
return getPlatform().fetch(getModel(),
getSelectQueryForAllString(table, orderColumn),
new Table[] { table });
}
/**
* Returns the original model adjusted for type changes because of the native type mappings
* which when read back from the database will map to different types.
*
* @return The adjusted model
*/
protected Database getAdjustedModel()
{
Database model = new CloneHelper().clone(getModel());
for (int tableIdx = 0; tableIdx < model.getTableCount(); tableIdx++)
{
Table table = model.getTable(tableIdx);
for (int columnIdx = 0; columnIdx < table.getColumnCount(); columnIdx++)
{
Column column = table.getColumn(columnIdx);
int origType = column.getTypeCode();
int targetType = getPlatformInfo().getTargetJdbcType(origType);
// we adjust the column types if the native type would back-map to a
// different jdbc type
if (targetType != origType)
{
column.setTypeCode(targetType);
// we should also adapt the default value
if (column.getDefaultValue() != null)
{
DefaultValueHelper helper = getPlatform().getSqlBuilder().getDefaultValueHelper();
column.setDefaultValue(helper.convert(column.getDefaultValue(), origType, targetType));
}
}
// we also promote the default size if the column has no size
// spec of its own
if ((column.getSize() == null) && getPlatformInfo().hasSize(targetType))
{
Integer defaultSize = getPlatformInfo().getDefaultSize(targetType);
if (defaultSize != null)
{
column.setSize(defaultSize.toString());
}
}
// finally the platform might return a synthetic default value if the column
// is a primary key column
if (getPlatformInfo().isSyntheticDefaultValueForRequiredReturned() &&
(column.getDefaultValue() == null) && column.isRequired() && !column.isAutoIncrement())
{
switch (column.getTypeCode())
{
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
column.setDefaultValue("0");
break;
case Types.REAL:
case Types.FLOAT:
case Types.DOUBLE:
column.setDefaultValue("0.0");
break;
case Types.BIT:
column.setDefaultValue("false");
break;
default:
column.setDefaultValue("");
break;
}
}
if (column.isPrimaryKey() && getPlatformInfo().isPrimaryKeyColumnAutomaticallyRequired())
{
column.setRequired(true);
}
if (column.isAutoIncrement() && getPlatformInfo().isIdentityColumnAutomaticallyRequired())
{
column.setRequired(true);
}
}
// we also add the default names to foreign keys that are initially unnamed
for (int fkIdx = 0; fkIdx < table.getForeignKeyCount(); fkIdx++)
{
ForeignKey fk = table.getForeignKey(fkIdx);
if (fk.getName() == null)
{
fk.setName(getPlatform().getSqlBuilder().getForeignKeyName(table, fk));
}
}
}
return model;
}
/**
* Compares the specified attribute value of the given bean with the expected object.
*
* @param expected The expected object
* @param bean The bean
* @param attrName The attribute name
*/
protected void assertEquals(Object expected, Object bean, String attrName)
{
DynaBean dynaBean = (DynaBean)bean;
Object value = dynaBean.get(attrName);
if ((value instanceof byte[]) && !(expected instanceof byte[]) && (dynaBean instanceof SqlDynaBean))
{
SqlDynaClass dynaClass = (SqlDynaClass)((SqlDynaBean)dynaBean).getDynaClass();
Column column = ((SqlDynaProperty)dynaClass.getDynaProperty(attrName)).getColumn();
if (TypeMap.isBinaryType(column.getTypeCode()))
{
value = new BinaryObjectsHelper().deserialize((byte[])value);
}
}
if (expected == null)
{
assertNull(value);
}
else
{
assertEquals(expected, value);
}
}
/**
* Asserts that the two given database models are equal, and if not, writes both of them
* in XML form to <code>stderr</code>.
*
* @param expected The expected model
* @param actual The actual model
*/
protected void assertEquals(Database expected, Database actual)
{
try
{
assertEquals("Model names do not match.",
expected.getName(),
actual.getName());
assertEquals("Not the same number of tables.",
expected.getTableCount(),
actual.getTableCount());
for (int tableIdx = 0; tableIdx < actual.getTableCount(); tableIdx++)
{
assertEquals(expected.getTable(tableIdx),
actual.getTable(tableIdx));
}
}
catch (Throwable ex)
{
StringWriter writer = new StringWriter();
DatabaseIO dbIo = new DatabaseIO();
dbIo.write(expected, writer);
getLog().error("Expected model:\n" + writer.toString());
writer = new StringWriter();
dbIo.write(actual, writer);
getLog().error("Actual model:\n" + writer.toString());
if (ex instanceof Error)
{
throw (Error)ex;
}
else
{
throw new DdlUtilsException(ex);
}
}
}
/**
* Asserts that the two given database tables are equal.
*
* @param expected The expected table
* @param actual The actual table
*/
protected void assertEquals(Table expected, Table actual)
{
if (_useDelimitedIdentifiers)
{
assertEquals("Table names do not match.",
getPlatform().getSqlBuilder().shortenName(expected.getName(), getSqlBuilder().getMaxTableNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName(), getSqlBuilder().getMaxTableNameLength()));
}
else
{
assertEquals("Table names do not match (ignoring case).",
getPlatform().getSqlBuilder().shortenName(expected.getName().toUpperCase(), getSqlBuilder().getMaxTableNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName().toUpperCase(), getSqlBuilder().getMaxTableNameLength()));
}
assertEquals("Not the same number of columns in table "+actual.getName()+".",
expected.getColumnCount(),
actual.getColumnCount());
for (int columnIdx = 0; columnIdx < actual.getColumnCount(); columnIdx++)
{
assertEquals(expected.getColumn(columnIdx),
actual.getColumn(columnIdx));
}
assertEquals("Not the same number of foreign keys in table "+actual.getName()+".",
expected.getForeignKeyCount(),
actual.getForeignKeyCount());
// order is not assumed with the way foreignkeys are returned.
for (int expectedFkIdx = 0; expectedFkIdx < expected.getForeignKeyCount(); expectedFkIdx++)
{
ForeignKey expectedFk = expected.getForeignKey(expectedFkIdx);
String expectedName = getPlatform().getSqlBuilder().shortenName(expectedFk.getName(), getSqlBuilder().getMaxForeignKeyNameLength());
for (int actualFkIdx = 0; actualFkIdx < actual.getForeignKeyCount(); actualFkIdx++)
{
ForeignKey actualFk = actual.getForeignKey(actualFkIdx);
String actualName = getPlatform().getSqlBuilder().shortenName(actualFk.getName(), getSqlBuilder().getMaxForeignKeyNameLength());
if ((_useDelimitedIdentifiers && expectedName.equals(actualName)) ||
(!_useDelimitedIdentifiers && expectedName.equalsIgnoreCase(actualName)))
{
assertEquals(expectedFk, actualFk);
}
}
}
assertEquals("Not the same number of indices in table "+actual.getName()+".",
expected.getIndexCount(),
actual.getIndexCount());
for (int indexIdx = 0; indexIdx < actual.getIndexCount(); indexIdx++)
{
assertEquals(expected.getIndex(indexIdx),
actual.getIndex(indexIdx));
}
}
/**
* Asserts that the two given columns are equal.
*
* @param expected The expected column
* @param actual The actual column
*/
protected void assertEquals(Column expected, Column actual)
{
if (_useDelimitedIdentifiers)
{
assertEquals("Column names do not match.",
getPlatform().getSqlBuilder().shortenName(expected.getName(), getSqlBuilder().getMaxColumnNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName(), getSqlBuilder().getMaxColumnNameLength()));
}
else
{
assertEquals("Column names do not match (ignoring case).",
getPlatform().getSqlBuilder().shortenName(expected.getName().toUpperCase(), getSqlBuilder().getMaxColumnNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName().toUpperCase(), getSqlBuilder().getMaxColumnNameLength()));
}
assertEquals("Primary key status not the same for column "+actual.getName()+".",
expected.isPrimaryKey(),
actual.isPrimaryKey());
assertEquals("Required status not the same for column "+actual.getName()+".",
expected.isRequired(),
actual.isRequired());
if (getPlatformInfo().getIdentityStatusReadingSupported())
{
// we're only comparing this if the platform can actually read the
// auto-increment status back from an existing database
assertEquals("Auto-increment status not the same for column "+actual.getName()+".",
expected.isAutoIncrement(),
actual.isAutoIncrement());
}
assertEquals("Type not the same for column "+actual.getName()+".",
expected.getType(),
actual.getType());
assertEquals("Type code not the same for column "+actual.getName()+".",
expected.getTypeCode(),
actual.getTypeCode());
assertEquals("Parsed default values do not match for column "+actual.getName()+".",
expected.getParsedDefaultValue(),
actual.getParsedDefaultValue());
// comparing the size makes only sense for types where it is relevant
if ((expected.getTypeCode() == Types.NUMERIC) ||
(expected.getTypeCode() == Types.DECIMAL))
{
assertEquals("Precision not the same for column "+actual.getName()+".",
expected.getSizeAsInt(),
actual.getSizeAsInt());
assertEquals("Scale not the same for column "+actual.getName()+".",
expected.getScale(),
actual.getScale());
}
else if ((expected.getTypeCode() == Types.CHAR) ||
(expected.getTypeCode() == Types.VARCHAR) ||
(expected.getTypeCode() == Types.BINARY) ||
(expected.getTypeCode() == Types.VARBINARY))
{
assertEquals("Size not the same for column "+actual.getName()+".",
expected.getSize(),
actual.getSize());
}
}
/**
* Asserts that the two given foreign keys are equal.
*
* @param expected The expected foreign key
* @param actual The actual foreign key
*/
protected void assertEquals(ForeignKey expected, ForeignKey actual)
{
if (_useDelimitedIdentifiers)
{
assertEquals("Foreign key names do not match.",
getPlatform().getSqlBuilder().shortenName(expected.getName(), getSqlBuilder().getMaxForeignKeyNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName(), getSqlBuilder().getMaxForeignKeyNameLength()));
assertEquals("Referenced table names do not match.",
getPlatform().getSqlBuilder().shortenName(expected.getForeignTableName(), getSqlBuilder().getMaxTableNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getForeignTableName(), getSqlBuilder().getMaxTableNameLength()));
}
else
{
assertEquals("Foreign key names do not match (ignoring case).",
getPlatform().getSqlBuilder().shortenName(expected.getName().toUpperCase(), getSqlBuilder().getMaxForeignKeyNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName().toUpperCase(), getSqlBuilder().getMaxForeignKeyNameLength()));
assertEquals("Referenced table names do not match (ignoring case).",
getPlatform().getSqlBuilder().shortenName(expected.getForeignTableName().toUpperCase(), getSqlBuilder().getMaxTableNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getForeignTableName().toUpperCase(), getSqlBuilder().getMaxTableNameLength()));
}
if ((expected.getOnUpdate() == CascadeActionEnum.NONE) || (expected.getOnUpdate() == CascadeActionEnum.RESTRICT))
{
assertTrue("Not the same onUpdate setting in foreign key "+actual.getName()+".",
(actual.getOnUpdate() == CascadeActionEnum.NONE) || (actual.getOnUpdate() == CascadeActionEnum.RESTRICT));
}
else
{
assertEquals("Not the same onUpdate setting in foreign key "+actual.getName()+".",
expected.getOnUpdate(),
actual.getOnUpdate());
}
if ((expected.getOnDelete() == CascadeActionEnum.NONE) || (expected.getOnDelete() == CascadeActionEnum.RESTRICT))
{
assertTrue("Not the same onDelete setting in foreign key "+actual.getName()+".",
(actual.getOnDelete() == CascadeActionEnum.NONE) || (actual.getOnDelete() == CascadeActionEnum.RESTRICT));
}
else
{
assertEquals("Not the same onDelete setting in foreign key "+actual.getName()+".",
expected.getOnDelete(),
actual.getOnDelete());
}
assertEquals("Not the same number of references in foreign key "+actual.getName()+".",
expected.getReferenceCount(),
actual.getReferenceCount());
for (int refIdx = 0; refIdx < actual.getReferenceCount(); refIdx++)
{
assertEquals(expected.getReference(refIdx),
actual.getReference(refIdx));
}
}
/**
* Asserts that the two given references are equal.
*
* @param expected The expected reference
* @param actual The actual reference
*/
protected void assertEquals(Reference expected, Reference actual)
{
if (_useDelimitedIdentifiers)
{
assertEquals("Local column names do not match.",
getPlatform().getSqlBuilder().shortenName(expected.getLocalColumnName(), getSqlBuilder().getMaxColumnNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getLocalColumnName(), getSqlBuilder().getMaxColumnNameLength()));
assertEquals("Foreign column names do not match.",
getPlatform().getSqlBuilder().shortenName(expected.getForeignColumnName(), getSqlBuilder().getMaxColumnNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getForeignColumnName(), getSqlBuilder().getMaxColumnNameLength()));
}
else
{
assertEquals("Local column names do not match (ignoring case).",
getPlatform().getSqlBuilder().shortenName(expected.getLocalColumnName().toUpperCase(), getSqlBuilder().getMaxColumnNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getLocalColumnName().toUpperCase(), getSqlBuilder().getMaxColumnNameLength()));
assertEquals("Foreign column names do not match (ignoring case).",
getPlatform().getSqlBuilder().shortenName(expected.getForeignColumnName().toUpperCase(), getSqlBuilder().getMaxColumnNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getForeignColumnName().toUpperCase(), getSqlBuilder().getMaxColumnNameLength()));
}
}
/**
* Asserts that the two given indices are equal.
*
* @param expected The expected index
* @param actual The actual index
*/
protected void assertEquals(Index expected, Index actual)
{
if (_useDelimitedIdentifiers)
{
assertEquals("Index names do not match.",
getPlatform().getSqlBuilder().shortenName(expected.getName(), getSqlBuilder().getMaxConstraintNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName(), getSqlBuilder().getMaxConstraintNameLength()));
}
else
{
assertEquals("Index names do not match (ignoring case).",
getPlatform().getSqlBuilder().shortenName(expected.getName().toUpperCase(), getSqlBuilder().getMaxConstraintNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName().toUpperCase(), getSqlBuilder().getMaxConstraintNameLength()));
}
assertEquals("Unique status not the same for index "+actual.getName()+".",
expected.isUnique(),
actual.isUnique());
assertEquals("Not the same number of columns in index "+actual.getName()+".",
expected.getColumnCount(),
actual.getColumnCount());
for (int columnIdx = 0; columnIdx < actual.getColumnCount(); columnIdx++)
{
assertEquals(expected.getColumn(columnIdx),
actual.getColumn(columnIdx));
}
}
/**
* Asserts that the two given index columns are equal.
*
* @param expected The expected index column
* @param actual The actual index column
*/
protected void assertEquals(IndexColumn expected, IndexColumn actual)
{
if (_useDelimitedIdentifiers)
{
assertEquals("Index column names do not match.",
getPlatform().getSqlBuilder().shortenName(expected.getName(), getSqlBuilder().getMaxColumnNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName(), getSqlBuilder().getMaxColumnNameLength()));
}
else
{
assertEquals("Index column names do not match (ignoring case).",
getPlatform().getSqlBuilder().shortenName(expected.getName().toUpperCase(), getSqlBuilder().getMaxColumnNameLength()),
getPlatform().getSqlBuilder().shortenName(actual.getName().toUpperCase(), getSqlBuilder().getMaxColumnNameLength()));
}
assertEquals("Size not the same for index column "+actual.getName()+".",
expected.getSize(),
actual.getSize());
}
}