
- Only types of fixed size used in API interface - The actual function return value specifies whether the parsing process succeeded, while "logical" return values are returned as out arguments. The wrapper function currently ignores the function return value.
1461 lines
34 KiB
C++
1461 lines
34 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/bsl.
|
|
*
|
|
* 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/gwdirs.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;
|
|
} 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
|
|
|
|
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 << s << endl;
|
|
global.result_printed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (global.verbosity >= VERBOSITY_NORMAL)
|
|
{
|
|
if (!global.query_printed)
|
|
{
|
|
report_query();
|
|
}
|
|
|
|
cout << 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 : " << 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 : ";
|
|
|
|
uint32_t rv1;
|
|
pClassifier1->qc_get_type(pCopy1, &rv1);
|
|
uint32_t rv2;
|
|
pClassifier2->qc_get_type(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 compare_get_prepare_operation(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1,
|
|
QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2)
|
|
{
|
|
bool success = false;
|
|
const char HEADING[] = "qc_get_prepare_operation : ";
|
|
|
|
int32_t rv1;
|
|
pClassifier1->qc_get_prepare_operation(pCopy1, &rv1);
|
|
int32_t rv2;
|
|
pClassifier2->qc_get_prepare_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 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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
|
|
{
|
|
ss << "ERR: " << f1 << " != " << f2;
|
|
}
|
|
|
|
report(success, ss.str());
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, const string& s)
|
|
{
|
|
GWBUF* pCopy1 = create_gwbuf(s);
|
|
GWBUF* pCopy2 = create_gwbuf(s);
|
|
|
|
int errors = 0;
|
|
|
|
errors += !compare_parse(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_type(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_operation(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_created_table_name(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_is_drop_table_query(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_table_names(pClassifier1, pCopy1, pClassifier2, pCopy2, false);
|
|
errors += !compare_get_table_names(pClassifier1, pCopy1, pClassifier2, pCopy2, true);
|
|
errors += !compare_query_has_clause(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_database_names(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_prepare_name(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_prepare_operation(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_field_info(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
errors += !compare_get_function_info(pClassifier1, pCopy1, pClassifier2, pCopy2);
|
|
|
|
gwbuf_free(pCopy1);
|
|
gwbuf_free(pCopy2);
|
|
|
|
if (global.result_printed)
|
|
{
|
|
cout << endl;
|
|
}
|
|
|
|
return errors == 0;
|
|
}
|
|
|
|
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;
|
|
}
|