Java tutorial
/* * Apache Derby is a subproject of the Apache DB project, and is licensed under * the Apache License, Version 2.0 (the "License"); you may not use these files * 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. * * Splice Machine, Inc. has modified this file. * * All Splice Machine modifications are Copyright 2012 - 2016 Splice Machine, Inc., * and are licensed to you under the License; you may not use this file except in * compliance with the License. * * 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 com.splicemachine.db.impl.sql.compile; import com.splicemachine.db.iapi.services.io.FormatableBitSet; import com.splicemachine.db.iapi.services.sanity.SanityManager; import com.splicemachine.db.iapi.error.StandardException; import com.splicemachine.db.iapi.reference.Property; import com.splicemachine.db.iapi.services.property.PropertyUtil; import com.splicemachine.db.iapi.sql.StatementType; import com.splicemachine.db.iapi.sql.compile.CompilerContext; import com.splicemachine.db.iapi.sql.compile.C_NodeTypes; import com.splicemachine.db.iapi.types.DataTypeDescriptor; import com.splicemachine.db.iapi.types.TypeId; import com.splicemachine.db.catalog.types.DefaultInfoImpl; import com.splicemachine.db.iapi.sql.dictionary.ColumnDescriptorList; import com.splicemachine.db.iapi.sql.dictionary.ConstraintDescriptor; import com.splicemachine.db.iapi.sql.dictionary.DataDictionary; import com.splicemachine.db.iapi.sql.dictionary.SchemaDescriptor; import com.splicemachine.db.iapi.sql.dictionary.TableDescriptor; import com.splicemachine.db.iapi.sql.execute.ConstantAction; import com.splicemachine.db.iapi.sql.depend.DependencyManager; import com.splicemachine.db.iapi.sql.depend.ProviderInfo; import com.splicemachine.db.iapi.sql.depend.ProviderList; import com.splicemachine.db.iapi.reference.SQLState; import com.splicemachine.db.impl.sql.catalog.Aggregate; import com.splicemachine.db.impl.sql.execute.ColumnInfo; import com.splicemachine.db.impl.sql.execute.ConstraintInfo; import com.splicemachine.db.iapi.sql.dictionary.ConstraintDescriptorList; import com.splicemachine.db.iapi.sql.dictionary.ColumnDescriptor; import com.splicemachine.db.catalog.UUID; import org.apache.commons.lang.ArrayUtils; import java.util.*; /** * A TableElementList represents the list of columns and other table elements * such as constraints in a CREATE TABLE or ALTER TABLE statement. * */ public class TableElementList extends QueryTreeNodeVector { private int numColumns; private TableDescriptor td; /** * Add a TableElementNode to this TableElementList * * @param tableElement The TableElementNode to add to this list */ public 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 { int size = size(); int collationType = sd.getCollationType(); for (int index = 0; index < size; index++) { TableElementNode tableElement = (TableElementNode) elementAt(index); if (tableElement instanceof ColumnDefinitionNode) { ColumnDefinitionNode cdn = (ColumnDefinitionNode) elementAt(index); setCollationTypeOnCharacterStringColumn(sd, cdn); } } } /** * 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()) { return; } else { 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(); Hashtable columnHT = new Hashtable(size + 2, (float) .999); Hashtable constraintHT = new Hashtable(size + 2, (float) .999); //all the primary key/unique key constraints for this table Vector constraintsVector = new Vector(); //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) constraintsVector.addElement(cd); } } } int tableType = TableDescriptor.BASE_TABLE_TYPE; if (ddlStmt instanceof CreateTableNode) tableType = ((CreateTableNode) ddlStmt).tableType; for (int index = 0; index < size; index++) { TableElementNode tableElement = (TableElementNode) elementAt(index); if (tableElement instanceof ColumnDefinitionNode) { ColumnDefinitionNode cdn = (ColumnDefinitionNode) elementAt(index); 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, columnHT, 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 vector if (cdn.getConstraintType() == DataDictionary.PRIMARYKEY_CONSTRAINT || cdn.getConstraintType() == DataDictionary.UNIQUE_CONSTRAINT) { /* In case of create table, the vector 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 < constraintsVector.size(); i++) { destConstraint = constraintsVector.elementAt(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); } constraintsVector.addElement(cdn); } /* Make sure that there are no duplicate constraint names in the list */ checkForDuplicateConstraintNames(ddlStmt, constraintHT, cdn.getConstraintMoniker()); /* Make sure that the constraint we are trying to drop exists */ if (cdn.getConstraintType() == DataDictionary.DROP_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_NON_EXISTENT_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); } else if (cdn.hasForeignKeyConstraint()) { // for FOREIGN KEY, check that columns are unique verifyUniqueColumnList(ddlStmt, cdn); } } /* Can have only one autoincrement column in DB2 mode */ if (numAutoCols > 1) 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 { int size = size(); for (int index = 0; index < size; index++) { TableElementNode tableElement = (TableElementNode) elementAt(index); 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. */ public int countConstraints(int constraintType) { int numConstraints = 0; int size = size(); for (int index = 0; index < size; index++) { ConstraintDefinitionNode cdn; TableElementNode element = (TableElementNode) elementAt(index); if (!(element instanceof ConstraintDefinitionNode)) { continue; } cdn = (ConstraintDefinitionNode) element; if (constraintType == cdn.getConstraintType()) { numConstraints++; } } return numConstraints; } /** * Count the number of generation clauses. */ public int countGenerationClauses() { int numGenerationClauses = 0; int size = size(); for (int index = 0; index < size; index++) { ColumnDefinitionNode cdn; TableElementNode element = (TableElementNode) elementAt(index); if (!(element instanceof ColumnDefinitionNode)) { continue; } cdn = (ColumnDefinitionNode) element; if (cdn.hasGenerationClause()) { numGenerationClauses++; } } return numGenerationClauses; } /** * Count the number of columns. * * @return int The number of columns. */ public 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. */ public int genColumnInfos(ColumnInfo[] colInfos, ResultColumnList partitionedColumnList) throws StandardException { int numConstraints = 0; int size = size(); String[] columnNames = partitionedColumnList == null ? new String[0] : partitionedColumnList.getColumnNames(); for (int index = 0; index < size; index++) { if (((TableElementNode) elementAt(index)).getElementType() == TableElementNode.AT_DROP_COLUMN) { String columnName = ((TableElementNode) elementAt(index)).getName(); colInfos[index] = new ColumnInfo(columnName, td.getColumnDescriptor(columnName).getType(), null, null, null, null, null, ColumnInfo.DROP, 0, 0, 0, ArrayUtils.indexOf(columnNames, columnName)); 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), -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 */ public 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 = (ValueNode) getNodeFactory().getNode(C_NodeTypes.BASE_COLUMN_NODE, cdn.getColumnName(), exposedName, cdn.getType(), getContextManager()); resultColumn = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN, 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 { CompilerContext cc; FromBaseTable table = (FromBaseTable) fromList.elementAt(0); int size = size(); cc = getCompilerContext(); Vector aggregateVector = new Vector(); for (int index = 0; index < size; index++) { ConstraintDefinitionNode cdn; TableElementNode element = (TableElementNode) elementAt(index); 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, aggregateVector); // no aggregates, please if (aggregateVector.size() != 0) { 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 build an array of * 1-based columnIds that the constraint references. */ ResultColumnList rcl = table.getResultColumns(); int numReferenced = rcl.countReferencedColumns(); int[] checkColumnReferences = new int[numReferenced]; rcl.recordColumnReferences(checkColumnReferences, 1); cdn.setCheckColumnReferences(checkColumnReferences); /* 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 refRCL = (ResultColumnList) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN_LIST, 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 = ((ResultColumn) (cdn.getColumnList().elementAt(0))).getName(); if (numReferenced > 1 || !colName.equals(((ResultColumn) (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(); } } /** * 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 { CompilerContext cc; FromBaseTable table = (FromBaseTable) fromList.elementAt(0); ResultColumnList tableColumns = table.getResultColumns(); int columnCount = table.getResultColumns().size(); int size = size(); // complain if a generation clause references another generated column findIllegalGenerationReferences(fromList, baseTable); generatedColumns.grow(columnCount + 1); cc = getCompilerContext(); List<AggregateNode> aggregateVector = new ArrayList<AggregateNode>(); for (int index = 0; index < size; index++) { ColumnDefinitionNode cdn; TableElementNode element = (TableElementNode) elementAt(index); 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, aggregateVector); // // 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 (aggregateVector.size() != 0) { 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] = ((ResultColumn) 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 generatedColumns = new ArrayList(); HashSet names = new HashSet(); int size = size(); // 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 (int index = 0; index < size; index++) { ColumnDefinitionNode cdn; TableElementNode element = (TableElementNode) elementAt(index); 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 = (ColumnDefinitionNode) generatedColumns.get(i); GenerationClauseNode generationClauseNode = cdn.getGenerationClauseNode(); Vector referencedColumns = generationClauseNode.findReferencedColumns(); int refCount = referencedColumns.size(); for (int j = 0; j < refCount; j++) { String name = ((ColumnReference) referencedColumns.elementAt(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(); int size = size(); // loop through the foreign keys, looking for keys which violate the // rulse we're enforcing for (int index = 0; index < size; index++) { TableElementNode element = (TableElementNode) elementAt(index); 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. // ResultColumnList keyCols = fk.getColumnList(); int keyCount = keyCols.size(); for (int i = 0; i < keyCount; i++) { ResultColumn keyCol = (ResultColumn) keyCols.elementAt(i); 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, ConstantAction[] conActions, String tableName, SchemaDescriptor tableSd, DataDictionary dd) throws StandardException { int size = size(); int conActionIndex = 0; for (int index = 0; index < size; index++) { String[] columnNames = null; TableElementNode ten = (TableElementNode) elementAt(index); ConstantAction indexAction = null; if (!ten.hasConstraint()) { continue; } if (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(); 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) { 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, null, constraintDN, columnNames, true, tableSd, tableName, constraintType, dd); } else { indexAction = genIndexAction(forCreateTable, constraintDN.requiresUniqueIndex(), false, 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 { ProviderList apl = constraintDN.getAuxiliaryProviderList(); ConstraintInfo refInfo = null; ProviderInfo[] providerInfos = null; 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, forCreateTable, tableName, ((td != null) ? td.getUUID() : (UUID) null), tableSd.getSchemaName(), columnNames, indexAction, constraintText, true, // enabled refInfo, providerInfos); } } } //check if one array is same as another private boolean columnsMatch(String[] columnNames1, String[] columnNames2) { int srcCount, srcSize, destCount, destSize; boolean match = true; if (columnNames1.length != columnNames2.length) return false; srcSize = columnNames1.length; destSize = columnNames2.length; for (srcCount = 0; srcCount < srcSize; srcCount++) { 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 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 ConstantAction genIndexAction(boolean forCreateTable, boolean isUnique, boolean isUniqueWithDuplicateNulls, 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, "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; } ResultColumnList rcl = cdn.getColumnList(); int approxLength = 0; for (int index = 0; index < rcl.size(); index++) { String colName = ((ResultColumn) rcl.elementAt(index)).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 ht Hashtable for enforcing uniqueness. * @param colName Column name to check for. * * @exception StandardException Thrown on error */ private void checkForDuplicateColumns(DDLStatementNode ddlStmt, Hashtable ht, String colName) throws StandardException { Object object = ht.put(colName, colName); if (object != null) { /* 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 * * @exception StandardException Thrown on error */ private void checkForDuplicateConstraintNames(DDLStatementNode ddlStmt, Hashtable ht, String constraintName) throws StandardException { if (constraintName == null) return; Object object = ht.put(constraintName, constraintName); if (object != null) { /* 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) { ResultColumnList rcl = cdn.getColumnList(); int rclSize = rcl.size(); for (int index = 0; index < rclSize; index++) { String colName = ((ResultColumn) rcl.elementAt(index)).getName(); findColumnDefinition(colName).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) { ResultColumnList rcl = cdn.getColumnList(); int rclSize = rcl.size(); for (int index = 0; index < rclSize; index++) { String colName = ((ResultColumn) rcl.elementAt(index)).getName(); DataTypeDescriptor dtd; if (td == null) { dtd = getColumnDataTypeDescriptor(colName); } else { dtd = 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 { ResultColumnList rcl = cdn.getColumnList(); int rclSize = rcl.size(); for (int index = 0; index < rclSize; index++) { String colName = ((ResultColumn) rcl.elementAt(index)).getName(); DataTypeDescriptor dtd; if (td == null) { dtd = getColumnDataTypeDescriptor(colName); } else { dtd = getColumnDataTypeDescriptor(colName, td); } // todo dtd may be null if the column does not exist, we should check that first if (dtd != null && dtd.isNullable()) { throw StandardException.newException(SQLState.LANG_ADD_PRIMARY_KEY_ON_NULL_COLS, colName); } } } 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) { int size = size(); for (int index = 0; index < size; index++) { TableElementNode tableElement = (TableElementNode) elementAt(index); if (tableElement instanceof ColumnDefinitionNode) { ColumnDefinitionNode cdn = (ColumnDefinitionNode) tableElement; 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. */ public boolean containsColumnName(String colName) { return findColumnDefinition(colName) != null; } }