blob: a9e4a68056a5732d125311d84897ea4c90fe0403 [file] [log] [blame]
package org.apache.ddlutils.platform.sybase;
/*
* 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.ddlutils.DatabaseOperationException;
import org.apache.ddlutils.DdlUtilsException;
import org.apache.ddlutils.PlatformInfo;
import org.apache.ddlutils.alteration.AddColumnChange;
import org.apache.ddlutils.alteration.AddPrimaryKeyChange;
import org.apache.ddlutils.alteration.ColumnDefinitionChange;
import org.apache.ddlutils.alteration.ModelComparator;
import org.apache.ddlutils.alteration.RemoveColumnChange;
import org.apache.ddlutils.alteration.RemovePrimaryKeyChange;
import org.apache.ddlutils.alteration.TableChange;
import org.apache.ddlutils.alteration.TableDefinitionChangesPredicate;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.Table;
import org.apache.ddlutils.model.TypeMap;
import org.apache.ddlutils.platform.CreationParameters;
import org.apache.ddlutils.platform.DefaultTableDefinitionChangesPredicate;
import org.apache.ddlutils.platform.PlatformImplBase;
/**
* The platform implementation for Sybase.
*
* @version $Revision: 231306 $
*/
public class SybasePlatform extends PlatformImplBase
{
/** Database name of this platform. */
public static final String DATABASENAME = "Sybase";
/** The standard Sybase jdbc driver. */
public static final String JDBC_DRIVER = "com.sybase.jdbc2.jdbc.SybDriver";
/** The old Sybase jdbc driver. */
public static final String JDBC_DRIVER_OLD = "com.sybase.jdbc.SybDriver";
/** The subprotocol used by the standard Sybase driver. */
public static final String JDBC_SUBPROTOCOL = "sybase:Tds";
/** The maximum size that text and binary columns can have. */
public static final long MAX_TEXT_SIZE = 2147483647;
/**
* Creates a new platform instance.
*/
public SybasePlatform()
{
PlatformInfo info = getPlatformInfo();
info.setMaxIdentifierLength(28);
info.setNullAsDefaultValueRequired(true);
info.setIdentityColumnAutomaticallyRequired(true);
info.setCommentPrefix("/*");
info.setCommentSuffix("*/");
info.addNativeTypeMapping(Types.ARRAY, "IMAGE");
// BIGINT is mapped back in the model reader
info.addNativeTypeMapping(Types.BIGINT, "DECIMAL(19,0)");
// we're not using the native BIT type because it is rather limited (cannot be NULL, cannot be indexed)
info.addNativeTypeMapping(Types.BIT, "SMALLINT", Types.SMALLINT);
info.addNativeTypeMapping(Types.BLOB, "IMAGE", Types.LONGVARBINARY);
info.addNativeTypeMapping(Types.CLOB, "TEXT", Types.LONGVARCHAR);
info.addNativeTypeMapping(Types.DATE, "DATETIME", Types.TIMESTAMP);
info.addNativeTypeMapping(Types.DISTINCT, "IMAGE", Types.LONGVARBINARY);
info.addNativeTypeMapping(Types.DOUBLE, "DOUBLE PRECISION");
info.addNativeTypeMapping(Types.FLOAT, "DOUBLE PRECISION", Types.DOUBLE);
info.addNativeTypeMapping(Types.INTEGER, "INT");
info.addNativeTypeMapping(Types.JAVA_OBJECT, "IMAGE", Types.LONGVARBINARY);
info.addNativeTypeMapping(Types.LONGVARBINARY, "IMAGE");
info.addNativeTypeMapping(Types.LONGVARCHAR, "TEXT");
info.addNativeTypeMapping(Types.NULL, "IMAGE", Types.LONGVARBINARY);
info.addNativeTypeMapping(Types.OTHER, "IMAGE", Types.LONGVARBINARY);
info.addNativeTypeMapping(Types.REF, "IMAGE", Types.LONGVARBINARY);
info.addNativeTypeMapping(Types.STRUCT, "IMAGE", Types.LONGVARBINARY);
info.addNativeTypeMapping(Types.TIME, "DATETIME", Types.TIMESTAMP);
info.addNativeTypeMapping(Types.TIMESTAMP, "DATETIME", Types.TIMESTAMP);
info.addNativeTypeMapping(Types.TINYINT, "SMALLINT", Types.SMALLINT);
info.addNativeTypeMapping("BOOLEAN", "SMALLINT", "SMALLINT");
info.addNativeTypeMapping("DATALINK", "IMAGE", "LONGVARBINARY");
info.setDefaultSize(Types.BINARY, 254);
info.setDefaultSize(Types.VARBINARY, 254);
info.setDefaultSize(Types.CHAR, 254);
info.setDefaultSize(Types.VARCHAR, 254);
setSqlBuilder(new SybaseBuilder(this));
setModelReader(new SybaseModelReader(this));
}
/**
* {@inheritDoc}
*/
public String getName()
{
return DATABASENAME;
}
/**
* Sets the text size which is the maximum amount of bytes that Sybase returns in a SELECT statement
* for binary/text columns (e.g. blob, longvarchar etc.).
*
* @param size The size to set
*/
private void setTextSize(long size)
{
Connection connection = borrowConnection();
Statement stmt = null;
try
{
stmt = connection.createStatement();
stmt.execute("SET textsize "+size);
}
catch (SQLException ex)
{
throw new DatabaseOperationException(ex);
}
finally
{
closeStatement(stmt);
returnConnection(connection);
}
}
/**
* {@inheritDoc}
*/
protected Object extractColumnValue(ResultSet resultSet, String columnName, int columnIdx, int jdbcType) throws DatabaseOperationException, SQLException
{
boolean useIdx = (columnName == null);
if ((jdbcType == Types.LONGVARBINARY) || (jdbcType == Types.BLOB))
{
InputStream stream = useIdx ? resultSet.getBinaryStream(columnIdx) : resultSet.getBinaryStream(columnName);
if (stream == null)
{
return null;
}
else
{
byte[] buf = new byte[65536];
byte[] result = new byte[0];
int len;
try
{
do
{
len = stream.read(buf);
if (len > 0)
{
byte[] newResult = new byte[result.length + len];
System.arraycopy(result, 0, newResult, 0, result.length);
System.arraycopy(buf, 0, newResult, result.length, len);
result = newResult;
}
}
while (len > 0);
stream.close();
return result;
}
catch (IOException ex)
{
throw new DatabaseOperationException("Error while extracting the value of column " + columnName + " of type " +
TypeMap.getJdbcTypeName(jdbcType) + " from a result set", ex);
}
}
}
else
{
return super.extractColumnValue(resultSet, columnName, columnIdx, jdbcType);
}
}
/**
* {@inheritDoc}
*/
protected void setStatementParameterValue(PreparedStatement statement, int sqlIndex, int typeCode, Object value) throws SQLException
{
if ((typeCode == Types.BLOB) || (typeCode == Types.LONGVARBINARY))
{
// jConnect doesn't like the BLOB type, but works without problems with LONGVARBINARY
// even when using the Blob class
if (value instanceof byte[])
{
byte[] data = (byte[])value;
statement.setBinaryStream(sqlIndex, new ByteArrayInputStream(data), data.length);
}
else
{
// Sybase doesn't like the BLOB type, but works without problems with LONGVARBINARY
// even when using the Blob class
super.setStatementParameterValue(statement, sqlIndex, Types.LONGVARBINARY, value);
}
}
else if (typeCode == Types.CLOB)
{
// Same for CLOB and LONGVARCHAR
super.setStatementParameterValue(statement, sqlIndex, Types.LONGVARCHAR, value);
}
else
{
super.setStatementParameterValue(statement, sqlIndex, typeCode, value);
}
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, Collection parameters, Table[] queryHints, int start, int end) throws DatabaseOperationException
{
setTextSize(MAX_TEXT_SIZE);
return super.fetch(model, sql, parameters, queryHints, start, end);
}
/**
* {@inheritDoc}
*/
public List fetch(Database model, String sql, Table[] queryHints, int start, int end) throws DatabaseOperationException
{
setTextSize(MAX_TEXT_SIZE);
return super.fetch(model, sql, queryHints, start, end);
}
/**
* {@inheritDoc}
*/
public Iterator query(Database model, String sql, Collection parameters, Table[] queryHints) throws DatabaseOperationException
{
setTextSize(MAX_TEXT_SIZE);
return super.query(model, sql, parameters, queryHints);
}
/**
* {@inheritDoc}
*/
public Iterator query(Database model, String sql, Table[] queryHints) throws DatabaseOperationException
{
setTextSize(MAX_TEXT_SIZE);
return super.query(model, sql, queryHints);
}
/**
* Determines whether we need to use identity override mode for the given table.
*
* @param table The table
* @return <code>true</code> if identity override mode is needed
*/
private boolean useIdentityOverrideFor(Table table)
{
return isIdentityOverrideOn() &&
getPlatformInfo().isIdentityOverrideAllowed() &&
(table.getAutoIncrementColumns().length > 0);
}
/**
* {@inheritDoc}
*/
protected void beforeInsert(Connection connection, Table table) throws SQLException
{
if (useIdentityOverrideFor(table))
{
SybaseBuilder builder = (SybaseBuilder)getSqlBuilder();
String quotationOn = builder.getQuotationOnStatement();
String identityInsertOn = builder.getEnableIdentityOverrideSql(table);
Statement stmt = connection.createStatement();
if (quotationOn.length() > 0)
{
stmt.execute(quotationOn);
}
stmt.execute(identityInsertOn);
stmt.close();
}
}
/**
* {@inheritDoc}
*/
protected void afterInsert(Connection connection, Table table) throws SQLException
{
if (useIdentityOverrideFor(table))
{
SybaseBuilder builder = (SybaseBuilder)getSqlBuilder();
String quotationOn = builder.getQuotationOnStatement();
String identityInsertOff = builder.getDisableIdentityOverrideSql(table);
Statement stmt = connection.createStatement();
if (quotationOn.length() > 0)
{
stmt.execute(quotationOn);
}
stmt.execute(identityInsertOff);
stmt.close();
}
}
/**
* {@inheritDoc}
*/
protected void beforeUpdate(Connection connection, Table table) throws SQLException
{
beforeInsert(connection, table);
}
/**
* {@inheritDoc}
*/
protected void afterUpdate(Connection connection, Table table) throws SQLException
{
afterInsert(connection, table);
}
/**
* {@inheritDoc}
*/
protected ModelComparator getModelComparator()
{
ModelComparator comparator = super.getModelComparator();
comparator.setGeneratePrimaryKeyChanges(false);
comparator.setCanDropPrimaryKeyColumns(false);
return comparator;
}
/**
* {@inheritDoc}
*/
protected TableDefinitionChangesPredicate getTableDefinitionChangesPredicate()
{
return new DefaultTableDefinitionChangesPredicate()
{
protected boolean isSupported(Table intermediateTable, TableChange change)
{
if ((change instanceof RemoveColumnChange) ||
(change instanceof AddPrimaryKeyChange) ||
(change instanceof RemovePrimaryKeyChange))
{
return true;
}
else if (change instanceof AddColumnChange)
{
AddColumnChange addColumnChange = (AddColumnChange)change;
// Sybase can only add not insert columns, and they cannot be IDENTITY columns
// We also have to force recreation of the table if a required column is added
// that is neither IDENTITY nor has a default value
return (addColumnChange.getNextColumn() == null) &&
!addColumnChange.getNewColumn().isAutoIncrement() &&
(!addColumnChange.getNewColumn().isRequired() || !StringUtils.isEmpty(addColumnChange.getNewColumn().getDefaultValue()));
}
else if (change instanceof ColumnDefinitionChange)
{
ColumnDefinitionChange columnChange = (ColumnDefinitionChange)change;
Column oldColumn = intermediateTable.findColumn(columnChange.getChangedColumn(), isDelimitedIdentifierModeOn());
// Sybase cannot change the IDENTITY state of a column via ALTER TABLE MODIFY
return oldColumn.isAutoIncrement() == columnChange.getNewColumn().isAutoIncrement();
}
else
{
return false;
}
}
};
}
/**
* {@inheritDoc}
*/
protected Database processChanges(Database model, Collection changes, CreationParameters params) throws IOException, DdlUtilsException
{
if (!changes.isEmpty())
{
((SybaseBuilder)getSqlBuilder()).turnOnQuotation();
}
return super.processChanges(model, changes, params);
}
/**
* Processes the removal of a column from 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,
RemoveColumnChange change) throws IOException
{
Table changedTable = findChangedTable(currentModel, change);
Column removedColumn = changedTable.findColumn(change.getChangedColumn(), isDelimitedIdentifierModeOn());
((SybaseBuilder)getSqlBuilder()).dropColumn(changedTable, removedColumn);
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes the removal of a primary key from 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,
RemovePrimaryKeyChange change) throws IOException
{
Table changedTable = findChangedTable(currentModel, change);
((SybaseBuilder)getSqlBuilder()).dropPrimaryKey(changedTable);
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
/**
* Processes the change of a column definition..
*
* @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,
ColumnDefinitionChange change) throws IOException
{
Table changedTable = findChangedTable(currentModel, change);
Column changedColumn = changedTable.findColumn(change.getChangedColumn(), isDelimitedIdentifierModeOn());
Column newColumn = change.getNewColumn();
SybaseBuilder sqlBuilder = (SybaseBuilder)getSqlBuilder();
// if we only change the default value, then we need to use different SQL
if (!ColumnDefinitionChange.isTypeChanged(getPlatformInfo(), changedColumn, newColumn) &&
!ColumnDefinitionChange.isSizeChanged(getPlatformInfo(), changedColumn, newColumn) &&
!ColumnDefinitionChange.isRequiredStatusChanged(changedColumn, newColumn) &&
!ColumnDefinitionChange.isAutoIncrementChanged(changedColumn, newColumn))
{
sqlBuilder.changeColumnDefaultValue(changedTable, changedColumn, newColumn.getDefaultValue());
}
else
{
sqlBuilder.changeColumn(changedTable, changedColumn, newColumn);
}
change.apply(currentModel, isDelimitedIdentifierModeOn());
}
}