//columns which are used through REFERENCING clause
GenericDescriptorList tdl = dd.getTriggerDescriptors(td);
Enumeration descs = tdl.elements();
while (descs.hasMoreElements())
{
TriggerDescriptor trd = (TriggerDescriptor) descs.nextElement();
//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]--;
}
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]--;
}
dd.addDescriptor(trd, sd,
DataDictionary.SYSTRIGGERS_CATALOG_NUM,
false, tc);
}
}
}
ConstraintDescriptorList csdl = dd.getConstraintDescriptors(td);
int csdl_size = csdl.size();
ArrayList newCongloms = new ArrayList();
// 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 (int j = 0; j < fkcdl.size(); j++)
{
ConstraintDescriptor fkcd =
(ConstraintDescriptor) fkcdl.elementAt(j);
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 = (ColumnDescriptor) 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);
List deps = dd.getProvidersDescriptorList(td.getObjectID().toString());
for (Iterator depsIterator = deps.listIterator();
depsIterator.hasNext();)
{
DependencyDescriptor depDesc =
(DependencyDescriptor) depsIterator.next();
DependableFinder finder = depDesc.getProviderFinder();
if (finder instanceof DDColumnDependableFinder)
{
DDColumnDependableFinder colFinder =
(DDColumnDependableFinder) finder;
FormatableBitSet oldColumnBitMap =
new FormatableBitSet(colFinder.getColumnBitMap());
FormatableBitSet newColumnBitMap =
new FormatableBitSet(oldColumnBitMap);
newColumnBitMap.clear();
int bitLen = oldColumnBitMap.getLength();
for (int i = 0; i < bitLen; i++)
{
if (i < droppedColumnPosition && oldColumnBitMap.isSet(i))
newColumnBitMap.set(i);
if (i > droppedColumnPosition && oldColumnBitMap.isSet(i))
newColumnBitMap.set(i - 1);
}
if (newColumnBitMap.equals(oldColumnBitMap))
continue;
dd.dropStoredDependency(depDesc, tc);
colFinder.setColumnBitMap(newColumnBitMap.getByteArray());
dd.addDescriptor(depDesc, null,
DataDictionary.SYSDEPENDS_CATALOG_NUM,
true, tc);
}
}
// 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 ) );
// 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.
GenericDescriptorList tdlAfterColumnDrop = dd.getTriggerDescriptors(td);
Enumeration descsAfterColumnDrop = tdlAfterColumnDrop.elements();
while (descsAfterColumnDrop.hasMoreElements())
{
TriggerDescriptor trd = (TriggerDescriptor) descsAfterColumnDrop.nextElement();
dd.dropTriggerDescriptor(trd, tc);
// For the trigger, get the trigger action sql provided by the user
// in the create trigger sql. This sql is saved in the system
// table. Since a column has been dropped from the trigger table,
// the trigger action sql may not be valid anymore. To establish
// that, we need to regenerate the internal representation of that
// sql and bind it again.
// Here we get the trigger action sql and use the parser to build
// the parse tree for it.
SchemaDescriptor compSchema;
compSchema = dd.getSchemaDescriptor(trd.getSchemaDescriptor().getUUID(), null);
CompilerContext newCC = lcc.pushCompilerContext(compSchema);
Parser pa = newCC.getParser();
StatementNode stmtnode = (StatementNode)pa.parseStatement(trd.getTriggerDefinition());
lcc.popCompilerContext(newCC);
// Do not delete following. We use this in finally clause to
// determine if the CompilerContext needs to be popped.
newCC = null;
try {
// We are interested in ColumnReference classes in the parse tree
CollectNodesVisitor visitor = new CollectNodesVisitor(ColumnReference.class);
stmtnode.accept(visitor);
Vector refs = visitor.getList();
// Regenerate the internal representation for the trigger action
// sql using the ColumnReference classes in the parse tree. It
// will catch dropped column getting used in trigger action sql
// through the REFERENCING clause(this can happen only for the
// the triggers created prior to 10.7. Trigger created with
// 10.7 and higher keep track of trigger action column used
// through the REFERENCING clause in system table and hence
// use of dropped column will be detected earlier in this
// method for such triggers).
//
// We might catch errors like following during this step.
// Say that following pre-10.7 trigger exists in the system and
// user is dropping column c11. During the regeneration of the
// internal trigger action sql format, we will catch that
// column oldt.c11 does not exist anymore
// CREATE TRIGGER DERBY4998_SOFT_UPGRADE_RESTRICT_tr1
// AFTER UPDATE OF c12
// ON DERBY4998_SOFT_UPGRADE_RESTRICT REFERENCING OLD AS oldt
// FOR EACH ROW
// SELECT oldt.c11 from DERBY4998_SOFT_UPGRADE_RESTRICT
SPSDescriptor triggerActionSPSD = trd.getActionSPS(lcc);
int[] referencedColsInTriggerAction = new int[td.getNumberOfColumns()];
java.util.Arrays.fill(referencedColsInTriggerAction, -1);
triggerActionSPSD.setText(dd.getTriggerActionString(stmtnode,
trd.getOldReferencingName(),
trd.getNewReferencingName(),
trd.getTriggerDefinition(),
trd.getReferencedCols(),
referencedColsInTriggerAction,
0,
trd.getTableDescriptor(),
trd.getTriggerEventMask(),
true
));
// Now that we have the internal format of the trigger action sql,
// bind that sql to make sure that we are not using colunm being
// dropped in the trigger action sql directly (ie not through
// REFERENCING clause.
// eg
// create table atdc_12 (a integer, b integer);
// create trigger atdc_12_trigger_1 after update of a
// on atdc_12 for each row select a,b from atdc_12
// Drop one of the columns used in the trigger action
// alter table atdc_12 drop column b
// Following rebinding of the trigger action sql will catch the use
// of column b in trigger atdc_12_trigger_1
compSchema = dd.getSchemaDescriptor(trd.getSchemaDescriptor().getUUID(), null);
newCC = lcc.pushCompilerContext(compSchema);
newCC.setReliability(CompilerContext.INTERNAL_SQL_LEGAL);
pa = newCC.getParser();
stmtnode = (StatementNode)pa.parseStatement(triggerActionSPSD.getText());
// need a current dependent for bind
newCC.setCurrentDependent(triggerActionSPSD.getPreparedStatement());
stmtnode.bindStatement();
//Register the dependency between trigger table and trigger
// action SPS
dm.addDependency(triggerActionSPSD, td, lcc.getContextManager());
} catch (StandardException se)
{
if (se.getMessageId().equals(SQLState.LANG_COLUMN_NOT_FOUND))
{
if (cascade)
{
trd.drop(lcc);
activation.addWarning(
StandardException.newWarning(
SQLState.LANG_TRIGGER_DROPPED,
trd.getName(), td.getName()));
continue;
}
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() );
}
} else
throw se;
}
finally