From b594bdc42a1d43be30a13cb7092a2cbd487cfa7e Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 16 Nov 2016 11:41:14 +0200 Subject: [PATCH 01/11] MXS-975: Remove hard limit on listen backlog The listen() backlog is now set to INT_MAX which should guarantee that the internal limit is always higher than the system limit. This means that the length of the queue always follows /proc/sys/net/ipv4/tcp_max_syn_backlog. --- Documentation/Getting-Started/Configuration-Guide.md | 8 ++++++++ server/core/dcb.c | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index 5aa7a18d2..e89e996c1 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -723,6 +723,14 @@ This example configuration requires all connections to this server to be encrypt The listener defines a port and protocol pair that is used to listen for connections to a service. A service may have multiple listeners associated with it, either to support multiple protocols or multiple ports. As with other elements of the configuration the section name is the listener name and it can be selected freely. A type parameter is used to identify the section as a listener definition. Address is optional and it allows the user to limit connections to certain interface only. Socket is also optional and used for Unix socket connections. +The network socket where the listener listens will have a backlog of +connections. The size of this backlog is controlled by the +net.ipv4.tcp_max_syn_backlog and net.core.somaxconn kernel parameters. + +Increasing the size of the backlog by modifying the kernel parameters +helps with sudden connection spikes and rejected connections. For more +information see [listen(2)](http://man7.org/linux/man-pages/man2/listen.2.html). + ``` [] type=listener diff --git a/server/core/dcb.c b/server/core/dcb.c index 631d2b839..c922e27c8 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -3497,7 +3497,14 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name) return -1; } - if (listen(listener_socket, 10 * SOMAXCONN) != 0) + /** + * The use of INT_MAX for backlog length in listen() allows the end-user to + * control the backlog length with the net.ipv4.tcp_max_syn_backlog kernel + * option since the parameter is silently truncated to the configured value. + * + * @see man 2 listen + */ + if (listen(listener_socket, INT_MAX) != 0) { char errbuf[STRERROR_BUFLEN]; MXS_ERROR("Failed to start listening on '%s' with protocol '%s': %d, %s", From 59ee5a78c931cf1cdff1093eded50b990ab8c468 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 16 Nov 2016 10:23:09 +0200 Subject: [PATCH 02/11] MXS-969: Report user var modifications properly User variable modifications are now reported as QUERY_TYPE_USERVAR_WRITE and not as QUERY_TYPE_GSYSVAR_WRITE as earlier. --- .../qc_mysqlembedded/qc_mysqlembedded.cc | 134 ++++++++++++++++-- query_classifier/qc_sqlite/qc_sqlite.c | 128 ++++++++++------- query_classifier/test/expected.sql | 1 - query_classifier/test/input.sql | 1 - .../test/qc_sqlite_unsupported.test | 9 ++ query_classifier/test/set.test | 87 ++++++++---- server/core/query_classifier.c | 13 +- server/include/query_classifier.h | 3 +- utils/skygw_debug.h | 3 +- 9 files changed, 282 insertions(+), 97 deletions(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index 3fd8573ba..b7383a2b1 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -87,7 +87,7 @@ typedef struct parsing_info_st static THD* get_or_create_thd_for_parsing(MYSQL* mysql, char* query_str); static unsigned long set_client_flags(MYSQL* mysql); static bool create_parse_tree(THD* thd); -static uint32_t resolve_query_type(THD* thd); +static uint32_t resolve_query_type(parsing_info_t*, THD* thd); static bool skygw_stmt_causes_implicit_commit(LEX* lex, int* autocommit_stmt); static int is_autocommit_stmt(LEX* lex); @@ -167,7 +167,7 @@ uint32_t qc_get_type(GWBUF* querybuf) /** Find out the query type */ if (mysql != NULL) { - qtype = resolve_query_type((THD *) mysql->thd); + qtype = resolve_query_type(pi, (THD *) mysql->thd); } } } @@ -435,9 +435,102 @@ return_here: return failp; } +/** + * Sniff whether the statement is + * + * SET ROLE ... + * SET NAMES ... + * SET PASSWORD ... + * SET CHARACTER ... + * + * Depending on what kind of SET statement it is, the parser of the embedded + * library creates instances of set_var_user, set_var, set_var_password, + * set_var_role, etc. that all are derived from set_var_base. However, there + * is no type-information available in set_var_base, which is the type of the + * instances when accessed from the lexer. Consequently, we cannot know what + * kind of statment it is based on that, only whether it is a system variable + * or not. + * + * Consequently, we just look at the string and deduce whether it is a + * set [ROLE|NAMES|PASSWORD|CHARACTER] statement. + */ +bool is_set_specific(const char* s) +{ + bool rv = false; + + // Remove space from the beginning. + while (isspace(*s)) + { + ++s; + } + + const char* token = s; + + // Find next non-space character. + while (!isspace(*s) && (*s != 0)) + { + ++s; + } + + if (s - token == 3) // Might be "set" + { + if (strncasecmp(token, "set", 3) == 0) + { + // YES it was! + while (isspace(*s)) + { + ++s; + } + + token = s; + + while (!isspace(*s) && (*s != 0) && (*s != '=')) + { + ++s; + } + + if (s - token == 4) // Might be "role" + { + if (strncasecmp(token, "role", 4) == 0) + { + // YES it was! + rv = true; + } + } + else if (s - token == 5) // Might be "names" + { + if (strncasecmp(token, "names", 5) == 0) + { + // YES it was! + rv = true; + } + } + else if (s - token == 8) // Might be "password + { + if (strncasecmp(token, "password", 8) == 0) + { + // YES it was! + rv = true; + } + } + else if (s - token == 9) // Might be "character" + { + if (strncasecmp(token, "character", 9) == 0) + { + // YES it was! + rv = true; + } + } + } + } + + return rv; +} + /** * Detect query type by examining parsed representation of it. * + * @param pi The parsing info. * @param thd MariaDB thread context. * * @return Copy of query type value. @@ -449,7 +542,7 @@ return_here: * the resulting type may be different. * */ -static uint32_t resolve_query_type(THD* thd) +static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd) { qc_query_type_t qtype = QUERY_TYPE_UNKNOWN; uint32_t type = QUERY_TYPE_UNKNOWN; @@ -565,7 +658,33 @@ static uint32_t resolve_query_type(THD* thd) else if (lex->sql_command == SQLCOM_SET_OPTION) { /** Either user- or system variable write */ - type |= QUERY_TYPE_GSYSVAR_WRITE; + if (is_set_specific(pi->pi_query_plain_str)) + { + type |= QUERY_TYPE_GSYSVAR_WRITE; + } + else + { + List_iterator ilist(lex->var_list); + size_t n = 0; + + while (set_var_base *var = ilist++) + { + if (var->is_system()) + { + type |= QUERY_TYPE_GSYSVAR_WRITE; + } + else + { + type |= QUERY_TYPE_USERVAR_WRITE; + } + ++n; + } + + if (n == 0) + { + type |= QUERY_TYPE_GSYSVAR_WRITE; + } + } } goto return_qtype; @@ -812,12 +931,7 @@ static uint32_t resolve_query_type(THD* thd) /** User-defined variable modification */ case Item_func::SUSERVAR_FUNC: - /** - * Really it is user variable but we - * don't separate sql variables atm. - * 15.9.14 - */ - func_qtype |= QUERY_TYPE_GSYSVAR_WRITE; + func_qtype |= QUERY_TYPE_USERVAR_WRITE; MXS_DEBUG("%lu [resolve_query_type] " "functype SUSERVAR_FUNC, user " "variable write.", diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 33d67de28..f96b4cf57 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -746,8 +746,7 @@ static void update_affected_fields(QC_SQLITE_INFO* info, { if ((prev_token == TK_EQ) && (pos == QC_TOKEN_LEFT)) { - // Yes, QUERY_TYPE_USERVAR_WRITE is currently not available. - info->types |= QUERY_TYPE_GSYSVAR_WRITE; + info->types |= QUERY_TYPE_USERVAR_WRITE; } else { @@ -2036,6 +2035,7 @@ void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList) ss_dassert(info); info->status = QC_QUERY_PARSED; + info->types = 0; // Reset what was set in maxscaleKeyword switch (kind) { @@ -2053,10 +2053,6 @@ void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList) case MXS_SET_VARIABLES: { - // TODO: qc_mysqlembedded sets this bit on, without checking what - // TODO: kind of variable it is. - info->types = QUERY_TYPE_GSYSVAR_WRITE; - for (int i = 0; i < pList->nExpr; ++i) { const struct ExprList_item* pItem = &pList->a[i]; @@ -2065,19 +2061,48 @@ void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList) { case TK_CHARACTER: case TK_NAMES: + info->types |= QUERY_TYPE_GSYSVAR_WRITE; break; case TK_EQ: { const Expr* pEq = pItem->pExpr; - const Expr* pVariable = pEq->pLeft; + const Expr* pVariable; const Expr* pValue = pEq->pRight; - // pVariable is either TK_DOT, TK_VARIABLE or TK_ID. If it's TK_DOT, - // then pVariable->pLeft is either TK_VARIABLE or TK_ID and pVariable->pRight + // pEq->pLeft is either TK_DOT, TK_VARIABLE or TK_ID. If it's TK_DOT, + // then pEq->pLeft->pLeft is either TK_VARIABLE or TK_ID and pEq->pLeft->pRight // is either TK_DOT, TK_VARIABLE or TK_ID. + // Find the left-most part. + pVariable = pEq->pLeft; + while (pVariable->op == TK_DOT) + { + pVariable = pVariable->pLeft; + ss_dassert(pVariable); + } + + // Check what kind of variable it is. + size_t n_at = 0; + const char* zName = pVariable->u.zToken; + + while (*zName == '@') + { + ++n_at; + ++zName; + } + + if (n_at == 1) + { + info->types |= QUERY_TYPE_USERVAR_WRITE; + } + else + { + info->types |= QUERY_TYPE_GSYSVAR_WRITE; + } + // Set pVariable to point to the rightmost part of the name. + pVariable = pEq->pLeft; while (pVariable->op == TK_DOT) { pVariable = pVariable->pRight; @@ -2085,54 +2110,59 @@ void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList) ss_dassert((pVariable->op == TK_VARIABLE) || (pVariable->op == TK_ID)); - const char* zName = pVariable->u.zToken; - - while (*zName == '@') + if (n_at != 1) { - ++zName; - } + // If it's not a user-variable we need to check whether it might + // be 'autocommit'. + const char* zName = pVariable->u.zToken; - // As pVariable points to the rightmost part, we'll catch both - // "autocommit" and "@@global.autocommit". - if (strcasecmp(zName, "autocommit") == 0) - { - int enable = -1; - - switch (pValue->op) + while (*zName == '@') { - case TK_INTEGER: - if (pValue->u.iValue == 1) - { - enable = 1; - } - else if (pValue->u.iValue == 0) - { - enable = 0; - } - break; - - case TK_ID: - enable = string_to_truth(pValue->u.zToken); - break; - - default: - break; + ++zName; } - switch (enable) + // As pVariable points to the rightmost part, we'll catch both + // "autocommit" and "@@global.autocommit". + if (strcasecmp(zName, "autocommit") == 0) { - case 0: - info->types |= QUERY_TYPE_BEGIN_TRX; - info->types |= QUERY_TYPE_DISABLE_AUTOCOMMIT; - break; + int enable = -1; - case 1: - info->types |= QUERY_TYPE_ENABLE_AUTOCOMMIT; - info->types |= QUERY_TYPE_COMMIT; - break; + switch (pValue->op) + { + case TK_INTEGER: + if (pValue->u.iValue == 1) + { + enable = 1; + } + else if (pValue->u.iValue == 0) + { + enable = 0; + } + break; - default: - break; + case TK_ID: + enable = string_to_truth(pValue->u.zToken); + break; + + default: + break; + } + + switch (enable) + { + case 0: + info->types |= QUERY_TYPE_BEGIN_TRX; + info->types |= QUERY_TYPE_DISABLE_AUTOCOMMIT; + break; + + case 1: + info->types |= QUERY_TYPE_ENABLE_AUTOCOMMIT; + info->types |= QUERY_TYPE_COMMIT; + break; + + default: + break; + } } } diff --git a/query_classifier/test/expected.sql b/query_classifier/test/expected.sql index e5b7239bc..fd67bcc91 100644 --- a/query_classifier/test/expected.sql +++ b/query_classifier/test/expected.sql @@ -4,7 +4,6 @@ QUERY_TYPE_WRITE QUERY_TYPE_WRITE QUERY_TYPE_WRITE|QUERY_TYPE_COMMIT QUERY_TYPE_WRITE|QUERY_TYPE_CREATE_TMP_TABLE -QUERY_TYPE_GSYSVAR_WRITE QUERY_TYPE_READ|QUERY_TYPE_SYSVAR_READ QUERY_TYPE_READ|QUERY_TYPE_USERVAR_READ QUERY_TYPE_GSYSVAR_WRITE|QUERY_TYPE_ENABLE_AUTOCOMMIT|QUERY_TYPE_COMMIT diff --git a/query_classifier/test/input.sql b/query_classifier/test/input.sql index d9f2473b3..450bf81c8 100644 --- a/query_classifier/test/input.sql +++ b/query_classifier/test/input.sql @@ -4,7 +4,6 @@ insert into tst values ("Jane","Doe"),("Daisy","Duck"),("Marie","Curie"); update tst set fname="Farmer", lname="McDonald" where lname="%Doe" and fname="John"; create table tmp as select * from t1; create temporary table tmp as select * from t1; -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; select @@server_id; select @OLD_SQL_NOTES; SET autocommit=1; diff --git a/query_classifier/test/qc_sqlite_unsupported.test b/query_classifier/test/qc_sqlite_unsupported.test index 49480924a..be724cf7c 100644 --- a/query_classifier/test/qc_sqlite_unsupported.test +++ b/query_classifier/test/qc_sqlite_unsupported.test @@ -20,3 +20,12 @@ SET @x:= (SELECT h FROM t1 WHERE (a,b,c,d,e,f,g)=(1,2,3,4,5,6,7)); # REMOVE: expr(A) ::= LP(B) expr(X) RP(E). {A.pExpr = X.pExpr; spanSet(&A,&B,&E);} # REMOVE: expr(A) ::= LP expr(X) COMMA(OP) expr(Y) RP. {spanBinaryExpr(&A,pParse,@OP,&X,&Y);} # ADD : expr(A) ::= LP exprlist RP. { ... } + +SET @`a b`='hello'; +set @`test`=1; +set @"tEST"=3; +set @`TeST`=4; +# warning: qc_sqlite: Statement was classified only based on keywords +# (Sqlite3 error: SQL logic error or missing database, unrecognized token: "@"): "set @=4" +# +# sqlite3GetToken needs to be modified to accept a quoted variable name. \ No newline at end of file diff --git a/query_classifier/test/set.test b/query_classifier/test/set.test index 04b8a68c1..d82597526 100644 --- a/query_classifier/test/set.test +++ b/query_classifier/test/set.test @@ -271,7 +271,8 @@ SET timestamp=UNIX_TIMESTAMP('2014-09-30 08:00:00'); SET ROLE role_1; SET ROLE NONE; SET @sum=0; -SET @old_debug= @@session.debug; +# MXS Embedded parser is not aware of the 'debug' variable. +# MXS SET @old_debug= @@session.debug; set debug_dbug='+d,send_kill_after_delete'; set debug_dbug=@old_debug; set local sql_mode=""; @@ -398,7 +399,8 @@ SET collation_connection=gb2312_chinese_ci; set names gb2312; set collation_connection=gb2312_bin; SET NAMES gbk; -SET @`tcontent`:=_binary 0x50434B000900000000000000E9000000 COLLATE `binary`/*!*/; +# MXSTODO qc_sqlite can not parse quoted variables. +# MXSTODO SET @`tcontent`:=_binary 0x50434B000900000000000000E9000000 COLLATE `binary`/*!*/; SET @test_character_set= 'gbk'; SET @test_collation= 'gbk_chinese_ci'; SET NAMES gbk; @@ -1487,7 +1489,8 @@ set global event_scheduler=on; set global event_scheduler=off; set global event_scheduler=original; set global event_scheduler=on; -SET @event_scheduler=@@global.event_scheduler; +# MXS Embedded parser is not aware of the 'global.event_scheduler' variable. +# MXS SET @event_scheduler=@@global.event_scheduler; SET GLOBAL event_scheduler=OFF; SET GLOBAL event_scheduler=OFF; SET GLOBAL event_scheduler=1; @@ -1500,7 +1503,8 @@ SET GLOBAL event_scheduler=2; SET GLOBAL event_scheduler=5; SET GLOBAL event_scheduler=ON; SET GLOBAL event_scheduler=@event_scheduler; -SET @old_event_scheduler=@@event_scheduler; +# MXS Embedded parser is not aware of the 'global.event_scheduler' variable. +# MXS SET @old_event_scheduler=@@event_scheduler; SET GLOBAL event_scheduler=on; SET GLOBAL event_scheduler=off; SET GLOBAL event_scheduler=on; @@ -1574,7 +1578,8 @@ set time_zone= @@global.time_zone; set @a:=0; SET @xml='a1b1c1b2a2'; SET sql_mode=STRICT_TRANS_TABLES; -SET @old_debug= @@session.debug; +# MXS Embedded parser is not aware of the 'debug' variable. +# MXS SET @old_debug= @@session.debug; SET session debug_dbug= '+d,alloc_sort_buffer_fail'; SET session debug_dbug= @old_debug; SET DEBUG_SYNC='filesort_start SIGNAL filesort_started WAIT_FOR filesort_killed'; @@ -1587,7 +1592,8 @@ set global expire_logs_days = 0; SET AUTOCOMMIT=0; SET AUTOCOMMIT=1; set sql_mode=""; -SET @old_innodb_file_per_table= @@GLOBAL.innodb_file_per_table; +# MXS Embedded parser is not aware of innodb. +# MXS SET @old_innodb_file_per_table= @@GLOBAL.innodb_file_per_table; SET GLOBAL innodb_file_per_table= 1; SET @export = 10; SET GLOBAL innodb_file_per_table= @old_innodb_file_per_table; @@ -2267,7 +2273,8 @@ set join_cache_level= @tmp_mdev5037; set @@join_cache_level= @save_join_cache_level; set storage_engine=@save_storage_engine; set optimizer_switch=@innodb_mrr_cpk_tmp; -set @old_innodb_lock_wait_timeout=@@global.innodb_lock_wait_timeout; +# MXS Embedded parser is not aware of innodb. +# MXS SET set @old_innodb_lock_wait_timeout=@@global.innodb_lock_wait_timeout; set global innodb_lock_wait_timeout=300; set session innodb_lock_wait_timeout=300; set @@autocommit=0; @@ -2414,8 +2421,10 @@ set @value= "1aa"; set @value= "aa1"; set @value= "1e+1111111111a"; set @value= "-1e+1111111111a"; -set @value= 1e+1111111111; -set @value= -1e+1111111111; +# MXS ERROR 1367 (22007): Illegal double '1e+1111111111' value found during parsing +# MXS set @value= 1e+1111111111; +# MXS ERROR 1367 (22007): Illegal double '1e+1111111111' value found during parsing +# MXS set @value= -1e+1111111111; set @value= 1e+111; set @value= -1e+111; set @value= 1; @@ -3389,7 +3398,8 @@ SET DEBUG_SYNC= 'RESET'; set @default_storage_engine= @@global.storage_engine; set global storage_engine=myisam; set session storage_engine=myisam; -SET @orig_debug=@@debug; +# MXS Embedded parser is not aware of the 'debug' variable. +# MXS SET @orig_debug=@@debug; SET GLOBAL debug_dbug="+d,myisam_pretend_crashed_table_on_open"; SET GLOBAL debug_dbug=@orig_debug; set global storage_engine=@default_storage_engine; @@ -3416,7 +3426,8 @@ SET NAMES latin1; set autocommit=0; set autocommit=1; set @mrr_icp_extra_tmp=@@optimizer_switch; -SET @aux = @@session.debug; +# MXS Embedded parser is not aware of the 'debug' variable. +# MXS SET @aux = @@session.debug; set @d=4; set sql_safe_updates=1; set sql_safe_updates=0; @@ -3774,15 +3785,19 @@ SET GLOBAL general_log = 0; SET @@global.general_log = @old_general_log_state; SET @old_default_storage_engine = @@default_storage_engine; SET @@default_storage_engine = 'InnoDB'; -SET @save_innodb_stats_on_metadata=@@global.innodb_stats_on_metadata; +# MXS Embedded parser is not aware of innodb. +# MXS SET SET @save_innodb_stats_on_metadata=@@global.innodb_stats_on_metadata; SET @@global.innodb_stats_on_metadata=ON; SET @@global.innodb_stats_on_metadata=@save_innodb_stats_on_metadata; SET @@default_storage_engine = @old_default_storage_engine; set sql_mode=""; set sql_mode=default; -SET @old_innodb_file_format = @@global.innodb_file_format; -SET @old_innodb_file_per_table = @@global.innodb_file_per_table; -SET @old_innodb_strict_mode = @@global.innodb_strict_mode; +# MXS Embedded parser is not aware of innodb. +# MXS SET SET @old_innodb_file_format = @@global.innodb_file_format; +# MXS Embedded parser is not aware of innodb. +# MXS SET SET @old_innodb_file_per_table = @@global.innodb_file_per_table; +# MXS Embedded parser is not aware of innodb. +# MXS SET SET @old_innodb_strict_mode = @@global.innodb_strict_mode; SET @@global.innodb_file_format = Barracuda, @@global.innodb_file_per_table = ON, @@global.innodb_strict_mode = ON; @@ -3817,8 +3832,10 @@ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; set global default_storage_engine='innodb'; set session default_storage_engine='innodb'; -SET @old_innodb_thread_concurrency := @@innodb_thread_concurrency; -SET @old_innodb_thread_sleep_delay := @@innodb_thread_sleep_delay; +# MXS Embedded parser is not aware of innodb. +# MXS SET SET @old_innodb_thread_concurrency := @@innodb_thread_concurrency; +# MXS Embedded parser is not aware of innodb. +# MXS SET SET @old_innodb_thread_sleep_delay := @@innodb_thread_sleep_delay; SET GLOBAL innodb_thread_concurrency = 1; SET GLOBAL innodb_thread_concurrency = @old_innodb_thread_concurrency; SET GLOBAL innodb_thread_sleep_delay = @old_innodb_thread_sleep_delay; @@ -4825,7 +4842,8 @@ SET optimizer_switch=@save_optimizer_switch; SET @pass='my_pw'; SET @wrong='incorrect'; set sql_mode=""; -set @save_debug_dbug= @@debug_dbug; +# MXS ERROR 1193 (HY000): Unknown system variable 'debug_dbug' +# MXS set @save_debug_dbug= @@debug_dbug; set @@debug_dbug= @save_debug_dbug; set @@debug_dbug= @save_debug_dbug; set gtid_domain_id = 10; @@ -4943,7 +4961,8 @@ SET NAMES latin1; SET NAMES latin1; SET NAMES utf8; SET NAMES latin1; -SET @old_debug= @@session.debug; +# MXS Embedded parser is not aware of the 'debug' variable. +# MXS SET @old_debug= @@session.debug; set debug_sync='RESET'; set debug_dbug='+d,show_explain_probe_delete_exec_start'; set @show_explain_probe_select_id=1; @@ -4954,9 +4973,11 @@ set debug_sync='RESET'; set @show_explain_probe_select_id=1; set debug_dbug='d,show_explain_probe_join_exec_start'; set debug_dbug=''; -SET @old_debug= @@session.debug; +# MXS Embedded parser is not aware of the 'debug' variable. +# MXS SET @old_debug= @@session.debug; set debug_sync='RESET'; -SET @old_debug= @@session.debug; +# MXS Embedded parser is not aware of the 'debug' variable. +# MXS SET @old_debug= @@session.debug; set @show_explain_probe_select_id=1; set debug_dbug='+d,show_explain_probe_join_exec_start'; set @show_expl_tmp= @@optimizer_switch; @@ -5169,7 +5190,8 @@ set @@max_sp_recursion_depth= 20; set @@max_sp_recursion_depth= 0; SET sql_mode=ONLY_FULL_GROUP_BY; SET @lock_wait_timeout_saved= @@lock_wait_timeout; -SET @innodb_lock_wait_timeout_saved= @@innodb_lock_wait_timeout; +# MXS Embedded parser is not aware of innodb. +# MXS SET SET @innodb_lock_wait_timeout_saved= @@innodb_lock_wait_timeout; SET @@lock_wait_timeout= 1; SET @@innodb_lock_wait_timeout= 1; SET AUTOCOMMIT= 0; @@ -5815,7 +5837,8 @@ set @@optimizer_switch= default; set optimizer_switch='subquery_cache=on'; SET optimizer_switch=@save_optimizer_switch; set @@optimizer_switch= default; -SET @orig_debug=@@debug; +# MXS Embedded parser is not aware of the 'debug' variable. +# MXS SET @orig_debug=@@debug; SET GLOBAL debug_dbug="d,subselect_exec_fail"; SET GLOBAL debug_dbug=@orig_debug; set @subselect_mat_cost=@@optimizer_switch; @@ -6886,7 +6909,8 @@ set @@autocommit=0; set @@autocommit=1; set @@global.general_log=@save_general_log; SET TIMESTAMP=10000; -SET @`a b`='hello'; +# MXSTODO qc_sqlite can not parse quoted variables. +# MXSTODO SET @`a b`='hello'; set @var1= "';aaa"; SET @var2=char(ascii('a')); set @a := foo; @@ -6899,7 +6923,8 @@ set @a=_latin2'test'; set @a=_latin2'test' collate latin2_general_ci; set @var= NULL ; set @v1=null, @v2=1, @v3=1.1, @v4=now(); -set session @honk=99; +# 'set session @honk = 99' is not legal. +# MXS set session @honk=99; set @first_var= NULL; set @first_var= cast(NULL as signed integer); set @first_var= NULL; @@ -6928,7 +6953,8 @@ SET @aux = NULL; SET @bug12408412=1; SET @var=NULL; set @var= repeat('a',20000); -set @my_slave_net_timeout =@@global.slave_net_timeout; +# MXS Embedded parser is not aware of the 'global.slave_net_timeout' variable. +# MXS SET set @my_slave_net_timeout =@@global.slave_net_timeout; set global slave_net_timeout=100; set global sql_slave_skip_counter=100; set global slave_net_timeout=default; @@ -6987,10 +7013,13 @@ set @my_max_allowed_packet =@@global.max_allowed_packet; set @my_delay_key_write =@@global.delay_key_write; set @my_join_buffer_size =@@global.join_buffer_size; set @my_log_warnings =@@global.log_warnings; -set @`test`=1; +# MXSTODO qc_sqlite can not parse quoted variables. +# MXSTODO set @`test`=1; set @TEST=2; -set @"tEST"=3; -set @`TeST`=4; +# MXSTODO qc_sqlite can not parse quoted variables. +# MXSTODO set @"tEST"=3; +# MXSTODO qc_sqlite can not parse quoted variables. +# MXSTODO set @`TeST`=4; set @select=2,@t5=1.23456; set @test_int=10,@test_double=1e-10,@test_string="abcdeghi",@test_string2="abcdefghij",@select=NULL; set @test_int="hello",@test_double="hello",@test_string="hello",@test_string2="hello"; diff --git a/server/core/query_classifier.c b/server/core/query_classifier.c index 91dc1df3d..1d0723cbf 100644 --- a/server/core/query_classifier.c +++ b/server/core/query_classifier.c @@ -381,8 +381,14 @@ struct type_name_info type_to_type_name_info(qc_query_type_t type) } break; - /** Not implemented yet */ - //case QUERY_TYPE_USERVAR_WRITE: + case QUERY_TYPE_USERVAR_WRITE: + { + static const char name[] = "QUERY_TYPE_USERVAR_WRITE"; + info.name = name; + info.name_len = sizeof(name) - 1; + } + break; + case QUERY_TYPE_USERVAR_READ: { static const char name[] = "QUERY_TYPE_USERVAR_READ"; @@ -549,8 +555,7 @@ static const qc_query_type_t QUERY_TYPES[] = QUERY_TYPE_WRITE, QUERY_TYPE_MASTER_READ, QUERY_TYPE_SESSION_WRITE, - /** Not implemented yet */ - //QUERY_TYPE_USERVAR_WRITE, + QUERY_TYPE_USERVAR_WRITE, QUERY_TYPE_USERVAR_READ, QUERY_TYPE_SYSVAR_READ, /** Not implemented yet */ diff --git a/server/include/query_classifier.h b/server/include/query_classifier.h index ee434c295..ce60e798a 100644 --- a/server/include/query_classifier.h +++ b/server/include/query_classifier.h @@ -26,8 +26,7 @@ typedef enum QUERY_TYPE_WRITE = 0x000004, /*< Master data will be modified:master */ QUERY_TYPE_MASTER_READ = 0x000008, /*< Read from the master:master */ QUERY_TYPE_SESSION_WRITE = 0x000010, /*< Session data will be modified:master or all */ - /** Not implemented yet */ - //QUERY_TYPE_USERVAR_WRITE = 0x000020, /*< Write a user variable:master or all */ + QUERY_TYPE_USERVAR_WRITE = 0x000020, /*< Write a user variable:master or all */ QUERY_TYPE_USERVAR_READ = 0x000040, /*< Read a user variable:master or any */ QUERY_TYPE_SYSVAR_READ = 0x000080, /*< Read a system variable:master or any */ /** Not implemented yet */ diff --git a/utils/skygw_debug.h b/utils/skygw_debug.h index 15aa1b352..4fd321475 100644 --- a/utils/skygw_debug.h +++ b/utils/skygw_debug.h @@ -146,6 +146,7 @@ typedef enum skygw_chk_t ((t) == QUERY_TYPE_UNKNOWN ? "QUERY_TYPE_UNKNOWN" : \ ((t) == QUERY_TYPE_LOCAL_READ ? "QUERY_TYPE_LOCAL_READ" : \ ((t) == QUERY_TYPE_MASTER_READ ? "QUERY_TYPE_MASTER_READ" : \ + ((t) == QUERY_TYPE_USERVAR_WRITE ? "QUERY_TYPE_USERVAR_WRITE" : \ ((t) == QUERY_TYPE_USERVAR_READ ? "QUERY_TYPE_USERVAR_READ" : \ ((t) == QUERY_TYPE_SYSVAR_READ ? "QUERY_TYPE_SYSVAR_READ" : \ ((t) == QUERY_TYPE_GSYSVAR_READ ? "QUERY_TYPE_GSYSVAR_READ" : \ @@ -162,7 +163,7 @@ typedef enum skygw_chk_t ((t) == QUERY_TYPE_READ_TMP_TABLE ? "QUERY_TYPE_READ_TMP_TABLE" : \ ((t) == QUERY_TYPE_SHOW_DATABASES ? "QUERY_TYPE_SHOW_DATABASES" : \ ((t) == QUERY_TYPE_SHOW_TABLES ? "QUERY_TYPE_SHOW_TABLES" : \ - "Unknown query type")))))))))))))))))))))) + "Unknown query type"))))))))))))))))))))))) #define STRLOGPRIORITYNAME(n)\ ((n) == LOG_EMERG ? "LOG_EMERG" : \ From 5aefd35df9cde96661e9c5ce77f1c9f871a6eaf4 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 15 Nov 2016 13:36:28 +0200 Subject: [PATCH 03/11] MXS-969: Detect user variable modifications With the use_sql_variables_in=master option, readwritesplit should route all user variable modifications and reads with user variables to the master. Previously, the modification of user variables was grouped into generic system variables which caused all modifications to system variables to go to the master only. The router requires a finer grained distiction between normal system variable modifications and user variable modifications. With the improvements to the query classifier, readwritesplit now properly routes all user variable operations to the master and other system variable modifications to all servers. --- .../routing/readwritesplit/readwritesplit.c | 63 ++++++++----------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index c5e33c0d6..62e3cf545 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -1346,9 +1346,10 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, */ else if (!load_active && (QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE) || - /** Configured to allow writing variables to all nodes */ + /** Configured to allow writing user variables to all nodes */ (use_sql_variables_in == TYPE_ALL && - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_WRITE)) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_WRITE)) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_WRITE) || /** enable or disable autocommit are always routed to all */ QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT))) @@ -1388,44 +1389,29 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, * Hints may affect on routing of the following queries */ else if (!trx_active && !load_active && + !QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) && !QUERY_IS_TYPE(qtype, QUERY_TYPE_WRITE) && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || /*< any SELECT */ - QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */ - QUERY_IS_TYPE(qtype, - QUERY_TYPE_USERVAR_READ) || /*< read user var */ - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || /*< read sys var */ - QUERY_IS_TYPE(qtype, - QUERY_TYPE_EXEC_STMT) || /*< prepared stmt exec */ - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || - QUERY_IS_TYPE(qtype, - QUERY_TYPE_GSYSVAR_READ))) /*< read global sys var */ + (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ))) { - /** First set expected targets before evaluating hints */ - if (!QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */ - /** Configured to allow reading variables from slaves */ - (use_sql_variables_in == TYPE_ALL && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ))))) + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ)) + { + if (use_sql_variables_in == TYPE_ALL) + { + target = TARGET_SLAVE; + } + } + else if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || // Normal read + QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES) || // SHOW TABLES + QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || // System variable + QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ)) // Global system variable { target = TARGET_SLAVE; } - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_EXEC_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || - /** Configured not to allow reading variables from slaves */ - (use_sql_variables_in == TYPE_MASTER && - (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ)))) - { - target = TARGET_MASTER; - } - /** If nothing matches then choose the master */ if ((target & (TARGET_ALL | TARGET_SLAVE | TARGET_MASTER)) == 0) { @@ -1434,7 +1420,7 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, } else { - ss_dassert(trx_active || + ss_dassert(trx_active || load_active || (QUERY_IS_TYPE(qtype, QUERY_TYPE_WRITE) || QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_WRITE) || @@ -1446,6 +1432,8 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, use_sql_variables_in == TYPE_MASTER) || (QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_WRITE) && use_sql_variables_in == TYPE_MASTER) || + (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_WRITE) && + use_sql_variables_in == TYPE_MASTER) || QUERY_IS_TYPE(qtype, QUERY_TYPE_BEGIN_TRX) || QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT) || @@ -1454,7 +1442,10 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, QUERY_IS_TYPE(qtype, QUERY_TYPE_EXEC_STMT) || QUERY_IS_TYPE(qtype, QUERY_TYPE_CREATE_TMP_TABLE) || QUERY_IS_TYPE(qtype, QUERY_TYPE_READ_TMP_TABLE) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_UNKNOWN))); + QUERY_IS_TYPE(qtype, QUERY_TYPE_UNKNOWN)) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_EXEC_STMT) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || + QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT)); target = TARGET_MASTER; } From 4e007e87d01b431f82defda90f42f832daab0ebb Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 16 Nov 2016 22:59:47 +0200 Subject: [PATCH 04/11] Allow stale master status to be assigned via maxadmin If a master once had slaves and is in the stale status, it will not retain this status after a restart. Without storing on-disk information, the stale master status cannot be deduced by looking at the master alone. Because of this, the user should be able to manually enable the stale master status. --- Documentation/Reference/MaxAdmin.md | 4 ++++ server/core/server.c | 1 + 2 files changed, 5 insertions(+) diff --git a/Documentation/Reference/MaxAdmin.md b/Documentation/Reference/MaxAdmin.md index c1da5453e..6120c779e 100644 --- a/Documentation/Reference/MaxAdmin.md +++ b/Documentation/Reference/MaxAdmin.md @@ -520,6 +520,10 @@ The status bit that can be controlled are maintenance The server is in maintenance mode. In this mode no new connections will be established to the server. The monitors will also not monitor servers that are in maintenance mode. + + stale + The server is a stale master server. Read [MySQL Monitor](../Monitors/MySQL-Monitor.md) documentation for more details. + diff --git a/server/core/server.c b/server/core/server.c index 8de58eb5d..1517c566f 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -990,6 +990,7 @@ static struct { "ndb", SERVER_NDB }, { "maintenance", SERVER_MAINT }, { "maint", SERVER_MAINT }, + { "stale", SERVER_STALE_STATUS }, { NULL, 0 } }; From d64029102314dc63d342d8c32032a7f512100c97 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 18 Nov 2016 16:00:57 +0200 Subject: [PATCH 05/11] Add crashing test-case With 2.0.1 or earlier, if a statement contains a trailing NULL, the statement will inside qc_sqlite.c incorrectly be assumed not to be the one to be classified with a crash being indirectly the result. --- query_classifier/test/CMakeLists.txt | 9 +++- query_classifier/test/crash_qc_sqlite.c | 66 +++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 query_classifier/test/crash_qc_sqlite.c diff --git a/query_classifier/test/CMakeLists.txt b/query_classifier/test/CMakeLists.txt index 565b067f5..ba817cb5e 100644 --- a/query_classifier/test/CMakeLists.txt +++ b/query_classifier/test/CMakeLists.txt @@ -17,9 +17,16 @@ if (BUILD_QC_MYSQLEMBEDDED) endif() add_executable(classify classify.c) - add_executable(compare compare.cc) target_link_libraries(classify maxscale-common) + + add_executable(compare compare.cc) target_link_libraries(compare maxscale-common) + + add_executable(crash_qc_sqlite crash_qc_sqlite.c) + target_link_libraries(crash_qc_sqlite maxscale-common) + + add_test(TestQC_Crash_qcsqlite crash_qc_sqlite) + add_test(TestQC_MySQLEmbedded classify qc_mysqlembedded ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) add_test(TestQC_SqLite classify qc_sqlite ${CMAKE_CURRENT_SOURCE_DIR}/input.sql ${CMAKE_CURRENT_SOURCE_DIR}/expected.sql) diff --git a/query_classifier/test/crash_qc_sqlite.c b/query_classifier/test/crash_qc_sqlite.c new file mode 100644 index 000000000..2f1983f05 --- /dev/null +++ b/query_classifier/test/crash_qc_sqlite.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include +#include +#include +#include + +#define MYSQL_HEADER_LEN 4 + +GWBUF* create_gwbuf(const char* s, size_t len) +{ + size_t payload_len = len + 1; + size_t gwbuf_len = MYSQL_HEADER_LEN + payload_len; + + GWBUF* gwbuf = gwbuf_alloc(gwbuf_len); + + *((unsigned char*)((char*)GWBUF_DATA(gwbuf))) = payload_len; + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 1)) = (payload_len >> 8); + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 2)) = (payload_len >> 16); + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 3)) = 0x00; + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 4)) = 0x03; + memcpy((char*)GWBUF_DATA(gwbuf) + 5, s, len); + + return gwbuf; +} + +int main() +{ + int rv = EXIT_FAILURE; + + set_libdir(strdup("../qc_sqlite")); + + if (qc_init("qc_sqlite", NULL)) + { + const char s[] = "SELECT @@global.max_allowed_packet"; + + GWBUF *stmt = create_gwbuf(s, sizeof(s)); // Include superfluous NULL. + + // In 2.0.1 this crashed due to is_submitted_query() in qc_sqlite.c + // being of the opinion that the statement was not the one to be + // classified and hence an alien parse-tree being passed to sqlite3's + // code generator. + qc_parse(stmt); + + qc_end(); + + rv = EXIT_SUCCESS; + } + else + { + fprintf(stderr, "error: Could not load query classifier."); + } + + return rv; +} From ecb6680e71f064e1f14c316558ab4f688fec9265 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 18 Nov 2016 13:33:44 +0200 Subject: [PATCH 06/11] MXS-976: qc_sqlite: Force initialization of sqlite3 Sqlite3 performs some lazy initialization, during which it internally parses some SQL statements of its own. Earlier there was detection code for noticing that, but it was costly and errorprone. Now, sqlite3 is forced to perform the initialization at startup so that we no longer need any detection code. --- query_classifier/qc_sqlite/qc_sqlite.c | 121 ++++++++----------------- 1 file changed, 38 insertions(+), 83 deletions(-) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index f96b4cf57..d44c09949 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -77,6 +77,7 @@ typedef struct qc_sqlite_info size_t database_names_capacity; // The capacity of database_names. int keyword_1; // The first encountered keyword. int keyword_2; // The second encountered keyword. + bool initializing; // Whether we are initializing sqlite3. } QC_SQLITE_INFO; typedef enum qc_log_level @@ -188,7 +189,6 @@ static QC_SQLITE_INFO* info_alloc(void); static void info_finish(QC_SQLITE_INFO* info); static void info_free(QC_SQLITE_INFO* info); static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info); -static bool is_submitted_query(const QC_SQLITE_INFO* info, const Parse* pParse); static void log_invalid_data(GWBUF* query, const char* message); static bool parse_query(GWBUF* query); static void parse_query_string(const char* query, size_t len); @@ -381,6 +381,7 @@ static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info) info->database_names_capacity = 0; info->keyword_1 = 0; // Sqlite3 starts numbering tokens from 1, so 0 means info->keyword_2 = 0; // that we have not seen a keyword. + info->initializing = false; return info; } @@ -528,76 +529,6 @@ static bool query_is_parsed(GWBUF* query) return query && GWBUF_IS_PARSED(query); } -/* - * Check that the statement being reported about is the one that initially was - * submitted to parse_query_string(...). When sqlite3 is parsing other statements - * it may (right after it's used for the first time) parse selects of its own. - * We need to detect that, so that the internal stuff is not allowed to interfere - * with the parsing of the provided statement. - * - * @param info The thread specific info structure. - * @param pParse Sqlite3's parse context. - * - * @return True, if the current callback relates to the provided query, - * false otherwise. - */ -static bool is_submitted_query(const QC_SQLITE_INFO* info, const Parse* pParse) -{ - bool rv = false; - - if (*pParse->zTail == 0) - { - // Everything has been consumed => the callback relates to the statement - // that was provided to parse_query_string(...). - rv = true; - } - else if (pParse->sLastToken.z && (*pParse->sLastToken.z == ';')) - { - // A ';' has been reached, i.e. everything has been consumed => the callback - // relates to the statement that was provided to parse_query_string(...). - rv = true; - } - else - { - // If info->query contains a trailing ';', pParse->zTail may or may not contain - // one also. We need to cater for the situation that the former has one and the - // latter does not. - const char* i = info->query; - const char* end = i + info->query_len; - const char* j = pParse->zTail; - - // Walk forward as long as neither has reached the end, - // and the characters are the same. - while ((i < end) && (*j != 0) && (*i == *j)) - { - ++i; - ++j; - } - - if (i == end) - { - // If i has reached the end, then if j also has reached the end, - // both are the same. In this case both either have or have not - // a trailing ';'. - rv = (*j == 0); - } - else if (*j == 0) - { - // Else if j has reached the end, then if i points to ';', both - // are the same. In this case info->query contains a trailing ';' - // while pParse->zTail does not. - rv = (*i == ';'); - } - else - { - // Otherwise they are not the same. - rv = false; - } - } - - return rv; -} - /** * Logs information about invalid data. * @@ -1271,7 +1202,7 @@ void mxs_sqlite3EndTable(Parse *pParse, /* Parse context */ QC_SQLITE_INFO* info = this_thread.info; ss_dassert(info); - if (is_submitted_query(info, pParse)) + if (!info->initializing) { if (pSelect) { @@ -1359,10 +1290,7 @@ int mxs_sqlite3Select(Parse* pParse, Select* p, SelectDest* pDest) QC_SQLITE_INFO* info = this_thread.info; ss_dassert(info); - // Check whether the statement being parsed is the one that was passed - // to sqlite3_prepare in parse_query_string(). During inserts, sqlite may - // parse selects of its own. - if (is_submitted_query(info, pParse)) + if (!info->initializing) { info->status = QC_QUERY_PARSED; info->operation = QUERY_OP_SELECT; @@ -1391,7 +1319,7 @@ void mxs_sqlite3StartTable(Parse *pParse, /* Parser context */ QC_SQLITE_INFO* info = this_thread.info; ss_dassert(info); - if (is_submitted_query(info, pParse)) + if (!info->initializing) { info->status = QC_QUERY_PARSED; info->operation = QUERY_OP_CREATE; @@ -2479,14 +2407,13 @@ static bool qc_sqlite_init(const char* args) if (sqlite3_initialize() == 0) { + init_builtin_functions(); + this_unit.initialized = true; + this_unit.log_level = log_level; if (qc_sqlite_thread_init()) { - init_builtin_functions(); - - this_unit.log_level = log_level; - if (log_level != QC_LOG_NOTHING) { const char* message; @@ -2550,10 +2477,38 @@ static bool qc_sqlite_thread_init(void) int rc = sqlite3_open(":memory:", &this_thread.db); if (rc == SQLITE_OK) { - this_thread.initialized = true; - MXS_INFO("qc_sqlite: In-memory sqlite database successfully opened for thread %lu.", (unsigned long) pthread_self()); + + QC_SQLITE_INFO* info = info_alloc(); + + if (info) + { + this_thread.info = info; + + // With this statement we cause sqlite3 to initialize itself, so that it + // is not done as part of the actual classification of data. + const char* s = "CREATE TABLE __maxscale__internal__ (int field UNIQUE)"; + size_t len = strlen(s); + + this_thread.info->query = s; + this_thread.info->query_len = len; + this_thread.info->initializing = true; + parse_query_string(s, len); + this_thread.info->initializing = false; + this_thread.info->query = NULL; + this_thread.info->query_len = 0; + + info_free(this_thread.info); + this_thread.info = NULL; + + this_thread.initialized = true; + } + else + { + sqlite3_close(this_thread.db); + this_thread.db = NULL; + } } else { From 5198c3e45671cdd1d90f078825f1ac5fabf3c471 Mon Sep 17 00:00:00 2001 From: ekorh475 Date: Fri, 18 Nov 2016 10:54:14 +0200 Subject: [PATCH 07/11] Run astyle on httpd.c and maxinfo_exec.c --- server/modules/protocol/httpd.c | 21 +- server/modules/routing/maxinfo/maxinfo_exec.c | 769 ++++++++++-------- 2 files changed, 420 insertions(+), 370 deletions(-) diff --git a/server/modules/protocol/httpd.c b/server/modules/protocol/httpd.c index 8c98d8bba..83407c9ed 100644 --- a/server/modules/protocol/httpd.c +++ b/server/modules/protocol/httpd.c @@ -39,9 +39,9 @@ #include #include - /* @see function load_module in load_utils.c for explanation of the following - * lint directives. - */ +/* @see function load_module in load_utils.c for explanation of the following + * lint directives. +*/ /*lint -e14 */ MODULE_INFO info = { @@ -145,9 +145,9 @@ static int httpd_read_event(DCB* dcb) SESSION *session = dcb->session; int numchars = 1; - char buf[HTTPD_REQUESTLINE_MAXLEN-1] = ""; + char buf[HTTPD_REQUESTLINE_MAXLEN - 1] = ""; char *query_string = NULL; - char method[HTTPD_METHOD_MAXLEN-1] = ""; + char method[HTTPD_METHOD_MAXLEN - 1] = ""; char url[HTTPD_SMALL_BUFFER] = ""; size_t i, j; int headers_read = 0; @@ -163,11 +163,13 @@ static int httpd_read_event(DCB* dcb) numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); - i = 0; j = 0; + i = 0; + j = 0; while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { method[i] = buf[j]; - i++; j++; + i++; + j++; } method[i] = '\0'; @@ -190,7 +192,8 @@ static int httpd_read_event(DCB* dcb) while ((j < sizeof(buf) - 1) && !ISspace(buf[j]) && (i < sizeof(url) - 1)) { url[i] = buf[j]; - i++; j++; + i++; + j++; } url[i] = '\0'; @@ -225,7 +228,7 @@ static int httpd_read_event(DCB* dcb) { *value = '\0'; value++; - end = &value[strlen(value) -1]; + end = &value[strlen(value) - 1]; *end = '\0'; if (strncasecmp(buf, "Hostname", 6) == 0) diff --git a/server/modules/routing/maxinfo/maxinfo_exec.c b/server/modules/routing/maxinfo/maxinfo_exec.c index 8f77d2aec..bbe8a782e 100644 --- a/server/modules/routing/maxinfo/maxinfo_exec.c +++ b/server/modules/routing/maxinfo/maxinfo_exec.c @@ -18,8 +18,8 @@ * @verbatim * Revision History * - * Date Who Description - * 17/02/15 Mark Riddoch Initial implementation + * Date Who Description + * 17/02/15 Mark Riddoch Initial implementation * * @endverbatim */ @@ -59,20 +59,20 @@ void maxinfo_send_ok(DCB *dcb); /** * Execute a parse tree and return the result set or runtime error * - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ void maxinfo_execute(DCB *dcb, MAXINFO_TREE *tree) { - switch (tree->op) - { - case MAXOP_SHOW: - exec_show(dcb, tree); - break; - case MAXOP_SELECT: - exec_select(dcb, tree); - break; + switch (tree->op) + { + case MAXOP_SHOW: + exec_show(dcb, tree); + break; + case MAXOP_SELECT: + exec_select(dcb, tree); + break; case MAXOP_FLUSH: exec_flush(dcb, tree); @@ -90,212 +90,232 @@ maxinfo_execute(DCB *dcb, MAXINFO_TREE *tree) exec_restart(dcb, tree); break; - case MAXOP_TABLE: - case MAXOP_COLUMNS: - case MAXOP_LITERAL: - case MAXOP_PREDICATE: - case MAXOP_LIKE: - case MAXOP_EQUAL: - default: - maxinfo_send_error(dcb, 0, "Unexpected operator in parse tree"); - } + case MAXOP_TABLE: + case MAXOP_COLUMNS: + case MAXOP_LITERAL: + case MAXOP_PREDICATE: + case MAXOP_LIKE: + case MAXOP_EQUAL: + default: + maxinfo_send_error(dcb, 0, "Unexpected operator in parse tree"); + } } /** * Fetch the list of services and stream as a result set * - * @param dcb DCB to which to stream result set - * @param tree Potential like clause (currently unused) + * @param dcb DCB to which to stream result set + * @param tree Potential like clause (currently unused) */ static void exec_show_services(DCB *dcb, MAXINFO_TREE *tree) { -RESULTSET *set; + RESULTSET *set; - if ((set = serviceGetList()) == NULL) - return; - - resultset_stream_mysql(set, dcb); - resultset_free(set); + if ((set = serviceGetList()) == NULL) + { + return; + } + + resultset_stream_mysql(set, dcb); + resultset_free(set); } /** * Fetch the list of listeners and stream as a result set * - * @param dcb DCB to which to stream result set - * @param tree Potential like clause (currently unused) + * @param dcb DCB to which to stream result set + * @param tree Potential like clause (currently unused) */ static void exec_show_listeners(DCB *dcb, MAXINFO_TREE *tree) { -RESULTSET *set; + RESULTSET *set; - if ((set = serviceGetListenerList()) == NULL) - return; - - resultset_stream_mysql(set, dcb); - resultset_free(set); + if ((set = serviceGetListenerList()) == NULL) + { + return; + } + + resultset_stream_mysql(set, dcb); + resultset_free(set); } /** * Fetch the list of sessions and stream as a result set * - * @param dcb DCB to which to stream result set - * @param tree Potential like clause (currently unused) + * @param dcb DCB to which to stream result set + * @param tree Potential like clause (currently unused) */ static void exec_show_sessions(DCB *dcb, MAXINFO_TREE *tree) { -RESULTSET *set; + RESULTSET *set; - if ((set = sessionGetList(SESSION_LIST_ALL)) == NULL) - return; - - resultset_stream_mysql(set, dcb); - resultset_free(set); + if ((set = sessionGetList(SESSION_LIST_ALL)) == NULL) + { + return; + } + + resultset_stream_mysql(set, dcb); + resultset_free(set); } /** * Fetch the list of client sessions and stream as a result set * - * @param dcb DCB to which to stream result set - * @param tree Potential like clause (currently unused) + * @param dcb DCB to which to stream result set + * @param tree Potential like clause (currently unused) */ static void exec_show_clients(DCB *dcb, MAXINFO_TREE *tree) { -RESULTSET *set; + RESULTSET *set; - if ((set = sessionGetList(SESSION_LIST_CONNECTION)) == NULL) - return; - - resultset_stream_mysql(set, dcb); - resultset_free(set); + if ((set = sessionGetList(SESSION_LIST_CONNECTION)) == NULL) + { + return; + } + + resultset_stream_mysql(set, dcb); + resultset_free(set); } /** * Fetch the list of servers and stream as a result set * - * @param dcb DCB to which to stream result set - * @param tree Potential like clause (currently unused) + * @param dcb DCB to which to stream result set + * @param tree Potential like clause (currently unused) */ static void exec_show_servers(DCB *dcb, MAXINFO_TREE *tree) { -RESULTSET *set; + RESULTSET *set; - if ((set = serverGetList()) == NULL) - return; - - resultset_stream_mysql(set, dcb); - resultset_free(set); + if ((set = serverGetList()) == NULL) + { + return; + } + + resultset_stream_mysql(set, dcb); + resultset_free(set); } /** * Fetch the list of modules and stream as a result set * - * @param dcb DCB to which to stream result set - * @param tree Potential like clause (currently unused) + * @param dcb DCB to which to stream result set + * @param tree Potential like clause (currently unused) */ static void exec_show_modules(DCB *dcb, MAXINFO_TREE *tree) { -RESULTSET *set; + RESULTSET *set; - if ((set = moduleGetList()) == NULL) - return; - - resultset_stream_mysql(set, dcb); - resultset_free(set); + if ((set = moduleGetList()) == NULL) + { + return; + } + + resultset_stream_mysql(set, dcb); + resultset_free(set); } /** * Fetch the list of monitors and stream as a result set * - * @param dcb DCB to which to stream result set - * @param tree Potential like clause (currently unused) + * @param dcb DCB to which to stream result set + * @param tree Potential like clause (currently unused) */ static void exec_show_monitors(DCB *dcb, MAXINFO_TREE *tree) { -RESULTSET *set; + RESULTSET *set; - if ((set = monitorGetList()) == NULL) - return; - - resultset_stream_mysql(set, dcb); - resultset_free(set); + if ((set = monitorGetList()) == NULL) + { + return; + } + + resultset_stream_mysql(set, dcb); + resultset_free(set); } /** * Fetch the event times data * - * @param dcb DCB to which to stream result set - * @param tree Potential like clause (currently unused) + * @param dcb DCB to which to stream result set + * @param tree Potential like clause (currently unused) */ static void exec_show_eventTimes(DCB *dcb, MAXINFO_TREE *tree) { -RESULTSET *set; + RESULTSET *set; - if ((set = eventTimesGetList()) == NULL) - return; - - resultset_stream_mysql(set, dcb); - resultset_free(set); + if ((set = eventTimesGetList()) == NULL) + { + return; + } + + resultset_stream_mysql(set, dcb); + resultset_free(set); } /** * The table of show commands that are supported */ -static struct { - char *name; - void (*func)(DCB *, MAXINFO_TREE *); -} show_commands[] = { - { "variables", exec_show_variables }, - { "status", exec_show_status }, - { "services", exec_show_services }, - { "listeners", exec_show_listeners }, - { "sessions", exec_show_sessions }, - { "clients", exec_show_clients }, - { "servers", exec_show_servers }, - { "modules", exec_show_modules }, - { "monitors", exec_show_monitors }, - { "eventTimes", exec_show_eventTimes }, - { NULL, NULL } +static struct +{ + char *name; + void (*func)(DCB *, MAXINFO_TREE *); +} show_commands[] = +{ + { "variables", exec_show_variables }, + { "status", exec_show_status }, + { "services", exec_show_services }, + { "listeners", exec_show_listeners }, + { "sessions", exec_show_sessions }, + { "clients", exec_show_clients }, + { "servers", exec_show_servers }, + { "modules", exec_show_modules }, + { "monitors", exec_show_monitors }, + { "eventTimes", exec_show_eventTimes }, + { NULL, NULL } }; /** * Execute a show command parse tree and return the result set or runtime error * - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ static void exec_show(DCB *dcb, MAXINFO_TREE *tree) { -int i; -char errmsg[120]; + int i; + char errmsg[120]; - for (i = 0; show_commands[i].name; i++) - { - if (strcasecmp(show_commands[i].name, tree->value) == 0) - { - (*show_commands[i].func)(dcb, tree->right); - return; - } - } - if (strlen(tree->value) > 80) // Prevent buffer overrun - tree->value[80] = 0; - sprintf(errmsg, "Unsupported show command '%s'", tree->value); - maxinfo_send_error(dcb, 0, errmsg); - MXS_NOTICE("%s", errmsg); + for (i = 0; show_commands[i].name; i++) + { + if (strcasecmp(show_commands[i].name, tree->value) == 0) + { + (*show_commands[i].func)(dcb, tree->right); + return; + } + } + if (strlen(tree->value) > 80) // Prevent buffer overrun + { + tree->value[80] = 0; + } + sprintf(errmsg, "Unsupported show command '%s'", tree->value); + maxinfo_send_error(dcb, 0, errmsg); + MXS_NOTICE("%s", errmsg); } /** * Flush all logs to disk and rotate them. - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ void exec_flush_logs(DCB *dcb, MAXINFO_TREE *tree) { @@ -310,7 +330,8 @@ static struct { char *name; void (*func)(DCB *, MAXINFO_TREE *); -} flush_commands[] = { +} flush_commands[] = +{ { "logs", exec_flush_logs}, { NULL, NULL} }; @@ -318,8 +339,8 @@ static struct /** * Execute a flush command parse tree and return the result set or runtime error * - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ static void exec_flush(DCB *dcb, MAXINFO_TREE *tree) @@ -390,7 +411,8 @@ static struct { char *name; void (*func)(DCB *, MAXINFO_TREE *); -} set_commands[] = { +} set_commands[] = +{ { "server", exec_set_server}, { NULL, NULL} }; @@ -398,8 +420,8 @@ static struct /** * Execute a set command parse tree and return the result set or runtime error * - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ static void exec_set(DCB *dcb, MAXINFO_TREE *tree) @@ -470,7 +492,8 @@ static struct { char *name; void (*func)(DCB *, MAXINFO_TREE *); -} clear_commands[] = { +} clear_commands[] = +{ { "server", exec_clear_server}, { NULL, NULL} }; @@ -478,8 +501,8 @@ static struct /** * Execute a clear command parse tree and return the result set or runtime error * - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ static void exec_clear(DCB *dcb, MAXINFO_TREE *tree) @@ -590,7 +613,8 @@ static struct { char *name; void (*func)(DCB *, MAXINFO_TREE *); -} shutdown_commands[] = { +} shutdown_commands[] = +{ { "maxscale", exec_shutdown_maxscale}, { "monitor", exec_shutdown_monitor}, { "service", exec_shutdown_service}, @@ -600,8 +624,8 @@ static struct /** * Execute a shutdown command parse tree and return OK or runtime error * - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ static void exec_shutdown(DCB *dcb, MAXINFO_TREE *tree) @@ -699,7 +723,8 @@ static struct { char *name; void (*func)(DCB *, MAXINFO_TREE *); -} restart_commands[] = { +} restart_commands[] = +{ { "monitor", exec_restart_monitor}, { "service", exec_restart_service}, { NULL, NULL} @@ -708,8 +733,8 @@ static struct /** * Execute a restart command parse tree and return OK or runtime error * - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ static void exec_restart(DCB *dcb, MAXINFO_TREE *tree) @@ -742,7 +767,7 @@ exec_restart(DCB *dcb, MAXINFO_TREE *tree) static char * getVersion() { - return MAXSCALE_VERSION; + return MAXSCALE_VERSION; } static char *versionComment = "MariaDB MaxScale"; @@ -754,7 +779,7 @@ static char *versionComment = "MariaDB MaxScale"; static char * getVersionComment() { - return versionComment; + return versionComment; } /** @@ -765,109 +790,116 @@ getVersionComment() static char * getMaxScaleHome() { - return getenv("MAXSCALE_HOME"); + return getenv("MAXSCALE_HOME"); } /* The various methods to fetch the variables */ -#define VT_STRING 1 -#define VT_INT 2 +#define VT_STRING 1 +#define VT_INT 2 typedef void *(*STATSFUNC)(); /** * Variables that may be sent in a show variables */ -static struct { - char *name; - int type; - STATSFUNC func; -} variables[] = { - { "version", VT_STRING, (STATSFUNC)getVersion }, - { "version_comment", VT_STRING, (STATSFUNC)getVersionComment }, - { "basedir", VT_STRING, (STATSFUNC)getMaxScaleHome}, - { "MAXSCALE_VERSION", VT_STRING, (STATSFUNC)getVersion }, - { "MAXSCALE_THREADS", VT_INT, (STATSFUNC)config_threadcount }, - { "MAXSCALE_NBPOLLS", VT_INT, (STATSFUNC)config_nbpolls }, - { "MAXSCALE_POLLSLEEP", VT_INT, (STATSFUNC)config_pollsleep }, - { "MAXSCALE_UPTIME", VT_INT, (STATSFUNC)maxscale_uptime }, - { "MAXSCALE_SESSIONS", VT_INT, (STATSFUNC)serviceSessionCountAll }, - { NULL, 0, NULL } +static struct +{ + char *name; + int type; + STATSFUNC func; +} variables[] = +{ + { "version", VT_STRING, (STATSFUNC)getVersion }, + { "version_comment", VT_STRING, (STATSFUNC)getVersionComment }, + { "basedir", VT_STRING, (STATSFUNC)getMaxScaleHome}, + { "MAXSCALE_VERSION", VT_STRING, (STATSFUNC)getVersion }, + { "MAXSCALE_THREADS", VT_INT, (STATSFUNC)config_threadcount }, + { "MAXSCALE_NBPOLLS", VT_INT, (STATSFUNC)config_nbpolls }, + { "MAXSCALE_POLLSLEEP", VT_INT, (STATSFUNC)config_pollsleep }, + { "MAXSCALE_UPTIME", VT_INT, (STATSFUNC)maxscale_uptime }, + { "MAXSCALE_SESSIONS", VT_INT, (STATSFUNC)serviceSessionCountAll }, + { NULL, 0, NULL } }; -typedef struct { - int index; - char *like; +typedef struct +{ + int index; + char *like; } VARCONTEXT; /** * Callback function to populate rows of the show variable * command * - * @param data The context point - * @return The next row or NULL if end of rows + * @param data The context point + * @return The next row or NULL if end of rows */ static RESULT_ROW * variable_row(RESULTSET *result, void *data) { -VARCONTEXT *context = (VARCONTEXT *)data; -RESULT_ROW *row; -char buf[80]; + VARCONTEXT *context = (VARCONTEXT *)data; + RESULT_ROW *row; + char buf[80]; - if (variables[context->index].name) - { - if (context->like && - maxinfo_pattern_match(context->like, - variables[context->index].name)) - { - context->index++; - return variable_row(result, data); - } - row = resultset_make_row(result); - resultset_row_set(row, 0, variables[context->index].name); - switch (variables[context->index].type) - { - case VT_STRING: - resultset_row_set(row, 1, - (char *)(*variables[context->index].func)()); - break; - case VT_INT: - snprintf(buf, 80, "%ld", - (long)(*variables[context->index].func)()); - resultset_row_set(row, 1, buf); - break; - } - context->index++; - return row; - } - return NULL; + if (variables[context->index].name) + { + if (context->like && + maxinfo_pattern_match(context->like, + variables[context->index].name)) + { + context->index++; + return variable_row(result, data); + } + row = resultset_make_row(result); + resultset_row_set(row, 0, variables[context->index].name); + switch (variables[context->index].type) + { + case VT_STRING: + resultset_row_set(row, 1, + (char *)(*variables[context->index].func)()); + break; + case VT_INT: + snprintf(buf, 80, "%ld", + (long)(*variables[context->index].func)()); + resultset_row_set(row, 1, buf); + break; + } + context->index++; + return row; + } + return NULL; } /** * Execute a show variables command applying an optional filter * - * @param dcb The DCB connected to the client - * @param filter A potential like clause or NULL + * @param dcb The DCB connected to the client + * @param filter A potential like clause or NULL */ static void exec_show_variables(DCB *dcb, MAXINFO_TREE *filter) { -RESULTSET *result; -VARCONTEXT context; + RESULTSET *result; + VARCONTEXT context; - if (filter) - context.like = filter->value; - else - context.like = NULL; - context.index = 0; + if (filter) + { + context.like = filter->value; + } + else + { + context.like = NULL; + } + context.index = 0; - if ((result = resultset_create(variable_row, &context)) == NULL) - { - maxinfo_send_error(dcb, 0, "No resources available"); - return; - } - resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); - resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); - resultset_stream_mysql(result, dcb); - resultset_free(result); + if ((result = resultset_create(variable_row, &context)) == NULL) + { + maxinfo_send_error(dcb, 0, "No resources available"); + return; + } + resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); + resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); + resultset_stream_mysql(result, dcb); + resultset_free(result); } /** @@ -878,19 +910,19 @@ VARCONTEXT context; RESULTSET * maxinfo_variables() { -RESULTSET *result; -static VARCONTEXT context; + RESULTSET *result; + static VARCONTEXT context; - context.like = NULL; - context.index = 0; + context.like = NULL; + context.index = 0; - if ((result = resultset_create(variable_row, &context)) == NULL) - { - return NULL; - } - resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); - resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); - return result; + if ((result = resultset_create(variable_row, &context)) == NULL) + { + return NULL; + } + resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); + resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); + return result; } /** @@ -899,7 +931,7 @@ static VARCONTEXT context; static int maxinfo_all_dcbs() { - return dcb_count_by_usage(DCB_USAGE_ALL); + return dcb_count_by_usage(DCB_USAGE_ALL); } /** @@ -908,7 +940,7 @@ maxinfo_all_dcbs() static int maxinfo_client_dcbs() { - return dcb_count_by_usage(DCB_USAGE_CLIENT); + return dcb_count_by_usage(DCB_USAGE_CLIENT); } /** @@ -917,7 +949,7 @@ maxinfo_client_dcbs() static int maxinfo_listener_dcbs() { - return dcb_count_by_usage(DCB_USAGE_LISTENER); + return dcb_count_by_usage(DCB_USAGE_LISTENER); } /** @@ -926,7 +958,7 @@ maxinfo_listener_dcbs() static int maxinfo_backend_dcbs() { - return dcb_count_by_usage(DCB_USAGE_BACKEND); + return dcb_count_by_usage(DCB_USAGE_BACKEND); } /** @@ -935,7 +967,7 @@ maxinfo_backend_dcbs() static int maxinfo_internal_dcbs() { - return dcb_count_by_usage(DCB_USAGE_INTERNAL); + return dcb_count_by_usage(DCB_USAGE_INTERNAL); } /** @@ -944,7 +976,7 @@ maxinfo_internal_dcbs() static int maxinfo_zombie_dcbs() { - return dcb_count_by_usage(DCB_USAGE_ZOMBIE); + return dcb_count_by_usage(DCB_USAGE_ZOMBIE); } /** @@ -953,7 +985,7 @@ maxinfo_zombie_dcbs() static int maxinfo_read_events() { - return poll_get_stat(POLL_STAT_READ); + return poll_get_stat(POLL_STAT_READ); } /** @@ -962,7 +994,7 @@ maxinfo_read_events() static int maxinfo_write_events() { - return poll_get_stat(POLL_STAT_WRITE); + return poll_get_stat(POLL_STAT_WRITE); } /** @@ -971,7 +1003,7 @@ maxinfo_write_events() static int maxinfo_error_events() { - return poll_get_stat(POLL_STAT_ERROR); + return poll_get_stat(POLL_STAT_ERROR); } /** @@ -980,7 +1012,7 @@ maxinfo_error_events() static int maxinfo_hangup_events() { - return poll_get_stat(POLL_STAT_HANGUP); + return poll_get_stat(POLL_STAT_HANGUP); } /** @@ -989,7 +1021,7 @@ maxinfo_hangup_events() static int maxinfo_accept_events() { - return poll_get_stat(POLL_STAT_ACCEPT); + return poll_get_stat(POLL_STAT_ACCEPT); } /** @@ -998,7 +1030,7 @@ maxinfo_accept_events() static int maxinfo_event_queue_length() { - return poll_get_stat(POLL_STAT_EVQ_LEN); + return poll_get_stat(POLL_STAT_EVQ_LEN); } /** @@ -1007,7 +1039,7 @@ maxinfo_event_queue_length() static int maxinfo_event_pending_queue_length() { - return poll_get_stat(POLL_STAT_EVQ_PENDING); + return poll_get_stat(POLL_STAT_EVQ_PENDING); } /** @@ -1016,7 +1048,7 @@ maxinfo_event_pending_queue_length() static int maxinfo_max_event_queue_length() { - return poll_get_stat(POLL_STAT_EVQ_MAX); + return poll_get_stat(POLL_STAT_EVQ_MAX); } /** @@ -1025,7 +1057,7 @@ maxinfo_max_event_queue_length() static int maxinfo_max_event_queue_time() { - return poll_get_stat(POLL_STAT_MAX_QTIME); + return poll_get_stat(POLL_STAT_MAX_QTIME); } /** @@ -1034,112 +1066,118 @@ maxinfo_max_event_queue_time() static int maxinfo_max_event_exec_time() { - return poll_get_stat(POLL_STAT_MAX_EXECTIME); + return poll_get_stat(POLL_STAT_MAX_EXECTIME); } /** * Variables that may be sent in a show status */ -static struct { - char *name; - int type; - STATSFUNC func; -} status[] = { - { "Uptime", VT_INT, (STATSFUNC)maxscale_uptime }, - { "Uptime_since_flush_status", VT_INT, (STATSFUNC)maxscale_uptime }, - { "Threads_created", VT_INT, (STATSFUNC)config_threadcount }, - { "Threads_running", VT_INT, (STATSFUNC)config_threadcount }, - { "Threadpool_threads", VT_INT, (STATSFUNC)config_threadcount }, - { "Threads_connected", VT_INT, (STATSFUNC)serviceSessionCountAll }, - { "Connections", VT_INT, (STATSFUNC)maxinfo_all_dcbs }, - { "Client_connections", VT_INT, (STATSFUNC)maxinfo_client_dcbs }, - { "Backend_connections", VT_INT, (STATSFUNC)maxinfo_backend_dcbs }, - { "Listeners", VT_INT, (STATSFUNC)maxinfo_listener_dcbs }, - { "Zombie_connections", VT_INT, (STATSFUNC)maxinfo_zombie_dcbs }, - { "Internal_descriptors", VT_INT, (STATSFUNC)maxinfo_internal_dcbs }, - { "Read_events", VT_INT, (STATSFUNC)maxinfo_read_events }, - { "Write_events", VT_INT, (STATSFUNC)maxinfo_write_events }, - { "Hangup_events", VT_INT, (STATSFUNC)maxinfo_hangup_events }, - { "Error_events", VT_INT, (STATSFUNC)maxinfo_error_events }, - { "Accept_events", VT_INT, (STATSFUNC)maxinfo_accept_events }, - { "Event_queue_length", VT_INT, (STATSFUNC)maxinfo_event_queue_length }, - { "Pending_events", VT_INT, (STATSFUNC)maxinfo_event_pending_queue_length }, - { "Max_event_queue_length", VT_INT, (STATSFUNC)maxinfo_max_event_queue_length }, - { "Max_event_queue_time", VT_INT, (STATSFUNC)maxinfo_max_event_queue_time }, - { "Max_event_execution_time", VT_INT, (STATSFUNC)maxinfo_max_event_exec_time }, - { NULL, 0, NULL } +static struct +{ + char *name; + int type; + STATSFUNC func; +} status[] = +{ + { "Uptime", VT_INT, (STATSFUNC)maxscale_uptime }, + { "Uptime_since_flush_status", VT_INT, (STATSFUNC)maxscale_uptime }, + { "Threads_created", VT_INT, (STATSFUNC)config_threadcount }, + { "Threads_running", VT_INT, (STATSFUNC)config_threadcount }, + { "Threadpool_threads", VT_INT, (STATSFUNC)config_threadcount }, + { "Threads_connected", VT_INT, (STATSFUNC)serviceSessionCountAll }, + { "Connections", VT_INT, (STATSFUNC)maxinfo_all_dcbs }, + { "Client_connections", VT_INT, (STATSFUNC)maxinfo_client_dcbs }, + { "Backend_connections", VT_INT, (STATSFUNC)maxinfo_backend_dcbs }, + { "Listeners", VT_INT, (STATSFUNC)maxinfo_listener_dcbs }, + { "Zombie_connections", VT_INT, (STATSFUNC)maxinfo_zombie_dcbs }, + { "Internal_descriptors", VT_INT, (STATSFUNC)maxinfo_internal_dcbs }, + { "Read_events", VT_INT, (STATSFUNC)maxinfo_read_events }, + { "Write_events", VT_INT, (STATSFUNC)maxinfo_write_events }, + { "Hangup_events", VT_INT, (STATSFUNC)maxinfo_hangup_events }, + { "Error_events", VT_INT, (STATSFUNC)maxinfo_error_events }, + { "Accept_events", VT_INT, (STATSFUNC)maxinfo_accept_events }, + { "Event_queue_length", VT_INT, (STATSFUNC)maxinfo_event_queue_length }, + { "Pending_events", VT_INT, (STATSFUNC)maxinfo_event_pending_queue_length }, + { "Max_event_queue_length", VT_INT, (STATSFUNC)maxinfo_max_event_queue_length }, + { "Max_event_queue_time", VT_INT, (STATSFUNC)maxinfo_max_event_queue_time }, + { "Max_event_execution_time", VT_INT, (STATSFUNC)maxinfo_max_event_exec_time }, + { NULL, 0, NULL } }; /** * Callback function to populate rows of the show variable * command * - * @param data The context point - * @return The next row or NULL if end of rows + * @param data The context point + * @return The next row or NULL if end of rows */ static RESULT_ROW * status_row(RESULTSET *result, void *data) { -VARCONTEXT *context = (VARCONTEXT *)data; -RESULT_ROW *row; -char buf[80]; + VARCONTEXT *context = (VARCONTEXT *)data; + RESULT_ROW *row; + char buf[80]; - if (status[context->index].name) - { - if (context->like && - maxinfo_pattern_match(context->like, - status[context->index].name)) - { - context->index++; - return status_row(result, data); - } - row = resultset_make_row(result); - resultset_row_set(row, 0, status[context->index].name); - switch (status[context->index].type) - { - case VT_STRING: - resultset_row_set(row, 1, - (char *)(*status[context->index].func)()); - break; - case VT_INT: - snprintf(buf, 80, "%ld", - (long)(*status[context->index].func)()); - resultset_row_set(row, 1, buf); - break; - } - context->index++; - return row; - } - return NULL; + if (status[context->index].name) + { + if (context->like && + maxinfo_pattern_match(context->like, + status[context->index].name)) + { + context->index++; + return status_row(result, data); + } + row = resultset_make_row(result); + resultset_row_set(row, 0, status[context->index].name); + switch (status[context->index].type) + { + case VT_STRING: + resultset_row_set(row, 1, + (char *)(*status[context->index].func)()); + break; + case VT_INT: + snprintf(buf, 80, "%ld", + (long)(*status[context->index].func)()); + resultset_row_set(row, 1, buf); + break; + } + context->index++; + return row; + } + return NULL; } /** * Execute a show status command applying an optional filter * - * @param dcb The DCB connected to the client - * @param filter A potential like clause or NULL + * @param dcb The DCB connected to the client + * @param filter A potential like clause or NULL */ static void exec_show_status(DCB *dcb, MAXINFO_TREE *filter) { -RESULTSET *result; -VARCONTEXT context; + RESULTSET *result; + VARCONTEXT context; - if (filter) - context.like = filter->value; - else - context.like = NULL; - context.index = 0; + if (filter) + { + context.like = filter->value; + } + else + { + context.like = NULL; + } + context.index = 0; - if ((result = resultset_create(status_row, &context)) == NULL) - { - maxinfo_send_error(dcb, 0, "No resources available"); - return; - } - resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); - resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); - resultset_stream_mysql(result, dcb); - resultset_free(result); + if ((result = resultset_create(status_row, &context)) == NULL) + { + maxinfo_send_error(dcb, 0, "No resources available"); + return; + } + resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); + resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); + resultset_stream_mysql(result, dcb); + resultset_free(result); } /** @@ -1150,19 +1188,19 @@ VARCONTEXT context; RESULTSET * maxinfo_status() { -RESULTSET *result; -static VARCONTEXT context; + RESULTSET *result; + static VARCONTEXT context; - context.like = NULL; - context.index = 0; + context.like = NULL; + context.index = 0; - if ((result = resultset_create(status_row, &context)) == NULL) - { - return NULL; - } - resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); - resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); - return result; + if ((result = resultset_create(status_row, &context)) == NULL) + { + return NULL; + } + resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); + resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); + return result; } @@ -1170,57 +1208,65 @@ static VARCONTEXT context; * Execute a select command parse tree and return the result set * or runtime error * - * @param dcb The DCB that connects to the client - * @param tree The parse tree for the query + * @param dcb The DCB that connects to the client + * @param tree The parse tree for the query */ static void exec_select(DCB *dcb, MAXINFO_TREE *tree) { - maxinfo_send_error(dcb, 0, "Select not yet implemented"); + maxinfo_send_error(dcb, 0, "Select not yet implemented"); } /** * Perform a "like" pattern match. Only works for leading and trailing % * - * @param pattern Pattern to match - * @param str String to match against pattern - * @return Zero on match + * @param pattern Pattern to match + * @param str String to match against pattern + * @return Zero on match */ static int maxinfo_pattern_match(char *pattern, char *str) { -int anchor = 0, len, trailing; -char *fixed; -extern char *strcasestr(); + int anchor = 0, len, trailing; + char *fixed; + extern char *strcasestr(); - if (*pattern != '%') - { - fixed = pattern; - anchor = 1; - } - else - { - fixed = &pattern[1]; - } - len = strlen(fixed); - if (fixed[len - 1] == '%') - trailing = 1; - else - trailing = 0; - if (anchor == 1 && trailing == 0) // No wildcard - return strcasecmp(pattern, str); - else if (anchor == 1) - return strncasecmp(str, pattern, len - trailing); - else - { - char *portion = malloc(len + 1); - int rval; - strncpy(portion, fixed, len - trailing); - portion[len - trailing] = 0; - rval = (strcasestr(str, portion) != NULL ? 0 : 1); - free(portion); - return rval; - } + if (*pattern != '%') + { + fixed = pattern; + anchor = 1; + } + else + { + fixed = &pattern[1]; + } + len = strlen(fixed); + if (fixed[len - 1] == '%') + { + trailing = 1; + } + else + { + trailing = 0; + } + if (anchor == 1 && trailing == 0) // No wildcard + { + return strcasecmp(pattern, str); + } + else if (anchor == 1) + { + return strncasecmp(str, pattern, len - trailing); + } + else + { + char *portion = malloc(len + 1); + int rval; + strncpy(portion, fixed, len - trailing); + portion[len - trailing] = 0; + rval = (strcasestr(str, portion) != NULL ? 0 : 1); + free(portion); + return rval; + } } /** @@ -1229,7 +1275,8 @@ extern char *strcasestr(); */ void maxinfo_send_ok(DCB *dcb) { - static const char ok_packet[] ={ + static const char ok_packet[] = + { 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, From de4ea067cffbce9c93bf4ba84d2f78af64821be3 Mon Sep 17 00:00:00 2001 From: ekorh475 Date: Fri, 18 Nov 2016 11:30:02 +0200 Subject: [PATCH 08/11] Fix for MXS-968 This commit adds a free() to null_auth_free_client_data, which plugs the memory leak in maxinfo. Also, this commit fixes some segfaults when multiple threads are running status_row() or variable_row(). The functions use statically allocated index variables, which often go out-of-bounds in concurrent use. This fix changes the indexes to thread-specific variables, with allocating and deallocating. This does seem to slow the functions down somewhat. --- server/modules/authenticator/null_auth.c | 6 ++- server/modules/routing/maxinfo/maxinfo_exec.c | 49 ++++++++++++------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/server/modules/authenticator/null_auth.c b/server/modules/authenticator/null_auth.c index 6819e1d06..c4ca31f08 100644 --- a/server/modules/authenticator/null_auth.c +++ b/server/modules/authenticator/null_auth.c @@ -150,4 +150,8 @@ null_auth_is_client_ssl_capable(DCB *dcb) * @param dcb Request handler DCB connected to the client */ static void -null_auth_free_client_data(DCB *dcb) {} +null_auth_free_client_data(DCB *dcb) +{ + free(dcb->data); + dcb->data = NULL; +} diff --git a/server/modules/routing/maxinfo/maxinfo_exec.c b/server/modules/routing/maxinfo/maxinfo_exec.c index bbe8a782e..884d65e42 100644 --- a/server/modules/routing/maxinfo/maxinfo_exec.c +++ b/server/modules/routing/maxinfo/maxinfo_exec.c @@ -825,7 +825,6 @@ typedef struct int index; char *like; } VARCONTEXT; - /** * Callback function to populate rows of the show variable * command @@ -836,9 +835,9 @@ typedef struct static RESULT_ROW * variable_row(RESULTSET *result, void *data) { - VARCONTEXT *context = (VARCONTEXT *)data; - RESULT_ROW *row; - char buf[80]; + VARCONTEXT *context = (VARCONTEXT *) data; + RESULT_ROW *row; + char buf[80]; if (variables[context->index].name) { @@ -862,10 +861,14 @@ variable_row(RESULTSET *result, void *data) (long)(*variables[context->index].func)()); resultset_row_set(row, 1, buf); break; + default: + ss_dassert(!true); } context->index++; return row; } + // We only get to this point once all variables have been printed + free(data); return NULL; } @@ -910,16 +913,20 @@ exec_show_variables(DCB *dcb, MAXINFO_TREE *filter) RESULTSET * maxinfo_variables() { - RESULTSET *result; - static VARCONTEXT context; - - context.like = NULL; - context.index = 0; - - if ((result = resultset_create(variable_row, &context)) == NULL) + RESULTSET *result; + VARCONTEXT *context; + if ((context = malloc(sizeof(VARCONTEXT))) == NULL) { return NULL; } + context->like = NULL; + context->index = 0; + + if ((result = resultset_create(variable_row, context)) == NULL) + { + free(context); + return NULL; + } resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); return result; @@ -1140,10 +1147,14 @@ status_row(RESULTSET *result, void *data) (long)(*status[context->index].func)()); resultset_row_set(row, 1, buf); break; + default: + ss_dassert(!true); } context->index++; return row; } + // We only get to this point once all status elements have been printed + free(data); return NULL; } @@ -1189,15 +1200,19 @@ RESULTSET * maxinfo_status() { RESULTSET *result; - static VARCONTEXT context; - - context.like = NULL; - context.index = 0; - - if ((result = resultset_create(status_row, &context)) == NULL) + VARCONTEXT *context; + if ((context = malloc(sizeof(VARCONTEXT))) == NULL) { return NULL; } + context->like = NULL; + context->index = 0; + + if ((result = resultset_create(status_row, context)) == NULL) + { + free(context); + return NULL; + } resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR); resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR); return result; From 6a68338c7274e4d754df39b531acc7346ec1f530 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 21 Nov 2016 09:29:14 +0200 Subject: [PATCH 09/11] Update version to 2.0.2 --- VERSION.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.cmake b/VERSION.cmake index 0693bfd8b..cb7413725 100644 --- a/VERSION.cmake +++ b/VERSION.cmake @@ -5,7 +5,7 @@ set(MAXSCALE_VERSION_MAJOR "2" CACHE STRING "Major version") set(MAXSCALE_VERSION_MINOR "0" CACHE STRING "Minor version") -set(MAXSCALE_VERSION_PATCH "1" CACHE STRING "Patch version") +set(MAXSCALE_VERSION_PATCH "2" CACHE STRING "Patch version") # This should only be incremented if a package is rebuilt set(MAXSCALE_BUILD_NUMBER 1 CACHE STRING "Release number") From ef4fe8785f1144946e73f175c7293602a59dae02 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 21 Nov 2016 10:01:42 +0200 Subject: [PATCH 10/11] Update ChangeLog --- Documentation/Changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index 264a2038a..e7818829e 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -11,7 +11,9 @@ as JSON objects (beta level functionality). For more details, please refer to: +* [MariaDB MaxScale 2.0.2 Release Notes](Release-Notes/MaxScale-2.0.2-Release-Notes.md) * [MariaDB MaxScale 2.0.1 Release Notes](Release-Notes/MaxScale-2.0.1-Release-Notes.md) +* [MariaDB MaxScale 2.0.0 Release Notes](Release-Notes/MaxScale-2.0.0-Release-Notes.md) ## MariaDB MaxScale 1.4 * Authentication now allows table level resolution of grants. MaxScale service From 3b30d5c8100dbfc75fdbb83b1272102c7d99f75a Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 21 Nov 2016 09:55:24 +0200 Subject: [PATCH 11/11] Update 2.0.2 release notes --- .../MaxScale-2.0.2-Release-Notes.md | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md diff --git a/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md new file mode 100644 index 000000000..e50c25a00 --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-2.0.2-Release-Notes.md @@ -0,0 +1,62 @@ +# MariaDB MaxScale 2.0.2 Release Notes + +Release 2.0.2 is a GA release. + +This document describes the changes in release 2.0.2, when compared to +release [2.0.1](MaxScale-2.0.1-Release-Notes.md). + +If you are upgrading from release 1.4.4, please also read the release +notes of release [2.0.0](./MaxScale-2.0.0-Release-Notes.md) and +release [2.0.1](./MaxScale-2.0.1-Release-Notes.md). + +For any problems you encounter, please submit a bug report at +[Jira](https://jira.mariadb.org). + +## Updated Features + +### [MXS-978] (https://jira.mariadb.org/browse/MXS-978) Support for stale master in case of restart + +In case where replication monitor gets a stale master status (slaves are down) +and maxscale gets restarted, master loses the stale master status and no writes +can happen. + +To cater for this situation there is now a `set server stale` command. + +## Bug fixes + +[Here is a list of bugs fixed since the release of MaxScale 2.0.1.](https://jira.mariadb.org/browse/MXS-976?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.0.2) + +* [MXS-1018](https://jira.mariadb.org/browse/MXS-1018): Internal connections don't use TLS +* [MXS-976](https://jira.mariadb.org/browse/MXS-976): Crash in libqc_sqlite +* [MXS-975](https://jira.mariadb.org/browse/MXS-975): TCP backlog is capped at 1280 +* [MXS-970](https://jira.mariadb.org/browse/MXS-970): A fatal problem with maxscale automatically shut down +* [MXS-969](https://jira.mariadb.org/browse/MXS-969): use_sql_variables_in=master can break functionality of important session variables +* [MXS-967](https://jira.mariadb.org/browse/MXS-967): setting connection_timeout=value cause error : Not a boolean value +* [MXS-965](https://jira.mariadb.org/browse/MXS-965): galeramon erlaubt keine TLS verschlüsselte Verbindung +* [MXS-960](https://jira.mariadb.org/browse/MXS-960): MaxScale Binlog Server does not allow comma to be in password +* [MXS-957](https://jira.mariadb.org/browse/MXS-957): Temporary table creation from another temporary table isn't detected +* [MXS-955](https://jira.mariadb.org/browse/MXS-955): MaxScale 2.0.1 doesn't recognize user and passwd options in .maxadmin file +* [MXS-953](https://jira.mariadb.org/browse/MXS-953): Charset error when server configued in utf8mb4 +* [MXS-942](https://jira.mariadb.org/browse/MXS-942): describe table query not routed to shard that contains the schema +* [MXS-917](https://jira.mariadb.org/browse/MXS-917): False error message about master not being in use + +## Known Issues and Limitations + +There are some limitations and known issues within this version of MaxScale. +For more information, please refer to the [Limitations](../About/Limitations.md) document. + +## Packaging + +RPM and Debian packages are provided for the Linux distributions supported +by MariaDB Enterprise. + +Packages can be downloaded [here](https://mariadb.com/resources/downloads). + +## Source Code + +The source code of MaxScale is tagged at GitHub with a tag, which is derived +from the version of MaxScale. For instance, the tag of version `X.Y.Z` of MaxScale +is `maxscale-X.Y.Z`. + +The source code is available [here](https://github.com/mariadb-corporation/MaxScale). +