499 lines
10 KiB
C++
499 lines
10 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: 2023-01-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 <maxscale/ccdefs.hh>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <maxscale/modutil.hh>
|
|
#include <maxscale/paths.h>
|
|
#include <maxscale/protocol/mysql.hh>
|
|
#include "../core/internal/query_classifier.hh"
|
|
|
|
using namespace std;
|
|
|
|
namespace
|
|
{
|
|
|
|
enum test_target_t
|
|
{
|
|
TEST_PARSER = 0x1,
|
|
TEST_QC = 0x2,
|
|
TEST_ALL = (TEST_PARSER | TEST_QC)
|
|
};
|
|
|
|
GWBUF* create_gwbuf(const char* zStmt)
|
|
{
|
|
size_t len = strlen(zStmt);
|
|
size_t payload_len = len + 1;
|
|
size_t gwbuf_len = MYSQL_HEADER_LEN + payload_len;
|
|
|
|
GWBUF* pBuf = gwbuf_alloc(gwbuf_len);
|
|
|
|
*((unsigned char*)((char*)GWBUF_DATA(pBuf))) = payload_len;
|
|
*((unsigned char*)((char*)GWBUF_DATA(pBuf) + 1)) = (payload_len >> 8);
|
|
*((unsigned char*)((char*)GWBUF_DATA(pBuf) + 2)) = (payload_len >> 16);
|
|
*((unsigned char*)((char*)GWBUF_DATA(pBuf) + 3)) = 0x00;
|
|
*((unsigned char*)((char*)GWBUF_DATA(pBuf) + 4)) = 0x03;
|
|
memcpy((char*)GWBUF_DATA(pBuf) + 5, zStmt, len);
|
|
|
|
return pBuf;
|
|
}
|
|
|
|
uint32_t get_qc_trx_type_mask(GWBUF* pBuf)
|
|
{
|
|
return qc_get_trx_type_mask_using(pBuf, QC_TRX_PARSE_USING_QC);
|
|
}
|
|
|
|
uint32_t get_parser_trx_type_mask(GWBUF* pBuf)
|
|
{
|
|
return qc_get_trx_type_mask_using(pBuf, QC_TRX_PARSE_USING_PARSER);
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
struct test_case
|
|
{
|
|
const char* zStmt;
|
|
uint32_t type_mask;
|
|
} test_cases[] =
|
|
{
|
|
// Keep these all uppercase, lowercase are tested programmatically.
|
|
{"BEGIN",
|
|
QUERY_TYPE_BEGIN_TRX},
|
|
{"BEGIN WORK",
|
|
QUERY_TYPE_BEGIN_TRX},
|
|
|
|
{"COMMIT",
|
|
QUERY_TYPE_COMMIT},
|
|
{"COMMIT WORK",
|
|
QUERY_TYPE_COMMIT},
|
|
|
|
{"ROLLBACK",
|
|
QUERY_TYPE_ROLLBACK},
|
|
{"ROLLBACK WORK",
|
|
QUERY_TYPE_ROLLBACK},
|
|
|
|
{"START TRANSACTION",
|
|
QUERY_TYPE_BEGIN_TRX},
|
|
|
|
{"START TRANSACTION READ ONLY",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_READ},
|
|
{"START TRANSACTION READ WRITE",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_WRITE},
|
|
|
|
{"START TRANSACTION WITH CONSISTENT SNAPSHOT",
|
|
QUERY_TYPE_BEGIN_TRX},
|
|
|
|
{"START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_READ},
|
|
|
|
{"SET AUTOCOMMIT=true",
|
|
QUERY_TYPE_COMMIT
|
|
| QUERY_TYPE_ENABLE_AUTOCOMMIT},
|
|
|
|
{"SET AUTOCOMMIT=1",
|
|
QUERY_TYPE_COMMIT
|
|
| QUERY_TYPE_ENABLE_AUTOCOMMIT},
|
|
|
|
{"SET AUTOCOMMIT=false",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_DISABLE_AUTOCOMMIT},
|
|
|
|
{"SET AUTOCOMMIT=0",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_DISABLE_AUTOCOMMIT},
|
|
{"SET @@AUTOCOMMIT=0",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_DISABLE_AUTOCOMMIT},
|
|
{"SET GLOBAL AUTOCOMMIT=0",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_DISABLE_AUTOCOMMIT},
|
|
{"SET SESSION AUTOCOMMIT=0",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_DISABLE_AUTOCOMMIT},
|
|
{"SET @@SESSION . AUTOCOMMIT=0",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_DISABLE_AUTOCOMMIT},
|
|
{"SET @@GLOBAL . AUTOCOMMIT=0",
|
|
QUERY_TYPE_BEGIN_TRX
|
|
| QUERY_TYPE_DISABLE_AUTOCOMMIT},
|
|
};
|
|
|
|
const size_t N_TEST_CASES = sizeof(test_cases) / sizeof(test_cases[0]);
|
|
|
|
|
|
bool test(uint32_t (* getter)(GWBUF*), const char* zStmt, uint32_t expected_type_mask)
|
|
{
|
|
int rc = true;
|
|
|
|
GWBUF* pBuf = create_gwbuf(zStmt);
|
|
|
|
uint32_t type_mask = getter(pBuf);
|
|
|
|
if (type_mask != expected_type_mask)
|
|
{
|
|
cerr << "\"" << zStmt << "\""
|
|
<< ": expected " << expected_type_mask << ", but got " << type_mask << "." << endl;
|
|
rc = false;
|
|
}
|
|
|
|
gwbuf_free(pBuf);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
const char* prefixes[] =
|
|
{
|
|
" ",
|
|
" ",
|
|
"\n",
|
|
" \n",
|
|
"\n ",
|
|
"-- comment\n"
|
|
};
|
|
|
|
const int N_PREFIXES = sizeof(prefixes) / sizeof(prefixes[0]);
|
|
|
|
bool test_with_prefixes(uint32_t (* getter)(GWBUF*), const string& base, uint32_t type_mask)
|
|
{
|
|
bool rc = true;
|
|
|
|
for (int i = 0; i < N_PREFIXES; ++i)
|
|
{
|
|
string s = prefixes[i] + base;
|
|
|
|
if (!test(getter, s.c_str(), type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
const char* suffixes[] =
|
|
{
|
|
" ",
|
|
" ",
|
|
"\n",
|
|
" \n",
|
|
"\n ",
|
|
";",
|
|
" ;",
|
|
" ;",
|
|
" ;",
|
|
" ;",
|
|
" ; ",
|
|
";\n",
|
|
" ; ",
|
|
"-- comment this, comment that",
|
|
// "# comment this, comment that" /* qc_sqlite does not handle this */
|
|
};
|
|
|
|
const int N_SUFFIXES = sizeof(suffixes) / sizeof(suffixes[0]);
|
|
|
|
bool test_with_suffixes(uint32_t (* getter)(GWBUF*), const string& base, uint32_t type_mask)
|
|
{
|
|
bool rc = true;
|
|
|
|
for (int i = 0; i < N_SUFFIXES; ++i)
|
|
{
|
|
string s = base + suffixes[i];
|
|
|
|
if (!test(getter, s.c_str(), type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
const char* whitespace[] =
|
|
{
|
|
" ",
|
|
"\n",
|
|
"/**/",
|
|
"/***/",
|
|
"/****/",
|
|
"/* / * */",
|
|
"-- comment\n"
|
|
};
|
|
|
|
const int N_WHITESPACE = sizeof(whitespace) / sizeof(whitespace[0]);
|
|
|
|
bool test_with_whitespace(uint32_t (* getter)(GWBUF*), const string& base, uint32_t type_mask)
|
|
{
|
|
bool rc = true;
|
|
|
|
string::const_iterator i = base.begin();
|
|
string::const_iterator end = base.end();
|
|
|
|
string head;
|
|
|
|
while (i != end)
|
|
{
|
|
if (*i == ' ')
|
|
{
|
|
string tail(i + 1, end);
|
|
|
|
for (int j = 0; j < N_WHITESPACE; ++j)
|
|
{
|
|
string s = head + whitespace[j] + tail;
|
|
|
|
if (!test(getter, s.c_str(), type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
head += *i;
|
|
|
|
++i;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
const char* commas[] =
|
|
{
|
|
" ,",
|
|
" ,",
|
|
" , ",
|
|
" , ",
|
|
};
|
|
|
|
const int N_COMMAS = sizeof(commas) / sizeof(commas[0]);
|
|
|
|
bool test_with_commas(uint32_t (* getter)(GWBUF*), const string& base, uint32_t type_mask)
|
|
{
|
|
bool rc = true;
|
|
|
|
string::const_iterator i = base.begin();
|
|
string::const_iterator end = base.end();
|
|
|
|
string head;
|
|
|
|
while (i != end)
|
|
{
|
|
if (*i == ',')
|
|
{
|
|
string tail(i + 1, end);
|
|
|
|
for (int j = 0; j < N_COMMAS; ++j)
|
|
{
|
|
string s = head + commas[j] + tail;
|
|
|
|
if (!test(getter, s.c_str(), type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
head += *i;
|
|
|
|
++i;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool test(uint32_t (* getter)(GWBUF*), bool dont_bail_out)
|
|
{
|
|
bool rc = true;
|
|
|
|
test_case* pTest = test_cases;
|
|
test_case* pEnd = pTest + N_TEST_CASES;
|
|
|
|
while ((pTest < pEnd) && (dont_bail_out || rc))
|
|
{
|
|
string base(pTest->zStmt);
|
|
cout << base << endl;
|
|
|
|
if (!test(getter, base.c_str(), pTest->type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
|
|
if (dont_bail_out || rc)
|
|
{
|
|
// Test all lowercase.
|
|
string lc(base);
|
|
transform(lc.begin(), lc.end(), lc.begin(), ::tolower);
|
|
|
|
if (!test(getter, lc.c_str(), pTest->type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
if (dont_bail_out || rc)
|
|
{
|
|
if (!test_with_prefixes(getter, base, pTest->type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
if (dont_bail_out || rc)
|
|
{
|
|
if (!test_with_whitespace(getter, base, pTest->type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
if (dont_bail_out || rc)
|
|
{
|
|
if (!test_with_commas(getter, base, pTest->type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
if (dont_bail_out || rc)
|
|
{
|
|
if (!test_with_suffixes(getter, base, pTest->type_mask))
|
|
{
|
|
rc = false;
|
|
}
|
|
}
|
|
|
|
++pTest;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
char USAGE[] =
|
|
"usage: test_trxtracking [-p] [-q] [-r] [-d]\n"
|
|
"\n"
|
|
"-p : Test using custom parser\n"
|
|
"-q : Test using query classifier\n"
|
|
"-r : Test using regex matching\n"
|
|
"-d : Don't bail out at first error\n"
|
|
"\n"
|
|
"If neither -p, -q or -r has been specified, then all will be tested.\n";
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
int rc = EXIT_SUCCESS;
|
|
|
|
bool test_all = true;
|
|
uint32_t test_target = 0;
|
|
bool dont_bail_out = false;
|
|
|
|
int c;
|
|
while ((c = getopt(argc, argv, "dpq")) != -1)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 'p':
|
|
test_all = false;
|
|
test_target |= TEST_PARSER;
|
|
break;
|
|
|
|
case 'q':
|
|
test_all = false;
|
|
test_target = TEST_QC;
|
|
break;
|
|
|
|
case 'd':
|
|
dont_bail_out = true;
|
|
break;
|
|
|
|
default:
|
|
cout << USAGE << endl;
|
|
rc = EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (rc == EXIT_SUCCESS)
|
|
{
|
|
rc = EXIT_FAILURE;
|
|
|
|
if (test_all)
|
|
{
|
|
test_target = TEST_ALL;
|
|
}
|
|
|
|
set_datadir(strdup("/tmp"));
|
|
set_langdir(strdup("."));
|
|
set_process_datadir(strdup("/tmp"));
|
|
|
|
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
|
|
{
|
|
set_libdir(strdup("../../../query_classifier/qc_sqlite"));
|
|
|
|
// We have to setup something in order for the regexes to be compiled.
|
|
if (qc_init(NULL, QC_SQL_MODE_DEFAULT, "qc_sqlite", NULL))
|
|
{
|
|
rc = EXIT_SUCCESS;
|
|
|
|
if (test_target & TEST_QC)
|
|
{
|
|
cout << "QC" << endl;
|
|
cout << "==" << endl;
|
|
if (!test(get_qc_trx_type_mask, dont_bail_out))
|
|
{
|
|
rc = EXIT_FAILURE;
|
|
}
|
|
cout << endl;
|
|
}
|
|
|
|
if (test_target & TEST_PARSER)
|
|
{
|
|
cout << "Parser" << endl;
|
|
cout << "======" << endl;
|
|
if (!test(get_parser_trx_type_mask, dont_bail_out))
|
|
{
|
|
rc = EXIT_FAILURE;
|
|
}
|
|
cout << endl;
|
|
}
|
|
|
|
qc_end();
|
|
}
|
|
else
|
|
{
|
|
cerr << "error: Could not initialize qc_sqlite." << endl;
|
|
}
|
|
|
|
mxs_log_finish();
|
|
}
|
|
else
|
|
{
|
|
cerr << "error: Could not initialize log." << endl;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|