private void dropColumnFromTable()

in java/org.apache.derby.engine/org/apache/derby/impl/sql/execute/AlterTableConstantAction.java [1344:1867]


    private void dropColumnFromTable(String columnName )
	        throws StandardException
	{
		boolean cascade = (behavior == StatementType.DROP_CASCADE);

        // drop any generated columns which reference this column
        ColumnDescriptorList    generatedColumnList = td.getGeneratedColumns();
        int                                 generatedColumnCount = generatedColumnList.size();
        ArrayList<String>           cascadedDroppedColumns = new ArrayList<String> ();
        for ( int i = 0; i < generatedColumnCount; i++ )
        {
            ColumnDescriptor    generatedColumn = generatedColumnList.elementAt( i );
            String[]                       referencedColumnNames = generatedColumn.getDefaultInfo().getReferencedColumnNames();
            int                         referencedColumnCount = referencedColumnNames.length;
            for ( int j = 0; j < referencedColumnCount; j++ )
            {
                if ( columnName.equals( referencedColumnNames[ j ] ) )
                {
                    String      generatedColumnName = generatedColumn.getColumnName();
                    
                    // ok, the current generated column references the column
                    // we're trying to drop
                    if (! cascade)
                    {
                        // Reject the DROP COLUMN, because there exists a
                        // generated column which references this column.
                        //
                        throw StandardException.newException
                            (
                             SQLState.LANG_PROVIDER_HAS_DEPENDENT_OBJECT,
                             dm.getActionString(DependencyManager.DROP_COLUMN),
                             columnName, "GENERATED COLUMN",
                             generatedColumnName
                             );
                    }
                    else
                    {
                        cascadedDroppedColumns.add( generatedColumnName );
                    }
                }
            }
        }

		DataDescriptorGenerator ddg = dd.getDataDescriptorGenerator();
        int                             cascadedDrops = cascadedDroppedColumns.size();
		int sizeAfterCascadedDrops = td.getColumnDescriptorList().size() - cascadedDrops;

		// can NOT drop a column if it is the only one in the table
		if (sizeAfterCascadedDrops == 1)
		{
			throw StandardException.newException(
                    SQLState.LANG_PROVIDER_HAS_DEPENDENT_OBJECT,
                    dm.getActionString(DependencyManager.DROP_COLUMN),
                    "THE *LAST* COLUMN " + columnName,
                    "TABLE",
                    td.getQualifiedName() );
		}

        // now drop dependent generated columns
        for ( int i = 0; i < cascadedDrops; i++ )
        {
            String      generatedColumnName = cascadedDroppedColumns.get( i );
            
            activation.addWarning
                ( StandardException.newWarning( SQLState.LANG_GEN_COL_DROPPED, generatedColumnName, td.getName() ) );

            //
            // We can only recurse 2 levels since a generation clause cannot
            // refer to other generated columns.
            //
            dropColumnFromTable(generatedColumnName);
        }

        /*
         * Cascaded drops of dependent generated columns may require us to
         * rebuild the table descriptor.
         */
		td = dd.getTableDescriptor(tableId);

		ColumnDescriptor columnDescriptor = td.getColumnDescriptor( columnName );

		// We already verified this in bind, but do it again
		if (columnDescriptor == null)
		{
			throw 
				StandardException.newException(
                    SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, 
                    columnName,
                    td.getQualifiedName());
		}

		int size = td.getColumnDescriptorList().size();
		droppedColumnPosition = columnDescriptor.getPosition();

		FormatableBitSet toDrop = new FormatableBitSet(size + 1);
		toDrop.set(droppedColumnPosition);
		td.setReferencedColumnMap(toDrop);

		dm.invalidateFor(td, 
                        (cascade ? DependencyManager.DROP_COLUMN
                                 : DependencyManager.DROP_COLUMN_RESTRICT),
                        lcc);
					
		// If column has a default we drop the default and any dependencies
		if (columnDescriptor.getDefaultInfo() != null)
		{
			dm.clearDependencies(
                lcc, columnDescriptor.getDefaultDescriptor(dd));
		}

        // If the column is an identity column (and the dictionary is at least version 10.11),
        // then we need to drop the system-generated sequence backing it.
        if (
            columnDescriptor.isAutoincrement() &&
            dd.checkVersion( DataDictionary.DD_VERSION_DERBY_10_11, null )
            )
        {
            DropTableConstantAction.dropIdentitySequence( dd, td, activation );
        }

		//Now go through each trigger on this table and see if the column 
		//being dropped is part of it's trigger columns or trigger action 
		//columns which are used through REFERENCING clause
        for (TriggerDescriptor trd : dd.getTriggerDescriptors(td)) {
			//If we find that the trigger is dependent on the column being 
			//dropped because column is part of trigger columns list, then
			//we will give a warning or drop the trigger based on whether
			//ALTER TABLE DROP COLUMN is RESTRICT or CASCADE. In such a
			//case, no need to check if the trigger action columns referenced
			//through REFERENCING clause also used the column being dropped.
			boolean triggerDroppedAlready = false;

			int[] referencedCols = trd.getReferencedCols();
			if (referencedCols != null) {
				int refColLen = referencedCols.length, j;
				boolean changed = false;
				for (j = 0; j < refColLen; j++)
				{
					if (referencedCols[j] > droppedColumnPosition)
	                {
						//Trigger is not defined on the column being dropped
						//but the column position of trigger column is changing
						//because the position of the column being dropped is
						//before the the trigger column
						changed = true;
	                }
					else if (referencedCols[j] == droppedColumnPosition)
					{
						//the trigger is defined on the column being dropped
						if (cascade)
						{
	                        trd.drop(lcc);
	                        triggerDroppedAlready = true;
							activation.addWarning(
								StandardException.newWarning(
	                                SQLState.LANG_TRIGGER_DROPPED, 
	                                trd.getName(), td.getName()));
						}
						else
						{	// we'd better give an error if don't drop it,
							// otherwsie there would be unexpected behaviors
							throw StandardException.newException(
	                            SQLState.LANG_PROVIDER_HAS_DEPENDENT_OBJECT,
	                            dm.getActionString(DependencyManager.DROP_COLUMN),
	                            columnName, "TRIGGER",
	                            trd.getName() );
						}
						break;
					}
				}

				// The following if condition will be true if the column
				// getting dropped is not a trigger column, but one or more
				// of the trigge column's position has changed because of
				// drop column.
				if (j == refColLen && changed)
				{
					dd.dropTriggerDescriptor(trd, tc);
					for (j = 0; j < refColLen; j++)
					{
						if (referencedCols[j] > droppedColumnPosition)
							referencedCols[j]--;
					}
                    trd.setReferencedCols( referencedCols );
					dd.addDescriptor(trd, sd,
									 DataDictionary.SYSTRIGGERS_CATALOG_NUM,
									 false, tc);
				}
			}

			// If the trigger under consideration got dropped through the 
			// loop above, then move to next trigger
			if (triggerDroppedAlready) continue;
			
			// Column being dropped is not one of trigger columns. Check if 
			// that column is getting used inside the trigger action through 
			// REFERENCING clause. This can be tracked only for triggers 
			// created in 10.7 and higher releases. Derby releases prior to
			// that did not keep track of trigger action columns used 
			// through the REFERENCING clause.
			int[] referencedColsInTriggerAction = trd.getReferencedColsInTriggerAction();
			if (referencedColsInTriggerAction != null) {
				int refColInTriggerActionLen = referencedColsInTriggerAction.length, j;
				boolean changedColPositionInTriggerAction = false;
				for (j = 0; j < refColInTriggerActionLen; j++)
				{
					if (referencedColsInTriggerAction[j] > droppedColumnPosition)
					{
						changedColPositionInTriggerAction = true;
					}
					else if (referencedColsInTriggerAction[j] == droppedColumnPosition)
					{
						if (cascade)
						{
	                        trd.drop(lcc);
	                        triggerDroppedAlready = true;
							activation.addWarning(
								StandardException.newWarning(
	                                SQLState.LANG_TRIGGER_DROPPED, 
	                                trd.getName(), td.getName()));
						}
						else
						{	// we'd better give an error if don't drop it,
							throw StandardException.newException(
	                            SQLState.LANG_PROVIDER_HAS_DEPENDENT_OBJECT,
	                            dm.getActionString(DependencyManager.DROP_COLUMN),
	                            columnName, "TRIGGER",
	                            trd.getName() );
						}
						break;
					}
				}

				// change trigger to refer to columns in new positions
				// The following if condition will be true if the column
				// getting dropped is not getting used in the trigger action
				// sql through the REFERENCING clause but one or more of those
				// column's position has changed because of drop column.
				// This applies only to triggers created with 10.7 and higher.
				// Prior to that, Derby did not keep track of the trigger 
				// action column used through the REFERENCING clause. Such
				// triggers will be caught later on in this method after the
				// column has been actually dropped from the table descriptor.
				if (j == refColInTriggerActionLen && changedColPositionInTriggerAction)
				{
					dd.dropTriggerDescriptor(trd, tc);
					for (j = 0; j < refColInTriggerActionLen; j++)
					{
						if (referencedColsInTriggerAction[j] > droppedColumnPosition)
							referencedColsInTriggerAction[j]--;
					}
                    trd.setReferencedColsInTriggerAction( referencedColsInTriggerAction );
					dd.addDescriptor(trd, sd,
							 DataDictionary.SYSTRIGGERS_CATALOG_NUM,
							 false, tc);
				}
			}
		}

		ConstraintDescriptorList csdl = dd.getConstraintDescriptors(td);
		int csdl_size = csdl.size();

		ArrayList<ConstantAction> newCongloms = new ArrayList<ConstantAction>();

		// we want to remove referenced primary/unique keys in the second
		// round.  This will ensure that self-referential constraints will
		// work OK.
		int tbr_size = 0;
		ConstraintDescriptor[] toBeRemoved = 
            new ConstraintDescriptor[csdl_size];

		// let's go downwards, don't want to get messed up while removing
		for (int i = csdl_size - 1; i >= 0; i--)
		{
			ConstraintDescriptor cd = csdl.elementAt(i);
			int[] referencedColumns = cd.getReferencedColumns();
			int numRefCols = referencedColumns.length, j;
			boolean changed = false;
			for (j = 0; j < numRefCols; j++)
			{
				if (referencedColumns[j] > droppedColumnPosition)
					changed = true;
				if (referencedColumns[j] == droppedColumnPosition)
					break;
			}
			if (j == numRefCols)			// column not referenced
			{
				if ((cd instanceof CheckConstraintDescriptor) && changed)
				{
					dd.dropConstraintDescriptor(cd, tc);
					for (j = 0; j < numRefCols; j++)
					{
						if (referencedColumns[j] > droppedColumnPosition)
							referencedColumns[j]--;
					}
					((CheckConstraintDescriptor) cd).setReferencedColumnsDescriptor(new ReferencedColumnsDescriptorImpl(referencedColumns));
					dd.addConstraintDescriptor(cd, tc);
				}
				continue;
			}

			if (! cascade)
			{
				// Reject the DROP COLUMN, because there exists a constraint
				// which references this column.
				//
				throw StandardException.newException(
                        SQLState.LANG_PROVIDER_HAS_DEPENDENT_OBJECT,
                        dm.getActionString(DependencyManager.DROP_COLUMN),
                        columnName, "CONSTRAINT",
                        cd.getConstraintName() );
			}

			if (cd instanceof ReferencedKeyConstraintDescriptor)
			{
				// restrict will raise an error in invalidate if referenced
				toBeRemoved[tbr_size++] = cd;
				continue;
			}

			// drop now in all other cases
			dm.invalidateFor(cd, DependencyManager.DROP_CONSTRAINT,
									lcc);

			dropConstraint(cd, td, newCongloms, activation, lcc, true);
			activation.addWarning(
                StandardException.newWarning(SQLState.LANG_CONSTRAINT_DROPPED,
				cd.getConstraintName(), td.getName()));
		}

		for (int i = tbr_size - 1; i >= 0; i--)
		{
			ConstraintDescriptor cd = toBeRemoved[i];
			dropConstraint(cd, td, newCongloms, activation, lcc, false);

			activation.addWarning(
                StandardException.newWarning(SQLState.LANG_CONSTRAINT_DROPPED,
                cd.getConstraintName(), td.getName()));

			if (cascade)
			{
				ConstraintDescriptorList fkcdl = dd.getForeignKeys(cd.getUUID());

                for (ConstraintDescriptor fkcd : fkcdl)
                {
					dm.invalidateFor(fkcd,
									DependencyManager.DROP_CONSTRAINT,
									lcc);

					dropConstraint(fkcd, td,
						newCongloms, activation, lcc, true);

					activation.addWarning(
                        StandardException.newWarning(
                            SQLState.LANG_CONSTRAINT_DROPPED,
						    fkcd.getConstraintName(), 
                            fkcd.getTableDescriptor().getName()));
				}
			}

			dm.invalidateFor(cd, DependencyManager.DROP_CONSTRAINT, lcc);
			dm.clearDependencies(lcc, cd);
		}

		/* If there are new backing conglomerates which must be
		 * created to replace a dropped shared conglomerate
		 * (where the shared conglomerate was dropped as part
		 * of a "drop constraint" call above), then create them
		 * now.  We do this *after* dropping all dependent
		 * constraints because we don't want to waste time
		 * creating a new conglomerate if it's just going to be
		 * dropped again as part of another "drop constraint".
		 */
		createNewBackingCongloms(newCongloms, (long[])null);

        /*
         * The work we've done above, specifically the possible
         * dropping of primary key, foreign key, and unique constraints
         * and their underlying indexes, may have affected the table
         * descriptor. By re-reading the table descriptor here, we
         * ensure that the compressTable code is working with an
         * accurate table descriptor. Without this line, we may get
         * conglomerate-not-found errors and the like due to our
         * stale table descriptor.
         */
		td = dd.getTableDescriptor(tableId);

        compressTable();

		ColumnDescriptorList tab_cdl = td.getColumnDescriptorList();

		// drop the column from syscolumns 
		dd.dropColumnDescriptor(td.getUUID(), columnName, tc);		
		ColumnDescriptor[] cdlArray = 
            new ColumnDescriptor[size - columnDescriptor.getPosition()];

		// For each column in this table with a higher column position,
		// drop the entry from SYSCOLUMNS, but hold on to the column
		// descriptor and reset its position to adjust for the dropped
		// column. Then, re-add all those adjusted column descriptors
		// back to SYSCOLUMNS
		//
		for (int i = columnDescriptor.getPosition(), j = 0; i < size; i++, j++)
		{
            ColumnDescriptor cd = tab_cdl.elementAt(i);
			dd.dropColumnDescriptor(td.getUUID(), cd.getColumnName(), tc);
			cd.setPosition(i);
			if (cd.isAutoincrement())
			{
				cd.setAutoinc_create_or_modify_Start_Increment(
						ColumnDefinitionNode.CREATE_AUTOINCREMENT);
			}

			cdlArray[j] = cd;
		}
		dd.addDescriptorArray(cdlArray, td,
							  DataDictionary.SYSCOLUMNS_CATALOG_NUM, false, tc);

		// By this time, the column has been removed from the table descriptor.
		// Now, go through all the triggers and regenerate their trigger action
		// SPS and rebind the generated trigger action sql. If the trigger  
		// action is using the dropped column, it will get detected here. If 
		// not, then we will have generated the internal trigger action sql
		// which matches the trigger action sql provided by the user.
		//
		// eg of positive test case
		// create table atdc_16_tab1 (a1 integer, b1 integer, c1 integer);
		// create table atdc_16_tab2 (a2 integer, b2 integer, c2 integer);
		// create trigger atdc_16_trigger_1 
		//    after update of b1 on atdc_16_tab1
		//    REFERENCING NEW AS newt
		//    for each row 
		//    update atdc_16_tab2 set c2 = newt.c1
		// The internal representation for the trigger action before the column
		// is dropped is as follows
		// 	 update atdc_16_tab2 set c2 = 
		//   org.apache.derby.iapi.db.Factory::getTriggerExecutionContext().
		//   getONewRow().getInt(3)
		// After the drop column shown as below
		//   alter table DERBY4998_SOFT_UPGRADE_RESTRICT drop column c11
		// The above internal representation of tigger action sql is not 
		// correct anymore because column position of c1 in atdc_16_tab1 has 
		// now changed from 3 to 2. Following while loop will regenerate it and
		// change it to as follows
		// 	 update atdc_16_tab2 set c2 = 
		//   org.apache.derby.iapi.db.Factory::getTriggerExecutionContext().
		//   getONewRow().getInt(2)
		//
		// We could not do this before the actual column drop, because the 
		// rebind would have still found the column being dropped in the
		// table descriptor and hence use of such a column in the trigger
		// action rebind would not have been caught.

		//For the table on which ALTER TABLE is getting performed, find out
		// all the SPSDescriptors that use that table as a provider. We are
		// looking for SPSDescriptors that have been created internally for
		// trigger action SPSes. Through those SPSDescriptors, we will be
		// able to get to the triggers dependent on the table being altered
		//Following will get all the dependent objects that are using
		// ALTER TABLE table as provider
        List<DependencyDescriptor> depsOnAlterTableList =
                dd.getProvidersDescriptorList(td.getObjectID().toString());

        for (DependencyDescriptor depOnAT : depsOnAlterTableList)
        {
            // Go through all the dependent objects on the table being altered
            DependableFinder dependent = depOnAT.getDependentFinder();

            // For the given dependent, we are only interested in it if it is a
			// stored prepared statement.
			if (dependent.getSQLObjectType().equals(Dependable.STORED_PREPARED_STATEMENT))
			{
                // Look for all the dependent objects that are using this
				// stored prepared statement as provider. We are only 
				// interested in dependents that are triggers.
                List<DependencyDescriptor> depsTrigger =
                    dd.getProvidersDescriptorList(depOnAT.getUUID().toString());

                for (DependencyDescriptor depsTriggerDesc : depsTrigger)
				{
					DependableFinder providerIsTrigger = depsTriggerDesc.getDependentFinder();
					//For the given dependent, we are only interested in it if
					// it is a trigger
					if (providerIsTrigger.getSQLObjectType().equals(Dependable.TRIGGER)) {
						//Drop and recreate the trigger after regenerating 
						// it's trigger action plan. If the trigger action
						// depends on the column being dropped, it will be
						// caught here.
						TriggerDescriptor trdToBeDropped  = dd.getTriggerDescriptor(depsTriggerDesc.getUUID());

                        // First check for dependencies in the trigger's WHEN
                        // clause, if there is one.
                        UUID whenClauseId = trdToBeDropped.getWhenClauseId();
                        boolean gotDropped = false;
                        if (whenClauseId != null) {
                            gotDropped = columnDroppedAndTriggerDependencies(
                                    trdToBeDropped, whenClauseId, true,
                                    cascade, columnName);
                        }

                        // If no dependencies were found in the WHEN clause,
                        // we have to check if the triggered SQL statement
                        // depends on the column being dropped. But if there
                        // were dependencies and the trigger has already been
                        // dropped, there is no point in looking for more
                        // dependencies.
                        if (!gotDropped) {
                            columnDroppedAndTriggerDependencies(trdToBeDropped,
                                    trdToBeDropped.getActionId(), false,
                                    cascade, columnName);
                        }
					}
				}
			}
		}
		// Adjust the column permissions rows in SYSCOLPERMS to reflect the
		// changed column positions due to the dropped column:
		dd.updateSYSCOLPERMSforDropColumn(td.getUUID(), tc, columnDescriptor);

        // remove column descriptor from table descriptor. this fixes up the
        // list in case we were called recursively in order to cascade-drop a
        // dependent generated column.
        tab_cdl.remove( td.getColumnDescriptor( columnName ) );
	}