| /* |
| |
| Derby - Class org.apache.derby.impl.sql.compile.TableElementList |
| |
| 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 java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Set; |
| 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.Property; |
| import org.apache.derby.iapi.reference.SQLState; |
| import org.apache.derby.iapi.services.context.ContextManager; |
| import org.apache.derby.iapi.services.io.FormatableBitSet; |
| import org.apache.derby.iapi.services.property.PropertyUtil; |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| import org.apache.derby.iapi.sql.StatementType; |
| import org.apache.derby.iapi.sql.compile.CompilerContext; |
| import org.apache.derby.iapi.sql.depend.DependencyManager; |
| import org.apache.derby.iapi.sql.depend.ProviderInfo; |
| import org.apache.derby.iapi.sql.depend.ProviderList; |
| import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList; |
| 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.SchemaDescriptor; |
| 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; |
| import org.apache.derby.impl.sql.execute.ConstraintConstantAction; |
| import org.apache.derby.impl.sql.execute.ConstraintInfo; |
| import org.apache.derby.impl.sql.execute.IndexConstantAction; |
| |
| /** |
| * A TableElementList represents the list of columns and other table elements |
| * such as constraints in a CREATE TABLE or ALTER TABLE statement. |
| * |
| */ |
| |
| class TableElementList extends QueryTreeNodeVector<TableElementNode> |
| { |
| private int numColumns; |
| private TableDescriptor td; |
| |
| public TableElementList(ContextManager cm) { |
| super(TableElementNode.class, cm); |
| } |
| |
| /** |
| * Add a TableElementNode to this TableElementList |
| * |
| * @param tableElement The TableElementNode to add to this list |
| */ |
| |
| void addTableElement(TableElementNode tableElement) |
| { |
| addElement(tableElement); |
| if ((tableElement instanceof ColumnDefinitionNode) || |
| tableElement.getElementType() == TableElementNode.AT_DROP_COLUMN) |
| { |
| numColumns++; |
| } |
| } |
| |
| /** |
| * Use the passed schema descriptor's collation type to set the collation |
| * of the character string types in create table node |
| * @param sd |
| */ |
| void setCollationTypesOnCharacterStringColumns(SchemaDescriptor sd) |
| throws StandardException |
| { |
| for (TableElementNode te : this) |
| { |
| if (te instanceof ColumnDefinitionNode) |
| { |
| setCollationTypeOnCharacterStringColumn( |
| sd, (ColumnDefinitionNode)te ); |
| } |
| } |
| } |
| |
| /** |
| * Use the passed schema descriptor's collation type to set the collation |
| * of a character string column. |
| * @param sd |
| */ |
| void setCollationTypeOnCharacterStringColumn(SchemaDescriptor sd, ColumnDefinitionNode cdn ) |
| throws StandardException |
| { |
| int collationType = sd.getCollationType(); |
| |
| // |
| // Only generated columns can omit the datatype specification during the |
| // early phases of binding--before we have been able to bind the |
| // generation clause. |
| // |
| DataTypeDescriptor dtd = cdn.getType(); |
| if ( dtd == null ) |
| { |
| if ( !cdn.hasGenerationClause() ) |
| { |
| throw StandardException.newException |
| ( SQLState.LANG_NEEDS_DATATYPE, cdn.getColumnName() ); |
| } |
| } |
| else |
| { |
| if ( dtd.getTypeId().isStringTypeId() ) { cdn.setCollationType(collationType); } |
| } |
| } |
| |
| /** |
| * Validate this TableElementList. This includes checking for |
| * duplicate columns names, and checking that user types really exist. |
| * |
| * @param ddlStmt DDLStatementNode which contains this list |
| * @param dd DataDictionary to use |
| * @param td TableDescriptor for table, if existing table. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void validate(DDLStatementNode ddlStmt, |
| DataDictionary dd, |
| TableDescriptor td) |
| throws StandardException |
| { |
| this.td = td; |
| int numAutoCols = 0; |
| |
| int size = size(); |
| HashSet<String> columnNames = new HashSet<String>(size + 2, 0.999f); |
| HashSet<String> constraintNames = new HashSet<String>(size + 2, 0.999f); |
| //all the primary key/unique key constraints for this table |
| ArrayList<Object> constraints = new ArrayList<Object>(); |
| |
| //special case for alter table (td is not null in case of alter table) |
| if (td != null) |
| { |
| //In case of alter table, get the already existing primary key and unique |
| //key constraints for this table. And then we will compare them with new |
| //primary key/unique key constraint column lists. |
| ConstraintDescriptorList cdl = dd.getConstraintDescriptors(td); |
| ConstraintDescriptor cd; |
| |
| if (cdl != null) //table does have some pre-existing constraints defined on it |
| { |
| for (int i=0; i<cdl.size();i++) |
| { |
| cd = cdl.elementAt(i); |
| //if the constraint type is not primary key or unique key, ignore it. |
| if (cd.getConstraintType() == DataDictionary.PRIMARYKEY_CONSTRAINT || |
| cd.getConstraintType() == DataDictionary.UNIQUE_CONSTRAINT) |
| { |
| constraints.add(cd); |
| } |
| } |
| } |
| } |
| |
| int tableType = TableDescriptor.BASE_TABLE_TYPE; |
| if (ddlStmt instanceof CreateTableNode) |
| tableType = ((CreateTableNode)ddlStmt).tableType; |
| |
| for (TableElementNode tableElement : this) |
| { |
| if (tableElement instanceof ColumnDefinitionNode) |
| { |
| ColumnDefinitionNode cdn = (ColumnDefinitionNode)tableElement; |
| if (tableType == TableDescriptor.GLOBAL_TEMPORARY_TABLE_TYPE && |
| (cdn.getType().getTypeId().isLongConcatableTypeId() || |
| cdn.getType().getTypeId().isUserDefinedTypeId())) |
| { |
| throw StandardException.newException(SQLState.LANG_LONG_DATA_TYPE_NOT_ALLOWED, cdn.getColumnName()); |
| } |
| checkForDuplicateColumns(ddlStmt, columnNames, cdn.getColumnName()); |
| cdn.checkUserType(td); |
| cdn.bindAndValidateDefault(dd, td); |
| |
| cdn.validateAutoincrement(dd, td, tableType); |
| |
| if (tableElement instanceof ModifyColumnNode) |
| { |
| ModifyColumnNode mcdn = (ModifyColumnNode)cdn; |
| mcdn.checkExistingConstraints(td); |
| mcdn.useExistingCollation(td); |
| |
| } else if (cdn.isAutoincrementColumn()) |
| { numAutoCols ++; } |
| } |
| else if (tableElement.getElementType() == TableElementNode.AT_DROP_COLUMN) |
| { |
| String colName = tableElement.getName(); |
| if (td.getColumnDescriptor(colName) == null) |
| { |
| throw StandardException.newException( |
| SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, |
| colName, |
| td.getQualifiedName()); |
| } |
| break; |
| } |
| |
| /* The rest of this method deals with validating constraints */ |
| if (! (tableElement.hasConstraint())) |
| { |
| continue; |
| } |
| |
| ConstraintDefinitionNode cdn = (ConstraintDefinitionNode) tableElement; |
| |
| cdn.bind(ddlStmt, dd); |
| |
| // If constraint is primary key or unique key, add it to the list. |
| if (cdn.getConstraintType() == DataDictionary.PRIMARYKEY_CONSTRAINT || |
| cdn.getConstraintType() == DataDictionary.UNIQUE_CONSTRAINT) |
| { |
| /* In case of create table, the list can have only ConstraintDefinitionNode |
| * elements. In case of alter table, it can have both ConstraintDefinitionNode |
| * (for new constraints) and ConstraintDescriptor(for pre-existing constraints). |
| */ |
| |
| Object destConstraint; |
| String destName = null; |
| String[] destColumnNames = null; |
| |
| for (int i = 0; i < constraints.size(); i++) |
| { |
| |
| destConstraint = constraints.get(i); |
| if (destConstraint instanceof ConstraintDefinitionNode) |
| { |
| ConstraintDefinitionNode destCDN = (ConstraintDefinitionNode)destConstraint; |
| destName = destCDN.getConstraintMoniker(); |
| destColumnNames = destCDN.getColumnList().getColumnNames(); |
| } |
| else if (destConstraint instanceof ConstraintDescriptor) |
| { |
| //will come here only for pre-existing constraints in case of alter table |
| ConstraintDescriptor destCD = (ConstraintDescriptor)destConstraint; |
| destName = destCD.getConstraintName(); |
| destColumnNames = destCD.getColumnDescriptors().getColumnNames(); |
| } |
| //check if there are multiple constraints with same set of columns |
| if (columnsMatch(cdn.getColumnList().getColumnNames(), destColumnNames)) |
| throw StandardException.newException(SQLState.LANG_MULTIPLE_CONSTRAINTS_WITH_SAME_COLUMNS, |
| cdn.getConstraintMoniker(), destName); |
| } |
| constraints.add(cdn); |
| } |
| |
| /* Make sure that there are no duplicate constraint names in the list */ |
| checkForDuplicateConstraintNames(ddlStmt, constraintNames, cdn.getConstraintMoniker()); |
| |
| /* Make sure that the constraint we are trying to drop exists */ |
| if (cdn.getConstraintType() == DataDictionary.DROP_CONSTRAINT || |
| cdn.getConstraintType() == DataDictionary.MODIFY_CONSTRAINT) |
| { |
| /* |
| ** If no schema descriptor, then must be an invalid |
| ** schema name. |
| */ |
| |
| String dropConstraintName = cdn.getConstraintMoniker(); |
| |
| if (dropConstraintName != null) { |
| |
| String dropSchemaName = cdn.getDropSchemaName(); |
| |
| SchemaDescriptor sd = dropSchemaName == null ? td.getSchemaDescriptor() : |
| getSchemaDescriptor(dropSchemaName); |
| |
| ConstraintDescriptor cd = |
| dd.getConstraintDescriptorByName( |
| td, sd, dropConstraintName, |
| false); |
| if (cd == null) |
| { |
| throw StandardException.newException(SQLState.LANG_DROP_OR_ALTER_NON_EXISTING_CONSTRAINT, |
| (sd.getSchemaName() + "."+ dropConstraintName), |
| td.getQualifiedName()); |
| } |
| /* Statement is dependendent on the ConstraintDescriptor */ |
| getCompilerContext().createDependency(cd); |
| } |
| } |
| |
| // validation of primary key nullability moved to validatePrimaryKeyNullability(). |
| if (cdn.hasPrimaryKeyConstraint()) |
| { |
| // for PRIMARY KEY, check that columns are unique |
| verifyUniqueColumnList(ddlStmt, cdn); |
| } |
| else if (cdn.hasUniqueKeyConstraint()) |
| { |
| // for UNIQUE, check that columns are unique |
| verifyUniqueColumnList(ddlStmt, cdn); |
| |
| // unique constraints on nullable columns added in 10.4, |
| // disallow until database hard upgraded at least to 10.4. |
| if (!dd.checkVersion( |
| DataDictionary.DD_VERSION_DERBY_10_4, null)) |
| { |
| checkForNullColumns(cdn, td); |
| } |
| } |
| else if (cdn.hasForeignKeyConstraint()) |
| { |
| // for FOREIGN KEY, check that columns are unique |
| verifyUniqueColumnList(ddlStmt, cdn); |
| } |
| } |
| |
| /* Can have only one autoincrement column in DB2 mode */ |
| // Raise an error if we have more than one autoincrement column in a |
| // CREATE TABLE statement, or if we are adding a new autoincrement |
| // column to an existing table that already has an autoincrement column, |
| // with ALTER TABLE ADD COLUMN. |
| if (numAutoCols > 1 || |
| (numAutoCols > 0 && td != null && td.tableHasAutoincrement())) { |
| throw StandardException.newException(SQLState.LANG_MULTIPLE_AUTOINCREMENT_COLUMNS); |
| } |
| |
| } |
| |
| /** |
| * Validate nullability of primary keys. This logic was moved out of the main validate |
| * method so that it can be called after binding generation clauses. We need |
| * to perform the nullability checks later on because the datatype may be |
| * omitted on the generation clause--we can't set/vet the nullability of the |
| * datatype until we determine what the datatype is. |
| */ |
| public void validatePrimaryKeyNullability() |
| throws StandardException |
| { |
| for (TableElementNode tableElement : this) |
| { |
| if (! (tableElement.hasConstraint())) |
| { |
| continue; |
| } |
| |
| ConstraintDefinitionNode cdn = (ConstraintDefinitionNode) tableElement; |
| |
| if (cdn.hasPrimaryKeyConstraint()) |
| { |
| if (td == null) |
| { |
| // in CREATE TABLE so set PRIMARY KEY columns to NOT NULL |
| setColumnListToNotNull(cdn); |
| } |
| else |
| { |
| // in ALTER TABLE so raise error if any columns are nullable |
| checkForNullColumns(cdn, td); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Count the number of constraints of the specified type. |
| * |
| * @param constraintType The constraint type to search for. |
| * |
| * @return int The number of constraints of the specified type. |
| */ |
| int countConstraints(int constraintType) |
| { |
| int numConstraints = 0; |
| |
| for (TableElementNode element : this) |
| { |
| if (element instanceof ConstraintDefinitionNode && |
| ((ConstraintDefinitionNode)element).getConstraintType() == |
| constraintType) { |
| numConstraints++; |
| } |
| } |
| |
| return numConstraints; |
| } |
| |
| /** |
| * Count the number of generation clauses. |
| */ |
| int countGenerationClauses() |
| { |
| int numGenerationClauses = 0; |
| |
| for (TableElementNode element : this) |
| { |
| if (element instanceof ColumnDefinitionNode && |
| ((ColumnDefinitionNode)element).hasGenerationClause()) { |
| numGenerationClauses++; |
| } |
| } |
| |
| return numGenerationClauses; |
| } |
| |
| /** |
| * Count the number of columns. |
| * |
| * @return int The number of columns. |
| */ |
| int countNumberOfColumns() |
| { |
| return numColumns; |
| } |
| |
| /** |
| * Fill in the ColumnInfo[] for this table element list. |
| * |
| * @param colInfos The ColumnInfo[] to be filled in. |
| * |
| * @return int The number of constraints in the create table. |
| */ |
| int genColumnInfos( ColumnInfo[] colInfos) |
| throws StandardException |
| { |
| int numConstraints = 0; |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| if (elementAt(index).getElementType() == TableElementNode.AT_DROP_COLUMN) |
| { |
| String columnName = elementAt(index).getName(); |
| |
| colInfos[index] = new ColumnInfo( |
| columnName, |
| td.getColumnDescriptor( columnName ).getType(), |
| null, null, null, null, null, |
| ColumnInfo.DROP, 0, 0, 0); |
| break; |
| } |
| |
| if (! (elementAt(index) instanceof ColumnDefinitionNode)) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( elementAt(index) instanceof ConstraintDefinitionNode, |
| "elementAt(index) expected to be instanceof " + |
| "ConstraintDefinitionNode"); |
| } |
| |
| /* Remember how many constraints we've seen */ |
| numConstraints++; |
| continue; |
| } |
| |
| ColumnDefinitionNode coldef = (ColumnDefinitionNode) elementAt(index); |
| |
| // |
| // Generated columns may depend on functions mentioned in their |
| // generation clauses. |
| // |
| ProviderList apl = null; |
| ProviderInfo[] providerInfos = null; |
| if ( coldef.hasGenerationClause() ) |
| { |
| apl = coldef.getGenerationClauseNode().getAuxiliaryProviderList(); |
| } |
| if (apl != null && apl.size() > 0) |
| { |
| DependencyManager dm = getDataDictionary().getDependencyManager(); |
| providerInfos = dm.getPersistentProviderInfos(apl); |
| } |
| |
| colInfos[index - numConstraints] = |
| new ColumnInfo(coldef.getColumnName(), |
| coldef.getType(), |
| coldef.getDefaultValue(), |
| coldef.getDefaultInfo(), |
| providerInfos, |
| (UUID) null, |
| coldef.getOldDefaultUUID(), |
| coldef.getAction(), |
| (coldef.isAutoincrementColumn() ? |
| coldef.getAutoincrementStart() : 0), |
| (coldef.isAutoincrementColumn() ? |
| coldef.getAutoincrementIncrement() : 0), |
| (coldef.isAutoincrementColumn() ? |
| coldef.getAutoinc_create_or_modify_Start_Increment() : -1)); |
| |
| /* Remember how many constraints that we've seen */ |
| if (coldef.hasConstraint()) |
| { |
| numConstraints++; |
| } |
| } |
| |
| return numConstraints; |
| } |
| /** |
| * Append goobered up ResultColumns to the table's RCL. |
| * This is useful for binding check constraints for CREATE and ALTER TABLE. |
| * |
| * @param table The table in question. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void appendNewColumnsToRCL(FromBaseTable table) |
| throws StandardException |
| { |
| int size = size(); |
| ResultColumnList rcl = table.getResultColumns(); |
| TableName exposedName = table.getTableName(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| if (elementAt(index) instanceof ColumnDefinitionNode) |
| { |
| ColumnDefinitionNode cdn = (ColumnDefinitionNode) elementAt(index); |
| ResultColumn resultColumn; |
| ValueNode valueNode; |
| |
| /* Build a ResultColumn/BaseColumnNode pair for the column */ |
| valueNode = new BaseColumnNode(cdn.getColumnName(), |
| exposedName, |
| cdn.getType(), |
| getContextManager()); |
| |
| resultColumn = new ResultColumn( |
| cdn.getType(), valueNode, getContextManager()); |
| resultColumn.setName(cdn.getColumnName()); |
| rcl.addElement(resultColumn); |
| } |
| } |
| } |
| |
| /** |
| * Bind and validate all of the check constraints in this list against |
| * the specified FromList. |
| * |
| * @param fromList The FromList in question. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void bindAndValidateCheckConstraints(FromList fromList) |
| throws StandardException |
| { |
| FromBaseTable table = (FromBaseTable) fromList.elementAt(0); |
| CompilerContext cc = getCompilerContext(); |
| |
| ArrayList<AggregateNode> aggregates = new ArrayList<AggregateNode>(); |
| |
| for (TableElementNode element : this) |
| { |
| ConstraintDefinitionNode cdn; |
| ValueNode checkTree; |
| |
| if (! (element instanceof ConstraintDefinitionNode)) |
| { |
| continue; |
| } |
| |
| cdn = (ConstraintDefinitionNode) element; |
| |
| if (cdn.getConstraintType() != DataDictionary.CHECK_CONSTRAINT) |
| { |
| continue; |
| } |
| |
| checkTree = cdn.getCheckCondition(); |
| |
| // bind the check condition |
| // verify that it evaluates to a boolean |
| final int previousReliability = cc.getReliability(); |
| try |
| { |
| /* Each check constraint can have its own set of dependencies. |
| * These dependencies need to be shared with the prepared |
| * statement as well. We create a new auxiliary provider list |
| * for the check constraint, "push" it on the compiler context |
| * by swapping it with the current auxiliary provider list |
| * and the "pop" it when we're done by restoring the old |
| * auxiliary provider list. |
| */ |
| ProviderList apl = new ProviderList(); |
| |
| ProviderList prevAPL = cc.getCurrentAuxiliaryProviderList(); |
| cc.setCurrentAuxiliaryProviderList(apl); |
| |
| // Tell the compiler context to only allow deterministic nodes |
| cc.setReliability( CompilerContext.CHECK_CONSTRAINT ); |
| checkTree = checkTree.bindExpression( |
| fromList, (SubqueryList) null, aggregates); |
| |
| // no aggregates, please |
| if (!aggregates.isEmpty()) |
| { |
| throw StandardException.newException(SQLState.LANG_INVALID_CHECK_CONSTRAINT, cdn.getConstraintText()); |
| } |
| |
| checkTree = checkTree.checkIsBoolean(); |
| cdn.setCheckCondition(checkTree); |
| |
| /* Save the APL off in the constraint node */ |
| if (apl.size() > 0) |
| { |
| cdn.setAuxiliaryProviderList(apl); |
| } |
| |
| // Restore the previous AuxiliaryProviderList |
| cc.setCurrentAuxiliaryProviderList(prevAPL); |
| } |
| finally |
| { |
| cc.setReliability(previousReliability); |
| } |
| |
| /* We have a valid check constraint. |
| * Now we build a list with only the referenced columns and |
| * copy it to the cdn. Thus we can build the array of |
| * column names for the referenced columns during generate(). |
| */ |
| ResultColumnList rcl = table.getResultColumns(); |
| int numReferenced = rcl.countReferencedColumns(); |
| ResultColumnList refRCL = new ResultColumnList(getContextManager()); |
| rcl.copyReferencedColumnsToNewList(refRCL); |
| |
| /* A column check constraint can only refer to that column. If this is a |
| * column constraint, we should have an RCL with that column |
| */ |
| if (cdn.getColumnList() != null) |
| { |
| String colName = cdn.getColumnList().elementAt(0).getName(); |
| if (numReferenced > 1 || |
| !colName.equals(refRCL.elementAt(0).getName())) |
| throw StandardException.newException(SQLState.LANG_DB2_INVALID_CHECK_CONSTRAINT, colName); |
| |
| } |
| cdn.setColumnList(refRCL); |
| |
| /* Clear the column references in the RCL so each check constraint |
| * starts with a clean list. |
| */ |
| rcl.clearColumnReferences(); |
| |
| // Make sure all names are schema qualified (DERBY-6362) |
| cdn.qualifyNames(); |
| } |
| } |
| |
| /** |
| * Bind and validate all of the generation clauses in this list against |
| * the specified FromList. |
| * |
| * @param sd Schema where the table lives. |
| * @param fromList The FromList in question. |
| * @param generatedColumns Bitmap of generated columns in the table. Vacuous for CREATE TABLE, but may be non-trivial for ALTER TABLE. This routine may set bits for new generated columns. |
| * @param baseTable Table descriptor if this is an ALTER TABLE statement. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void bindAndValidateGenerationClauses( SchemaDescriptor sd, FromList fromList, FormatableBitSet generatedColumns, TableDescriptor baseTable ) |
| throws StandardException |
| { |
| FromBaseTable table = (FromBaseTable) fromList.elementAt(0); |
| ResultColumnList tableColumns = table.getResultColumns(); |
| int columnCount = table.getResultColumns().size(); |
| |
| // complain if a generation clause references another generated column |
| findIllegalGenerationReferences( fromList, baseTable ); |
| |
| generatedColumns.grow( columnCount + 1 ); |
| |
| CompilerContext cc = getCompilerContext(); |
| |
| ArrayList<AggregateNode> aggregates = new ArrayList<AggregateNode>(); |
| |
| for (TableElementNode element : this) |
| { |
| ColumnDefinitionNode cdn; |
| GenerationClauseNode generationClauseNode; |
| ValueNode generationTree; |
| |
| if (! (element instanceof ColumnDefinitionNode)) |
| { |
| continue; |
| } |
| |
| cdn = (ColumnDefinitionNode) element; |
| |
| if (!cdn.hasGenerationClause()) |
| { |
| continue; |
| } |
| |
| generationClauseNode = cdn.getGenerationClauseNode(); |
| |
| // bind the generation clause |
| final int previousReliability = cc.getReliability(); |
| ProviderList prevAPL = cc.getCurrentAuxiliaryProviderList(); |
| try |
| { |
| /* Each generation clause can have its own set of dependencies. |
| * These dependencies need to be shared with the prepared |
| * statement as well. We create a new auxiliary provider list |
| * for the generation clause, "push" it on the compiler context |
| * by swapping it with the current auxiliary provider list |
| * and the "pop" it when we're done by restoring the old |
| * auxiliary provider list. |
| */ |
| ProviderList apl = new ProviderList(); |
| |
| cc.setCurrentAuxiliaryProviderList(apl); |
| |
| // Tell the compiler context to forbid subqueries and |
| // non-deterministic functions. |
| cc.setReliability( CompilerContext.GENERATION_CLAUSE_RESTRICTION ); |
| generationTree = generationClauseNode.bindExpression( |
| fromList, (SubqueryList) null, aggregates); |
| |
| SelectNode.checkNoWindowFunctions(generationClauseNode, "generation clause"); |
| |
| // |
| // If the user did not declare a type for this column, then the column type defaults |
| // to the type of the generation clause. |
| // However, if the user did declare a type for this column, then the |
| // type of the generation clause must be assignable to the declared |
| // type. |
| // |
| DataTypeDescriptor generationClauseType = generationTree.getTypeServices(); |
| DataTypeDescriptor declaredType = cdn.getType(); |
| if ( declaredType == null ) |
| { |
| cdn.setType( generationClauseType ); |
| |
| // |
| // Poke the type into the FromTable so that constraints will |
| // compile. |
| // |
| tableColumns.getResultColumn( cdn.getColumnName(), false ).setType( generationClauseType ); |
| |
| // |
| // We skipped these steps earlier on because we didn't have |
| // a datatype. Now that we have a datatype, revisit these |
| // steps. |
| // |
| setCollationTypeOnCharacterStringColumn( sd, cdn ); |
| cdn.checkUserType( table.getTableDescriptor() ); |
| } |
| else |
| { |
| TypeId declaredTypeId = declaredType.getTypeId(); |
| TypeId resolvedTypeId = generationClauseType.getTypeId(); |
| |
| if ( !getTypeCompiler( resolvedTypeId ).convertible( declaredTypeId, false ) ) |
| { |
| throw StandardException.newException |
| ( SQLState.LANG_UNASSIGNABLE_GENERATION_CLAUSE, cdn.getName(), resolvedTypeId.getSQLTypeName() ); |
| } |
| } |
| |
| // no aggregates, please |
| if (!aggregates.isEmpty()) |
| { |
| throw StandardException.newException( SQLState.LANG_AGGREGATE_IN_GENERATION_CLAUSE, cdn.getName()); |
| } |
| |
| /* Save the APL off in the constraint node */ |
| if (apl.size() > 0) |
| { |
| generationClauseNode.setAuxiliaryProviderList(apl); |
| } |
| |
| } |
| finally |
| { |
| // Restore previous compiler state |
| cc.setCurrentAuxiliaryProviderList(prevAPL); |
| cc.setReliability(previousReliability); |
| } |
| |
| /* We have a valid generation clause, now build an array of |
| * 1-based columnIds that the clause references. |
| */ |
| ResultColumnList rcl = table.getResultColumns(); |
| int numReferenced = rcl.countReferencedColumns(); |
| int[] generationClauseColumnReferences = new int[numReferenced]; |
| int position = rcl.getPosition( cdn.getColumnName(), 1 ); |
| |
| generatedColumns.set( position ); |
| |
| rcl.recordColumnReferences(generationClauseColumnReferences, 1); |
| |
| String[] referencedColumnNames = new String[ numReferenced ]; |
| |
| for ( int i = 0; i < numReferenced; i++ ) |
| { |
| referencedColumnNames[i] = |
| rcl.elementAt(generationClauseColumnReferences[i] - 1). |
| getName(); |
| } |
| |
| String currentSchemaName = getLanguageConnectionContext().getCurrentSchemaName(); |
| DefaultInfoImpl dii = new DefaultInfoImpl |
| ( generationClauseNode.getExpressionText(), referencedColumnNames, currentSchemaName ); |
| cdn.setDefaultInfo( dii ); |
| |
| /* Clear the column references in the RCL so each generation clause |
| * starts with a clean list. |
| */ |
| rcl.clearColumnReferences(); |
| } |
| |
| |
| } |
| |
| /** |
| * Complain if a generation clause references other generated columns. This |
| * is required by the SQL Standard, part 2, section 4.14.8. |
| * |
| * @param fromList The FromList in question. |
| * @param baseTable Table descriptor if this is an ALTER TABLE statement. |
| * @exception StandardException Thrown on error |
| */ |
| void findIllegalGenerationReferences( FromList fromList, TableDescriptor baseTable ) |
| throws StandardException |
| { |
| ArrayList<ColumnDefinitionNode> generatedColumns = new ArrayList<ColumnDefinitionNode>(); |
| HashSet<String> names = new HashSet<String>(); |
| |
| // add in existing generated columns if this is an ALTER TABLE statement |
| if ( baseTable != null ) |
| { |
| ColumnDescriptorList cdl = baseTable.getGeneratedColumns(); |
| int count = cdl.size(); |
| for ( int i = 0; i < count; i++ ) |
| { |
| names.add( cdl.elementAt( i ).getColumnName() ); |
| } |
| } |
| |
| // find all of the generated columns |
| for (TableElementNode element : this) |
| { |
| ColumnDefinitionNode cdn; |
| |
| if (! (element instanceof ColumnDefinitionNode)) { continue; } |
| |
| cdn = (ColumnDefinitionNode) element; |
| |
| if (!cdn.hasGenerationClause()) { continue; } |
| |
| generatedColumns.add( cdn ); |
| names.add( cdn.getColumnName() ); |
| } |
| |
| // now look at their generation clauses to see if they reference one |
| // another |
| int count = generatedColumns.size(); |
| for ( int i = 0; i < count; i++ ) |
| { |
| ColumnDefinitionNode cdn = generatedColumns.get( i ); |
| GenerationClauseNode generationClauseNode = cdn.getGenerationClauseNode(); |
| List<ColumnReference> referencedColumns = |
| generationClauseNode.findReferencedColumns(); |
| int refCount = referencedColumns.size(); |
| for ( int j = 0; j < refCount; j++ ) |
| { |
| String name = referencedColumns.get(j).getColumnName(); |
| |
| if ( name != null ) |
| { |
| if ( names.contains( name ) ) |
| { |
| throw StandardException.newException(SQLState.LANG_CANT_REFERENCE_GENERATED_COLUMN, cdn.getColumnName()); |
| } |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * Prevent foreign keys on generated columns from violating the SQL spec, |
| * part 2, section 11.8 (<column definition>), syntax rule 12: the |
| * referential action may not specify SET NULL or SET DEFAULT and the update |
| * rule may not specify ON UPDATE CASCADE. |
| * |
| * @param fromList The FromList in question. |
| * @param generatedColumns Bitmap of generated columns in the table. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void validateForeignKeysOnGenerationClauses(FromList fromList, FormatableBitSet generatedColumns ) |
| throws StandardException |
| { |
| // nothing to do if there are no generated columns |
| if ( generatedColumns.getNumBitsSet() <= 0 ) { return; } |
| |
| FromBaseTable table = (FromBaseTable) fromList.elementAt(0); |
| ResultColumnList tableColumns = table.getResultColumns(); |
| |
| // loop through the foreign keys, looking for keys which violate the |
| // rulse we're enforcing |
| for (TableElementNode element : this) |
| { |
| if (! (element instanceof FKConstraintDefinitionNode)) |
| { |
| continue; |
| } |
| |
| FKConstraintDefinitionNode fk = (FKConstraintDefinitionNode) element; |
| ConstraintInfo ci = fk.getReferencedConstraintInfo(); |
| int deleteRule = ci.getReferentialActionDeleteRule(); |
| int updateRule = ci.getReferentialActionUpdateRule(); |
| |
| // |
| // Currently we don't support ON UPDATE CASCADE. Someday we might. |
| // We're laying a trip-wire here so that we won't neglect to code the appropriate check |
| // when we support ON UPDATE CASCADE. |
| // |
| if ( |
| ( updateRule != StatementType.RA_RESTRICT ) && |
| ( updateRule != StatementType.RA_NOACTION ) |
| ) |
| { |
| throw StandardException.newException( SQLState.BTREE_UNIMPLEMENTED_FEATURE ); |
| } |
| |
| if ( |
| ( deleteRule != StatementType.RA_SETNULL ) && |
| ( deleteRule != StatementType.RA_SETDEFAULT ) |
| ) |
| { continue; } |
| |
| // |
| // OK, we have found a foreign key whose referential action is SET NULL or |
| // SET DEFAULT or whose update rule is ON UPDATE CASCADE. |
| // See if any of the key columns are generated columns. |
| // |
| for (ResultColumn keyCol : fk.getColumnList()) |
| { |
| String keyColName = keyCol.getName(); |
| int position = tableColumns.getPosition( keyColName, 1 ); |
| |
| if ( generatedColumns.isSet( position ) ) |
| { |
| throw StandardException.newException(SQLState.LANG_BAD_FK_ON_GENERATED_COLUMN, keyColName ); |
| } |
| } |
| |
| } // end of loop through table elements |
| } |
| |
| /** |
| * Fill in the ConstraintConstantAction[] for this create/alter table. |
| * |
| * @param forCreateTable ConstraintConstantAction is for a create table. |
| * @param conActions The ConstraintConstantAction[] to be filled in. |
| * @param tableName The name of the Table being created. |
| * @param tableSd The schema for that table. |
| * @param dd The DataDictionary |
| * |
| * @exception StandardException Thrown on failure |
| */ |
| void genConstraintActions(boolean forCreateTable, |
| ConstraintConstantAction[] conActions, |
| String tableName, |
| SchemaDescriptor tableSd, |
| DataDictionary dd) |
| throws StandardException |
| { |
| int conActionIndex = 0; |
| |
| for (TableElementNode ten : this) |
| { |
| String[] columnNames = null; |
| IndexConstantAction indexAction = null; |
| |
| if (! ten.hasConstraint() || |
| ten instanceof ColumnDefinitionNode) |
| { |
| continue; |
| } |
| |
| ConstraintDefinitionNode constraintDN = (ConstraintDefinitionNode) ten; |
| |
| if (constraintDN.getColumnList() != null) |
| { |
| columnNames = new String[constraintDN.getColumnList().size()]; |
| constraintDN.getColumnList().exportNames(columnNames); |
| } |
| |
| int constraintType = constraintDN.getConstraintType(); |
| boolean[] cChars = constraintDN.getCharacteristics(); |
| String constraintText = constraintDN.getConstraintText(); |
| |
| /* |
| ** If the constraint is not named (e.g. |
| ** create table x (x int primary key)), then |
| ** the constraintSd is the same as the table. |
| */ |
| String constraintName = constraintDN.getConstraintMoniker(); |
| |
| /* At execution time, we will generate a unique name for the backing |
| * index (for CREATE CONSTRAINT) and we will look up the conglomerate |
| * name (for DROP CONSTRAINT). |
| */ |
| if (constraintDN.requiresBackingIndex()) |
| { |
| // implement unique constraints using a unique backing index |
| // unless it is soft upgrade in version before 10.4, or if |
| // constraint contains no nullable columns. In 10.4 use |
| // "unique with duplicate null" backing index for constraints |
| // that contain at least one nullable column. |
| |
| if (constraintDN.constraintType == |
| DataDictionary.UNIQUE_CONSTRAINT && |
| (dd.checkVersion( |
| DataDictionary.DD_VERSION_DERBY_10_4, null))) |
| { |
| boolean contains_nullable_columns = |
| areColumnsNullable(constraintDN, td); |
| |
| // if all the columns are non nullable, continue to use |
| // a unique backing index. |
| boolean unique = |
| !contains_nullable_columns; |
| |
| // Only use a "unique with duplicate nulls" backing index |
| // for constraints with nullable columns. |
| boolean uniqueWithDuplicateNulls = |
| contains_nullable_columns; |
| |
| indexAction = genIndexAction( |
| forCreateTable, |
| unique, |
| uniqueWithDuplicateNulls, |
| cChars[0], // deferrable? |
| cChars[1], // initiallyDeferred? |
| null, constraintDN, |
| columnNames, true, tableSd, tableName, |
| constraintType, dd); |
| } |
| else |
| { // PRIMARY KEY, FOREIGN KEY |
| // For foreign key constraint we do no mark the |
| // index as deferrable; since checking isn't done on |
| // duplicate keys there. |
| indexAction = genIndexAction( |
| forCreateTable, |
| constraintDN.requiresUniqueIndex(), false, |
| cChars[0], |
| cChars[1], |
| null, constraintDN, |
| columnNames, true, tableSd, tableName, |
| constraintType, dd); |
| } |
| } |
| |
| if (constraintType == DataDictionary.DROP_CONSTRAINT) |
| { |
| if (SanityManager.DEBUG) |
| { |
| // Can't drop constraints on a create table. |
| SanityManager.ASSERT(!forCreateTable); |
| } |
| conActions[conActionIndex] = |
| getGenericConstantActionFactory(). |
| getDropConstraintConstantAction( |
| constraintName, |
| constraintDN.getDropSchemaName(), /// FiX |
| tableName, |
| td.getUUID(), |
| tableSd.getSchemaName(), |
| indexAction, |
| constraintDN.getDropBehavior(), |
| constraintDN.getVerifyType()); |
| } |
| else if (constraintType == DataDictionary.MODIFY_CONSTRAINT) { |
| conActions[conActionIndex] = |
| getGenericConstantActionFactory(). |
| getAlterConstraintConstantAction( |
| constraintName, |
| constraintDN.getDropSchemaName(), |
| cChars, |
| tableName, |
| td.getUUID(), |
| tableSd.getSchemaName(), |
| indexAction); |
| |
| } |
| else |
| { |
| ProviderList apl = constraintDN.getAuxiliaryProviderList(); |
| ConstraintInfo refInfo = null; |
| ProviderInfo[] providerInfos; |
| |
| if (constraintDN instanceof FKConstraintDefinitionNode) |
| { |
| refInfo = ((FKConstraintDefinitionNode)constraintDN).getReferencedConstraintInfo(); |
| } |
| |
| /* Create the ProviderInfos, if the constraint is dependent on any Providers */ |
| if (apl != null && apl.size() > 0) |
| { |
| /* Get all the dependencies for the current statement and transfer |
| * them to this view. |
| */ |
| DependencyManager dm = dd.getDependencyManager(); |
| providerInfos = dm.getPersistentProviderInfos(apl); |
| } |
| else |
| { |
| providerInfos = new ProviderInfo[0]; |
| // System.out.println("TABLE ELEMENT LIST EMPTY"); |
| } |
| |
| conActions[conActionIndex++] = |
| getGenericConstantActionFactory(). |
| getCreateConstraintConstantAction( |
| constraintName, |
| constraintType, |
| cChars, |
| forCreateTable, |
| tableName, |
| ((td != null) ? td.getUUID() : (UUID) null), |
| tableSd.getSchemaName(), |
| columnNames, |
| indexAction, |
| constraintText, |
| refInfo, |
| providerInfos); |
| } |
| } |
| } |
| |
| //check if one array is same as another |
| private boolean columnsMatch(String[] columnNames1, String[] columnNames2) |
| { |
| int srcCount, srcSize, destCount,destSize; |
| |
| if (columnNames1.length != columnNames2.length) |
| return false; |
| |
| srcSize = columnNames1.length; |
| destSize = columnNames2.length; |
| |
| for (srcCount = 0; srcCount < srcSize; srcCount++) |
| { |
| boolean match = false; |
| for (destCount = 0; destCount < destSize; destCount++) { |
| if (columnNames1[srcCount].equals(columnNames2[destCount])) { |
| match = true; |
| break; |
| } |
| } |
| if (match == false) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * utility to generated the call to create the index. |
| * <p> |
| * |
| * |
| * @param forCreateTable Executed as part of a CREATE TABLE |
| * @param isUnique True means it will be a unique index |
| * @param isUniqueWithDuplicateNulls True means index check and disallow |
| * any duplicate key if key has no |
| * column with a null value. If any |
| * column in the key has a null value, |
| * no checking is done and insert will |
| * always succeed. |
| * @param hasDeferrableChecking True if index is used to back a |
| * deferrable constraint |
| * @param initiallyDeferred True means the deferrable constraint |
| * has deferred mode |
| * @param indexName The type of index (BTREE, for |
| * example) |
| * @param cdn |
| * @param columnNames Names of the columns in the index, |
| * in order. |
| * @param isConstraint TRUE if index is backing up a |
| * constraint, else FALSE. |
| * @param sd |
| * @param tableName Name of table the index will be on |
| * @param constraintType |
| * @param dd |
| **/ |
| private IndexConstantAction genIndexAction( |
| boolean forCreateTable, |
| boolean isUnique, |
| boolean isUniqueWithDuplicateNulls, |
| boolean hasDeferrableChecking, |
| boolean initiallyDeferred, |
| String indexName, |
| ConstraintDefinitionNode cdn, |
| String[] columnNames, |
| boolean isConstraint, |
| SchemaDescriptor sd, |
| String tableName, |
| int constraintType, |
| DataDictionary dd) |
| throws StandardException |
| { |
| if (indexName == null) |
| { |
| indexName = cdn.getBackingIndexName(dd); |
| } |
| |
| if (constraintType == DataDictionary.DROP_CONSTRAINT) |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (forCreateTable) |
| SanityManager.THROWASSERT( |
| "DROP INDEX with forCreateTable true"); |
| } |
| |
| return getGenericConstantActionFactory().getDropIndexConstantAction( |
| null, |
| indexName, |
| tableName, |
| sd.getSchemaName(), |
| td.getUUID(), |
| td.getHeapConglomerateId()); |
| } |
| else |
| { |
| boolean[] isAscending = new boolean[columnNames.length]; |
| |
| for (int i = 0; i < isAscending.length; i++) |
| isAscending[i] = true; |
| |
| return getGenericConstantActionFactory().getCreateIndexConstantAction( |
| forCreateTable, |
| isUnique, |
| isUniqueWithDuplicateNulls, |
| hasDeferrableChecking, |
| initiallyDeferred, |
| constraintType, |
| "BTREE", // indexType |
| sd.getSchemaName(), |
| indexName, |
| tableName, |
| ((td != null) ? td.getUUID() : (UUID) null), |
| columnNames, |
| isAscending, |
| isConstraint, |
| cdn.getBackingIndexUUID(), |
| checkIndexPageSizeProperty(cdn)); |
| } |
| } |
| /** |
| * Checks if the index should use a larger page size. |
| * |
| * If the columns in the index are large, and if the user hasn't already |
| * specified a page size to use, then we may need to default to the |
| * large page size in order to get an index with sufficiently large pages. |
| * For example, this DDL should use a larger page size for the index |
| * that backs the PRIMARY KEY constraint: |
| * |
| * create table t (x varchar(1000) primary key) |
| * |
| * @param cdn Constraint node |
| * |
| * @return properties to use for creating the index |
| */ |
| private Properties checkIndexPageSizeProperty(ConstraintDefinitionNode cdn) |
| throws StandardException |
| { |
| Properties result = cdn.getProperties(); |
| if (result == null) |
| result = new Properties(); |
| if ( result.get(Property.PAGE_SIZE_PARAMETER) != null || |
| PropertyUtil.getServiceProperty( |
| getLanguageConnectionContext().getTransactionCompile(), |
| Property.PAGE_SIZE_PARAMETER) != null) |
| { |
| // do not override the user's choice of page size, whether it |
| // is set for the whole database or just set on this statement. |
| return result; |
| } |
| |
| int approxLength = 0; |
| |
| for (ResultColumn rc : cdn.getColumnList()) |
| { |
| String colName = rc.getName(); |
| DataTypeDescriptor dtd; |
| if (td == null) |
| dtd = getColumnDataTypeDescriptor(colName); |
| else |
| dtd = getColumnDataTypeDescriptor(colName, td); |
| // There may be no DTD if the column does not exist. That syntax |
| // error is not caught til later in processing, so here we just |
| // skip the length checking if the column doesn't exist. |
| if (dtd != null) |
| approxLength+=dtd.getTypeId().getApproximateLengthInBytes(dtd); |
| } |
| if (approxLength > Property.IDX_PAGE_SIZE_BUMP_THRESHOLD) |
| { |
| result.put( |
| Property.PAGE_SIZE_PARAMETER, |
| Property.PAGE_SIZE_DEFAULT_LONG); |
| } |
| return result; |
| } |
| |
| |
| /** |
| * Check to make sure that there are no duplicate column names |
| * in the list. (The comparison here is case sensitive. |
| * The work of converting column names that are not quoted |
| * identifiers to upper case is handled by the parser.) |
| * RESOLVE: This check will also be performed by alter table. |
| * |
| * @param ddlStmt DDLStatementNode which contains this list |
| * @param seenNames The column names seen so far (for enforcing uniqueness) |
| * @param colName Column name to check for. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| private void checkForDuplicateColumns(DDLStatementNode ddlStmt, |
| Set<String> seenNames, |
| String colName) |
| throws StandardException |
| { |
| if (!seenNames.add(colName)) |
| { |
| /* RESOLVE - different error messages for create and alter table */ |
| if (ddlStmt instanceof CreateTableNode) |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_NAME_CREATE, colName); |
| } |
| } |
| } |
| |
| /** |
| * Check to make sure that there are no duplicate constraint names |
| * in the list. (The comparison here is case sensitive. |
| * The work of converting column names that are not quoted |
| * identifiers to upper case is handled by the parser.) |
| * RESOLVE: This check will also be performed by alter table. |
| * |
| * @param ddlStmt DDLStatementNode which contains this list |
| * @param seenNames The constraint names seen so far (for enforcing |
| * uniqueness) |
| * |
| * @exception StandardException Thrown on error |
| */ |
| private void checkForDuplicateConstraintNames(DDLStatementNode ddlStmt, |
| Set<String> seenNames, |
| String constraintName) |
| throws StandardException |
| { |
| if (constraintName == null) |
| return; |
| |
| if (!seenNames.add(constraintName)) { |
| |
| /* RESOLVE - different error messages for create and alter table */ |
| if (ddlStmt instanceof CreateTableNode) |
| { |
| /* RESOLVE - new error message */ |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_CONSTRAINT_NAME_CREATE, |
| constraintName); |
| } |
| } |
| } |
| |
| /** |
| * Verify that a primary/unique table constraint has a valid column list. |
| * (All columns in table and no duplicates.) |
| * |
| * @param ddlStmt The outer DDLStatementNode |
| * @param cdn The ConstraintDefinitionNode |
| * |
| * @exception StandardException Thrown if the column list is invalid |
| */ |
| private void verifyUniqueColumnList(DDLStatementNode ddlStmt, |
| ConstraintDefinitionNode cdn) |
| throws StandardException |
| { |
| String invalidColName; |
| |
| /* Verify that every column in the list appears in the table's list of columns */ |
| if (ddlStmt instanceof CreateTableNode) |
| { |
| invalidColName = cdn.getColumnList().verifyCreateConstraintColumnList(this); |
| if (invalidColName != null) |
| { |
| throw StandardException.newException(SQLState.LANG_INVALID_CREATE_CONSTRAINT_COLUMN_LIST, |
| ddlStmt.getRelativeName(), |
| invalidColName); |
| } |
| } |
| else |
| { |
| /* RESOLVE - alter table will need to get table descriptor */ |
| } |
| |
| /* Check the uniqueness of the column names within the list */ |
| invalidColName = cdn.getColumnList().verifyUniqueNames(false); |
| if (invalidColName != null) |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_CONSTRAINT_COLUMN_NAME, invalidColName); |
| } |
| } |
| |
| /** |
| * Set all columns in that appear in a PRIMARY KEY constraint in a CREATE TABLE statement to NOT NULL. |
| * |
| * @param cdn The ConstraintDefinitionNode for a PRIMARY KEY constraint |
| */ |
| private void setColumnListToNotNull(ConstraintDefinitionNode cdn) |
| { |
| for (ResultColumn rc : cdn.getColumnList()) |
| { |
| findColumnDefinition(rc.getName()).setNullability(false); |
| } |
| } |
| |
| /** |
| * Checks if any of the columns in the constraint can be null. |
| * |
| * @param cdn Constraint node |
| * @param td tabe descriptor of the target table |
| * |
| * @return true if any of the column can be null false other wise |
| */ |
| private boolean areColumnsNullable ( |
| ConstraintDefinitionNode cdn, |
| TableDescriptor td) |
| { |
| for (ResultColumn rc : cdn.getColumnList()) |
| { |
| String colName = rc.getName(); |
| |
| DataTypeDescriptor dtd = (td == null) ? |
| getColumnDataTypeDescriptor(colName) : |
| getColumnDataTypeDescriptor(colName, td); |
| |
| // todo dtd may be null if the column does not exist, we should check that first |
| if (dtd != null && dtd.isNullable()) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void checkForNullColumns(ConstraintDefinitionNode cdn, TableDescriptor td) throws StandardException |
| { |
| for (ResultColumn rc : cdn.getColumnList()) |
| { |
| DataTypeDescriptor dtd = (td == null) ? |
| getColumnDataTypeDescriptor(rc.getName()) : |
| getColumnDataTypeDescriptor(rc.getName(), td); |
| |
| // todo dtd may be null if the column does not exist, we should check that first |
| if (dtd != null && dtd.isNullable()) |
| { |
| String errorState = |
| (getLanguageConnectionContext().getDataDictionary() |
| .checkVersion(DataDictionary.DD_VERSION_DERBY_10_4, null)) |
| ? SQLState.LANG_ADD_PRIMARY_KEY_ON_NULL_COLS |
| : SQLState.LANG_DB2_ADD_UNIQUE_OR_PRIMARY_KEY_ON_NULL_COLS; |
| |
| throw StandardException.newException(errorState, rc.getName()); |
| } |
| } |
| } |
| |
| private DataTypeDescriptor getColumnDataTypeDescriptor(String colName) |
| { |
| ColumnDefinitionNode col = findColumnDefinition(colName); |
| if (col != null) |
| return col.getType(); |
| |
| return null; |
| } |
| |
| private DataTypeDescriptor getColumnDataTypeDescriptor(String colName, TableDescriptor td) |
| { |
| // check existing columns |
| ColumnDescriptor cd = td.getColumnDescriptor(colName); |
| if (cd != null) |
| { |
| return cd.getType(); |
| } |
| // check for new columns |
| return getColumnDataTypeDescriptor(colName); |
| } |
| |
| /** |
| * Find the column definition node in this list that matches |
| * the passed in column name. |
| * @param colName |
| * @return Reference to column definition node or null if the column is |
| * not in the list. |
| */ |
| private ColumnDefinitionNode findColumnDefinition(String colName) { |
| for (TableElementNode te : this) { |
| if (te instanceof ColumnDefinitionNode) { |
| ColumnDefinitionNode cdn = (ColumnDefinitionNode) te; |
| if (colName.equals(cdn.getName())) { |
| return cdn; |
| } |
| } |
| } |
| return null; |
| } |
| |
| |
| /** |
| * Determine whether or not the parameter matches a column name in this |
| * list. |
| * |
| * @param colName |
| * The column name to search for. |
| * |
| * @return boolean Whether or not a match is found. |
| */ |
| boolean containsColumnName(String colName) |
| { |
| return findColumnDefinition(colName) != null; |
| } |
| } |
| |