/*

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

   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 java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Properties;
import org.apache.derby.catalog.UUID;
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.iapi.services.monitor.ModuleFactory;
import org.apache.derby.iapi.services.monitor.Monitor;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.services.uuid.UUIDFactory;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.sql.depend.ProviderList;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;

/**
 * A ConstraintDefintionNode is a class for all nodes that can represent
 * constraint definitions.
 *
 */

public class ConstraintDefinitionNode extends TableElementNode
{
	
	private TableName constraintName;
	protected int constraintType;
	protected Properties properties;
	ProviderList apl;

	UUIDFactory		uuidFactory;

	String			backingIndexName;
	UUID			backingIndexUUID;
	ResultColumnList columnList;
	String			 constraintText;
	ValueNode		 checkCondition;
	private int				 behavior;
    private int verifyType;

    public final static boolean DEFERRABLE_DEFAULT = false;
    public final static boolean INITIALLY_DEFERRED_DEFAULT = false;
    public final static boolean ENFORCED_DEFAULT = true;

    /**
     * boolean[3]: {deferrable?, initiallyDeferred?, enforced?}
     */
    private boolean[] characteristics;

    ConstraintDefinitionNode(
                    TableName constraintName,
                    int constraintType,
                    ResultColumnList rcl,
                    Properties properties,
                    ValueNode checkCondition,
                    String constraintText,
                    int behavior,
                    int verifyType,
                    ContextManager cm)
	{
		/* We need to pass null as name to TableElementNode's constructor 
		 * since constraintName may be null.
		 */
        super(null, cm);

        this.constraintName = constraintName;

		if (this.constraintName != null)
		{
			this.name = this.constraintName.getTableName();
		}
        this.constraintType = constraintType;
        this.properties = properties;
        this.columnList = rcl;
        this.checkCondition = checkCondition;
        this.constraintText = constraintText;
        this.behavior = behavior;
        this.verifyType = verifyType;
	}


    void setCharacteristics(boolean[] cc) {
        characteristics = cc.clone();
    }

    boolean[] getCharacteristics() {
        if (characteristics == null) {
            characteristics = new boolean[]{
                ConstraintDefinitionNode.DEFERRABLE_DEFAULT,
                ConstraintDefinitionNode.INITIALLY_DEFERRED_DEFAULT,
                ConstraintDefinitionNode.ENFORCED_DEFAULT
            };
        }

        return characteristics.clone();
    }

	/**
	 * 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 "constraintName: " + 
				( ( constraintName != null) ?
						constraintName.toString() : "null" ) + "\n" +
				"constraintType: " + constraintType + "\n" + 
				"properties: " +
				((properties != null) ? properties.toString() : "null") + "\n" +
				super.toString();
		}
		else
		{
			return "";
		}
	}

	/**
	 * Bind this constraint definition. 
	 *
	   @param ddlNode the create or alter table node
	 * @param dd the dd
	 *
	 * @exception StandardException on error
	 */
    void bind(DDLStatementNode ddlNode, DataDictionary dd) throws StandardException
	{
		// we need to allow drops on constraints with different schemas
		// to support removing constraints created pre 5.2.
		if (constraintType == DataDictionary.DROP_CONSTRAINT)
			return;

		// ensure the schema of the constraint matches the schema of the table
		if (constraintName != null) {

			String constraintSchema = constraintName.getSchemaName();


			if (constraintSchema != null) {



				TableName tableName = ddlNode.getObjectName();
				String tableSchema = tableName.getSchemaName();
				if (tableSchema == null) {
					tableSchema = getSchemaDescriptor((String) null).getSchemaName();
					tableName.setSchemaName(tableSchema);
				}
				if (!constraintSchema.equals(tableSchema)) {
					throw StandardException.newException(SQLState.LANG_CONSTRAINT_SCHEMA_MISMATCH,
												constraintName, tableName);

				}
			}
		}
		else {
			name = getBackingIndexName(dd);
		}
	}

	/**
	  *	Get the name of the constraint. If the user didn't provide one, we make one up. This allows Replication
	  *	to agree with the core compiler on the names of constraints.
	  *
	  *	@return	constraint name
	  */
	String	getConstraintMoniker()
	{
		return name;
	}

	/**
        To support dropping existing constraints that may have mismatched schema names
		we need to support ALTER TABLE S1.T DROP CONSTRAINT S2.C.
		If a constraint name was specified this returns it, otherwise it returns null.
	*/
	String getDropSchemaName() {
		if (constraintName != null)
			return constraintName.getSchemaName();
		return null;
	}

	/**
	  *	Allocates a UUID if one doesn't already exist for the index backing this constraint. This allows Replication
	  *	logic to agree with the core compiler on what the UUIDs of indices are.
	  *
	  *	@return	a UUID for the constraint. allocates one if this is the first time this method is called.
	  */
	UUID	getBackingIndexUUID()
	{
		if ( backingIndexUUID == null )
		{
			backingIndexUUID = getUUIDFactory().createUUID();
		}

		return	backingIndexUUID;
	}

	/**
	  *	Gets a unique name for the backing index for this constraint of the form SQLyymmddhhmmssxxn
	  *	  yy - year, mm - month, dd - day of month, hh - hour, mm - minute, ss - second,
	  *	  xx - the first 2 digits of millisec because we don't have enough space to keep the exact millisec value,
	  *	  n - number between 0-9
	  *
	  *	@return	name of backing index
	  */
	String	getBackingIndexName(DataDictionary dd)
	{
		if ( backingIndexName == null )
			backingIndexName = dd.getSystemSQLName();

		return	backingIndexName;
	}

	/**
	 * Set the auxiliary provider list.
	 *
	 * @param apl	The new auxiliary provider list.
	 */
	void setAuxiliaryProviderList(ProviderList apl)
	{
		this.apl = apl;
	}

	/**
	 * Return the auxiliary provider list.
	 *
	 * @return	The auxiliary provider list.
	 */
    ProviderList getAuxiliaryProviderList()
	{
		return apl;
	}

	/**
	 * Is this a primary key constraint.
	 *
	 * @return boolean	Whether or not this is a primary key constraint
	 */
    @Override
	boolean hasPrimaryKeyConstraint()
	{
		return constraintType == DataDictionary.PRIMARYKEY_CONSTRAINT;
	}

	/**
	 * Is this a unique key constraint.
	 *
	 * @return boolean	Whether or not this is a unique key constraint
	 */
    @Override
	boolean hasUniqueKeyConstraint()
	{
		return constraintType == DataDictionary.UNIQUE_CONSTRAINT;
	}

	/**
	 * Is this a foreign key constraint.
	 *
	 * @return boolean	Whether or not this is a unique key constraint
	 */
    @Override
	boolean hasForeignKeyConstraint()
	{
		return constraintType == DataDictionary.FOREIGNKEY_CONSTRAINT;
	}

	/**
	 * Does this element have a check constraint.
	 *
	 * @return boolean	Whether or not this element has a check constraint
	 */
    @Override
	boolean hasCheckConstraint()
	{
		return constraintType == DataDictionary.CHECK_CONSTRAINT;
	}

	/**
	 * Does this element have a constraint on it.
	 *
	 * @return boolean	Whether or not this element has a constraint on it
	 */
    @Override
	boolean hasConstraint()
	{
		return true;
	}

	/**
     * Does this constraint require a backing index for its implementation?
	 *
     * @return boolean  {@code true} if this constraint requires a backing
     *                  index, i.e. if is a foreign key, primary key or
     *                  unique key constraint
	 */
	boolean requiresBackingIndex()
	{
		switch (constraintType)
		{
			case DataDictionary.FOREIGNKEY_CONSTRAINT:
			case DataDictionary.PRIMARYKEY_CONSTRAINT:
			case DataDictionary.UNIQUE_CONSTRAINT:
				return true;
			default:
				return false;
		}
	}	

	/**
     * Is this a primary key or unique constraint?
	 *
     * @return boolean  {@code true} if this is a primary key or
     *                  unique key constraint
	 */
	boolean requiresUniqueIndex()
	{
		switch (constraintType)
		{
			case DataDictionary.PRIMARYKEY_CONSTRAINT:
			case DataDictionary.UNIQUE_CONSTRAINT:
				return true;
			default:
				return false;
		}
	}

	/**
	 * Get the constraint type
	 *
	 * @return constraintType	The constraint type.
	 */
	int getConstraintType()
	{
		return constraintType;
	}

	/**
	 * Set the optional properties for the backing index to this constraint.
	 *
	 * @param properties	The optional Properties for this constraint.
	 */
    void setProperties(Properties properties)
	{
		this.properties = properties;
	}

	/** 
	 * Get the optional properties for the backing index to this constraint.
	 *
	 *
	 * @return The optional properties for the backing index to this constraint
	 */
    Properties getProperties()
	{
		return properties;
	}


	/** 
	 * Is this constraint referenced.
	 *
	 * @return true/false
	 */
    boolean isReferenced()
	{
		return false;
	}

	/** 
	 * Get the count of enabled fks
	 * that reference this constraint
	 *
	 * @return the number
	 */
    int getReferenceCount()
	{
		return 0;
	}
	/** 
	 * Is this constraint enabled.
	 *
	 * @return true/false
	 */
    boolean isEnabled()
	{
		return true;
	}

	/**
	 * Get the column list from this node.
	 *
	 * @return ResultColumnList The column list from this table constraint.
	 */
    ResultColumnList getColumnList()
	{
		return columnList;
	}

	/**
	 * Set the column list for this node.  This is useful for check constraints
	 * where the list of referenced columns is built at bind time.
	 *
	 * @param columnList	The new columnList.
	 */
    void setColumnList(ResultColumnList columnList)
	{
		this.columnList = columnList;
	}

	/**
	 * Get the check condition from this table constraint.
	 *
	 * @return The check condition from this node.
	 */
    ValueNode getCheckCondition()
	{
		return checkCondition;
	}

	/**
	 * Set the check condition for this table constraint.
	 *
	 * @param checkCondition	The check condition
	 */
    void setCheckCondition(ValueNode checkCondition)
	{
		this.checkCondition = checkCondition;
	}

	/**
	 * Get the text of the constraint. (Only meaningful for check constraints.)
	 *
	 * @return The constraint text.
	 */
    String getConstraintText()
	{
		return constraintText;
	}

	/**
     * Return the behavior of this constraint.
     * See {@link org.apache.derby.iapi.sql.StatementType#DROP_CASCADE} etc.
	 *
	 * @return the behavior
	 */
	int getDropBehavior()
	{
		return behavior;
	}

    /**
     * @return the expected type of the constraint, DataDictionary.DROP_CONSTRAINT if the constraint is
     *         to be dropped without checking its type.
     */
    int getVerifyType()
    {
        return verifyType;
    }

	///////////////////////////////////////////////////////////////////////////
	//
	//	MINIONS
	//
	///////////////////////////////////////////////////////////////////////////
	/**
	  *	Get the UUID factory
	  *
	  *	@return	the UUID factory
	  *
	  */
	private	UUIDFactory	getUUIDFactory()
	{
		if ( uuidFactory == null )
		{
			uuidFactory = getMonitor().getUUIDFactory();
		}
		return	uuidFactory;
	}

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

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

    /**
     * Qualify all SQL object names in a CHECK constraint with schema name.
     * @throws StandardException if an error occurs
     */
    void qualifyNames() throws StandardException {
        // Get all references to SQL object names in the CHECK constraint,
        // ordered as they appear in the constraint definition.
        OffsetOrderVisitor<TableName> visitor =
                new OffsetOrderVisitor<TableName>(TableName.class,
                        checkCondition.getBeginOffset(),
                        checkCondition.getEndOffset() + 1);
        checkCondition.accept(visitor);

        StringBuilder sb = new StringBuilder();
        int pos = 0;
        int offset = checkCondition.getBeginOffset();

        // Replace all names with fully qualified names.
        for (TableName tableName : visitor.getNodes()) {
            sb.append(constraintText, pos, tableName.getBeginOffset() - offset);
            sb.append(tableName.getFullSQLName());
            pos = tableName.getEndOffset() + 1 - offset;
        }

        sb.append(constraintText, pos, constraintText.length());

        constraintText = sb.toString();
    }
    
    /**
     * Privileged Monitor lookup. Must be private so that user code
     * can't call this entry point.
     */
    private  static  ModuleFactory  getMonitor()
    {
        return AccessController.doPrivileged
            (
             new PrivilegedAction<ModuleFactory>()
             {
                 public ModuleFactory run()
                 {
                     return Monitor.getMonitor();
                 }
             }
             );
    }

}
