--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.ModifyColumnNode\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.DefaultDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptorList;\r
+import org.apache.derby.iapi.sql.dictionary.KeyConstraintDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;\r
+\r
+import org.apache.derby.iapi.types.TypeId;\r
+import org.apache.derby.iapi.types.DataTypeDescriptor;\r
+import org.apache.derby.iapi.types.StringDataValue;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.impl.sql.execute.ColumnInfo;\r
+import org.apache.derby.catalog.TypeDescriptor;\r
+import org.apache.derby.catalog.UUID;\r
+import org.apache.derby.catalog.types.DefaultInfoImpl;\r
+\r
+/**\r
+ * A ModifyColumnNode represents a modify column in an ALTER TABLE statement.\r
+ *\r
+ */\r
+\r
+public class ModifyColumnNode extends ColumnDefinitionNode\r
+{\r
+ int columnPosition = -1;\r
+ UUID oldDefaultUUID;\r
+\r
+ /**\r
+ * Get the UUID of the old column default.\r
+ *\r
+ * @return The UUID of the old column default.\r
+ */\r
+ UUID getOldDefaultUUID()\r
+ {\r
+ return oldDefaultUUID;\r
+ }\r
+\r
+ /**\r
+ * Get the column position for the column.\r
+ *\r
+ * @return The column position for the column.\r
+ */\r
+ public int getColumnPosition()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(columnPosition > 0,\r
+ "columnPosition expected to be > 0");\r
+ }\r
+ return columnPosition;\r
+ }\r
+\r
+ /**\r
+ * Check the validity of a user type. Checks that\r
+ * 1. the column type is either varchar, ....\r
+ * 2. is the same type after the alter.\r
+ * 3. length is greater than the old length.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void checkUserType(TableDescriptor td)\r
+ throws StandardException\r
+ {\r
+ if (getNodeType() != C_NodeTypes.MODIFY_COLUMN_TYPE_NODE)\r
+ return; // nothing to do if user not changing length\r
+\r
+ ColumnDescriptor cd = td.getColumnDescriptor(name);\r
+ if (cd == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, name, td.getName());\r
+ }\r
+ \r
+ DataTypeDescriptor oldType = cd.getType();\r
+ dataTypeServices = \r
+ getDataTypeServices().getNullabilityType(oldType.isNullable());\r
+\r
+ // can't change types yet.\r
+ if (!(oldType.getTypeId().equals(getDataTypeServices().getTypeId())))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_MODIFY_COLUMN_CHANGE_TYPE, name);\r
+ } \r
+ \r
+ // can only alter the length of varchar, bitvarying columns\r
+ String typeName = getDataTypeServices().getTypeName();\r
+ if (!(typeName.equals(TypeId.VARCHAR_NAME)) &&\r
+ !(typeName.equals(TypeId.VARBIT_NAME)))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_MODIFY_COLUMN_INVALID_TYPE);\r
+ }\r
+ \r
+ // cannot decrease the length of a column\r
+ if (getDataTypeServices().getMaximumWidth() < oldType.getMaximumWidth())\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_MODIFY_COLUMN_INVALID_LENGTH, name);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * If the type of a column is being changed (for mulan, the length of the\r
+ * column is being increased then make sure that this does not violate\r
+ * any key constraints; \r
+ * the column being altered is \r
+ * 1. part of foreign key constraint \r
+ * ==> ERROR. This references a Primary Key constraint and the\r
+ * type & lengths of the pkey/fkey must match exactly.\r
+ * 2. part of a unique/primary key constraint\r
+ * ==> OK if no fkey references this constraint.\r
+ * ==> ERROR if any fkey in the system references this constraint.\r
+ *\r
+ * @param td The Table Descriptor on which the ALTER is being done.\r
+ *\r
+ * @exception StandardException Thrown on Error.\r
+ *\r
+ */\r
+ public void checkExistingConstraints(TableDescriptor td)\r
+ throws StandardException\r
+ {\r
+ if ((getNodeType() != C_NodeTypes.MODIFY_COLUMN_TYPE_NODE) &&\r
+ (getNodeType() != C_NodeTypes.MODIFY_COLUMN_CONSTRAINT_NODE) &&\r
+ (getNodeType() != C_NodeTypes.MODIFY_COLUMN_CONSTRAINT_NOT_NULL_NODE))\r
+ return;\r
+\r
+ DataDictionary dd = getDataDictionary();\r
+ ConstraintDescriptorList cdl = dd.getConstraintDescriptors(td);\r
+ int intArray[] = new int[1];\r
+ intArray[0] = columnPosition;\r
+\r
+ for (int index = 0; index < cdl.size(); index++)\r
+ {\r
+ ConstraintDescriptor existingConstraint =\r
+ cdl.elementAt(index);\r
+ if (!(existingConstraint instanceof KeyConstraintDescriptor))\r
+ continue;\r
+\r
+ if (!existingConstraint.columnIntersects(intArray))\r
+ continue;\r
+ \r
+ int constraintType = existingConstraint.getConstraintType();\r
+\r
+ // cannot change the length of a column that is part of a \r
+ // foreign key constraint. Must be an exact match between pkey\r
+ // and fkey columns.\r
+ if ((constraintType == DataDictionary.FOREIGNKEY_CONSTRAINT) \r
+ &&\r
+ (getNodeType() == C_NodeTypes.MODIFY_COLUMN_TYPE_NODE))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_MODIFY_COLUMN_FKEY_CONSTRAINT, name, existingConstraint.getConstraintName());\r
+ } \r
+ \r
+ else\r
+ {\r
+ // a column that is part of a primary key or unique constraint\r
+ // is being made nullable; can't be done.\r
+ if ((getNodeType() == \r
+ C_NodeTypes.MODIFY_COLUMN_CONSTRAINT_NODE) &&\r
+ ((existingConstraint.getConstraintType() == \r
+ DataDictionary.PRIMARYKEY_CONSTRAINT) ||\r
+ (existingConstraint.getConstraintType() == \r
+ DataDictionary.UNIQUE_CONSTRAINT)))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_MODIFY_COLUMN_EXISTING_CONSTRAINT, name);\r
+ }\r
+ // unique key or primary key.\r
+ ConstraintDescriptorList \r
+ refcdl = dd.getForeignKeys(existingConstraint.getUUID());\r
+ \r
+ if (refcdl.size() > 0)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_MODIFY_COLUMN_REFERENCED, name, refcdl.elementAt(0).getConstraintName());\r
+ }\r
+ \r
+ // Make the statement dependent on the primary key constraint.\r
+ getCompilerContext().createDependency(existingConstraint);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * If the column being modified is of character string type, then it should\r
+ * get it's collation from the corresponding column in the TableDescriptor.\r
+ * This will ensure that at alter table time, the existing character string\r
+ * type columns do not loose their collation type. If the alter table is \r
+ * doing a drop column, then we do not need to worry about collation info.\r
+ * \r
+ * @param td Table Descriptor that holds the column which is being altered\r
+ * @throws StandardException\r
+ */\r
+ public void useExistingCollation(TableDescriptor td)\r
+ throws StandardException\r
+ {\r
+ ColumnDescriptor cd;\r
+\r
+ // First verify that the column exists\r
+ cd = td.getColumnDescriptor(name);\r
+ if (cd == null)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, name, td.getName());\r
+ }\r
+ //getType() == null means we are dealing with drop column and hence \r
+ //no need to worry about collation info\r
+ if (getDataTypeServices() != null) {\r
+ if (getDataTypeServices().getTypeId().isStringTypeId()) {\r
+ this.getDataTypeServices().setCollationType(cd.getType().getCollationType());\r
+ this.getDataTypeServices().setCollationDerivation(StringDataValue.COLLATION_DERIVATION_IMPLICIT);\r
+ \r
+ }\r
+ }\r
+ }\r
+ /**\r
+ * Get the action associated with this node.\r
+ *\r
+ * @return The action associated with this node.\r
+ */\r
+ int getAction()\r
+ {\r
+ switch (getNodeType())\r
+ {\r
+ case C_NodeTypes.MODIFY_COLUMN_DEFAULT_NODE:\r
+ if (autoinc_create_or_modify_Start_Increment == ColumnDefinitionNode.MODIFY_AUTOINCREMENT_RESTART_VALUE)\r
+ return ColumnInfo.MODIFY_COLUMN_DEFAULT_RESTART;\r
+ else if (autoinc_create_or_modify_Start_Increment ==\r
+ ColumnDefinitionNode.MODIFY_AUTOINCREMENT_INC_VALUE)\r
+ return ColumnInfo.MODIFY_COLUMN_DEFAULT_INCREMENT;\r
+ else\r
+ return ColumnInfo.MODIFY_COLUMN_DEFAULT_VALUE;\r
+ case C_NodeTypes.MODIFY_COLUMN_TYPE_NODE:\r
+ return ColumnInfo.MODIFY_COLUMN_TYPE;\r
+ case C_NodeTypes.MODIFY_COLUMN_CONSTRAINT_NODE:\r
+ return ColumnInfo.MODIFY_COLUMN_CONSTRAINT;\r
+ case C_NodeTypes.MODIFY_COLUMN_CONSTRAINT_NOT_NULL_NODE:\r
+ return ColumnInfo.MODIFY_COLUMN_CONSTRAINT_NOT_NULL;\r
+ case C_NodeTypes.DROP_COLUMN_NODE:\r
+ return ColumnInfo.DROP;\r
+ default:\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT("Unexpected nodeType = " + \r
+ getNodeType());\r
+ }\r
+ return 0;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Check the validity of the default, if any, for this node.\r
+ *\r
+ * @param dd The DataDictionary.\r
+ * @param td The TableDescriptor.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ void bindAndValidateDefault(DataDictionary dd, TableDescriptor td) \r
+ throws StandardException\r
+ {\r
+ ColumnDescriptor cd;\r
+\r
+ // First verify that the column exists\r
+ cd = td.getColumnDescriptor(name);\r
+ if (cd == null)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, name, td.getName());\r
+ }\r
+\r
+\r
+ // Get the UUID for the old default\r
+ DefaultDescriptor defaultDescriptor = cd.getDefaultDescriptor(dd);\r
+ \r
+ oldDefaultUUID = (defaultDescriptor == null) ? null : defaultDescriptor.getUUID();\r
+\r
+ // Remember the column position\r
+ columnPosition = cd.getPosition();\r
+\r
+ // No other work to do if no user specified default\r
+ if (getNodeType() != C_NodeTypes.MODIFY_COLUMN_DEFAULT_NODE)\r
+ {\r
+ return;\r
+ }\r
+\r
+ // If the statement is not setting the column's default, then\r
+ // recover the old default and re-use it. If the statement is\r
+ // changing the start value for the auto-increment, then recover\r
+ // the old increment-by value and re-use it. If the statement is\r
+ // changing the increment-by value, then recover the old start value\r
+ // and re-use it. This way, the column alteration only changes the\r
+ // aspects of the autoincrement settings that it intends to change,\r
+ // and does not lose the other aspecs.\r
+ if (defaultNode == null)\r
+ defaultInfo = (DefaultInfoImpl)cd.getDefaultInfo();\r
+ if (autoinc_create_or_modify_Start_Increment ==\r
+ ColumnDefinitionNode.MODIFY_AUTOINCREMENT_RESTART_VALUE)\r
+ autoincrementIncrement = cd.getAutoincInc();\r
+ if (autoinc_create_or_modify_Start_Increment ==\r
+ ColumnDefinitionNode.MODIFY_AUTOINCREMENT_INC_VALUE)\r
+ autoincrementStart = cd.getAutoincStart();\r
+\r
+ /* Fill in the DataTypeServices from the DataDictionary */\r
+ dataTypeServices = cd.getType();\r
+\r
+ // Now validate the default\r
+ validateDefault(dd, td);\r
+ }\r
+ \r
+ private ColumnDescriptor getLocalColumnDescriptor(String name, TableDescriptor td)\r
+ throws StandardException\r
+ {\r
+ ColumnDescriptor cd;\r
+\r
+ // First verify that the column exists\r
+ cd = td.getColumnDescriptor(name);\r
+ if (cd == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, name, td.getName());\r
+ }\r
+\r
+ return cd;\r
+ }\r
+ /**\r
+ * check the validity of autoincrement values in the case that we are \r
+ * modifying an existing column (includes checking if autoincrement is set\r
+ * when making a column nullable)\r
+ */\r
+ public void validateAutoincrement(DataDictionary dd, TableDescriptor td, int tableType)\r
+ throws StandardException\r
+ {\r
+ ColumnDescriptor cd;\r
+\r
+ // a column that has an autoincrement default can't be made nullable\r
+ if (getNodeType() == C_NodeTypes.MODIFY_COLUMN_CONSTRAINT_NODE)\r
+ {\r
+ cd = getLocalColumnDescriptor(name, td);\r
+ if (cd.isAutoincrement())\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_AI_CANNOT_NULL_AI,\r
+ getColumnName());\r
+ }\r
+ }\r
+\r
+ if (autoincrementVerify)\r
+ {\r
+ cd = getLocalColumnDescriptor(name, td);\r
+ if (!cd.isAutoincrement())\r
+ throw StandardException.newException(SQLState.LANG_INVALID_ALTER_TABLE_ATTRIBUTES,\r
+ td.getQualifiedName(), name);\r
+ }\r
+ if (isAutoincrement == false)\r
+ return;\r
+ \r
+ super.validateAutoincrement(dd, td, tableType);\r
+ if (getDataTypeServices().isNullable())\r
+ throw StandardException.newException(SQLState.LANG_AI_CANNOT_ADD_AI_TO_NULLABLE,\r
+ getColumnName());\r
+ }\r
+}\r