blob: f2c4fa6769a20c58c1e422b378c2921720cd6e19 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.AlterTableNode
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 org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.Limits;
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.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
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.sql.execute.ConstantAction;
import org.apache.derby.impl.sql.execute.ColumnInfo;
import org.apache.derby.impl.sql.execute.ConstraintConstantAction;
import org.apache.derby.impl.sql.execute.CreateConstraintConstantAction;
/**
* A AlterTableNode represents a DDL statement that alters a table.
* It contains the name of the object to be created.
*
*/
class AlterTableNode extends DDLStatementNode
{
// The alter table action
public TableElementList tableElementList = null;
char lockGranularity;
/**
* updateStatistics will indicate that we are here for updating the
* statistics. It could be statistics of just one index or all the
* indexes on a given table.
*/
private boolean updateStatistics = false;
/**
* The flag updateStatisticsAll will tell if we are going to update the
* statistics of all indexes or just one index on a table.
*/
private boolean updateStatisticsAll = false;
/**
* dropStatistics will indicate that we are here for dropping the
* statistics. It could be statistics of just one index or all the
* indexes on a given table.
*/
private boolean dropStatistics;
/**
* The flag dropStatisticsAll will tell if we are going to drop the
* statistics of all indexes or just one index on a table.
*/
private boolean dropStatisticsAll;
/**
* If statistic is getting updated/dropped for just one index, then
* indexNameForStatistics will tell the name of the specific index
* whose statistics need to be updated/dropped.
*/
private String indexNameForStatistics;
public boolean compressTable = false;
public boolean sequential = false;
//The following three (purge, defragment and truncateEndOfTable) apply for
//inplace compress
public boolean purge = false;
public boolean defragment = false;
public boolean truncateEndOfTable = false;
public int behavior; // currently for drop column
public TableDescriptor baseTable;
protected int numConstraints;
private int changeType = UNKNOWN_TYPE;
private boolean truncateTable = false;
// constant action arguments
protected SchemaDescriptor schemaDescriptor = null;
protected ColumnInfo[] colInfos = null;
protected ConstraintConstantAction[] conActions = null;
/**
* Constructor for TRUNCATE TABLE
*
* @param tableName The name of the table being truncated
* @param cm Context manager
* @exception StandardException
*/
AlterTableNode(TableName tableName,
ContextManager cm) throws StandardException {
super(tableName, cm);
truncateTable = true;
schemaDescriptor = getSchemaDescriptor();
}
/**
* Constructor for COMPRESS using temporary tables
* rather than in place compress
*
* @param tableName The name of the table being altered
* @param sequential Whether or not the COMPRESS is SEQUENTIAL
* @param cm Context manager
*
* @exception StandardException Thrown on error
*/
AlterTableNode(TableName tableName,
boolean sequential,
ContextManager cm) throws StandardException {
super(tableName, cm);
this.sequential = sequential;
compressTable = true;
schemaDescriptor = getSchemaDescriptor();
}
/**
* Constructor for INPLACE COMPRESS
*
* @param tableName The name of the table being altered
* @param purge PURGE during INPLACE COMPRESS?
* @param defragment DEFRAGMENT during INPLACE COMPRESS?
* @param truncateEndOfTable TRUNCATE END during INPLACE COMPRESS?
* @param cm Context manager
*
* @exception StandardException Thrown on error
*/
AlterTableNode(TableName tableName,
boolean purge,
boolean defragment,
boolean truncateEndOfTable,
ContextManager cm) throws StandardException {
super(tableName, cm);
this.purge = purge;
this.defragment = defragment;
this.truncateEndOfTable = truncateEndOfTable;
compressTable = true;
schemaDescriptor = getSchemaDescriptor(true, false);
}
/**
* Constructor for UPDATE_STATISTICS or DROP_STATISTICS
*
* @param tableName The name of the table being altered
* @param changeType update or drop statistics
* @param statsAll {@code true} means update or drop
* the statistics of all the indexes on the table.
* {@code false} means update or drop the statistics of
* only the index name provided by next parameter.
* @param indexName Name of the index for which statistics is to be updated
* or dropped
* @param cm Context manager
* @throws StandardException
*/
AlterTableNode(
TableName tableName,
int changeType,
boolean statsAll,
String indexName,
ContextManager cm) throws StandardException {
super(tableName, cm);
this.changeType = changeType;
this.indexNameForStatistics = indexName;
switch (changeType) {
case UPDATE_STATISTICS:
this.updateStatisticsAll = statsAll;
updateStatistics = true;
break;
case DROP_STATISTICS:
this.dropStatisticsAll = statsAll;
dropStatistics = true;
break;
default:
if (SanityManager.DEBUG) {
SanityManager.NOTREACHED();
}
}
schemaDescriptor = getSchemaDescriptor();
}
/**
* Constructor for ADD_TYPE, DROP_TYPE, MODIFY_TYPE and LOCK_TYPE
* @param tableName The name of the table being altered
* @param changeType add, drop, modify or lock
* @param impactedElements list of table elements impacted
* @param lockGranularity lock granularity encoded in a single character
* @param behavior cascade or restrict (for DROP_TYPE)
* @param cm Context Manager
* @throws StandardException
*/
AlterTableNode(
TableName tableName,
int changeType,
TableElementList impactedElements,
char lockGranularity,
int behavior,
ContextManager cm) throws StandardException {
super(tableName, cm);
this.changeType = changeType;
switch (changeType) {
case ADD_TYPE:
case DROP_TYPE:
case MODIFY_TYPE:
case LOCKING_TYPE:
this.tableElementList = impactedElements;
this.lockGranularity = lockGranularity;
this.behavior = behavior;
break;
default:
if (SanityManager.DEBUG) {
SanityManager.NOTREACHED();
}
}
schemaDescriptor = getSchemaDescriptor();
}
/**
* Convert this object to a String. See comments in QueryTreeNode.java
* for how this should be done for tree printing.
*
* @return This object as a String
*/
@Override
public String toString()
{
if (SanityManager.DEBUG)
{
return super.toString() +
"objectName: " + getObjectName() + "\n" +
"lockGranularity: " + lockGranularity + "\n" +
"compressTable: " + compressTable + "\n" +
"sequential: " + sequential + "\n" +
"truncateTable: " + truncateTable + "\n" +
"purge: " + purge + "\n" +
"defragment: " + defragment + "\n" +
"truncateEndOfTable: " + truncateEndOfTable + "\n" +
"updateStatistics: " + updateStatistics + "\n" +
"updateStatisticsAll: " + updateStatisticsAll + "\n" +
"dropStatistics: " + dropStatistics + "\n" +
"dropStatisticsAll: " + dropStatisticsAll + "\n" +
"indexNameForStatistics: " +
indexNameForStatistics + "\n";
}
else
{
return "";
}
}
/**
* Prints the sub-nodes of this object. See QueryTreeNode.java for
* how tree printing is supposed to work.
* @param depth The depth to indent the sub-nodes
*/
@Override
void printSubNodes(int depth) {
if (SanityManager.DEBUG) {
if (tableElementList != null) {
printLabel(depth, "tableElementList: ");
tableElementList.treePrint(depth + 1);
}
}
}
public String statementToString()
{
if(truncateTable)
return "TRUNCATE TABLE";
else
return "ALTER TABLE";
}
public int getChangeType() { return changeType; }
// We inherit the generate() method from DDLStatementNode.
/**
* Bind this AlterTableNode. This means doing any static error
* checking that can be done before actually creating the table.
* For example, verifying that the user is not trying to add a
* non-nullable column.
*
*
* @exception StandardException Thrown on error
*/
@Override
public void bindStatement() throws StandardException
{
DataDictionary dd = getDataDictionary();
int numCheckConstraints = 0;
int numReferenceConstraints = 0;
int numGenerationClauses = 0;
int numBackingIndexes = 0;
/*
** Get the table descriptor. Checks the schema
** and the table.
*/
if(compressTable && (purge || defragment || truncateEndOfTable)) {
//We are dealing with inplace compress here and inplace compress is
//allowed on system schemas. In order to support inplace compress
//on user as well as system tables, we need to use special
//getTableDescriptor(boolean) call to get TableDescriptor. This
//getTableDescriptor(boolean) allows getting TableDescriptor for
//system tables without throwing an exception.
baseTable = getTableDescriptor(false);
} else
baseTable = getTableDescriptor();
//throw an exception if user is attempting to alter a temporary table
if (baseTable.getTableType() == TableDescriptor.GLOBAL_TEMPORARY_TABLE_TYPE)
{
throw StandardException.newException(SQLState.LANG_NOT_ALLOWED_FOR_DECLARED_GLOBAL_TEMP_TABLE);
}
/* Statement is dependent on the TableDescriptor */
getCompilerContext().createDependency(baseTable);
//If we are dealing with add column character type, then set that
//column's collation type to be the collation type of the schema.
//The collation derivation of such a column would be "implicit".
if (changeType == ADD_TYPE) {//the action is of type add.
if (tableElementList != null) {//check if is is add column
for (int i=0; i<tableElementList.size();i++) {
if (tableElementList.elementAt(i) instanceof ColumnDefinitionNode) {
ColumnDefinitionNode cdn = (ColumnDefinitionNode) tableElementList.elementAt(i);
//check if we are dealing with add character column
//
// For generated columns which omit an explicit
// datatype, we have to defer this work until we bind
// the generation clause
//
if ( cdn.hasGenerationClause() && ( cdn.getType() == null ) ) { continue; }
if ( cdn.getType() == null )
{
throw StandardException.newException
( SQLState.LANG_NEEDS_DATATYPE, cdn.getColumnName() );
}
if (cdn.getType().getTypeId().isStringTypeId()) {
//we found what we are looking for. Set the
//collation type of this column to be the same as
//schema descriptor's collation. Set the collation
//derivation as implicit
cdn.setCollationType(schemaDescriptor.getCollationType());
}
}
}
}
}
if (tableElementList != null)
{
tableElementList.validate(this, dd, baseTable);
/* Only 1012 columns allowed per table */
if ((tableElementList.countNumberOfColumns() + baseTable.getNumberOfColumns()) > Limits.DB2_MAX_COLUMNS_IN_TABLE)
{
throw StandardException.newException(SQLState.LANG_TOO_MANY_COLUMNS_IN_TABLE_OR_VIEW,
String.valueOf(tableElementList.countNumberOfColumns() + baseTable.getNumberOfColumns()),
getRelativeName(),
String.valueOf(Limits.DB2_MAX_COLUMNS_IN_TABLE));
}
/* Number of backing indexes in the alter table statment */
numBackingIndexes = tableElementList.countConstraints(DataDictionary.PRIMARYKEY_CONSTRAINT) +
tableElementList.countConstraints(DataDictionary.FOREIGNKEY_CONSTRAINT) +
tableElementList.countConstraints(DataDictionary.UNIQUE_CONSTRAINT);
/* Check the validity of all check constraints */
numCheckConstraints = tableElementList.countConstraints(
DataDictionary.CHECK_CONSTRAINT);
numReferenceConstraints = tableElementList.countConstraints(
DataDictionary.FOREIGNKEY_CONSTRAINT);
numGenerationClauses = tableElementList.countGenerationClauses();
}
//If the sum of backing indexes for constraints in alter table statement and total number of indexes on the table
//so far is more than 32767, then we need to throw an exception
if ((numBackingIndexes + baseTable.getTotalNumberOfIndexes()) > Limits.DB2_MAX_INDEXES_ON_TABLE)
{
throw StandardException.newException(SQLState.LANG_TOO_MANY_INDEXES_ON_TABLE,
String.valueOf(numBackingIndexes + baseTable.getTotalNumberOfIndexes()),
getRelativeName(),
String.valueOf(Limits.DB2_MAX_INDEXES_ON_TABLE));
}
if ( (numCheckConstraints > 0) || (numGenerationClauses > 0) || (numReferenceConstraints > 0))
{
/* In order to check the validity of the check constraints and
* generation clauses
* we must goober up a FromList containing a single table,
* the table being alter, with an RCL containing the existing and
* new columns and their types. This will allow us to
* bind the constraint definition trees against that
* FromList. When doing this, we verify that there are
* no nodes which can return non-deterministic results.
*/
FromList fromList = makeFromList( dd, tableElementList, false );
FormatableBitSet generatedColumns = baseTable.makeColumnMap( baseTable.getGeneratedColumns() );
/* Now that we've finally goobered stuff up, bind and validate
* the check constraints and generation clauses.
*/
if (numGenerationClauses > 0)
{ tableElementList.bindAndValidateGenerationClauses( schemaDescriptor, fromList, generatedColumns, baseTable ); }
if (numCheckConstraints > 0) { tableElementList.bindAndValidateCheckConstraints(fromList); }
if ( numReferenceConstraints > 0) { tableElementList.validateForeignKeysOnGenerationClauses( fromList, generatedColumns ); }
}
// must be done after resolving the datatypes of the generation clauses
if (tableElementList != null) { tableElementList.validatePrimaryKeyNullability(); }
//Check if we are in alter table to update/drop the statistics. If yes,
// then check if we are here to update/drop the statistics of a specific
// index. If yes, then verify that the indexname provided is a valid one.
if ((updateStatistics && !updateStatisticsAll) || (dropStatistics && !dropStatisticsAll))
{
ConglomerateDescriptor cd = null;
if (schemaDescriptor.getUUID() != null)
cd = dd.getConglomerateDescriptor(indexNameForStatistics, schemaDescriptor, false);
if (cd == null)
{
throw StandardException.newException(
SQLState.LANG_INDEX_NOT_FOUND,
schemaDescriptor.getSchemaName() + "." + indexNameForStatistics);
}
}
/* Unlike most other DDL, we will make this ALTER TABLE statement
* dependent on the table being altered. In general, we try to
* avoid this for DDL, but we are already requiring the table to
* exist at bind time (not required for create index) and we don't
* want the column ids to change out from under us before
* execution.
*/
getCompilerContext().createDependency(baseTable);
}
/**
* Return true if the node references SESSION schema tables (temporary or permanent)
*
* @return true if references SESSION schema tables, else false
*
* @exception StandardException Thrown on error
*/
@Override
public boolean referencesSessionSchema()
throws StandardException
{
//If alter table is on a SESSION schema table, then return true.
return isSessionSchema(baseTable.getSchemaName());
}
/**
* Create the Constant information that will drive the guts of Execution.
*
* @exception StandardException Thrown on failure
*/
@Override
public ConstantAction makeConstantAction() throws StandardException
{
prepConstantAction();
return getGenericConstantActionFactory().getAlterTableConstantAction(schemaDescriptor,
getRelativeName(),
baseTable.getUUID(),
baseTable.getHeapConglomerateId(),
TableDescriptor.BASE_TABLE_TYPE,
colInfos,
conActions,
lockGranularity,
compressTable,
behavior,
sequential,
truncateTable,
purge,
defragment,
truncateEndOfTable,
updateStatistics,
updateStatisticsAll,
dropStatistics,
dropStatisticsAll,
indexNameForStatistics);
}
/**
* Generate arguments to constant action. Called by makeConstantAction() in this class and in
* our subclass RepAlterTableNode.
*
*
* @exception StandardException Thrown on failure
*/
private void prepConstantAction() throws StandardException
{
if (tableElementList != null)
{
genColumnInfo();
}
/* If we've seen a constraint, then build a constraint list */
if (numConstraints > 0)
{
conActions = new ConstraintConstantAction[numConstraints];
tableElementList.genConstraintActions(false, conActions, getRelativeName(), schemaDescriptor,
getDataDictionary());
for (int conIndex = 0; conIndex < conActions.length; conIndex++)
{
ConstraintConstantAction cca = conActions[conIndex];
if (cca instanceof CreateConstraintConstantAction)
{
int constraintType = cca.getConstraintType();
if (constraintType == DataDictionary.PRIMARYKEY_CONSTRAINT)
{
DataDictionary dd = getDataDictionary();
// Check to see if a constraint of the same type
// already exists
ConstraintDescriptorList cdl =
dd.getConstraintDescriptors(baseTable);
if (cdl.getPrimaryKey() != null)
{
throw StandardException.newException(
SQLState.LANG_ADD_PRIMARY_KEY_FAILED1,
baseTable.getQualifiedName());
}
}
}
}
}
}
/**
* Generate the ColumnInfo argument for the constant action. Return the number of constraints.
*/
public void genColumnInfo()
throws StandardException
{
// for each column, stuff system.column
colInfos = new ColumnInfo[tableElementList.countNumberOfColumns()];
numConstraints = tableElementList.genColumnInfos(colInfos);
}
/**
* Accept the visitor for all visitable children of this node.
*
* @param v the visitor
*
* @exception StandardException on error
*/
@Override
void acceptChildren(Visitor v)
throws StandardException
{
super.acceptChildren(v);
if (tableElementList != null)
{
tableElementList.accept(v);
}
}
/*
* class interface
*/
}