/*

   Derby - Class org.apache.derby.iapi.sql.dictionary.DDUtils

   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.iapi.sql.dictionary;

import java.util.Enumeration;
import java.util.Hashtable;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.i18n.MessageService;
import org.apache.derby.iapi.sql.StatementType;

/**
 *	Static Data dictionary utilities.
 *
 * @version 0.1
 */

public	class	DDUtils
{

	/*
	** For a foreign key, this is used to locate the referenced
	** key using the ConstraintInfo.  If it doesn't find the
	** correct constraint it will throw an error.
	*/
	public	static ReferencedKeyConstraintDescriptor locateReferencedConstraint
	(
		DataDictionary	dd,
		TableDescriptor	td,
		String			myConstraintName,	// for error messages
		String[]		myColumnNames,
		ConsInfo		otherConstraintInfo
	)
		throws StandardException
	{
		TableDescriptor refTd = otherConstraintInfo.getReferencedTableDescriptor(dd);
		if (refTd == null)
		{
				throw StandardException.newException(SQLState.LANG_INVALID_FK_NO_REF_TAB, 
												myConstraintName, 
												otherConstraintInfo.getReferencedTableName());
		}


        ReferencedKeyConstraintDescriptor refCd;

		String[]	refColumnNames = otherConstraintInfo.getReferencedColumnNames();

		if (refColumnNames == null ||
			refColumnNames.length == 0)
		{
			/*
			** There were no column names specified, just find
			** the primary key on the table in question
			*/
			refCd = refTd.getPrimaryKey();
			if (refCd == null)
			{
				throw StandardException.newException(SQLState.LANG_INVALID_FK_NO_PK, 
												myConstraintName, 
												refTd.getQualifiedName());
			}

			ColumnDescriptorList cdl = getColumnDescriptors(dd, td, myColumnNames);

			/*
			** Check the column list length to give a more informative
			** error in case they aren't the same.
			*/
			if (cdl.size() != refCd.getColumnDescriptors().size())
			{
				throw StandardException.newException(SQLState.LANG_INVALID_FK_DIFFERENT_COL_COUNT, 
												myConstraintName, String.valueOf(cdl.size()), 
												String.valueOf(refCd.getColumnDescriptors().size())); 
			}
	
			/*
			** Make sure all types are the same.
			*/	
			if (!refCd.areColumnsComparable(cdl))
			{
				throw StandardException.newException(SQLState.LANG_INVALID_FK_COL_TYPES_DO_NOT_MATCH, 
												myConstraintName);
			}

			return refCd;	
		} 
		else
		{
			/*
			** Check the referenced columns vs. each unique or primary key to
			** see if they match the foreign key.
			*/
			ConstraintDescriptor cd;

			ColumnDescriptorList colDl = getColumnDescriptors(dd, td, myColumnNames);
			ConstraintDescriptorList refCDL = dd.getConstraintDescriptors(refTd);

			int refCDLSize = refCDL.size();
			for (int index = 0; index < refCDLSize; index++)
			{
				cd = refCDL.elementAt(index);

				/*
				** Matches if it is not a check or fk, and
				** all the types line up.
				*/
				if ((cd instanceof ReferencedKeyConstraintDescriptor) &&
					 cd.areColumnsComparable(colDl) &&
					 columnNamesMatch(refColumnNames, 
										cd.getColumnDescriptors()))
				{
                    if (cd.deferrable()) {
                        final int onDelete = otherConstraintInfo.
                                getReferentialActionDeleteRule();

                        if (onDelete == StatementType.RA_CASCADE ||
                            onDelete == StatementType.RA_SETNULL) {
                            // DERBY-532: Not yet implemented
                            throw StandardException.newException(
                                    SQLState.LANG_INVALID_FK_REF_KEY,
                                    myConstraintName,
                                    refTd.getQualifiedName());
                        }
                    }
					return (ReferencedKeyConstraintDescriptor)cd;
				}
			}

			/*
			** If we got here, we didn't find anything
			*/
			throw StandardException.newException(SQLState.LANG_INVALID_FK_NO_REF_KEY, myConstraintName, 
														refTd.getQualifiedName());
		}
	}

    public	static ColumnDescriptorList getColumnDescriptors
	(
		DataDictionary	dd,
		TableDescriptor td,
		String[] 		columnNames
	)
		throws StandardException
	{
		ColumnDescriptorList cdl = new ColumnDescriptorList();
		for (int colCtr = 0; colCtr < columnNames.length; colCtr++)
		{
			ColumnDescriptor cd = td.getColumnDescriptor(columnNames[colCtr]);
			cdl.add(td.getUUID(), cd);
		}
		return cdl;
	}

	public	static boolean columnNamesMatch(String []columnNames, ColumnDescriptorList cdl)
		throws StandardException
	{
		if (columnNames.length != cdl.size())
		{
			return false;
		}
		
		for (int index = 0; index < columnNames.length; index++)
		{
            String name = cdl.elementAt(index).getColumnName();
			if (!name.equals(columnNames[index]))
			{
				return false;
			}
		}

		return true;
	}


	/*
	**checks whether the foreign key relation ships referential action
	**is violating the restrictions we have in the current system.
	**/
    @SuppressWarnings("UseOfObsoleteCollectionType")
	public static void validateReferentialActions
    (
		DataDictionary	dd,
		TableDescriptor	td,
		String			myConstraintName,	// for error messages
		ConsInfo		otherConstraintInfo,
		String[]        columnNames
	)
		throws StandardException
	{


		int refAction = otherConstraintInfo.getReferentialActionDeleteRule();

		//Do not allow ON DELETE SET NULL as a referential action 
		//if none of the foreign key columns are  nullable.
		if(refAction == StatementType.RA_SETNULL)
		{
			boolean foundNullableColumn = false;
			//check if we have a nullable foreign key column
			for (int colCtr = 0; colCtr < columnNames.length; colCtr++)
			{
				ColumnDescriptor cd = td.getColumnDescriptor(columnNames[colCtr]);
				if ((cd.getType().isNullable()))
				{
					foundNullableColumn = true;
					break;
				}
			}

			if(!foundNullableColumn)
			{
				throw StandardException.newException(SQLState.LANG_INVALID_FK_COL_FOR_SETNULL, 
													 myConstraintName);
			}
		}

		//check whether the foreign key relation ships referential action
		//is not violating the restrictions we have in the current system.
		TableDescriptor refTd = otherConstraintInfo.getReferencedTableDescriptor(dd);
		Hashtable<String,Integer> deleteConnHashtable = new Hashtable<String,Integer>();
		//find whether the foreign key is self referencing.
		boolean isSelfReferencingFk = (refTd.getUUID().equals(td.getUUID()));
		String refTableName = refTd.getSchemaName() + "." + refTd.getName();
		//look for the other foreign key constraints on this table first
		int currentSelfRefValue = getCurrentDeleteConnections(dd, td, -1, deleteConnHashtable, false, true);
		validateDeleteConnection(dd, td, refTd, 
								 refAction, 
                                 deleteConnHashtable,
                                 new Hashtable<String, Integer>(deleteConnHashtable),
								 true, myConstraintName, false , 
								 new StringBuffer(0), refTableName,
								 isSelfReferencingFk,
								 currentSelfRefValue);

		//if it not a selfreferencing key check for violation of exiting connections.
		if(!isSelfReferencingFk)
		{
			checkForAnyExistingDeleteConnectionViolations(dd, td,
														  refAction, 
														  deleteConnHashtable, 
														  myConstraintName);
		}	
	}

    /**
     * Finds the existing delete connection for the table and the referential
     * actions that will occur and stores the information in the hash table.
     * HashTable (key , value) = ( table name that this table is delete
     * connected to, referential action that will occur if there is a delete on
     * the table this table connected to [CASCADE, SET NULL, RESTRICT, NO
     * ACTION] )
     */
    @SuppressWarnings("UseOfObsoleteCollectionType")
	private	static int  getCurrentDeleteConnections
	(
	 DataDictionary	dd,
	 TableDescriptor	td,
	 int refActionType,
	 Hashtable<String,Integer> dch,
	 boolean prevNotCascade,
	 boolean findSelfRef
	 )
		throws StandardException
	{

		int selfRefValue = -1; //store the self reference referential action 

		//make sure we get any foreign key constraints added earlier in the same statement.
		td.emptyConstraintDescriptorList();
		ConstraintDescriptorList cdl = dd.getConstraintDescriptors(td);
		int cdlSize = cdl.size();

		boolean passedInPrevNotCascade = prevNotCascade;
		for (int index = 0; index < cdlSize; index++)
		{
				ConstraintDescriptor cd = cdl.elementAt(index);

				//look for  foreign keys
				if ((cd instanceof ForeignKeyConstraintDescriptor))
				{
					ForeignKeyConstraintDescriptor fkcd = (ForeignKeyConstraintDescriptor) cd;
					String constraintName = fkcd.getConstraintName();
					int raDeleteRule = fkcd.getRaDeleteRule();
					int raUpdateRule = fkcd.getRaUpdateRule();

					 if(findSelfRef && fkcd.isSelfReferencingFK())
					 {
						 //All self references will have same  referential actions type
						 selfRefValue = raDeleteRule;
						 findSelfRef = false;
					 }

					ReferencedKeyConstraintDescriptor refcd =
						fkcd.getReferencedConstraint(); 
					TableDescriptor refTd = refcd.getTableDescriptor();
					int childRefAction = refActionType == -1 ? raDeleteRule : refActionType;
				   
					String refTableName = refTd.getSchemaName() + "." + refTd.getName();
					//check with  the existing references.
                    Integer rAction = dch.get(refTableName);

					if(rAction != null) // we already looked at this table
					{
						prevNotCascade = passedInPrevNotCascade;
						continue;
					}

					//if we are not cascading, check whether the link before
					//this was cascade or not. If we travel through  two NON CASCADE ACTION
					//links then the  delete connection is broken(only a delete can have further
					// referential effects)
					if(raDeleteRule != StatementType.RA_CASCADE)
					{
						if(prevNotCascade)
						{
							prevNotCascade = passedInPrevNotCascade;
							continue;
						}
						else
							prevNotCascade = true;
					}

					//store the delete connection info in the hash table,
					//note that the referential action value is not what is
					//not specified on the current link. It is actually the 
					//value of what happens to the table whose delete
					// connections we are finding.
                    dch.put(refTableName, Integer.valueOf(childRefAction));
					
					//find the next delete conectiions on this path for non
					//self referencig delete connections.
					if(!fkcd.isSelfReferencingFK())
						getCurrentDeleteConnections(dd , refTd, childRefAction,
													dch, true, false);
					prevNotCascade = passedInPrevNotCascade;
				}
		}
		
		return selfRefValue;
	}


    /**
     * The following function validates whether the new foreign key relationship
     * violates any restriction on the referential actions. The current
     * refAction implementation does not allow cases where we can possible land
     * up having multiple action for the same row in a table. This could happen
     * because the user can possibly define different actions through multiple
     * foreign key paths.  The following function throws an error while creating
     * foreign keys if the new releationship leads to any such conditions.
     * NOTE: The SQL99 standard also does not clearly says what we are supposed
     * to do in these cases.  Our implementation just follows what is done in
     * DB2 and throws error messaged similar to DB2 (sql0632N, sql0633N,
     * sql0634N).
     */
    @SuppressWarnings("UseOfObsoleteCollectionType")
	private	static void validateDeleteConnection
	(
		DataDictionary	dd,
		TableDescriptor actualTd,  // the table we are adding the foriegn key.
		TableDescriptor	refTd,
		int refActionType,
		Hashtable<String,Integer> dch,
		Hashtable<String,Integer> ech,  //existing delete connections
		boolean checkImmediateRefTable,
		String myConstraintName,
		boolean prevNotCascade,
		StringBuffer cycleString, 
		String currentRefTableName, //the name of the table we are referring too.
		boolean isSelfReferencingFk,
		int currentSelfRefValue
		)
		throws StandardException
	{

		Integer rAction;

		/*
		** Validate the new referentail action value with respect to the 
		** already existing connections to this table we gathered  from
		** the getCurrentDeleteConnections() call.
		*/

		if(checkImmediateRefTable)
		{
		    String refTableName = refTd.getSchemaName() + "." + refTd.getName();
            rAction = dch.get(refTableName);
			
			// check possible invalide cases incase of self referencing foreign key
			if(isSelfReferencingFk)
			{
				//All the relation ship referring to a table should have the
				//same refaction except incase of SET NULL. In this case
				//it is the same table , so we have to check with existing self
				//referencing actions.
				if(currentSelfRefValue !=  -1)
				{
					if(currentSelfRefValue != refActionType)
					{
						//If there is a SET NULL relation ship we can not have any
						// other relation ship with it.
						if(currentSelfRefValue == StatementType.RA_SETNULL)
							throw
								generateError(SQLState.LANG_CANT_BE_DEPENDENT_ESELF, 
										  myConstraintName, currentRefTableName);
						else
						{
								/*
								** case where we can cleary say what the
								** referential actions should be. Like,	   
								** if there is NO ACTION relationsip
								**already, new relation ship also shold be NO ACTION. 
								*/
							throw
								generateError(SQLState.LANG_DELETE_RULE_MUSTBE_ESELF,
											  myConstraintName, currentSelfRefValue);
						}
					}else
					{
						//more than one  ON DELET SET NULL to the same table is not allowed
						if(currentSelfRefValue == StatementType.RA_SETNULL &&
						   refActionType == StatementType.RA_SETNULL)
						{
							throw
								generateError(SQLState.LANG_CANT_BE_DEPENDENT_ESELF,
											  myConstraintName, currentRefTableName);
						}
					}
				}

				/*
				** If the new releation ship is self referencing and if
				** the current existing relation ship to other tables is
				** CASCADE type them  new self reference should be of type
				** CASCADE, otherwise we should throw error.
				*/

                if( isSelfReferencingFk &&
                    dch.contains(Integer.valueOf(StatementType.RA_CASCADE)) &&
                    refActionType !=  StatementType.RA_CASCADE)
				{
					throw
						generateError(SQLState.LANG_DELETE_RULE_MUSTBE_ECASCADE,
									  myConstraintName,StatementType.RA_CASCADE);	
				}

				//end of possible error case scenarios for self reference key additions
				return;
			}
		
			//cases where the new  reference is referring to  another table

			//check whether it matched with existing self references.
			// If A self-referencing constraint exists with a delete rule of
			// SET NULL,  NO ACTION or RESTRICT. We can not add CASCADE
			// relationship with another table.
				
			if(currentSelfRefValue !=  -1)
			{
				if(refActionType == StatementType.RA_CASCADE && 
				   currentSelfRefValue != StatementType.RA_CASCADE) 
				{
					throw generateError(SQLState.LANG_DELETE_RULE_CANT_BE_CASCADE_ESELF,  myConstraintName);
					
				}

			}

			
			//check for the cases with existing relationships to the
			//referenced table
			if(rAction != null)
			{
				checkForMultiplePathInvalidCases(rAction.intValue(),
												  refActionType,
												  myConstraintName,currentRefTableName);
			}

			
			//mark the current connect to the reference table to identify the cycle.
			if(refActionType != StatementType.RA_CASCADE)
			{
				prevNotCascade = true;
			}
			
			/*
			** cycle string is used to keep track of the referential actions of 
			** the nodes we visited, this is required to make sure that in case
			** of cycles , all the nodes in the cycle have same type of
			** referential action.
			**/
			cycleString.append(refActionType);
		}


		boolean passedInPrevNotCascade = prevNotCascade;

		//delete connection is broken for if we see ON DELET SET NULL link
		// one level deeper than the table we are adding the foreing key
		//Where as to check for cycles we need to go for more level also;
        // To check cases like CASCADE CASCADE SET NULL cycle is not valid. 
		//Following variable is used make the distinction.
		boolean multiPathCheck = true;

		// check for cases where the new connection we are forming to the 
		// reference table could create invalid any cycles or mutiple paths 
		// with the delete-connections the  referencing table might have already.
		ConstraintDescriptorList refCDL = dd.getConstraintDescriptors(refTd);
		int refCDLSize = refCDL.size();
		for (int index = 0; index < refCDLSize; index++)
		{
			ConstraintDescriptor cd = refCDL.elementAt(index);

			if ((cd instanceof ForeignKeyConstraintDescriptor))
			{
				ForeignKeyConstraintDescriptor fkcd = (ForeignKeyConstraintDescriptor) cd;
				String constraintName = fkcd.getConstraintName();
				int raDeleteRule = fkcd.getRaDeleteRule();
				int raUpdateRule = fkcd.getRaUpdateRule();
				
				ReferencedKeyConstraintDescriptor refcd =
					fkcd.getReferencedConstraint(); 
				TableDescriptor nextRefTd = refcd.getTableDescriptor();

				//if we are not cascading, check  whether the link before
				//this was cascade or not. If we travel through  two NON CASCADE ACTION
				//links then the delete connection is broken(only a delete can have further
				//referential effects)
				if(raDeleteRule != StatementType.RA_CASCADE)
				{
					if(prevNotCascade)
					{
						prevNotCascade = passedInPrevNotCascade;
						continue;
					}
					else
					{
						prevNotCascade = true;
						multiPathCheck = false;
					}

				}

				//check whether the current link is a self referencing one
				boolean isSelfRefLink = fkcd.isSelfReferencingFK();
				
				//check for this is non self referencing cycles case
				//In cases of cycle, whole cycle should have the same refAction
				// value. Other wise we should throw an exception
				cycleString.append(raDeleteRule);
				boolean isFormingCycle = (nextRefTd.getUUID().equals(actualTd.getUUID()));
				if(isFormingCycle)
				{
					//make sure that all the nodes in the cycle have the same
					//referential action  value, otherwise we should throw an error. 
					for(int i = 0 ; i < cycleString.length(); i++)
					{
						int otherRefAction = Character.getNumericValue(cycleString.charAt(i));
						if(otherRefAction != refActionType)
						{
							//cases where one of the existing relation ships in
							//the cycle is not cascade , so we can not have
							// cascade relation ship.
							if(otherRefAction != StatementType.RA_CASCADE)
							{
								throw generateError(SQLState.LANG_DELETE_RULE_CANT_BE_CASCADE_ECYCLE, myConstraintName);
							}
							else
							{
								//possibly all the other nodes in the cycle has
								//cascade relationsship , we can not add a non
								//cascade relation ship.
								throw
									generateError(SQLState.LANG_CANT_BE_DEPENDENT_ECYCLE, 
												  myConstraintName, currentRefTableName);
							}
						}
					}
				}	


				

				String nextRefTableName =  nextRefTd.getSchemaName() + "." + nextRefTd.getName();
                rAction = ech.get(nextRefTableName);
				if(rAction != null)
				{
					/*
					** If the table name has entry in the hash table means, there
					** is already  a path to this table exists from the table
					** the new foreign key relation ship is being formed.
					** Note: refValue in the hash table is how the table we are
					** adding the new relationsship is going to affected not
					** current path refvalue.
					**/
					if(!isSelfRefLink && multiPathCheck)
						checkForMultiplePathInvalidCases(rAction.intValue(),
														 refActionType, 
														 myConstraintName,currentRefTableName);

				}else
				{
                    rAction = dch.get(nextRefTableName);
					if(rAction == null)
					{
                        if (multiPathCheck) {
                            dch.put(nextRefTableName,
                                    Integer.valueOf(refActionType));
                        }

						if(!isSelfRefLink)
						{
							validateDeleteConnection(dd, actualTd,  nextRefTd,
												 refActionType, dch, ech, false,
												 myConstraintName,prevNotCascade,
												 cycleString, currentRefTableName, 
												 isSelfReferencingFk, currentSelfRefValue); 
						}
					}
				}
				prevNotCascade = passedInPrevNotCascade;
				//removes the char added for the current call
				cycleString.setLength(cycleString.length() -1);
				
			}
		}
	}


	/*
	**Check whether the mulitple path case is valid or not following
	** cases are invalid:
	** case 1: The relationship causes the table to be delete-connected to
	** the indicated table through multiple relationships and the
	** delete rule of the existing relationship is SET NULL. 
	** case 2: The relationship would cause the table to be
	** delete-connected to the same table through multiple
	** relationships and such relationships must have the same 
	** delete rule (NO ACTION, RESTRICT or CASCADE). 
	** case 3: The relationship would cause another table to be
	** delete-connected to the same table through multiple paths
	** with different delete rules or with delete rule equal to SET NULL. 
	**/

	private static void checkForMultiplePathInvalidCases(int currentRefAction,
														  int refActionType,
														  String myConstraintName,
														  String currentRefTableName)
		throws StandardException
	{

		//All the relation ship referring to a table should have the
		//same refaction except incase of SET NULL
		if(currentRefAction != refActionType)
		{

			//If there is a SET NULL relation ship we can not have any
			// other relation ship with it.
			if(currentRefAction == StatementType.RA_SETNULL)
				throw generateError(SQLState.LANG_CANT_BE_DEPENDENT_MPATH,
									myConstraintName, currentRefTableName);
			else
				//This error say what the delete rule must be for the
				// foreign key be valid 
				throw generateError(SQLState.LANG_DELETE_RULE_MUSTBE_MPATH,
									myConstraintName, currentRefAction);

		}else
		{
			//more than one  ON DELET SET NULL to the same table is not allowed
			if(currentRefAction == StatementType.RA_SETNULL &&
			   refActionType == StatementType.RA_SETNULL)
			{
				throw		
					generateError(SQLState.LANG_CANT_BE_DEPENDENT_MPATH,
								  myConstraintName, currentRefTableName);
			}
		}
	}



    /*
	** Check whether the delete rule of FOREIGN KEY  must not be CASCADE because
	** the  new relationship would cause another table to be delete-connected to
	** the same table through multiple paths with different delete rules or with 
	** delete rule equal to SET NULL. 
	**
	** For example :
	**                      t1
    **  		 CASCADE   /  \  CASCADE
    **                    /    \ 
	**                  t2      t3
    **                   \      /   
    **          SET NULL  \    /  CASCADE (Can we add this one ? NO)
 	**			          \   /
	                       \t4/
	**					
    **   existing links:
	**   t2 references t1   ON DELETE CASCADE  (fkey1)
	**   t3 references t1   ON DELETE CASCADE  (fkey2)
	**   t2 reference  t4   ON DELETE SET NULL (fkey3)
	**   Now if if try to add a new link i.e
	**   t4 references t3   ON DELETE SET NULL  (fkey4)
	**   Say if we add it,  then if we execute 'delete from t1' 
	**   Because of referential actions , we will try to delete a row through
	**   one path and tries to update  through another path. 
	**   Nothing in standard that say whether we are suppose to delete the row
	**   or update the row.  DB2UDB raises error when we try to create the
	**   foreign key fkey4, Derby also does the same.
	** 
	**   How we catch the error case ?
	**   Point to note here is the table(t4) we are  adding the foreign key does
	**   not have a problem in this scenarion because we are adding a
    **   a CASACDE link , some other table(t2) that is referring  
	**   can get multiple referential action paths. We can not
	**   this error case for self referencing links.
	**   Algorithm:
	**   -Gather the foreign keys that are
	**   referring(ReferencedKeyConstraintDescriptor) to the table we are adding
	**   foreign key, in our example case we get (fkey3 - table t2 -t4 link)
	**   for each ReferencedKeyConstraintDescriptor
	**   {
	**    1)find the delete connections of the referring table.
	**    [getCurrentDeleteConnections() will return this hash table]
	**	  2) we already have collected the Delete connections 
    **       in validDeleteConnections() for the actual table we are adding the 
    **       foreign key.
	**    3) Now check whether the referring table  is also 
    **       referring  any table that the table we are adding
    **       foreign key has delete connection.
	**
	**     for each table referring table delete connection hash table
	**     {
	**      if it is there in the actual table delete connection hash table
    **      {
	**         //In our example case we find t1 in both the hash tables.
	**         make sure we are having valid referential action
    **         from the existing path and the new path we got from 
	**         new foreign key relation ship.
	**        //In our example case t2 has CASCADE relations with t1
	**        //Because of new foreign key added we also get 
	**        //SET NULL relation ship with t1. This is not valid
	**        //so we should throw error.
    **      }  
	**     }
	** }	
	**/


    @SuppressWarnings("UseOfObsoleteCollectionType")
    private static void checkForAnyExistingDeleteConnectionViolations
	(
	 DataDictionary	dd,
	 TableDescriptor td,
	 int refActionType,
	 Hashtable<String,Integer> newDconnHashTable,
	 String myConstraintName
	 )
	throws StandardException
	{

		//We need to check for the condition in this function only when we are
		//adding ref action of type CASCADE
		if(refActionType != StatementType.RA_CASCADE)
			return;
		
		//find the tables that are referring to the table we 
		//are adding the foreign key and check whether we violate their existing rules.
		String addTableName = td.getSchemaName() + "." + td.getName();;

        for (ConstraintDescriptor cd : dd.getConstraintDescriptors(td))
        {
			if ((cd instanceof ReferencedKeyConstraintDescriptor))
			{
				ConstraintDescriptorList fkcdl = dd.getActiveConstraintDescriptors
					( ((ReferencedKeyConstraintDescriptor)cd).getForeignKeyConstraints(ConstraintDescriptor.ALL));
	
				int size = fkcdl.size();
				if (size == 0) 
				{ 
					continue; 
				}
				
				//Note: More than one table can refer to the same
				//ReferencedKeyConstraintDescriptor, so we need to find all the tables.
				Hashtable<String,Integer> dConnHashtable = new Hashtable<String,Integer>();
				for (int inner = 0; inner < size; inner++)
				{
					ForeignKeyConstraintDescriptor fkcd = (ForeignKeyConstraintDescriptor) fkcdl.elementAt(inner);
					TableDescriptor fktd = fkcd.getTableDescriptor();
					//Delete rule that we have to the table we are adding the
					// foreign key relation shop
					int raDeleteRuleToAddTable = fkcd.getRaDeleteRule();

					//This check should not be done on self referencing references.
					if(!fkcd.isSelfReferencingFK())
					{

						//gather the delete connections of the table that is
						//referring to the table we are adding foreign key relation ship

						getCurrentDeleteConnections(dd, fktd, -1, dConnHashtable, false, true);

						/*
						**Find out if we introduced more than one delete connection
						**paths to the table that are referring the table we adding
						**the foreign key relatiosn ship.
						**If we have multiple paths they should have the same type
						**referential action and only one SET NULL path.
						**/

						for (Enumeration<String> e = dConnHashtable.keys() ; e.hasMoreElements() ;) 
						{
							String tName = e.nextElement();
							//we should not check for the table name to which  we are
							//adding the foreign key relation ship.
							if(!tName.equals(addTableName))
							{
								if(newDconnHashTable.containsKey(tName))
								{
									int currentDeleteRule = (dConnHashtable.get(tName)).intValue();
									if((currentDeleteRule == StatementType.RA_SETNULL
										&& raDeleteRuleToAddTable == StatementType.RA_SETNULL) ||
									   currentDeleteRule  != raDeleteRuleToAddTable)
									{
										throw
											generateError(SQLState.LANG_DELETE_RULE_CANT_BE_CASCADE_MPATH, 
														  myConstraintName);
									}
								}
							}
						}
					}
					//same hash table can be used for the other referring tables
					//so clear the hash table.
					dConnHashtable.clear();
				}
			}
		}
	}


	
	private static StandardException generateError(String messageId, 
												   String myConstraintName)
	{
		String message = MessageService.getTextMessage(messageId);
		return StandardException.newException(SQLState.LANG_DELETE_RULE_VIOLATION, 
												myConstraintName, message);
	}	

	private static StandardException generateError(String messageId, 
												   String myConstraintName, 
												   int raRule)
	{
		String raRuleStringId;
		switch (raRule){
		case StatementType.RA_CASCADE:
			raRuleStringId = "CASCADE";
			break;
		case StatementType.RA_RESTRICT:
			raRuleStringId = "RESTRICT";
				break;
		case StatementType.RA_SETNULL:
			raRuleStringId = "SET NULL";
			break;
		case StatementType.RA_SETDEFAULT:
			raRuleStringId = "SET DEFAULT";
			break;
		case StatementType.RA_NOACTION:
		default: 
			raRuleStringId = "NO ACTION";
			break;
		}

		String raRuleMessageString = MessageService.getTextMessage(raRuleStringId); 
		String message = MessageService.getTextMessage(messageId, raRuleMessageString);
		return StandardException.newException(SQLState.LANG_DELETE_RULE_VIOLATION, 
												myConstraintName, message);
	}	

	private static StandardException generateError(String messageId, 
												   String myConstraintName,
												   String refTableName)
	{

		String message = MessageService.getTextMessage(messageId, refTableName);
		return StandardException.newException(SQLState.LANG_DELETE_RULE_VIOLATION, 
												myConstraintName, message);
	}	

}















