Enable trx boundary detection using regexes
Transaction boundaries can now be detected using regexes. All else being equal, it gives a 10% performance improvement compared to qc-based detection. In a subsequent change, mysql_client.c will be modified to use qc_get_trx_type_mask() instead of qc_get_type_mask(). Currently the use of regex matching is turned on using an environment variable. That will change.
This commit is contained in:
@ -645,6 +645,30 @@ char** qc_get_table_names(GWBUF* stmt, int* size, bool fullnames);
|
|||||||
*/
|
*/
|
||||||
uint32_t qc_get_type_mask(GWBUF* stmt);
|
uint32_t qc_get_type_mask(GWBUF* stmt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type bitmask of transaction related statements.
|
||||||
|
*
|
||||||
|
* If the statement starts a transaction, ends a transaction or
|
||||||
|
* changes the autocommit state, the returned bitmap will be a
|
||||||
|
* combination of:
|
||||||
|
*
|
||||||
|
* QUERY_TYPE_BEGIN_TRX
|
||||||
|
* QUERY_TYPE_COMMIT
|
||||||
|
* QUERY_TYPE_ROLLBACK
|
||||||
|
* QUERY_TYPE_ENABLE_AUTOCOMMIT
|
||||||
|
* QUERY_TYPE_DISABLE_AUTOCOMMIT
|
||||||
|
* QUERY_TYPE_READ (explicitly read only transaction)
|
||||||
|
* QUERY_TYPE_WRITE (explicitly read write transaction)
|
||||||
|
*
|
||||||
|
* Otherwise the result will be 0.
|
||||||
|
*
|
||||||
|
* @param stmt A COM_QUERY or COM_STMT_PREPARE packet.
|
||||||
|
*
|
||||||
|
* @return The relevant type bits if the statement is transaction
|
||||||
|
* related, otherwise 0.
|
||||||
|
*/
|
||||||
|
uint32_t qc_get_trx_type_mask(GWBUF* stmt);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the statement is a DROP TABLE statement.
|
* Returns whether the statement is a DROP TABLE statement.
|
||||||
*
|
*
|
||||||
|
40
server/core/maxscale/query_classifier.h
Normal file
40
server/core/maxscale/query_classifier.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
/*
|
||||||
|
* 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 <maxscale/cdefs.h>
|
||||||
|
#include <maxscale/query_classifier.h>
|
||||||
|
|
||||||
|
MXS_BEGIN_DECLS
|
||||||
|
|
||||||
|
typedef enum qc_trx_parse_using
|
||||||
|
{
|
||||||
|
QC_TRX_PARSE_USING_QC, /**< Use the query classifier. */
|
||||||
|
QC_TRX_PARSE_USING_REGEX, /**< Use regexp mathing. */
|
||||||
|
QC_TRX_PARSE_USING_PARSER, /**< Use custom parser. */
|
||||||
|
} qc_trx_parse_using_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type bitmask of transaction related statements.
|
||||||
|
*
|
||||||
|
* @param stmt A COM_QUERY or COM_STMT_PREPARE packet.
|
||||||
|
* @param use What method should be used.
|
||||||
|
*
|
||||||
|
* @return The relevant type bits if the statement is transaction
|
||||||
|
* related, otherwise 0.
|
||||||
|
*
|
||||||
|
* @see qc_get_trx_type_mask
|
||||||
|
*/
|
||||||
|
uint32_t qc_get_trx_type_mask_using(GWBUF* stmt, qc_trx_parse_using_t use);
|
||||||
|
|
||||||
|
MXS_END_DECLS
|
@ -11,10 +11,12 @@
|
|||||||
* Public License.
|
* Public License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <maxscale/query_classifier.h>
|
#include "maxscale/query_classifier.h"
|
||||||
#include <maxscale/log_manager.h>
|
#include <maxscale/log_manager.h>
|
||||||
#include <maxscale/modutil.h>
|
#include <maxscale/modutil.h>
|
||||||
#include <maxscale/alloc.h>
|
#include <maxscale/alloc.h>
|
||||||
|
#include <maxscale/platform.h>
|
||||||
|
#include <maxscale/pcre2.h>
|
||||||
#include <maxscale/utils.h>
|
#include <maxscale/utils.h>
|
||||||
|
|
||||||
#include "../core/maxscale/modules.h"
|
#include "../core/maxscale/modules.h"
|
||||||
@ -34,10 +36,166 @@ struct type_name_info
|
|||||||
size_t name_len;
|
size_t name_len;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char default_qc_name[] = "qc_sqlite";
|
static const char DEFAULT_QC_NAME[] = "qc_sqlite";
|
||||||
|
static const char QC_TRX_PARSE_USING[] = "QC_TRX_PARSE_USING";
|
||||||
|
|
||||||
static QUERY_CLASSIFIER* classifier;
|
static QUERY_CLASSIFIER* classifier;
|
||||||
|
|
||||||
|
typedef struct qc_trx_regex
|
||||||
|
{
|
||||||
|
const char* match;
|
||||||
|
uint32_t type_mask;
|
||||||
|
pcre2_code* code;
|
||||||
|
} QC_TRX_REGEX;
|
||||||
|
|
||||||
|
static QC_TRX_REGEX qc_trx_regexes[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"^\\s*BEGIN(\\s+WORK)?\\s*;?\\s*$",
|
||||||
|
QUERY_TYPE_BEGIN_TRX
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"^\\s*COMMIT(\\s+WORK)?\\s*;?\\s*$",
|
||||||
|
QUERY_TYPE_COMMIT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"^\\s*ROLLBACK(\\s+WORK)?\\s*;?\\s*$",
|
||||||
|
QUERY_TYPE_ROLLBACK
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"^\\s*START\\s+TRANSACTION\\s+READ\\s+ONLY\\s*;?\\s*$",
|
||||||
|
QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"^\\s*START\\s+TRANSACTION\\s+READ\\s+WRITE\\s*;?\\s*$",
|
||||||
|
QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"^\\s*START\\s+TRANSACTION(\\s*;?\\s*|(\\s+.*))$",
|
||||||
|
QUERY_TYPE_BEGIN_TRX
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"^\\s*SET\\s+AUTOCOMMIT\\s*\\=\\s*(1|true)\\s*;?\\s*$",
|
||||||
|
QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"^\\s*SET\\s+AUTOCOMMIT\\s*\\=\\s*(0|false)\\s*;?\\s*$",
|
||||||
|
QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define N_TRX_REGEXES (sizeof(qc_trx_regexes) / sizeof(qc_trx_regexes[0]))
|
||||||
|
|
||||||
|
static thread_local pcre2_match_data* qc_trx_thread_datas[N_TRX_REGEXES];
|
||||||
|
|
||||||
|
static qc_trx_parse_using_t qc_trx_parse_using = QC_TRX_PARSE_USING_QC;
|
||||||
|
|
||||||
|
static bool compile_trx_regexes();
|
||||||
|
static bool create_trx_thread_datas();
|
||||||
|
static void free_trx_regexes();
|
||||||
|
static void free_trx_thread_datas();
|
||||||
|
|
||||||
|
static bool compile_trx_regexes()
|
||||||
|
{
|
||||||
|
QC_TRX_REGEX* regex = qc_trx_regexes;
|
||||||
|
QC_TRX_REGEX* end = regex + N_TRX_REGEXES;
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
while (success && (regex < end))
|
||||||
|
{
|
||||||
|
int errcode;
|
||||||
|
PCRE2_SIZE erroffset;
|
||||||
|
regex->code = pcre2_compile((PCRE2_SPTR)regex->match, PCRE2_ZERO_TERMINATED, PCRE2_CASELESS,
|
||||||
|
&errcode, &erroffset, NULL);
|
||||||
|
|
||||||
|
if (!regex->code)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
PCRE2_UCHAR errbuf[512];
|
||||||
|
pcre2_get_error_message(errcode, errbuf, sizeof(errbuf));
|
||||||
|
|
||||||
|
MXS_ERROR("Regex compilation failed at %lu for regex '%s': %s.",
|
||||||
|
erroffset, regex->match, errbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
++regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
free_trx_regexes();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_trx_regexes()
|
||||||
|
{
|
||||||
|
QC_TRX_REGEX* begin = qc_trx_regexes;
|
||||||
|
QC_TRX_REGEX* regex = begin + N_TRX_REGEXES;
|
||||||
|
|
||||||
|
while (regex > begin)
|
||||||
|
{
|
||||||
|
--regex;
|
||||||
|
|
||||||
|
if (regex->code)
|
||||||
|
{
|
||||||
|
pcre2_code_free(regex->code);
|
||||||
|
regex->code = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool create_trx_thread_datas()
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
QC_TRX_REGEX* regex = qc_trx_regexes;
|
||||||
|
QC_TRX_REGEX* end = regex + N_TRX_REGEXES;
|
||||||
|
|
||||||
|
pcre2_match_data** data = qc_trx_thread_datas;
|
||||||
|
|
||||||
|
while (success && (regex < end))
|
||||||
|
{
|
||||||
|
*data = pcre2_match_data_create_from_pattern(regex->code, NULL);
|
||||||
|
|
||||||
|
if (!*data)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
MXS_ERROR("PCRE2 match data creation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
++regex;
|
||||||
|
++data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
free_trx_thread_datas();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_trx_thread_datas()
|
||||||
|
{
|
||||||
|
pcre2_match_data** begin = qc_trx_thread_datas;
|
||||||
|
pcre2_match_data** data = begin + N_TRX_REGEXES;
|
||||||
|
|
||||||
|
while (data > begin)
|
||||||
|
{
|
||||||
|
--data;
|
||||||
|
|
||||||
|
if (*data)
|
||||||
|
{
|
||||||
|
pcre2_match_data_free(*data);
|
||||||
|
*data = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool qc_setup(const char* plugin_name, const char* plugin_args)
|
bool qc_setup(const char* plugin_name, const char* plugin_args)
|
||||||
{
|
{
|
||||||
QC_TRACE();
|
QC_TRACE();
|
||||||
@ -45,8 +203,8 @@ bool qc_setup(const char* plugin_name, const char* plugin_args)
|
|||||||
|
|
||||||
if (!plugin_name || (*plugin_name == 0))
|
if (!plugin_name || (*plugin_name == 0))
|
||||||
{
|
{
|
||||||
MXS_NOTICE("No query classifier specified, using default '%s'.", default_qc_name);
|
MXS_NOTICE("No query classifier specified, using default '%s'.", DEFAULT_QC_NAME);
|
||||||
plugin_name = default_qc_name;
|
plugin_name = DEFAULT_QC_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t rv = QC_RESULT_ERROR;
|
int32_t rv = QC_RESULT_ERROR;
|
||||||
@ -59,7 +217,6 @@ bool qc_setup(const char* plugin_name, const char* plugin_args)
|
|||||||
if (rv != QC_RESULT_OK)
|
if (rv != QC_RESULT_OK)
|
||||||
{
|
{
|
||||||
qc_unload(classifier);
|
qc_unload(classifier);
|
||||||
classifier = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,11 +228,57 @@ bool qc_process_init(uint32_t kind)
|
|||||||
QC_TRACE();
|
QC_TRACE();
|
||||||
ss_dassert(classifier);
|
ss_dassert(classifier);
|
||||||
|
|
||||||
bool rc = true;
|
const char* parse_using = getenv(QC_TRX_PARSE_USING);
|
||||||
|
|
||||||
|
if (parse_using)
|
||||||
|
{
|
||||||
|
if (strcmp(parse_using, "QC_TRX_PARSE_USING_QC") == 0)
|
||||||
|
{
|
||||||
|
qc_trx_parse_using = QC_TRX_PARSE_USING_QC;
|
||||||
|
MXS_NOTICE("Transaction detection using QC.");
|
||||||
|
}
|
||||||
|
else if (strcmp(parse_using, "QC_TRX_PARSE_USING_REGEX") == 0)
|
||||||
|
{
|
||||||
|
qc_trx_parse_using = QC_TRX_PARSE_USING_REGEX;
|
||||||
|
MXS_NOTICE("Transaction detection using REGEX.");
|
||||||
|
}
|
||||||
|
else if (strcmp(parse_using, "QC_TRX_PARSE_USING_PARSER") == 0)
|
||||||
|
{
|
||||||
|
ss_dassert(!true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MXS_NOTICE("QC_TRX_PARSE_USING set, but the value %s is not known. "
|
||||||
|
"Parsing using QC.", parse_using);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rc = compile_trx_regexes();
|
||||||
|
|
||||||
|
if (rc)
|
||||||
|
{
|
||||||
|
rc = qc_thread_init(QC_INIT_SELF);
|
||||||
|
|
||||||
|
if (rc)
|
||||||
|
{
|
||||||
if (kind & QC_INIT_PLUGIN)
|
if (kind & QC_INIT_PLUGIN)
|
||||||
{
|
{
|
||||||
rc = classifier->qc_process_init() == 0;
|
rc = classifier->qc_process_init() == 0;
|
||||||
|
|
||||||
|
if (!rc)
|
||||||
|
{
|
||||||
|
qc_thread_end(QC_INIT_SELF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
free_trx_regexes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MXS_ERROR("Could not compile transaction regexes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
@ -89,8 +292,11 @@ void qc_process_end(uint32_t kind)
|
|||||||
if (kind & QC_INIT_PLUGIN)
|
if (kind & QC_INIT_PLUGIN)
|
||||||
{
|
{
|
||||||
classifier->qc_process_end();
|
classifier->qc_process_end();
|
||||||
classifier = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qc_thread_end(QC_INIT_SELF);
|
||||||
|
|
||||||
|
free_trx_regexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUERY_CLASSIFIER* qc_load(const char* plugin_name)
|
QUERY_CLASSIFIER* qc_load(const char* plugin_name)
|
||||||
@ -113,6 +319,7 @@ void qc_unload(QUERY_CLASSIFIER* classifier)
|
|||||||
{
|
{
|
||||||
// TODO: The module loading/unloading needs an overhaul before we
|
// TODO: The module loading/unloading needs an overhaul before we
|
||||||
// TODO: actually can unload something.
|
// TODO: actually can unload something.
|
||||||
|
classifier = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool qc_thread_init(uint32_t kind)
|
bool qc_thread_init(uint32_t kind)
|
||||||
@ -120,11 +327,20 @@ bool qc_thread_init(uint32_t kind)
|
|||||||
QC_TRACE();
|
QC_TRACE();
|
||||||
ss_dassert(classifier);
|
ss_dassert(classifier);
|
||||||
|
|
||||||
bool rc = true;
|
bool rc = create_trx_thread_datas();
|
||||||
|
|
||||||
|
if (rc)
|
||||||
|
{
|
||||||
if (kind & QC_INIT_PLUGIN)
|
if (kind & QC_INIT_PLUGIN)
|
||||||
{
|
{
|
||||||
rc = classifier->qc_thread_init() == 0;
|
rc = classifier->qc_thread_init() == 0;
|
||||||
|
|
||||||
|
if (!rc)
|
||||||
|
{
|
||||||
|
free_trx_thread_datas();
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
@ -139,6 +355,8 @@ void qc_thread_end(uint32_t kind)
|
|||||||
{
|
{
|
||||||
classifier->qc_thread_end();
|
classifier->qc_thread_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free_trx_thread_datas();
|
||||||
}
|
}
|
||||||
|
|
||||||
qc_parse_result_t qc_parse(GWBUF* query)
|
qc_parse_result_t qc_parse(GWBUF* query)
|
||||||
@ -797,3 +1015,82 @@ char* qc_typemask_to_string(uint32_t types)
|
|||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t qc_get_trx_type_mask_using_qc(GWBUF* stmt)
|
||||||
|
{
|
||||||
|
uint32_t type_mask = qc_get_type_mask(stmt);
|
||||||
|
|
||||||
|
if (!(type_mask & QUERY_TYPE_BEGIN_TRX))
|
||||||
|
{
|
||||||
|
type_mask &= ~(QUERY_TYPE_WRITE | QUERY_TYPE_READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_mask &= (QUERY_TYPE_BEGIN_TRX |
|
||||||
|
QUERY_TYPE_WRITE |
|
||||||
|
QUERY_TYPE_READ |
|
||||||
|
QUERY_TYPE_COMMIT |
|
||||||
|
QUERY_TYPE_ROLLBACK |
|
||||||
|
QUERY_TYPE_ENABLE_AUTOCOMMIT |
|
||||||
|
QUERY_TYPE_DISABLE_AUTOCOMMIT);
|
||||||
|
|
||||||
|
return type_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t qc_get_trx_type_mask_using_regex(GWBUF* stmt)
|
||||||
|
{
|
||||||
|
uint32_t type_mask = 0;
|
||||||
|
|
||||||
|
char* sql;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
// This will exclude prepared statement but we are fine with that.
|
||||||
|
if (modutil_extract_SQL(stmt, &sql, &len))
|
||||||
|
{
|
||||||
|
QC_TRX_REGEX* regex = qc_trx_regexes;
|
||||||
|
QC_TRX_REGEX* end = regex + N_TRX_REGEXES;
|
||||||
|
pcre2_match_data** data = qc_trx_thread_datas;
|
||||||
|
|
||||||
|
while ((type_mask == 0) && (regex < end))
|
||||||
|
{
|
||||||
|
if (pcre2_match(regex->code, (PCRE2_SPTR)sql, len, 0, 0, *data, NULL) >= 0)
|
||||||
|
{
|
||||||
|
type_mask = regex->type_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
++regex;
|
||||||
|
++data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t qc_get_trx_type_mask_using(GWBUF* stmt, qc_trx_parse_using_t use)
|
||||||
|
{
|
||||||
|
uint32_t type_mask = 0;
|
||||||
|
|
||||||
|
switch (use)
|
||||||
|
{
|
||||||
|
case QC_TRX_PARSE_USING_QC:
|
||||||
|
type_mask = qc_get_trx_type_mask_using_qc(stmt);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QC_TRX_PARSE_USING_REGEX:
|
||||||
|
type_mask = qc_get_trx_type_mask_using_regex(stmt);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QC_TRX_PARSE_USING_PARSER:
|
||||||
|
ss_dassert(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ss_dassert(!true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return type_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t qc_get_trx_type_mask(GWBUF* stmt)
|
||||||
|
{
|
||||||
|
return qc_get_trx_type_mask_using(stmt, qc_trx_parse_using);
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ add_executable(test_queuemanager testqueuemanager.c)
|
|||||||
add_executable(test_server testserver.c)
|
add_executable(test_server testserver.c)
|
||||||
add_executable(test_service testservice.c)
|
add_executable(test_service testservice.c)
|
||||||
add_executable(test_spinlock testspinlock.c)
|
add_executable(test_spinlock testspinlock.c)
|
||||||
|
add_executable(test_trxtracking testtrxtracking.cc)
|
||||||
add_executable(test_users testusers.c)
|
add_executable(test_users testusers.c)
|
||||||
add_executable(testfeedback testfeedback.c)
|
add_executable(testfeedback testfeedback.c)
|
||||||
add_executable(testmaxscalepcre2 testmaxscalepcre2.c)
|
add_executable(testmaxscalepcre2 testmaxscalepcre2.c)
|
||||||
@ -33,6 +34,7 @@ target_link_libraries(test_queuemanager maxscale-common)
|
|||||||
target_link_libraries(test_server maxscale-common)
|
target_link_libraries(test_server maxscale-common)
|
||||||
target_link_libraries(test_service maxscale-common)
|
target_link_libraries(test_service maxscale-common)
|
||||||
target_link_libraries(test_spinlock maxscale-common)
|
target_link_libraries(test_spinlock maxscale-common)
|
||||||
|
target_link_libraries(test_trxtracking maxscale-common)
|
||||||
target_link_libraries(test_users maxscale-common)
|
target_link_libraries(test_users maxscale-common)
|
||||||
target_link_libraries(testfeedback maxscale-common)
|
target_link_libraries(testfeedback maxscale-common)
|
||||||
target_link_libraries(testmaxscalepcre2 maxscale-common)
|
target_link_libraries(testmaxscalepcre2 maxscale-common)
|
||||||
@ -58,6 +60,7 @@ add_test(TestSpinlock test_spinlock)
|
|||||||
add_test(TestUsers test_users)
|
add_test(TestUsers test_users)
|
||||||
add_test(TestModulecmd testmodulecmd)
|
add_test(TestModulecmd testmodulecmd)
|
||||||
add_test(TestConfig testconfig)
|
add_test(TestConfig testconfig)
|
||||||
|
add_test(TestTrxTracking test_trxtracking)
|
||||||
|
|
||||||
# This test requires external dependencies and thus cannot be run
|
# This test requires external dependencies and thus cannot be run
|
||||||
# as a part of the core test set
|
# as a part of the core test set
|
||||||
|
295
server/core/test/testtrxtracking.cc
Normal file
295
server/core/test/testtrxtracking.cc
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
/*
|
||||||
|
* 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 <maxscale/cppdefs.hh>
|
||||||
|
#include <iostream>
|
||||||
|
#include <maxscale/modutil.h>
|
||||||
|
#include <maxscale/paths.h>
|
||||||
|
#include <maxscale/protocol/mysql.h>
|
||||||
|
#include "../../server/core/maxscale/query_classifier.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
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_regex_trx_type_mask(GWBUF* pBuf)
|
||||||
|
{
|
||||||
|
return qc_get_trx_type_mask_using(pBuf, QC_TRX_PARSE_USING_REGEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct test_case
|
||||||
|
{
|
||||||
|
const char* zStmt;
|
||||||
|
uint32_t type_mask;
|
||||||
|
} test_cases[] =
|
||||||
|
{
|
||||||
|
{ "BEGIN", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
{ "BEGIN WORK", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
{ "BEGIN WORK", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
|
||||||
|
{ "COMMIT", QUERY_TYPE_COMMIT },
|
||||||
|
{ "COMMIT WORK", QUERY_TYPE_COMMIT },
|
||||||
|
{ "COMMIT WORK", QUERY_TYPE_COMMIT },
|
||||||
|
|
||||||
|
{ "ROLLBACK", QUERY_TYPE_ROLLBACK },
|
||||||
|
{ "ROLLBACK WORK", QUERY_TYPE_ROLLBACK },
|
||||||
|
{ "ROLLBACK WORK", QUERY_TYPE_ROLLBACK },
|
||||||
|
|
||||||
|
{ "START TRANSACTION", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
{ "START TRANSACTION", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
{ "START TRANSACTION READ ONLY", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ },
|
||||||
|
{ "START TRANSACTION READ ONLY", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ },
|
||||||
|
{ "START TRANSACTION READ ONLY ", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ },
|
||||||
|
{ "START TRANSACTION READ ONLY ", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ },
|
||||||
|
{ "START TRANSACTION READ WRITE", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE },
|
||||||
|
{ "START TRANSACTION READ WRITE", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE },
|
||||||
|
{ "START TRANSACTION READ WRITE", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE },
|
||||||
|
{ "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", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
{ "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
{ "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
{ "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
{ "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX },
|
||||||
|
|
||||||
|
{ "SET AUTOCOMMIT=true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT=true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT =true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT =true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT = true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT = true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT=1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT=1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT =1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT =1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT = 1", 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=false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT =false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT =false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT },
|
||||||
|
{ "SET AUTOCOMMIT = false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_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 AUTOCOMMIT =0", 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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test(uint32_t (*getter)(GWBUF*))
|
||||||
|
{
|
||||||
|
bool rc = true;
|
||||||
|
|
||||||
|
test_case* pTest = test_cases;
|
||||||
|
test_case* pEnd = pTest + N_TEST_CASES;
|
||||||
|
|
||||||
|
while (pTest < pEnd)
|
||||||
|
{
|
||||||
|
string base(pTest->zStmt);
|
||||||
|
cout << base << endl;
|
||||||
|
|
||||||
|
string s;
|
||||||
|
|
||||||
|
// Just the string
|
||||||
|
s = base;
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepended with one space.
|
||||||
|
s = " " + base;
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepended with two spaces.
|
||||||
|
s = " " + base;
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with one space.
|
||||||
|
s = base + " ";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with two spaces.
|
||||||
|
s = base + " ";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with a ";".
|
||||||
|
s = base + ";";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with a " ;".
|
||||||
|
s = base + " ;";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with a " ;".
|
||||||
|
s = base + " ;";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with a "; ".
|
||||||
|
s = base + "; ";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with a "; ".
|
||||||
|
s = base + "; ";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with a " ; ".
|
||||||
|
s = base + " ; ";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appended with a " ; ".
|
||||||
|
s = base + " ; ";
|
||||||
|
if (!test(getter, s.c_str(), pTest->type_mask))
|
||||||
|
{
|
||||||
|
rc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
++pTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
int rc = EXIT_FAILURE;
|
||||||
|
|
||||||
|
set_datadir(strdup("/tmp"));
|
||||||
|
set_langdir(strdup("."));
|
||||||
|
set_process_datadir(strdup("/tmp"));
|
||||||
|
|
||||||
|
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
|
||||||
|
{
|
||||||
|
// We have to setup something in order for the regexes to be compiled.
|
||||||
|
if (qc_setup("qc_sqlite", NULL) && qc_process_init(QC_INIT_BOTH))
|
||||||
|
{
|
||||||
|
rc = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
cout << "QC" << endl;
|
||||||
|
cout << "==" << endl;
|
||||||
|
if (!test(get_qc_trx_type_mask))
|
||||||
|
{
|
||||||
|
rc = EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
cout << "Regex" << endl;
|
||||||
|
cout << "=====" << endl;
|
||||||
|
if (!test(get_regex_trx_type_mask))
|
||||||
|
{
|
||||||
|
rc = EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
qc_process_end(QC_INIT_BOTH);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cerr << "error: Could not initialize qc_sqlite." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
mxs_log_finish();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cerr << "error: Could not initialize log." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
@ -1523,7 +1523,7 @@ static int route_by_statement(MXS_SESSION* session, uint64_t capabilities, GWBUF
|
|||||||
|
|
||||||
if (MYSQL_GET_COMMAND(data) == MYSQL_COM_QUERY)
|
if (MYSQL_GET_COMMAND(data) == MYSQL_COM_QUERY)
|
||||||
{
|
{
|
||||||
uint32_t type = qc_get_type_mask(packetbuf);
|
uint32_t type = qc_get_trx_type_mask(packetbuf);
|
||||||
|
|
||||||
if (type & QUERY_TYPE_BEGIN_TRX)
|
if (type & QUERY_TYPE_BEGIN_TRX)
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user