 0c206ff428
			
		
	
	0c206ff428
	
	
	
		
			
			"INTERVAL N <unit>" is now handled as an expression in itself and
as asuch will cause both statements such as
  "SELECT '2008-12-31 23:59:59' + INTERVAL 1 SECOND;"
and
  "select id from db2.t1 where DATE_ADD("2017-06-15", INTERVAL 10 DAY) < "2017-06-15";"
to be handled correctly. The compare test program contains some
heuristic checking, as the the embedded parser will in all cases
report date manipulation as the use of the add_date_interval()
function.
		
	
		
			
				
	
	
		
			1525 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1525 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * 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 <unistd.h>
 | |
| #include <cstdlib>
 | |
| #include <algorithm>
 | |
| #include <fstream>
 | |
| #include <iostream>
 | |
| #include <map>
 | |
| #include <set>
 | |
| #include <string>
 | |
| #include <sstream>
 | |
| #define MYSQL_COM_QUIT        COM_QUIT
 | |
| #define MYSQL_COM_INIT_DB     COM_INIT_DB
 | |
| #define MYSQL_COM_CHANGE_USER COM_CHANGE_USER
 | |
| #include <maxscale/paths.h>
 | |
| #include <maxscale/log_manager.h>
 | |
| #include <maxscale/protocol/mysql.h>
 | |
| #include <maxscale/query_classifier.h>
 | |
| #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<c_parse_result_t>(" << (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, QC_COLLECT_ESSENTIALS, &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, QC_COLLECT_ESSENTIALS, &rv2);
 | |
|     clock_gettime(CLOCK_MONOTONIC_RAW, &finish);
 | |
|     update_time(&global.time2, start, finish);
 | |
| 
 | |
|     stringstream ss;
 | |
|     ss << HEADING;
 | |
| 
 | |
|     if (rv1 == rv2)
 | |
|     {
 | |
|         ss << "Ok : " << static_cast<qc_parse_result_t>(rv1);
 | |
|         success = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (global.strict)
 | |
|         {
 | |
|             ss << "ERR: ";
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             ss << "INF: ";
 | |
|             success = true;
 | |
|         }
 | |
| 
 | |
|         ss << static_cast<qc_parse_result_t>(rv1) << " != " << static_cast<qc_parse_result_t>(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<qc_query_op_t>(rv1));
 | |
|         success = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         ss << "ERR: "
 | |
|            << qc_op_to_string(static_cast<qc_query_op_t>(rv1))
 | |
|            << " != "
 | |
|            << qc_op_to_string(static_cast<qc_query_op_t>(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<bool>(rv1);
 | |
|         success = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         ss << "ERR: " << static_cast<bool>(rv1) << " != " << static_cast<bool>(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<string> names1;
 | |
|     std::set<string> 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<bool>(rv1);
 | |
|         success = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         ss << "ERR: " << static_cast<bool>(rv1) << " != " << static_cast<bool>(rv2);
 | |
|     }
 | |
| 
 | |
|     report(success, ss.str());
 | |
| 
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| void add_fields(std::set<string>& 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<string>& s)
 | |
| {
 | |
|     std::set<string>::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<QcFieldInfo>& x)
 | |
| {
 | |
|     std::set<QcFieldInfo>::iterator i = x.begin();
 | |
|     std::set<QcFieldInfo>::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<QcFieldInfo> f1;
 | |
|     f1.insert(infos1, infos1 + n_infos1);
 | |
| 
 | |
|     std::set<QcFieldInfo> 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;
 | |
|     }
 | |
| 
 | |
|     const std::string& name() const
 | |
|     {
 | |
|         return m_name;
 | |
|     }
 | |
| 
 | |
|     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<QcFunctionInfo>& x)
 | |
| {
 | |
|     std::set<QcFunctionInfo>::iterator i = x.begin();
 | |
|     std::set<QcFunctionInfo>::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);
 | |
| }
 | |
| 
 | |
| void collect_missing_function_names(const std::set<QcFunctionInfo>& one,
 | |
|                                     const std::set<QcFunctionInfo>& other,
 | |
|                                     std::set<std::string>* pNames)
 | |
| {
 | |
|     for (std::set<QcFunctionInfo>::const_iterator i = one.begin(); i != one.end(); ++i)
 | |
|     {
 | |
|         if (other.count(*i) == 0)
 | |
|         {
 | |
|             pNames->insert(i->name());
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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<QcFunctionInfo> f1;
 | |
|     f1.insert(infos1, infos1 + n_infos1);
 | |
| 
 | |
|     std::set<QcFunctionInfo> f2;
 | |
|     f2.insert(infos2, infos2 + n_infos2);
 | |
| 
 | |
|     if (f1 == f2)
 | |
|     {
 | |
|         ss << "Ok : ";
 | |
|         ss << f1;
 | |
|         success = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         std::set<std::string> names1;
 | |
|         collect_missing_function_names(f1, f2, &names1);
 | |
| 
 | |
|         std::set<std::string> names2;
 | |
|         collect_missing_function_names(f2, f1, &names2);
 | |
| 
 | |
|         bool real_error = false;
 | |
| 
 | |
|         // We assume that names1 are from the qc_mysqlembedded and names2 from qc_sqlite.
 | |
|         // The embedded parser reports all date_add(), adddate(), date_sub() and subdate()
 | |
|         // functions as date_add_interval(). Further, all "DATE + INTERVAL ..." cases become
 | |
|         // use of date_add_interval() functions.
 | |
|         for  (std::set<std::string>::iterator i = names1.begin(); i != names1.end(); ++i)
 | |
|         {
 | |
|             if (*i == "date_add_interval")
 | |
|             {
 | |
|                 if ((names2.count("date_add") == 0) &&
 | |
|                     (names2.count("adddate") == 0) &&
 | |
|                     (names2.count("date_sub") == 0) &&
 | |
|                     (names2.count("subdate") == 0) &&
 | |
|                     (names2.count("+") == 0) &&
 | |
|                     (names2.count("-") == 0))
 | |
|                 {
 | |
|                     real_error = true;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 real_error = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (real_error)
 | |
|         {
 | |
|             ss << "ERR: " << f1 << " != " << f2;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             ss << "Ok : " << f1 << " != " << f2;
 | |
|             success = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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<int, int>(std::isspace))));
 | |
| }
 | |
| 
 | |
| inline void rtrim(std::string &s)
 | |
| {
 | |
|     s.erase(std::find_if(s.rbegin(), s.rend(),
 | |
|                          std::not1(std::ptr_fun<int, int>(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<verbosity_t>(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;
 | |
| }
 |