/* 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() > 1 ){
str1 = ( str1.subSequence(0, str1.length()-2) ).toString();
}
if( str2.length() > 1 ){
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;
}
}
}