To prevent bugs caused by pointers having the wrong type (e.g. uint32_t instead of uint8_t), some macros are changed into inline functions so that the normal type-checking is performed. The macros MYSQL_GET_ERRCODE, MYSQL_GET_STMTOK_NPARAM, MYSQL_GET_STMTOK_NATTR, and MYSQL_GET_NATTR were not changed, because they may be too specific to be present in a general purpose header in the first place.
		
			
				
	
	
		
			2603 lines
		
	
	
		
			70 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2603 lines
		
	
	
		
			70 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/**
 | 
						|
 * @section LICENCE
 | 
						|
 *
 | 
						|
 * This file is distributed as part of the MariaDB Corporation MaxScale. It is
 | 
						|
 * free software: you can redistribute it and/or modify it under
 | 
						|
 * the terms of the GNU General Public License as published by the
 | 
						|
 * Free Software Foundation, version 2.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 * GNU General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License
 | 
						|
 * along with this program; if not, write to the Free Software
 | 
						|
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | 
						|
 * 02110-1301 USA.
 | 
						|
 *
 | 
						|
 * Copyright MariaDB Corporation Ab
 | 
						|
 *
 | 
						|
 * @file
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
#define EMBEDDED_LIBRARY
 | 
						|
#define MYSQL_YACC
 | 
						|
#define MYSQL_LEX012
 | 
						|
#define MYSQL_SERVER
 | 
						|
#if defined(MYSQL_CLIENT)
 | 
						|
#undef MYSQL_CLIENT
 | 
						|
#endif
 | 
						|
 | 
						|
#include <my_config.h>
 | 
						|
#include <mysql.h>
 | 
						|
#include <my_sys.h>
 | 
						|
#include <my_global.h>
 | 
						|
#include <my_dbug.h>
 | 
						|
#include <my_base.h>
 | 
						|
#include <sql_list.h>
 | 
						|
#include <mysqld_error.h>
 | 
						|
#include <sql_class.h>
 | 
						|
#include <sql_lex.h>
 | 
						|
#include <embedded_priv.h>
 | 
						|
#include <sql_class.h>
 | 
						|
#include <sql_lex.h>
 | 
						|
#include <sql_parse.h>
 | 
						|
#include <errmsg.h>
 | 
						|
#include <client_settings.h>
 | 
						|
// In client_settings.h mysql_server_init and mysql_server_end are defined to
 | 
						|
// mysql_client_plugin_init and mysql_client_plugin_deinit respectively.
 | 
						|
// Those must be undefined, so that we here really call mysql_server_[init|end].
 | 
						|
#undef mysql_server_init
 | 
						|
#undef mysql_server_end
 | 
						|
#include <set_var.h>
 | 
						|
#include <strfunc.h>
 | 
						|
#include <item_func.h>
 | 
						|
 | 
						|
#include <maxscale/debug.h>
 | 
						|
#include <maxscale/log_manager.h>
 | 
						|
#include <maxscale/query_classifier.h>
 | 
						|
// <maxscale/protocol/mysql.h> assumes it is being compiled agains Connector-C,
 | 
						|
// so we need to make certain Connector-C constants visible.
 | 
						|
#define MYSQL_COM_QUIT        COM_QUIT
 | 
						|
#define MYSQL_COM_INIT_DB     COM_INIT_DB
 | 
						|
#define MYSQL_COM_CHANGE_USER COM_CHANGE_USER
 | 
						|
#include <maxscale/protocol/mysql.h>
 | 
						|
#include <maxscale/gwdirs.h>
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdarg.h>
 | 
						|
 | 
						|
#define MYSQL_COM_QUERY_HEADER_SIZE 5 /*< 3 bytes size, 1 sequence, 1 command */
 | 
						|
#define MAX_QUERYBUF_SIZE 2048
 | 
						|
typedef struct parsing_info_st
 | 
						|
{
 | 
						|
#if defined(SS_DEBUG)
 | 
						|
    skygw_chk_t pi_chk_top;
 | 
						|
#endif
 | 
						|
    void* pi_handle; /*< parsing info object pointer */
 | 
						|
    char* pi_query_plain_str; /*< query as plain string */
 | 
						|
    void (*pi_done_fp)(void *); /*< clean-up function for parsing info */
 | 
						|
    QC_FIELD_INFO* field_infos;
 | 
						|
    size_t field_infos_len;
 | 
						|
    size_t field_infos_capacity;
 | 
						|
#if defined(SS_DEBUG)
 | 
						|
    skygw_chk_t pi_chk_tail;
 | 
						|
#endif
 | 
						|
} parsing_info_t;
 | 
						|
 | 
						|
#define QTYPE_LESS_RESTRICTIVE_THAN_WRITE(t) (t<QUERY_TYPE_WRITE ? true : false)
 | 
						|
 | 
						|
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(parsing_info_t*, THD* thd);
 | 
						|
static bool skygw_stmt_causes_implicit_commit(LEX* lex, int* autocommit_stmt);
 | 
						|
 | 
						|
static int is_autocommit_stmt(LEX* lex);
 | 
						|
static parsing_info_t* parsing_info_init(void (*donefun)(void *));
 | 
						|
static void parsing_info_set_plain_str(void* ptr, char* str);
 | 
						|
/** Free THD context and close MYSQL */
 | 
						|
static void parsing_info_done(void* ptr);
 | 
						|
static TABLE_LIST* skygw_get_affected_tables(void* lexptr);
 | 
						|
static bool ensure_query_is_parsed(GWBUF* query);
 | 
						|
static bool parse_query(GWBUF* querybuf);
 | 
						|
static bool query_is_parsed(GWBUF* buf);
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Ensures that the query is parsed. If it is not already parsed, it
 | 
						|
 * will be parsed.
 | 
						|
 *
 | 
						|
 * @return true if the query is parsed, false otherwise.
 | 
						|
 */
 | 
						|
bool ensure_query_is_parsed(GWBUF* query)
 | 
						|
{
 | 
						|
    bool parsed = query_is_parsed(query);
 | 
						|
 | 
						|
    if (!parsed)
 | 
						|
    {
 | 
						|
        parsed = parse_query(query);
 | 
						|
 | 
						|
        if (!parsed)
 | 
						|
        {
 | 
						|
            MXS_ERROR("Unable to parse query, out of resources?");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return parsed;
 | 
						|
}
 | 
						|
 | 
						|
qc_parse_result_t qc_parse(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    bool parsed = ensure_query_is_parsed(querybuf);
 | 
						|
 | 
						|
    // Since the query is parsed using the same parser - subject to version
 | 
						|
    // differences between the embedded library and the server - either the
 | 
						|
    // query is valid and hence correctly parsed, or the query is invalid in
 | 
						|
    // which case the server will also consider it invalid and reject it. So,
 | 
						|
    // it's always ok to claim it has been parsed.
 | 
						|
    return parsed ? QC_QUERY_PARSED : QC_QUERY_INVALID;
 | 
						|
}
 | 
						|
 | 
						|
uint32_t qc_get_type(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    MYSQL* mysql;
 | 
						|
    uint32_t qtype = QUERY_TYPE_UNKNOWN;
 | 
						|
    bool succp;
 | 
						|
 | 
						|
    ss_info_dassert(querybuf != NULL, ("querybuf is NULL"));
 | 
						|
 | 
						|
    if (querybuf == NULL)
 | 
						|
    {
 | 
						|
        succp = false;
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    succp = ensure_query_is_parsed(querybuf);
 | 
						|
 | 
						|
    /** Read thd pointer and resolve the query type with it. */
 | 
						|
    if (succp)
 | 
						|
    {
 | 
						|
        parsing_info_t* pi;
 | 
						|
 | 
						|
        pi = (parsing_info_t*) gwbuf_get_buffer_object_data(querybuf,
 | 
						|
                                                            GWBUF_PARSING_INFO);
 | 
						|
 | 
						|
        if (pi != NULL)
 | 
						|
        {
 | 
						|
            mysql = (MYSQL *) pi->pi_handle;
 | 
						|
 | 
						|
            /** Find out the query type */
 | 
						|
            if (mysql != NULL)
 | 
						|
            {
 | 
						|
                qtype = resolve_query_type(pi, (THD *) mysql->thd);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
retblock:
 | 
						|
    return qtype;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create parsing info and try to parse the query included in the query buffer.
 | 
						|
 * Store pointer to created parse_tree_t object to buffer.
 | 
						|
 *
 | 
						|
 * @param querybuf buffer including the query and possibly the parsing information
 | 
						|
 *
 | 
						|
 * @return true if succeed, false otherwise
 | 
						|
 */
 | 
						|
static bool parse_query(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    bool succp;
 | 
						|
    THD* thd;
 | 
						|
    uint8_t* data;
 | 
						|
    size_t len;
 | 
						|
    char* query_str = NULL;
 | 
						|
    parsing_info_t* pi;
 | 
						|
 | 
						|
    CHK_GWBUF(querybuf);
 | 
						|
    /** Do not parse without releasing previous parse info first */
 | 
						|
    ss_dassert(!query_is_parsed(querybuf));
 | 
						|
 | 
						|
    if (querybuf == NULL || query_is_parsed(querybuf))
 | 
						|
    {
 | 
						|
        MXS_ERROR("Query is NULL (%p) or query is already parsed.", querybuf);
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    /** Create parsing info */
 | 
						|
    pi = parsing_info_init(parsing_info_done);
 | 
						|
 | 
						|
    if (pi == NULL)
 | 
						|
    {
 | 
						|
        MXS_ERROR("Parsing info initialization failed.");
 | 
						|
        succp = false;
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    /** Extract query and copy it to different buffer */
 | 
						|
    data = (uint8_t*) GWBUF_DATA(querybuf);
 | 
						|
    len = MYSQL_GET_PACKET_LEN(data) - 1; /*< distract 1 for packet type byte */
 | 
						|
 | 
						|
 | 
						|
    if (len < 1 || len >= ~((size_t) 0) - 1 || (query_str = (char *) malloc(len + 1)) == NULL)
 | 
						|
    {
 | 
						|
        /** Free parsing info data */
 | 
						|
        MXS_ERROR("Length (%lu) is 0 or query string allocation failed (%p). Buffer is %lu bytes.",
 | 
						|
                  len, query_str, GWBUF_LENGTH(querybuf));
 | 
						|
        parsing_info_done(pi);
 | 
						|
        succp = false;
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    memcpy(query_str, &data[5], len);
 | 
						|
    memset(&query_str[len], 0, 1);
 | 
						|
    parsing_info_set_plain_str(pi, query_str);
 | 
						|
 | 
						|
    /** Get one or create new THD object to be use in parsing */
 | 
						|
    thd = get_or_create_thd_for_parsing((MYSQL *) pi->pi_handle, query_str);
 | 
						|
 | 
						|
    if (thd == NULL)
 | 
						|
    {
 | 
						|
        MXS_ERROR("THD creation failed.");
 | 
						|
        /** Free parsing info data */
 | 
						|
        parsing_info_done(pi);
 | 
						|
        succp = false;
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Create parse_tree inside thd.
 | 
						|
     * thd and lex are readable even if creating parse tree fails.
 | 
						|
     */
 | 
						|
    create_parse_tree(thd);
 | 
						|
    /** Add complete parsing info struct to the query buffer */
 | 
						|
    gwbuf_add_buffer_object(querybuf,
 | 
						|
                            GWBUF_PARSING_INFO,
 | 
						|
                            (void *) pi,
 | 
						|
                            parsing_info_done);
 | 
						|
 | 
						|
    succp = true;
 | 
						|
retblock:
 | 
						|
    return succp;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * If buffer has non-NULL gwbuf_parsing_info it is parsed and it has parsing
 | 
						|
 * information included.
 | 
						|
 *
 | 
						|
 * @param buf buffer being examined
 | 
						|
 *
 | 
						|
 * @return true or false
 | 
						|
 */
 | 
						|
static bool query_is_parsed(GWBUF* buf)
 | 
						|
{
 | 
						|
    CHK_GWBUF(buf);
 | 
						|
    return (buf != NULL && GWBUF_IS_PARSED(buf));
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create a thread context, thd, init embedded server, connect to it, and allocate
 | 
						|
 * query to thd.
 | 
						|
 *
 | 
						|
 * Parameters:
 | 
						|
 * @param mysql         Database handle
 | 
						|
 *
 | 
						|
 * @param query_str     Query in plain txt string
 | 
						|
 *
 | 
						|
 * @return Thread context pointer
 | 
						|
 *
 | 
						|
 */
 | 
						|
static THD* get_or_create_thd_for_parsing(MYSQL* mysql, char* query_str)
 | 
						|
{
 | 
						|
    THD* thd = NULL;
 | 
						|
    unsigned long client_flags;
 | 
						|
    char* db = mysql->options.db;
 | 
						|
    bool failp = FALSE;
 | 
						|
    size_t query_len;
 | 
						|
 | 
						|
    ss_info_dassert(mysql != NULL, ("mysql is NULL"));
 | 
						|
    ss_info_dassert(query_str != NULL, ("query_str is NULL"));
 | 
						|
 | 
						|
    query_len = strlen(query_str);
 | 
						|
    client_flags = set_client_flags(mysql);
 | 
						|
 | 
						|
    /** Get THD.
 | 
						|
     * NOTE: Instead of creating new every time, THD instance could
 | 
						|
     * be get from a pool of them.
 | 
						|
     */
 | 
						|
    thd = (THD *) create_embedded_thd(client_flags);
 | 
						|
 | 
						|
    if (thd == NULL)
 | 
						|
    {
 | 
						|
        MXS_ERROR("Failed to create thread context for parsing.");
 | 
						|
        goto return_thd;
 | 
						|
    }
 | 
						|
 | 
						|
    mysql->thd = thd;
 | 
						|
    init_embedded_mysql(mysql, client_flags);
 | 
						|
    failp = check_embedded_connection(mysql, db);
 | 
						|
 | 
						|
    if (failp)
 | 
						|
    {
 | 
						|
        MXS_ERROR("Call to check_embedded_connection failed.");
 | 
						|
        goto return_err_with_thd;
 | 
						|
    }
 | 
						|
 | 
						|
    thd->clear_data_list();
 | 
						|
 | 
						|
    /** Check that we are calling the client functions in right order */
 | 
						|
    if (mysql->status != MYSQL_STATUS_READY)
 | 
						|
    {
 | 
						|
        set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate);
 | 
						|
        MXS_ERROR("Invalid status %d in embedded server.",
 | 
						|
                  mysql->status);
 | 
						|
        goto return_err_with_thd;
 | 
						|
    }
 | 
						|
 | 
						|
    /** Clear result variables */
 | 
						|
    thd->current_stmt = NULL;
 | 
						|
    thd->store_globals();
 | 
						|
    /**
 | 
						|
     * We have to call free_old_query before we start to fill mysql->fields
 | 
						|
     * for new query. In the case of embedded server we collect field data
 | 
						|
     * during query execution (not during data retrieval as it is in remote
 | 
						|
     * client). So we have to call free_old_query here
 | 
						|
     */
 | 
						|
    free_old_query(mysql);
 | 
						|
    thd->extra_length = query_len;
 | 
						|
    thd->extra_data = query_str;
 | 
						|
    alloc_query(thd, query_str, query_len);
 | 
						|
    goto return_thd;
 | 
						|
 | 
						|
return_err_with_thd:
 | 
						|
    (*mysql->methods->free_embedded_thd)(mysql);
 | 
						|
    thd = 0;
 | 
						|
    mysql->thd = 0;
 | 
						|
return_thd:
 | 
						|
    return thd;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @node  Set client flags. This is copied from libmysqld.c:mysql_real_connect
 | 
						|
 *
 | 
						|
 * Parameters:
 | 
						|
 * @param mysql - <usage>
 | 
						|
 *          <description>
 | 
						|
 *
 | 
						|
 * @return
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * @details (write detailed description here)
 | 
						|
 *
 | 
						|
 */
 | 
						|
static unsigned long set_client_flags(MYSQL* mysql)
 | 
						|
{
 | 
						|
    unsigned long f = 0;
 | 
						|
 | 
						|
    f |= mysql->options.client_flag;
 | 
						|
 | 
						|
    /* Send client information for access check */
 | 
						|
    f |= CLIENT_CAPABILITIES;
 | 
						|
 | 
						|
    if (f & CLIENT_MULTI_STATEMENTS)
 | 
						|
    {
 | 
						|
        f |= CLIENT_MULTI_RESULTS;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * No compression in embedded as we don't send any data,
 | 
						|
     * and no pluggable auth, as we cannot do a client-server dialog
 | 
						|
     */
 | 
						|
    f &= ~(CLIENT_COMPRESS | CLIENT_PLUGIN_AUTH);
 | 
						|
 | 
						|
    if (mysql->options.db != NULL)
 | 
						|
    {
 | 
						|
        f |= CLIENT_CONNECT_WITH_DB;
 | 
						|
    }
 | 
						|
 | 
						|
    return f;
 | 
						|
}
 | 
						|
 | 
						|
static bool create_parse_tree(THD* thd)
 | 
						|
{
 | 
						|
    Parser_state parser_state;
 | 
						|
    bool failp = FALSE;
 | 
						|
    const char* virtual_db = "skygw_virtual";
 | 
						|
 | 
						|
    if (parser_state.init(thd, thd->query(), thd->query_length()))
 | 
						|
    {
 | 
						|
        failp = TRUE;
 | 
						|
        goto return_here;
 | 
						|
    }
 | 
						|
 | 
						|
    thd->reset_for_next_command();
 | 
						|
 | 
						|
    /**
 | 
						|
     * Set some database to thd so that parsing won't fail because of
 | 
						|
     * missing database. Then parse.
 | 
						|
     */
 | 
						|
    failp = thd->set_db(virtual_db, strlen(virtual_db));
 | 
						|
 | 
						|
    if (failp)
 | 
						|
    {
 | 
						|
        MXS_ERROR("Failed to set database in thread context.");
 | 
						|
    }
 | 
						|
 | 
						|
    failp = parse_sql(thd, &parser_state, NULL);
 | 
						|
 | 
						|
    if (failp)
 | 
						|
    {
 | 
						|
        MXS_DEBUG("%lu [readwritesplit:create_parse_tree] failed to "
 | 
						|
                  "create parse tree.",
 | 
						|
                  pthread_self());
 | 
						|
    }
 | 
						|
 | 
						|
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.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * @details Query type is deduced by checking for certain properties
 | 
						|
 * of them. The order is essential. Some SQL commands have multiple
 | 
						|
 * flags set and changing the order in which flags are tested,
 | 
						|
 * the resulting type may be different.
 | 
						|
 *
 | 
						|
 */
 | 
						|
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;
 | 
						|
    int set_autocommit_stmt = -1; /*< -1 no, 0 disable, 1 enable */
 | 
						|
    LEX* lex;
 | 
						|
    Item* item;
 | 
						|
    /**
 | 
						|
     * By default, if sql_log_bin, that is, recording data modifications
 | 
						|
     * to binary log, is disabled, gateway treats operations normally.
 | 
						|
     * Effectively nothing is replicated.
 | 
						|
     * When force_data_modify_op_replication is TRUE, gateway distributes
 | 
						|
     * all write operations to all nodes.
 | 
						|
     */
 | 
						|
#if defined(NOT_IN_USE)
 | 
						|
    bool force_data_modify_op_replication;
 | 
						|
    force_data_modify_op_replication = FALSE;
 | 
						|
#endif /* NOT_IN_USE */
 | 
						|
    ss_info_dassert(thd != NULL, ("thd is NULL\n"));
 | 
						|
 | 
						|
    lex = thd->lex;
 | 
						|
 | 
						|
    /** SELECT ..INTO variable|OUTFILE|DUMPFILE */
 | 
						|
    if (lex->result != NULL)
 | 
						|
    {
 | 
						|
        type = QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
        goto return_qtype;
 | 
						|
    }
 | 
						|
 | 
						|
    if (skygw_stmt_causes_implicit_commit(lex, &set_autocommit_stmt))
 | 
						|
    {
 | 
						|
        if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            if (sql_command_flags[lex->sql_command] &
 | 
						|
                CF_IMPLICT_COMMIT_BEGIN)
 | 
						|
            {
 | 
						|
                MXS_INFO("Implicit COMMIT before executing the next command.");
 | 
						|
            }
 | 
						|
            else if (sql_command_flags[lex->sql_command] &
 | 
						|
                     CF_IMPLICIT_COMMIT_END)
 | 
						|
            {
 | 
						|
                MXS_INFO("Implicit COMMIT after executing the next command.");
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (set_autocommit_stmt == 1)
 | 
						|
        {
 | 
						|
            type |= QUERY_TYPE_ENABLE_AUTOCOMMIT;
 | 
						|
        }
 | 
						|
 | 
						|
        type |= QUERY_TYPE_COMMIT;
 | 
						|
    }
 | 
						|
 | 
						|
    if (set_autocommit_stmt == 0)
 | 
						|
    {
 | 
						|
        if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            MXS_INFO("Disable autocommit : implicit START TRANSACTION"
 | 
						|
                     " before executing the next command.");
 | 
						|
        }
 | 
						|
 | 
						|
        type |= QUERY_TYPE_DISABLE_AUTOCOMMIT;
 | 
						|
        type |= QUERY_TYPE_BEGIN_TRX;
 | 
						|
    }
 | 
						|
 | 
						|
    if (lex->option_type == OPT_GLOBAL)
 | 
						|
    {
 | 
						|
        /**
 | 
						|
         * SHOW syntax http://dev.mysql.com/doc/refman/5.6/en/show.html
 | 
						|
         */
 | 
						|
        if (lex->sql_command == SQLCOM_SHOW_VARIABLES)
 | 
						|
        {
 | 
						|
            type |= QUERY_TYPE_GSYSVAR_READ;
 | 
						|
        }
 | 
						|
            /**
 | 
						|
             * SET syntax http://dev.mysql.com/doc/refman/5.6/en/set-statement.html
 | 
						|
             */
 | 
						|
        else if (lex->sql_command == SQLCOM_SET_OPTION)
 | 
						|
        {
 | 
						|
            type |= QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
        }
 | 
						|
 | 
						|
            /*
 | 
						|
             * SHOW GLOBAL STATUS - Route to master
 | 
						|
             */
 | 
						|
        else if (lex->sql_command == SQLCOM_SHOW_STATUS)
 | 
						|
        {
 | 
						|
            type = QUERY_TYPE_WRITE;
 | 
						|
        }
 | 
						|
            /**
 | 
						|
             * REVOKE ALL, ASSIGN_TO_KEYCACHE,
 | 
						|
             * PRELOAD_KEYS, FLUSH, RESET, CREATE|ALTER|DROP SERVER
 | 
						|
             */
 | 
						|
 | 
						|
        else
 | 
						|
        {
 | 
						|
            type |= QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
        }
 | 
						|
 | 
						|
        goto return_qtype;
 | 
						|
    }
 | 
						|
    else if (lex->option_type == OPT_SESSION)
 | 
						|
    {
 | 
						|
        /**
 | 
						|
         * SHOW syntax http://dev.mysql.com/doc/refman/5.6/en/show.html
 | 
						|
         */
 | 
						|
        if (lex->sql_command == SQLCOM_SHOW_VARIABLES)
 | 
						|
        {
 | 
						|
            type |= QUERY_TYPE_SYSVAR_READ;
 | 
						|
        }
 | 
						|
            /**
 | 
						|
             * SET syntax http://dev.mysql.com/doc/refman/5.6/en/set-statement.html
 | 
						|
             */
 | 
						|
        else if (lex->sql_command == SQLCOM_SET_OPTION)
 | 
						|
        {
 | 
						|
            /** Either user- or system variable write */
 | 
						|
            if (is_set_specific(pi->pi_query_plain_str))
 | 
						|
            {
 | 
						|
                type |= QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                List_iterator<set_var_base> 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;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 1:ALTER TABLE, TRUNCATE, REPAIR, OPTIMIZE, ANALYZE, CHECK.
 | 
						|
     * 2:CREATE|ALTER|DROP|TRUNCATE|RENAME TABLE, LOAD, CREATE|DROP|ALTER DB,
 | 
						|
     *   CREATE|DROP INDEX, CREATE|DROP VIEW, CREATE|DROP TRIGGER,
 | 
						|
     *   CREATE|ALTER|DROP EVENT, UPDATE, INSERT, INSERT(SELECT),
 | 
						|
     *   DELETE, REPLACE, REPLACE(SELECT), CREATE|RENAME|DROP USER,
 | 
						|
     *   GRANT, REVOKE, OPTIMIZE, CREATE|ALTER|DROP FUNCTION|PROCEDURE,
 | 
						|
     *   CREATE SPFUNCTION, INSTALL|UNINSTALL PLUGIN
 | 
						|
     */
 | 
						|
    if (is_log_table_write_query(lex->sql_command) ||
 | 
						|
        is_update_query(lex->sql_command))
 | 
						|
    {
 | 
						|
#if defined(NOT_IN_USE)
 | 
						|
 | 
						|
        if (thd->variables.sql_log_bin == 0 &&
 | 
						|
            force_data_modify_op_replication)
 | 
						|
        {
 | 
						|
            /** Not replicated */
 | 
						|
            type |= QUERY_TYPE_SESSION_WRITE;
 | 
						|
        }
 | 
						|
        else
 | 
						|
#endif /* NOT_IN_USE */
 | 
						|
        {
 | 
						|
            /** Written to binlog, that is, replicated except tmp tables */
 | 
						|
            type |= QUERY_TYPE_WRITE; /*< to master */
 | 
						|
 | 
						|
            if (lex->sql_command == SQLCOM_CREATE_TABLE &&
 | 
						|
                (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
 | 
						|
            {
 | 
						|
                type |= QUERY_TYPE_CREATE_TMP_TABLE; /*< remember in router */
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /** Try to catch session modifications here */
 | 
						|
    switch (lex->sql_command)
 | 
						|
    {
 | 
						|
    case SQLCOM_EMPTY_QUERY:
 | 
						|
        type |= QUERY_TYPE_READ;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_CHANGE_DB:
 | 
						|
        type |= QUERY_TYPE_SESSION_WRITE;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_DEALLOCATE_PREPARE:
 | 
						|
        type |= QUERY_TYPE_WRITE;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SELECT:
 | 
						|
    case SQLCOM_SHOW_SLAVE_STAT:
 | 
						|
        type |= QUERY_TYPE_READ;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_CALL:
 | 
						|
        type |= QUERY_TYPE_WRITE;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_BEGIN:
 | 
						|
        type |= QUERY_TYPE_BEGIN_TRX;
 | 
						|
        if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_READ_WRITE)
 | 
						|
        {
 | 
						|
            type |= QUERY_TYPE_WRITE;
 | 
						|
        }
 | 
						|
        else if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_READ_ONLY)
 | 
						|
        {
 | 
						|
            type |= QUERY_TYPE_READ;
 | 
						|
        }
 | 
						|
        goto return_qtype;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_COMMIT:
 | 
						|
        type |= QUERY_TYPE_COMMIT;
 | 
						|
        goto return_qtype;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_ROLLBACK:
 | 
						|
        type |= QUERY_TYPE_ROLLBACK;
 | 
						|
        goto return_qtype;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_PREPARE:
 | 
						|
        type |= QUERY_TYPE_PREPARE_NAMED_STMT;
 | 
						|
        goto return_qtype;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SHOW_DATABASES:
 | 
						|
        type |= QUERY_TYPE_SHOW_DATABASES;
 | 
						|
        goto return_qtype;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SHOW_FIELDS:
 | 
						|
        type |= QUERY_TYPE_READ;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SHOW_TABLES:
 | 
						|
        type |= QUERY_TYPE_SHOW_TABLES;
 | 
						|
        goto return_qtype;
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        type |= QUERY_TYPE_WRITE;
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
#if defined(UPDATE_VAR_SUPPORT)
 | 
						|
 | 
						|
    if (QTYPE_LESS_RESTRICTIVE_THAN_WRITE(type))
 | 
						|
#endif
 | 
						|
        // TODO: This test is meaningless, since at this point
 | 
						|
        // TODO: qtype (not type) is QUERY_TYPE_UNKNOWN.
 | 
						|
        if (qc_query_is_type(qtype, QUERY_TYPE_UNKNOWN) ||
 | 
						|
            qc_query_is_type(qtype, QUERY_TYPE_LOCAL_READ) ||
 | 
						|
            qc_query_is_type(qtype, QUERY_TYPE_READ) ||
 | 
						|
            qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) ||
 | 
						|
            qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) ||
 | 
						|
            qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ))
 | 
						|
        {
 | 
						|
            /**
 | 
						|
             * These values won't change qtype more restrictive than write.
 | 
						|
             * UDFs and procedures could possibly cause session-wide write,
 | 
						|
             * but unless their content is replicated this is a limitation
 | 
						|
             * of this implementation.
 | 
						|
             * In other words : UDFs and procedures are not allowed to
 | 
						|
             * perform writes which are not replicated but need to repeat
 | 
						|
             * in every node.
 | 
						|
             * It is not sure if such statements exist. vraa 25.10.13
 | 
						|
             */
 | 
						|
 | 
						|
            /**
 | 
						|
             * Search for system functions, UDFs and stored procedures.
 | 
						|
             */
 | 
						|
            for (item = thd->free_list; item != NULL; item = item->next)
 | 
						|
            {
 | 
						|
                Item::Type itype;
 | 
						|
 | 
						|
                itype = item->type();
 | 
						|
                MXS_DEBUG("%lu [resolve_query_type] Item %s:%s",
 | 
						|
                          pthread_self(), item->name, STRITEMTYPE(itype));
 | 
						|
 | 
						|
                if (itype == Item::SUBSELECT_ITEM)
 | 
						|
                {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
                else if (itype == Item::FUNC_ITEM)
 | 
						|
                {
 | 
						|
                    int func_qtype = QUERY_TYPE_UNKNOWN;
 | 
						|
                    /**
 | 
						|
                     * Item types:
 | 
						|
                     * FIELD_ITEM = 0, FUNC_ITEM,
 | 
						|
                     * SUM_FUNC_ITEM,  STRING_ITEM,    INT_ITEM,
 | 
						|
                     * REAL_ITEM,      NULL_ITEM,      VARBIN_ITEM,
 | 
						|
                     * COPY_STR_ITEM,  FIELD_AVG_ITEM,
 | 
						|
                     * DEFAULT_VALUE_ITEM,             PROC_ITEM,
 | 
						|
                     * COND_ITEM,      REF_ITEM,       FIELD_STD_ITEM,
 | 
						|
                     * FIELD_VARIANCE_ITEM,
 | 
						|
                     * INSERT_VALUE_ITEM,
 | 
						|
                     * SUBSELECT_ITEM, ROW_ITEM,       CACHE_ITEM,
 | 
						|
                     * TYPE_HOLDER,    PARAM_ITEM,
 | 
						|
                     * TRIGGER_FIELD_ITEM,             DECIMAL_ITEM,
 | 
						|
                     * XPATH_NODESET,  XPATH_NODESET_CMP,
 | 
						|
                     * VIEW_FIXER_ITEM,
 | 
						|
                     * EXPR_CACHE_ITEM == 27
 | 
						|
                     **/
 | 
						|
 | 
						|
                    Item_func::Functype ftype;
 | 
						|
                    ftype = ((Item_func*) item)->functype();
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * Item_func types:
 | 
						|
                     *
 | 
						|
                     * UNKNOWN_FUNC = 0,EQ_FUNC,      EQUAL_FUNC,
 | 
						|
                     * NE_FUNC,         LT_FUNC,      LE_FUNC,
 | 
						|
                     * GE_FUNC,         GT_FUNC,      FT_FUNC,
 | 
						|
                     * LIKE_FUNC == 10, ISNULL_FUNC,  ISNOTNULL_FUNC,
 | 
						|
                     * COND_AND_FUNC,   COND_OR_FUNC, XOR_FUNC,
 | 
						|
                     * BETWEEN,         IN_FUNC,
 | 
						|
                     * MULT_EQUAL_FUNC, INTERVAL_FUNC,
 | 
						|
                     * ISNOTNULLTEST_FUNC == 20,
 | 
						|
                     * SP_EQUALS_FUNC,  SP_DISJOINT_FUNC,
 | 
						|
                     * SP_INTERSECTS_FUNC,
 | 
						|
                     * SP_TOUCHES_FUNC, SP_CROSSES_FUNC,
 | 
						|
                     * SP_WITHIN_FUNC,  SP_CONTAINS_FUNC,
 | 
						|
                     * SP_OVERLAPS_FUNC,
 | 
						|
                     * SP_STARTPOINT,   SP_ENDPOINT == 30,
 | 
						|
                     * SP_EXTERIORRING, SP_POINTN,    SP_GEOMETRYN,
 | 
						|
                     * SP_INTERIORRINGN,NOT_FUNC,     NOT_ALL_FUNC,
 | 
						|
                     * NOW_FUNC,        TRIG_COND_FUNC,
 | 
						|
                     * SUSERVAR_FUNC,   GUSERVAR_FUNC == 40,
 | 
						|
                     * COLLATE_FUNC,    EXTRACT_FUNC,
 | 
						|
                     * CHAR_TYPECAST_FUNC,
 | 
						|
                     * FUNC_SP,         UDF_FUNC,     NEG_FUNC,
 | 
						|
                     * GSYSVAR_FUNC == 47
 | 
						|
                     **/
 | 
						|
                    switch (ftype)
 | 
						|
                    {
 | 
						|
                    case Item_func::FUNC_SP:
 | 
						|
                        /**
 | 
						|
                         * An unknown (for maxscale) function / sp
 | 
						|
                         * belongs to this category.
 | 
						|
                         */
 | 
						|
                        func_qtype |= QUERY_TYPE_WRITE;
 | 
						|
                        MXS_DEBUG("%lu [resolve_query_type] "
 | 
						|
                                  "functype FUNC_SP, stored proc "
 | 
						|
                                  "or unknown function.",
 | 
						|
                                  pthread_self());
 | 
						|
                        break;
 | 
						|
 | 
						|
                    case Item_func::UDF_FUNC:
 | 
						|
                        func_qtype |= QUERY_TYPE_WRITE;
 | 
						|
                        MXS_DEBUG("%lu [resolve_query_type] "
 | 
						|
                                  "functype UDF_FUNC, user-defined "
 | 
						|
                                  "function.",
 | 
						|
                                  pthread_self());
 | 
						|
                        break;
 | 
						|
 | 
						|
                    case Item_func::NOW_FUNC:
 | 
						|
                        // If this is part of a CREATE TABLE, then local read is not
 | 
						|
                        // applicable.
 | 
						|
                        if (lex->sql_command != SQLCOM_CREATE_TABLE)
 | 
						|
                        {
 | 
						|
                            func_qtype |= QUERY_TYPE_LOCAL_READ;
 | 
						|
                            MXS_DEBUG("%lu [resolve_query_type] "
 | 
						|
                                      "functype NOW_FUNC, could be "
 | 
						|
                                      "executed in MaxScale.",
 | 
						|
                                      pthread_self());
 | 
						|
                        }
 | 
						|
                        break;
 | 
						|
 | 
						|
                        /** System session variable */
 | 
						|
                    case Item_func::GSYSVAR_FUNC:
 | 
						|
                        func_qtype |= QUERY_TYPE_SYSVAR_READ;
 | 
						|
                        MXS_DEBUG("%lu [resolve_query_type] "
 | 
						|
                                  "functype GSYSVAR_FUNC, system "
 | 
						|
                                  "variable read.",
 | 
						|
                                  pthread_self());
 | 
						|
                        break;
 | 
						|
 | 
						|
                        /** User-defined variable read */
 | 
						|
                    case Item_func::GUSERVAR_FUNC:
 | 
						|
                        func_qtype |= QUERY_TYPE_USERVAR_READ;
 | 
						|
                        MXS_DEBUG("%lu [resolve_query_type] "
 | 
						|
                                  "functype GUSERVAR_FUNC, user "
 | 
						|
                                  "variable read.",
 | 
						|
                                  pthread_self());
 | 
						|
                        break;
 | 
						|
 | 
						|
                        /** User-defined variable modification */
 | 
						|
                    case Item_func::SUSERVAR_FUNC:
 | 
						|
                        func_qtype |= QUERY_TYPE_USERVAR_WRITE;
 | 
						|
                        MXS_DEBUG("%lu [resolve_query_type] "
 | 
						|
                                  "functype SUSERVAR_FUNC, user "
 | 
						|
                                  "variable write.",
 | 
						|
                                  pthread_self());
 | 
						|
                        break;
 | 
						|
 | 
						|
                    case Item_func::UNKNOWN_FUNC:
 | 
						|
 | 
						|
                        if (((Item_func*) item)->func_name() != NULL &&
 | 
						|
                            strcmp((char*) ((Item_func*) item)->func_name(), "last_insert_id") == 0)
 | 
						|
                        {
 | 
						|
                            func_qtype |= QUERY_TYPE_MASTER_READ;
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        {
 | 
						|
                            func_qtype |= QUERY_TYPE_READ;
 | 
						|
                        }
 | 
						|
 | 
						|
                        /**
 | 
						|
                         * Many built-in functions are of this
 | 
						|
                         * type, for example, rand(), soundex(),
 | 
						|
                         * repeat() .
 | 
						|
                         */
 | 
						|
                        MXS_DEBUG("%lu [resolve_query_type] "
 | 
						|
                                  "functype UNKNOWN_FUNC, "
 | 
						|
                                  "typically some system function.",
 | 
						|
                                  pthread_self());
 | 
						|
                        break;
 | 
						|
 | 
						|
                    default:
 | 
						|
                        MXS_DEBUG("%lu [resolve_query_type] "
 | 
						|
                                  "Functype %d.",
 | 
						|
                                  pthread_self(),
 | 
						|
                                  ftype);
 | 
						|
                        break;
 | 
						|
                    } /**< switch */
 | 
						|
 | 
						|
                    /**< Set new query type */
 | 
						|
                    type |= func_qtype;
 | 
						|
                }
 | 
						|
 | 
						|
#if defined(UPDATE_VAR_SUPPORT)
 | 
						|
 | 
						|
                /**
 | 
						|
                 * Write is as restrictive as it gets due functions,
 | 
						|
                 * so break.
 | 
						|
                 */
 | 
						|
                if ((type & QUERY_TYPE_WRITE) == QUERY_TYPE_WRITE)
 | 
						|
                {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
 | 
						|
#endif
 | 
						|
            } /**< for */
 | 
						|
        } /**< if */
 | 
						|
 | 
						|
return_qtype:
 | 
						|
    qtype = (qc_query_type_t) type;
 | 
						|
    return qtype;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Checks if statement causes implicit COMMIT.
 | 
						|
 * autocommit_stmt gets values 1, 0 or -1 if stmt is enable, disable or
 | 
						|
 * something else than autocommit.
 | 
						|
 *
 | 
						|
 * @param lex           Parse tree
 | 
						|
 * @param autocommit_stmt   memory address for autocommit status
 | 
						|
 *
 | 
						|
 * @return true if statement causes implicit commit and false otherwise
 | 
						|
 */
 | 
						|
static bool skygw_stmt_causes_implicit_commit(LEX* lex, int* autocommit_stmt)
 | 
						|
{
 | 
						|
    bool succp;
 | 
						|
 | 
						|
    if (!(sql_command_flags[lex->sql_command] & CF_AUTO_COMMIT_TRANS))
 | 
						|
    {
 | 
						|
        succp = false;
 | 
						|
        goto return_succp;
 | 
						|
    }
 | 
						|
 | 
						|
    switch (lex->sql_command)
 | 
						|
    {
 | 
						|
    case SQLCOM_DROP_TABLE:
 | 
						|
        succp = !(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE);
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_ALTER_TABLE:
 | 
						|
    case SQLCOM_CREATE_TABLE:
 | 
						|
        /* If CREATE TABLE of non-temporary table, do implicit commit */
 | 
						|
        succp = !(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE);
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SET_OPTION:
 | 
						|
        if ((*autocommit_stmt = is_autocommit_stmt(lex)) == 1)
 | 
						|
        {
 | 
						|
            succp = true;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            succp = false;
 | 
						|
        }
 | 
						|
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        succp = true;
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
return_succp:
 | 
						|
    return succp;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Finds out if stmt is SET autocommit
 | 
						|
 * and if the new value matches with the enable_cmd argument.
 | 
						|
 *
 | 
						|
 * @param lex   parse tree
 | 
						|
 *
 | 
						|
 * @return 1, 0, or -1 if command was:
 | 
						|
 * enable, disable, or not autocommit, respectively.
 | 
						|
 */
 | 
						|
static int is_autocommit_stmt(LEX* lex)
 | 
						|
{
 | 
						|
    struct list_node* node;
 | 
						|
    set_var* setvar;
 | 
						|
    int rc = -1;
 | 
						|
    static char target[8]; /*< for converted string */
 | 
						|
    Item* item = NULL;
 | 
						|
 | 
						|
    node = lex->var_list.first_node();
 | 
						|
    setvar = (set_var*) node->info;
 | 
						|
 | 
						|
    if (setvar == NULL)
 | 
						|
    {
 | 
						|
        goto return_rc;
 | 
						|
    }
 | 
						|
 | 
						|
    do /*< Search for the last occurrence of 'autocommit' */
 | 
						|
    {
 | 
						|
        if ((sys_var*) setvar->var == Sys_autocommit_ptr)
 | 
						|
        {
 | 
						|
            item = setvar->value;
 | 
						|
        }
 | 
						|
 | 
						|
        node = node->next;
 | 
						|
    }
 | 
						|
    while ((setvar = (set_var*) node->info) != NULL);
 | 
						|
 | 
						|
    if (item != NULL) /*< found autocommit command */
 | 
						|
    {
 | 
						|
        if (item->type() == Item::INT_ITEM) /*< '0' or '1' */
 | 
						|
        {
 | 
						|
            rc = item->val_int();
 | 
						|
 | 
						|
            if (rc > 1 || rc < 0)
 | 
						|
            {
 | 
						|
                rc = -1;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if (item->type() == Item::STRING_ITEM) /*< 'on' or 'off' */
 | 
						|
        {
 | 
						|
            String str(target, sizeof (target), system_charset_info);
 | 
						|
            String* res = item->val_str(&str);
 | 
						|
 | 
						|
            if ((rc = find_type(&bool_typelib, res->ptr(), res->length(), false)))
 | 
						|
            {
 | 
						|
                ss_dassert(rc >= 0 && rc <= 2);
 | 
						|
                /**
 | 
						|
                 * rc is the position of matchin string in
 | 
						|
                 * typelib's value array.
 | 
						|
                 * 1=OFF, 2=ON.
 | 
						|
                 */
 | 
						|
                rc -= 1;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
return_rc:
 | 
						|
    return rc;
 | 
						|
}
 | 
						|
 | 
						|
#if defined(NOT_USED)
 | 
						|
 | 
						|
char* qc_get_stmtname(GWBUF* buf)
 | 
						|
{
 | 
						|
    MYSQL* mysql;
 | 
						|
 | 
						|
    if (buf == NULL ||
 | 
						|
        buf->gwbuf_bufobj == NULL ||
 | 
						|
        buf->gwbuf_bufobj->bo_data == NULL ||
 | 
						|
        (mysql = (MYSQL *) ((parsing_info_t *) buf->gwbuf_bufobj->bo_data)->pi_handle) == NULL ||
 | 
						|
        mysql->thd == NULL ||
 | 
						|
        (THD *) (mysql->thd))->lex == NULL ||
 | 
						|
        (THD *) (mysql->thd))->lex->prepared_stmt_name == NULL)
 | 
						|
        {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
 | 
						|
    return ((THD *) (mysql->thd))->lex->prepared_stmt_name.str;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/**
 | 
						|
 * Get the parsing info structure from a GWBUF
 | 
						|
 *
 | 
						|
 * @param querybuf A GWBUF
 | 
						|
 *
 | 
						|
 * @return The parsing info object, or NULL
 | 
						|
 */
 | 
						|
parsing_info_t* get_pinfo(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    parsing_info_t *pi = NULL;
 | 
						|
 | 
						|
    if ((querybuf != NULL) && GWBUF_IS_PARSED(querybuf))
 | 
						|
    {
 | 
						|
        pi = (parsing_info_t *) gwbuf_get_buffer_object_data(querybuf, GWBUF_PARSING_INFO);
 | 
						|
    }
 | 
						|
 | 
						|
    return pi;
 | 
						|
}
 | 
						|
 | 
						|
LEX* get_lex(parsing_info_t* pi)
 | 
						|
{
 | 
						|
    MYSQL* mysql = (MYSQL *) pi->pi_handle;
 | 
						|
    ss_dassert(mysql);
 | 
						|
    THD* thd = (THD *) mysql->thd;
 | 
						|
    ss_dassert(thd);
 | 
						|
 | 
						|
    return thd->lex;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Get the parse tree from parsed querybuf.
 | 
						|
 * @param querybuf  The parsed GWBUF
 | 
						|
 *
 | 
						|
 * @return Pointer to the LEX struct or NULL if an error occurred or the query
 | 
						|
 * was not parsed
 | 
						|
 */
 | 
						|
LEX* get_lex(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    LEX* lex = NULL;
 | 
						|
    parsing_info_t* pi = get_pinfo(querybuf);
 | 
						|
 | 
						|
    if (pi)
 | 
						|
    {
 | 
						|
        MYSQL* mysql = (MYSQL *) pi->pi_handle;
 | 
						|
        ss_dassert(mysql);
 | 
						|
        THD* thd = (THD *) mysql->thd;
 | 
						|
        ss_dassert(thd);
 | 
						|
        lex = thd->lex;
 | 
						|
    }
 | 
						|
 | 
						|
    return lex;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Finds the head of the list of tables affected by the current select statement.
 | 
						|
 * @param thd Pointer to a valid THD
 | 
						|
 * @return Pointer to the head of the TABLE_LIST chain or NULL in case of an error
 | 
						|
 */
 | 
						|
static TABLE_LIST* skygw_get_affected_tables(void* lexptr)
 | 
						|
{
 | 
						|
    LEX* lex = (LEX*) lexptr;
 | 
						|
 | 
						|
    if (lex == NULL || lex->current_select == NULL)
 | 
						|
    {
 | 
						|
        ss_dassert(lex != NULL && lex->current_select != NULL);
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    TABLE_LIST *tbl = lex->current_select->table_list.first;
 | 
						|
 | 
						|
    if (tbl && tbl->schema_select_lex && tbl->schema_select_lex->table_list.elements &&
 | 
						|
        lex->sql_command != SQLCOM_SHOW_KEYS)
 | 
						|
    {
 | 
						|
        /**
 | 
						|
         * Some statements e.g. EXPLAIN or SHOW COLUMNS give `information_schema`
 | 
						|
         * as the underlying table and the table in the query is stored in
 | 
						|
         * @c schema_select_lex.
 | 
						|
         *
 | 
						|
         * SHOW [KEYS | INDEX] does the reverse so we need to skip the
 | 
						|
         * @c schema_select_lex when processing a SHOW [KEYS | INDEX] statement.
 | 
						|
         */
 | 
						|
        tbl = tbl->schema_select_lex->table_list.first;
 | 
						|
    }
 | 
						|
 | 
						|
    return tbl;
 | 
						|
}
 | 
						|
 | 
						|
char** qc_get_table_names(GWBUF* querybuf, int* tblsize, bool fullnames)
 | 
						|
{
 | 
						|
    LEX* lex;
 | 
						|
    TABLE_LIST* tbl;
 | 
						|
    int i = 0, currtblsz = 0;
 | 
						|
    char **tables = NULL, **tmp = NULL;
 | 
						|
 | 
						|
    if (querybuf == NULL || tblsize == NULL)
 | 
						|
    {
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!ensure_query_is_parsed(querybuf))
 | 
						|
    {
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    if ((lex = get_lex(querybuf)) == NULL)
 | 
						|
    {
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    lex->current_select = lex->all_selects_list;
 | 
						|
 | 
						|
    while (lex->current_select)
 | 
						|
    {
 | 
						|
        tbl = skygw_get_affected_tables(lex);
 | 
						|
 | 
						|
        while (tbl)
 | 
						|
        {
 | 
						|
            if (i >= currtblsz)
 | 
						|
            {
 | 
						|
                tmp = (char**) malloc(sizeof (char*)*(currtblsz * 2 + 1));
 | 
						|
 | 
						|
                if (tmp)
 | 
						|
                {
 | 
						|
                    if (currtblsz > 0)
 | 
						|
                    {
 | 
						|
                        for (int x = 0; x < currtblsz; x++)
 | 
						|
                        {
 | 
						|
                            tmp[x] = tables[x];
 | 
						|
                        }
 | 
						|
 | 
						|
                        free(tables);
 | 
						|
                    }
 | 
						|
 | 
						|
                    tables = tmp;
 | 
						|
                    currtblsz = currtblsz * 2 + 1;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            if (tmp != NULL)
 | 
						|
            {
 | 
						|
                char *catnm = NULL;
 | 
						|
 | 
						|
                if (fullnames)
 | 
						|
                {
 | 
						|
                    if (tbl->db &&
 | 
						|
                        (strcmp(tbl->db, "skygw_virtual") != 0) &&
 | 
						|
                        (strcmp(tbl->table_name, "*") != 0))
 | 
						|
                    {
 | 
						|
                        catnm = (char*) calloc(strlen(tbl->db) +
 | 
						|
                                               strlen(tbl->table_name) +
 | 
						|
                                               2,
 | 
						|
                                               sizeof (char));
 | 
						|
                        strcpy(catnm, tbl->db);
 | 
						|
                        strcat(catnm, ".");
 | 
						|
                        strcat(catnm, tbl->table_name);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (catnm)
 | 
						|
                {
 | 
						|
                    tables[i++] = catnm;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    // Sometimes the tablename is "*"; we exclude that.
 | 
						|
                    if (strcmp(tbl->table_name, "*") != 0)
 | 
						|
                    {
 | 
						|
                        tables[i++] = strdup(tbl->table_name);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                tbl = tbl->next_local;
 | 
						|
            }
 | 
						|
        } /*< while (tbl) */
 | 
						|
 | 
						|
        lex->current_select = lex->current_select->next_select_in_list();
 | 
						|
    } /*< while(lex->current_select) */
 | 
						|
 | 
						|
retblock:
 | 
						|
 | 
						|
    if (tblsize)
 | 
						|
    {
 | 
						|
        *tblsize = i;
 | 
						|
    }
 | 
						|
 | 
						|
    return tables;
 | 
						|
}
 | 
						|
 | 
						|
char* qc_get_created_table_name(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    if (querybuf == NULL)
 | 
						|
    {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!ensure_query_is_parsed(querybuf))
 | 
						|
    {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    char* table_name = NULL;
 | 
						|
 | 
						|
    LEX* lex = get_lex(querybuf);
 | 
						|
 | 
						|
    if (lex && (lex->sql_command == SQLCOM_CREATE_TABLE))
 | 
						|
    {
 | 
						|
        if (lex->create_last_non_select_table &&
 | 
						|
            lex->create_last_non_select_table->table_name)
 | 
						|
        {
 | 
						|
            table_name = strdup(lex->create_last_non_select_table->table_name);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return table_name;
 | 
						|
}
 | 
						|
 | 
						|
bool qc_is_real_query(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    bool succp;
 | 
						|
    LEX* lex;
 | 
						|
 | 
						|
    if (querybuf == NULL)
 | 
						|
    {
 | 
						|
        succp = false;
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!ensure_query_is_parsed(querybuf))
 | 
						|
    {
 | 
						|
        succp = false;
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    if ((lex = get_lex(querybuf)) == NULL)
 | 
						|
    {
 | 
						|
        succp = false;
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    switch (lex->sql_command)
 | 
						|
    {
 | 
						|
    case SQLCOM_SELECT:
 | 
						|
        succp = lex->all_selects_list->table_list.elements > 0;
 | 
						|
        goto retblock;
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_UPDATE_MULTI:
 | 
						|
    case SQLCOM_UPDATE:
 | 
						|
    case SQLCOM_INSERT:
 | 
						|
    case SQLCOM_INSERT_SELECT:
 | 
						|
    case SQLCOM_DELETE:
 | 
						|
    case SQLCOM_DELETE_MULTI:
 | 
						|
    case SQLCOM_TRUNCATE:
 | 
						|
    case SQLCOM_REPLACE:
 | 
						|
    case SQLCOM_REPLACE_SELECT:
 | 
						|
    case SQLCOM_PREPARE:
 | 
						|
    case SQLCOM_EXECUTE:
 | 
						|
        succp = true;
 | 
						|
        goto retblock;
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        succp = false;
 | 
						|
        goto retblock;
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
retblock:
 | 
						|
    return succp;
 | 
						|
}
 | 
						|
 | 
						|
bool qc_is_drop_table_query(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    bool answer = false;
 | 
						|
 | 
						|
    if (querybuf)
 | 
						|
    {
 | 
						|
        if (ensure_query_is_parsed(querybuf))
 | 
						|
        {
 | 
						|
            LEX* lex = get_lex(querybuf);
 | 
						|
 | 
						|
            answer = lex && lex->sql_command == SQLCOM_DROP_TABLE;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return answer;
 | 
						|
}
 | 
						|
 | 
						|
bool qc_query_has_clause(GWBUF* buf)
 | 
						|
{
 | 
						|
    bool clause = false;
 | 
						|
 | 
						|
    if (buf)
 | 
						|
    {
 | 
						|
        if (ensure_query_is_parsed(buf))
 | 
						|
        {
 | 
						|
            LEX* lex = get_lex(buf);
 | 
						|
 | 
						|
            if (lex)
 | 
						|
            {
 | 
						|
                SELECT_LEX* current = lex->all_selects_list;
 | 
						|
 | 
						|
                while (current && !clause)
 | 
						|
                {
 | 
						|
                    if (current->where || current->having)
 | 
						|
                    {
 | 
						|
                        clause = true;
 | 
						|
                    }
 | 
						|
 | 
						|
                    current = current->next_select_in_list();
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return clause;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create parsing information; initialize mysql handle, allocate parsing info
 | 
						|
 * struct and set handle and free function pointer to it.
 | 
						|
 *
 | 
						|
 * @param donefun       pointer to free function
 | 
						|
 *
 | 
						|
 * @return pointer to parsing information
 | 
						|
 */
 | 
						|
static parsing_info_t* parsing_info_init(void (*donefun)(void *))
 | 
						|
{
 | 
						|
    parsing_info_t* pi = NULL;
 | 
						|
    MYSQL* mysql;
 | 
						|
    const char* user = "skygw";
 | 
						|
    const char* db = "skygw";
 | 
						|
 | 
						|
    ss_dassert(donefun != NULL);
 | 
						|
 | 
						|
    /** Get server handle */
 | 
						|
    mysql = mysql_init(NULL);
 | 
						|
 | 
						|
    if (mysql == NULL)
 | 
						|
    {
 | 
						|
        MXS_ERROR("Call to mysql_real_connect failed due %d, %s.",
 | 
						|
                  mysql_errno(mysql),
 | 
						|
                  mysql_error(mysql));
 | 
						|
        ss_dassert(mysql != NULL);
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    /** Set methods and authentication to mysql */
 | 
						|
    mysql_options(mysql, MYSQL_READ_DEFAULT_GROUP, "libmysqld_skygw");
 | 
						|
    mysql_options(mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL);
 | 
						|
    mysql->methods = &embedded_methods;
 | 
						|
    mysql->user = my_strdup(user, MYF(0));
 | 
						|
    mysql->db = my_strdup(db, MYF(0));
 | 
						|
    mysql->passwd = NULL;
 | 
						|
 | 
						|
    pi = (parsing_info_t*) calloc(1, sizeof (parsing_info_t));
 | 
						|
 | 
						|
    if (pi == NULL)
 | 
						|
    {
 | 
						|
        mysql_close(mysql);
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
#if defined(SS_DEBUG)
 | 
						|
    pi->pi_chk_top = CHK_NUM_PINFO;
 | 
						|
    pi->pi_chk_tail = CHK_NUM_PINFO;
 | 
						|
#endif
 | 
						|
    /** Set handle and free function to parsing info struct */
 | 
						|
    pi->pi_handle = mysql;
 | 
						|
    pi->pi_done_fp = donefun;
 | 
						|
 | 
						|
retblock:
 | 
						|
    return pi;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Free function for parsing info. Called by gwbuf_free or in case initialization
 | 
						|
 * of parsing information fails.
 | 
						|
 *
 | 
						|
 * @param ptr Pointer to parsing information, cast required
 | 
						|
 *
 | 
						|
 * @return void
 | 
						|
 *
 | 
						|
 */
 | 
						|
static void parsing_info_done(void* ptr)
 | 
						|
{
 | 
						|
    parsing_info_t* pi;
 | 
						|
    THD* thd;
 | 
						|
 | 
						|
    if (ptr)
 | 
						|
    {
 | 
						|
        pi = (parsing_info_t *) ptr;
 | 
						|
 | 
						|
        if (pi->pi_handle != NULL)
 | 
						|
        {
 | 
						|
            MYSQL* mysql = (MYSQL *) pi->pi_handle;
 | 
						|
 | 
						|
            if (mysql->thd != NULL)
 | 
						|
            {
 | 
						|
                thd = (THD*) mysql->thd;
 | 
						|
                thd->end_statement();
 | 
						|
                (*mysql->methods->free_embedded_thd)(mysql);
 | 
						|
                mysql->thd = NULL;
 | 
						|
            }
 | 
						|
 | 
						|
            mysql_close(mysql);
 | 
						|
        }
 | 
						|
 | 
						|
        /** Free plain text query string */
 | 
						|
        if (pi->pi_query_plain_str != NULL)
 | 
						|
        {
 | 
						|
            free(pi->pi_query_plain_str);
 | 
						|
        }
 | 
						|
 | 
						|
        for (size_t i = 0; i < pi->field_infos_len; ++i)
 | 
						|
        {
 | 
						|
            free(pi->field_infos[i].database);
 | 
						|
            free(pi->field_infos[i].table);
 | 
						|
            free(pi->field_infos[i].column);
 | 
						|
        }
 | 
						|
        free(pi->field_infos);
 | 
						|
 | 
						|
        free(pi);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Add plain text query string to parsing info.
 | 
						|
 *
 | 
						|
 * @param ptr   Pointer to parsing info struct, cast required
 | 
						|
 * @param str   String to be added
 | 
						|
 *
 | 
						|
 * @return void
 | 
						|
 */
 | 
						|
static void parsing_info_set_plain_str(void* ptr, char* str)
 | 
						|
{
 | 
						|
    parsing_info_t* pi = (parsing_info_t *) ptr;
 | 
						|
    CHK_PARSING_INFO(pi);
 | 
						|
 | 
						|
    pi->pi_query_plain_str = str;
 | 
						|
}
 | 
						|
 | 
						|
char** qc_get_database_names(GWBUF* querybuf, int* size)
 | 
						|
{
 | 
						|
    LEX* lex;
 | 
						|
    TABLE_LIST* tbl;
 | 
						|
    char **databases = NULL, **tmp = NULL;
 | 
						|
    int currsz = 0, i = 0;
 | 
						|
 | 
						|
    if (!querybuf)
 | 
						|
    {
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!ensure_query_is_parsed(querybuf))
 | 
						|
    {
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    if ((lex = get_lex(querybuf)) == NULL)
 | 
						|
    {
 | 
						|
        goto retblock;
 | 
						|
    }
 | 
						|
 | 
						|
    lex->current_select = lex->all_selects_list;
 | 
						|
 | 
						|
    while (lex->current_select)
 | 
						|
    {
 | 
						|
        tbl = lex->current_select->table_list.first;
 | 
						|
 | 
						|
        while (tbl)
 | 
						|
        {
 | 
						|
            if (lex->sql_command == SQLCOM_SHOW_FIELDS)
 | 
						|
            {
 | 
						|
                // If we are describing, we want the actual table, not the information_schema.
 | 
						|
                if (tbl->schema_select_lex)
 | 
						|
                {
 | 
						|
                    tbl = tbl->schema_select_lex->table_list.first;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // The database is sometimes an empty string. So as not to return
 | 
						|
            // an array of empty strings, we need to check for that possibility.
 | 
						|
            if ((strcmp(tbl->db, "skygw_virtual") != 0) && (*tbl->db != 0))
 | 
						|
            {
 | 
						|
                if (i >= currsz)
 | 
						|
                {
 | 
						|
                    tmp = (char**) realloc(databases,
 | 
						|
                                           sizeof (char*)*(currsz * 2 + 1));
 | 
						|
 | 
						|
                    if (tmp == NULL)
 | 
						|
                    {
 | 
						|
                        goto retblock;
 | 
						|
                    }
 | 
						|
 | 
						|
                    databases = tmp;
 | 
						|
                    currsz = currsz * 2 + 1;
 | 
						|
                }
 | 
						|
 | 
						|
                databases[i++] = strdup(tbl->db);
 | 
						|
            }
 | 
						|
 | 
						|
            tbl = tbl->next_local;
 | 
						|
        }
 | 
						|
 | 
						|
        lex->current_select = lex->current_select->next_select_in_list();
 | 
						|
    }
 | 
						|
 | 
						|
retblock:
 | 
						|
    *size = i;
 | 
						|
    return databases;
 | 
						|
}
 | 
						|
 | 
						|
qc_query_op_t qc_get_operation(GWBUF* querybuf)
 | 
						|
{
 | 
						|
    qc_query_op_t operation = QUERY_OP_UNDEFINED;
 | 
						|
 | 
						|
    if (querybuf)
 | 
						|
    {
 | 
						|
        if (ensure_query_is_parsed(querybuf))
 | 
						|
        {
 | 
						|
            LEX* lex = get_lex(querybuf);
 | 
						|
 | 
						|
            if (lex)
 | 
						|
            {
 | 
						|
                switch (lex->sql_command)
 | 
						|
                {
 | 
						|
                case SQLCOM_SELECT:
 | 
						|
                    operation = QUERY_OP_SELECT;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_CREATE_DB:
 | 
						|
                case SQLCOM_CREATE_EVENT:
 | 
						|
                case SQLCOM_CREATE_FUNCTION:
 | 
						|
                case SQLCOM_CREATE_INDEX:
 | 
						|
                case SQLCOM_CREATE_PROCEDURE:
 | 
						|
                case SQLCOM_CREATE_SERVER:
 | 
						|
                case SQLCOM_CREATE_SPFUNCTION:
 | 
						|
                case SQLCOM_CREATE_TABLE:
 | 
						|
                case SQLCOM_CREATE_TRIGGER:
 | 
						|
                case SQLCOM_CREATE_USER:
 | 
						|
                case SQLCOM_CREATE_VIEW:
 | 
						|
                    operation = QUERY_OP_CREATE;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_ALTER_DB:
 | 
						|
                case SQLCOM_ALTER_DB_UPGRADE:
 | 
						|
                case SQLCOM_ALTER_EVENT:
 | 
						|
                case SQLCOM_ALTER_FUNCTION:
 | 
						|
                case SQLCOM_ALTER_PROCEDURE:
 | 
						|
                case SQLCOM_ALTER_SERVER:
 | 
						|
                case SQLCOM_ALTER_TABLE:
 | 
						|
                case SQLCOM_ALTER_TABLESPACE:
 | 
						|
                    operation = QUERY_OP_ALTER;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_UPDATE:
 | 
						|
                case SQLCOM_UPDATE_MULTI:
 | 
						|
                    operation = QUERY_OP_UPDATE;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_INSERT:
 | 
						|
                case SQLCOM_INSERT_SELECT:
 | 
						|
                case SQLCOM_REPLACE:
 | 
						|
                case SQLCOM_REPLACE_SELECT:
 | 
						|
                    operation = QUERY_OP_INSERT;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_DELETE:
 | 
						|
                case SQLCOM_DELETE_MULTI:
 | 
						|
                    operation = QUERY_OP_DELETE;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_TRUNCATE:
 | 
						|
                    operation = QUERY_OP_TRUNCATE;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_DROP_DB:
 | 
						|
                case SQLCOM_DROP_EVENT:
 | 
						|
                case SQLCOM_DROP_FUNCTION:
 | 
						|
                case SQLCOM_DROP_INDEX:
 | 
						|
                case SQLCOM_DROP_PROCEDURE:
 | 
						|
                case SQLCOM_DROP_SERVER:
 | 
						|
                case SQLCOM_DROP_TABLE:
 | 
						|
                case SQLCOM_DROP_TRIGGER:
 | 
						|
                case SQLCOM_DROP_USER:
 | 
						|
                case SQLCOM_DROP_VIEW:
 | 
						|
                    operation = QUERY_OP_DROP;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_CHANGE_DB:
 | 
						|
                    operation = QUERY_OP_CHANGE_DB;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_LOAD:
 | 
						|
                    operation = QUERY_OP_LOAD;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case SQLCOM_GRANT:
 | 
						|
                    operation = QUERY_OP_GRANT;
 | 
						|
                        break;
 | 
						|
 | 
						|
                case SQLCOM_REVOKE:
 | 
						|
                case SQLCOM_REVOKE_ALL:
 | 
						|
                    operation = QUERY_OP_REVOKE;
 | 
						|
                        break;
 | 
						|
 | 
						|
                default:
 | 
						|
                    operation = QUERY_OP_UNDEFINED;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return operation;
 | 
						|
}
 | 
						|
 | 
						|
char* qc_get_prepare_name(GWBUF* stmt)
 | 
						|
{
 | 
						|
    char* name = NULL;
 | 
						|
 | 
						|
    if (stmt)
 | 
						|
    {
 | 
						|
        if (ensure_query_is_parsed(stmt))
 | 
						|
        {
 | 
						|
            LEX* lex = get_lex(stmt);
 | 
						|
 | 
						|
            if ((lex->sql_command == SQLCOM_PREPARE) ||
 | 
						|
                (lex->sql_command == SQLCOM_EXECUTE) ||
 | 
						|
                (lex->sql_command == SQLCOM_DEALLOCATE_PREPARE))
 | 
						|
            {
 | 
						|
                name = (char*)malloc(lex->prepared_stmt_name.length + 1);
 | 
						|
                if (name)
 | 
						|
                {
 | 
						|
                    memcpy(name, lex->prepared_stmt_name.str, lex->prepared_stmt_name.length);
 | 
						|
                    name[lex->prepared_stmt_name.length] = 0;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return name;
 | 
						|
}
 | 
						|
 | 
						|
qc_query_op_t qc_get_prepare_operation(GWBUF* stmt)
 | 
						|
{
 | 
						|
    qc_query_op_t operation = QUERY_OP_UNDEFINED;
 | 
						|
 | 
						|
    if (stmt)
 | 
						|
    {
 | 
						|
        if (ensure_query_is_parsed(stmt))
 | 
						|
        {
 | 
						|
            LEX* lex = get_lex(stmt);
 | 
						|
 | 
						|
            if (lex->sql_command == SQLCOM_PREPARE)
 | 
						|
            {
 | 
						|
                // This is terriby inefficient, but as qc_mysqlembedded is not used
 | 
						|
                // for anything else but comparisons it is ok.
 | 
						|
                const char* prepare_str = lex->prepared_stmt_code.str;
 | 
						|
                size_t prepare_str_len = lex->prepared_stmt_code.length;
 | 
						|
 | 
						|
                // MySQL does not parse e.g. "select * from x where ?=5". To work
 | 
						|
                // around that we'll replace all "?":s with "@a":s. We might replace
 | 
						|
                // something unnecessarily, but that won't hurt the classification.
 | 
						|
                size_t n_questions = 0;
 | 
						|
                const char* p = prepare_str;
 | 
						|
                while (p < prepare_str + prepare_str_len)
 | 
						|
                {
 | 
						|
                    if (*p == '?')
 | 
						|
                    {
 | 
						|
                        ++n_questions;
 | 
						|
                    }
 | 
						|
 | 
						|
                    ++p;
 | 
						|
                }
 | 
						|
 | 
						|
                size_t payload_len = prepare_str_len + n_questions * 2 + 1;
 | 
						|
                size_t prepare_stmt_len = MYSQL_HEADER_LEN + payload_len;
 | 
						|
 | 
						|
                GWBUF* prepare_stmt = gwbuf_alloc(prepare_stmt_len);
 | 
						|
 | 
						|
                if (prepare_stmt)
 | 
						|
                {
 | 
						|
                    // Encode the length of the payload in the 3 first bytes.
 | 
						|
                    *((unsigned char*)GWBUF_DATA(prepare_stmt) + 0) = payload_len;
 | 
						|
                    *((unsigned char*)GWBUF_DATA(prepare_stmt) + 1) = (payload_len >> 8);
 | 
						|
                    *((unsigned char*)GWBUF_DATA(prepare_stmt) + 2) = (payload_len >> 16);
 | 
						|
                    // Sequence id
 | 
						|
                    *((unsigned char*)GWBUF_DATA(prepare_stmt) + 3) = 0x00;
 | 
						|
                    // Payload, starts with command.
 | 
						|
                    *((unsigned char*)GWBUF_DATA(prepare_stmt) + 4) = COM_QUERY;
 | 
						|
                    // Is followed by the statement.
 | 
						|
                    char *s = (char*)GWBUF_DATA(prepare_stmt) + 5;
 | 
						|
                    p = prepare_str;
 | 
						|
 | 
						|
                    while (p < prepare_str + prepare_str_len)
 | 
						|
                    {
 | 
						|
                        switch (*p)
 | 
						|
                        {
 | 
						|
                        case '?':
 | 
						|
                            *s++ = '@';
 | 
						|
                            *s = 'a';
 | 
						|
                            break;
 | 
						|
 | 
						|
                        default:
 | 
						|
                            *s = *p;
 | 
						|
                        }
 | 
						|
 | 
						|
                        ++p;
 | 
						|
                        ++s;
 | 
						|
                    }
 | 
						|
 | 
						|
                    operation = qc_get_operation(prepare_stmt);
 | 
						|
                    gwbuf_free(prepare_stmt);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return operation;
 | 
						|
}
 | 
						|
 | 
						|
static bool should_exclude(const char* name, List<Item>* excludep)
 | 
						|
{
 | 
						|
    bool exclude = false;
 | 
						|
    List_iterator<Item> ilist(*excludep);
 | 
						|
    Item* exclude_item;
 | 
						|
 | 
						|
    while (!exclude && (exclude_item = ilist++))
 | 
						|
    {
 | 
						|
        const char* exclude_name = exclude_item->name;
 | 
						|
 | 
						|
        if (exclude_name && (strcasecmp(name, exclude_name) == 0))
 | 
						|
        {
 | 
						|
            exclude = true;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!exclude)
 | 
						|
        {
 | 
						|
            exclude_name = strrchr(exclude_item->full_name(), '.');
 | 
						|
 | 
						|
            if (exclude_name)
 | 
						|
            {
 | 
						|
                ++exclude_name; // Char after the '.'
 | 
						|
 | 
						|
                if (strcasecmp(name, exclude_name) == 0)
 | 
						|
                {
 | 
						|
                    exclude = true;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return exclude;
 | 
						|
}
 | 
						|
 | 
						|
static void add_field_info(parsing_info_t* info,
 | 
						|
                           const char* database,
 | 
						|
                           const char* table,
 | 
						|
                           const char* column,
 | 
						|
                           uint32_t usage,
 | 
						|
                           List<Item>* excludep)
 | 
						|
{
 | 
						|
    ss_dassert(column);
 | 
						|
 | 
						|
    QC_FIELD_INFO item = { (char*)database, (char*)table, (char*)column, usage };
 | 
						|
 | 
						|
    size_t i;
 | 
						|
    for (i = 0; i < info->field_infos_len; ++i)
 | 
						|
    {
 | 
						|
        QC_FIELD_INFO* field_info = info->field_infos + i;
 | 
						|
 | 
						|
        if (strcasecmp(item.column, field_info->column) == 0)
 | 
						|
        {
 | 
						|
            if (!item.table && !field_info->table)
 | 
						|
            {
 | 
						|
                ss_dassert(!item.database && !field_info->database);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            else if (item.table && field_info->table && (strcmp(item.table, field_info->table) == 0))
 | 
						|
            {
 | 
						|
                if (!item.database && !field_info->database)
 | 
						|
                {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                else if (item.database &&
 | 
						|
                         field_info->database &&
 | 
						|
                         (strcmp(item.database, field_info->database) == 0))
 | 
						|
                {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    QC_FIELD_INFO* field_infos = NULL;
 | 
						|
 | 
						|
    if (i == info->field_infos_len) // If true, the field was not present already.
 | 
						|
    {
 | 
						|
        // If only a column is specified, but not a table or database and we
 | 
						|
        // have a list of expressions that should be excluded, we check if the column
 | 
						|
        // value is present in that list. This is in order to exclude the second "d" in
 | 
						|
        // a statement like "select a as d from x where d = 2".
 | 
						|
        if (!(column && !table && !database && excludep && should_exclude(column, excludep)))
 | 
						|
        {
 | 
						|
            if (info->field_infos_len < info->field_infos_capacity)
 | 
						|
            {
 | 
						|
                field_infos = info->field_infos;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                size_t capacity = info->field_infos_capacity ? 2 * info->field_infos_capacity : 8;
 | 
						|
                field_infos = (QC_FIELD_INFO*)realloc(info->field_infos, capacity * sizeof(QC_FIELD_INFO));
 | 
						|
 | 
						|
                if (field_infos)
 | 
						|
                {
 | 
						|
                    info->field_infos = field_infos;
 | 
						|
                    info->field_infos_capacity = capacity;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        info->field_infos[i].usage |= usage;
 | 
						|
    }
 | 
						|
 | 
						|
    // If field_infos is NULL, then the field was found and has already been noted.
 | 
						|
    if (field_infos)
 | 
						|
    {
 | 
						|
        item.database = item.database ? strdup(item.database) : NULL;
 | 
						|
        item.table = item.table ? strdup(item.table) : NULL;
 | 
						|
        ss_dassert(item.column);
 | 
						|
        item.column = strdup(item.column);
 | 
						|
 | 
						|
        // We are happy if we at least could dup the column.
 | 
						|
 | 
						|
        if (item.column)
 | 
						|
        {
 | 
						|
            field_infos[info->field_infos_len++] = item;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void add_field_info(parsing_info_t* pi, Item_field* item, uint32_t usage, List<Item>* excludep)
 | 
						|
{
 | 
						|
    const char* database = item->db_name;
 | 
						|
    const char* table = item->table_name;
 | 
						|
    const char* column = item->field_name;
 | 
						|
 | 
						|
    LEX* lex = get_lex(pi);
 | 
						|
 | 
						|
    switch (lex->sql_command)
 | 
						|
    {
 | 
						|
    case SQLCOM_SHOW_FIELDS:
 | 
						|
        if (!database)
 | 
						|
        {
 | 
						|
            database = "information_schema";
 | 
						|
        }
 | 
						|
 | 
						|
        if (!table)
 | 
						|
        {
 | 
						|
            table = "COLUMNS";
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SHOW_KEYS:
 | 
						|
        if (!database)
 | 
						|
        {
 | 
						|
            database = "information_schema";
 | 
						|
        }
 | 
						|
 | 
						|
        if (!table)
 | 
						|
        {
 | 
						|
            table = "STATISTICS";
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SHOW_STATUS:
 | 
						|
        if (!database)
 | 
						|
        {
 | 
						|
            database = "information_schema";
 | 
						|
        }
 | 
						|
 | 
						|
        if (!table)
 | 
						|
        {
 | 
						|
            table = "SESSION_STATUS";
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SHOW_TABLES:
 | 
						|
        if (!database)
 | 
						|
        {
 | 
						|
            database = "information_schema";
 | 
						|
        }
 | 
						|
 | 
						|
        if (!table)
 | 
						|
        {
 | 
						|
            table = "TABLE_NAMES";
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SHOW_TABLE_STATUS:
 | 
						|
        if (!database)
 | 
						|
        {
 | 
						|
            database = "information_schema";
 | 
						|
        }
 | 
						|
 | 
						|
        if (!table)
 | 
						|
        {
 | 
						|
            table = "TABLES";
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case SQLCOM_SHOW_VARIABLES:
 | 
						|
        if (!database)
 | 
						|
        {
 | 
						|
            database = "information_schema";
 | 
						|
        }
 | 
						|
 | 
						|
        if (!table)
 | 
						|
        {
 | 
						|
            table = "SESSION_STATUS";
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
    add_field_info(pi, database, table, column, usage, excludep);
 | 
						|
}
 | 
						|
 | 
						|
static void add_field_info(parsing_info_t* pi, Item* item, uint32_t usage, List<Item>* excludep)
 | 
						|
{
 | 
						|
    const char* database = NULL;
 | 
						|
    const char* table = NULL;
 | 
						|
    const char* column = item->name;
 | 
						|
 | 
						|
    add_field_info(pi, database, table, column, usage, excludep);
 | 
						|
}
 | 
						|
 | 
						|
typedef enum collect_source
 | 
						|
{
 | 
						|
    COLLECT_SELECT,
 | 
						|
    COLLECT_WHERE,
 | 
						|
    COLLECT_HAVING,
 | 
						|
    COLLECT_GROUP_BY,
 | 
						|
} collect_source_t;
 | 
						|
 | 
						|
static void update_field_infos(parsing_info_t* pi,
 | 
						|
                               LEX* lex,
 | 
						|
                               st_select_lex* select,
 | 
						|
                               uint32_t usage,
 | 
						|
                               List<Item>* excludep);
 | 
						|
 | 
						|
static void update_field_infos(parsing_info_t* pi,
 | 
						|
                               collect_source_t source,
 | 
						|
                               Item* item,
 | 
						|
                               uint32_t usage,
 | 
						|
                               List<Item>* excludep)
 | 
						|
{
 | 
						|
    switch (item->type())
 | 
						|
    {
 | 
						|
    case Item::COND_ITEM:
 | 
						|
        {
 | 
						|
            Item_cond* cond_item = static_cast<Item_cond*>(item);
 | 
						|
            List_iterator<Item> ilist(*cond_item->argument_list());
 | 
						|
 | 
						|
            while (Item *i = ilist++)
 | 
						|
            {
 | 
						|
                update_field_infos(pi, source, i, usage, excludep);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case Item::FIELD_ITEM:
 | 
						|
        add_field_info(pi, static_cast<Item_field*>(item), usage, excludep);
 | 
						|
        break;
 | 
						|
 | 
						|
    case Item::REF_ITEM:
 | 
						|
        {
 | 
						|
            if (source != COLLECT_SELECT)
 | 
						|
            {
 | 
						|
                Item_ref* ref_item = static_cast<Item_ref*>(item);
 | 
						|
 | 
						|
                add_field_info(pi, item, usage, excludep);
 | 
						|
 | 
						|
                size_t n_items = ref_item->cols();
 | 
						|
 | 
						|
                for (size_t i = 0; i < n_items; ++i)
 | 
						|
                {
 | 
						|
                    Item* reffed_item = ref_item->element_index(i);
 | 
						|
 | 
						|
                    if (reffed_item != ref_item)
 | 
						|
                    {
 | 
						|
                        update_field_infos(pi, source, ref_item->element_index(i), usage, excludep);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case Item::ROW_ITEM:
 | 
						|
        {
 | 
						|
            Item_row* row_item = static_cast<Item_row*>(item);
 | 
						|
            size_t n_items = row_item->cols();
 | 
						|
 | 
						|
            for (size_t i = 0; i < n_items; ++i)
 | 
						|
            {
 | 
						|
                update_field_infos(pi, source, row_item->element_index(i), usage, excludep);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case Item::FUNC_ITEM:
 | 
						|
    case Item::SUM_FUNC_ITEM:
 | 
						|
        {
 | 
						|
            Item_func* func_item = static_cast<Item_func*>(item);
 | 
						|
            Item** items = func_item->arguments();
 | 
						|
            size_t n_items = func_item->argument_count();
 | 
						|
 | 
						|
            for (size_t i = 0; i < n_items; ++i)
 | 
						|
            {
 | 
						|
                update_field_infos(pi, source, items[i], usage, excludep);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case Item::SUBSELECT_ITEM:
 | 
						|
        {
 | 
						|
            Item_subselect* subselect_item = static_cast<Item_subselect*>(item);
 | 
						|
 | 
						|
            switch (subselect_item->substype())
 | 
						|
            {
 | 
						|
            case Item_subselect::IN_SUBS:
 | 
						|
            case Item_subselect::ALL_SUBS:
 | 
						|
            case Item_subselect::ANY_SUBS:
 | 
						|
                {
 | 
						|
                    Item_in_subselect* in_subselect_item = static_cast<Item_in_subselect*>(item);
 | 
						|
 | 
						|
#if (((MYSQL_VERSION_MAJOR == 5) &&\
 | 
						|
      ((MYSQL_VERSION_MINOR > 5) ||\
 | 
						|
       ((MYSQL_VERSION_MINOR == 5) && (MYSQL_VERSION_PATCH >= 48))\
 | 
						|
      )\
 | 
						|
     ) ||\
 | 
						|
     (MYSQL_VERSION_MAJOR >= 10)\
 | 
						|
    )
 | 
						|
                    if (in_subselect_item->left_expr_orig)
 | 
						|
                    {
 | 
						|
                        update_field_infos(pi, source,
 | 
						|
                                           in_subselect_item->left_expr_orig, usage, excludep);
 | 
						|
                    }
 | 
						|
                    st_select_lex* ssl = in_subselect_item->get_select_lex();
 | 
						|
                    if (ssl)
 | 
						|
                    {
 | 
						|
                        uint32_t sub_usage = usage;
 | 
						|
 | 
						|
                        sub_usage &= ~QC_USED_IN_SELECT;
 | 
						|
                        sub_usage |= QC_USED_IN_SUBSELECT;
 | 
						|
 | 
						|
                        update_field_infos(pi,
 | 
						|
                                           get_lex(pi),
 | 
						|
                                           ssl,
 | 
						|
                                           sub_usage,
 | 
						|
                                           excludep);
 | 
						|
                    }
 | 
						|
#else
 | 
						|
#pragma message "Figure out what to do with versions < 5.5.48."
 | 
						|
#endif
 | 
						|
                    // TODO: Anything else that needs to be looked into?
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
            case Item_subselect::EXISTS_SUBS:
 | 
						|
                // TODO: Handle these explicitly as well.
 | 
						|
                break;
 | 
						|
 | 
						|
            case Item_subselect::SINGLEROW_SUBS:
 | 
						|
                {
 | 
						|
                    Item_singlerow_subselect* ss_item = static_cast<Item_singlerow_subselect*>(item);
 | 
						|
                    st_select_lex *ssl = ss_item->get_select_lex();
 | 
						|
 | 
						|
                    usage &= ~QC_USED_IN_SELECT;
 | 
						|
                    usage |= QC_USED_IN_SUBSELECT;
 | 
						|
 | 
						|
                    update_field_infos(pi, get_lex(pi), ssl, usage, excludep);
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
            case Item_subselect::UNKNOWN_SUBS:
 | 
						|
            default:
 | 
						|
                MXS_ERROR("Unknown subselect type: %d", subselect_item->substype());
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void update_field_infos(parsing_info_t* pi,
 | 
						|
                               LEX* lex,
 | 
						|
                               st_select_lex* select,
 | 
						|
                               uint32_t usage,
 | 
						|
                               List<Item>* excludep)
 | 
						|
{
 | 
						|
    List_iterator<Item> ilist(select->item_list);
 | 
						|
 | 
						|
    while (Item *item = ilist++)
 | 
						|
    {
 | 
						|
        update_field_infos(pi, COLLECT_SELECT, item, usage, NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    if (select->group_list.first)
 | 
						|
    {
 | 
						|
        ORDER* order = select->group_list.first;
 | 
						|
        while (order)
 | 
						|
        {
 | 
						|
            Item* item = *order->item;
 | 
						|
 | 
						|
            update_field_infos(pi, COLLECT_GROUP_BY, item, QC_USED_IN_GROUP_BY,
 | 
						|
                               &select->item_list);
 | 
						|
 | 
						|
            order = order->next;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (select->where)
 | 
						|
    {
 | 
						|
        update_field_infos(pi, COLLECT_WHERE,
 | 
						|
                           select->where,
 | 
						|
                           QC_USED_IN_WHERE,
 | 
						|
                           &select->item_list);
 | 
						|
    }
 | 
						|
 | 
						|
#if defined(COLLECT_HAVING_AS_WELL)
 | 
						|
    // A HAVING clause can only refer to fields that already have been
 | 
						|
    // mentioned. Consequently, they need not be collected.
 | 
						|
    if (select->having)
 | 
						|
    {
 | 
						|
        update_field_infos(pi, COLLECT_HAVING,
 | 
						|
                           select->having,
 | 
						|
                           0,
 | 
						|
                           &select->item_list);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    TABLE_LIST* table_list = select->get_table_list();
 | 
						|
 | 
						|
    if (table_list)
 | 
						|
    {
 | 
						|
        st_select_lex *sl = table_list->get_single_select();
 | 
						|
 | 
						|
        if (sl)
 | 
						|
        {
 | 
						|
            // This is for "SELECT 1 FROM (SELECT ...)"
 | 
						|
            usage &= ~QC_USED_IN_SELECT;
 | 
						|
            usage |= QC_USED_IN_SUBSELECT;
 | 
						|
            update_field_infos(pi, get_lex(pi), sl, usage, excludep);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void qc_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, size_t* n_infos)
 | 
						|
{
 | 
						|
    if (!buf)
 | 
						|
    {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!ensure_query_is_parsed(buf))
 | 
						|
    {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    parsing_info_t* pi = get_pinfo(buf);
 | 
						|
    ss_dassert(pi);
 | 
						|
 | 
						|
    if (!pi->field_infos)
 | 
						|
    {
 | 
						|
        LEX* lex = get_lex(buf);
 | 
						|
        ss_dassert(lex);
 | 
						|
 | 
						|
        if (!lex)
 | 
						|
        {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        uint32_t usage = 0;
 | 
						|
 | 
						|
        switch (lex->sql_command)
 | 
						|
        {
 | 
						|
        case SQLCOM_UPDATE:
 | 
						|
        case SQLCOM_UPDATE_MULTI:
 | 
						|
            usage |= QC_USED_IN_SET;
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            usage |= QC_USED_IN_SELECT;
 | 
						|
        }
 | 
						|
 | 
						|
        lex->current_select = &lex->select_lex;
 | 
						|
 | 
						|
        update_field_infos(pi, lex, &lex->select_lex, usage, NULL);
 | 
						|
 | 
						|
        List_iterator<Item> ilist(lex->value_list);
 | 
						|
        while (Item* item = ilist++)
 | 
						|
        {
 | 
						|
            update_field_infos(pi, COLLECT_SELECT, item, 0, NULL);
 | 
						|
        }
 | 
						|
 | 
						|
        if ((lex->sql_command == SQLCOM_INSERT) ||
 | 
						|
            (lex->sql_command == SQLCOM_INSERT_SELECT) ||
 | 
						|
            (lex->sql_command == SQLCOM_REPLACE))
 | 
						|
        {
 | 
						|
            List_iterator<Item> ilist(lex->field_list);
 | 
						|
            while (Item *item = ilist++)
 | 
						|
            {
 | 
						|
                update_field_infos(pi, COLLECT_SELECT, item, 0, NULL);
 | 
						|
            }
 | 
						|
 | 
						|
            if (lex->insert_list)
 | 
						|
            {
 | 
						|
                List_iterator<Item> ilist(*lex->insert_list);
 | 
						|
                while (Item *item = ilist++)
 | 
						|
                {
 | 
						|
                    update_field_infos(pi, COLLECT_SELECT, item, 0, NULL);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (lex->sql_command == SQLCOM_SET_OPTION)
 | 
						|
        {
 | 
						|
#if defined(WAY_TO_DOWNCAST_SET_VAR_BASE_EXISTS)
 | 
						|
            // The list of set_var_base contains the value of variables.
 | 
						|
            // However, the actual type is a derived type of set_var_base
 | 
						|
            // and there is no information using which we could do the
 | 
						|
            // downcast...
 | 
						|
            List_iterator<set_var_base> ilist(lex->var_list);
 | 
						|
            while (set_var_base* var = ilist++)
 | 
						|
            {
 | 
						|
                // Is set_var_base a set_var, set_var_user, set_var_password
 | 
						|
                // set_var_role
 | 
						|
                ...
 | 
						|
            }
 | 
						|
#endif
 | 
						|
            // ...so, we will simply assume that any nested selects are
 | 
						|
            // from statements like "set @a:=(SELECT a from t1)".
 | 
						|
 | 
						|
            usage &= ~QC_USED_IN_SELECT;
 | 
						|
            usage |= QC_USED_IN_SUBSELECT;
 | 
						|
 | 
						|
            st_select_lex* select = lex->all_selects_list;
 | 
						|
 | 
						|
            while (select)
 | 
						|
            {
 | 
						|
                if (select->nest_level != 0) // Not the top-level select.
 | 
						|
                {
 | 
						|
                    update_field_infos(pi, lex, select, usage, NULL);
 | 
						|
                }
 | 
						|
 | 
						|
                select = select->next_select_in_list();
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    *infos = pi->field_infos;
 | 
						|
    *n_infos = pi->field_infos_len;
 | 
						|
}
 | 
						|
 | 
						|
namespace
 | 
						|
{
 | 
						|
 | 
						|
// Do not change the order without making corresponding changes to IDX_... below.
 | 
						|
const char* server_options[] =
 | 
						|
{
 | 
						|
    "MariaDB Corporation MaxScale",
 | 
						|
    "--no-defaults",
 | 
						|
    "--datadir=",
 | 
						|
    "--language=",
 | 
						|
    "--skip-innodb",
 | 
						|
    "--default-storage-engine=myisam",
 | 
						|
    NULL
 | 
						|
};
 | 
						|
 | 
						|
const int IDX_DATADIR = 2;
 | 
						|
const int IDX_LANGUAGE = 3;
 | 
						|
const int N_OPTIONS = (sizeof(server_options) / sizeof(server_options[0])) - 1;
 | 
						|
 | 
						|
const char* server_groups[] = {
 | 
						|
    "embedded",
 | 
						|
    "server",
 | 
						|
    "server",
 | 
						|
    "embedded",
 | 
						|
    "server",
 | 
						|
    "server",
 | 
						|
    NULL
 | 
						|
};
 | 
						|
 | 
						|
const int OPTIONS_DATADIR_SIZE = 10 + PATH_MAX; // strlen("--datadir=");
 | 
						|
const int OPTIONS_LANGUAGE_SIZE = 11 + PATH_MAX; // strlen("--language=");
 | 
						|
 | 
						|
char datadir_arg[OPTIONS_DATADIR_SIZE];
 | 
						|
char language_arg[OPTIONS_LANGUAGE_SIZE];
 | 
						|
 | 
						|
 | 
						|
void configure_options(const char* datadir, const char* langdir)
 | 
						|
{
 | 
						|
    int rv;
 | 
						|
 | 
						|
    rv = snprintf(datadir_arg, OPTIONS_DATADIR_SIZE, "--datadir=%s", datadir);
 | 
						|
    ss_dassert(rv < OPTIONS_DATADIR_SIZE); // Ensured by create_datadir().
 | 
						|
    server_options[IDX_DATADIR] = datadir_arg;
 | 
						|
 | 
						|
    rv = sprintf(language_arg, "--language=%s", langdir);
 | 
						|
    ss_dassert(rv < OPTIONS_LANGUAGE_SIZE); // Ensured by qc_init().
 | 
						|
    server_options[IDX_LANGUAGE] = language_arg;
 | 
						|
 | 
						|
    // To prevent warning of unused variable when built in release mode,
 | 
						|
    // when ss_dassert() turns into empty statement.
 | 
						|
    (void)rv;
 | 
						|
}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
bool qc_init(const char* args)
 | 
						|
{
 | 
						|
    bool inited = false;
 | 
						|
 | 
						|
    if (args)
 | 
						|
    {
 | 
						|
        MXS_WARNING("qc_mysqlembedded: '%s' provided as arguments, "
 | 
						|
                    "even though no arguments are supported.", args);
 | 
						|
    }
 | 
						|
 | 
						|
    if (strlen(get_langdir()) >= PATH_MAX)
 | 
						|
    {
 | 
						|
        fprintf(stderr, "MaxScale: error: Language path is too long: %s.", get_langdir());
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        configure_options(get_process_datadir(), get_langdir());
 | 
						|
 | 
						|
        int argc = N_OPTIONS;
 | 
						|
        char** argv = const_cast<char**>(server_options);
 | 
						|
        char** groups = const_cast<char**>(server_groups);
 | 
						|
 | 
						|
        int rc = mysql_library_init(argc, argv, groups);
 | 
						|
 | 
						|
        if (rc != 0)
 | 
						|
        {
 | 
						|
            MXS_ERROR("mysql_library_init() failed. Error code: %d", rc);
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
#if MYSQL_VERSION_ID >= 100000
 | 
						|
            set_malloc_size_cb(NULL);
 | 
						|
#endif
 | 
						|
            MXS_NOTICE("Query classifier initialized.");
 | 
						|
            inited = true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return inited;
 | 
						|
}
 | 
						|
 | 
						|
void qc_end(void)
 | 
						|
{
 | 
						|
    mysql_library_end();
 | 
						|
}
 | 
						|
 | 
						|
bool qc_thread_init(void)
 | 
						|
{
 | 
						|
    bool inited = (mysql_thread_init() == 0);
 | 
						|
 | 
						|
    if (!inited)
 | 
						|
    {
 | 
						|
        MXS_ERROR("mysql_thread_init() failed.");
 | 
						|
    }
 | 
						|
 | 
						|
    return inited;
 | 
						|
}
 | 
						|
 | 
						|
void qc_thread_end(void)
 | 
						|
{
 | 
						|
    mysql_thread_end();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * EXPORTS
 | 
						|
 */
 | 
						|
 | 
						|
extern "C"
 | 
						|
{
 | 
						|
 | 
						|
static char version_string[] = "V1.0.0";
 | 
						|
 | 
						|
static QUERY_CLASSIFIER qc =
 | 
						|
{
 | 
						|
    qc_init,
 | 
						|
    qc_end,
 | 
						|
    qc_thread_init,
 | 
						|
    qc_thread_end,
 | 
						|
    qc_parse,
 | 
						|
    qc_get_type,
 | 
						|
    qc_get_operation,
 | 
						|
    qc_get_created_table_name,
 | 
						|
    qc_is_drop_table_query,
 | 
						|
    qc_is_real_query,
 | 
						|
    qc_get_table_names,
 | 
						|
    NULL,
 | 
						|
    qc_query_has_clause,
 | 
						|
    qc_get_database_names,
 | 
						|
    qc_get_prepare_name,
 | 
						|
    qc_get_prepare_operation,
 | 
						|
    qc_get_field_info,
 | 
						|
};
 | 
						|
 | 
						|
 /* @see function load_module in load_utils.c for explanation of the following
 | 
						|
  * lint directives.
 | 
						|
 */
 | 
						|
/*lint -e14 */
 | 
						|
MODULE_INFO info =
 | 
						|
{
 | 
						|
    MODULE_API_QUERY_CLASSIFIER,
 | 
						|
    MODULE_IN_DEVELOPMENT,
 | 
						|
    QUERY_CLASSIFIER_VERSION,
 | 
						|
    const_cast<char*>("Query classifier based upon MySQL Embedded"),
 | 
						|
};
 | 
						|
 | 
						|
char* version()
 | 
						|
{
 | 
						|
    return const_cast<char*>(version_string);
 | 
						|
}
 | 
						|
 | 
						|
void ModuleInit()
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
QUERY_CLASSIFIER* GetModuleObject()
 | 
						|
{
 | 
						|
    return &qc;
 | 
						|
}
 | 
						|
/*lint +e14 */
 | 
						|
 | 
						|
}
 |