788 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			788 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/**
 | 
						|
 * @section LICENCE
 | 
						|
 * 
 | 
						|
 * This file is distributed as part of the SkySQL Gateway. 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 SkySQL Ab
 | 
						|
 * 
 | 
						|
 * @file 
 | 
						|
 * 
 | 
						|
 */
 | 
						|
 | 
						|
#define EMBEDDED_LIBRARY
 | 
						|
#define MYSQL_YACC
 | 
						|
#define MYSQL_LEX012
 | 
						|
#define MYSQL_SERVER
 | 
						|
#if defined(MYSQL_CLIENT)
 | 
						|
# undef MYSQL_CLIENT
 | 
						|
#endif
 | 
						|
 | 
						|
#include <query_classifier.h>
 | 
						|
#include "../utils/skygw_types.h"
 | 
						|
#include "../utils/skygw_debug.h"
 | 
						|
#include <log_manager.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>
 | 
						|
#include <set_var.h>
 | 
						|
#include <strfunc.h>
 | 
						|
#include <item_func.h>
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdarg.h>
 | 
						|
 | 
						|
extern int lm_enabled_logfiles_bitmask;
 | 
						|
 | 
						|
#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 skygw_query_type_t resolve_query_type(
 | 
						|
        THD* thd);
 | 
						|
 | 
						|
static bool skygw_stmt_causes_implicit_commit(
 | 
						|
        LEX*  lex,
 | 
						|
        int* autocommit_stmt);
 | 
						|
 | 
						|
static int is_autocommit_stmt(
 | 
						|
        LEX* lex);
 | 
						|
 | 
						|
/** 
 | 
						|
 * @node (write brief function description here) 
 | 
						|
 *
 | 
						|
 * Parameters:
 | 
						|
 * @param query_str - <usage>
 | 
						|
 *          <description>
 | 
						|
 *
 | 
						|
 * @param client_flag - <usage>
 | 
						|
 *          <description>
 | 
						|
 *
 | 
						|
 * @return 
 | 
						|
 *
 | 
						|
 * 
 | 
						|
 * @details (write detailed description here)
 | 
						|
 *
 | 
						|
 */
 | 
						|
skygw_query_type_t skygw_query_classifier_get_type(
 | 
						|
        const char*   query,
 | 
						|
        unsigned long client_flags)
 | 
						|
{
 | 
						|
        MYSQL*      mysql;
 | 
						|
        char*       query_str;
 | 
						|
        const char* user  = "skygw";
 | 
						|
        const char* db    = "skygw";
 | 
						|
        THD*        thd;
 | 
						|
        skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN;
 | 
						|
        bool        failp = FALSE;
 | 
						|
 | 
						|
        ss_info_dassert(query != NULL, ("query_str is NULL"));
 | 
						|
        
 | 
						|
        query_str = const_cast<char*>(query);
 | 
						|
        LOGIF(LT, (skygw_log_write(
 | 
						|
                LOGFILE_TRACE,
 | 
						|
                "%lu [skygw_query_classifier_get_type] Query : \"%s\"",
 | 
						|
                pthread_self(),
 | 
						|
                query_str)));
 | 
						|
        
 | 
						|
        /** Get server handle */
 | 
						|
        mysql = mysql_init(NULL);
 | 
						|
        
 | 
						|
        if (mysql == NULL) {
 | 
						|
                LOGIF(LE, (skygw_log_write_flush(
 | 
						|
                        LOGFILE_ERROR,
 | 
						|
                        "Error : call to mysql_real_connect failed due %d, %s.",
 | 
						|
                        mysql_errno(mysql),
 | 
						|
                        mysql_error(mysql))));
 | 
						|
                
 | 
						|
                mysql_library_end();
 | 
						|
                goto return_without_server;
 | 
						|
        }
 | 
						|
 | 
						|
        /** 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;
 | 
						|
        
 | 
						|
        /** Get one or create new THD object to be use in parsing */
 | 
						|
        thd = get_or_create_thd_for_parsing(mysql, query_str);
 | 
						|
 | 
						|
        if (thd == NULL) {
 | 
						|
                goto return_with_server_handle;
 | 
						|
        }
 | 
						|
        /** Create parse_tree inside thd */
 | 
						|
        failp = create_parse_tree(thd);
 | 
						|
 | 
						|
        if (failp) {
 | 
						|
                goto return_with_thd;
 | 
						|
        }
 | 
						|
        qtype = resolve_query_type(thd);
 | 
						|
        
 | 
						|
return_with_thd:
 | 
						|
        (*mysql->methods->free_embedded_thd)(mysql);
 | 
						|
        mysql->thd = 0;
 | 
						|
return_with_server_handle:
 | 
						|
        mysql_close(mysql);
 | 
						|
        mysql_thread_end();
 | 
						|
return_without_server:
 | 
						|
        return qtype;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/** 
 | 
						|
 * @node (write brief function description here) 
 | 
						|
 *
 | 
						|
 * Parameters:
 | 
						|
 * @param mysql - <usage>
 | 
						|
 *          <description>
 | 
						|
 *
 | 
						|
 * @param query_str - <usage>
 | 
						|
 *          <description>
 | 
						|
 *
 | 
						|
 * @return 
 | 
						|
 *
 | 
						|
 * 
 | 
						|
 * @details (write detailed description here)
 | 
						|
 *
 | 
						|
 */
 | 
						|
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) {
 | 
						|
                LOGIF(LE, (skygw_log_write_flush(
 | 
						|
                        LOGFILE_ERROR,
 | 
						|
                        "Error : Failed to create thread context for parsing. "
 | 
						|
                        "Exiting.")));
 | 
						|
                goto return_thd;
 | 
						|
        }
 | 
						|
        mysql->thd = thd;
 | 
						|
        init_embedded_mysql(mysql, client_flags);
 | 
						|
        failp = check_embedded_connection(mysql, db);
 | 
						|
 | 
						|
        if (failp) {
 | 
						|
                LOGIF(LE, (skygw_log_write_flush(
 | 
						|
                        LOGFILE_ERROR,
 | 
						|
                        "Error : Call to check_embedded_connection failed. "
 | 
						|
                        "Exiting.")));
 | 
						|
                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);
 | 
						|
                LOGIF(LE, (skygw_log_write_flush(
 | 
						|
                        LOGFILE_ERROR,
 | 
						|
                        "Error : Invalid status %d in embedded server. "
 | 
						|
                        "Exiting.")));
 | 
						|
                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;
 | 
						|
        }
 | 
						|
        mysql_reset_thd_for_next_command(thd);
 | 
						|
        
 | 
						|
        /** 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) {
 | 
						|
                LOGIF(LE, (skygw_log_write_flush(
 | 
						|
                        LOGFILE_ERROR,
 | 
						|
                        "Error : Failed to set database in thread context.")));
 | 
						|
        }
 | 
						|
        failp = parse_sql(thd, &parser_state, NULL);
 | 
						|
 | 
						|
        if (failp) {
 | 
						|
                LOGIF(LD, (skygw_log_write(
 | 
						|
                        LOGFILE_DEBUG,
 | 
						|
                        "%lu [readwritesplit:create_parse_tree] failed to "
 | 
						|
                        "create parse tree.",
 | 
						|
                        pthread_self())));
 | 
						|
        }
 | 
						|
return_here:
 | 
						|
        return failp;
 | 
						|
}
 | 
						|
 | 
						|
/** 
 | 
						|
 * @node Set new query type if new is more restrictive than old. 
 | 
						|
 *
 | 
						|
 * Parameters:
 | 
						|
 * @param qtype - <usage>
 | 
						|
 *          <description>
 | 
						|
 *
 | 
						|
 * @param new_type - <usage>
 | 
						|
 *          <description>
 | 
						|
 *
 | 
						|
 * @return 
 | 
						|
 *
 | 
						|
 * 
 | 
						|
 * @details The implementation relies on that enumerated values correspond
 | 
						|
 * to the restrictiviness of the value. That is, smaller value means less
 | 
						|
 * restrictive, for example, QUERY_TYPE_READ is smaller than QUERY_TYPE_WRITE.
 | 
						|
 *
 | 
						|
 */
 | 
						|
static u_int16_t set_query_type(
 | 
						|
        u_int16_t* qtype,
 | 
						|
        u_int16_t  new_type)
 | 
						|
{
 | 
						|
        *qtype = MAX(*qtype, new_type);
 | 
						|
        return *qtype;
 | 
						|
}
 | 
						|
 | 
						|
/** 
 | 
						|
 * @node Detect query type, read-only, write, or session update 
 | 
						|
 *
 | 
						|
 * Parameters:
 | 
						|
 * @param thd - <usage>
 | 
						|
 *          <description>
 | 
						|
 *
 | 
						|
 * @return 
 | 
						|
 *
 | 
						|
 * 
 | 
						|
 * @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 skygw_query_type_t resolve_query_type(
 | 
						|
        THD* thd)
 | 
						|
{
 | 
						|
        skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN;
 | 
						|
        u_int16_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.
 | 
						|
         */
 | 
						|
        bool               force_data_modify_op_replication;
 | 
						|
        
 | 
						|
        ss_info_dassert(thd != NULL, ("thd is NULL\n"));
 | 
						|
 | 
						|
        force_data_modify_op_replication = FALSE;        
 | 
						|
        lex = thd->lex;
 | 
						|
        
 | 
						|
        /** SELECT ..INTO variable|OUTFILE|DUMPFILE */
 | 
						|
        if (lex->result != NULL) {
 | 
						|
                type = QUERY_TYPE_SESSION_WRITE;
 | 
						|
                goto return_qtype;
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (skygw_stmt_causes_implicit_commit(
 | 
						|
                lex, 
 | 
						|
                &set_autocommit_stmt))
 | 
						|
        {
 | 
						|
                if (LOG_IS_ENABLED(LOGFILE_TRACE))
 | 
						|
                {
 | 
						|
                        if (sql_command_flags[lex->sql_command] & 
 | 
						|
                                CF_IMPLICT_COMMIT_BEGIN)
 | 
						|
                        {
 | 
						|
                                skygw_log_write(
 | 
						|
                                        LOGFILE_TRACE,
 | 
						|
                                        "Implicit COMMIT before executing the "
 | 
						|
                                        "next command.");
 | 
						|
                        }
 | 
						|
                        else if (sql_command_flags[lex->sql_command] & 
 | 
						|
                                CF_IMPLICIT_COMMIT_END)
 | 
						|
                        {
 | 
						|
                                skygw_log_write(
 | 
						|
                                        LOGFILE_TRACE,
 | 
						|
                                        "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 (LOG_IS_ENABLED(LOGFILE_TRACE))
 | 
						|
                {
 | 
						|
                        skygw_log_write(
 | 
						|
                                LOGFILE_TRACE,
 | 
						|
                                "Disable autocommit : implicit START TRANSACTION"
 | 
						|
                                " before executing the next command.");
 | 
						|
                }
 | 
						|
                type |= QUERY_TYPE_DISABLE_AUTOCOMMIT;  
 | 
						|
                type |= QUERY_TYPE_BEGIN_TRX;
 | 
						|
        }
 | 
						|
        /**
 | 
						|
        * REVOKE ALL, ASSIGN_TO_KEYCACHE,
 | 
						|
        * PRELOAD_KEYS, FLUSH, RESET, CREATE|ALTER|DROP SERVER
 | 
						|
        */
 | 
						|
        if (lex->option_type == OPT_GLOBAL)
 | 
						|
        {
 | 
						|
                type |= QUERY_TYPE_GLOBAL_WRITE;
 | 
						|
                goto return_qtype;
 | 
						|
        }
 | 
						|
        else if (lex->option_type == OPT_SESSION)
 | 
						|
        {
 | 
						|
                type |=  QUERY_TYPE_SESSION_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 (thd->variables.sql_log_bin == 0 &&
 | 
						|
                        force_data_modify_op_replication)
 | 
						|
                {
 | 
						|
                        type |= QUERY_TYPE_SESSION_WRITE;
 | 
						|
                } else {
 | 
						|
                        type |= QUERY_TYPE_WRITE;
 | 
						|
                }
 | 
						|
            
 | 
						|
                goto return_qtype;
 | 
						|
        }
 | 
						|
        
 | 
						|
        /** Try to catch session modifications here */
 | 
						|
        switch (lex->sql_command) {
 | 
						|
                case SQLCOM_SET_OPTION: /*< SET commands. */
 | 
						|
                        if (lex->option_type == OPT_GLOBAL)
 | 
						|
                        {
 | 
						|
                                type |= QUERY_TYPE_GLOBAL_WRITE;
 | 
						|
                                break;
 | 
						|
                        }
 | 
						|
                /**<! fall through */
 | 
						|
                case SQLCOM_CHANGE_DB:
 | 
						|
                        type |= QUERY_TYPE_SESSION_WRITE;
 | 
						|
                        break;
 | 
						|
 | 
						|
                case SQLCOM_SELECT:
 | 
						|
                        type |= QUERY_TYPE_READ;
 | 
						|
                        break;
 | 
						|
 | 
						|
                case SQLCOM_CALL:
 | 
						|
                        type |= QUERY_TYPE_WRITE;
 | 
						|
                        break;
 | 
						|
                        
 | 
						|
                case SQLCOM_BEGIN:
 | 
						|
                        type |= QUERY_TYPE_BEGIN_TRX;
 | 
						|
                        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;
 | 
						|
                        
 | 
						|
                default:
 | 
						|
                        break;
 | 
						|
        }
 | 
						|
 | 
						|
        if (QTYPE_LESS_RESTRICTIVE_THAN_WRITE(type)) {
 | 
						|
                /**
 | 
						|
                 * 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 nede 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();
 | 
						|
                        LOGIF(LD, (skygw_log_write(
 | 
						|
                                LOGFILE_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;
 | 
						|
                                        LOGIF(LD, (skygw_log_write(
 | 
						|
                                                LOGFILE_DEBUG,
 | 
						|
                                                "%lu [resolve_query_type] "
 | 
						|
                                                "functype FUNC_SP, stored proc "
 | 
						|
                                                "or unknown function.",
 | 
						|
                                                "%s:%s",
 | 
						|
                                                pthread_self())));
 | 
						|
                                        break;
 | 
						|
                                case Item_func::UDF_FUNC:
 | 
						|
                                        func_qtype |= QUERY_TYPE_WRITE;
 | 
						|
                                        LOGIF(LD, (skygw_log_write(
 | 
						|
                                                LOGFILE_DEBUG,
 | 
						|
                                                "%lu [resolve_query_type] "
 | 
						|
                                                "functype UDF_FUNC, user-defined "
 | 
						|
                                                "function.",
 | 
						|
                                                pthread_self())));
 | 
						|
                                        break;
 | 
						|
                                case Item_func::NOW_FUNC:
 | 
						|
                                case Item_func::GSYSVAR_FUNC:
 | 
						|
                                        func_qtype |= QUERY_TYPE_LOCAL_READ;
 | 
						|
                                        LOGIF(LD, (skygw_log_write(
 | 
						|
                                                LOGFILE_DEBUG,
 | 
						|
                                                "%lu [resolve_query_type] "
 | 
						|
                                                "functype NOW_FUNC, could be "
 | 
						|
                                                "executed in MaxScale.",
 | 
						|
                                                pthread_self())));
 | 
						|
                                        break;
 | 
						|
                                case Item_func::UNKNOWN_FUNC:
 | 
						|
                                        func_qtype |= QUERY_TYPE_READ;
 | 
						|
                                        /**
 | 
						|
                                         * Many built-in functions are of this
 | 
						|
                                         * type, for example, rand(), soundex(),
 | 
						|
                                         * repeat() .
 | 
						|
                                         */
 | 
						|
                                        LOGIF(LD, (skygw_log_write(
 | 
						|
                                                LOGFILE_DEBUG,
 | 
						|
                                                "%lu [resolve_query_type] "
 | 
						|
                                                "functype UNKNOWN_FUNC, "
 | 
						|
                                                "typically some system function.",
 | 
						|
                                                pthread_self())));
 | 
						|
                                        break;
 | 
						|
                                default:
 | 
						|
                                        LOGIF(LD, (skygw_log_write(
 | 
						|
                                                LOGFILE_DEBUG,
 | 
						|
                                                "%lu [resolve_query_type] "
 | 
						|
                                                "Unknown functype %d. Something "
 | 
						|
                                                "has gone wrong.",
 | 
						|
                                                pthread_self(),
 | 
						|
                                                ftype)));
 | 
						|
                                        break;
 | 
						|
                                } /**< switch */
 | 
						|
                                /**< Set new query type */
 | 
						|
                                type |= set_query_type(&type, func_qtype);
 | 
						|
                        }
 | 
						|
                        /**
 | 
						|
                         * Write is as restrictive as it gets due functions,
 | 
						|
                         * so break.
 | 
						|
                         */
 | 
						|
                        if ((type & QUERY_TYPE_WRITE) == QUERY_TYPE_WRITE) {
 | 
						|
                                break;
 | 
						|
                        }
 | 
						|
                } /**< for */
 | 
						|
        } /**< if */
 | 
						|
return_qtype:
 | 
						|
        qtype = (skygw_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. 
 | 
						|
 */
 | 
						|
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->drop_temporary);
 | 
						|
                        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.
 | 
						|
 * 
 | 
						|
 * Returns 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;
 | 
						|
}
 |