/* * Copyright (c) 2016 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2019-07-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ #include #include #include #include #include #include #include #include #include #define MYSQL_COM_QUIT COM_QUIT #define MYSQL_COM_INIT_DB COM_INIT_DB #define MYSQL_COM_CHANGE_USER COM_CHANGE_USER #include #include #include #include #include "testreader.hh" using std::cerr; using std::cin; using std::cout; using std::endl; using std::ifstream; using std::istream; using std::map; using std::ostream; using std::string; using std::stringstream; namespace { char USAGE[] = "usage: compare [-r count] [-d] [-1 classfier1] [-2 classifier2] " "[-A args] [-B args] [-v [0..2]] [-s statement]|[file]]\n\n" "-r redo the test the specified number of times; 0 means forever, default is 1\n" "-d don't stop after first failed query\n" "-1 the first classifier, default qc_mysqlembedded\n" "-2 the second classifier, default qc_sqlite\n" "-A arguments for the first classifier\n" "-B arguments for the second classifier\n" "-s compare single statement\n" "-S strict, also require that the parse result is identical\n" "-v 0, only return code\n" " 1, query and result for failed cases\n" " 2, all queries, and result for failed cases\n" " 3, all queries and all results\n"; enum verbosity_t { VERBOSITY_MIN, VERBOSITY_NORMAL, VERBOSITY_EXTENDED, VERBOSITY_MAX }; struct State { bool query_printed; string query; verbosity_t verbosity; bool result_printed; bool stop_at_error; bool strict; size_t line; size_t n_statements; size_t n_errors; struct timespec time1; struct timespec time2; string indent; } global = { false, // query_printed "", // query VERBOSITY_NORMAL, // verbosity false, // result_printed true, // stop_at_error false, // strict 0, // line 0, // n_statements 0, // n_errors { 0, 0 }, // time1 { 0, 0}, // time2 "" // indent }; ostream& operator << (ostream& out, qc_parse_result_t x) { switch (x) { case QC_QUERY_INVALID: out << "QC_QUERY_INVALID"; break; case QC_QUERY_TOKENIZED: out << "QC_QUERY_TOKENIZED"; break; case QC_QUERY_PARTIALLY_PARSED: out << "QC_QUERY_PARTIALLY_PARSED"; break; case QC_QUERY_PARSED: out << "QC_QUERY_PARSED"; break; default: out << "static_cast(" << (int)x << ")"; } return out; } GWBUF* create_gwbuf(const string& s) { size_t len = s.length(); size_t payload_len = len + 1; size_t gwbuf_len = MYSQL_HEADER_LEN + payload_len; GWBUF* gwbuf = gwbuf_alloc(gwbuf_len); *((unsigned char*)((char*)GWBUF_DATA(gwbuf))) = payload_len; *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 1)) = (payload_len >> 8); *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 2)) = (payload_len >> 16); *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 3)) = 0x00; *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 4)) = 0x03; memcpy((char*)GWBUF_DATA(gwbuf) + 5, s.c_str(), len); return gwbuf; } QUERY_CLASSIFIER* load_classifier(const char* name) { bool loaded = false; size_t len = strlen(name); char libdir[len + 1]; sprintf(libdir, "../%s", name); set_libdir(strdup(libdir)); QUERY_CLASSIFIER *pClassifier = qc_load(name); if (!pClassifier) { cerr << "error: Could not load classifier " << name << "." << endl; } return pClassifier; } QUERY_CLASSIFIER* get_classifier(const char* zName, const char* zArgs) { QUERY_CLASSIFIER* pClassifier = load_classifier(zName); if (pClassifier) { if ((pClassifier->qc_setup(zArgs) != QC_RESULT_OK) || ((pClassifier->qc_process_init() != QC_RESULT_OK))) { cerr << "error: Could not setup or init classifier " << zName << "." << endl; qc_unload(pClassifier); pClassifier = 0; } } return pClassifier; } void put_classifier(QUERY_CLASSIFIER* pClassifier) { if (pClassifier) { pClassifier->qc_process_end(); qc_unload(pClassifier); } } bool get_classifiers(const char* zName1, const char* zArgs1, QUERY_CLASSIFIER** ppClassifier1, const char* zName2, const char* zArgs2, QUERY_CLASSIFIER** ppClassifier2) { bool rc = false; QUERY_CLASSIFIER* pClassifier1 = get_classifier(zName1, zArgs1); if (pClassifier1) { QUERY_CLASSIFIER* pClassifier2 = get_classifier(zName2, zArgs2); if (pClassifier2) { *ppClassifier1 = pClassifier1; *ppClassifier2 = pClassifier2; rc = true; } else { put_classifier(pClassifier1); } } return rc; } void put_classifiers(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2) { put_classifier(pClassifier1); put_classifier(pClassifier2); } void report_query() { cout << "(" << global.line << "): " << global.query << endl; global.query_printed = true; } void report(bool success, const string& s) { if (success) { if (global.verbosity >= VERBOSITY_NORMAL) { if (global.verbosity >= VERBOSITY_EXTENDED) { if (!global.query_printed) { report_query(); } if (global.verbosity >= VERBOSITY_MAX) { cout << global.indent << s << endl; global.result_printed = true; } } } } else { if (global.verbosity >= VERBOSITY_NORMAL) { if (!global.query_printed) { report_query(); } cout << global.indent << s << endl; global.result_printed = true; } } } static timespec timespec_subtract(const timespec& later, const timespec& earlier) { timespec result = { 0, 0 }; ss_dassert((later.tv_sec > earlier.tv_sec) || ((later.tv_sec == earlier.tv_sec) && (later.tv_nsec > earlier.tv_nsec))); if (later.tv_nsec >= earlier.tv_nsec) { result.tv_sec = later.tv_sec - earlier.tv_sec; result.tv_nsec = later.tv_nsec - earlier.tv_nsec; } else { result.tv_sec = later.tv_sec - earlier.tv_sec - 1; result.tv_nsec = 1000000000 + later.tv_nsec - earlier.tv_nsec; } return result; } static void update_time(timespec* pResult, timespec& start, timespec& finish) { timespec difference = timespec_subtract(finish, start); long nanosecs = pResult->tv_nsec + difference.tv_nsec; if (nanosecs > 1000000000) { pResult->tv_sec += 1; pResult->tv_nsec += (nanosecs - 1000000000); } else { pResult->tv_nsec = nanosecs; } pResult->tv_sec += difference.tv_sec; } bool compare_parse(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_parse : "; struct timespec start; struct timespec finish; clock_gettime(CLOCK_MONOTONIC_RAW, &start); int32_t rv1; pClassifier1->qc_parse(pCopy1, &rv1); clock_gettime(CLOCK_MONOTONIC_RAW, &finish); update_time(&global.time1, start, finish); clock_gettime(CLOCK_MONOTONIC_RAW, &start); int32_t rv2; pClassifier2->qc_parse(pCopy2, &rv2); clock_gettime(CLOCK_MONOTONIC_RAW, &finish); update_time(&global.time2, start, finish); stringstream ss; ss << HEADING; if (rv1 == rv2) { ss << "Ok : " << static_cast(rv1); success = true; } else { if (global.strict) { ss << "ERR: "; } else { ss << "INF: "; success = true; } ss << static_cast(rv1) << " != " << static_cast(rv2); } report(success, ss.str()); return success; } bool compare_get_type(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_get_type_mask : "; uint32_t rv1; pClassifier1->qc_get_type_mask(pCopy1, &rv1); uint32_t rv2; pClassifier2->qc_get_type_mask(pCopy2, &rv2); stringstream ss; ss << HEADING; if (rv1 == rv2) { char* types = qc_typemask_to_string(rv1); ss << "Ok : " << types; free(types); success = true; } else { uint32_t rv1b = rv1; if (rv1b & QUERY_TYPE_WRITE) { rv1b &= ~(uint32_t)QUERY_TYPE_READ; } uint32_t rv2b = rv2; if (rv2b & QUERY_TYPE_WRITE) { rv2b &= ~(uint32_t)QUERY_TYPE_READ; } if (rv1b & QUERY_TYPE_READ) { rv1b &= ~(uint32_t)QUERY_TYPE_LOCAL_READ; } if (rv2b & QUERY_TYPE_READ) { rv2b &= ~(uint32_t)QUERY_TYPE_LOCAL_READ; } char* types1 = qc_typemask_to_string(rv1); char* types2 = qc_typemask_to_string(rv2); if (rv1b == rv2b) { ss << "WRN: " << types1 << " != " << types2; success = true; } else { ss << "ERR: " << types1 << " != " << types2; } free(types1); free(types2); } report(success, ss.str()); return success; } bool compare_get_operation(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_get_operation : "; int32_t rv1; pClassifier1->qc_get_operation(pCopy1, &rv1); int32_t rv2; pClassifier2->qc_get_operation(pCopy2, &rv2); stringstream ss; ss << HEADING; if (rv1 == rv2) { ss << "Ok : " << qc_op_to_string(static_cast(rv1)); success = true; } else { ss << "ERR: " << qc_op_to_string(static_cast(rv1)) << " != " << qc_op_to_string(static_cast(rv2)); } report(success, ss.str()); return success; } bool compare_get_created_table_name(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_get_created_table_name: "; char* rv1; pClassifier1->qc_get_created_table_name(pCopy1, &rv1); char* rv2; pClassifier2->qc_get_created_table_name(pCopy2, &rv2); stringstream ss; ss << HEADING; if ((!rv1 && !rv2) || (rv1 && rv2 && (strcmp(rv1, rv2) == 0))) { ss << "Ok : " << (rv1 ? rv1 : "NULL"); success = true; } else { ss << "ERR: " << (rv1 ? rv1 : "NULL") << " != " << (rv2 ? rv2 : "NULL"); } report(success, ss.str()); free(rv1); free(rv2); return success; } bool compare_is_drop_table_query(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_is_drop_table_query : "; int32_t rv1; pClassifier1->qc_is_drop_table_query(pCopy1, &rv1); int32_t rv2; pClassifier2->qc_is_drop_table_query(pCopy2, &rv2); stringstream ss; ss << HEADING; if (rv1 == rv2) { ss << "Ok : " << static_cast(rv1); success = true; } else { ss << "ERR: " << static_cast(rv1) << " != " << static_cast(rv2); } report(success, ss.str()); return success; } bool compare_strings(const char* const* strings1, const char* const* strings2, int n) { for (int i = 0; i < n; ++i) { const char* s1 = strings1[i]; const char* s2 = strings2[i]; if (strcmp(s1, s2) != 0) { return false; } } return true; } void free_strings(char** strings, int n) { if (strings) { for (int i = 0; i < n; ++i) { free(strings[i]); } free(strings); } } void print_names(ostream& out, const char* const* strings, int n) { if (strings) { for (int i = 0; i < n; ++i) { out << strings[i]; if (i < n - 1) { out << ", "; } } } else { out << "NULL"; } } bool compare_get_table_names(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2, bool full) { bool success = false; const char* HEADING; if (full) { HEADING = "qc_get_table_names(full) : "; } else { HEADING = "qc_get_table_names : "; } int n1 = 0; int n2 = 0; char** rv1; pClassifier1->qc_get_table_names(pCopy1, full, &rv1, &n1); char** rv2; pClassifier2->qc_get_table_names(pCopy2, full, &rv2, &n2); // The order need not be the same, so let's compare a set. std::set names1; std::set names2; if (rv1) { std::copy(rv1, rv1 + n1, inserter(names1, names1.begin())); } if (rv2) { std::copy(rv2, rv2 + n2, inserter(names2, names2.begin())); } stringstream ss; ss << HEADING; if ((!rv1 && !rv2) || (names1 == names2)) { if (n1 == n2) { ss << "Ok : "; print_names(ss, rv1, n1); } else { ss << "WRN: "; print_names(ss, rv1, n1); ss << " != "; print_names(ss, rv2, n2); } success = true; } else { ss << "ERR: "; print_names(ss, rv1, n1); ss << " != "; print_names(ss, rv2, n2); } report(success, ss.str()); free_strings(rv1, n1); free_strings(rv2, n2); return success; } bool compare_query_has_clause(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_query_has_clause : "; int32_t rv1; pClassifier1->qc_query_has_clause(pCopy1, &rv1); int32_t rv2; pClassifier2->qc_query_has_clause(pCopy2, &rv2); stringstream ss; ss << HEADING; if (rv1 == rv2) { ss << "Ok : " << static_cast(rv1); success = true; } else { ss << "ERR: " << static_cast(rv1) << " != " << static_cast(rv2); } report(success, ss.str()); return success; } void add_fields(std::set& m, const char* fields) { const char* begin = fields; const char* end = begin; // As long as we have not reached the end. while (*end != 0) { // Walk over everything but whitespace. while (!isspace(*end) && (*end != 0)) { ++end; } // Insert whatever we found. m.insert(string(begin, end - begin)); // Walk over all whitespace. while (isspace(*end) && (*end != 0)) { ++end; } // Move begin to the next non-whitespace character. begin = end; } if (begin != end) { m.insert(string(begin, end - begin)); } } ostream& operator << (ostream& o, const std::set& s) { std::set::iterator i = s.begin(); while (i != s.end()) { o << *i; ++i; if (i != s.end()) { o << " "; } } return o; } bool compare_get_database_names(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_get_database_names : "; int n1 = 0; int n2 = 0; char** rv1; pClassifier1->qc_get_database_names(pCopy1, &rv1, &n1); char** rv2; pClassifier2->qc_get_database_names(pCopy2, &rv2, &n2); stringstream ss; ss << HEADING; if ((!rv1 && !rv2) || ((n1 == n2) && compare_strings(rv1, rv2, n1))) { ss << "Ok : "; print_names(ss, rv1, n1); success = true; } else { ss << "ERR: "; print_names(ss, rv1, n1); ss << " != "; print_names(ss, rv2, n2); } report(success, ss.str()); free_strings(rv1, n1); free_strings(rv2, n2); return success; } bool compare_get_prepare_name(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_get_prepare_name : "; char* rv1; pClassifier1->qc_get_prepare_name(pCopy1, &rv1); char* rv2; pClassifier2->qc_get_prepare_name(pCopy2, &rv2); stringstream ss; ss << HEADING; if ((!rv1 && !rv2) || (rv1 && rv2 && (strcmp(rv1, rv2) == 0))) { ss << "Ok : " << (rv1 ? rv1 : "NULL"); success = true; } else { ss << "ERR: " << (rv1 ? rv1 : "NULL") << " != " << (rv2 ? rv2 : "NULL"); } report(success, ss.str()); free(rv1); free(rv2); return success; } bool operator == (const QC_FIELD_INFO& lhs, const QC_FIELD_INFO& rhs) { bool rv = false; if (lhs.column && rhs.column && (strcasecmp(lhs.column, rhs.column) == 0)) { if (!lhs.table && !rhs.table) { rv = true; } else if (lhs.table && rhs.table && (strcmp(lhs.table, rhs.table) == 0)) { if (!lhs.database && !rhs.database) { rv = true; } else if (lhs.database && rhs.database && (strcmp(lhs.database, rhs.database) == 0)) { rv = true; } } } return rv; } ostream& operator << (ostream& out, const QC_FIELD_INFO& x) { if (x.database) { out << x.database; out << "."; ss_dassert(x.table); } if (x.table) { out << x.table; out << "."; } ss_dassert(x.column); out << x.column; return out; } class QcFieldInfo { public: QcFieldInfo(const QC_FIELD_INFO& info) : m_database(info.database ? info.database : "") , m_table(info.table ? info.table : "") , m_column(info.column ? info.column : "") , m_usage(info.usage) {} bool eq(const QcFieldInfo& rhs) const { return m_database == rhs.m_database && m_table == rhs.m_table && m_column == rhs.m_column && m_usage == rhs.m_usage; } bool lt(const QcFieldInfo& rhs) const { bool rv = false; if (m_database < rhs.m_database) { rv = true; } else if (m_database > rhs.m_database) { rv = false; } else { if (m_table < rhs.m_table) { rv = true; } else if (m_table > rhs.m_table) { rv = false; } else { if (m_column < rhs.m_column) { rv = true; } else if (m_column > rhs.m_column) { rv = false; } else { rv = (m_usage < rhs.m_usage); } } } return rv; } void print(ostream& out) const { if (!m_database.empty()) { out << m_database; out << "."; } if (!m_table.empty()) { out << m_table; out << "."; } out << m_column; out << "("; char* s = qc_field_usage_mask_to_string(m_usage); out << s; free(s); out << ")"; } private: std::string m_database; std::string m_table; std::string m_column; uint32_t m_usage; }; ostream& operator << (ostream& out, const QcFieldInfo& x) { x.print(out); return out; } ostream& operator << (ostream& out, std::set& x) { std::set::iterator i = x.begin(); std::set::iterator end = x.end(); while (i != end) { out << *i++; if (i != end) { out << " "; } } return out; } bool operator < (const QcFieldInfo& lhs, const QcFieldInfo& rhs) { return lhs.lt(rhs); } bool operator == (const QcFieldInfo& lhs, const QcFieldInfo& rhs) { return lhs.eq(rhs); } bool are_equal(const QC_FIELD_INFO* fields1, size_t n_fields1, const QC_FIELD_INFO* fields2, size_t n_fields2) { bool rv = (n_fields1 == n_fields2); if (rv) { size_t i = 0; while (rv && (i < n_fields1)) { rv = *fields1 == *fields2; ++i; } } return rv; } ostream& print(ostream& out, const QC_FIELD_INFO* fields, size_t n_fields) { size_t i = 0; while (i < n_fields) { out << fields[i++]; if (i != n_fields) { out << " "; } } return out; } bool compare_get_field_info(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_get_field_info : "; const QC_FIELD_INFO* infos1; const QC_FIELD_INFO* infos2; uint32_t n_infos1; uint32_t n_infos2; pClassifier1->qc_get_field_info(pCopy1, &infos1, &n_infos1); pClassifier2->qc_get_field_info(pCopy2, &infos2, &n_infos2); stringstream ss; ss << HEADING; int i; std::set f1; f1.insert(infos1, infos1 + n_infos1); std::set f2; f2.insert(infos2, infos2 + n_infos2); if (f1 == f2) { ss << "Ok : "; ss << f1; success = true; } else { ss << "ERR: " << f1 << " != " << f2; } report(success, ss.str()); return success; } class QcFunctionInfo { public: QcFunctionInfo(const QC_FUNCTION_INFO& info) : m_name(info.name) , m_usage(info.usage) { // We want case-insensitive comparisons. std::transform(m_name.begin(), m_name.end(), m_name.begin(), tolower); } bool eq(const QcFunctionInfo& rhs) const { return m_name == rhs.m_name && m_usage == rhs.m_usage; } bool lt(const QcFunctionInfo& rhs) const { bool rv = false; if (m_name < rhs.m_name) { rv = true; } else if (m_name > rhs.m_name) { rv = false; } else { rv = (m_usage < rhs.m_usage); } return rv; } void print(ostream& out) const { out << m_name; out << "("; char* s = qc_field_usage_mask_to_string(m_usage); out << s; free(s); out << ")"; } private: std::string m_name; uint32_t m_usage; }; ostream& operator << (ostream& out, const QcFunctionInfo& x) { x.print(out); return out; } ostream& operator << (ostream& out, std::set& x) { std::set::iterator i = x.begin(); std::set::iterator end = x.end(); while (i != end) { out << *i++; if (i != end) { out << " "; } } return out; } bool operator < (const QcFunctionInfo& lhs, const QcFunctionInfo& rhs) { return lhs.lt(rhs); } bool operator == (const QcFunctionInfo& lhs, const QcFunctionInfo& rhs) { return lhs.eq(rhs); } bool compare_get_function_info(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) { bool success = false; const char HEADING[] = "qc_get_function_info : "; const QC_FUNCTION_INFO* infos1; const QC_FUNCTION_INFO* infos2; uint32_t n_infos1; uint32_t n_infos2; pClassifier1->qc_get_function_info(pCopy1, &infos1, &n_infos1); pClassifier2->qc_get_function_info(pCopy2, &infos2, &n_infos2); stringstream ss; ss << HEADING; int i; std::set f1; f1.insert(infos1, infos1 + n_infos1); std::set f2; f2.insert(infos2, infos2 + n_infos2); if (f1 == f2) { ss << "Ok : "; ss << f1; success = true; } else { ss << "ERR: " << f1 << " != " << f2; } report(success, ss.str()); return success; } bool compare(QUERY_CLASSIFIER* pClassifier1, GWBUF* pBuf1, QUERY_CLASSIFIER* pClassifier2, GWBUF* pBuf2) { int errors = 0; errors += !compare_parse(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_get_type(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_get_operation(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_get_created_table_name(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_is_drop_table_query(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_get_table_names(pClassifier1, pBuf1, pClassifier2, pBuf2, false); errors += !compare_get_table_names(pClassifier1, pBuf1, pClassifier2, pBuf2, true); errors += !compare_query_has_clause(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_get_database_names(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_get_prepare_name(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_get_field_info(pClassifier1, pBuf1, pClassifier2, pBuf2); errors += !compare_get_function_info(pClassifier1, pBuf1, pClassifier2, pBuf2); if (global.result_printed) { cout << endl; } bool success = (errors == 0); uint32_t type_mask1; pClassifier1->qc_get_type_mask(pBuf1, &type_mask1); uint32_t type_mask2; pClassifier2->qc_get_type_mask(pBuf2, &type_mask2); if ((type_mask1 == type_mask2) && ((type_mask1 & QUERY_TYPE_PREPARE_NAMED_STMT) || (type_mask1 & QUERY_TYPE_PREPARE_STMT))) { GWBUF* pPreparable1; pClassifier1->qc_get_preparable_stmt(pBuf1, &pPreparable1); ss_dassert(pPreparable1); GWBUF* pPreparable2; pClassifier2->qc_get_preparable_stmt(pBuf2, &pPreparable2); ss_dassert(pPreparable2); string indent = global.indent; global.indent += string(4, ' '); success = compare(pClassifier1, pPreparable1, pClassifier2, pPreparable2); global.indent = indent; } return success; } bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, const string& s) { GWBUF* pCopy1 = create_gwbuf(s); GWBUF* pCopy2 = create_gwbuf(s); bool success = compare(pClassifier1, pCopy1, pClassifier2, pCopy2); gwbuf_free(pCopy1); gwbuf_free(pCopy2); return success; } inline void ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); } inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); } static void trim(std::string &s) { ltrim(s); rtrim(s); } int run(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, istream& in) { bool stop = false; // Whether we should exit. maxscale::TestReader reader(in); while (!stop && (reader.get_statement(global.query) == maxscale::TestReader::RESULT_STMT)) { global.line = reader.line(); global.query_printed = false; global.result_printed = false; ++global.n_statements; if (global.verbosity >= VERBOSITY_EXTENDED) { // In case the execution crashes, we want the query printed. report_query(); } bool success = compare(pClassifier1, pClassifier2, global.query); if (!success) { ++global.n_errors; if (global.stop_at_error) { stop = true; } } global.query.clear(); } return global.n_errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } int run(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, const string& statement) { global.query = statement; ++global.n_statements; if (global.verbosity >= VERBOSITY_EXTENDED) { // In case the execution crashes, we want the query printed. report_query(); } if (!compare(pClassifier1, pClassifier2, global.query)) { ++global.n_errors; } return global.n_errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } } int main(int argc, char* argv[]) { int rc = EXIT_SUCCESS; const char* zClassifier1 = "qc_mysqlembedded"; const char* zClassifier2 = "qc_sqlite"; const char* zClassifier1Args = NULL; const char* zClassifier2Args = "log_unrecognized_statements=1"; const char* zStatement = NULL; size_t rounds = 1; int v = VERBOSITY_NORMAL; int c; while ((c = getopt(argc, argv, "r:d1:2:v:A:B:s:S")) != -1) { switch (c) { case 'r': rounds = atoi(optarg); break; case 'v': v = atoi(optarg); break; case '1': zClassifier1 = optarg; break; case '2': zClassifier2 = optarg; break; case 'A': zClassifier1Args = optarg; break; case 'B': zClassifier2Args = optarg; break; case 'd': global.stop_at_error = false; break; case 's': zStatement = optarg; break; case 'S': global.strict = true; break; default: rc = EXIT_FAILURE; break; }; } if ((rc == EXIT_SUCCESS) && (v >= VERBOSITY_MIN && v <= VERBOSITY_MAX)) { rc = EXIT_FAILURE; global.verbosity = static_cast(v); int n = argc - (optind - 1); if ((n == 1) || (n == 2)) { set_datadir(strdup("/tmp")); set_langdir(strdup(".")); set_process_datadir(strdup("/tmp")); if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT)) { QUERY_CLASSIFIER* pClassifier1; QUERY_CLASSIFIER* pClassifier2; if (get_classifiers(zClassifier1, zClassifier1Args, &pClassifier1, zClassifier2, zClassifier2Args, &pClassifier2)) { size_t round = 0; bool terminate = false; do { ++round; global.n_statements = 0; global.n_errors = 0; global.query_printed = false; global.result_printed = false; if (zStatement) { rc = run(pClassifier1, pClassifier2, zStatement); } else if (n == 1) { rc = run(pClassifier1, pClassifier2, cin); } else { ss_dassert(n == 2); ifstream in(argv[argc - 1]); if (in) { rc = run(pClassifier1, pClassifier2, in); } else { terminate = true; cerr << "error: Could not open " << argv[argc - 1] << "." << endl; } } cout << "\n" << "Statements: " << global.n_statements << endl << "Errors : " << global.n_errors << endl; if (!terminate && ((rounds == 0) || (round < rounds))) { cout << endl; } } while (!terminate && ((rounds == 0) || (round < rounds))); put_classifiers(pClassifier1, pClassifier2); cout << "\n"; cout << "1st classifier: " << global.time1.tv_sec << "." << global.time1.tv_nsec << endl; cout << "2nd classifier: " << global.time2.tv_sec << "." << global.time2.tv_nsec << endl; } mxs_log_finish(); } else { cerr << "error: Could not initialize log." << endl; } } else { cout << USAGE << endl; } } else { cout << USAGE << endl; } return rc; }