Package org.voltdb.planner

Source Code of org.voltdb.planner.plannerTester$diffPair

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb.planner;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json_voltpatches.JSONArray;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONObject;
import org.voltdb.catalog.Database;
import org.voltdb.plannodes.AbstractPlanNode;
import org.voltdb.plannodes.AbstractScanPlanNode;
import org.voltdb.plannodes.IndexScanPlanNode;
import org.voltdb.plannodes.PlanNodeTree;
import org.voltdb.plannodes.SendPlanNode;
import org.voltdb.types.PlanNodeType;

public class plannerTester {
    private static PlannerTestCase s_singleton = new PlannerTestCase();
    private static String m_workPath = "/tmp/plannertester/";
    private static String m_baselinePath;
    private static String m_fixedBaselinePath = null;
    private static ArrayList<String> m_stmts = new ArrayList<String>();
    private static int m_treeSizeDiff;
    private static boolean m_changedSQL;

    private static boolean m_isCompile = false;
    private static boolean m_isSave = false;
    private static boolean m_isDiff = false;
    private static boolean m_reportExplainedPlan = false;
    private static boolean m_reportDiffExplainedPlan = false;
    private static boolean m_reportSQLStatement = false;
    private static ArrayList<String> m_config = new ArrayList<String>();

    private static int m_numPass;
    private static int m_numFail;
    public static ArrayList<String> m_diffMessages = new ArrayList<String>();
    private static String m_reportPath = "/tmp/";
    private static BufferedWriter m_reportWriter;
    private static ArrayList<String>  m_filters = new ArrayList<String> ();

    public static class diffPair {
        private Object m_first;
        private Object m_second;

        public diffPair( Object first, Object second ) {
            m_first = first;
            m_second = second;
        }

        @Override
        public String toString() {
            String first = ( ( m_first == null ) || ( m_first == "" ) ) ?
                            "[]" : m_first.toString();
            String second = ( m_second == null || ( m_second == "" )) ?
                            "[]" : m_second.toString();
            return "("+first+" => "+second+")";
        }

        public boolean equals() {
            return m_first.equals(m_second);
        }

        public void setFirst( Object first ) {
            m_first = first;
        }

        public void setSecond( Object second ) {
            m_second = second;
        }

        public void set( Object first, Object second ) {
            m_first = first;
            m_second = second;
        }
    }

    public static void main( String[] args ) {
        int numError = 0;
        for(String str : args) {
            if( str.startsWith("-C=")) {
                String subStr = str.split("=")[1];
                String [] configs = subStr.split(",");
                for( String config : configs ) {
                    m_config.add( config.trim() );
                }
            }
            else if( str.startsWith("-sp=")) {
                m_workPath = str.split("=")[1];
                m_workPath = m_workPath.trim();
                if( ! m_workPath.endsWith("/") ) {
                    m_workPath += "/";
                }
            }
            else if( str.startsWith("-b=")) {
                m_fixedBaselinePath = str.split("=")[1];
                m_fixedBaselinePath = m_fixedBaselinePath.trim();
                if( !m_fixedBaselinePath.endsWith("/") ) {
                    m_fixedBaselinePath += "/";
                }
            }
            else if( str.equals("-s") ) {
                m_isCompile = true;
                m_isSave = true;
            }
            else if( str.equals("-sv") ) {
                m_isCompile = true;
                m_isSave = true;
                m_reportExplainedPlan = true;
                m_reportSQLStatement = true;
            }
            else if( str.equals("-d") ) {
                m_isCompile = true;
                m_isDiff = true;
            }
            else if( str.equals("-dv") ) {
                m_isCompile = true;
                m_isDiff = true;
                m_reportDiffExplainedPlan = true;
                m_reportSQLStatement = true;
            }
            else if( str.startsWith("-r=") ){
                m_reportPath = str.split("=")[1];
                m_reportPath = m_reportPath.trim();
                if( !m_reportPath.endsWith("/") ) {
                    m_reportPath += "/";
                }
            }
            else if( str.equals("-re") ){
                m_reportExplainedPlan = true;
                m_reportDiffExplainedPlan = true;
            }
            else if( str.equals("-rs") ){
                m_reportSQLStatement = true;
            }
            else if( str.startsWith("-i=") ) {
                m_filters.add(str.split("=")[1] );
            }
            else if( str.startsWith("-help") || str.startsWith("-h") ){
                printUsage();
                System.exit(0);
            }
            else {
                System.out.println("Illegal command line argument: " + str);
                printUsage();
                System.exit(0);
            }
        }

        if( !new File(m_workPath).exists() ) {
            new File(m_workPath).mkdirs();
        }

        if( m_isCompile ) {
            for( String config : m_config ) {
                try {
                    configCompileSave(config, m_isSave);
                } catch (Exception e) {
                    e.printStackTrace();
                    ++numError;
                }
            }
        }
        if( m_isDiff ) {
            if( !new File(m_reportPath).exists() ) {
                new File(m_reportPath).mkdirs();
            }
            try {
                m_reportWriter = new BufferedWriter(new FileWriter( m_reportPath+"plannerTester.report" ));
            } catch (IOException e1) {
                System.out.println(e1.getMessage());
                System.exit(-1);
            }
            for( String config : m_config ) {
                try {
                    configDiff(config);
                } catch (Exception e) {
                    e.printStackTrace();
                    ++numError;
                }
            }
            int numTest = m_numPass + m_numFail;
            String summary = "\nTest: "+numTest+"\nPass: "+m_numPass+"\nFail: "+
                    m_numFail+"\nError: "+numError+"\n";
            System.out.print(summary);
            try {
                m_reportWriter.write(summary);
                m_reportWriter.flush();
                m_reportWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("Report file created at "+m_reportPath+"plannerTester.report");
        }
        if( numError != 0 ) {
            System.exit(2);
        }
        if( m_numFail != 0 ) {
            System.exit(1);
        }
        System.exit(0);
    }

    public static void printUsage() {
        System.out.println("-C=configDir1[,configDir2,...]" +
                        "\nSpecify the path to each config file.\n");
        System.out.println("-sp=savePath" +
                        "\nspecify path for newly generated plan files.\n");
        System.out.println("-b=baselinePath" +
                        "\nspecify path for ALL baseline reference plan files. Omit for separate <configDir>/baseline dirs\n");
        System.out.println("-r=reportFilePath " +
                        "\nSpecify report file path, default will be ./reports, report file name is plannerTester.report.\n");
        System.out.println("-i=ignorePattern" +
                        "\nSpecify a pattern to ignore, the pattern will not be recorded in the report file.\n");
        System.out.println("-s" +
                        "\nSave compiled queries in the baseline path (<config>/baseline by default.\n");
        System.out.println("-d" +
                        "\nDo the diff between plan files in baseline and the current ones.\n");
        System.out.println("-re" +
                        "\nOutput explained plan along with diff.\n");
        System.out.println("-rs" +
                        "\nOutput sql statement along with diff.\n");
        System.out.println("-dv" +
                        "\nSame as -d -re -rs.\n");
        System.out.println("-sv" +
                        "\nSame as -s -re -rs.\n");

    }

    public static boolean setUp( String config ) throws Exception {
        m_baselinePath = (m_fixedBaselinePath != null) ? m_fixedBaselinePath : (config + "/baseline/");
        String ddlFilePath = null;
        m_stmts.clear();
        BufferedReader reader = new BufferedReader( new FileReader(config + "/config") );
        String line = null;
        while( ( line = reader.readLine() ) != null ) {
            if( line.startsWith("#") ) {
                continue;
            }
            else if( line.equalsIgnoreCase("DDL:")) {
                if ( ( line = reader.readLine() ) == null ) {
                    break;
                }
                ddlFilePath = new File( line ).getCanonicalPath();
            }
            else if( line.equalsIgnoreCase("SQL:")) {
                boolean atEof = false;
                while (true) {
                    if ( ( line = reader.readLine() ) == null ) {
                        atEof = true;
                        break;
                    }
                    if( line.startsWith("#") ) {
                        continue;
                    }
                    if (line.length() <= 6 ) {
                        break;
                    }
                    if( line.startsWith("JOIN:") ) {
                        // These lines have three parts JOIN:<joinOrder>:<query>
                        if ( line.split(":").length != 3 ) {
                            System.err.println("Config file syntax error : ignoring line: " + line);
                        }
                    }
                    m_stmts.add( line );
                }
                if (atEof) {
                    break;
                }
            }
            // This section of the config file is optional, deprecated, and ignored.
            else if( line.equalsIgnoreCase("Partition Columns:") ) {
                if ( ( line = reader.readLine() ) == null ) {
                    break;
                }
            }
            else if ( ! line.trim().equals("")) {
                System.err.println("Config file syntax error : ignoring line: " + line);
            }
        }
        boolean success = true;
        if (ddlFilePath == null ) {
            System.err.println("ERROR: syntax error : config file '" + config + "/config' has no 'DDL:' section");
            success = false;
        }
        if (m_stmts.isEmpty()) {
            System.err.println("ERROR: syntax error : config file '" + config + "/config' has no 'SQL:' section or SQL statements");
            success = false;
        }
        if (success) {
            File ddlFile = new File(ddlFilePath);
            URL ddlURL = ddlFile.toURI().toURL();
            s_singleton.setupSchema(ddlURL, config, false);
        }
        return success;
    }

    public static void setUpForTest(String pathDDL, String config)
    {
        try {
            File ddlFile = new File(pathDDL);
            URL ddlURL = ddlFile.toURI().toURL();
            s_singleton.setupSchema(ddlURL, config, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static List<AbstractPlanNode> testCompile(String sql) throws Exception
    {
        return s_singleton.compileToFragments(sql);
    }

    public static void writePlanToFile( AbstractPlanNode pn, String pathToDir, String fileName, String sql) {
        if( pn == null ) {
            System.err.println("the plan node is null, nothing to write");
            return;
        }
        PlanNodeTree pnt = new PlanNodeTree( pn );
        String prettyJson = pnt.toJSONString();
        if( !new File(pathToDir).exists() ) {
            new File(pathToDir).mkdirs();
        }
        try {
            BufferedWriter writer = new BufferedWriter( new FileWriter( pathToDir+fileName ) );
            writer.write( sql );
            writer.write( "\n" );
            writer.write( prettyJson );
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static PlanNodeTree loadPlanFromFile( String path, ArrayList<String> getsql ) throws FileNotFoundException {
        PlanNodeTree pnt = new PlanNodeTree();
        String prettyJson = "";
        String line = null;
        BufferedReader reader = new BufferedReader( new FileReader( path ));
        try {
            getsql.add( reader.readLine() );
            while( (line = reader.readLine() ) != null ){
                line = line.trim();
                prettyJson += line;
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        JSONObject jobj;
        try {
            jobj = new JSONObject( prettyJson );
            JSONArray jarray =  jobj.getJSONArray("PLAN_NODES");
            Database db = s_singleton.getDatabase();
            pnt.loadFromJSONArray(jarray, db);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return pnt;
    }

    public static ArrayList< AbstractPlanNode > getJoinNodes( ArrayList<AbstractPlanNode> pnlist ) {
        ArrayList< AbstractPlanNode > joinNodeList = new ArrayList<AbstractPlanNode>();

        for( AbstractPlanNode pn : pnlist ) {
            if( pn.getPlanNodeType().equals(PlanNodeType.NESTLOOP) ||
                    pn.getPlanNodeType().equals(PlanNodeType.NESTLOOPINDEX) ) {
                joinNodeList.add(pn);
            }
        }
        return joinNodeList;
    }

    private static void configCompileSave(String config, boolean isSave) throws Exception {
        if ( ! setUp( config )) {
            return;
        }
        int size = m_stmts.size();
        for( int i = 0; i < size; i++ ) {
            String query = m_stmts.get(i);
            String joinOrder = null;
            if( query.startsWith("JOIN:") ) {
                String[] splitLine = query.split(":");
                joinOrder = splitLine[1];
                query = splitLine[2];
            }
            List<AbstractPlanNode> pnList = s_singleton.compileWithJoinOrderToFragments(query, joinOrder);
            AbstractPlanNode pn = pnList.get(0);
            if( pnList.size() == 2 ){//multi partition query plan
                assert( pnList.get(1) instanceof SendPlanNode );
                if( ! pn.reattachFragment( ( SendPlanNode) pnList.get(1) ) ) {
                    System.err.println( "Receive plan node not found in reattachFragment." );
                }
            }
            writePlanToFile(pn, m_workPath, config+".plan"+i, m_stmts.get(i) );
            if (isSave) {
                writePlanToFile(pn, m_baselinePath, config+".plan"+i, m_stmts.get(i) );
            }
        }
        if (isSave) {
            System.out.println("Baseline files generated at: " + m_baselinePath);
        }
    }

    //parameters : path to baseline and the new plans
    //size : number of total files in the baseline directory
    public static void configDiff(String config) throws Exception {
        if ( ! setUp( config )) {
            return;
        }
        m_reportWriter.write( "===================================================================Begin "+config+"\n" );
        PlanNodeTree pnt1 = null;
        PlanNodeTree pnt2 = null;
        int size = m_stmts.size();
        String baseStmt = null;
        for( int i = 0; i < size; i++ ){
            //* Enable for debug:*/ System.out.println("DEBUG: comparing " + m_savePlanPath+config+".plan"+i + " and " + m_baselinePath+config+".plan"+i);
            ArrayList<String> getsql = new ArrayList<String>();
            try {
                pnt1 = loadPlanFromFile( m_baselinePath+config+".plan"+i, getsql );
                baseStmt = getsql.get(0);
            } catch (FileNotFoundException e) {
                String message = "ERROR: Plan file "+m_baselinePath+config+".plan"+i+" doesn't exist. Use -s (the Compile/Save option) or 'ant plannertestrefresh -Dconfig="+config + " ' to generate plans to the baseline directory.\n";
                System.err.print(message);
                m_reportWriter.write(message);
                System.exit(1);
            }

            //if sql stmts not consistent
            if( !baseStmt.equalsIgnoreCase( m_stmts.get(i)) ) {
                diffPair strPair = new diffPair( baseStmt, m_stmts.get(i) );
                m_reportWriter.write("Statement "+i+" of "+config+"/config:\n SQL statement is not consistent with the one in baseline :"+"\n"+
                        strPair.toString()+"\n");
                m_numFail++;
                continue;
            }

            try{
                pnt2  = loadPlanFromFile( m_workPath+config+".plan"+i, getsql );
            } catch (FileNotFoundException e) {
                String message = "ERROR: Temporary plan file "+m_workPath+config+".plan"+i+" was not generated.\n";
                System.err.print(message);
                m_reportWriter.write(message);
                System.exit(1);
            }
            AbstractPlanNode pn1 = pnt1.getRootPlanNode();
            AbstractPlanNode pn2 = pnt2.getRootPlanNode();


            if( diff( pn1, pn2, false ) ) {
                m_numPass++;
                if( m_reportExplainedPlan ) {
                    m_reportWriter.write( "SQL statement:\n"+m_stmts.get(i)+"\n");
                    m_reportWriter.write("\nExplained plan:\n"+pn2.toExplainPlanString()+"\n");
                }
            } else {
                m_numFail++;
                m_reportWriter.write( "Statement "+i+" of "+config+": \n" );
                //TODO add more logic to determine which plan is better
                if( !m_changedSQL ){
                    if( m_treeSizeDiff < 0 ){
                        m_reportWriter.write( "Old plan might be better\n" );
                    }
                    else if( m_treeSizeDiff > 0 ) {
                        m_reportWriter.write( "New plan might be better\n" );
                    }
                }

                for( String msg : m_diffMessages ) {
                    boolean isIgnore = false;
                    for( String filter : m_filters ) {
                        if( msg.contains( filter ) ) {
                            isIgnore = true;
                            break;
                        }
                    }
                    if( !isIgnore )
                        m_reportWriter.write( msg+"\n\n" );
                }
                if( m_reportSQLStatement ) {
                    m_reportWriter.write( "SQL statement:\n"+baseStmt+"\n==>\n"+m_stmts.get(i)+"\n");
                }

                if( m_reportDiffExplainedPlan ) {
                    m_reportWriter.write("\nExplained plan:\n"+pn1.toExplainPlanString()+"\n==>\n"+pn2.toExplainPlanString()+"\n");
                }

                m_reportWriter.write("Path to the config file :"+config+"\n" +
                                     "Path to the baseline file :"+m_baselinePath+config+".plan"+i+"\n" +
                                     "Path to the current plan file :"+m_workPath+config+".plan"+i +
                                     "\n\n----------------------------------------------------------------------\n");
            }
        }
        m_reportWriter.write("===================================================================" +
                            "End "+config+"\n");
        m_reportWriter.flush();
    }

    public static boolean diffInlineAndJoin( AbstractPlanNode oldpn1, AbstractPlanNode newpn2 ) {
        m_treeSizeDiff = 0;
        boolean noDiff = true;
        ArrayList<String> messages = new ArrayList<String>();
        ArrayList<AbstractPlanNode> list1 = oldpn1.getPlanNodeList();
        ArrayList<AbstractPlanNode> list2 = newpn2.getPlanNodeList();
        int size1 = list1.size();
        int size2 = list2.size();
        m_treeSizeDiff = size1 - size2;
        diffPair intdiffPair = new diffPair(0,0);
        diffPair stringdiffPair = new diffPair(null,null);
        if( size1 != size2 ) {
            intdiffPair.set(size1, size2);
            messages.add( "Plan tree size diff: "+intdiffPair.toString() );
        }
        Map<String,ArrayList<Integer>> planNodesPosMap1 = new LinkedHashMap<String,ArrayList<Integer>> ();
        Map<String,ArrayList<AbstractPlanNode>> inlineNodesPosMap1 = new LinkedHashMap<String,ArrayList<AbstractPlanNode>> ();

        Map<String,ArrayList<Integer>> planNodesPosMap2 = new LinkedHashMap<String,ArrayList<Integer>> ();
        Map<String,ArrayList<AbstractPlanNode>> inlineNodesPosMap2 = new LinkedHashMap<String,ArrayList<AbstractPlanNode>> ();

        fetchPositionInfoFromList(list1, planNodesPosMap1, inlineNodesPosMap1);
        fetchPositionInfoFromList(list2, planNodesPosMap2, inlineNodesPosMap2);

        planNodePositionDiff( planNodesPosMap1, planNodesPosMap2, messages );
        inlineNodePositionDiff( inlineNodesPosMap1, inlineNodesPosMap2, messages );

        //join nodes diff
        ArrayList<AbstractPlanNode> joinNodes1 = getJoinNodes( list1 );
        ArrayList<AbstractPlanNode> joinNodes2 = getJoinNodes( list2 );
        size1 = joinNodes1.size();
        size2 = joinNodes2.size();
        if( size1 != size2 ) {
            intdiffPair.set( size1 , size2);
            messages.add( "Join Nodes Number diff:\n"+intdiffPair.toString()+"\nSQL statement might be changed.");
            m_changedSQL = true;
            String str1 = "";
            String str2 = "";
            for( AbstractPlanNode pn : joinNodes1 ) {
                str1 = str1 + pn.toString() + ", ";
            }
            for( AbstractPlanNode pn : joinNodes2 ) {
                str2 = str2 + pn.toString() + ", ";
            }
            if( str1.length() > ){
                str1 = ( str1.subSequence(0, str1.length()-2) ).toString();
            }
            if( str2.length() > ){
                str2 = ( str2.subSequence(0, str2.length()-2) ).toString();
            }
            stringdiffPair.set( str1, str2 );
            messages.add( "Join Node List diff: "+"\n"+stringdiffPair.toString()+"\n");
        }
        else {
            for( int i = 0 ; i < size1 ; i++  ) {
                AbstractPlanNode pn1 = joinNodes1.get(i);
                AbstractPlanNode pn2 = joinNodes2.get(i);
                PlanNodeType pnt1 = pn1.getPlanNodeType();
                PlanNodeType pnt2 = pn2.getPlanNodeType();
                if( !pnt1.equals(pnt2) ) {
                    stringdiffPair.set( pn1.toString(), pn2.toString() );
                    messages.add( "Join Node Type diff:\n"+stringdiffPair.toString());
                }
            }
        }

        for( String msg : messages ) {
            if( msg.contains("diff") || msg.contains("Diff") ) {
                noDiff = false;
                break;
            }
        }
        m_diffMessages.addAll(messages);
        return noDiff;
    }

    private static void fetchPositionInfoFromList( Collection<AbstractPlanNode> list,
            Map<String,ArrayList<Integer>> planNodesPosMap,
            Map<String,ArrayList<AbstractPlanNode>> inlineNodesPosMap ) {
        for( AbstractPlanNode pn : list ) {
            String nodeTypeStr = pn.getPlanNodeType().name();
            if( !planNodesPosMap.containsKey(nodeTypeStr) ) {
                ArrayList<Integer> intList = new ArrayList<Integer>( );
                intList.add( pn.getPlanNodeId() );
                planNodesPosMap.put(nodeTypeStr, intList );
            }
            else{
                planNodesPosMap.get( nodeTypeStr ).add( pn.getPlanNodeId() );
            }
            //walk inline nodes
            for( AbstractPlanNode inlinepn : pn.getInlinePlanNodes().values() ) {
                String inlineNodeTypeStr = inlinepn.getPlanNodeType().name();
                if( !inlineNodesPosMap.containsKey( inlineNodeTypeStr ) ) {
                    ArrayList<AbstractPlanNode> nodeList = new ArrayList<AbstractPlanNode>( );
                    nodeList.add(pn);
                    inlineNodesPosMap.put( inlineNodeTypeStr, nodeList );
                }
                else{
                    inlineNodesPosMap.get( inlineNodeTypeStr ).add( pn );
                }
            }
        }
    }

    private static void planNodePositionDiff( Map<String,ArrayList<Integer>> planNodesPosMap1, Map<String,ArrayList<Integer>> planNodesPosMap2, ArrayList<String> messages ) {
        Set<String> typeWholeSet = new HashSet<String>();
        typeWholeSet.addAll( planNodesPosMap1.keySet() );
        typeWholeSet.addAll( planNodesPosMap2.keySet() );

        for( String planNodeTypeStr : typeWholeSet ) {
            if( ! planNodesPosMap1.containsKey( planNodeTypeStr ) &&  planNodesPosMap2.containsKey( planNodeTypeStr ) ){
                diffPair strPair = new diffPair( null, planNodesPosMap2.get(planNodeTypeStr).toString() );
                messages.add( planNodeTypeStr+" diff: \n"+strPair.toString() );
            }
            else if( planNodesPosMap1.containsKey( planNodeTypeStr ) &&  !planNodesPosMap2.containsKey( planNodeTypeStr ) ) {
                diffPair strPair = new diffPair( planNodesPosMap1.get(planNodeTypeStr).toString(), null );
                messages.add( planNodeTypeStr+" diff: \n"+strPair.toString() );
            }
            else{
                diffPair strPair = new diffPair( planNodesPosMap1.get(planNodeTypeStr).toString(), planNodesPosMap2.get(planNodeTypeStr).toString() );
                if( !strPair.equals() ) {
                    messages.add( planNodeTypeStr+" diff: \n"+strPair.toString() );
                }
            }
        }
    }

    private static void inlineNodePositionDiff( Map<String,ArrayList<AbstractPlanNode>> inlineNodesPosMap1, Map<String,ArrayList<AbstractPlanNode>> inlineNodesPosMap2, ArrayList<String> messages ) {
        Set<String> typeWholeSet = new HashSet<String>();
        typeWholeSet.addAll( inlineNodesPosMap1.keySet() );
        typeWholeSet.addAll( inlineNodesPosMap2.keySet() );

        for( String planNodeTypeStr : typeWholeSet ) {
            if( ! inlineNodesPosMap1.containsKey( planNodeTypeStr ) &&  inlineNodesPosMap2.containsKey( planNodeTypeStr ) ){
                diffPair strPair = new diffPair( null, inlineNodesPosMap2.get(planNodeTypeStr).toString() );
                messages.add( "Inline "+planNodeTypeStr+" diff: \n"+strPair.toString() );
            }
            else if( inlineNodesPosMap1.containsKey( planNodeTypeStr ) &&  !inlineNodesPosMap2.containsKey( planNodeTypeStr ) ) {
                diffPair strPair = new diffPair( inlineNodesPosMap1.get(planNodeTypeStr).toString(), null );
                messages.add( "Inline "+planNodeTypeStr+" diff: \n"+strPair.toString() );
            }
            else{
                diffPair strPair = new diffPair( inlineNodesPosMap1.get(planNodeTypeStr).toString(), inlineNodesPosMap2.get(planNodeTypeStr).toString() );
                if( !strPair.equals() ) {
                    messages.add( "Inline "+planNodeTypeStr+" diff: \n"+strPair.toString() );
                }
            }
        }
    }

    private static void scanNodeDiffModule( int leafID, AbstractScanPlanNode spn1, AbstractScanPlanNode spn2, ArrayList<String> messages ) {
        diffPair stringdiffPair = new diffPair("", "");
        String table1 = "";
        String table2 = "";
        String nodeType1 = "";
        String nodeType2 = "";
        String index1 = "";
        String index2 = "";
        if( spn1 == null && spn2 != null ) {
            table2 = spn2.getTargetTableName();
            nodeType2 = spn2.getPlanNodeType().toString();
            if( nodeType2.equalsIgnoreCase(PlanNodeType.INDEXSCAN.name() ) ) {
                index2 = ((IndexScanPlanNode)spn2).getTargetIndexName();
            }
        }
        else if( spn2 == null && spn1 != null ) {
            table1 = spn1.getTargetTableName();
            nodeType1 = spn1.getPlanNodeType().toString();
            if( nodeType1.equalsIgnoreCase(PlanNodeType.INDEXSCAN.name() ) ) {
                index1 = ((IndexScanPlanNode)spn1).getTargetIndexName();
            }
        }
        //both null is not possible
        else{
            table1 = spn1.getTargetTableName();
            table2 = spn2.getTargetTableName();
            nodeType1 = spn1.getPlanNodeType().toString();
            nodeType2 = spn2.getPlanNodeType().toString();
            if( nodeType1.equalsIgnoreCase(PlanNodeType.INDEXSCAN.name() ) ) {
                index1 = ((IndexScanPlanNode)spn1).getTargetIndexName();
            }
            if( nodeType2.equalsIgnoreCase(PlanNodeType.INDEXSCAN.name() ) ) {
                index2 = ((IndexScanPlanNode)spn2).getTargetIndexName();
            }
        }
        if( !table1.equals(table2) ) {
            stringdiffPair.set( table1.equals("") ? null : nodeType1+" on "+table1,
                                table2.equals("") ? null : nodeType2+" on "+table2 );
            messages.add( "Table diff at leaf "+leafID+":"+"\n"+stringdiffPair.toString());
        }
        else if( !nodeType1.equals(nodeType2) ) {
            stringdiffPair.set(nodeType1+" on "+table1, nodeType2+" on "+table2);
            messages.add("Scan diff at leaf "+leafID+" :"+"\n"+stringdiffPair.toString());
        }
        else if ( nodeType1.equalsIgnoreCase(PlanNodeType.INDEXSCAN.name()) ) {
            if( !index1.equals(index2) ) {
                stringdiffPair.set( index1, index2);
                messages.add("Index diff at leaf "+leafID+" :"+"\n"+stringdiffPair.toString());
            } else {
                messages.add("Same at leaf "+leafID);
            }
        }
        //either index scan using same index or seqscan on same table
        else{
            messages.add("Same at leaf "+leafID);
        }
    }

    public static boolean diffScans( AbstractPlanNode oldpn, AbstractPlanNode newpn ){
        m_changedSQL = false;
        boolean noDiff = true;
        ArrayList<AbstractScanPlanNode> list1 = oldpn.getScanNodeList();
        ArrayList<AbstractScanPlanNode> list2 = newpn.getScanNodeList();
        int size1 = list1.size();
        int size2 = list2.size();
        int max = Math.max(size1, size2);
        int min = Math.min(size1, size2);
        diffPair intdiffPair = new diffPair(0,0);
        ArrayList<String> messages = new ArrayList<String>();
        AbstractScanPlanNode spn1 = null;
        AbstractScanPlanNode spn2 = null;
        if( max == 0 ) {
            messages.add("0 scan statement");
        }
        else {
            if( size1 != size2 ){
                intdiffPair.set(size1, size2);
                messages.add("Scan time diff : "+"\n"+intdiffPair.toString()+"\n"+"SQL statement might be changed");
                m_changedSQL = true;
                for( int i = 0; i < min; i++ ) {
                    spn1 = list1.get(i);
                    spn2 = list2.get(i);
                    scanNodeDiffModule(i, spn1, spn2, messages);
                }
                //lists size are different
                if( size2 < max ) {
                    for( int i = min; i < max; i++ ) {
                        spn1 = list1.get(i);
                        spn2 = null;
                        scanNodeDiffModule(i, spn1, spn2, messages);
                    }
                }
                else if( size1 < max ) {
                    for( int i = min; i < max; i++ ) {
                        spn1 = null;
                        spn2 = list2.get(i);
                        scanNodeDiffModule(i, spn1, spn2, messages);
                    }
                }
            }
            else {
                messages.add( "same leaf size" );
                if( max == 1 ) {
                    messages.add("Single scan plan");
                    spn1 = list1.get(0);
                    spn2 = list2.get(0);
                    scanNodeDiffModule(0, spn1, spn2, messages);
                }
                else {
                    messages.add("Join query");
                    for( int i = 0; i < max; i++ ) {
                        spn1 = list1.get(i);
                        spn2 = list2.get(i);
                        scanNodeDiffModule(i, spn1, spn2, messages);
                    }
                }
            }
        }
        for( String msg : messages ) {
            if( msg.contains("diff") || msg.contains("Diff") ) {
                noDiff = false;
                break;
            }
        }
        m_diffMessages.addAll(messages);
        return noDiff;
    }

    //return true is there are no diff
    //false if there's any diff
    public static boolean diff( AbstractPlanNode oldpn, AbstractPlanNode newpn, boolean print ) {
        m_diffMessages.clear();
        boolean noDiff1 = diffScans(oldpn, newpn);
        boolean noDiff2 = diffInlineAndJoin(oldpn, newpn);
        noDiff1 = noDiff1 && noDiff2;

        if( noDiff1  ) {
            return true;
        }
        else {
            if( print  ) {
                for( String msg : m_diffMessages ) {
                    System.out.println(msg);
                }
            }
            return false;
        }
    }
}
TOP

Related Classes of org.voltdb.planner.plannerTester$diffPair

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.