enhancement
major
major
major
#29846
Changing the type of a column with content fails on Oracle (ORA-01439) and of an indexed column on MS SQL Server
Problem
SQLFactory.modifyColumnType(...) (SQLModifyColumn with ModificationAspect.TYPE) is rendered as a plain ALTER TABLE ... MODIFY / ALTER COLUMN statement. This fails in two cases:
Oracle: column with content
Oracle only accepts ALTER TABLE <table> MODIFY "<column>" <new type> for compatible changes (e.g. enlarging a NVARCHAR2). A real change of the data type (e.g. NUMBER -> NVARCHAR2, or VARCHAR2 -> NVARCHAR2 for a binary -> non-binary string) is rejected as soon as the column contains data:
java.sql.SQLException: ORA-01439: column to be modified must be empty to change datatype ALTER TABLE TestDBConcurrentAccess MODIFY "b" NVARCHAR2(60)
MS SQL Server: indexed column
MS SQL Server does not change the type of a column that is part of an index:
java.sql.SQLException: The index 'testIndex' is dependent on column 'b'. ALTER TABLE TestDBConcurrentAccess ALTER COLUMN "b" NVARCHAR(60) COLLATE Latin1_General_CI_AS NULL
The statement is also used by migrations on productive data (AlterColumnProcessor, RecollateStringColumnsProcessor), so such migrations fail on these databases.
Schema extraction misses indexes on Oracle
SchemaExtraction.addTable looked up the indexes of a table with the plain table name, while columns and primary key are looked up with DBHelper.tablePattern(String) (upper case on Oracle and DB2). Therefore, no indexes were extracted on Oracle and DB2 for tables whose name is not given in upper case.
Solution
Oracle
OracleHelper.appendChangeColumnType now renders a PL/SQL block. It first executes the plain MODIFY as before, so compatible changes behave unchanged. Only if this fails with ORA-01439, the column is changed by clearing and refilling it:
- Add a temporary column tl_tmp_type_change of the new type and copy the values into it (implicit conversion).
- Remove a NOT NULL constraint of the column (remembering whether it existed) and set the column to NULL.
- Execute the MODIFY again (now allowed, since the column is empty).
- Copy the values back, drop the temporary column, and restore the NOT NULL constraint if there was one.
This keeps the position, the indexes and the constraints of the column.
Limitations: Oracle commits each DDL statement, so a failure within the fallback (e.g. a value that cannot be converted to the new type) may leave the temporary column behind. A column that is part of a primary key or referenced by a foreign key cannot be cleared, so its type change still fails.
MS SQL Server
MSSQLHelper.appendChangeColumnType now renders a T-SQL batch that reads the definitions of all indexes containing the column from the system catalog (unique, clustered / non-clustered, key columns with order, included columns, filter), drops these indexes, executes the ALTER COLUMN, and re-creates the indexes. Since DDL is transactional in MS SQL Server, a failure rolls back all changes.
Limitation: Indexes backing a primary key or unique constraint are not handled, the type change of such a column still fails.
Schema extraction
SchemaExtraction.addTable uses DBHelper.tablePattern(String) for the index lookup, too.
Tests
test.com.top_logic.basic.db.model.sql.TestAlterTable modifies a table containing 5 rows: rename, drop column, set mandatory, and type changes of a nullable column (incl. NULL values), a mandatory column, to and from a binary string column, and of a column contained in a unique index (index must be kept).