/*

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

   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.io.PrintWriter;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.compile.AccessPath;
import org.apache.derby.iapi.sql.compile.CostEstimate;
import org.apache.derby.iapi.sql.compile.JoinStrategy;
import org.apache.derby.iapi.sql.compile.OptTrace;
import org.apache.derby.iapi.sql.compile.Optimizable;
import org.apache.derby.iapi.sql.compile.OptimizableList;
import org.apache.derby.iapi.sql.compile.Optimizer;
import org.apache.derby.iapi.sql.compile.RequiredRowOrdering;
import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;
import org.apache.derby.iapi.util.JBitSet;

/**
 * This is the default optimizer tracing logic for use when a custom
 * tracer wasn't specified.
 */
public  class   DefaultOptTrace implements  OptTrace
{
    ////////////////////////////////////////////////////////////////////////
    //
    //	CONSTANTS
    //
    ////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////////
    //
    //	STATE
    //
    ////////////////////////////////////////////////////////////////////////

    private StringBuilder _buffer;
    
    ////////////////////////////////////////////////////////////////////////
    //
    //	CONSTRUCTOR
    //
    ////////////////////////////////////////////////////////////////////////

    /** Make a DefaultOptTrace */
    public  DefaultOptTrace()
    {
        _buffer = new StringBuilder();
    }

    ////////////////////////////////////////////////////////////////////////
    //
    //	OptTrace BEHAVIOR
    //
    ////////////////////////////////////////////////////////////////////////

    public  void    traceStartStatement( String statementText )
    {
        appendTraceString( statementText );
    }
    
    public  void    traceStartQueryBlock( long timeOptimizationStarted, int optimizerID, OptimizableList optimizableList )
    {
        appendTraceString
            (
             "Optimization started at time " + 
             timeOptimizationStarted +
             " using optimizer " + optimizerID
             );
    }

    public  void    traceEndQueryBlock() {}

    public  void    traceTimeout( long currentTime, CostEstimate bestCost )
    {
        appendTraceString
            (
             "Optimization time exceeded at time " +
             currentTime + "\n" + bestCost
             );
    }
   
    public  void    traceVacuous()
    {
        appendTraceString( "No tables to optimize." );
    }

    public  void    traceCompleteJoinOrder()
    {
        appendTraceString( "We have a complete join order." );
    }

    public  void    traceSortCost( CostEstimate sortCost, CostEstimate currentCost )
    {
        appendTraceString( "Cost of sorting is " + sortCost );
        appendTraceString( "Total cost of non-sort-avoidance plan with sort cost added is " + currentCost );
    }

    public  void    traceNoBestPlan()
    {
        appendTraceString( "No best plan found." );
    }

    public  void    traceModifyingAccessPaths( int optimizerID )
    {
        appendTraceString( "Modifying access paths using optimizer " + optimizerID );
    }

    public  void    traceShortCircuiting( boolean timeExceeded, Optimizable thisOpt, int joinPosition )
    {
        String basis = (timeExceeded) ? "time exceeded" : "cost";
        if ( thisOpt.getBestAccessPath().getCostEstimate() == null ) { basis = "no best plan found"; }
        
        appendTraceString( "Short circuiting based on " + basis + " at join position " + joinPosition );
    }
    
    public  void    traceSkippingJoinOrder
        ( int nextOptimizable, int joinPosition, int[] proposedJoinOrder, JBitSet assignedTableMap )
    {
        appendTraceString
            (
             reportJoinOrder( "\n\nSkipping join order: ", true, nextOptimizable, joinPosition, proposedJoinOrder, assignedTableMap )
             );
    }

    public  void    traceIllegalUserJoinOrder()
    {
        appendTraceString( "User specified join order is not legal." );
    }
    
    public  void    traceUserJoinOrderOptimized()
    {
        appendTraceString( "User-specified join order has now been optimized." );
    }

    public  void    traceJoinOrderConsideration( int joinPosition, int[] proposedJoinOrder, JBitSet assignedTableMap )
    {
        appendTraceString
            (
             reportJoinOrder( "\n\nConsidering join order: ", false, 0, joinPosition, proposedJoinOrder, assignedTableMap )
             );
    }

    public  void    traceCostWithoutSortAvoidance( CostEstimate currentCost )
    {
        appendTraceString( "Total cost of non-sort-avoidance plan is " + currentCost );
    }

    public  void    traceCostWithSortAvoidance( CostEstimate currentSortAvoidanceCost )
    {
        appendTraceString( "Total cost of sort avoidance plan is " + currentSortAvoidanceCost );
    }

    public  void    traceCurrentPlanAvoidsSort( CostEstimate bestCost, CostEstimate currentSortAvoidanceCost )
    {
        appendTraceString
            (
             "Current plan is a sort avoidance plan." + 
             "\n\tBest cost is : " + bestCost +
             "\n\tThis cost is : " + currentSortAvoidanceCost
             );
    }

    public  void    traceCheapestPlanSoFar( int planType, CostEstimate currentCost )
    {
        appendTraceString( "This is the cheapest plan so far." );
        appendTraceString
            (
             "Plan is a " +
             (planType == Optimizer.NORMAL_PLAN ? "normal" : "sort avoidance") +
             " plan."
             );
        appendTraceString( "Cost of cheapest plan is " + currentCost );
    }

    public  void    traceSortNeededForOrdering( int planType, RequiredRowOrdering requiredRowOrdering )
    {
        appendTraceString
            (
             "Sort needed for ordering: " + (planType != Optimizer.SORT_AVOIDANCE_PLAN) +
             "\n\tRow ordering: " + requiredRowOrdering
             );
    }

    public  void    traceRememberingBestJoinOrder
        ( int joinPosition, int[] bestJoinOrder, int planType, CostEstimate planCost, JBitSet assignedTableMap )
    {
        appendTraceString
            (
             reportJoinOrder( "\n\nRemembering join order as best: ", false, 0, joinPosition, bestJoinOrder, assignedTableMap )
             );
    }

    public  void    traceSkippingBecauseTooMuchMemory( int maxMemoryPerTable )
    {
        appendTraceString( "Skipping access path due to excess memory usage, maximum is " + maxMemoryPerTable );
    }

    public  void    traceCostOfNScans( int tableNumber, double rowCount, CostEstimate cost )
    {
        appendTraceString
            (
             "Cost of " + rowCount + " scans is: " + cost +
             " for table " + tableNumber
             );
    }

    public  void    traceSkipUnmaterializableHashJoin()
    {
        appendTraceString( "Skipping HASH JOIN because optimizable is not materializable" );
    }

    public  void    traceSkipHashJoinNoHashKeys()
    {
        appendTraceString( "Skipping HASH JOIN because there are no hash key columns" );
    }

    public  void    traceHashKeyColumns( int[] hashKeyColumns )
    {
        String  traceString = "# hash key columns = " + hashKeyColumns.length;
        for (int index = 0; index < hashKeyColumns.length; index++)
        {
            traceString = 
                "\n" + traceString + "hashKeyColumns[" + index +
                "] = " + hashKeyColumns[index];
        }

        appendTraceString( traceString );
    }

    public  void    traceOptimizingJoinNode()
    {
        appendTraceString( "Calling optimizeIt() for join node" );
    }

    public  void    traceConsideringJoinStrategy( JoinStrategy js, int tableNumber )
    {
        appendTraceString
            (
             "\nConsidering join strategy " + js +
             " for table " + tableNumber
             );
    }

    public  void    traceRememberingBestAccessPath( AccessPath accessPath, int tableNumber, int planType )
    {
        appendTraceString
            (
             "Remembering access path " + accessPath +
             " as truly the best for table " + tableNumber + 
             " for plan type " + (planType == Optimizer.NORMAL_PLAN ? " normal " : "sort avoidance") +
             "\n"
             );
    }

    public  void    traceNoMoreConglomerates( int tableNumber )
    {
        appendTraceString( "No more conglomerates to consider for table " + tableNumber );
    }

    public  void    traceConsideringConglomerate( ConglomerateDescriptor cd, int tableNumber )
    {
        appendTraceString
            (
             "\nConsidering conglomerate " + reportConglomerateDescriptor( cd ) +
             " for table " + tableNumber
             );
    }

    public  void    traceScanningHeapWithUniqueKey()
    {
        appendTraceString( "Scanning heap, but we have a full match on a unique key." );
    }

    public  void    traceAddingUnorderedOptimizable( int predicateCount )
    {
        appendTraceString( "Adding unordered optimizable, # of predicates = " + predicateCount );
    }

    public  void    traceChangingAccessPathForTable( int tableNumber )
    {
        appendTraceString( "Changing access path for table " + tableNumber );
    }

    public  void    traceNoStartStopPosition()
    {
        appendTraceString( "Lock mode set to MODE_TABLE because no start or stop position" );
    }

    public  void    traceNonCoveringIndexCost( double cost, int tableNumber )
    {
        appendTraceString
            (
             "Index does not cover query - cost including base row fetch is: " + cost +
             " for table " + tableNumber
             );
    }

    public  void    traceConstantStartStopPositions()
    {
        appendTraceString( "Lock mode set to MODE_RECORD because all start and stop positions are constant" );
    }

    public  void    traceEstimatingCostOfConglomerate( ConglomerateDescriptor cd, int tableNumber )
    {
        String  cdString = reportConglomerateDescriptor( cd );
        appendTraceString
            (
             "Estimating cost of conglomerate: " +
             reportCostForTable( cdString, tableNumber )
             );

    }

    public  void    traceLookingForSpecifiedIndex( String indexName, int tableNumber )
    {
        appendTraceString
            (
             "Looking for user-specified index: " + indexName +
             " for table " + tableNumber
             );
    }

    public  void    traceSingleMatchedRowCost( double cost, int tableNumber )
    {
        appendTraceString
            (
             "Guaranteed to match a single row - cost is: " + cost +
             " for table " + tableNumber
             );
    }

    public  void    traceCostIncludingExtra1stColumnSelectivity( CostEstimate cost, int tableNumber )
    {
        appendTraceString
            (
             "Cost including extra first column selectivity is : " + cost +
             " for table " + tableNumber
             );
    }

    public  void    traceNextAccessPath( String baseTable, int predicateCount )
    {
        appendTraceString
            (
             "Calling nextAccessPath() for base table " + baseTable +
             " with " + predicateCount + " predicates."
             );
    }

    public  void    traceCostIncludingExtraStartStop( CostEstimate cost, int tableNumber )
    {
        appendTraceString( reportCostIncluding( "start/stop", cost, tableNumber ) );
    }

    public  void    traceCostIncludingExtraQualifierSelectivity( CostEstimate cost, int tableNumber )
    {
        appendTraceString( reportCostIncluding( "qualifier", cost, tableNumber ) );
    }

    public  void    traceCostIncludingExtraNonQualifierSelectivity( CostEstimate cost, int tableNumber )
    {
        appendTraceString( reportCostIncluding( "non-qualifier", cost, tableNumber ) );
    }

    public  void    traceCostOfNoncoveringIndex( CostEstimate cost, int tableNumber )
    {
        appendTraceString
            (
             "Index does not cover query: cost including row fetch is: " +
             reportCostForTable( cost, tableNumber )
             );
    }

    public  void    traceRememberingJoinStrategy( JoinStrategy joinStrategy, int tableNumber )
    {
        appendTraceString
            (
             "\nRemembering join strategy " + joinStrategy +
             " as best for table " + tableNumber
             );
    }

    public  void    traceRememberingBestAccessPathSubstring( AccessPath ap, int tableNumber )
    {
        appendTraceString( "in best access path" );
    }

    public  void    traceRememberingBestSortAvoidanceAccessPathSubstring( AccessPath ap, int tableNumber )
    {
        appendTraceString( "in best sort avoidance access path" );
    }

    public  void    traceRememberingBestUnknownAccessPathSubstring( AccessPath ap, int tableNumber )
    {
        appendTraceString( "in best unknown access path" );
    }

    public  void    traceCostOfConglomerateScan
        (
         int    tableNumber,
         ConglomerateDescriptor cd,
         CostEstimate   costEstimate,
         int    numExtraFirstColumnPreds,
         double    extraFirstColumnSelectivity,
         int    numExtraStartStopPreds,
         double    extraStartStopSelectivity,
         int    startStopPredCount,
         double    statStartStopSelectivity,
         int    numExtraQualifiers,
         double    extraQualifierSelectivity,
         int    numExtraNonQualifiers,
         double    extraNonQualifierSelectivity
         )
    {
        appendTraceString
            (
             "Cost of conglomerate " + reportConglomerateDescriptor( cd ) +
             " scan for table number " + tableNumber + " is : "
             );
        appendTraceString( costEstimate.toString() );
        appendTraceString
            (
             "\tNumber of extra first column predicates is : " + numExtraFirstColumnPreds +
             ", extra first column selectivity is : " + extraFirstColumnSelectivity
             );
        appendTraceString
            (
             "\tNumber of extra start/stop predicates is : " + numExtraStartStopPreds +
             ", extra start/stop selectivity is : " + extraStartStopSelectivity
             );
        appendTraceString
            (
             "\tNumber of start/stop statistics predicates is : " + startStopPredCount +
             ", statistics start/stop selectivity is : " + statStartStopSelectivity
             );
        appendTraceString
            (
             "\tNumber of extra qualifiers is : " + numExtraQualifiers +
             ", extra qualifier selectivity is : " + extraQualifierSelectivity
             );
        appendTraceString
            (
             "\tNumber of extra non-qualifiers is : " + numExtraNonQualifiers +
             ", extra non-qualifier selectivity is : " + extraNonQualifierSelectivity
             );
    }

    public  void    traceCostIncludingCompositeSelectivityFromStats( CostEstimate cost, int tableNumber )
    {
        appendTraceString( reportCostIncluding( "selectivity from statistics", cost, tableNumber ) );
    }

    public  void    traceCompositeSelectivityFromStatistics( double statCompositeSelectivity )
    {
        appendTraceString( "Selectivity from statistics found. It is " + statCompositeSelectivity );
    }

    public  void    traceCostIncludingStatsForIndex( CostEstimate cost, int tableNumber )
    {
        appendTraceString( reportCostIncluding( "statistics for index being considered", cost, tableNumber ) );
    }

    public  void    printToWriter( PrintWriter out )
    {
        out.println( _buffer.toString() );
    }
    
    ////////////////////////////////////////////////////////////////////////
    //
    //	REPORTING MINIONS
    //
    ////////////////////////////////////////////////////////////////////////

	private String reportJoinOrder
        (
         String prefix,
         boolean addJoinOrderNumber,
         int joinOrderNumber,
         int joinPosition,
         int[] joinOrder,
         JBitSet    assignedTableMap
         )
	{
        StringBuilder joinOrderString = new StringBuilder();
        joinOrderString.append(prefix);

		for (int i = 0; i <= joinPosition; i++)
		{
			joinOrderString.append(" ").append(joinOrder[i]);
		}
		if (addJoinOrderNumber)
		{
			joinOrderString.append(" ").append(joinOrderNumber);
		}

        joinOrderString.append(" with assignedTableMap = ").append(assignedTableMap).append("\n\n");
        return joinOrderString.toString();
	}

	private String reportConglomerateDescriptor( ConglomerateDescriptor cd )
	{
		if (SanityManager.DEBUG)
		{
			return cd.toString();
		}

		String		keyString = "";
		String[]	columnNames = cd.getColumnNames();

		if (cd.isIndex() && columnNames != null )
		{
			IndexRowGenerator irg = cd.getIndexDescriptor();

			int[] keyColumns = irg.baseColumnPositions();

			keyString = ", key columns = {" + columnNames[keyColumns[0] - 1];
			for (int index = 1; index < keyColumns.length; index++)
			{
				keyString = keyString + ", " + columnNames[keyColumns[index] - 1];
			}
			keyString = keyString + "}";
		}

		return "CD: conglomerateNumber = " + cd.getConglomerateNumber() +
			   " name = " + cd.getConglomerateName() +
			   " uuid = " + cd.getUUID() +
			   " indexable = " + cd.isIndex() +
			   keyString;
	}
    
	private String reportCostForTable( Object cost, int tableNumber )
	{
		return cost + " for table " + tableNumber;
	}

	private String reportCostIncluding( String selectivityType, CostEstimate cost, int tableNumber )
	{
		return
			"Cost including extra " + selectivityType +
			" start/stop selectivity is : " +
			reportCostForTable( cost, tableNumber );
	}

    /** Append a string to the optimizer trace */
    private void    appendTraceString( String traceString )
    {
        _buffer.append( traceString );
        _buffer.append( "\n" );
    }

}
