/*

   Derby - Class org.apache.derby.impl.sql.compile.RenameNode

   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.shared.common.error.StandardException;
import org.apache.derby.shared.common.reference.SQLState;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.StatementType;
import org.apache.derby.iapi.sql.compile.CompilerContext;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;
import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;
import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptorList;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;
import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
import org.apache.derby.iapi.sql.dictionary.TupleDescriptor;
import org.apache.derby.iapi.sql.execute.ConstantAction;

/**
 * A RenameNode is the root of a QueryTree that represents a
 * RENAME TABLE/COLUMN/INDEX statement.
 *
 */

class RenameNode extends DDLStatementNode
{
	protected TableName newTableName;

	// original name of the object being renamed
	protected String oldObjectName;
	// original name for that object
	protected String newObjectName;

	protected TableDescriptor	td;
	private long conglomerateNumber;

	/* You can rename using either alter table or rename command to
	 * rename a table/column. An index can only be renamed with rename
	 * command. usedAlterTable flag is used to keep that information.
	 */
	protected boolean	usedAlterTable;

	/* renamingWhat will be set to 1 if user is renaming a table.
	 * Will be set to 2 if user is renaming a column and will be
	 * set to 3 if user is renaming an index
	 */
	protected int renamingWhat;

	/**
     * Constructor for a RenameNode
	 *
	 * @param tableName The name of the table. This is the table which is
	 *		being renamed in case of rename table. In case of rename
	 *		column, the column being renamed belongs to this table.
	 *		In case of rename index, this is null because index name
	 *		is unique within a schema and doesn't have to be
     *      associated with a table name.
     *      Coming from ALTER TABLE path, tableName will
     *      be TableName object. Mostly a TableName object, but coming from
     *      RENAME COLUMN path, tableName will be a String.
	 * @param oldObjectName This is either the name of column/index in case
	 *		of rename column/index. For rename table, this is null.
	 * @param newObjectName This is new name for table/column/index
	 * @param usedAlterTable True-Used Alter Table, False-Used Rename.
	 *		For rename index, this will always be false because
	 *		there is no alter table command to rename index
	 * @param renamingWhat Rename a 1 - table, 2 - column, 3 - index
     * @param cm context manager
	 *
	 * @exception StandardException Thrown on error
	 */
    RenameNode(Object tableName,
               String oldObjectName,
               String newObjectName,
               boolean usedAlterTable,
               int renamingWhat,
               ContextManager cm) throws StandardException
	{
        super(cm);
        this.usedAlterTable = usedAlterTable;
        this.renamingWhat = renamingWhat;

		switch (this.renamingWhat)
		{
			case StatementType.RENAME_TABLE:
				initAndCheck((TableName) tableName);
				this.newTableName =
                    makeTableName(getObjectName().getSchemaName(), newObjectName);
				this.oldObjectName = null;
				this.newObjectName = this.newTableName.getTableName();
				break;

			case StatementType.RENAME_COLUMN:
				/* coming from ALTER TABLE path, tableName will
				 * be TableName object. Coming from RENAME COLUMN
				 * path, tableName will be just a String.
				 */
				TableName actingObjectName;
				if (tableName instanceof TableName)
					actingObjectName = (TableName) tableName;
				else
					actingObjectName = makeTableName(null,
					(String)tableName);
				initAndCheck(actingObjectName);


                this.oldObjectName = oldObjectName;
                this.newObjectName = newObjectName;
				break;

			case StatementType.RENAME_INDEX:
                this.oldObjectName = oldObjectName;
                this.newObjectName = newObjectName;
				break;

			default:
				if (SanityManager.DEBUG) 
				SanityManager.THROWASSERT(
				"Unexpected rename action in RenameNode");
		}
	}

	/**
	 * 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) {

		switch (renamingWhat)
		{
			case StatementType.RENAME_TABLE:
				return super.toString() +
				"oldTableName: " + "\n" + getRelativeName() + "\n" +
				"newTableName: " + "\n" + newTableName + "\n" ;

			case StatementType.RENAME_COLUMN:
				return super.toString() +
				"oldTableName.oldColumnName:" + "\n" +
				getRelativeName() + "." + oldObjectName + "\n" +
				"newColumnName: " + "\n" + newObjectName + "\n" ;

			case StatementType.RENAME_INDEX:
				return super.toString() +
				"oldIndexName:" + "\n" + oldObjectName + "\n" +
				"newIndexName: " + "\n" + newObjectName + "\n" ;

			default:
				SanityManager.THROWASSERT(
				"Unexpected rename action in RenameNode");
				return "UNKNOWN";
		}
		} else {
			return "";
		}
	}

    String statementToString()
	{
		if (usedAlterTable)
			return "ALTER TABLE";
		else {
			switch (renamingWhat)
			{
				case StatementType.RENAME_TABLE:
					return "RENAME TABLE";

				case StatementType.RENAME_COLUMN:
					return "RENAME COLUMN";

				case StatementType.RENAME_INDEX:
					return "RENAME INDEX";

				default:
					if (SanityManager.DEBUG)
						SanityManager.THROWASSERT(
						"Unexpected rename action in RenameNode");
					return "UNKNOWN";
			}
    }
	}

	// We inherit the generate() method from DDLStatementNode.

	/**
	 * Bind this node.  This means doing any static error checking that
	 * can be done before actually renaming the table/column/index.
	 *
	 * For a table rename: looking up the from table, verifying it exists
	 * verifying it's not a system table, verifying it's not view
	 * and looking up to table, verifying it doesn't exist.
	 *
	 * For a column rename: looking up the table, verifying it exists,
	 * verifying it's not a system table, verifying it's not view, verifying
	 * the from column exists, verifying the to column doesn't exist.
	 *
	 * For a index rename: looking up the table, verifying it exists,
	 * verifying it's not a system table, verifying it's not view, verifying
	 * the from index exists, verifying the to index doesn't exist.
	 *
	 * @exception StandardException		Thrown on error
	 */
    @Override
	public void bindStatement() throws StandardException
	{
		CompilerContext			cc = getCompilerContext();
		DataDictionary			dd = getDataDictionary();
		ConglomerateDescriptor	cd;
		SchemaDescriptor sd;

		/* in case of rename index, the only thing we get from parser is
		 * current and new index names with no information about the
		 * table it belongs to. This is because index names are unique
		 * within a schema and hence then is no need to qualify an index
		 * name with a table name which we have to do for rename column.
		 * But from the index name, using the data dictionary, you can
		 * find the table it belongs to. Since most of the checking
		 * in bind is done using table descriptor, in the following if
		 * statement, we are trying to get the table information from the
		 * index name so it is available for the rest of he bind code.
		 */
		TableName baseTable;

		if (renamingWhat == StatementType.RENAME_INDEX) {

			sd = getSchemaDescriptor((String)null);

			ConglomerateDescriptor indexDescriptor =
				dd.getConglomerateDescriptor(oldObjectName, sd, false);
			if (indexDescriptor == null)
				throw StandardException.newException(
									 SQLState.LANG_INDEX_NOT_FOUND, oldObjectName);
			/* Get the table descriptor */
			td = dd.getTableDescriptor(indexDescriptor.getTableID());
			initAndCheck(makeTableName(td.getSchemaName(),
									   td.getName()));
		} else
			sd = getSchemaDescriptor();

		td = getTableDescriptor();

		//throw an exception if user is attempting a rename on temporary table
		if (td.getTableType() == TableDescriptor.GLOBAL_TEMPORARY_TABLE_TYPE)
		{
				throw StandardException.newException(SQLState.LANG_NOT_ALLOWED_FOR_DECLARED_GLOBAL_TEMP_TABLE);
		}

		switch (this.renamingWhat)
		{
			case StatementType.RENAME_TABLE:
				/* Verify that new table name does not exist in the database */
                TableDescriptor tabDesc = getTableDescriptor(newObjectName, sd);
                if (tabDesc != null)
                    throw descriptorExistsException(tabDesc, sd);
				renameTableBind(dd);
				break;

			case StatementType.RENAME_COLUMN:
				renameColumnBind(dd);
				break;

			case StatementType.RENAME_INDEX:
				ConglomerateDescriptor conglomDesc = dd.getConglomerateDescriptor(newObjectName, sd, false);
  				if (conglomDesc != null)
					throw descriptorExistsException(conglomDesc, sd);
  				break;

			default:
				if (SanityManager.DEBUG)
					SanityManager.THROWASSERT(
							"Unexpected rename action in RenameNode");
				break;
		}

		conglomerateNumber = td.getHeapConglomerateId();

		/* Get the base conglomerate descriptor */
		cd = td.getConglomerateDescriptor(conglomerateNumber);

		/* Statement is dependent on the TableDescriptor and ConglomerateDescriptor */
		cc.createDependency(td);
		cc.createDependency(cd);
	}

	/**
	 * 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 rename is on a SESSION schema table, then return true. 
		if (isSessionSchema(td.getSchemaName()))//existing table with rename action
			return true;

		//new name in rename action
		if (renamingWhat == StatementType.RENAME_TABLE && isSessionSchema(getSchemaDescriptor()))
			return true;

		return false;
	}

	//do any checking needs to be done at bind time for rename table
	private void renameTableBind(DataDictionary dd)
		throws StandardException
	{
		/* Verify that there are no check constraints on the table */
		ConstraintDescriptorList constraintDescriptorList = dd.getConstraintDescriptors(td);
		int size =
			constraintDescriptorList == null ? 0 : constraintDescriptorList.size();

		ConstraintDescriptor constraintDescriptor;

		// go through all the constraints defined on the table
		for (int index = 0; index < size; index++)
		{
			constraintDescriptor = constraintDescriptorList.elementAt(index);
			// if it is a check constraint, error
			if (constraintDescriptor.getConstraintType() == DataDictionary.CHECK_CONSTRAINT)
			{
				throw StandardException.newException(
						SQLState.LANG_PROVIDER_HAS_DEPENDENT_OBJECT,
						"RENAME",
						td.getName(),
						"CONSTRAINT",
						constraintDescriptor.getConstraintName());
			}
		}
	}
                           
	//do any checking needs to be done at bind time for rename column
	private void renameColumnBind(DataDictionary dd)
		throws StandardException
	{
		ColumnDescriptor columnDescriptor = td.getColumnDescriptor(oldObjectName);

		/* Verify that old column name does exist in the table */
		if (columnDescriptor == null)
			throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE,
			oldObjectName, getFullName());

		/* Verify that new column name does not exist in the table */
		ColumnDescriptor cd = td.getColumnDescriptor(newObjectName);
  		if (cd != null)
  			throw descriptorExistsException(cd, td);

        //
		// You cannot rename a column which is referenced by the generation
        // clause of a generated column.
        //
        ColumnDescriptorList    generatedColumns = td.getGeneratedColumns();
        int                                 generatedColumnCount = generatedColumns.size();
        for ( int i = 0; i < generatedColumnCount; i++ )
        {
            ColumnDescriptor    gc = generatedColumns.elementAt( i );
            String[]                    referencedColumns = gc.getDefaultInfo().getReferencedColumnNames();
            int                         refColCount = referencedColumns.length;
            for ( int j = 0; j < refColCount; j++ )
            {
                String      refName = referencedColumns[ j ];
                if ( oldObjectName.equals( refName ) )
                {
                    throw StandardException.newException( SQLState.LANG_GEN_COL_BAD_RENAME, oldObjectName, gc.getColumnName() );
                }
            }
        }

		/* Verify that there are no check constraints using the column being renamed */
		ConstraintDescriptorList constraintDescriptorList =
			dd.getConstraintDescriptors(td);
		int size =
			constraintDescriptorList == null ? 0 : constraintDescriptorList.size();

		ConstraintDescriptor constraintDescriptor;
		ColumnDescriptorList checkConstraintCDL;
		int	checkConstraintCDLSize;

		// go through all the constraints defined on the table
		for (int index = 0; index < size; index++)
		{
			constraintDescriptor = constraintDescriptorList.elementAt(index);
			// if it is a check constraint, verify that column being
			// renamed is not used in it's sql
			if (constraintDescriptor.getConstraintType() == DataDictionary.CHECK_CONSTRAINT)
			{
				checkConstraintCDL = constraintDescriptor.getColumnDescriptors();
				checkConstraintCDLSize = checkConstraintCDL.size();

				for (int index2 = 0; index2 < checkConstraintCDLSize; index2++)
					if (checkConstraintCDL.elementAt( index2 ) == columnDescriptor)
						throw StandardException.newException(
						SQLState.LANG_RENAME_COLUMN_WILL_BREAK_CHECK_CONSTRAINT,
						oldObjectName,
						constraintDescriptor.getConstraintName());
			}
		}
	}
                           
	/**
	 * Create the Constant information that will drive the guts of Execution
	 *
	 * @exception StandardException		Thrown on failure
	 */
    @Override
    public ConstantAction   makeConstantAction()
		throws StandardException
	{
		return	getGenericConstantActionFactory().getRenameConstantAction(getFullName(),
			getRelativeName(),
			oldObjectName,
			newObjectName,
			getSchemaDescriptor(),
			td.getUUID(),
			usedAlterTable,
			renamingWhat);
	}

	private StandardException descriptorExistsException(TupleDescriptor tuple,
														TupleDescriptor parent)
	{
		return
			StandardException.newException(SQLState.LANG_OBJECT_ALREADY_EXISTS_IN_OBJECT,
										   tuple.getDescriptorType(),
										   tuple.getDescriptorName(),
										   parent.getDescriptorType(),
										   parent.getDescriptorName());
	}

    @Override
    void acceptChildren(Visitor v) throws StandardException {
        super.acceptChildren(v);

        if (newTableName != null) {
            newTableName = (TableName) newTableName.accept(v);
        }
    }
}
