411 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			411 lines
		
	
	
		
			12 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 <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 <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <stdarg.h>
 | 
						|
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
/** 
 | 
						|
 * @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_dfprintf(stderr, ">> skygw_query_classifier_get_type\n");
 | 
						|
        ss_info_dassert(query != NULL, ("query_str is NULL"));
 | 
						|
        
 | 
						|
        query_str = const_cast<char*>(query);
 | 
						|
#if QUERY_DEBUG
 | 
						|
        fprintf(stderr, "   Query \"%s\"\n", query_str);
 | 
						|
#endif
 | 
						|
        
 | 
						|
        /** Get server handle */
 | 
						|
        mysql = mysql_init(NULL);
 | 
						|
        
 | 
						|
        if (mysql == NULL) {
 | 
						|
            fprintf(stderr,
 | 
						|
                    "mysql_real_connect failed, %d : %s\n",
 | 
						|
                    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:
 | 
						|
        //ss_dfprintf(stderr,
 | 
						|
        //            "<< skygw_query_classifier_get_type : %s\n",
 | 
						|
        //            STRQTYPE(qtype));
 | 
						|
        //ss_dfflush(stderr);
 | 
						|
        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_dfprintf(stderr, "> get_or_create_thd_for_parsing\n");
 | 
						|
        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) {
 | 
						|
            ss_dfprintf(stderr, "Couldn't create embedded thd\n");
 | 
						|
            goto return_thd;
 | 
						|
        }
 | 
						|
        mysql->thd = thd;
 | 
						|
        init_embedded_mysql(mysql, client_flags);
 | 
						|
        failp = check_embedded_connection(mysql, db);
 | 
						|
 | 
						|
        if (failp) {
 | 
						|
            ss_dfprintf(stderr, "Checking embedded connection failed.\n");
 | 
						|
            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);
 | 
						|
            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:
 | 
						|
        //ss_dfprintf(stderr, "< get_or_create_thd_for_parsing : %p\n", thd);
 | 
						|
        //ss_dfflush(stderr);
 | 
						|
        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;
 | 
						|
        //ss_dfprintf(stderr, "> set_client_flags\n");
 | 
						|
        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;
 | 
						|
        }
 | 
						|
        //ss_dfprintf(stderr, "< set_client_flags : %lu\n", f);
 | 
						|
        //ss_dfflush(stderr);
 | 
						|
        return f;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static bool create_parse_tree(
 | 
						|
        THD* thd)
 | 
						|
{
 | 
						|
        Parser_state parser_state;
 | 
						|
        bool         failp = FALSE;
 | 
						|
        const char*  virtual_db = "skygw_virtual";
 | 
						|
        //ss_dfprintf(stderr, "> create_parse_tree\n");
 | 
						|
        
 | 
						|
        if (parser_state.init(thd, thd->query(), thd->query_length())) {
 | 
						|
            failp = TRUE;
 | 
						|
            goto return_here;
 | 
						|
        }
 | 
						|
        mysql_reset_thd_for_next_command(thd, opt_userstat_running);
 | 
						|
        
 | 
						|
        /** Set some database to thd so that parsing won't fail because of
 | 
						|
         * missing database. Then parse. */
 | 
						|
        failp = thd->set_db(virtual_db, sizeof(virtual_db));
 | 
						|
 | 
						|
        if (failp) {
 | 
						|
            fprintf(stderr, "Setting database for thd failed\n");
 | 
						|
        }
 | 
						|
        failp = parse_sql(thd, &parser_state, NULL);
 | 
						|
 | 
						|
        if (failp) {
 | 
						|
            fprintf(stderr, "parse_sql failed\n");
 | 
						|
        }
 | 
						|
return_here:
 | 
						|
        //ss_dfprintf(stderr, "< create_parse_tree : %s\n", STRBOOL(failp));
 | 
						|
        //fflush(stderr);
 | 
						|
        return failp;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/** 
 | 
						|
 * @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;
 | 
						|
        LEX*               lex;
 | 
						|
        /**
 | 
						|
         * 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_dfprintf(stderr, "> resolve_query_type\n");
 | 
						|
        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) {
 | 
						|
            qtype = QUERY_TYPE_SESSION_WRITE;
 | 
						|
            goto return_here;
 | 
						|
        }
 | 
						|
        /**
 | 
						|
         * 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)
 | 
						|
            {
 | 
						|
                qtype = QUERY_TYPE_SESSION_WRITE;
 | 
						|
            } else {
 | 
						|
                qtype = QUERY_TYPE_WRITE;
 | 
						|
            }
 | 
						|
            
 | 
						|
            goto return_here;
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * REVOKE ALL, ASSIGN_TO_KEYCACHE,
 | 
						|
         * PRELOAD_KEYS, FLUSH, RESET, CREATE|ALTER|DROP SERVER
 | 
						|
         */
 | 
						|
        if (sql_command_flags[lex->sql_command] & CF_AUTO_COMMIT_TRANS) {
 | 
						|
            qtype =  QUERY_TYPE_SESSION_WRITE;
 | 
						|
            goto return_here;
 | 
						|
        }
 | 
						|
        
 | 
						|
        /** Try to catch session modifications here */
 | 
						|
        switch (lex->sql_command) {
 | 
						|
            case SQLCOM_CHANGE_DB:
 | 
						|
            case SQLCOM_SET_OPTION:
 | 
						|
                qtype = QUERY_TYPE_SESSION_WRITE;
 | 
						|
                break;
 | 
						|
 | 
						|
            case SQLCOM_SELECT:
 | 
						|
                qtype = QUERY_TYPE_READ;
 | 
						|
                break;
 | 
						|
 | 
						|
            case SQLCOM_CALL:
 | 
						|
                qtype = QUERY_TYPE_WRITE;
 | 
						|
                break;
 | 
						|
                
 | 
						|
            default:
 | 
						|
                break;
 | 
						|
        }
 | 
						|
return_here:
 | 
						|
        //ss_dfprintf(stderr, "< resolve_query_type : %s\n", STRQTYPE(qtype));
 | 
						|
        return qtype;
 | 
						|
}
 |