| /* |
| |
| Derby - Class org.apache.derby.impl.sql.compile.ModifyColumnNode |
| |
| 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. |
| |
| */ |
| |
| package org.apache.derby.impl.sql.compile; |
| |
| import org.apache.derby.catalog.UUID; |
| import org.apache.derby.catalog.types.DefaultInfoImpl; |
| import org.apache.derby.iapi.error.StandardException; |
| import org.apache.derby.iapi.reference.SQLState; |
| import org.apache.derby.iapi.services.context.ContextManager; |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptorList; |
| import org.apache.derby.iapi.sql.dictionary.DataDictionary; |
| import org.apache.derby.iapi.sql.dictionary.DefaultDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.KeyConstraintDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.TableDescriptor; |
| import org.apache.derby.iapi.types.DataTypeDescriptor; |
| import org.apache.derby.iapi.types.TypeId; |
| import org.apache.derby.impl.sql.execute.ColumnInfo; |
| |
| /** |
| * A ModifyColumnNode represents a modify column in an ALTER TABLE statement. |
| * |
| */ |
| |
| class ModifyColumnNode extends ColumnDefinitionNode |
| { |
| int columnPosition = -1; |
| UUID oldDefaultUUID; |
| |
| // Allowed kinds |
| final static int K_MODIFY_COLUMN_TYPE = 0; |
| final static int K_MODIFY_COLUMN_DEFAULT = 1; |
| final static int K_MODIFY_COLUMN_CONSTRAINT = 2; |
| final static int K_MODIFY_COLUMN_CONSTRAINT_NOT_NULL = 3; |
| final static int K_DROP_COLUMN = 4; |
| final static int K_MODIFY_COLUMN_GENERATED_ALWAYS = 5; |
| final static int K_MODIFY_COLUMN_GENERATED_BY_DEFAULT = 6; |
| |
| /** |
| * This class is used to hold logically different objects for |
| * space efficiency. {@code kind} represents the logical object |
| * type. See also {@link ValueNode#isSameNodeKind}. |
| */ |
| final int kind; |
| |
| ModifyColumnNode(int kind, |
| String name, |
| ValueNode defaultNode, |
| DataTypeDescriptor dataTypeServices, |
| long[] autoIncrementInfo, |
| ContextManager cm) throws StandardException { |
| super(name, defaultNode, dataTypeServices, autoIncrementInfo, cm); |
| this.kind = kind; |
| } |
| |
| /** |
| * Get the UUID of the old column default. |
| * |
| * @return The UUID of the old column default. |
| */ |
| @Override |
| UUID getOldDefaultUUID() |
| { |
| return oldDefaultUUID; |
| } |
| |
| /** |
| * Get the column position for the column. |
| * |
| * @return The column position for the column. |
| */ |
| int getColumnPosition() |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(columnPosition > 0, |
| "columnPosition expected to be > 0"); |
| } |
| return columnPosition; |
| } |
| |
| /** |
| * Check the validity of a user type. Checks that |
| * 1. the column type is either varchar, .... |
| * 2. is the same type after the alter. |
| * 3. length is greater than the old length. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| @Override |
| void checkUserType(TableDescriptor td) |
| throws StandardException |
| { |
| if (kind != K_MODIFY_COLUMN_TYPE) { |
| return; // nothing to do if user not changing length |
| } |
| |
| ColumnDescriptor cd = td.getColumnDescriptor(name); |
| if (cd == null) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, name, td.getName()); |
| } |
| |
| DataTypeDescriptor oldType = cd.getType(); |
| setNullability(oldType.isNullable()); |
| |
| // can't change types yet. |
| if (!(oldType.getTypeId().equals(getType().getTypeId()))) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_MODIFY_COLUMN_CHANGE_TYPE, name); |
| } |
| |
| // can only alter the length of varchar, bitvarying columns |
| String typeName = getType().getTypeName(); |
| if (!(typeName.equals(TypeId.VARCHAR_NAME)) && |
| !(typeName.equals(TypeId.VARBIT_NAME))&& |
| !(typeName.equals(TypeId.BLOB_NAME))&& |
| !(typeName.equals(TypeId.CLOB_NAME))) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_MODIFY_COLUMN_INVALID_TYPE); |
| } |
| |
| // cannot decrease the length of a column |
| if (getType().getMaximumWidth() < oldType.getMaximumWidth()) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_MODIFY_COLUMN_INVALID_LENGTH, name); |
| } |
| } |
| |
| /** |
| * Check if the the column can be modified, and throw error if not. |
| * |
| * If the type of a column is being changed (for instance if the length |
| * of the column is being increased) then make sure that this does not |
| * violate any key constraints; |
| * the column being altered is |
| * 1. part of foreign key constraint |
| * ==> ERROR. This references a Primary Key constraint and the |
| * type and lengths of the pkey/fkey must match exactly. |
| * 2. part of a unique/primary key constraint |
| * ==> OK if no fkey references this constraint. |
| * ==> ERROR if any fkey in the system references this constraint. |
| * |
| * @param td The Table Descriptor on which the ALTER is being done. |
| * |
| * @exception StandardException Thrown on Error. |
| * |
| */ |
| void checkExistingConstraints(TableDescriptor td) |
| throws StandardException |
| { |
| if ((kind != K_MODIFY_COLUMN_TYPE) && |
| (kind != K_MODIFY_COLUMN_CONSTRAINT) && |
| (kind != K_MODIFY_COLUMN_CONSTRAINT_NOT_NULL)) |
| return; |
| |
| DataDictionary dd = getDataDictionary(); |
| ConstraintDescriptorList cdl = dd.getConstraintDescriptors(td); |
| int intArray[] = new int[1]; |
| intArray[0] = columnPosition; |
| |
| for (int index = 0; index < cdl.size(); index++) |
| { |
| ConstraintDescriptor existingConstraint = |
| cdl.elementAt(index); |
| |
| if (!(existingConstraint instanceof KeyConstraintDescriptor)) |
| continue; |
| |
| if (!existingConstraint.columnIntersects(intArray)) |
| continue; |
| |
| int constraintType = existingConstraint.getConstraintType(); |
| |
| // cannot change the length of a column that is part of a |
| // foreign key constraint. Must be an exact match between pkey |
| // and fkey columns. |
| if ((constraintType == DataDictionary.FOREIGNKEY_CONSTRAINT) |
| && |
| (kind == K_MODIFY_COLUMN_TYPE)) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_MODIFY_COLUMN_FKEY_CONSTRAINT, |
| name, existingConstraint.getConstraintName()); |
| } |
| else |
| { |
| if (!dd.checkVersion( |
| DataDictionary.DD_VERSION_DERBY_10_4, null)) |
| { |
| // If a column is part of unique constraint it can't be |
| // made nullable in soft upgrade mode from a pre-10.4 db. |
| if (kind == K_MODIFY_COLUMN_CONSTRAINT && |
| (existingConstraint.getConstraintType() == |
| DataDictionary.UNIQUE_CONSTRAINT)) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_MODIFY_COLUMN_EXISTING_CONSTRAINT, |
| name); |
| } |
| } |
| |
| // A column that is part of a primary key |
| // is being made nullable; can't be done. |
| if ((kind == K_MODIFY_COLUMN_CONSTRAINT) && |
| ((existingConstraint.getConstraintType() == |
| DataDictionary.PRIMARYKEY_CONSTRAINT))) |
| { |
| String errorState = |
| (getLanguageConnectionContext().getDataDictionary() |
| .checkVersion(DataDictionary.DD_VERSION_DERBY_10_4, |
| null)) |
| ? SQLState.LANG_MODIFY_COLUMN_EXISTING_PRIMARY_KEY |
| : SQLState.LANG_MODIFY_COLUMN_EXISTING_CONSTRAINT; |
| throw StandardException.newException(errorState, name); |
| } |
| // unique key or primary key. |
| ConstraintDescriptorList |
| refcdl = dd.getForeignKeys(existingConstraint.getUUID()); |
| |
| if (refcdl.size() > 0) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_MODIFY_COLUMN_REFERENCED, |
| name, refcdl.elementAt(0).getConstraintName()); |
| } |
| |
| // Make the statement dependent on the primary key constraint. |
| getCompilerContext().createDependency(existingConstraint); |
| } |
| } |
| } |
| |
| /** |
| * If the column being modified is of character string type, then it should |
| * get its collation from the corresponding column in the TableDescriptor. |
| * This will ensure that at alter table time, the existing character string |
| * type columns do not loose their collation type. If the alter table is |
| * doing a drop column, then we do not need to worry about collation info. |
| * |
| * @param td Table Descriptor that holds the column which is being altered |
| * @throws StandardException |
| */ |
| void useExistingCollation(TableDescriptor td) |
| throws StandardException |
| { |
| ColumnDescriptor cd; |
| |
| // First verify that the column exists |
| cd = td.getColumnDescriptor(name); |
| if (cd == null) |
| { |
| throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, name, td.getName()); |
| } |
| //getType() == null means we are dealing with drop column and hence |
| //no need to worry about collation info |
| if (getType() != null) { |
| if (getType().getTypeId().isStringTypeId()) { |
| setCollationType(cd.getType().getCollationType()); |
| } |
| } |
| } |
| |
| /** |
| * Get the action associated with this node. |
| * |
| * @return The action associated with this node. |
| */ |
| @Override |
| int getAction() |
| { |
| switch (kind) { |
| case K_MODIFY_COLUMN_DEFAULT: |
| if (autoinc_create_or_modify_Start_Increment == |
| ColumnDefinitionNode.MODIFY_AUTOINCREMENT_RESTART_VALUE) { |
| return ColumnInfo.MODIFY_COLUMN_DEFAULT_RESTART; |
| |
| } else if (autoinc_create_or_modify_Start_Increment == |
| ColumnDefinitionNode.MODIFY_AUTOINCREMENT_INC_VALUE) { |
| return ColumnInfo.MODIFY_COLUMN_DEFAULT_INCREMENT; |
| |
| } else { |
| return ColumnInfo.MODIFY_COLUMN_DEFAULT_VALUE; |
| } |
| case K_MODIFY_COLUMN_TYPE: |
| return ColumnInfo.MODIFY_COLUMN_TYPE; |
| |
| case K_MODIFY_COLUMN_CONSTRAINT: |
| return ColumnInfo.MODIFY_COLUMN_CONSTRAINT; |
| |
| case K_MODIFY_COLUMN_CONSTRAINT_NOT_NULL: |
| return ColumnInfo.MODIFY_COLUMN_CONSTRAINT_NOT_NULL; |
| |
| case K_DROP_COLUMN: |
| return ColumnInfo.DROP; |
| |
| case K_MODIFY_COLUMN_GENERATED_ALWAYS: |
| return ColumnInfo.MODIFY_COLUMN_GENERATED_ALWAYS; |
| |
| case K_MODIFY_COLUMN_GENERATED_BY_DEFAULT: |
| return ColumnInfo.MODIFY_COLUMN_GENERATED_BY_DEFAULT; |
| |
| default: |
| if (SanityManager.DEBUG) { |
| SanityManager.THROWASSERT("Unexpected type = " + kind); |
| } |
| return 0; |
| } |
| } |
| |
| /** |
| * Check the validity of the default, if any, for this node. |
| * |
| * @param dd The DataDictionary. |
| * @param td The TableDescriptor. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| @Override |
| void bindAndValidateDefault(DataDictionary dd, TableDescriptor td) |
| throws StandardException |
| { |
| ColumnDescriptor cd; |
| |
| // First verify that the column exists |
| cd = td.getColumnDescriptor(name); |
| if (cd == null) |
| { |
| throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, name, td.getName()); |
| } |
| |
| |
| // Get the UUID for the old default |
| DefaultDescriptor defaultDescriptor = cd.getDefaultDescriptor(dd); |
| |
| oldDefaultUUID = (defaultDescriptor == null) ? null : defaultDescriptor.getUUID(); |
| |
| // Remember the column position |
| columnPosition = cd.getPosition(); |
| |
| // No other work to do if no user specified default |
| if (kind != K_MODIFY_COLUMN_DEFAULT) { |
| return; |
| } |
| |
| // If the statement is not setting the column's default, then |
| // recover the old default and re-use it. If the statement is |
| // changing the start value for the auto-increment, then recover |
| // the old increment-by value and re-use it. If the statement is |
| // changing the increment-by value, then recover the old start value |
| // and re-use it. This way, the column alteration only changes the |
| // aspects of the autoincrement settings that it intends to change, |
| // and does not lose the other aspecs. |
| if (keepCurrentDefault) |
| { defaultInfo = (DefaultInfoImpl)cd.getDefaultInfo(); } |
| else |
| { |
| if ( cd.hasGenerationClause() || cd.isAutoincrement() ) |
| { |
| throw StandardException.newException( SQLState.LANG_GEN_COL_DEFAULT, cd.getColumnName() ); |
| } |
| } |
| if (autoinc_create_or_modify_Start_Increment == |
| ColumnDefinitionNode.MODIFY_AUTOINCREMENT_RESTART_VALUE) |
| autoincrementIncrement = cd.getAutoincInc(); |
| if (autoinc_create_or_modify_Start_Increment == |
| ColumnDefinitionNode.MODIFY_AUTOINCREMENT_INC_VALUE) |
| autoincrementStart = cd.getAutoincStart(); |
| |
| /* Fill in the DataTypeServices from the DataDictionary */ |
| type = cd.getType(); |
| |
| // Now validate the default |
| validateDefault(dd, td); |
| } |
| |
| private ColumnDescriptor getLocalColumnDescriptor(String name, TableDescriptor td) |
| throws StandardException |
| { |
| ColumnDescriptor cd; |
| |
| // First verify that the column exists |
| cd = td.getColumnDescriptor(name); |
| if (cd == null) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, name, td.getName()); |
| } |
| |
| return cd; |
| } |
| /** |
| * check the validity of autoincrement values in the case that we are |
| * modifying an existing column (includes checking if autoincrement is set |
| * when making a column nullable) |
| */ |
| @Override |
| void validateAutoincrement(DataDictionary dd, |
| TableDescriptor td, |
| int tableType) throws StandardException |
| { |
| ColumnDescriptor cd; |
| |
| // only autoincrement columns can have their generation property changed |
| if ( |
| (kind == K_MODIFY_COLUMN_GENERATED_ALWAYS) || |
| (kind == K_MODIFY_COLUMN_GENERATED_BY_DEFAULT) |
| ) |
| { |
| cd = getLocalColumnDescriptor(name, td); |
| if (!cd.isAutoincrement()) |
| { |
| throw StandardException.newException(SQLState.LANG_AI_CANNOT_ALTER_IDENTITYNESS, |
| getColumnName()); |
| } |
| |
| if (kind == K_MODIFY_COLUMN_GENERATED_BY_DEFAULT) |
| { |
| defaultInfo = createDefaultInfoOfAutoInc(); |
| } |
| |
| // nothing more to do here |
| return; |
| } |
| |
| // a column that has an autoincrement default can't be made nullable |
| if (kind == K_MODIFY_COLUMN_CONSTRAINT) |
| { |
| cd = getLocalColumnDescriptor(name, td); |
| if (cd.isAutoincrement()) |
| { |
| throw StandardException.newException(SQLState.LANG_AI_CANNOT_NULL_AI, |
| getColumnName()); |
| } |
| } |
| |
| if (autoincrementVerify) |
| { |
| cd = getLocalColumnDescriptor(name, td); |
| if (!cd.isAutoincrement()) |
| throw StandardException.newException(SQLState.LANG_INVALID_ALTER_TABLE_ATTRIBUTES, |
| td.getQualifiedName(), name); |
| } |
| if (isAutoincrement == false) |
| return; |
| |
| super.validateAutoincrement(dd, td, tableType); |
| if (getType().isNullable()) |
| throw StandardException.newException(SQLState.LANG_AI_CANNOT_ADD_AI_TO_NULLABLE, |
| getColumnName()); |
| } |
| } |