MaxScale/server/core/test/test_trxtracking.cc
2019-06-25 10:11:55 +03:00

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;
}