/*

   Derby - Class org.apache.derby.impl.sql.catalog.TabInfoImpl

   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.catalog;

import org.apache.derby.iapi.services.io.FormatableBitSet;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.services.io.StreamStorable;
import org.apache.derby.shared.common.error.StandardException;
import org.apache.derby.iapi.sql.dictionary.CatalogRowFactory;
import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;
import org.apache.derby.iapi.sql.execute.ExecIndexRow;
import org.apache.derby.iapi.sql.execute.ExecRow;
import org.apache.derby.iapi.sql.execute.RowChanger;
import org.apache.derby.iapi.sql.execute.TupleFilter;
import org.apache.derby.iapi.sql.Activation;

import org.apache.derby.iapi.store.access.ConglomerateController;
import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
import org.apache.derby.iapi.store.access.Qualifier;
import org.apache.derby.iapi.store.access.ScanController;
import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
import org.apache.derby.iapi.store.access.TransactionController;

import org.apache.derby.iapi.types.DataValueDescriptor;

import org.apache.derby.iapi.types.RowLocation;
import java.util.Properties;

/**
* A poor mans structure used in DataDictionaryImpl.java.
* Used to save heapId, name pairs for non core tables.
*
*/
class TabInfoImpl
{
    /**
     * ROWNOTDUPLICATE is out of range for a row
     * number.  If a return code does not equal
     * this value, then it refers to the row
     * that is a duplicate.
     */
    static  final   int     ROWNOTDUPLICATE = -1;

	private IndexInfoImpl[]				indexes;
	private long						heapConglomerate;
	private int							numIndexesSet;
	private boolean						heapSet;
	private final CatalogRowFactory			crf;

	/**
	 * Constructor
	 *
	 * @param crf				the associated CatalogRowFactory
	 */
	TabInfoImpl(CatalogRowFactory crf)
	{
		this.heapConglomerate = -1;
		this.crf = crf;

		int numIndexes = crf.getNumIndexes();

		if (numIndexes > 0)
		{
			indexes = new IndexInfoImpl[numIndexes];

			/* Init indexes */
			for (int indexCtr = 0; indexCtr < numIndexes; indexCtr++)
			{
				indexes[indexCtr] = new IndexInfoImpl(
											indexCtr,
											crf);
			}
		}
	}

    /**
     * Get the conglomerate for the heap.
     *
     * @return long     The conglomerate for the heap.
     */
	long getHeapConglomerate()
	{
		return heapConglomerate;
	}

    /**
     * Set the heap conglomerate for this.
     *
     * @param heapConglomerate  The new heap conglomerate.
     */
	void setHeapConglomerate(long heapConglomerate)
	{
		this.heapConglomerate = heapConglomerate;
		heapSet = true;
	}

    /**
     * Get the conglomerate for the specified index.
     *
     * @return long     The conglomerate for the specified index.
     */
	long getIndexConglomerate(int indexID)
	{
		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(indexes != null,
				"indexes is expected to be non-null");
            if (indexID >= indexes.length)
            {
                SanityManager.THROWASSERT(
                    "indexID (" + indexID + ") is out of range(0-" +
                    indexes.length + ")");
            }
		}

		return indexes[indexID].getConglomerateNumber();
	}

    /**
     * Set the index conglomerate for the table.
     *
     * @param index             Index number for index for table
     * @param indexConglomerate The conglomerate for that index
     */
	void setIndexConglomerate(int index, long indexConglomerate)
	{
		/* Index names must be set before conglomerates.
		 * Also verify that we are not setting the same conglomerate
		 * twice.
		 */
		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(indexes[index] != null,
				"indexes[index] expected to be non-null");
			SanityManager.ASSERT(indexes[index].getConglomerateNumber() == -1,
				"indexes[index] expected to be -1");
		}
		indexes[index].setConglomerateNumber(indexConglomerate);

		/* We are completely initialized when all indexes have 
		 * their conglomerates initialized 
		 */
		numIndexesSet++;
	}

    /**
     * Set the index conglomerate for the table.
     *
     * @param cd    The ConglomerateDescriptor for one of the index
     *              for this table.
     */
    void setIndexConglomerate(ConglomerateDescriptor cd)
	{
		int		index;
		String	indexName = cd.getConglomerateName();

		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(indexes != null,
				"indexes is expected to be non-null");
		}

		for (index = 0; index < indexes.length; index++)
		{
			/* All index names expected to be set before
			 * any conglomerate is set.
			 */
			if (SanityManager.DEBUG)
			{
				SanityManager.ASSERT(indexes[index] != null,
					"indexes[index] expected to be non-null");
				SanityManager.ASSERT(indexes[index].getIndexName() != null,
					"indexes[index].getIndexName() expected to be non-null");
			}

			/* Do we have a match? */
			if (indexes[index].getIndexName().equals(indexName))
			{
				indexes[index].setConglomerateNumber(cd.getConglomerateNumber());
				break;
			}
		}

		if (SanityManager.DEBUG)
		{
			if (index == indexes.length)
			{
				SanityManager.THROWASSERT("match not found for " + indexName);
			}
		}

		/* We are completely initialized when all indexIds are initialized */
		numIndexesSet++;
	}

    /**
     * Get the table name.
     *
     * @return String   The table name.
     */
	String getTableName()
	{
		return crf.getCatalogName();
	}

    /**
     * Get the index name.
     *
     * @param indexId   Index number for index for table
     *
     * @return String   The index name.
     */
	String getIndexName(int indexId)
	{
		return indexes[indexId].getIndexName();
	}

    /** 
     * Get the CatalogRowFactory for this.
     *
     * @return CatalogRowFactory    The CatalogRowFactory for this.
     */
	CatalogRowFactory getCatalogRowFactory()
	{
		return crf;
	}

    /**
     * Is this fully initialized.  
     * (i.e., is all conglomerate info initialized)
     *
     * @return boolean  Whether or not this is fully initialized.
     */
	boolean isComplete()
	{
		/* We are complete when heap conglomerate and all
		 * index conglomerates are set.
		 */
		if (! heapSet)
		{
			return false;
		}
		return (indexes == null ||	indexes.length == numIndexesSet);
	}

    /**
     * Get the column count for the specified index number.
     *
     * @param indexNumber   The index number.
     *
     * @return int          The column count for the specified index.
     */
	int getIndexColumnCount(int indexNumber)
	{
		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(indexes != null,
				"indexes is expected to be non-null");

			if (!(indexNumber < indexes.length))
			{
				SanityManager.THROWASSERT("indexNumber (" + indexNumber + ") is out of range(0-" +
				indexes.length + ")");
			}
		}

		return indexes[indexNumber].getColumnCount();
	}

    /**
     * Get the IndexRowGenerator for the specified index number.
     *
     * @param indexNumber   The index number.
     *
     * @return IndexRowGenerator    The IRG for the specified index number.
     */
	IndexRowGenerator getIndexRowGenerator(int indexNumber)
	{
		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(indexes != null,
				"indexes is expected to be non-null");
            if (indexNumber >= indexes.length)
            {
                SanityManager.THROWASSERT(
                    "indexNumber (" + indexNumber + ") is out of range(0-" +
                    indexes.length + ")");
            }
		}
		return indexes[indexNumber].getIndexRowGenerator();
	}

    /**
     * Set the IndexRowGenerator for the specified index number.
     *
     * @param indexNumber   The index number.
     * @param irg           The IndexRowGenerator for the specified index number.
     */
	void setIndexRowGenerator(int indexNumber, IndexRowGenerator irg)
	{
		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(indexes != null,
				"indexes is expected to be non-null");
            if (indexNumber >= indexes.length)
            {
                SanityManager.THROWASSERT(
                    "indexNumber (" + indexNumber + ") is out of range(0-" +
                    indexes.length + ")");
            }
		}

		indexes[indexNumber].setIndexRowGenerator(irg);
	}

    /** 
     * Get the number of indexes on this catalog.
     *
     * @return int  The number of indexes on this catalog.
     */
	int getNumberOfIndexes()
	{
		if (indexes == null)
		{
			return 0;
		}
		else
		{
			return indexes.length;
		}
	}

    /**
     * Get the base column position for a column within a catalog
     * given the (0-based) index number for this catalog and the
     * (0-based) column number for the column within the index.
     *
     * @param indexNumber   The index number
     * @param colNumber     The column number within the index
     *
     * @return int      The base column position for the column.
     */
	int getBaseColumnPosition(int indexNumber, int colNumber)
	{
		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(indexes != null,
				"indexes is expected to be non-null");
			if (indexNumber >= indexes.length)
			{
				SanityManager.THROWASSERT("indexNumber (" + indexNumber + ") is out of range(0-" +
					indexes.length + ")");
			}
		}

		return indexes[indexNumber].getBaseColumnPosition(colNumber);
	}

    /**
     * Return whether or not this index is declared unique
     *
     * @param indexNumber   The index number
     *
     * @return boolean      Whether or not this index is declared unique
     */
	boolean isIndexUnique(int indexNumber)
	{
		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT(indexes != null,
				"indexes is expected to be non-null");

			if (indexNumber >= indexes.length)
			{
				SanityManager.THROWASSERT("indexNumber (" + indexNumber + ") is out of range(0-" +
					indexes.length + ")");
			}
		}

		return indexes[indexNumber].isIndexUnique();
	}

	/**
	 * Inserts a base row into a catalog and inserts all the corresponding
	 * index rows.
	 *
	 *	@param	row			row to insert
	 *	@param	tc			transaction
	 *	@return	row number (&gt;= 0) if duplicate row inserted into an index
	 *			ROWNOTDUPLICATE otherwise
	 *
	 * @exception StandardException		Thrown on failure
	 */
	int insertRow( ExecRow row, TransactionController tc)
		throws StandardException
	{

		RowLocation[] 			notUsed = new RowLocation[1]; 

		return insertRowListImpl(new ExecRow[] {row},tc,notUsed);
	}

	/**
	 * Inserts a list of base rows into a catalog and inserts all the corresponding
	 * index rows.
	 *
	 *	@param	rowList		List of rows to insert
	 *	@param	tc			transaction controller
	 *
	 *
	 *	@return	row  number (&gt;= 0) if duplicate row inserted into an index
	 *			ROWNOTDUPLICATE otherwise
	 *
	 * @exception StandardException		Thrown on failure
	 */
	int insertRowList(ExecRow[] rowList, TransactionController tc )
		throws StandardException
	{
		RowLocation[] 			notUsed = new RowLocation[1]; 

		return insertRowListImpl(rowList,tc,notUsed);
	}

	/**
	  Insert logic to insert a list of rows into a table. This logic has two
	  odd features.

	  <OL>
	  <LI>Returns an indication if any returned row was a duplicate.
	  <LI>Returns the RowLocation of the last row inserted.
	  </OL>
	  @param rowList the list of rows to insert
	  @param tc	transaction controller
	  @param rowLocationOut on output rowLocationOut[0] is set to the
	         last RowLocation inserted.
	  @return row number (&gt;= 0) if duplicate row inserted into an index
	  			ROWNOTDUPLICATE otherwise
	 */
	private int insertRowListImpl(ExecRow[] rowList, TransactionController tc,
                                  RowLocation[] rowLocationOut)
		throws StandardException
	{
		ConglomerateController		heapController;
		RowLocation					heapLocation;
		ExecIndexRow				indexableRow;
		int							insertRetCode;
		int							retCode = ROWNOTDUPLICATE;
		int							indexCount = crf.getNumIndexes();
		ConglomerateController[]	indexControllers = new ConglomerateController[ indexCount ];

		// Open the conglomerates
		heapController = 
            tc.openConglomerate(
                getHeapConglomerate(), 
                false,
				TransactionController.OPENMODE_FORUPDATE,
                TransactionController.MODE_RECORD,
                TransactionController.ISOLATION_REPEATABLE_READ);
		
		/* NOTE: Due to the lovely problem of trying to add
		 * a new column to syscolumns and an index on that
		 * column during upgrade, we have to deal with the
		 * issue of the index not existing yet.  So, it's okay
		 * if the index doesn't exist yet.  (It will magically
		 * get created at a later point during upgrade.)
		 */

		for ( int ictr = 0; ictr < indexCount; ictr++ )
		{
			long conglomNumber = getIndexConglomerate(ictr);
			if (conglomNumber > -1)
			{
				indexControllers[ ictr ] = 
		            tc.openConglomerate( 
			            conglomNumber, 
                        false,
						TransactionController.OPENMODE_FORUPDATE,
					    TransactionController.MODE_RECORD,
						TransactionController.ISOLATION_REPEATABLE_READ);
			}
		}

		heapLocation = heapController.newRowLocationTemplate();
		rowLocationOut[0]=heapLocation;

		// loop through rows on this list, inserting them into system table
		for (int rowNumber = 0; rowNumber < rowList.length; rowNumber++)
		{
			ExecRow row = rowList[rowNumber];
			// insert the base row and get its new location 
			heapController.insertAndFetchLocation(row.getRowArray(), heapLocation);
			
			for ( int ictr = 0; ictr < indexCount; ictr++ )
		    {
				if (indexControllers[ ictr ] == null)
				{
					continue;
				}

				// Get an index row based on the base row
				indexableRow = getIndexRowFromHeapRow( getIndexRowGenerator(ictr),
													   heapLocation,
													   row );

                insertRetCode = indexControllers[ ictr ].insert(
                        indexableRow.getRowArray());

				if ( insertRetCode == ConglomerateController.ROWISDUPLICATE )
				{
					retCode = rowNumber;
				}
			}

		}	// end loop through rows on list

		// Close the open conglomerates
		for ( int ictr = 0; ictr < indexCount; ictr++ )
		{
			if (indexControllers[ ictr ] == null)
			{
				continue;
			}

			indexControllers[ ictr ].close();
		}
		heapController.close();

		return	retCode;
	}


    /**
      * Given a key row, delete all matching heap rows and their index
      * rows.
      * <p>
      * LOCKING: row locking if there is a key; otherwise, 
      * table locking.
      *
      * @param  tc          transaction controller
      * @param  key         key to delete by.
      * @param  indexNumber Key is appropriate for this index.
      * @return the number of rows deleted. If key is not unique,
      *         this may be more than one.
      * @exception StandardException        Thrown on failure
      */
	int deleteRow( TransactionController tc, ExecIndexRow key, int indexNumber )
		throws StandardException
	{
		// Always row locking
		return  deleteRows(tc,
						   key,
						   ScanController.GE,
						   null,
						   null,
						   key,
						   ScanController.GT,
						   indexNumber,
						   true);
	}

	int deleteRow( TransactionController tc, ExecIndexRow key,
							int indexNumber, boolean wait)
		throws StandardException
	{
		//  Always row locking
		return  deleteRows(tc,
						   key,
						   ScanController.GE,
						   null,
						   null,
						   key,
						   ScanController.GT,
						   indexNumber,
						   wait);
	}
	
    /**
      * Delete the set of rows defined by a scan on an index
      * from the table. Most of the parameters are simply passed
      * to TransactionController.openScan. Please refer to the
      * TransactionController documentation for details.
      * <p>
      * LOCKING: row locking if there is a start and a stop
      * key; otherwise, table locking
      *
      * @param  tc          transaction controller
      * @param  startKey    key to start the scan.
      * @param  startOp     operation to start the scan.
      * @param  stopKey     key to start the scan.
      * @param  qualifier   a qualifier for the scan.
      * @param  filter      filter on base rows
      * @param  stopOp      operation to start the scan.
      * @param  indexNumber Key is appropriate for this index.
      * @return the number of rows deleted.
      * @exception StandardException        Thrown on failure
      * @see TransactionController#openScan
      */
	int deleteRows(TransactionController tc,
							ExecIndexRow startKey,
							int startOp,
							Qualifier[][] qualifier,
							TupleFilter filter,
							ExecIndexRow stopKey,
							int stopOp,
							int indexNumber) throws StandardException
    {
		return  deleteRows(tc,
				   startKey,
				   startOp,
				   qualifier,
				   filter,
				   stopKey,
				   stopOp,
				   indexNumber,
				   true);
    }

	/**
	 * @inheritDoc
	 */
	private int deleteRows(TransactionController tc,
						  ExecIndexRow startKey,
						  int startOp,
						  Qualifier[][] qualifier,
						  TupleFilter filter,
						  ExecIndexRow stopKey,
						  int stopOp,
						  int indexNumber,
						  boolean wait)
		 throws StandardException
	{
		ConglomerateController		heapCC;
		ScanController				drivingScan;
		ExecIndexRow	 			drivingIndexRow;
		RowLocation					baseRowLocation;
		RowChanger 					rc;
		ExecRow						baseRow = crf.makeEmptyRow();
		int                         rowsDeleted = 0;
		boolean						passedFilter = true;
		
		rc = getRowChanger( tc, (int[])null,baseRow );

		/*
		** If we have a start and a stop key, then we are going to 
		** get row locks, otherwise, we are getting table locks.
		** This may be excessive locking for the case where there
		** is a start key and no stop key or vice versa.
		*/
		int lockMode = ((startKey != null) && (stopKey != null)) ? 
                TransactionController.MODE_RECORD : 
                TransactionController.MODE_TABLE;

		/*
		** Don't use level 3 if we have the same start/stop key.
		*/
		int isolation = 
            ((startKey != null) && (stopKey != null) && (startKey == stopKey)) ?
				TransactionController.ISOLATION_REPEATABLE_READ :
				TransactionController.ISOLATION_SERIALIZABLE;

		// Row level locking
		rc.open(lockMode, wait);

		DataValueDescriptor[] startKeyRow = 
            startKey == null ? null : startKey.getRowArray();

		DataValueDescriptor[] stopKeyRow = 
            stopKey == null  ? null : stopKey.getRowArray();

		/* Open the heap conglomerate */
		heapCC = tc.openConglomerate(
                    getHeapConglomerate(),
                    false,
                    (TransactionController.OPENMODE_FORUPDATE |
                            ((wait) ? 0 : TransactionController.OPENMODE_LOCK_NOWAIT)),
                    lockMode,
                    TransactionController.ISOLATION_REPEATABLE_READ);

		drivingScan = tc.openScan(
			getIndexConglomerate(indexNumber),  // conglomerate to open
			false, // don't hold open across commit
			(TransactionController.OPENMODE_FORUPDATE | 
				((wait) ? 0 : TransactionController.OPENMODE_LOCK_NOWAIT)),
            lockMode,
			isolation,
			(FormatableBitSet) null, // all fields as objects
			startKeyRow,   // start position - first row
            startOp,      // startSearchOperation
			qualifier, //scanQualifier
			stopKeyRow,   // stop position - through last row
            stopOp);     // stopSearchOperation

		// Get an index row based on the base row
		drivingIndexRow = getIndexRowFromHeapRow(
			getIndexRowGenerator( indexNumber ),
			heapCC.newRowLocationTemplate(),
			crf.makeEmptyRow());

		while (drivingScan.fetchNext(drivingIndexRow.getRowArray()))
		{
			baseRowLocation = (RowLocation)
						drivingIndexRow.getColumn(drivingIndexRow.nColumns());

			boolean base_row_exists = 
                heapCC.fetch(
                    baseRowLocation, baseRow.getRowArray(), (FormatableBitSet) null);

            if (SanityManager.DEBUG)
            {
                // it can not be possible for heap row to disappear while 
                // holding scan cursor on index at ISOLATION_REPEATABLE_READ.
                SanityManager.ASSERT(base_row_exists, "base row not found");
            }

			// only delete rows which pass the base-row filter
			if ( filter != null ) { passedFilter = filter.execute( baseRow ).equals( true ); }
			if ( passedFilter )
			{
				rc.deleteRow( baseRow, baseRowLocation );
				rowsDeleted++;
			}
		}

		heapCC.close();
		drivingScan.close();
		rc.close();
		
		return rowsDeleted;
	}

    /**
      * Given a key row, return the first matching heap row.
      * <p>
      * LOCKING: shared row locking.
      *
      * @param  tc          transaction controller
      * @param  key         key to read by.
      * @param  indexNumber Key is appropriate for this index.
      * @exception StandardException        Thrown on failure
      */
	ExecRow getRow( TransactionController tc,
						ExecIndexRow key,
						int indexNumber )
		throws StandardException
	{
		ConglomerateController		heapCC;

		/* Open the heap conglomerate */
		heapCC = tc.openConglomerate(
                    getHeapConglomerate(),
                    false,
                    0, 						// for read only
                    TransactionController.MODE_RECORD,
                    TransactionController.ISOLATION_REPEATABLE_READ);

		try { return getRow( tc, heapCC, key, indexNumber ); }
		finally { heapCC.close(); }
	}

	/**
	 * Given an index row and index number return the RowLocation
	 * in the heap of the first matching row.
	 * Used by the autoincrement code to get the RowLocation in
	 * syscolumns given a &lt;tablename, columname&gt; pair.
	 * 
	 * @see DataDictionaryImpl#computeRowLocation(TransactionController, TableDescriptor, String)
	 *
	 * @param tc		  Transaction Controller to use.
	 * @param key		  Index Row to search in the index.
	 * @param indexNumber Identifies the index to use.
	 *
	 * @exception		  StandardException thrown on failure.
	 */
	RowLocation getRowLocation(TransactionController tc,
									  ExecIndexRow key,
									  int indexNumber)
			  throws StandardException
	{
		ConglomerateController		heapCC;
		heapCC = tc.openConglomerate(
                    getHeapConglomerate(),
                    false,
                    0, 						// for read only
                    TransactionController.MODE_RECORD,
                    TransactionController.ISOLATION_REPEATABLE_READ);

		try 
		{
			RowLocation rl[] = new RowLocation[1];
			ExecRow notUsed = getRowInternal(tc, heapCC, key, indexNumber, rl);
			return rl[0];
		}
		finally
		{
			heapCC.close();
		}
	}
    /**
      * Given a key row, return the first matching heap row.
      * <p>
      * LOCKING: shared row locking.
      *
      * @param  tc          transaction controller
      * @param  heapCC      heap to look in
      * @param  key         key to read by.
      * @param  indexNumber Key is appropriate for this index.
      * @exception StandardException        Thrown on failure
      */
	ExecRow getRow( TransactionController tc,
						   ConglomerateController heapCC,
						   ExecIndexRow key,
						   int indexNumber)
							
		 throws StandardException
	{
		RowLocation rl[] = new RowLocation[1];
		return getRowInternal(tc, heapCC, key, indexNumber, rl);
	}

	/**
	  * @exception StandardException		Thrown on failure
	  */
	private ExecRow getRowInternal( TransactionController tc,
									ConglomerateController heapCC,
									ExecIndexRow key,
									int indexNumber,
									RowLocation rl[])

		 throws StandardException
	{
		ScanController				drivingScan;
		ExecIndexRow	 			drivingIndexRow;
		RowLocation					baseRowLocation;
		ExecRow						baseRow = crf.makeEmptyRow();

		drivingScan = tc.openScan(
			getIndexConglomerate(indexNumber),
			                     // conglomerate to open
			false,               // don't hold open across commit
			0,                   // open for read
            TransactionController.MODE_RECORD,
            TransactionController.ISOLATION_REPEATABLE_READ,
			(FormatableBitSet) null,      // all fields as objects
			key.getRowArray(),   // start position - first row
            ScanController.GE,   // startSearchOperation
			null,                //scanQualifier
			key.getRowArray(),   // stop position - through last row
            ScanController.GT);  // stopSearchOperation

		// Get an index row based on the base row
		drivingIndexRow = getIndexRowFromHeapRow(
			getIndexRowGenerator( indexNumber ),
			heapCC.newRowLocationTemplate(),
			crf.makeEmptyRow());

		try	{
			if (drivingScan.fetchNext(drivingIndexRow.getRowArray()))
			{
				rl[0] = baseRowLocation = (RowLocation)
					drivingIndexRow.getColumn(drivingIndexRow.nColumns());
				boolean base_row_exists = 
                    heapCC.fetch(
                        baseRowLocation, baseRow.getRowArray(), (FormatableBitSet) null);

                if (SanityManager.DEBUG)
                {
                    // it can not be possible for heap row to disappear while 
                    // holding scan cursor on index at ISOLATION_REPEATABLE_READ.
                    SanityManager.ASSERT(base_row_exists, "base row not found");
                }

				return baseRow;
			}
			else
			{
				return null;
			}
		}

		finally {
			drivingScan.close();
		}
	}

	/**
	 * Updates a base row in a catalog and updates all the corresponding
	 * index rows.
	 *
	 *	@param	key			key row
	 *	@param	newRow		new version of the row
	 *	@param	indexNumber	index that key operates
	 *	@param	indicesToUpdate	array of booleans, one for each index on the catalog.
	 *							if a boolean is true, that means we must update the
	 *							corresponding index because changes in the newRow
	 *							affect it.
	 *	@param  colsToUpdate	array of ints indicating which columns (1 based)
	 *							to update.  If null, do all.
	 *	@param	tc			transaction controller
	 *
	 * @exception StandardException		Thrown on failure
	 */
	void updateRow( ExecIndexRow				key, 
						   ExecRow					newRow, 
						   int						indexNumber,
						   boolean[]				indicesToUpdate,
						   int[]					colsToUpdate,
						   TransactionController	tc)
		throws StandardException
	{
		ExecRow[] newRows = new ExecRow[1];
		newRows[0] = newRow;
		updateRow(key, newRows, indexNumber, indicesToUpdate, colsToUpdate, tc);
	}

	/**
	 * Updates a set of base rows in a catalog with the same key on an index
	 * and updates all the corresponding index rows. 
	 *
	 *	@param	key			key row
	 *	@param	newRows		new version of the array of rows
	 *	@param	indexNumber	index that key operates
	 *	@param	indicesToUpdate	array of booleans, one for each index on the catalog.
	 *							if a boolean is true, that means we must update the
	 *							corresponding index because changes in the newRow
	 *							affect it.
	 *	@param  colsToUpdate	array of ints indicating which columns (1 based)
	 *							to update.  If null, do all.
	 *	@param	tc			transaction controller
	 *
	 * @exception StandardException		Thrown on failure
	 */
	void updateRow( ExecIndexRow				key,
						   ExecRow[]				newRows,
						   int						indexNumber,
						   boolean[]				indicesToUpdate,
						   int[]					colsToUpdate,
						   TransactionController	tc )
		throws StandardException
	{
		ConglomerateController		heapCC;
		ScanController				drivingScan;
		ExecIndexRow	 			drivingIndexRow;
		RowLocation					baseRowLocation;
		ExecRow						baseRow = crf.makeEmptyRow();

		if (SanityManager.DEBUG)
		{
			SanityManager.ASSERT( indicesToUpdate.length == crf.getNumIndexes(),
								 "Wrong number of indices." );
		}

		RowChanger 					rc  = getRowChanger( tc, colsToUpdate,baseRow );

		// Row level locking
		rc.openForUpdate(indicesToUpdate, TransactionController.MODE_RECORD, true);

		/* Open the heap conglomerate */
		heapCC = tc.openConglomerate(
                    getHeapConglomerate(),
                    false,
                    TransactionController.OPENMODE_FORUPDATE,
                    TransactionController.MODE_RECORD,
                    TransactionController.ISOLATION_REPEATABLE_READ);

		drivingScan = tc.openScan(
			getIndexConglomerate(indexNumber),  // conglomerate to open
			false, // don't hold open across commit
			TransactionController.OPENMODE_FORUPDATE,
            TransactionController.MODE_RECORD,
            TransactionController.ISOLATION_REPEATABLE_READ,
			(FormatableBitSet) null,     // all fields as objects
			key.getRowArray(),   // start position - first row
            ScanController.GE,      // startSearchOperation
			null, //scanQualifier
			key.getRowArray(),   // stop position - through last row
            ScanController.GT);     // stopSearchOperation

		// Get an index row based on the base row
		drivingIndexRow = getIndexRowFromHeapRow(
			getIndexRowGenerator( indexNumber ),
			heapCC.newRowLocationTemplate(),
			crf.makeEmptyRow());

		int rowNum = 0;
		while (drivingScan.fetchNext(drivingIndexRow.getRowArray()))
		{
			baseRowLocation = (RowLocation)
						drivingIndexRow.getColumn(drivingIndexRow.nColumns());
			boolean base_row_exists = 
                heapCC.fetch(
                    baseRowLocation, baseRow.getRowArray(), (FormatableBitSet) null);

            if (SanityManager.DEBUG)
            {
                // it can not be possible for heap row to disappear while 
                // holding scan cursor on index at ISOLATION_REPEATABLE_READ.
                SanityManager.ASSERT(base_row_exists, "base row not found");
            }
			
			rc.updateRow(baseRow, (rowNum == newRows.length - 1) ?
						newRows[rowNum] : newRows[rowNum++], baseRowLocation );
		}
		rc.finish();
		heapCC.close();
		drivingScan.close();
		rc.close();
	}

	/**
	 * Get the Properties associated with creating the heap.
	 *
	 * @return The Properties associated with creating the heap.
	 */
	Properties getCreateHeapProperties()
	{
		return crf.getCreateHeapProperties();
	}

	/**
	 * Get the Properties associated with creating the specified index.
	 *
	 * @param indexNumber	The specified index number.
	 *
	 * @return The Properties associated with creating the specified index.
	 */
	Properties getCreateIndexProperties(int indexNumber)
	{
		return crf.getCreateIndexProperties(indexNumber);
	}

	/**
	  *	Gets a row changer for this catalog.
	  *
	  *	@param	tc	transaction controller
	  *	@param	changedCols	the columns to change (1 based), may be null
	  * @param  baseRow used to detemine column types at creation time
	  *         only. The row changer does ***Not*** keep a referance to
	  *         this row or change it in any way.
	  *
	  *	@return	a row changer for this catalog.
	  * @exception StandardException		Thrown on failure
	  */
	private	RowChanger	getRowChanger( TransactionController tc,
									   int[] changedCols,
									   ExecRow baseRow)
		throws StandardException
	{
		RowChanger 					rc;
		int							indexCount = crf.getNumIndexes();
		IndexRowGenerator[]			irgs = new IndexRowGenerator[ indexCount ];
		long[]						cids = new long[ indexCount ];

		if (SanityManager.DEBUG)
		{
			if (changedCols != null)
			{
				for (int i = changedCols.length - 1; i >= 0; i--)
				{
					SanityManager.ASSERT(changedCols[i] != 0, 
						"Column id is 0, but should be 1 based");
				}
			}
		}

		for ( int ictr = 0; ictr < indexCount; ictr++ )
		{
			irgs[ictr] = getIndexRowGenerator(ictr);
			cids[ictr] = getIndexConglomerate(ictr);
		}

		rc = crf.getExecutionFactory().getRowChanger(getHeapConglomerate(),
											(StaticCompiledOpenConglomInfo) null,
											(DynamicCompiledOpenConglomInfo) null,
											irgs,
											cids,
											(StaticCompiledOpenConglomInfo[]) null,
											(DynamicCompiledOpenConglomInfo[]) null,
											crf.getHeapColumnCount(),
											tc,
											changedCols,
											getStreamStorableHeapColIds(baseRow),
											(Activation) null);
		return	rc;
	}

	private boolean computedStreamStorableHeapColIds = false;
	private int[] streamStorableHeapColIds;
	private int[] getStreamStorableHeapColIds(ExecRow baseRow) throws StandardException
	{
		if (!computedStreamStorableHeapColIds)
		{
			int sshcidLen = 0;
			//
			//Compute the length of streamStorableHeapColIds
			//One entry for each column id.
			DataValueDescriptor[] ra = baseRow.getRowArray();
			for(int ix=0;ix<ra.length;ix++)
				if (ra[ix] instanceof StreamStorable) sshcidLen++;

			//
			//If we have some streamStorableHeapColIds we
			//allocate an array to remember them and fill in
			//the array with the 0 based column ids. If we
			//have none leave streamStorableHeapColIds Null.
			if (sshcidLen > 0)
			{
				streamStorableHeapColIds = new int[sshcidLen];
				int sshcidOffset=0;
				for(int ix=0;ix<ra.length;ix++)
					if (ra[ix] instanceof StreamStorable)
 						streamStorableHeapColIds[sshcidOffset++] = ix;
			}
			computedStreamStorableHeapColIds = true;
		}
		return streamStorableHeapColIds;
	}

	/**
	 * Get an index row based on a row from the heap.
	 *
	 * @param irg		IndexRowGenerator to use
	 * @param rl		RowLocation for heap
	 * @param heapRow	Row from the heap
	 *
	 * @return ExecIndexRow	Index row.
	 *
	 * @exception StandardException		Thrown on error
	 */
	private ExecIndexRow getIndexRowFromHeapRow(IndexRowGenerator irg,
								   RowLocation rl,
								   ExecRow heapRow)
		throws StandardException
	{
		ExecIndexRow		indexRow;

		indexRow = irg.getIndexRowTemplate();
		// Get an index row based on the base row
		irg.getIndexRow(heapRow, rl, indexRow, (FormatableBitSet) null);

		return indexRow;
	}

	public String toString()
	{
		if (SanityManager.DEBUG)
		{
			return "name: " + this.getTableName() + 
				"\n\theapCongolomerate: "+heapConglomerate +
				"\n\tnumIndexes: " + ((indexes != null) ? indexes.length : 0) +
				"\n\tnumIndexesSet: " + numIndexesSet +
				"\n\theapSet: " + heapSet +
				"\n"; 
		}
		else
		{
			return "";
		}
	}
}
