/* This file is part of VoltDB.
* Copyright (C) 2008-2017 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.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) {
assert(pn != null);
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 AbstractPlanNode loadPlanFromFile(String path, ArrayList<String> getsql) {
BufferedReader reader;
try {
reader = new BufferedReader(new FileReader(path));
}
catch (FileNotFoundException e1) {
e1.printStackTrace();
String message = "ERROR: Plan file " + path + " doesn't exist.\n" +
"Use -s (the Compile/Save option) or 'ant plannertestrefresh'" +
" ' to generate plans to the baseline directory.\n";
System.err.print(message);
try {
m_reportWriter.write(message);
}
catch (IOException e2) {
e2.printStackTrace();
}
return null;
}
try {
String json = "";
try {
String line = reader.readLine();
getsql.add(line);
while((line = reader.readLine()) != null) {
json += line;
}
}
catch (IOException e2) {
e2.printStackTrace();
return null;
}
try {
PlanNodeTree pnt = new PlanNodeTree();
JSONObject jobj = new JSONObject(json);
Database db = s_singleton.getDatabase();
pnt.loadFromJSONPlan(jobj, db);
return pnt.getRootPlanNode();
}
catch (JSONException e3) {
e3.printStackTrace();
System.out.println("Failed on input from file: " + path +
" with JSON text: \n'" + json + "'");
return null;
}
}
finally {
try {
reader.close();
}
catch (IOException e2) {
e2.printStackTrace();
}
}
}
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];
}
// If one compilation fails, try subsequent ones.
// This avoids cascading "file-not-found" errors.
try {
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(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));
}
} catch (PlanningErrorException ex) {
System.err.printf("Planning error, line %d: %s\n", i, ex.getMessage());
}
}
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");
AbstractPlanNode pn1 = null;
AbstractPlanNode pn2 = 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>();
pn1 = loadPlanFromFile(m_baselinePath + config + ".plan" + i, getsql);
if (pn1 == null) {
continue;
}
baseStmt = getsql.get(0);
// 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;
}
pn2 = loadPlanFromFile(m_workPath + config + ".plan" + i, getsql);
if (pn2 == null) {
continue;
}
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>();
if (max == 0) {
messages.add("0 scan statement");
}
else {
AbstractScanPlanNode spn1 = null;
AbstractScanPlanNode spn2 = null;
if (size1 != size2) {
intdiffPair.set(size1, size2);
messages.add("Scan time diff : " + "\n" + intdiffPair.toString() +
"\nSQL 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);
if (noDiff1 && noDiff2) {
return true;
}
if (print) {
for (String msg : m_diffMessages) {
System.out.println(msg);
}
}
return false;
}
}