Merge branch '2.1' into develop
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c config_runtime.c dcb.c filter.c filter.cc externcmd.c paths.c hashtable.c hint.c housekeeper.c load_utils.c log_manager.cc maxscale_pcre2.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c router.cc secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c ssl.c mysql_utils.c mysql_binlog.c modulecmd.c )
|
||||
add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c config_runtime.c dcb.c filter.c filter.cc externcmd.c paths.c hashtable.c hint.c housekeeper.c load_utils.c log_manager.cc maxscale_pcre2.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.cc poll.c random_jkiss.c resultset.c router.cc secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c ssl.c mysql_utils.c mysql_binlog.c modulecmd.c)
|
||||
|
||||
if(WITH_JEMALLOC)
|
||||
target_link_libraries(maxscale-common ${JEMALLOC_LIBRARIES})
|
||||
|
||||
@ -2711,8 +2711,7 @@ dcb_accept(DCB *listener)
|
||||
if (client_conn.ss_family == AF_UNIX)
|
||||
{
|
||||
// client address
|
||||
// Should this be `localhost` like it is in the MariaDB server?
|
||||
client_dcb->remote = MXS_STRDUP_A("localhost_from_socket");
|
||||
client_dcb->remote = MXS_STRDUP_A("localhost");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -104,7 +104,7 @@ static int pidfd = PIDFD_CLOSED;
|
||||
/**
|
||||
* exit flag for log flusher.
|
||||
*/
|
||||
static bool do_exit = FALSE;
|
||||
static bool do_exit = false;
|
||||
|
||||
/**
|
||||
* If MaxScale is started to run in daemon process the value is true.
|
||||
@ -138,6 +138,7 @@ static struct option long_options[] =
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"version-full", no_argument, 0, 'V'},
|
||||
{"help", no_argument, 0, '?'},
|
||||
{"connector_plugindir", required_argument, 0, 'H'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
static bool syslog_configured = false;
|
||||
@ -920,6 +921,8 @@ static void usage(void)
|
||||
" -E, --execdir=PATH path to the maxscale and other executable files\n"
|
||||
" -F, --persistdir=PATH path to persisted configuration directory\n"
|
||||
" -M, --module_configdir=PATH path to module configuration directory\n"
|
||||
" -H, --connector_plugindir=PATH\n"
|
||||
" path to MariaDB Connector-C plugin directory\n"
|
||||
" -N, --language=PATH path to errmsg.sys file\n"
|
||||
" -P, --piddir=PATH path to PID file directory\n"
|
||||
" -R, --basedir=PATH base path for all other paths\n"
|
||||
@ -967,15 +970,25 @@ static void usage(void)
|
||||
*/
|
||||
void worker_thread_main(void* arg)
|
||||
{
|
||||
if (modules_thread_init())
|
||||
if (qc_thread_init(QC_INIT_SELF))
|
||||
{
|
||||
poll_waitevents(arg);
|
||||
if (modules_thread_init())
|
||||
{
|
||||
poll_waitevents(arg);
|
||||
|
||||
modules_thread_finish();
|
||||
modules_thread_finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Could not perform thread initialization for all modules. Thread exits.");
|
||||
}
|
||||
|
||||
qc_thread_end(QC_INIT_SELF);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Could not perform thread initialization for all modules. Thread exits.");
|
||||
MXS_ERROR("Could not perform thread initialization for the "
|
||||
"internal query classifier. Thread exits.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1236,6 +1249,12 @@ bool set_dirs(const char *basedir)
|
||||
set_config_persistdir(path);
|
||||
}
|
||||
|
||||
if (rv && (rv = handle_path_arg(&path, basedir,
|
||||
"var/" MXS_DEFAULT_CONNECTOR_PLUGIN_SUBPATH, true, true)))
|
||||
{
|
||||
set_connector_plugindir(path);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -1325,7 +1344,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "dcf:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:F:M:",
|
||||
while ((opt = getopt_long(argc, argv, "dcf:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:F:M:H:",
|
||||
long_options, &option_index)) != -1)
|
||||
{
|
||||
bool succp = true;
|
||||
@ -1491,6 +1510,16 @@ int main(int argc, char **argv)
|
||||
succp = false;
|
||||
}
|
||||
break;
|
||||
case 'H':
|
||||
if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
|
||||
{
|
||||
set_connector_plugindir(tmp_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
break;
|
||||
case 'F':
|
||||
if (handle_path_arg(&tmp_path, optarg, NULL, true, true))
|
||||
{
|
||||
@ -1899,6 +1928,16 @@ int main(int argc, char **argv)
|
||||
|
||||
dcb_global_init();
|
||||
|
||||
/* Initialize the internal query classifier. The plugin will be initialized
|
||||
* via the module initialization below.
|
||||
*/
|
||||
if (!qc_process_init(QC_INIT_SELF))
|
||||
{
|
||||
MXS_ERROR("Failed to initialize the internal query classifier.");
|
||||
rc = MAXSCALE_INTERNALERROR;
|
||||
goto return_main;
|
||||
}
|
||||
|
||||
/* Init MaxScale modules */
|
||||
if (!modules_process_init())
|
||||
{
|
||||
@ -2017,6 +2056,11 @@ int main(int argc, char **argv)
|
||||
/*< Call finish on all modules. */
|
||||
modules_process_finish();
|
||||
|
||||
/* Finalize the internal query classifier. The plugin was finalized
|
||||
* via the module finalizarion above.
|
||||
*/
|
||||
qc_process_end(QC_INIT_SELF);
|
||||
|
||||
log_exit_status();
|
||||
MXS_NOTICE("MaxScale is shutting down.");
|
||||
|
||||
@ -2078,7 +2122,7 @@ int maxscale_shutdown()
|
||||
|
||||
static void log_flush_shutdown(void)
|
||||
{
|
||||
do_exit = TRUE;
|
||||
do_exit = true;
|
||||
}
|
||||
|
||||
|
||||
@ -2525,6 +2569,20 @@ static int cnf_preparser(void* data, const char* section, const char* name, cons
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcmp(name, "connector_plugindir") == 0)
|
||||
{
|
||||
if (strcmp(get_connector_plugindir(), default_connector_plugindir) == 0)
|
||||
{
|
||||
if (handle_path_arg((char**)&tmp, (char*)value, NULL, true, false))
|
||||
{
|
||||
set_connector_plugindir(tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcmp(name, "persistdir") == 0)
|
||||
{
|
||||
if (strcmp(get_config_persistdir(), default_config_persistdir) == 0)
|
||||
|
||||
39
server/core/maxscale/query_classifier.h
Normal file
39
server/core/maxscale/query_classifier.h
Normal file
@ -0,0 +1,39 @@
|
||||
#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_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
|
||||
841
server/core/maxscale/trxboundaryparser.hh
Normal file
841
server/core/maxscale/trxboundaryparser.hh
Normal file
@ -0,0 +1,841 @@
|
||||
#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/cppdefs.hh>
|
||||
#include <ctype.h>
|
||||
#include <maxscale/modutil.h>
|
||||
#include <maxscale/query_classifier.h>
|
||||
|
||||
namespace maxscale
|
||||
{
|
||||
|
||||
#define TBP_EXPECT_TOKEN(string_literal) string_literal, (sizeof(string_literal) - 1)
|
||||
|
||||
/**
|
||||
* @class TrxBoundaryParser
|
||||
*
|
||||
* @ TrxBoundaryParser is a class capable of parsing and returning the
|
||||
* correct type mask of statements affecting the transaction state and
|
||||
* autocommit mode.
|
||||
*
|
||||
* The class is intended to be used in context where the performance is
|
||||
* of utmost importance; consequently it is defined in its entirety
|
||||
* in the header to allow for aggressive inlining.
|
||||
*/
|
||||
class TrxBoundaryParser
|
||||
{
|
||||
public:
|
||||
enum token_t
|
||||
{
|
||||
TK_AUTOCOMMIT,
|
||||
TK_BEGIN,
|
||||
TK_COMMA,
|
||||
TK_COMMIT,
|
||||
TK_CONSISTENT,
|
||||
TK_DOT,
|
||||
TK_EQ,
|
||||
TK_FALSE,
|
||||
TK_GLOBAL,
|
||||
TK_GLOBAL_VAR,
|
||||
TK_ONE,
|
||||
TK_ONLY,
|
||||
TK_READ,
|
||||
TK_ROLLBACK,
|
||||
TK_SESSION,
|
||||
TK_SESSION_VAR,
|
||||
TK_SET,
|
||||
TK_SNAPSHOT,
|
||||
TK_START,
|
||||
TK_TRANSACTION,
|
||||
TK_TRUE,
|
||||
TK_WITH,
|
||||
TK_WORK,
|
||||
TK_WRITE,
|
||||
TK_ZERO,
|
||||
|
||||
PARSER_UNKNOWN_TOKEN,
|
||||
PARSER_EXHAUSTED,
|
||||
};
|
||||
|
||||
/**
|
||||
* TrxBoundaryParser is not thread-safe. As a very lightweight class,
|
||||
* the intention is that an instance is created on the stack whenever
|
||||
* parsing needs to be performed.
|
||||
*
|
||||
* @code
|
||||
* void f(GWBUF *pBuf)
|
||||
* {
|
||||
* TrxBoundaryParser tbp;
|
||||
*
|
||||
* uint32_t type_mask = tbp.parse(pBuf);
|
||||
* ...
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
TrxBoundaryParser()
|
||||
: m_pSql(NULL)
|
||||
, m_len(0)
|
||||
, m_pI(NULL)
|
||||
, m_pEnd(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type mask of a statement, provided the statement affects
|
||||
* transaction state or autocommit mode.
|
||||
*
|
||||
* @param pSql SQL statament.
|
||||
* @param len Length of pSql.
|
||||
*
|
||||
* @return The corresponding type mask or 0, if the statement does not
|
||||
* affect transaction state or autocommit mode.
|
||||
*/
|
||||
uint32_t type_mask_of(const char* pSql, size_t len)
|
||||
{
|
||||
uint32_t type_mask = 0;
|
||||
|
||||
m_pSql = pSql;
|
||||
m_len = len;
|
||||
|
||||
m_pI = m_pSql;
|
||||
m_pEnd = m_pI + m_len;
|
||||
|
||||
return parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type mask of a statement, provided the statement affects
|
||||
* transaction state or autocommit mode.
|
||||
*
|
||||
* @param pBuf A COM_QUERY
|
||||
*
|
||||
* @return The corresponding type mask or 0, if the statement does not
|
||||
* affect transaction state or autocommit mode.
|
||||
*/
|
||||
uint32_t type_mask_of(GWBUF* pBuf)
|
||||
{
|
||||
uint32_t type_mask = 0;
|
||||
|
||||
char* pSql;
|
||||
if (modutil_extract_SQL(pBuf, &pSql, &m_len))
|
||||
{
|
||||
m_pSql = pSql;
|
||||
m_pI = m_pSql;
|
||||
m_pEnd = m_pI + m_len;
|
||||
|
||||
type_mask = parse();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
private:
|
||||
enum token_required_t
|
||||
{
|
||||
TOKEN_REQUIRED,
|
||||
TOKEN_NOT_REQUIRED,
|
||||
};
|
||||
|
||||
void log_unexpected()
|
||||
{
|
||||
#ifdef SS_DEBUG
|
||||
MXS_NOTICE("Transaction tracking: In statement '%.*s', unexpected token at '%.*s'.",
|
||||
(int)m_len, m_pSql, (int)(m_pEnd - m_pI), m_pI);
|
||||
#endif
|
||||
}
|
||||
|
||||
void log_exhausted()
|
||||
{
|
||||
#ifdef SS_DEBUG
|
||||
MXS_NOTICE("Transaction tracking: More tokens expected in statement '%.*s'.", (int)m_len, m_pSql);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t parse()
|
||||
{
|
||||
uint32_t type_mask = 0;
|
||||
|
||||
token_t token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_BEGIN:
|
||||
type_mask = parse_begin(type_mask);
|
||||
break;
|
||||
|
||||
case TK_COMMIT:
|
||||
type_mask = parse_commit(type_mask);
|
||||
break;
|
||||
|
||||
case TK_ROLLBACK:
|
||||
type_mask = parse_rollback(type_mask);
|
||||
break;
|
||||
|
||||
case TK_START:
|
||||
type_mask = parse_start(type_mask);
|
||||
break;
|
||||
|
||||
case TK_SET:
|
||||
type_mask = parse_set(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_begin(uint32_t type_mask)
|
||||
{
|
||||
type_mask |= QUERY_TYPE_BEGIN_TRX;
|
||||
|
||||
token_t token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_WORK:
|
||||
type_mask = parse_work(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_commit(uint32_t type_mask)
|
||||
{
|
||||
type_mask |= QUERY_TYPE_COMMIT;
|
||||
|
||||
token_t token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_WORK:
|
||||
type_mask = parse_work(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_only(uint32_t type_mask)
|
||||
{
|
||||
type_mask |= QUERY_TYPE_READ;
|
||||
|
||||
token_t token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_COMMA:
|
||||
type_mask = parse_transaction(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_read(uint32_t type_mask)
|
||||
{
|
||||
token_t token = next_token(TOKEN_REQUIRED);
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_ONLY:
|
||||
type_mask = parse_only(type_mask);
|
||||
break;
|
||||
|
||||
case TK_WRITE:
|
||||
type_mask = parse_write(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
type_mask = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_rollback(uint32_t type_mask)
|
||||
{
|
||||
type_mask |= QUERY_TYPE_ROLLBACK;
|
||||
|
||||
token_t token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_WORK:
|
||||
type_mask = parse_work(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_set_autocommit(uint32_t type_mask)
|
||||
{
|
||||
token_t token = next_token(TOKEN_REQUIRED);
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_EQ:
|
||||
token = next_token(TOKEN_REQUIRED);
|
||||
if (token == TK_ONE || token == TK_TRUE)
|
||||
{
|
||||
type_mask |= (QUERY_TYPE_COMMIT | QUERY_TYPE_ENABLE_AUTOCOMMIT);
|
||||
}
|
||||
else if (token == TK_ZERO || token == TK_FALSE)
|
||||
{
|
||||
type_mask = (QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_DISABLE_AUTOCOMMIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
type_mask = 0;
|
||||
|
||||
if (token != PARSER_EXHAUSTED)
|
||||
{
|
||||
log_unexpected();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
type_mask = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_set(uint32_t type_mask)
|
||||
{
|
||||
token_t token = next_token(TOKEN_REQUIRED);
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_AUTOCOMMIT:
|
||||
type_mask = parse_set_autocommit(type_mask);
|
||||
break;
|
||||
|
||||
case TK_GLOBAL:
|
||||
case TK_SESSION:
|
||||
token = next_token(TOKEN_REQUIRED);
|
||||
if (token == TK_AUTOCOMMIT)
|
||||
{
|
||||
type_mask = parse_set_autocommit(type_mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
type_mask = 0;
|
||||
if (token != PARSER_EXHAUSTED)
|
||||
{
|
||||
log_unexpected();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TK_GLOBAL_VAR:
|
||||
case TK_SESSION_VAR:
|
||||
token = next_token(TOKEN_REQUIRED);
|
||||
if (token == TK_DOT)
|
||||
{
|
||||
token = next_token(TOKEN_REQUIRED);
|
||||
if (token == TK_AUTOCOMMIT)
|
||||
{
|
||||
type_mask = parse_set_autocommit(type_mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
type_mask = 0;
|
||||
if (token != PARSER_EXHAUSTED)
|
||||
{
|
||||
log_unexpected();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
type_mask = 0;
|
||||
if (token != PARSER_EXHAUSTED)
|
||||
{
|
||||
log_unexpected();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
type_mask = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_start(uint32_t type_mask)
|
||||
{
|
||||
token_t token = next_token(TOKEN_REQUIRED);
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_TRANSACTION:
|
||||
type_mask = parse_transaction(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
type_mask = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_transaction(uint32_t type_mask)
|
||||
{
|
||||
type_mask |= QUERY_TYPE_BEGIN_TRX;
|
||||
|
||||
token_t token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_READ:
|
||||
type_mask = parse_read(type_mask);
|
||||
break;
|
||||
|
||||
case TK_WITH:
|
||||
type_mask = parse_with_consistent_snapshot(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_with_consistent_snapshot(uint32_t type_mask)
|
||||
{
|
||||
token_t token = next_token(TOKEN_REQUIRED);
|
||||
|
||||
if (token == TK_CONSISTENT)
|
||||
{
|
||||
token = next_token(TOKEN_REQUIRED);
|
||||
|
||||
if (token == TK_SNAPSHOT)
|
||||
{
|
||||
token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_COMMA:
|
||||
type_mask = parse_transaction(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_work(uint32_t type_mask)
|
||||
{
|
||||
token_t token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case PARSER_EXHAUSTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
uint32_t parse_write(uint32_t type_mask)
|
||||
{
|
||||
type_mask |= QUERY_TYPE_WRITE;
|
||||
|
||||
token_t token = next_token();
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case TK_COMMA:
|
||||
type_mask = parse_transaction(type_mask);
|
||||
break;
|
||||
|
||||
case PARSER_EXHAUSTED:
|
||||
break;
|
||||
|
||||
default:
|
||||
type_mask = 0;
|
||||
log_unexpected();
|
||||
}
|
||||
|
||||
return type_mask;
|
||||
}
|
||||
|
||||
inline bool is_next_alpha(char uc, int offset = 1) const
|
||||
{
|
||||
ss_dassert(uc >= 'A' && uc <= 'Z');
|
||||
|
||||
char lc = uc + ('a' - 'A');
|
||||
|
||||
return
|
||||
((m_pI + offset) < m_pEnd) &&
|
||||
((*(m_pI + offset) == uc) || (*(m_pI + offset) == lc));
|
||||
}
|
||||
|
||||
bool is_next_char(char c, int offset = 1) const
|
||||
{
|
||||
return ((m_pI + offset) < m_pEnd) && (*(m_pI + offset) == c);
|
||||
}
|
||||
|
||||
bool peek_next_char(char* pC) const
|
||||
{
|
||||
bool rc = (m_pI + 1 < m_pEnd);
|
||||
|
||||
if (rc)
|
||||
{
|
||||
*pC = *(m_pI + 1);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Significantly faster than library version.
|
||||
static char toupper(char c)
|
||||
{
|
||||
return (c >= 'a' && c <='z') ? c - ('a' - 'A') : c;
|
||||
}
|
||||
|
||||
token_t expect_token(const char* zWord, int len, token_t token)
|
||||
{
|
||||
const char* pI = m_pI;
|
||||
const char* pEnd = zWord + len;
|
||||
|
||||
while ((pI < m_pEnd) && (zWord < pEnd) && (toupper(*pI) == *zWord))
|
||||
{
|
||||
++pI;
|
||||
++zWord;
|
||||
}
|
||||
|
||||
if (zWord == pEnd)
|
||||
{
|
||||
if ((pI == m_pEnd) || (!isalpha(*pI))) // Handwritten isalpha not faster than library version.
|
||||
{
|
||||
m_pI = pI;
|
||||
}
|
||||
else
|
||||
{
|
||||
token = PARSER_UNKNOWN_TOKEN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
token = PARSER_UNKNOWN_TOKEN;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
void bypass_whitespace()
|
||||
{
|
||||
m_pI = modutil_MySQL_bypass_whitespace(const_cast<char*>(m_pI), m_pEnd - m_pI);
|
||||
}
|
||||
|
||||
token_t next_token(token_required_t required = TOKEN_NOT_REQUIRED)
|
||||
{
|
||||
token_t token = PARSER_UNKNOWN_TOKEN;
|
||||
|
||||
bypass_whitespace();
|
||||
|
||||
if (m_pI == m_pEnd)
|
||||
{
|
||||
token = PARSER_EXHAUSTED;
|
||||
}
|
||||
else if (*m_pI == ';')
|
||||
{
|
||||
++m_pI;
|
||||
|
||||
while ((m_pI != m_pEnd) && isspace(*m_pI))
|
||||
{
|
||||
++m_pI;
|
||||
}
|
||||
|
||||
if (m_pI != m_pEnd)
|
||||
{
|
||||
MXS_WARNING("Non-space data found after semi-colon: '%.*s'.",
|
||||
(int)(m_pEnd - m_pI), m_pI);
|
||||
}
|
||||
|
||||
token = PARSER_EXHAUSTED;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (*m_pI)
|
||||
{
|
||||
case '@':
|
||||
if (is_next_alpha('A', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("@@AUTOCOMMIT"), TK_AUTOCOMMIT);
|
||||
}
|
||||
else if (is_next_alpha('S', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("@@SESSION"), TK_SESSION_VAR);
|
||||
}
|
||||
else if (is_next_alpha('G', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("@@GLOBAL"), TK_GLOBAL_VAR);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
case 'A':
|
||||
token = expect_token(TBP_EXPECT_TOKEN("AUTOCOMMIT"), TK_AUTOCOMMIT);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
case 'B':
|
||||
token = expect_token(TBP_EXPECT_TOKEN("BEGIN"), TK_BEGIN);
|
||||
break;
|
||||
|
||||
case ',':
|
||||
++m_pI;
|
||||
token = TK_COMMA;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
case 'C':
|
||||
if (is_next_alpha('O'))
|
||||
{
|
||||
if (is_next_alpha('M', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("COMMIT"), TK_COMMIT);
|
||||
}
|
||||
else if (is_next_alpha('N', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("CONSISTENT"), TK_CONSISTENT);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '.':
|
||||
++m_pI;
|
||||
token = TK_DOT;
|
||||
break;
|
||||
|
||||
case '=':
|
||||
++m_pI;
|
||||
token = TK_EQ;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
case 'F':
|
||||
token = expect_token(TBP_EXPECT_TOKEN("FALSE"), TK_FALSE);
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
case 'G':
|
||||
token = expect_token(TBP_EXPECT_TOKEN("GLOBAL"), TK_GLOBAL);
|
||||
break;
|
||||
|
||||
case '1':
|
||||
{
|
||||
char c;
|
||||
if (!peek_next_char(&c) || !isdigit(c))
|
||||
{
|
||||
++m_pI;
|
||||
token = TK_ONE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
case 'O':
|
||||
if (is_next_alpha('F'))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("OFF"), TK_ZERO);
|
||||
}
|
||||
else if (is_next_alpha('N'))
|
||||
{
|
||||
if (is_next_char('L', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("ONLY"), TK_ONLY);
|
||||
}
|
||||
else
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("ON"), TK_ONE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
case 'R':
|
||||
if (is_next_alpha('E'))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("READ"), TK_READ);
|
||||
}
|
||||
else if (is_next_alpha('O'))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("ROLLBACK"), TK_ROLLBACK);
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
case 'S':
|
||||
if (is_next_alpha('E'))
|
||||
{
|
||||
if (is_next_alpha('S', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("SESSION"), TK_SESSION);
|
||||
}
|
||||
else
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("SET"), TK_SET);
|
||||
}
|
||||
}
|
||||
else if (is_next_alpha('N'))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("SNAPSHOT"), TK_SNAPSHOT);
|
||||
}
|
||||
else if (is_next_char('T'))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("START"), TK_START);
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
case 'T':
|
||||
if (is_next_alpha('R'))
|
||||
{
|
||||
if (is_next_alpha('A', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("TRANSACTION"), TK_TRANSACTION);
|
||||
}
|
||||
else if (is_next_alpha('U', 2))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("TRUE"), TK_TRUE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
case 'W':
|
||||
if (is_next_alpha('I'))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("WITH"), TK_WITH);
|
||||
}
|
||||
else if (is_next_alpha('O'))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("WORK"), TK_WORK);
|
||||
}
|
||||
else if (is_next_alpha('R'))
|
||||
{
|
||||
token = expect_token(TBP_EXPECT_TOKEN("WRITE"), TK_WRITE);
|
||||
}
|
||||
break;
|
||||
|
||||
case '0':
|
||||
{
|
||||
char c;
|
||||
if (!peek_next_char(&c) || !isdigit(c))
|
||||
{
|
||||
++m_pI;
|
||||
token = TK_ZERO;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
if ((token == PARSER_EXHAUSTED) && (required == TOKEN_REQUIRED))
|
||||
{
|
||||
log_exhausted();
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private:
|
||||
TrxBoundaryParser(const TrxBoundaryParser&);
|
||||
TrxBoundaryParser& operator = (const TrxBoundaryParser&);
|
||||
|
||||
private:
|
||||
const char* m_pSql;
|
||||
int m_len;
|
||||
const char* m_pI;
|
||||
const char* m_pEnd;
|
||||
};
|
||||
|
||||
}
|
||||
@ -1213,3 +1213,111 @@ char* modutil_get_canonical(GWBUF* querybuf)
|
||||
|
||||
return querystr;
|
||||
}
|
||||
|
||||
|
||||
char* modutil_MySQL_bypass_whitespace(char* sql, size_t len)
|
||||
{
|
||||
char *i = sql;
|
||||
char *end = i + len;
|
||||
|
||||
while (i != end)
|
||||
{
|
||||
if (isspace(*i))
|
||||
{
|
||||
++i;
|
||||
}
|
||||
else if (*i == '/') // Might be a comment
|
||||
{
|
||||
if ((i + 1 != end) && (*(i + 1) == '*')) // Indeed it was
|
||||
{
|
||||
i += 2;
|
||||
|
||||
while (i != end)
|
||||
{
|
||||
if (*i == '*') // Might be the end of the comment
|
||||
{
|
||||
++i;
|
||||
|
||||
if (i != end)
|
||||
{
|
||||
if (*i == '/') // Indeed it was
|
||||
{
|
||||
++i;
|
||||
break; // Out of this inner while.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// It was not the end of the comment.
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Was not a comment, so we'll bail out.
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (*i == '-') // Might be the start of a comment to the end of line
|
||||
{
|
||||
bool is_comment = false;
|
||||
|
||||
if (i + 1 != end)
|
||||
{
|
||||
if (*(i + 1) == '-') // Might be, yes.
|
||||
{
|
||||
if (i + 2 != end)
|
||||
{
|
||||
if (isspace(*(i + 2))) // Yes, it is.
|
||||
{
|
||||
is_comment = true;
|
||||
|
||||
i += 3;
|
||||
|
||||
while ((i != end) && (*i != '\n'))
|
||||
{
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i != end)
|
||||
{
|
||||
ss_dassert(*i == '\n');
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_comment)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (*i == '#')
|
||||
{
|
||||
++i;
|
||||
|
||||
while ((i != end) && (*i != '\n'))
|
||||
{
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i != end)
|
||||
{
|
||||
ss_dassert(*i == '\n');
|
||||
++i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Neither whitespace not start of a comment, so we bail out.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
@ -1181,10 +1181,10 @@ mon_connect_to_db(MXS_MONITOR* mon, MXS_MONITOR_SERVERS *database)
|
||||
|
||||
char *dpwd = decrypt_password(passwd);
|
||||
|
||||
mysql_options(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *) &mon->connect_timeout);
|
||||
mysql_options(database->con, MYSQL_OPT_READ_TIMEOUT, (void *) &mon->read_timeout);
|
||||
mysql_options(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *) &mon->write_timeout);
|
||||
|
||||
mysql_optionsv(database->con, MYSQL_OPT_CONNECT_TIMEOUT, (void *) &mon->connect_timeout);
|
||||
mysql_optionsv(database->con, MYSQL_OPT_READ_TIMEOUT, (void *) &mon->read_timeout);
|
||||
mysql_optionsv(database->con, MYSQL_OPT_WRITE_TIMEOUT, (void *) &mon->write_timeout);
|
||||
mysql_optionsv(database->con, MYSQL_PLUGIN_DIR, get_connector_plugindir());
|
||||
time_t start = time(NULL);
|
||||
bool result = (mxs_mysql_real_connect(database->con, database->server, uname, dpwd) != NULL);
|
||||
time_t end = time(NULL);
|
||||
|
||||
@ -137,6 +137,17 @@ void set_execdir(char* param)
|
||||
execdir = param;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connector plugin directory.
|
||||
* @param str Path to directory
|
||||
*/
|
||||
void set_connector_plugindir(char* param)
|
||||
{
|
||||
MXS_FREE(connector_plugindir);
|
||||
clean_up_pathname(param);
|
||||
connector_plugindir = param;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory with all the modules.
|
||||
* @return The module directory
|
||||
@ -235,3 +246,12 @@ char* get_execdir()
|
||||
{
|
||||
return execdir ? execdir : (char*) default_execdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connector plugin directory
|
||||
* @return The connector plugin directory
|
||||
*/
|
||||
char* get_connector_plugindir()
|
||||
{
|
||||
return connector_plugindir ? connector_plugindir : (char*) default_connector_plugindir;
|
||||
}
|
||||
|
||||
@ -11,11 +11,14 @@
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <maxscale/query_classifier.h>
|
||||
#include "maxscale/query_classifier.h"
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <maxscale/modutil.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/platform.h>
|
||||
#include <maxscale/pcre2.h>
|
||||
#include <maxscale/utils.h>
|
||||
#include "maxscale/trxboundaryparser.hh"
|
||||
|
||||
#include "../core/maxscale/modules.h"
|
||||
|
||||
@ -34,10 +37,13 @@ struct type_name_info
|
||||
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 qc_trx_parse_using_t qc_trx_parse_using = QC_TRX_PARSE_USING_PARSER;
|
||||
|
||||
|
||||
bool qc_setup(const char* plugin_name, const char* plugin_args)
|
||||
{
|
||||
@ -46,8 +52,8 @@ bool qc_setup(const char* plugin_name, const char* plugin_args)
|
||||
|
||||
if (!plugin_name || (*plugin_name == 0))
|
||||
{
|
||||
MXS_NOTICE("No query classifier specified, using default '%s'.", default_qc_name);
|
||||
plugin_name = default_qc_name;
|
||||
MXS_NOTICE("No query classifier specified, using default '%s'.", DEFAULT_QC_NAME);
|
||||
plugin_name = DEFAULT_QC_NAME;
|
||||
}
|
||||
|
||||
int32_t rv = QC_RESULT_ERROR;
|
||||
@ -60,28 +66,67 @@ bool qc_setup(const char* plugin_name, const char* plugin_args)
|
||||
if (rv != QC_RESULT_OK)
|
||||
{
|
||||
qc_unload(classifier);
|
||||
classifier = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return (rv == QC_RESULT_OK) ? true : false;
|
||||
}
|
||||
|
||||
bool qc_process_init(void)
|
||||
bool qc_process_init(uint32_t kind)
|
||||
{
|
||||
QC_TRACE();
|
||||
ss_dassert(classifier);
|
||||
|
||||
return classifier->qc_process_init() == 0;
|
||||
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_PARSER") == 0)
|
||||
{
|
||||
qc_trx_parse_using = QC_TRX_PARSE_USING_PARSER;
|
||||
MXS_NOTICE("Transaction detection using custom PARSER.");
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_NOTICE("QC_TRX_PARSE_USING set, but the value %s is not known. "
|
||||
"Parsing using QC.", parse_using);
|
||||
}
|
||||
}
|
||||
|
||||
bool rc = qc_thread_init(QC_INIT_SELF);
|
||||
|
||||
if (rc)
|
||||
{
|
||||
if (kind & QC_INIT_PLUGIN)
|
||||
{
|
||||
rc = classifier->qc_process_init() == 0;
|
||||
|
||||
if (!rc)
|
||||
{
|
||||
qc_thread_end(QC_INIT_SELF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void qc_process_end(void)
|
||||
void qc_process_end(uint32_t kind)
|
||||
{
|
||||
QC_TRACE();
|
||||
ss_dassert(classifier);
|
||||
|
||||
classifier->qc_process_end();
|
||||
classifier = NULL;
|
||||
if (kind & QC_INIT_PLUGIN)
|
||||
{
|
||||
classifier->qc_process_end();
|
||||
}
|
||||
|
||||
qc_thread_end(QC_INIT_SELF);
|
||||
}
|
||||
|
||||
QUERY_CLASSIFIER* qc_load(const char* plugin_name)
|
||||
@ -104,32 +149,43 @@ void qc_unload(QUERY_CLASSIFIER* classifier)
|
||||
{
|
||||
// TODO: The module loading/unloading needs an overhaul before we
|
||||
// TODO: actually can unload something.
|
||||
classifier = NULL;
|
||||
}
|
||||
|
||||
bool qc_thread_init(void)
|
||||
bool qc_thread_init(uint32_t kind)
|
||||
{
|
||||
QC_TRACE();
|
||||
ss_dassert(classifier);
|
||||
|
||||
return classifier->qc_thread_init() == 0;
|
||||
bool rc = true;
|
||||
|
||||
if (kind & QC_INIT_PLUGIN)
|
||||
{
|
||||
rc = classifier->qc_thread_init() == 0;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void qc_thread_end(void)
|
||||
void qc_thread_end(uint32_t kind)
|
||||
{
|
||||
QC_TRACE();
|
||||
ss_dassert(classifier);
|
||||
|
||||
return classifier->qc_thread_end();
|
||||
if (kind & QC_INIT_PLUGIN)
|
||||
{
|
||||
classifier->qc_thread_end();
|
||||
}
|
||||
}
|
||||
|
||||
qc_parse_result_t qc_parse(GWBUF* query)
|
||||
qc_parse_result_t qc_parse(GWBUF* query, uint32_t collect)
|
||||
{
|
||||
QC_TRACE();
|
||||
ss_dassert(classifier);
|
||||
|
||||
int32_t result = QC_QUERY_INVALID;
|
||||
|
||||
classifier->qc_parse(query, &result);
|
||||
classifier->qc_parse(query, collect, &result);
|
||||
|
||||
return (qc_parse_result_t)result;
|
||||
}
|
||||
@ -778,3 +834,70 @@ char* qc_typemask_to_string(uint32_t types)
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static uint32_t qc_get_trx_type_mask_using_qc(GWBUF* stmt)
|
||||
{
|
||||
uint32_t type_mask = qc_get_type_mask(stmt);
|
||||
|
||||
if (qc_query_is_type(type_mask, QUERY_TYPE_WRITE) &&
|
||||
qc_query_is_type(type_mask, QUERY_TYPE_COMMIT))
|
||||
{
|
||||
// This is a commit reported for "CREATE TABLE...",
|
||||
// "DROP TABLE...", etc. that cause an implicit commit.
|
||||
type_mask = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only START TRANSACTION can be explicitly READ or WRITE.
|
||||
if (!(type_mask & QUERY_TYPE_BEGIN_TRX))
|
||||
{
|
||||
// So, strip them away for everything else.
|
||||
type_mask &= ~(QUERY_TYPE_WRITE | QUERY_TYPE_READ);
|
||||
}
|
||||
|
||||
// Then leave only the bits related to transaction and
|
||||
// autocommit state.
|
||||
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_parser(GWBUF* stmt)
|
||||
{
|
||||
maxscale::TrxBoundaryParser parser;
|
||||
|
||||
return parser.type_mask_of(stmt);
|
||||
}
|
||||
|
||||
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_PARSER:
|
||||
type_mask = qc_get_trx_type_mask_using_parser(stmt);
|
||||
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);
|
||||
}
|
||||
@ -1379,3 +1379,22 @@ void server_clear_status(SERVER *server, int bit)
|
||||
}
|
||||
spinlock_release(&server->lock);
|
||||
}
|
||||
|
||||
bool server_is_mxs_service(const SERVER *server)
|
||||
{
|
||||
bool rval = false;
|
||||
|
||||
/** Do a coarse check for local server pointing to a MaxScale service */
|
||||
if (strcmp(server->name, "127.0.0.1") == 0 ||
|
||||
strcmp(server->name, "::1") == 0 ||
|
||||
strcmp(server->name, "localhost") == 0 ||
|
||||
strcmp(server->name, "localhost.localdomain") == 0)
|
||||
{
|
||||
if (service_port_is_used(server->port))
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
@ -2299,3 +2299,29 @@ void service_print_users(DCB *dcb, const SERVICE *service)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool service_port_is_used(unsigned short port)
|
||||
{
|
||||
bool rval = false;
|
||||
spinlock_acquire(&service_spin);
|
||||
|
||||
for (SERVICE *service = allServices; service && !rval; service = service->next)
|
||||
{
|
||||
spinlock_acquire(&service->spin);
|
||||
|
||||
for (SERV_LISTENER *proto = service->ports; proto; proto = proto->next)
|
||||
{
|
||||
if (proto->port == port)
|
||||
{
|
||||
rval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&service->spin);
|
||||
}
|
||||
|
||||
spinlock_release(&service_spin);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
@ -11,48 +11,27 @@
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file spinlock.c - Spinlock operations for the MariaDB Corporation MaxScale
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 10/06/13 Mark Riddoch Initial implementation
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#include <maxscale/spinlock.h>
|
||||
#include <maxscale/atomic.h>
|
||||
#include <time.h>
|
||||
#include <maxscale/debug.h>
|
||||
|
||||
/**
|
||||
* Initialise a spinlock.
|
||||
*
|
||||
* @param lock The spinlock to initialise.
|
||||
*/
|
||||
void
|
||||
spinlock_init(SPINLOCK *lock)
|
||||
|
||||
void spinlock_init(SPINLOCK *lock)
|
||||
{
|
||||
lock->lock = 0;
|
||||
#if SPINLOCK_PROFILE
|
||||
lock->spins = 0;
|
||||
lock->maxspins = 0;
|
||||
lock->acquired = 0;
|
||||
lock->waiting = 0;
|
||||
lock->max_waiting = 0;
|
||||
lock->contended = 0;
|
||||
lock->owner = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire a spinlock.
|
||||
*
|
||||
* @param lock The spinlock to acquire
|
||||
*/
|
||||
void
|
||||
spinlock_acquire(const SPINLOCK *const_lock)
|
||||
void spinlock_acquire(const SPINLOCK *const_lock)
|
||||
{
|
||||
SPINLOCK *lock = (SPINLOCK*)const_lock;
|
||||
#if SPINLOCK_PROFILE
|
||||
@ -61,20 +40,14 @@ spinlock_acquire(const SPINLOCK *const_lock)
|
||||
atomic_add(&(lock->waiting), 1);
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
while (__sync_lock_test_and_set(&(lock->lock), 1))
|
||||
while (lock->lock)
|
||||
{
|
||||
#else
|
||||
while (atomic_add(&(lock->lock), 1) != 0)
|
||||
{
|
||||
atomic_add(&(lock->lock), -1);
|
||||
#endif
|
||||
#if SPINLOCK_PROFILE
|
||||
atomic_add(&(lock->spins), 1);
|
||||
spins++;
|
||||
atomic_add(&(lock->spins), 1);
|
||||
spins++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if SPINLOCK_PROFILE
|
||||
if (spins)
|
||||
{
|
||||
@ -90,42 +63,24 @@ spinlock_acquire(const SPINLOCK *const_lock)
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire a spinlock if it is not already locked.
|
||||
*
|
||||
* @param lock The spinlock to acquire
|
||||
* @return True if the spinlock was acquired, otherwise false
|
||||
*/
|
||||
int
|
||||
bool
|
||||
spinlock_acquire_nowait(const SPINLOCK *const_lock)
|
||||
{
|
||||
SPINLOCK *lock = (SPINLOCK*)const_lock;
|
||||
#ifdef __GNUC__
|
||||
if (__sync_lock_test_and_set(&(lock->lock), 1))
|
||||
{
|
||||
return FALSE;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (atomic_add(&(lock->lock), 1) != 0)
|
||||
{
|
||||
atomic_add(&(lock->lock), -1);
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SPINLOCK_PROFILE
|
||||
lock->acquired++;
|
||||
lock->owner = thread_self();
|
||||
#endif
|
||||
return TRUE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release a spinlock.
|
||||
*
|
||||
* @param lock The spinlock to release
|
||||
*/
|
||||
void
|
||||
spinlock_release(const SPINLOCK *const_lock)
|
||||
void spinlock_release(const SPINLOCK *const_lock)
|
||||
{
|
||||
SPINLOCK *lock = (SPINLOCK*)const_lock;
|
||||
ss_dassert(lock->lock != 0);
|
||||
@ -135,48 +90,32 @@ spinlock_release(const SPINLOCK *const_lock)
|
||||
lock->max_waiting = lock->waiting;
|
||||
}
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
__sync_synchronize(); /* Memory barrier. */
|
||||
lock->lock = 0;
|
||||
#else
|
||||
atomic_add(&(lock->lock), -1);
|
||||
#endif
|
||||
|
||||
__sync_lock_release(&lock->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report statistics on a spinlock. This only has an effect if the
|
||||
* spinlock code has been compiled with the SPINLOCK_PROFILE option set.
|
||||
*
|
||||
* NB A callback function is used to return the data rather than
|
||||
* merely printing to a DCB in order to avoid a dependency on the DCB
|
||||
* form the spinlock code and also to facilitate other uses of the
|
||||
* statistics reporting.
|
||||
*
|
||||
* @param lock The spinlock to report on
|
||||
* @param reporter The callback function to pass the statistics to
|
||||
* @param hdl A handle that is passed to the reporter function
|
||||
*/
|
||||
void
|
||||
spinlock_stats(const SPINLOCK *lock, void (*reporter)(void *, char *, int), void *hdl)
|
||||
void spinlock_stats(const SPINLOCK *lock, void (*reporter)(void *, char *, int), void *hdl)
|
||||
{
|
||||
#if SPINLOCK_PROFILE
|
||||
reporter(hdl, "Spinlock acquired", lock->acquired);
|
||||
if (lock->acquired)
|
||||
{
|
||||
reporter(hdl, "Total no. of spins", lock->spins);
|
||||
reporter(hdl, "Average no. of spins (overall)",
|
||||
lock->spins / lock->acquired);
|
||||
if (lock->acquired)
|
||||
{
|
||||
reporter(hdl, "Average no. of spins (overall)", lock->spins / lock->acquired);
|
||||
}
|
||||
if (lock->contended)
|
||||
{
|
||||
reporter(hdl, "Average no. of spins (when contended)",
|
||||
lock->spins / lock->contended);
|
||||
reporter(hdl, "Average no. of spins (when contended)", lock->spins / lock->contended);
|
||||
}
|
||||
reporter(hdl, "Maximum no. of spins", lock->maxspins);
|
||||
reporter(hdl, "Maximim no. of blocked threads",
|
||||
lock->max_waiting);
|
||||
reporter(hdl, "Maximim no. of blocked threads", lock->max_waiting);
|
||||
reporter(hdl, "Contended locks", lock->contended);
|
||||
reporter(hdl, "Contention percentage",
|
||||
(lock->contended * 100) / lock->acquired);
|
||||
if (lock->acquired)
|
||||
{
|
||||
reporter(hdl, "Contention percentage", (lock->contended * 100) / lock->acquired);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -13,11 +13,14 @@ add_executable(test_queuemanager testqueuemanager.c)
|
||||
add_executable(test_server testserver.c)
|
||||
add_executable(test_service testservice.c)
|
||||
add_executable(test_spinlock testspinlock.c)
|
||||
add_executable(test_trxcompare testtrxcompare.cc ../../../query_classifier/test/testreader.cc)
|
||||
add_executable(test_trxtracking testtrxtracking.cc)
|
||||
add_executable(test_users testusers.c)
|
||||
add_executable(testfeedback testfeedback.c)
|
||||
add_executable(testmaxscalepcre2 testmaxscalepcre2.c)
|
||||
add_executable(testmodulecmd testmodulecmd.c)
|
||||
add_executable(testconfig testconfig.c)
|
||||
add_executable(trxboundaryparser_profile trxboundaryparser_profile.cc)
|
||||
target_link_libraries(test_adminusers maxscale-common)
|
||||
target_link_libraries(test_buffer maxscale-common)
|
||||
target_link_libraries(test_dcb maxscale-common)
|
||||
@ -33,11 +36,14 @@ target_link_libraries(test_queuemanager maxscale-common)
|
||||
target_link_libraries(test_server maxscale-common)
|
||||
target_link_libraries(test_service maxscale-common)
|
||||
target_link_libraries(test_spinlock maxscale-common)
|
||||
target_link_libraries(test_trxcompare maxscale-common)
|
||||
target_link_libraries(test_trxtracking maxscale-common)
|
||||
target_link_libraries(test_users maxscale-common)
|
||||
target_link_libraries(testfeedback maxscale-common)
|
||||
target_link_libraries(testmaxscalepcre2 maxscale-common)
|
||||
target_link_libraries(testmodulecmd maxscale-common)
|
||||
target_link_libraries(testconfig maxscale-common)
|
||||
target_link_libraries(trxboundaryparser_profile maxscale-common)
|
||||
add_test(TestAdminUsers test_adminusers)
|
||||
add_test(TestBuffer test_buffer)
|
||||
add_test(TestDCB test_dcb)
|
||||
@ -58,6 +64,16 @@ add_test(TestSpinlock test_spinlock)
|
||||
add_test(TestUsers test_users)
|
||||
add_test(TestModulecmd testmodulecmd)
|
||||
add_test(TestConfig testconfig)
|
||||
add_test(TestTrxTracking test_trxtracking)
|
||||
add_test(TestTrxCompare_Create test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/create.test)
|
||||
add_test(TestTrxCompare_Delete test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/delete.test)
|
||||
add_test(TestTrxCompare_Insert test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/insert.test)
|
||||
add_test(TestTrxCompare_Join test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/join.test)
|
||||
add_test(TestTrxCompare_Select test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/select.test)
|
||||
add_test(TestTrxCompare_Set test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/set.test)
|
||||
add_test(TestTrxCompare_Update test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/update.test)
|
||||
add_test(TestTrxCompare_MaxScale test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/maxscale.test)
|
||||
|
||||
|
||||
# This test requires external dependencies and thus cannot be run
|
||||
# as a part of the core test set
|
||||
|
||||
@ -578,6 +578,43 @@ void test_large_packets()
|
||||
}
|
||||
}
|
||||
|
||||
char* bypass_whitespace(char* sql)
|
||||
{
|
||||
return modutil_MySQL_bypass_whitespace(sql, strlen(sql));
|
||||
}
|
||||
|
||||
void test_bypass_whitespace()
|
||||
{
|
||||
char* sql;
|
||||
|
||||
sql = bypass_whitespace("SELECT");
|
||||
ss_info_dassert(*sql == 'S', "1");
|
||||
|
||||
sql = bypass_whitespace(" SELECT");
|
||||
ss_info_dassert(*sql == 'S', "2");
|
||||
|
||||
sql = bypass_whitespace("\tSELECT");
|
||||
ss_info_dassert(*sql == 'S', "3");
|
||||
|
||||
sql = bypass_whitespace("\nSELECT");
|
||||
ss_info_dassert(*sql == 'S', "4");
|
||||
|
||||
sql = bypass_whitespace("/* comment */SELECT");
|
||||
ss_info_dassert(*sql == 'S', "5");
|
||||
|
||||
sql = bypass_whitespace(" /* comment */ SELECT");
|
||||
ss_info_dassert(*sql == 'S', "6");
|
||||
|
||||
sql = bypass_whitespace("-- comment\nSELECT");
|
||||
ss_info_dassert(*sql == 'S', "7");
|
||||
|
||||
sql = bypass_whitespace("-- comment\n /* comment */ SELECT");
|
||||
ss_info_dassert(*sql == 'S', "8");
|
||||
|
||||
sql = bypass_whitespace("# comment\nSELECT");
|
||||
ss_info_dassert(*sql == 'S', "9");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int result = 0;
|
||||
@ -591,5 +628,6 @@ int main(int argc, char **argv)
|
||||
test_strnchr_esc();
|
||||
test_strnchr_esc_mysql();
|
||||
test_large_packets();
|
||||
test_bypass_whitespace();
|
||||
exit(result);
|
||||
}
|
||||
|
||||
237
server/core/test/testtrxcompare.cc
Normal file
237
server/core/test/testtrxcompare.cc
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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 <unistd.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "../maxscale/query_classifier.h"
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/paths.h>
|
||||
#include <maxscale/protocol/mysql.h>
|
||||
#include "../../../query_classifier/test/testreader.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
char USAGE[] =
|
||||
"test_trxcompare [-v] (-s stmt)|[file]"
|
||||
"\n"
|
||||
"-s test single statement\n"
|
||||
"-v 0, only return code\n"
|
||||
" 1, failed cases (default)\n"
|
||||
" 2, successful transactional cases\n"
|
||||
" 4, successful cases\n"
|
||||
" 7, all cases\n";
|
||||
|
||||
enum verbosity_t
|
||||
{
|
||||
VERBOSITY_NOTHING = 0, // 000
|
||||
VERBOSITY_FAILED = 1, // 001
|
||||
VERBOSITY_SUCCESSFUL_TRANSACTIONAL = 2, // 010
|
||||
VERBOSITY_SUCCESSFUL = 4, // 100
|
||||
VERBOSITY_ALL = 7, // 111
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
class Tester
|
||||
{
|
||||
public:
|
||||
Tester(uint32_t verbosity)
|
||||
: m_verbosity(verbosity)
|
||||
{
|
||||
}
|
||||
|
||||
int run(const char* zStmt)
|
||||
{
|
||||
int rc = EXIT_SUCCESS;
|
||||
|
||||
GWBUF* pStmt = create_gwbuf(zStmt);
|
||||
|
||||
uint32_t type_mask_qc = qc_get_trx_type_mask_using(pStmt, QC_TRX_PARSE_USING_QC);
|
||||
uint32_t type_mask_parser = qc_get_trx_type_mask_using(pStmt, QC_TRX_PARSE_USING_PARSER);
|
||||
|
||||
gwbuf_free(pStmt);
|
||||
|
||||
if (type_mask_qc == type_mask_parser)
|
||||
{
|
||||
if ((m_verbosity & VERBOSITY_SUCCESSFUL) ||
|
||||
((m_verbosity & VERBOSITY_SUCCESSFUL_TRANSACTIONAL) && (type_mask_qc != 0)))
|
||||
{
|
||||
char* zType_mask = qc_typemask_to_string(type_mask_qc);
|
||||
|
||||
cout << zStmt << ": " << zType_mask << endl;
|
||||
|
||||
MXS_FREE(zType_mask);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_verbosity & VERBOSITY_FAILED)
|
||||
{
|
||||
char* zType_mask_qc = qc_typemask_to_string(type_mask_qc);
|
||||
char* zType_mask_parser = qc_typemask_to_string(type_mask_parser);
|
||||
|
||||
cout << zStmt << "\n"
|
||||
<< " QC : " << zType_mask_qc << "\n"
|
||||
<< " PARSER: " << zType_mask_parser << endl;
|
||||
|
||||
MXS_FREE(zType_mask_qc);
|
||||
MXS_FREE(zType_mask_parser);
|
||||
}
|
||||
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int run(istream& in)
|
||||
{
|
||||
int rc = EXIT_SUCCESS;
|
||||
|
||||
maxscale::TestReader reader(in);
|
||||
|
||||
string stmt;
|
||||
|
||||
while (reader.get_statement(stmt) == maxscale::TestReader::RESULT_STMT)
|
||||
{
|
||||
if (run(stmt.c_str()) == EXIT_FAILURE)
|
||||
{
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
private:
|
||||
Tester(const Tester&);
|
||||
Tester& operator = (const Tester&);
|
||||
|
||||
private:
|
||||
uint32_t m_verbosity;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int rc = EXIT_SUCCESS;
|
||||
|
||||
int verbosity = VERBOSITY_FAILED;
|
||||
const char* zStatement = NULL;
|
||||
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "s:v:")) != -1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 's':
|
||||
zStatement = optarg;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbosity = atoi(optarg);
|
||||
break;
|
||||
|
||||
default:
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if ((rc == EXIT_SUCCESS) && (verbosity >= VERBOSITY_NOTHING) && (verbosity <= VERBOSITY_ALL))
|
||||
{
|
||||
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))
|
||||
{
|
||||
Tester tester(verbosity);
|
||||
|
||||
int n = argc - (optind - 1);
|
||||
|
||||
if (zStatement)
|
||||
{
|
||||
rc = tester.run(zStatement);
|
||||
}
|
||||
else if (n == 1)
|
||||
{
|
||||
rc = tester.run(cin);
|
||||
}
|
||||
else
|
||||
{
|
||||
ss_dassert(n == 2);
|
||||
|
||||
ifstream in(argv[argc - 1]);
|
||||
|
||||
if (in)
|
||||
{
|
||||
rc = tester.run(in);
|
||||
}
|
||||
else
|
||||
{
|
||||
cerr << "error: Could not open " << argv[argc - 1] << "." << 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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << USAGE << endl;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
455
server/core/test/testtrxtracking.cc
Normal file
455
server/core/test/testtrxtracking.cc
Normal file
@ -0,0 +1,455 @@
|
||||
/*
|
||||
* 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 "../core/maxscale/query_classifier.h"
|
||||
|
||||
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[] =
|
||||
{
|
||||
{ "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;
|
||||
|
||||
string s;
|
||||
|
||||
s = base;
|
||||
if (!test(getter, s.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))
|
||||
{
|
||||
// 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;
|
||||
|
||||
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_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;
|
||||
}
|
||||
116
server/core/test/trxboundaryparser_profile.cc
Normal file
116
server/core/test/trxboundaryparser_profile.cc
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 <iomanip>
|
||||
#include <iostream>
|
||||
#include <maxscale/paths.h>
|
||||
#include "../maxscale/trxboundaryparser.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
char USAGE[] = "usage: trxboundaryparser -n count -s statement\n";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int rc = EXIT_SUCCESS;
|
||||
|
||||
int nCount = 0;
|
||||
const char* zStatement = NULL;
|
||||
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "n:s:")) != -1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 'n':
|
||||
nCount = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
zStatement = optarg;
|
||||
break;
|
||||
|
||||
default:
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if ((rc == EXIT_SUCCESS) && zStatement && (nCount > 0))
|
||||
{
|
||||
rc = EXIT_FAILURE;
|
||||
|
||||
set_datadir(strdup("/tmp"));
|
||||
set_langdir(strdup("."));
|
||||
set_process_datadir(strdup("/tmp"));
|
||||
|
||||
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
|
||||
{
|
||||
size_t len = strlen(zStatement);
|
||||
maxscale::TrxBoundaryParser parser;
|
||||
|
||||
struct timespec start;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
|
||||
|
||||
for (int i = 0; i < nCount; ++i)
|
||||
{
|
||||
parser.type_mask_of(zStatement, len);
|
||||
}
|
||||
|
||||
struct timespec finish;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &finish);
|
||||
|
||||
struct timespec diff = timespec_subtract(finish, start);
|
||||
|
||||
cout << "Time:" << diff.tv_sec << "." << setfill('0') << setw(9) << diff.tv_nsec << endl;
|
||||
|
||||
mxs_log_finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
cerr << "error: Could not initialize log." << endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << USAGE << endl;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
Reference in New Issue
Block a user