blob: a3c94530bbe700d2c58557c7542f0d51ed0f4f80 [file] [log] [blame]
package org.apache.ddlutils.platform;
/*
* 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.IOException;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.BatchUpdateException;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ddlutils.DatabaseOperationException;
import org.apache.ddlutils.DdlUtilsException;
import org.apache.ddlutils.Platform;
import org.apache.ddlutils.PlatformInfo;
import org.apache.ddlutils.alteration.AddColumnChange;
import org.apache.ddlutils.alteration.AddForeignKeyChange;
import org.apache.ddlutils.alteration.AddIndexChange;
import org.apache.ddlutils.alteration.AddPrimaryKeyChange;
import org.apache.ddlutils.alteration.AddTableChange;
import org.apache.ddlutils.alteration.ColumnDefinitionChange;
import org.apache.ddlutils.alteration.ColumnOrderChange;
import org.apache.ddlutils.alteration.ForeignKeyChange;
import org.apache.ddlutils.alteration.IndexChange;
import org.apache.ddlutils.alteration.ModelChange;
import org.apache.ddlutils.alteration.ModelComparator;
import org.apache.ddlutils.alteration.PrimaryKeyChange;
import org.apache.ddlutils.alteration.RecreateTableChange;
import org.apache.ddlutils.alteration.RemoveColumnChange;
import org.apache.ddlutils.alteration.RemoveForeignKeyChange;
import org.apache.ddlutils.alteration.RemoveIndexChange;
import org.apache.ddlutils.alteration.RemovePrimaryKeyChange;
import org.apache.ddlutils.alteration.RemoveTableChange;
import org.apache.ddlutils.alteration.TableChange;
import org.apache.ddlutils.alteration.TableDefinitionChangesPredicate;
import org.apache.ddlutils.dynabean.SqlDynaClass;
import org.apache.ddlutils.dynabean.SqlDynaProperty;
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.ModelException;
import org.apache.ddlutils.model.Table;
import org.apache.ddlutils.model.TypeMap;
import org.apache.ddlutils.util.JdbcSupport;
import org.apache.ddlutils.util.SqlTokenizer;
/**
* Base class for platform implementations.
*
* @version $Revision: 231110 $
*/
public abstract class PlatformImplBase extends JdbcSupport implements Platform
{
/** The default name for models read from the database, if no name as given.*/
protected static final String MODEL_DEFAULT_NAME = "default";
/** The log for this platform. */
private final Log _log = LogFactory.getLog(getClass());
/** The platform info. */
private PlatformInfo _info = new PlatformInfo();
/** The sql builder for this platform. */
private SqlBuilder _builder;
/** The model reader for this platform. */
private JdbcModelReader _modelReader;
/** Whether script mode is on. */
private boolean _scriptModeOn;
/** Whether SQL comments are generated or not. */
private boolean _sqlCommentsOn = true;
/** Whether delimited identifiers are used or not. */
private boolean _delimitedIdentifierModeOn;
/** Whether identity override is enabled. */
private boolean _identityOverrideOn;
/** Whether read foreign keys shall be sorted alphabetically. */
private boolean _foreignKeysSorted;
/** Whether to use the default ON UPDATE action if the specified one is unsupported. */
private boolean _useDefaultOnUpdateActionIfUnsupported = true;
/** Whether to use the default ON DELETE action if the specified one is unsupported. */
private boolean _useDefaultOnDeleteActionIfUnsupported = true;
/**
* {@inheritDoc}
*/
public SqlBuilder getSqlBuilder()
{
return _builder;
}
/**
* Sets the sql builder for this platform.
*
* @param builder The sql builder
*/
protected void setSqlBuilder(SqlBuilder builder)
{
_builder = builder;
}
/**
* {@inheritDoc}
*/
public JdbcModelReader getModelReader()
{
if (_modelReader == null)
{
_modelReader = new JdbcModelReader(this);
}
return _modelReader;
}
/**
* Sets the model reader for this platform.
*
* @param modelReader The model reader
*/
protected void setModelReader(JdbcModelReader modelReader)
{
_modelReader = modelReader;
}
/**
* {@inheritDoc}
*/
public PlatformInfo getPlatformInfo()
{
return _info;
}
/**
* {@inheritDoc}
*/
public boolean isScriptModeOn()
{
return _scriptModeOn;
}
/**
* {@inheritDoc}
*/
public void setScriptModeOn(boolean scriptModeOn)
{
_scriptModeOn = scriptModeOn;
}
/**
* {@inheritDoc}
*/
public boolean isSqlCommentsOn()
{
return _sqlCommentsOn;
}
/**
* {@inheritDoc}
*/
public void setSqlCommentsOn(boolean sqlCommentsOn)
{
if (!getPlatformInfo().isSqlCommentsSupported() && sqlCommentsOn)
{
throw new DdlUtilsException("Platform " + getName() + " does not support SQL comments");
}
_sqlCommentsOn = sqlCommentsOn;
}
/**
* {@inheritDoc}
*/
public boolean isDelimitedIdentifierModeOn()
{
return _delimitedIdentifierModeOn;
}
/**
* {@inheritDoc}
*/
public void setDelimitedIdentifierModeOn(boolean delimitedIdentifierModeOn)
{
if (!getPlatformInfo().isDelimitedIdentifiersSupported() && delimitedIdentifierModeOn)
{
throw new DdlUtilsException("Platform " + getName() + " does not support delimited identifier");
}
_delimitedIdentifierModeOn = delimitedIdentifierModeOn;
}
/**
* {@inheritDoc}
*/
public boolean isIdentityOverrideOn()
{
return _identityOverrideOn;
}
/**
* {@inheritDoc}
*/
public void setIdentityOverrideOn(boolean identityOverrideOn)
{
_identityOverrideOn = identityOverrideOn;
}
/**
* {@inheritDoc}
*/
public boolean isForeignKeysSorted()
{
return _foreignKeysSorted;
}
/**
* {@inheritDoc}
*/
public void setForeignKeysSorted(boolean foreignKeysSorted)
{
_foreignKeysSorted = foreignKeysSorted;
}
/**
* {@inheritDoc}
*/
public boolean isDefaultOnUpdateActionUsedIfUnsupported()
{
return _useDefaultOnUpdateActionIfUnsupported;
}
/**
* {@inheritDoc}
*/
public void setDefaultOnUpdateActionUsedIfUnsupported(boolean useDefault)
{
_useDefaultOnUpdateActionIfUnsupported = useDefault;
}
/**
* {@inheritDoc}
*/
public boolean isDefaultOnDeleteActionUsedIfUnsupported()
{
return _useDefaultOnDeleteActionIfUnsupported;
}
/**
* {@inheritDoc}
*/
public void setDefaultOnDeleteActionUsedIfUnsupported(boolean useDefault)
{
_useDefaultOnDeleteActionIfUnsupported = useDefault;
}
/**
* Returns the log for this platform.
*
* @return The log
*/
protected Log getLog()
{
return _log;
}
/**
* Logs any warnings associated to the given connection. Note that the connection needs
* to be open for this.
*
* @param connection The open connection
*/
protected void logWarnings(Connection connection) throws SQLException
{
SQLWarning warning = connection.getWarnings();
while (warning != null)
{
getLog().warn(warning.getLocalizedMessage(), warning.getCause());
warning = warning.getNextWarning();
}
}
/**
* {@inheritDoc}
*/
public int evaluateBatch(String sql, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
return evaluateBatch(connection, sql, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public int evaluateBatch(Connection connection, String sql, boolean continueOnError) throws DatabaseOperationException
{
Statement statement = null;
int errors = 0;
int commandCount = 0;
// we tokenize the SQL along the delimiters, and we also make sure that only delimiters
// at the end of a line or the end of the string are used (row mode)
try
{
statement = connection.createStatement();
SqlTokenizer tokenizer = new SqlTokenizer(sql);
while (tokenizer.hasMoreStatements())
{
String command = tokenizer.getNextStatement();
// ignore whitespace
command = command.trim();
if (command.length() == 0)
{
continue;
}
commandCount++;
if (_log.isDebugEnabled())
{
_log.debug("About to execute SQL " + command);
}
try
{
int results = statement.executeUpdate(command);
if (_log.isDebugEnabled())
{
_log.debug("After execution, " + results + " row(s) have been changed");
}
}
catch (SQLException ex)
{
if (continueOnError)
{
// Since the user deciced to ignore this error, we log the error
// on level warn, and the exception itself on level debug
_log.warn("SQL Command " + command + " failed with: " + ex.getMessage());
if (_log.isDebugEnabled())
{
_log.debug(ex);
}
errors++;
}
else
{
throw new DatabaseOperationException("Error while executing SQL "+command, ex);
}
}
// lets display any warnings
SQLWarning warning = connection.getWarnings();
while (warning != null)
{
_log.warn(warning.toString());
warning = warning.getNextWarning();
}
connection.clearWarnings();
}
_log.info("Executed "+ commandCount + " SQL command(s) with " + errors + " error(s)");
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while executing SQL", ex);
}
finally
{
closeStatement(statement);
}
return errors;
}
/**
* {@inheritDoc}
*/
public void shutdownDatabase() throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
shutdownDatabase(connection);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void shutdownDatabase(Connection connection) throws DatabaseOperationException
{
// Per default do nothing as most databases don't need this
}
/**
* {@inheritDoc}
*/
public void createDatabase(String jdbcDriverClassName, String connectionUrl, String username, String password, Map parameters) throws DatabaseOperationException, UnsupportedOperationException
{
throw new UnsupportedOperationException("Database creation is not supported for the database platform "+getName());
}
/**
* {@inheritDoc}
*/
public void dropDatabase(String jdbcDriverClassName, String connectionUrl, String username, String password) throws DatabaseOperationException, UnsupportedOperationException
{
throw new UnsupportedOperationException("Database deletion is not supported for the database platform "+getName());
}
/**
* {@inheritDoc}
*/
public void createTables(Database model, boolean dropTablesFirst, boolean continueOnError) throws DatabaseOperationException
{
createModel(model, dropTablesFirst, continueOnError);
}
/**
* {@inheritDoc}
*/
public void createTables(Database model, CreationParameters params, boolean dropTablesFirst, boolean continueOnError) throws DatabaseOperationException
{
createModel(model, params, dropTablesFirst, continueOnError);
}
/**
* {@inheritDoc}
*/
public void createTables(Connection connection, Database model, boolean dropTablesFirst, boolean continueOnError) throws DatabaseOperationException
{
createModel(connection, model, dropTablesFirst, continueOnError);
}
/**
* {@inheritDoc}
*/
public void createTables(Connection connection, Database model, CreationParameters params, boolean dropTablesFirst, boolean continueOnError) throws DatabaseOperationException
{
createModel(connection, model, params, dropTablesFirst, continueOnError);
}
/**
* {@inheritDoc}
*/
public String getCreateTablesSql(Database model, boolean dropTablesFirst, boolean continueOnError)
{
return getCreateModelSql(model, dropTablesFirst, continueOnError);
}
/**
* {@inheritDoc}
*/
public String getCreateTablesSql(Database model, CreationParameters params, boolean dropTablesFirst, boolean continueOnError)
{
return getCreateModelSql(model, params, dropTablesFirst, continueOnError);
}
/**
* {@inheritDoc}
*/
public void createModel(Database model, boolean dropTablesFirst, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
createModel(connection, model, dropTablesFirst, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void createModel(Connection connection, Database model, boolean dropTablesFirst, boolean continueOnError) throws DatabaseOperationException
{
String sql = getCreateModelSql(model, dropTablesFirst, continueOnError);
evaluateBatch(connection, sql, continueOnError);
}
/**
* {@inheritDoc}
*/
public void createModel(Database model, CreationParameters params, boolean dropTablesFirst, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
createModel(connection, model, params, dropTablesFirst, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void createModel(Connection connection, Database model, CreationParameters params, boolean dropTablesFirst, boolean continueOnError) throws DatabaseOperationException
{
String sql = getCreateModelSql(model, params, dropTablesFirst, continueOnError);
evaluateBatch(connection, sql, continueOnError);
}
/**
* {@inheritDoc}
*/
public String getCreateModelSql(Database model, boolean dropTablesFirst, boolean continueOnError)
{
String sql = null;
try
{
StringWriter buffer = new StringWriter();
getSqlBuilder().setWriter(buffer);
getSqlBuilder().createTables(model, dropTablesFirst);
sql = buffer.toString();
}
catch (IOException e)
{
// won't happen because we're using a string writer
}
return sql;
}
/**
* {@inheritDoc}
*/
public String getCreateModelSql(Database model, CreationParameters params, boolean dropTablesFirst, boolean continueOnError)
{
String sql = null;
try
{
StringWriter buffer = new StringWriter();
getSqlBuilder().setWriter(buffer);
getSqlBuilder().createTables(model, params, dropTablesFirst);
sql = buffer.toString();
}
catch (IOException e)
{
// won't happen because we're using a string writer
}
return sql;
}
/**
* Returns the model comparator to be used for this platform. This method is intendeded
* to be redefined by platforms that need to customize the model reader.
*
* @return The model comparator
*/
protected ModelComparator getModelComparator()
{
return new ModelComparator(getPlatformInfo(),
getTableDefinitionChangesPredicate(),
isDelimitedIdentifierModeOn());
}
/**
* Returns the predicate that defines which changes are supported by the platform.
*
* @return The predicate
*/
protected TableDefinitionChangesPredicate getTableDefinitionChangesPredicate()
{
return new DefaultTableDefinitionChangesPredicate();
}
/**
* {@inheritDoc}
*/
public List getChanges(Database currentModel, Database desiredModel)
{
List changes = getModelComparator().compare(currentModel, desiredModel);
return sortChanges(changes);
}
/**
* Sorts the changes so that they can be executed by the database. E.g. tables need to be created before
* they can be referenced by foreign keys, indexes should be dropped before a table is dropped etc.
*
* @param changes The original changes
* @return The sorted changes - this can be the original list object or a new one
*/
protected List sortChanges(List changes)
{
final Map typeOrder = new HashMap();
typeOrder.put(RemoveForeignKeyChange.class, new Integer(0));
typeOrder.put(RemoveIndexChange.class, new Integer(1));
typeOrder.put(RemoveTableChange.class, new Integer(2));
typeOrder.put(RecreateTableChange.class, new Integer(3));
typeOrder.put(RemovePrimaryKeyChange.class, new Integer(3));
typeOrder.put(RemoveColumnChange.class, new Integer(4));
typeOrder.put(ColumnDefinitionChange.class, new Integer(5));
typeOrder.put(ColumnOrderChange.class, new Integer(5));
typeOrder.put(AddColumnChange.class, new Integer(5));
typeOrder.put(PrimaryKeyChange.class, new Integer(5));
typeOrder.put(AddPrimaryKeyChange.class, new Integer(6));
typeOrder.put(AddTableChange.class, new Integer(7));
typeOrder.put(AddIndexChange.class, new Integer(8));
typeOrder.put(AddForeignKeyChange.class, new Integer(9));
Collections.sort(changes, new Comparator()
{
public int compare(Object objA, Object objB)
{
Integer orderValueA = (Integer)typeOrder.get(objA.getClass());
Integer orderValueB = (Integer)typeOrder.get(objB.getClass());
if (orderValueA == null)
{
return (orderValueB == null ? 0 : 1);
}
else if (orderValueB == null)
{
return -1;
}
else
{
return orderValueA.compareTo(orderValueB);
}
}
});
return changes;
}
/**
* {@inheritDoc}
*/
public void alterTables(Database desiredModel, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName());
alterModel(currentModel, desiredModel, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void alterTables(Database desiredModel, CreationParameters params, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName());
alterModel(currentModel, desiredModel, params, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void alterTables(String catalog, String schema, String[] tableTypes, Database desiredModel, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName(), catalog, schema, tableTypes);
alterModel(currentModel, desiredModel, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void alterTables(String catalog, String schema, String[] tableTypes, Database desiredModel, CreationParameters params, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName(), catalog, schema, tableTypes);
alterModel(currentModel, desiredModel, params, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void alterTables(Connection connection, Database desiredModel, boolean continueOnError) throws DatabaseOperationException
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName());
alterModel(currentModel, desiredModel, continueOnError);
}
/**
* {@inheritDoc}
*/
public void alterTables(Connection connection, Database desiredModel, CreationParameters params, boolean continueOnError) throws DatabaseOperationException
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName());
alterModel(currentModel, desiredModel, params, continueOnError);
}
/**
* {@inheritDoc}
*/
public void alterTables(Connection connection, String catalog, String schema, String[] tableTypes, Database desiredModel, boolean continueOnError) throws DatabaseOperationException
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName(), catalog, schema, tableTypes);
alterModel(currentModel, desiredModel, continueOnError);
}
/**
* {@inheritDoc}
*/
public void alterTables(Connection connection, String catalog, String schema, String[] tableTypes, Database desiredModel, CreationParameters params, boolean continueOnError) throws DatabaseOperationException
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName(), catalog, schema, tableTypes);
alterModel(currentModel, desiredModel, params, continueOnError);
}
/**
* {@inheritDoc}
*/
public String getAlterTablesSql(Database desiredModel) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName());
return getAlterModelSql(currentModel, desiredModel);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public String getAlterTablesSql(Database desiredModel, CreationParameters params) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName());
return getAlterModelSql(currentModel, desiredModel, params);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public String getAlterTablesSql(String catalog, String schema, String[] tableTypes, Database desiredModel) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName(), catalog, schema, tableTypes);
return getAlterModelSql(currentModel, desiredModel);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public String getAlterTablesSql(String catalog, String schema, String[] tableTypes, Database desiredModel, CreationParameters params) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName(), catalog, schema, tableTypes);
return getAlterModelSql(currentModel, desiredModel, params);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public String getAlterTablesSql(Connection connection, Database desiredModel) throws DatabaseOperationException
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName());
return getAlterModelSql(currentModel, desiredModel);
}
/**
* {@inheritDoc}
*/
public String getAlterTablesSql(Connection connection, Database desiredModel, CreationParameters params) throws DatabaseOperationException
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName());
return getAlterModelSql(currentModel, desiredModel, params);
}
/**
* {@inheritDoc}
*/
public String getAlterTablesSql(Connection connection, String catalog, String schema, String[] tableTypes, Database desiredModel) throws DatabaseOperationException
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName(), catalog, schema, tableTypes);
return getAlterModelSql(currentModel, desiredModel);
}
/**
* {@inheritDoc}
*/
public String getAlterTablesSql(Connection connection, String catalog, String schema, String[] tableTypes, Database desiredModel, CreationParameters params) throws DatabaseOperationException
{
Database currentModel = readModelFromDatabase(connection, desiredModel.getName(), catalog, schema, tableTypes);
return getAlterModelSql(currentModel, desiredModel, params);
}
/**
* {@inheritDoc}
*/
public String getAlterModelSql(Database currentModel, Database desiredModel) throws DatabaseOperationException
{
return getAlterModelSql(currentModel, desiredModel, null);
}
/**
* {@inheritDoc}
*/
public String getAlterModelSql(Database currentModel, Database desiredModel, CreationParameters params) throws DatabaseOperationException
{
List changes = getChanges(currentModel, desiredModel);
String sql = null;
try
{
StringWriter buffer = new StringWriter();
getSqlBuilder().setWriter(buffer);
processChanges(currentModel, changes, params);
sql = buffer.toString();
}
catch (IOException ex)
{
// won't happen because we're using a string writer
}
return sql;
}
/**
* {@inheritDoc}
*/
public void alterModel(Database currentModel, Database desiredModel, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
alterModel(connection, currentModel, desiredModel, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void alterModel(Database currentModel, Database desiredModel, CreationParameters params, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
alterModel(connection, currentModel, desiredModel, params, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void alterModel(Connection connection, Database currentModel, Database desiredModel, boolean continueOnError) throws DatabaseOperationException
{
String sql = getAlterModelSql(currentModel, desiredModel);
evaluateBatch(connection, sql, continueOnError);
}
/**
* {@inheritDoc}
*/
public void alterModel(Connection connection, Database currentModel, Database desiredModel, CreationParameters params, boolean continueOnError) throws DatabaseOperationException
{
String sql = getAlterModelSql(currentModel, desiredModel, params);
evaluateBatch(connection, sql, continueOnError);
}
/**
* {@inheritDoc}
*/
public void dropTable(Connection connection, Database model, Table table, boolean continueOnError) throws DatabaseOperationException
{
String sql = getDropTableSql(model, table, continueOnError);
evaluateBatch(connection, sql, continueOnError);
}
/**
* {@inheritDoc}
*/
public void dropTable(Database model, Table table, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
dropTable(connection, model, table, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public String getDropTableSql(Database model, Table table, boolean continueOnError)
{
String sql = null;
try
{
StringWriter buffer = new StringWriter();
getSqlBuilder().setWriter(buffer);
getSqlBuilder().dropTable(model, table);
sql = buffer.toString();
}
catch (IOException e)
{
// won't happen because we're using a string writer
}
return sql;
}
/**
* {@inheritDoc}
*/
public void dropTables(Database model, boolean continueOnError) throws DatabaseOperationException
{
dropModel(model, continueOnError);
}
/**
* {@inheritDoc}
*/
public void dropTables(Connection connection, Database model, boolean continueOnError) throws DatabaseOperationException
{
dropModel(connection, model, continueOnError);
}
/**
* {@inheritDoc}
*/
public String getDropTablesSql(Database model, boolean continueOnError)
{
return getDropModelSql(model);
}
/**
* {@inheritDoc}
*/
public void dropModel(Database model, boolean continueOnError) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
dropModel(connection, model, continueOnError);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void dropModel(Connection connection, Database model, boolean continueOnError) throws DatabaseOperationException
{
String sql = getDropModelSql(model);
evaluateBatch(connection, sql, continueOnError);
}
/**
* {@inheritDoc}
*/
public String getDropModelSql(Database model)
{
String sql = null;
try
{
StringWriter buffer = new StringWriter();
getSqlBuilder().setWriter(buffer);
getSqlBuilder().dropTables(model);
sql = buffer.toString();
}
catch (IOException e)
{
// won't happen because we're using a string writer
}
return sql;
}
/**
* Processes the given changes in the specified order. Basically, this method finds the
* appropriate handler method (one of the <code>processChange</code> methods) defined in
* the concrete sql builder for each change, and invokes it.
*
* @param model The database model; this object is not going to be changed by this method
* @param changes The changes
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @return The changed database model
*/
protected Database processChanges(Database model,
Collection changes,
CreationParameters params) throws IOException, DdlUtilsException
{
Database currentModel = new CloneHelper().clone(model);
for (Iterator it = changes.iterator(); it.hasNext();)
{
invokeChangeHandler(currentModel, params, (ModelChange)it.next());
}
return currentModel;
}
/**
* Invokes the change handler (one of the <code>processChange</code> methods) for the given
* change object.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
private void invokeChangeHandler(Database currentModel,
CreationParameters params,
ModelChange change) throws IOException
{
Class curClass = getClass();
// find the handler for the change
while ((curClass != null) && !Object.class.equals(curClass))
{
try
{
Method method = null;
try
{
method = curClass.getDeclaredMethod("processChange",
new Class[] { Database.class,
CreationParameters.class,
change.getClass() });
}
catch (NoSuchMethodException ex)
{
// we actually expect this one
}
if (method != null)
{
method.invoke(this, new Object[] { currentModel, params, change });
return;
}
else
{
curClass = curClass.getSuperclass();
}
}
catch (InvocationTargetException ex)
{
if (ex.getTargetException() instanceof IOException)
{
throw (IOException)ex.getTargetException();
}
else
{
throw new DdlUtilsException(ex.getTargetException());
}
}
catch (Exception ex)
{
throw new DdlUtilsException(ex);
}
}
throw new DdlUtilsException("No handler for change of type " + change.getClass().getName() + " defined");
}
/**
* Finds the table changed by the change object in the given model.
*
* @param currentModel The model to find the table in
* @param change The table change
* @return The table
* @throws ModelException If the table could not be found
*/
protected Table findChangedTable(Database currentModel, TableChange change) throws ModelException
{
Table table = currentModel.findTable(change.getChangedTable(),
getPlatformInfo().isDelimitedIdentifiersSupported());
if (table == null)
{
throw new ModelException("Could not find table " + change.getChangedTable() + " in the given model");
}
else
{
return table;
}
}
/**
* Finds the index changed by the change object in the given model.
*
* @param currentModel The model to find the index in
* @param change The index change
* @return The index
* @throws ModelException If the index could not be found
*/
protected Index findChangedIndex(Database currentModel, IndexChange change) throws ModelException
{
Index index = change.findChangedIndex(currentModel,
getPlatformInfo().isDelimitedIdentifiersSupported());
if (index == null)
{
throw new ModelException("Could not find the index to change in table " + change.getChangedTable() + " in the given model");
}
else
{
return index;
}
}
/**
* Finds the foreign key changed by the change object in the given model.
*
* @param currentModel The model to find the foreign key in
* @param change The foreign key change
* @return The foreign key
* @throws ModelException If the foreign key could not be found
*/
protected ForeignKey findChangedForeignKey(Database currentModel, ForeignKeyChange change) throws ModelException
{
ForeignKey fk = change.findChangedForeignKey(currentModel,
getPlatformInfo().isDelimitedIdentifiersSupported());
if (fk == null)
{
throw new ModelException("Could not find the foreign key to change in table " + change.getChangedTable() + " in the given model");
}
else
{
return fk;
}
}
/**
* Processes a change representing the addition of a table.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
AddTableChange change) throws IOException
{
getSqlBuilder().createTable(currentModel,
change.getNewTable(),
params == null ? null : params.getParametersFor(change.getNewTable()));
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes a change representing the removal of a table.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
RemoveTableChange change) throws IOException, ModelException
{
Table changedTable = findChangedTable(currentModel, change);
getSqlBuilder().dropTable(changedTable);
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes a change representing the addition of a foreign key.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
AddForeignKeyChange change) throws IOException
{
Table changedTable = findChangedTable(currentModel, change);
getSqlBuilder().createForeignKey(currentModel,
changedTable,
change.getNewForeignKey());
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes a change representing the removal of a foreign key.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
RemoveForeignKeyChange change) throws IOException, ModelException
{
Table changedTable = findChangedTable(currentModel, change);
ForeignKey changedFk = findChangedForeignKey(currentModel, change);
getSqlBuilder().dropForeignKey(changedTable, changedFk);
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes a change representing the addition of an index.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
AddIndexChange change) throws IOException
{
Table changedTable = findChangedTable(currentModel, change);
getSqlBuilder().createIndex(changedTable, change.getNewIndex());
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes a change representing the removal of an index.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
RemoveIndexChange change) throws IOException, ModelException
{
Table changedTable = findChangedTable(currentModel, change);
Index changedIndex = findChangedIndex(currentModel, change);
getSqlBuilder().dropIndex(changedTable, changedIndex);
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes a change representing the addition of a column.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
AddColumnChange change) throws IOException
{
Table changedTable = findChangedTable(currentModel, change);
getSqlBuilder().addColumn(changedTable, change.getNewColumn());
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes a change representing the addition of a primary key.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
AddPrimaryKeyChange change) throws IOException
{
Table changedTable = findChangedTable(currentModel, change);
String[] pkColumnNames = change.getPrimaryKeyColumns();
Column[] pkColumns = new Column[pkColumnNames.length];
for (int colIdx = 0; colIdx < pkColumns.length; colIdx++)
{
pkColumns[colIdx] = changedTable.findColumn(pkColumnNames[colIdx], isDelimitedIdentifierModeOn());
}
getSqlBuilder().createPrimaryKey(changedTable, pkColumns);
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes a change representing the recreation of a table.
*
* @param currentModel The current database schema
* @param params The parameters used in the creation of new tables. Note that for existing
* tables, the parameters won't be applied
* @param change The change object
*/
public void processChange(Database currentModel,
CreationParameters params,
RecreateTableChange change) throws IOException
{
// we can only copy the data if no required columns without default value and
// non-autoincrement have been added
boolean canMigrateData = true;
for (Iterator it = change.getOriginalChanges().iterator(); canMigrateData && it.hasNext();)
{
TableChange curChange = (TableChange)it.next();
if (curChange instanceof AddColumnChange)
{
AddColumnChange addColumnChange = (AddColumnChange)curChange;
if (addColumnChange.getNewColumn().isRequired() &&
!addColumnChange.getNewColumn().isAutoIncrement() &&
(addColumnChange.getNewColumn().getDefaultValue() == null))
{
_log.warn("Data cannot be retained in table " + change.getChangedTable() +
" because of the addition of the required column " + addColumnChange.getNewColumn().getName());
canMigrateData = false;
}
}
}
Table changedTable = findChangedTable(currentModel, change);
Table targetTable = change.getTargetTable();
Map parameters = (params == null ? null : params.getParametersFor(targetTable));
if (canMigrateData)
{
Table tempTable = getTemporaryTableFor(targetTable);
getSqlBuilder().createTemporaryTable(currentModel, tempTable, parameters);
getSqlBuilder().copyData(changedTable, tempTable);
// Note that we don't drop the indices here because the DROP TABLE will take care of that
// Likewise, foreign keys have already been dropped as necessary
getSqlBuilder().dropTable(changedTable);
getSqlBuilder().createTable(currentModel, targetTable, parameters);
getSqlBuilder().copyData(tempTable, targetTable);
getSqlBuilder().dropTemporaryTable(currentModel, tempTable);
}
else
{
getSqlBuilder().dropTable(changedTable);
getSqlBuilder().createTable(currentModel, targetTable, parameters);
}
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Creates a temporary table object that corresponds to the given table.
* Database-specific implementations may redefine this method if e.g. the
* database directly supports temporary tables. The default implementation
* simply appends an underscore to the table name and uses that as the
* table name.
*
* @param targetTable The target table
* @return The temporary table
*/
protected Table getTemporaryTableFor(Table targetTable)
{
CloneHelper cloneHelper = new CloneHelper();
Table table = new Table();
table.setCatalog(targetTable.getCatalog());
table.setSchema(targetTable.getSchema());
table.setName(targetTable.getName() + "_");
table.setType(targetTable.getType());
for (int idx = 0; idx < targetTable.getColumnCount(); idx++)
{
// TODO: clone PK status ?
table.addColumn(cloneHelper.clone(targetTable.getColumn(idx), true));
}
return table;
}
/**
* {@inheritDoc}
*/
public Iterator query(Database model, String sql) throws DatabaseOperationException
{
return query(model, sql, (Table[])null);
}
/**
* {@inheritDoc}
*/
public Iterator query(Database model, String sql, Collection parameters) throws DatabaseOperationException
{
return query(model, sql, parameters, null);
}
/**
* {@inheritDoc}
*/
public Iterator query(Database model, String sql, Table[] queryHints) throws DatabaseOperationException
{
Connection connection = borrowConnection();
Statement statement = null;
ResultSet resultSet = null;
Iterator answer = null;
try
{
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
answer = createResultSetIterator(model, resultSet, queryHints);
return answer;
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while performing a query", ex);
}
finally
{
// if any exceptions are thrown, close things down
// otherwise we're leaving it open for the iterator
if (answer == null)
{
closeStatement(statement);
returnConnection(connection);
}
}
}
/**
* {@inheritDoc}
*/
public Iterator query(Database model, String sql, Collection parameters, Table[] queryHints) throws DatabaseOperationException
{
Connection connection = borrowConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
Iterator answer = null;
try
{
statement = connection.prepareStatement(sql);
int paramIdx = 1;
for (Iterator iter = parameters.iterator(); iter.hasNext(); paramIdx++)
{
Object arg = iter.next();
if (arg instanceof BigDecimal)
{
// to avoid scale problems because setObject assumes a scale of 0
statement.setBigDecimal(paramIdx, (BigDecimal)arg);
}
else
{
statement.setObject(paramIdx, arg);
}
}
resultSet = statement.executeQuery();
answer = createResultSetIterator(model, resultSet, queryHints);
return answer;
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while performing a query", ex);
}
finally
{
// if any exceptions are thrown, close things down
// otherwise we're leaving it open for the iterator
if (answer == null)
{
closeStatement(statement);
returnConnection(connection);
}
}
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql) throws DatabaseOperationException
{
return fetch(model, sql, (Table[])null, 0, -1);
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, Table[] queryHints) throws DatabaseOperationException
{
return fetch(model, sql, queryHints, 0, -1);
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, int start, int end) throws DatabaseOperationException
{
return fetch(model, sql, (Table[])null, start, end);
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, Table[] queryHints, int start, int end) throws DatabaseOperationException
{
Connection connection = borrowConnection();
Statement statement = null;
ResultSet resultSet = null;
List result = new ArrayList();
try
{
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
int rowIdx = 0;
for (ModelBasedResultSetIterator it = createResultSetIterator(model, resultSet, queryHints); ((end < 0) || (rowIdx <= end)) && it.hasNext(); rowIdx++)
{
if (rowIdx >= start)
{
result.add(it.next());
}
else
{
it.advance();
}
}
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while fetching data from the database", ex);
}
finally
{
// the iterator should return the connection automatically
// so this is usually not necessary (but just in case)
closeStatement(statement);
returnConnection(connection);
}
return result;
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, Collection parameters) throws DatabaseOperationException
{
return fetch(model, sql, parameters, null, 0, -1);
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, Collection parameters, int start, int end) throws DatabaseOperationException
{
return fetch(model, sql, parameters, null, start, end);
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, Collection parameters, Table[] queryHints) throws DatabaseOperationException
{
return fetch(model, sql, parameters, queryHints, 0, -1);
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, Collection parameters, Table[] queryHints, int start, int end) throws DatabaseOperationException
{
Connection connection = borrowConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
List result = new ArrayList();
try
{
statement = connection.prepareStatement(sql);
int paramIdx = 1;
for (Iterator iter = parameters.iterator(); iter.hasNext(); paramIdx++)
{
Object arg = iter.next();
if (arg instanceof BigDecimal)
{
// to avoid scale problems because setObject assumes a scale of 0
statement.setBigDecimal(paramIdx, (BigDecimal)arg);
}
else
{
statement.setObject(paramIdx, arg);
}
}
resultSet = statement.executeQuery();
int rowIdx = 0;
for (ModelBasedResultSetIterator it = createResultSetIterator(model, resultSet, queryHints); ((end < 0) || (rowIdx <= end)) && it.hasNext(); rowIdx++)
{
if (rowIdx >= start)
{
result.add(it.next());
}
else
{
it.advance();
}
}
}
catch (SQLException ex)
{
// any other exception comes from the iterator which closes the resources automatically
closeStatement(statement);
returnConnection(connection);
throw new DatabaseOperationException("Error while fetching data from the database", ex);
}
return result;
}
/**
* Creates the SQL for inserting an object of the given type. If a concrete bean is given,
* then a concrete insert statement is created, otherwise an insert statement usable in a
* prepared statement is build.
*
* @param model The database model
* @param dynaClass The type
* @param properties The properties to write
* @param bean Optionally the concrete bean to insert
* @return The SQL required to insert an instance of the class
*/
protected String createInsertSql(Database model, SqlDynaClass dynaClass, SqlDynaProperty[] properties, DynaBean bean)
{
Table table = model.findTable(dynaClass.getTableName());
HashMap columnValues = toColumnValues(properties, bean);
return _builder.getInsertSql(table, columnValues, bean == null);
}
/**
* Creates the SQL for querying for the id generated by the last insert of an object of the given type.
*
* @param model The database model
* @param dynaClass The type
* @return The SQL required for querying for the id, or <code>null</code> if the database does not
* support this
*/
protected String createSelectLastInsertIdSql(Database model, SqlDynaClass dynaClass)
{
Table table = model.findTable(dynaClass.getTableName());
return _builder.getSelectLastIdentityValues(table);
}
/**
* {@inheritDoc}
*/
public String getInsertSql(Database model, DynaBean dynaBean)
{
SqlDynaClass dynaClass = model.getDynaClassFor(dynaBean);
SqlDynaProperty[] properties = dynaClass.getSqlDynaProperties();
if (properties.length == 0)
{
_log.info("Cannot insert instances of type " + dynaClass + " because it has no properties");
return null;
}
return createInsertSql(model, dynaClass, properties, dynaBean);
}
/**
* Returns all properties where the column is not non-autoincrement and for which the bean
* either has a value or the column hasn't got a default value, for the given dyna class.
*
* @param model The database model
* @param dynaClass The dyna class
* @param bean The bean
* @return The properties
*/
private SqlDynaProperty[] getPropertiesForInsertion(Database model, SqlDynaClass dynaClass, final DynaBean bean)
{
SqlDynaProperty[] properties = dynaClass.getSqlDynaProperties();
Collection result = CollectionUtils.select(Arrays.asList(properties), new Predicate() {
public boolean evaluate(Object input) {
SqlDynaProperty prop = (SqlDynaProperty)input;
if (bean.get(prop.getName()) != null)
{
// we ignore properties for which a value is present in the bean
// only if they are identity and identity override is off or
// the platform does not allow the override of the auto-increment
// specification
return !prop.getColumn().isAutoIncrement() ||
(isIdentityOverrideOn() && getPlatformInfo().isIdentityOverrideAllowed());
}
else
{
// we also return properties without a value in the bean
// if they ain't auto-increment and don't have a default value
// in this case, a NULL is inserted
return !prop.getColumn().isAutoIncrement() &&
(prop.getColumn().getDefaultValue() == null);
}
}
});
return (SqlDynaProperty[])result.toArray(new SqlDynaProperty[result.size()]);
}
/**
* Returns all identity properties whose value were defined by the database and which
* now need to be read back from the DB.
*
* @param model The database model
* @param dynaClass The dyna class
* @param bean The bean
* @return The columns
*/
private Column[] getRelevantIdentityColumns(Database model, SqlDynaClass dynaClass, final DynaBean bean)
{
SqlDynaProperty[] properties = dynaClass.getSqlDynaProperties();
Collection relevantProperties = CollectionUtils.select(Arrays.asList(properties), new Predicate() {
public boolean evaluate(Object input) {
SqlDynaProperty prop = (SqlDynaProperty)input;
// we only want those identity columns that were really specified by the DB
// if the platform allows specification of values for identity columns
// in INSERT/UPDATE statements, then we need to filter the corresponding
// columns out
return prop.getColumn().isAutoIncrement() &&
(!isIdentityOverrideOn() || !getPlatformInfo().isIdentityOverrideAllowed() || (bean.get(prop.getName()) == null));
}
});
Column[] columns = new Column[relevantProperties.size()];
int idx = 0;
for (Iterator propIt = relevantProperties.iterator(); propIt.hasNext(); idx++)
{
columns[idx] = ((SqlDynaProperty)propIt.next()).getColumn();
}
return columns;
}
/**
* {@inheritDoc}
*/
public void insert(Connection connection, Database model, DynaBean dynaBean) throws DatabaseOperationException
{
SqlDynaClass dynaClass = model.getDynaClassFor(dynaBean);
SqlDynaProperty[] properties = getPropertiesForInsertion(model, dynaClass, dynaBean);
Column[] autoIncrColumns = getRelevantIdentityColumns(model, dynaClass, dynaBean);
if ((properties.length == 0) && (autoIncrColumns.length == 0))
{
_log.warn("Cannot insert instances of type " + dynaClass + " because it has no usable properties");
return;
}
String insertSql = createInsertSql(model, dynaClass, properties, null);
String queryIdentitySql = null;
if (_log.isDebugEnabled())
{
_log.debug("About to execute SQL: " + insertSql);
}
if (autoIncrColumns.length > 0)
{
if (!getPlatformInfo().isLastIdentityValueReadable())
{
_log.warn("The database does not support querying for auto-generated column values");
}
else
{
queryIdentitySql = createSelectLastInsertIdSql(model, dynaClass);
}
}
boolean autoCommitMode = false;
PreparedStatement statement = null;
try
{
if (!getPlatformInfo().isAutoCommitModeForLastIdentityValueReading())
{
autoCommitMode = connection.getAutoCommit();
connection.setAutoCommit(false);
}
beforeInsert(connection, dynaClass.getTable());
statement = connection.prepareStatement(insertSql);
for (int idx = 0; idx < properties.length; idx++ )
{
setObject(statement, idx + 1, dynaBean, properties[idx]);
}
int count = statement.executeUpdate();
afterInsert(connection, dynaClass.getTable());
if (count != 1)
{
_log.warn("Attempted to insert a single row " + dynaBean +
" in table " + dynaClass.getTableName() +
" but changed " + count + " row(s)");
}
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while inserting into the database: " + ex.getMessage(), ex);
}
finally
{
closeStatement(statement);
}
if (queryIdentitySql != null)
{
Statement queryStmt = null;
ResultSet lastInsertedIds = null;
try
{
if (getPlatformInfo().isAutoCommitModeForLastIdentityValueReading())
{
// we'll commit the statement(s) if no auto-commit is enabled because
// otherwise it is possible that the auto increment hasn't happened yet
// (the db didn't actually perform the insert yet so no triggering of
// sequences did occur)
if (!connection.getAutoCommit())
{
connection.commit();
}
}
queryStmt = connection.createStatement();
lastInsertedIds = queryStmt.executeQuery(queryIdentitySql);
lastInsertedIds.next();
for (int idx = 0; idx < autoIncrColumns.length; idx++)
{
// we're using the index rather than the name because we cannot know how
// the SQL statement looks like; rather we assume that we get the values
// back in the same order as the auto increment columns
Object value = getObjectFromResultSet(lastInsertedIds, autoIncrColumns[idx], idx + 1);
PropertyUtils.setProperty(dynaBean, autoIncrColumns[idx].getName(), value);
}
}
catch (NoSuchMethodException ex)
{
// Can't happen because we're using dyna beans
}
catch (IllegalAccessException ex)
{
// Can't happen because we're using dyna beans
}
catch (InvocationTargetException ex)
{
// Can't happen because we're using dyna beans
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while retrieving the identity column value(s) from the database", ex);
}
finally
{
if (lastInsertedIds != null)
{
try
{
lastInsertedIds.close();
}
catch (SQLException ex)
{
// we ignore this one
}
}
closeStatement(statement);
}
}
if (!getPlatformInfo().isAutoCommitModeForLastIdentityValueReading())
{
try
{
// we need to do a manual commit now
connection.commit();
connection.setAutoCommit(autoCommitMode);
}
catch (SQLException ex)
{
throw new DatabaseOperationException(ex);
}
}
}
/**
* {@inheritDoc}
*/
public void insert(Database model, DynaBean dynaBean) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
insert(connection, model, dynaBean);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void insert(Connection connection, Database model, Collection dynaBeans) throws DatabaseOperationException
{
SqlDynaClass dynaClass = null;
SqlDynaProperty[] properties = null;
PreparedStatement statement = null;
int addedStmts = 0;
boolean identityWarningPrinted = false;
for (Iterator it = dynaBeans.iterator(); it.hasNext();)
{
DynaBean dynaBean = (DynaBean)it.next();
SqlDynaClass curDynaClass = model.getDynaClassFor(dynaBean);
if (curDynaClass != dynaClass)
{
if (dynaClass != null)
{
executeBatch(statement, addedStmts, dynaClass.getTable());
addedStmts = 0;
}
dynaClass = curDynaClass;
properties = getPropertiesForInsertion(model, curDynaClass, dynaBean);
if (properties.length == 0)
{
_log.warn("Cannot insert instances of type " + dynaClass + " because it has no usable properties");
continue;
}
if (!identityWarningPrinted &&
(getRelevantIdentityColumns(model, curDynaClass, dynaBean).length > 0))
{
_log.warn("Updating the bean properties corresponding to auto-increment columns is not supported in batch mode");
identityWarningPrinted = true;
}
String insertSql = createInsertSql(model, dynaClass, properties, null);
if (_log.isDebugEnabled())
{
_log.debug("Starting new batch with SQL: " + insertSql);
}
try
{
statement = connection.prepareStatement(insertSql);
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while preparing insert statement", ex);
}
}
try
{
for (int idx = 0; idx < properties.length; idx++ )
{
setObject(statement, idx + 1, dynaBean, properties[idx]);
}
statement.addBatch();
addedStmts++;
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while adding batch insert", ex);
}
}
if (dynaClass != null)
{
executeBatch(statement, addedStmts, dynaClass.getTable());
}
}
/**
* Performs the batch for the given statement, and checks that the specified amount of rows have been changed.
*
* @param statement The prepared statement
* @param numRows The number of rows that should change
* @param table The changed table
*/
private void executeBatch(PreparedStatement statement, int numRows, Table table) throws DatabaseOperationException
{
if (statement != null)
{
try
{
Connection connection = statement.getConnection();
beforeInsert(connection, table);
int[] results = statement.executeBatch();
closeStatement(statement);
afterInsert(connection, table);
boolean hasSum = true;
int sum = 0;
for (int idx = 0; (results != null) && (idx < results.length); idx++)
{
if (results[idx] < 0)
{
hasSum = false;
if (results[idx] == Statement.EXECUTE_FAILED)
{
_log.warn("The batch insertion of row " + idx + " into table " + table.getName() + " failed but the driver is able to continue processing");
}
else if (results[idx] != Statement.SUCCESS_NO_INFO)
{
_log.warn("The batch insertion of row " + idx + " into table " + table.getName() + " returned an undefined status value " + results[idx]);
}
}
else
{
sum += results[idx];
}
}
if (hasSum && (sum != numRows))
{
_log.warn("Attempted to insert " + numRows + " rows into table " + table.getName() + " but changed " + sum + " rows");
}
}
catch (SQLException ex)
{
if (ex instanceof BatchUpdateException)
{
SQLException sqlEx = ((BatchUpdateException)ex).getNextException();
throw new DatabaseOperationException("Error while inserting into the database", sqlEx);
}
else
{
throw new DatabaseOperationException("Error while inserting into the database", ex);
}
}
}
}
/**
* {@inheritDoc}
*/
public void insert(Database model, Collection dynaBeans) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
insert(connection, model, dynaBeans);
}
finally
{
returnConnection(connection);
}
}
/**
* Allows platforms to issue statements directly before rows are inserted into
* the specified table.
*
* @param connection The connection used for the insertion
* @param table The table that the rows are inserted into
*/
protected void beforeInsert(Connection connection, Table table) throws SQLException
{
}
/**
* Allows platforms to issue statements directly after rows have been inserted into
* the specified table.
*
* @param connection The connection used for the insertion
* @param table The table that the rows have been inserted into
*/
protected void afterInsert(Connection connection, Table table) throws SQLException
{
}
/**
* Creates the SQL for updating an object of the given type. If a concrete bean is given,
* then a concrete update statement is created, otherwise an update statement usable in a
* prepared statement is build.
*
* @param model The database model
* @param dynaClass The type
* @param primaryKeys The primary keys
* @param properties The properties to write
* @param bean Optionally the concrete bean to update
* @return The SQL required to update the instance
*/
protected String createUpdateSql(Database model, SqlDynaClass dynaClass, SqlDynaProperty[] primaryKeys, SqlDynaProperty[] properties, DynaBean bean)
{
Table table = model.findTable(dynaClass.getTableName());
HashMap columnValues = toColumnValues(properties, bean);
columnValues.putAll(toColumnValues(primaryKeys, bean));
return _builder.getUpdateSql(table, columnValues, bean == null);
}
/**
* Creates the SQL for updating an object of the given type. If a concrete bean is given,
* then a concrete update statement is created, otherwise an update statement usable in a
* prepared statement is build.
*
* @param model The database model
* @param dynaClass The type
* @param primaryKeys The primary keys
* @param properties The properties to write
* @param oldBean Contains column values to identify the rows to update (i.e. for the WHERE clause)
* @param newBean Contains the new column values to write
* @return The SQL required to update the instance
*/
protected String createUpdateSql(Database model, SqlDynaClass dynaClass, SqlDynaProperty[] primaryKeys, SqlDynaProperty[] properties, DynaBean oldBean, DynaBean newBean)
{
Table table = model.findTable(dynaClass.getTableName());
HashMap oldColumnValues = toColumnValues(primaryKeys, oldBean);
HashMap newColumnValues = toColumnValues(properties, newBean);
if (primaryKeys.length == 0)
{
_log.info("Cannot update instances of type " + dynaClass + " because it has no primary keys");
return null;
}
else
{
return _builder.getUpdateSql(table, oldColumnValues, newColumnValues, newBean == null);
}
}
/**
* {@inheritDoc}
*/
public String getUpdateSql(Database model, DynaBean dynaBean)
{
SqlDynaClass dynaClass = model.getDynaClassFor(dynaBean);
SqlDynaProperty[] primaryKeys = dynaClass.getPrimaryKeyProperties();
SqlDynaProperty[] nonPrimaryKeys = dynaClass.getNonPrimaryKeyProperties();
if (primaryKeys.length == 0)
{
_log.info("Cannot update instances of type " + dynaClass + " because it has no primary keys");
return null;
}
else
{
return createUpdateSql(model, dynaClass, primaryKeys, nonPrimaryKeys, dynaBean);
}
}
/**
* {@inheritDoc}
*/
public String getUpdateSql(Database model, DynaBean oldDynaBean, DynaBean newDynaBean)
{
SqlDynaClass dynaClass = model.getDynaClassFor(oldDynaBean);
SqlDynaProperty[] primaryKeys = dynaClass.getPrimaryKeyProperties();
SqlDynaProperty[] nonPrimaryKeys = dynaClass.getNonPrimaryKeyProperties();
if (primaryKeys.length == 0)
{
_log.info("Cannot update instances of type " + dynaClass + " because it has no primary keys");
return null;
}
else
{
return createUpdateSql(model, dynaClass, primaryKeys, nonPrimaryKeys, oldDynaBean, newDynaBean);
}
}
/**
* {@inheritDoc}
*/
public void update(Connection connection, Database model, DynaBean dynaBean) throws DatabaseOperationException
{
SqlDynaClass dynaClass = model.getDynaClassFor(dynaBean);
SqlDynaProperty[] primaryKeys = dynaClass.getPrimaryKeyProperties();
if (primaryKeys.length == 0)
{
_log.info("Cannot update instances of type " + dynaClass + " because it has no primary keys");
return;
}
SqlDynaProperty[] properties = dynaClass.getNonPrimaryKeyProperties();
String sql = createUpdateSql(model, dynaClass, primaryKeys, properties, null);
PreparedStatement statement = null;
if (_log.isDebugEnabled())
{
_log.debug("About to execute SQL: " + sql);
}
try
{
beforeUpdate(connection, dynaClass.getTable());
statement = connection.prepareStatement(sql);
int sqlIndex = 1;
for (int idx = 0; idx < properties.length; idx++)
{
setObject(statement, sqlIndex++, dynaBean, properties[idx]);
}
for (int idx = 0; idx < primaryKeys.length; idx++)
{
setObject(statement, sqlIndex++, dynaBean, primaryKeys[idx]);
}
int count = statement.executeUpdate();
afterUpdate(connection, dynaClass.getTable());
if (count != 1)
{
_log.warn("Attempted to insert a single row " + dynaBean +
" into table " + dynaClass.getTableName() +
" but changed " + count + " row(s)");
}
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while updating in the database", ex);
}
finally
{
closeStatement(statement);
}
}
/**
* {@inheritDoc}
*/
public void update(Database model, DynaBean dynaBean) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
update(connection, model, dynaBean);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void update(Connection connection, Database model, DynaBean oldDynaBean, DynaBean newDynaBean) throws DatabaseOperationException
{
SqlDynaClass dynaClass = model.getDynaClassFor(oldDynaBean);
SqlDynaProperty[] primaryKeys = dynaClass.getPrimaryKeyProperties();
if (!dynaClass.getTable().equals(model.getDynaClassFor(newDynaBean).getTable()))
{
throw new DatabaseOperationException("The old and new dyna beans need to be for the same table");
}
if (primaryKeys.length == 0)
{
_log.info("Cannot update instances of type " + dynaClass + " because it has no primary keys");
return;
}
SqlDynaProperty[] properties = dynaClass.getSqlDynaProperties();
String sql = createUpdateSql(model, dynaClass, primaryKeys, properties, null, null);
PreparedStatement statement = null;
if (_log.isDebugEnabled())
{
_log.debug("About to execute SQL: " + sql);
}
try
{
beforeUpdate(connection, dynaClass.getTable());
statement = connection.prepareStatement(sql);
int sqlIndex = 1;
for (int idx = 0; idx < properties.length; idx++)
{
setObject(statement, sqlIndex++, newDynaBean, properties[idx]);
}
for (int idx = 0; idx < primaryKeys.length; idx++)
{
setObject(statement, sqlIndex++, oldDynaBean, primaryKeys[idx]);
}
int count = statement.executeUpdate();
afterUpdate(connection, dynaClass.getTable());
if (count != 1)
{
_log.warn("Attempted to insert a single row " + newDynaBean +
" into table " + dynaClass.getTableName() +
" but changed " + count + " row(s)");
}
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while updating in the database", ex);
}
finally
{
closeStatement(statement);
}
}
/**
* {@inheritDoc}
*/
public void update(Database model, DynaBean oldDynaBean, DynaBean newDynaBean) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
update(connection, model, oldDynaBean, newDynaBean);
}
finally
{
returnConnection(connection);
}
}
/**
* Allows platforms to issue statements directly before rows are updated in
* the specified table.
*
* @param connection The connection used for the update
* @param table The table that the rows are updateed into
*/
protected void beforeUpdate(Connection connection, Table table) throws SQLException
{
}
/**
* Allows platforms to issue statements directly after rows have been updated in
* the specified table.
*
* @param connection The connection used for the update
* @param table The table that the rows have been updateed into
*/
protected void afterUpdate(Connection connection, Table table) throws SQLException
{
}
/**
* {@inheritDoc}
*/
public boolean exists(Database model, DynaBean dynaBean)
{
Connection connection = borrowConnection();
try
{
return exists(connection, model, dynaBean);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public boolean exists(Connection connection, Database model, DynaBean dynaBean)
{
SqlDynaClass dynaClass = model.getDynaClassFor(dynaBean);
SqlDynaProperty[] primaryKeys = dynaClass.getPrimaryKeyProperties();
if (primaryKeys.length == 0)
{
return false;
}
PreparedStatement stmt = null;
try
{
StringBuffer sql = new StringBuffer();
sql.append("SELECT * FROM ");
sql.append(_builder.getDelimitedIdentifier(dynaClass.getTable().getName()));
sql.append(" WHERE ");
for (int idx = 0; idx < primaryKeys.length; idx++)
{
String key = primaryKeys[idx].getColumn().getName();
if (idx > 0)
{
sql.append(" AND ");
}
sql.append(_builder.getDelimitedIdentifier(key));
sql.append("=?");
}
stmt = connection.prepareStatement(sql.toString());
for (int idx = 0; idx < primaryKeys.length; idx++)
{
setObject(stmt, idx + 1, dynaBean, primaryKeys[idx]);
}
ResultSet resultSet = stmt.executeQuery();
return resultSet.next();
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while reading from the database", ex);
}
finally
{
closeStatement(stmt);
}
}
/**
* {@inheritDoc}
*/
public void store(Database model, DynaBean dynaBean) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
store(connection, model, dynaBean);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void store(Connection connection, Database model, DynaBean dynaBean) throws DatabaseOperationException
{
if (exists(connection, model, dynaBean))
{
update(connection, model, dynaBean);
}
else
{
insert(connection, model, dynaBean);
}
}
/**
* Creates the SQL for deleting an object of the given type. If a concrete bean is given,
* then a concrete delete statement is created, otherwise a delete statement usable in a
* prepared statement is build.
*
* @param model The database model
* @param dynaClass The type
* @param primaryKeys The primary keys
* @param bean Optionally the concrete bean to update
* @return The SQL required to delete the instance
*/
protected String createDeleteSql(Database model, SqlDynaClass dynaClass, SqlDynaProperty[] primaryKeys, DynaBean bean)
{
Table table = model.findTable(dynaClass.getTableName());
HashMap pkValues = toColumnValues(primaryKeys, bean);
return _builder.getDeleteSql(table, pkValues, bean == null);
}
/**
* {@inheritDoc}
*/
public String getDeleteSql(Database model, DynaBean dynaBean)
{
SqlDynaClass dynaClass = model.getDynaClassFor(dynaBean);
SqlDynaProperty[] primaryKeys = dynaClass.getPrimaryKeyProperties();
if (primaryKeys.length == 0)
{
_log.warn("Cannot delete instances of type " + dynaClass + " because it has no primary keys");
return null;
}
else
{
return createDeleteSql(model, dynaClass, primaryKeys, dynaBean);
}
}
/**
* {@inheritDoc}
*/
public void delete(Database model, DynaBean dynaBean) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
delete(connection, model, dynaBean);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public void delete(Connection connection, Database model, DynaBean dynaBean) throws DatabaseOperationException
{
PreparedStatement statement = null;
try
{
SqlDynaClass dynaClass = model.getDynaClassFor(dynaBean);
SqlDynaProperty[] primaryKeys = dynaClass.getPrimaryKeyProperties();
if (primaryKeys.length == 0)
{
_log.warn("Cannot delete instances of type " + dynaClass + " because it has no primary keys");
return;
}
String sql = createDeleteSql(model, dynaClass, primaryKeys, null);
if (_log.isDebugEnabled())
{
_log.debug("About to execute SQL " + sql);
}
statement = connection.prepareStatement(sql);
for (int idx = 0; idx < primaryKeys.length; idx++)
{
setObject(statement, idx + 1, dynaBean, primaryKeys[idx]);
}
int count = statement.executeUpdate();
if (count != 1)
{
_log.warn("Attempted to delete a single row " + dynaBean +
" in table " + dynaClass.getTableName() +
" but changed " + count + " row(s).");
}
}
catch (SQLException ex)
{
throw new DatabaseOperationException("Error while deleting from the database", ex);
}
finally
{
closeStatement(statement);
}
}
/**
* {@inheritDoc}
*/
public Database readModelFromDatabase(String name) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
return readModelFromDatabase(connection, name);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public Database readModelFromDatabase(Connection connection, String name) throws DatabaseOperationException
{
try
{
Database model = getModelReader().getDatabase(connection, name);
postprocessModelFromDatabase(model);
return model;
}
catch (SQLException ex)
{
throw new DatabaseOperationException(ex);
}
}
/**
* {@inheritDoc}
*/
public Database readModelFromDatabase(String name, String catalog, String schema, String[] tableTypes) throws DatabaseOperationException
{
Connection connection = borrowConnection();
try
{
return readModelFromDatabase(connection, name, catalog, schema, tableTypes);
}
finally
{
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
public Database readModelFromDatabase(Connection connection, String name, String catalog, String schema, String[] tableTypes) throws DatabaseOperationException
{
try
{
JdbcModelReader reader = getModelReader();
Database model = reader.getDatabase(connection, name, catalog, schema, tableTypes);
postprocessModelFromDatabase(model);
if ((model.getName() == null) || (model.getName().length() == 0))
{
model.setName(MODEL_DEFAULT_NAME);
}
return model;
}
catch (SQLException ex)
{
throw new DatabaseOperationException(ex);
}
}
/**
* Allows the platform to postprocess the model just read from the database.
*
* @param model The model
*/
protected void postprocessModelFromDatabase(Database model)
{
// Default values for CHAR/VARCHAR/LONGVARCHAR columns have quotation marks
// around them which we'll remove now
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);
if (TypeMap.isTextType(column.getTypeCode()) ||
TypeMap.isDateTimeType(column.getTypeCode()))
{
String defaultValue = column.getDefaultValue();
if ((defaultValue != null) && (defaultValue.length() >= 2) &&
defaultValue.startsWith("'") && defaultValue.endsWith("'"))
{
defaultValue = defaultValue.substring(1, defaultValue.length() - 1);
column.setDefaultValue(defaultValue);
}
}
}
}
}
/**
* Derives the column values for the given dyna properties from the dyna bean.
*
* @param properties The properties
* @param bean The bean
* @return The values indexed by the column names
*/
protected HashMap toColumnValues(SqlDynaProperty[] properties, DynaBean bean)
{
HashMap result = new HashMap();
for (int idx = 0; idx < properties.length; idx++)
{
result.put(properties[idx].getName(),
bean == null ? null : bean.get(properties[idx].getName()));
}
return result;
}
/**
* Sets a parameter of the prepared statement based on the type of the column of the property.
*
* @param statement The statement
* @param sqlIndex The index of the parameter to set in the statement
* @param dynaBean The bean of which to take the value
* @param property The property of the bean, which also defines the corresponding column
*/
protected void setObject(PreparedStatement statement, int sqlIndex, DynaBean dynaBean, SqlDynaProperty property) throws SQLException
{
int typeCode = property.getColumn().getTypeCode();
Object value = dynaBean.get(property.getName());
setStatementParameterValue(statement, sqlIndex, typeCode, value);
}
/**
* This is the core method to set the parameter of a prepared statement to a given value.
* The primary purpose of this method is to call the appropriate method on the statement,
* and to give database-specific implementations the ability to change this behavior.
*
* @param statement The statement
* @param sqlIndex The parameter index
* @param typeCode The JDBC type code
* @param value The value
* @throws SQLException If an error occurred while setting the parameter value
*/
protected void setStatementParameterValue(PreparedStatement statement, int sqlIndex, int typeCode, Object value) throws SQLException
{
if (value == null)
{
statement.setNull(sqlIndex, typeCode);
}
else if (value instanceof String)
{
statement.setString(sqlIndex, (String)value);
}
else if (value instanceof byte[])
{
statement.setBytes(sqlIndex, (byte[])value);
}
else if (value instanceof Boolean)
{
statement.setBoolean(sqlIndex, ((Boolean)value).booleanValue());
}
else if (value instanceof Byte)
{
statement.setByte(sqlIndex, ((Byte)value).byteValue());
}
else if (value instanceof Short)
{
statement.setShort(sqlIndex, ((Short)value).shortValue());
}
else if (value instanceof Integer)
{
statement.setInt(sqlIndex, ((Integer)value).intValue());
}
else if (value instanceof Long)
{
statement.setLong(sqlIndex, ((Long)value).longValue());
}
else if (value instanceof BigDecimal)
{
// setObject assumes a scale of 0, so we rather use the typed setter
statement.setBigDecimal(sqlIndex, (BigDecimal)value);
}
else if (value instanceof Float)
{
statement.setFloat(sqlIndex, ((Float)value).floatValue());
}
else if (value instanceof Double)
{
statement.setDouble(sqlIndex, ((Double)value).doubleValue());
}
else
{
statement.setObject(sqlIndex, value, typeCode);
}
}
/**
* Helper method esp. for the {@link ModelBasedResultSetIterator} class that retrieves
* the value for a column from the given result set. If a table was specified,
* and it contains the column, then the jdbc type defined for the column is used for extracting
* the value, otherwise the object directly retrieved from the result set is returned.<br/>
* The method is defined here rather than in the {@link ModelBasedResultSetIterator} class
* so that concrete platforms can modify its behavior.
*
* @param resultSet The result set
* @param columnName The name of the column
* @param table The table
* @return The value
*/
protected Object getObjectFromResultSet(ResultSet resultSet, String columnName, Table table) throws SQLException
{
Column column = (table == null ? null : table.findColumn(columnName, isDelimitedIdentifierModeOn()));
Object value = null;
if (column != null)
{
int originalJdbcType = column.getTypeCode();
int targetJdbcType = getPlatformInfo().getTargetJdbcType(originalJdbcType);
int jdbcType = originalJdbcType;
// in general we're trying to retrieve the value using the original type
// but sometimes we also need the target type:
if ((originalJdbcType == Types.BLOB) && (targetJdbcType != Types.BLOB))
{
// we should not use the Blob interface if the database doesn't map to this type
jdbcType = targetJdbcType;
}
if ((originalJdbcType == Types.CLOB) && (targetJdbcType != Types.CLOB))
{
// we should not use the Clob interface if the database doesn't map to this type
jdbcType = targetJdbcType;
}
value = extractColumnValue(resultSet, columnName, 0, jdbcType);
}
else
{
value = resultSet.getObject(columnName);
}
return resultSet.wasNull() ? null : value;
}
/**
* Helper method for retrieving the value for a column from the given result set
* using the type code of the column.
*
* @param resultSet The result set
* @param column The column
* @param idx The value's index in the result set (starting from 1)
* @return The value
*/
protected Object getObjectFromResultSet(ResultSet resultSet, Column column, int idx) throws SQLException
{
int originalJdbcType = column.getTypeCode();
int targetJdbcType = getPlatformInfo().getTargetJdbcType(originalJdbcType);
int jdbcType = originalJdbcType;
Object value = null;
// in general we're trying to retrieve the value using the original type
// but sometimes we also need the target type:
if ((originalJdbcType == Types.BLOB) && (targetJdbcType != Types.BLOB))
{
// we should not use the Blob interface if the database doesn't map to this type
jdbcType = targetJdbcType;
}
if ((originalJdbcType == Types.CLOB) && (targetJdbcType != Types.CLOB))
{
// we should not use the Clob interface if the database doesn't map to this type
jdbcType = targetJdbcType;
}
value = extractColumnValue(resultSet, null, idx, jdbcType);
return resultSet.wasNull() ? null : value;
}
/**
* This is the core method to retrieve a value for a column from a result set. Its primary
* purpose is to call the appropriate method on the result set, and to provide an extension
* point where database-specific implementations can change this behavior.
*
* @param resultSet The result set to extract the value from
* @param columnName The name of the column; can be <code>null</code> in which case the
* <code>columnIdx</code> will be used instead
* @param columnIdx The index of the column's value in the result set; is only used if
* <code>columnName</code> is <code>null</code>
* @param jdbcType The jdbc type to extract
* @return The value
* @throws SQLException If an error occurred while accessing the result set
*/
protected Object extractColumnValue(ResultSet resultSet, String columnName, int columnIdx, int jdbcType) throws SQLException
{
boolean useIdx = (columnName == null);
Object value;
switch (jdbcType)
{
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
value = useIdx ? resultSet.getString(columnIdx) : resultSet.getString(columnName);
break;
case Types.NUMERIC:
case Types.DECIMAL:
value = useIdx ? resultSet.getBigDecimal(columnIdx) : resultSet.getBigDecimal(columnName);
break;
case Types.BIT:
case Types.BOOLEAN:
value = new Boolean(useIdx ? resultSet.getBoolean(columnIdx) : resultSet.getBoolean(columnName));
break;
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
value = new Integer(useIdx ? resultSet.getInt(columnIdx) : resultSet.getInt(columnName));
break;
case Types.BIGINT:
value = new Long(useIdx ? resultSet.getLong(columnIdx) : resultSet.getLong(columnName));
break;
case Types.REAL:
value = new Float(useIdx ? resultSet.getFloat(columnIdx) : resultSet.getFloat(columnName));
break;
case Types.FLOAT:
case Types.DOUBLE:
value = new Double(useIdx ? resultSet.getDouble(columnIdx) : resultSet.getDouble(columnName));
break;
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
value = useIdx ? resultSet.getBytes(columnIdx) : resultSet.getBytes(columnName);
break;
case Types.DATE:
value = useIdx ? resultSet.getDate(columnIdx) : resultSet.getDate(columnName);
break;
case Types.TIME:
value = useIdx ? resultSet.getTime(columnIdx) : resultSet.getTime(columnName);
break;
case Types.TIMESTAMP:
value = useIdx ? resultSet.getTimestamp(columnIdx) : resultSet.getTimestamp(columnName);
break;
case Types.CLOB:
Clob clob = useIdx ? resultSet.getClob(columnIdx) : resultSet.getClob(columnName);
if (clob == null)
{
value = null;
}
else
{
long length = clob.length();
if (length > Integer.MAX_VALUE)
{
value = clob;
}
else if (length == 0)
{
// the javadoc is not clear about whether Clob.getSubString
// can be used with a substring length of 0
// thus we do the safe thing and handle it ourselves
value = "";
}
else
{
value = clob.getSubString(1l, (int)length);
}
}
break;
case Types.BLOB:
Blob blob = useIdx ? resultSet.getBlob(columnIdx) : resultSet.getBlob(columnName);
if (blob == null)
{
value = null;
}
else
{
long length = blob.length();
if (length > Integer.MAX_VALUE)
{
value = blob;
}
else if (length == 0)
{
// the javadoc is not clear about whether Blob.getBytes
// can be used with for 0 bytes to be copied
// thus we do the safe thing and handle it ourselves
value = new byte[0];
}
else
{
value = blob.getBytes(1l, (int)length);
}
}
break;
case Types.ARRAY:
value = useIdx ? resultSet.getArray(columnIdx) : resultSet.getArray(columnName);
break;
case Types.REF:
value = useIdx ? resultSet.getRef(columnIdx) : resultSet.getRef(columnName);
break;
default:
value = useIdx ? resultSet.getObject(columnIdx) : resultSet.getObject(columnName);
break;
}
return resultSet.wasNull() ? null : value;
}
/**
* Creates an iterator over the given result set.
*
* @param model The database model
* @param resultSet The result set to iterate over
* @param queryHints The tables that were queried in the query that produced the
* given result set (optional)
* @return The iterator
*/
protected ModelBasedResultSetIterator createResultSetIterator(Database model, ResultSet resultSet, Table[] queryHints)
{
return new ModelBasedResultSetIterator(this, model, resultSet, queryHints, true);
}
}