/*

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

   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.util.List;
import java.util.Properties;
import org.apache.derby.catalog.types.ReferencedColumnsDescriptorImpl;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.ClassName;
import org.apache.derby.iapi.services.classfile.VMOpcode;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.services.io.FormatableBitSet;
import org.apache.derby.iapi.sql.compile.AccessPath;
import org.apache.derby.iapi.sql.compile.CostEstimate;
import org.apache.derby.iapi.sql.compile.Optimizable;
import org.apache.derby.iapi.sql.compile.RequiredRowOrdering;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;

/**
 * This node type translates an index row to a base row.  It takes a
 * FromBaseTable as its source ResultSetNode, and generates an
 * IndexRowToBaseRowResultSet that takes a TableScanResultSet on an
 * index conglomerate as its source.
 */
class IndexToBaseRowNode extends FromTable
{
	protected FromBaseTable	source;
	protected ConglomerateDescriptor	baseCD;
	protected boolean	cursorTargetTable;
	protected PredicateList restrictionList;
	protected boolean	forUpdate;
	private FormatableBitSet	heapReferencedCols;
	private FormatableBitSet	indexReferencedCols;
	private FormatableBitSet	allReferencedCols;
	private FormatableBitSet	heapOnlyReferencedCols;

    IndexToBaseRowNode(
            FromBaseTable    source,
            ConglomerateDescriptor  baseCD,
            ResultColumnList resultColumns,
            boolean          cursorTargetTable,
            FormatableBitSet heapReferencedCols,
            FormatableBitSet indexReferencedCols,
            PredicateList    restrictionList,
            boolean          forUpdate,
            Properties       tableProperties,
            ContextManager   cm)
	{
        super(null, tableProperties, cm);
        this.source = source;
        this.baseCD = baseCD;
        setResultColumns( resultColumns );
        this.cursorTargetTable = cursorTargetTable;
        this.restrictionList = restrictionList;
        this.forUpdate = forUpdate;
        this.heapReferencedCols = heapReferencedCols;
        this.indexReferencedCols = indexReferencedCols;

		if (this.indexReferencedCols == null) {
			this.allReferencedCols = this.heapReferencedCols;
			heapOnlyReferencedCols = this.heapReferencedCols;
		}
		else {
			this.allReferencedCols =
				new FormatableBitSet(this.heapReferencedCols);
			this.allReferencedCols.or(this.indexReferencedCols);
			heapOnlyReferencedCols =
				new FormatableBitSet(allReferencedCols);
			heapOnlyReferencedCols.xor(this.indexReferencedCols);
		}
	}

	/** @see Optimizable#forUpdate */
    @Override
	public boolean forUpdate()
	{
		return source.forUpdate();
	}

	/** @see Optimizable#getTrulyTheBestAccessPath */
    @Override
	public AccessPath getTrulyTheBestAccessPath()
	{
		// Get AccessPath comes from base table.
		return ((Optimizable) source).getTrulyTheBestAccessPath();
	}

    @Override
    CostEstimate getCostEstimate()
	{
		return source.getTrulyTheBestAccessPath().getCostEstimate();
	}

    @Override
    CostEstimate getFinalCostEstimate()
	{
		return source.getFinalCostEstimate();
	}

	/**
	 * Return whether or not the underlying ResultSet tree
	 * is ordered on the specified columns.
	 * RESOLVE - This method currently only considers the outermost table 
	 * of the query block.
	 *
	 * @param	crs					The specified ColumnReference[]
	 * @param	permuteOrdering		Whether or not the order of the CRs in the array can be permuted
     * @param   fbtHolder           List that is to be filled with the FromBaseTable
	 *
	 * @return	Whether the underlying ResultSet tree
	 * is ordered on the specified column.
	 *
	 * @exception StandardException		Thrown on error
	 */
    @Override
    boolean isOrderedOn(ColumnReference[] crs, boolean permuteOrdering, List<FromBaseTable> fbtHolder)
				throws StandardException
	{
        return source.isOrderedOn(crs, permuteOrdering, fbtHolder);
	}

	/**
	 * Generation of an IndexToBaseRowNode creates an
	 * IndexRowToBaseRowResultSet, which uses the RowLocation in the last
	 * column of an index row to get the row from the base conglomerate (heap).
	 *
	 * @param acb	The ActivationClassBuilder for the class being built
	 * @param mb the method  for the method to be built
	 *
	 * @exception StandardException		Thrown on error
	 */
    @Override
    void generate(ActivationClassBuilder acb, MethodBuilder mb)
							throws StandardException
	{
		ValueNode		restriction = null;

		/*
		** Get the next ResultSet #, so that we can number this ResultSetNode,
		** its ResultColumnList and ResultSet.
		*/
		assignResultSetNumber();

		// Get the CostEstimate info for the underlying scan
		setCostEstimate( getFinalCostEstimate() );

		/* Put the predicates back into the tree */
		if (restrictionList != null)
		{
			restriction = restrictionList.restorePredicates();
			/* Allow the restrictionList to get garbage collected now
			 * that we're done with it.
			 */
			restrictionList = null;
		}

		// for the restriction, we generate an exprFun
		// that evaluates the expression of the clause
		// against the current row of the child's result.
		// if the restriction is empty, simply pass null
		// to optimize for run time performance.

   		// generate the function and initializer:
   		// Note: Boolean lets us return nulls (boolean would not)
   		// private Boolean exprN()
   		// {
   		//   return <<restriction.generate(ps)>>;
   		// }
   		// static Method exprN = method pointer to exprN;



		int heapColRefItem = -1;
		if (heapReferencedCols != null)
		{
			heapColRefItem = acb.addItem(heapReferencedCols);
		}
		int allColRefItem = -1;
		if (allReferencedCols != null)
		{
			allColRefItem = acb.addItem(allReferencedCols);
		}
		int heapOnlyColRefItem = -1;
		if (heapOnlyReferencedCols != null)
		{
			heapOnlyColRefItem = acb.addItem(heapOnlyReferencedCols);
		}

		/* Create the ReferencedColumnsDescriptorImpl which tells which columns
		 * come from the index.
		 */
		int indexColMapItem = acb.addItem(new ReferencedColumnsDescriptorImpl(getIndexColMapping()));
		long heapConglomNumber = baseCD.getConglomerateNumber();
		StaticCompiledOpenConglomInfo scoci = getLanguageConnectionContext().
												getTransactionCompile().
													getStaticCompiledConglomInfo(heapConglomNumber);

		acb.pushGetResultSetFactoryExpression(mb);

		mb.push(heapConglomNumber);
		mb.push(acb.addItem(scoci));
		source.generate(acb, mb);
		
		mb.upCast(ClassName.NoPutResultSet);

        // Skip over the index columns that are propagated from the source
        // result set, if there are such columns. We won't pass the SQL NULL
        // wrappers down to store for those columns anyways, so no need to
        // generate them in the row template.
        // NOTE: We have to check for the case where indexReferencedCols is
        // not null, but no bits are set. This can happen when we need to get
        // all of the columns from the heap due to a check constraint.
        boolean skipPropagatedCols =
                indexReferencedCols != null &&
                indexReferencedCols.getNumBitsSet() != 0;
        mb.push(acb.addItem(getResultColumns()
                .buildRowTemplate(heapReferencedCols, skipPropagatedCols)));

		mb.push(getResultSetNumber());
		mb.push(source.getBaseTableName());
		mb.push(heapColRefItem);

		mb.push(allColRefItem);
		mb.push(heapOnlyColRefItem);

		mb.push(indexColMapItem);

		// if there is no restriction, we just want to pass null.
		if (restriction == null)
		{
		   	mb.pushNull(ClassName.GeneratedMethod);
		}
		else
		{
			// this sets up the method and the static field.
			// generates:
			// 	Object userExprFun { }
			MethodBuilder userExprFun = acb.newUserExprFun();

			// restriction knows it is returning its value;
	
			/* generates:
			 *    return <restriction.generate(acb)>;
			 * and adds it to userExprFun
			 * NOTE: The explicit cast to DataValueDescriptor is required
			 * since the restriction may simply be a boolean column or subquery
			 * which returns a boolean.  For example:
			 *		where booleanColumn
			 */
			restriction.generate(acb, userExprFun);
			userExprFun.methodReturn();

			// we are done modifying userExprFun, complete it.
			userExprFun.complete();

	   		// restriction is used in the final result set as an access of the new static
   			// field holding a reference to this new method.
			// generates:
			//	ActivationClass.userExprFun
			// which is the static field that "points" to the userExprFun
			// that evaluates the where clause.
   			acb.pushMethodReference(mb, userExprFun);
		}

		mb.push(forUpdate);
		mb.push(getCostEstimate().rowCount());
		mb.push(getCostEstimate().getEstimatedCost());
        mb.push( source.getTableDescriptor().getNumberOfColumns() );

		mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getIndexRowToBaseRowResultSet",
						ClassName.NoPutResultSet, 15);

		/* The IndexRowToBaseRowResultSet generator is what we return */

		/*
		** Remember if this result set is the cursor target table, so we
		** can know which table to use when doing positioned update and delete.
		*/
		if (cursorTargetTable)
		{
			acb.rememberCursorTarget(mb);
		}
	}

	/**
	 * Return whether or not the underlying ResultSet tree will return
	 * a single row, at most.
	 * This is important for join nodes where we can save the extra next
	 * on the right side if we know that it will return at most 1 row.
	 *
	 * @return Whether or not the underlying ResultSet tree will return a single row.
	 * @exception StandardException		Thrown on error
	 */
    @Override
    boolean isOneRowResultSet() throws StandardException
	{
		// Default is false
		return source.isOneRowResultSet();
	}

	/**
	 * Return whether or not the underlying FBT is for NOT EXISTS.
	 *
	 * @return Whether or not the underlying FBT is for NOT EXISTS.
	 */
    @Override
    boolean isNotExists()
	{
		return source.isNotExists();
	}

	/**
	 * Decrement (query block) level (0-based) for this FromTable.
	 * This is useful when flattening a subquery.
	 *
	 * @param decrement	The amount to decrement by.
	 */
    @Override
	void decrementLevel(int decrement)
	{
		source.decrementLevel(decrement);
	}

	/**
	 * Get the lock mode for the target of an update statement
	 * (a delete or update).  The update mode will always be row for
	 * CurrentOfNodes.  It will be table if there is no where clause.
	 *
	 * @return	The lock mode
	 */
    @Override
    int updateTargetLockMode()
	{
		return source.updateTargetLockMode();
	}

	/**
	 * @see ResultSetNode#adjustForSortElimination
	 */
    @Override
	void adjustForSortElimination()
	{
		/* NOTE: We use a different method to tell a FBT that
		 * it cannot do a bulk fetch as the ordering issues are
		 * specific to a FBT being under an IRTBR as opposed to a
		 * FBT being under a PRN, etc.
		 */
		source.disableBulkFetch();
	}

	/**
	 * @see ResultSetNode#adjustForSortElimination
	 */
    @Override
	void adjustForSortElimination(RequiredRowOrdering rowOrdering)
		throws StandardException
	{
		/* rowOrdering is not important to this specific node, so
		 * just call the no-arg version of the method.
		 */
		adjustForSortElimination();

		/* Now pass the rowOrdering down to source, which may
		 * need to do additional work. DERBY-3279.
		 */
		source.adjustForSortElimination(rowOrdering);
	}

	/** 
	 * Fill in the column mapping for those columns coming from the index.
	 *
	 * @return The int[] with the mapping.
	 */
	private int[] getIndexColMapping()
	{
		int		rclSize = getResultColumns().size();
		int[]	indexColMapping = new int[rclSize];

		for (int index = 0; index < rclSize; index++)
		{
            ResultColumn rc = getResultColumns().elementAt(index);

			if (indexReferencedCols != null && rc.getExpression() instanceof VirtualColumnNode)
			{
				// Column is coming from index
				VirtualColumnNode vcn = (VirtualColumnNode) rc.getExpression();
				indexColMapping[index] =
					vcn.getSourceColumn().getVirtualColumnId() - 1;
			}
			else
			{
				// Column is not coming from index
				indexColMapping[index] = -1;
			}
		}

		return indexColMapping;
	}

	/**
	 * Accept the visitor for all visitable children of this node.
	 * 
	 * @param v the visitor
	 *
	 * @exception StandardException on error
	 */
    @Override
	void acceptChildren(Visitor v)
		throws StandardException
	{
		super.acceptChildren(v);

		if (source != null)
		{
			source = (FromBaseTable)source.accept(v);
		}
	}

}
