Improved multi-statement detection in readwritesplit
Now comment blocks and identifiers quoted with backticks are taken into consideration when multi-statements are processed.
This commit is contained in:
@ -801,6 +801,115 @@ void* strnchr_esc(char* ptr, char c, int len)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first occurrence of a character in a string. This function ignores
|
||||||
|
* escaped characters and all characters that are enclosed in single or double quotes.
|
||||||
|
* Also MySQL style comment blocks are ignored.
|
||||||
|
* @param ptr Pointer to area of memory to inspect
|
||||||
|
* @param c Character to search for
|
||||||
|
* @param len Size of the memory area
|
||||||
|
* @return Pointer to the first non-escaped, non-quoted occurrence of the character.
|
||||||
|
* If the character is not found, NULL is returned.
|
||||||
|
*/
|
||||||
|
void* strnchr_esc_mysql(char* ptr, char c, int len)
|
||||||
|
{
|
||||||
|
char* p = (char*) ptr;
|
||||||
|
char* start = p;
|
||||||
|
bool quoted = false, escaped = false, backtick = false;
|
||||||
|
char qc;
|
||||||
|
|
||||||
|
while (p < start + len)
|
||||||
|
{
|
||||||
|
if (escaped)
|
||||||
|
{
|
||||||
|
escaped = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (*p)
|
||||||
|
{
|
||||||
|
case '\\':
|
||||||
|
escaped = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case'\'':
|
||||||
|
case '"':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
quoted = true;
|
||||||
|
qc = *p;
|
||||||
|
}
|
||||||
|
else if (*p == qc)
|
||||||
|
{
|
||||||
|
quoted = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '`':
|
||||||
|
backtick = !backtick;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '#':
|
||||||
|
if (!backtick)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
if (!backtick && p + 1 < start + len && *(p + 1) == '-')
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p == c && !escaped && !quoted)
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the start of the token is the start of a MySQL style comment block
|
||||||
|
* @param ptr String with at least two non-null characters in it
|
||||||
|
* @return True if the token starts a comment block
|
||||||
|
*/
|
||||||
|
bool is_mysql_comment_start(const char* start, int len)
|
||||||
|
{
|
||||||
|
const char *ptr = start;
|
||||||
|
|
||||||
|
while (ptr - start < len && isspace(*ptr))
|
||||||
|
{
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (*ptr)
|
||||||
|
{
|
||||||
|
case '-':
|
||||||
|
if (*(ptr + 1) == '-')
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '#':
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
if (*(ptr + 1) == '*')
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a COM_QUERY packet from a string.
|
* Create a COM_QUERY packet from a string.
|
||||||
* @param query Query to create.
|
* @param query Query to create.
|
||||||
|
@ -67,6 +67,10 @@ GWBUF* modutil_create_mysql_err_msg(int packet_number,
|
|||||||
const char *msg);
|
const char *msg);
|
||||||
int modutil_count_signal_packets(GWBUF*,int,int,int*);
|
int modutil_count_signal_packets(GWBUF*,int,int,int*);
|
||||||
mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string);
|
mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string);
|
||||||
|
|
||||||
|
/** Character and token searching functions */
|
||||||
void* strnchr_esc(char* ptr, char c, int len);
|
void* strnchr_esc(char* ptr, char c, int len);
|
||||||
|
void* strnchr_esc_mysql(char* ptr, char c, int len);
|
||||||
|
bool is_mysql_comment_start(const char* start, int len);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -308,6 +308,8 @@ static ROUTER_INSTANCE* instances;
|
|||||||
|
|
||||||
static int hashkeyfun(void* key);
|
static int hashkeyfun(void* key);
|
||||||
static int hashcmpfun (void *, void *);
|
static int hashcmpfun (void *, void *);
|
||||||
|
static void check_for_multi_stmt(ROUTER_CLIENT_SES* rses, GWBUF *buf,
|
||||||
|
mysql_server_cmd_t packet_type);
|
||||||
|
|
||||||
static int hashkeyfun(
|
static int hashkeyfun(
|
||||||
void* key)
|
void* key)
|
||||||
@ -2156,23 +2158,7 @@ static bool route_single_stmt(
|
|||||||
/** Check for multi-statement queries. We assume here that the client
|
/** Check for multi-statement queries. We assume here that the client
|
||||||
* protocol is MySQLClient.
|
* protocol is MySQLClient.
|
||||||
* TODO: add warnings when incompatible protocols are used */
|
* TODO: add warnings when incompatible protocols are used */
|
||||||
MySQLProtocol *proto = (MySQLProtocol*)rses->client_dcb->protocol;
|
check_for_multi_stmt(rses, querybuf, packet_type);
|
||||||
if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS &&
|
|
||||||
packet_type == MYSQL_COM_QUERY && rses->forced_node != rses->rses_master_ref)
|
|
||||||
{
|
|
||||||
for (GWBUF *buf = querybuf; buf != NULL; buf = buf->next)
|
|
||||||
{
|
|
||||||
if (strnchr_esc(GWBUF_DATA(buf), ';', GWBUF_LENGTH(buf)))
|
|
||||||
{
|
|
||||||
/** It is possible that the session state is modified inside
|
|
||||||
* the multi-statement query which would leave any slave sessions
|
|
||||||
* in an inconsistent state. Due to this, for the duration of
|
|
||||||
* this session, all queries will be sent to the master. */
|
|
||||||
rses->forced_node = rses->rses_master_ref;
|
|
||||||
MXS_INFO("Multi-statement query, routing all future queries to master.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the query has anything to do with temporary tables.
|
* Check if the query has anything to do with temporary tables.
|
||||||
@ -5357,12 +5343,35 @@ static backend_ref_t* get_root_master_bref(
|
|||||||
return candidate_bref;
|
return candidate_bref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Detect multi-statement queries
|
||||||
|
*
|
||||||
|
* It is possible that the session state is modified inside a multi-statement
|
||||||
|
* query which would leave any slave sessions in an inconsistent state. Due to
|
||||||
|
* this, for the duration of this session, all queries will be sent to the master
|
||||||
|
* if the current query contains a multi-statement query.
|
||||||
|
* @param rses Router client session
|
||||||
|
* @param buf Buffer containing the full query
|
||||||
|
*/
|
||||||
|
static void check_for_multi_stmt(ROUTER_CLIENT_SES* rses, GWBUF *buf,
|
||||||
|
mysql_server_cmd_t packet_type)
|
||||||
|
{
|
||||||
|
MySQLProtocol *proto = (MySQLProtocol*) rses->client_dcb->protocol;
|
||||||
|
|
||||||
|
if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS &&
|
||||||
|
packet_type == MYSQL_COM_QUERY && rses->forced_node != rses->rses_master_ref)
|
||||||
|
{
|
||||||
|
char *ptr, *data = GWBUF_DATA(buf) + 5;
|
||||||
|
int buflen = GWBUF_LENGTH(buf) - 5;
|
||||||
|
|
||||||
|
if ((ptr = strnchr_esc_mysql(data, ';', buflen)))
|
||||||
|
{
|
||||||
|
ptr++;
|
||||||
|
if (ptr - data < buflen && !is_mysql_comment_start(ptr, ptr - data))
|
||||||
|
{
|
||||||
|
rses->forced_node = rses->rses_master_ref;
|
||||||
|
MXS_INFO("Multi-statement query, routing all future queries to master.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user