- Fields in CTEs are marked as being used in subselects. - In qc_mysqlembedded all selects must be walked if CTE is present. - In qc_sqlite unions need special handling.
		
			
				
	
	
		
			4115 lines
		
	
	
		
			117 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			4115 lines
		
	
	
		
			117 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2016 MariaDB Corporation Ab
 | 
						|
 *
 | 
						|
 * Use of this software is governed by the Business Source License included
 | 
						|
 * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 | 
						|
 *
 | 
						|
 * Change Date: 2020-01-01
 | 
						|
 *
 | 
						|
 * On the date above, in accordance with the Business Source License, use
 | 
						|
 * of this software will be governed by version 2 or later of the General
 | 
						|
 * Public License.
 | 
						|
 */
 | 
						|
 | 
						|
#define MXS_MODULE_NAME "qc_sqlite"
 | 
						|
#include <sqliteInt.h>
 | 
						|
 | 
						|
#include <signal.h>
 | 
						|
#include <string.h>
 | 
						|
#include <maxscale/alloc.h>
 | 
						|
#include <maxscale/log_manager.h>
 | 
						|
#include <maxscale/modinfo.h>
 | 
						|
#include <maxscale/modutil.h>
 | 
						|
#include <maxscale/platform.h>
 | 
						|
#include <maxscale/protocol/mysql.h>
 | 
						|
#include <maxscale/query_classifier.h>
 | 
						|
#include "builtin_functions.h"
 | 
						|
 | 
						|
//#define QC_TRACE_ENABLED
 | 
						|
#undef QC_TRACE_ENABLED
 | 
						|
 | 
						|
#if defined(QC_TRACE_ENABLED)
 | 
						|
#define QC_TRACE() MXS_NOTICE(__func__)
 | 
						|
#else
 | 
						|
#define QC_TRACE()
 | 
						|
#endif
 | 
						|
 | 
						|
static inline bool qc_info_is_valid(qc_parse_result_t status)
 | 
						|
{
 | 
						|
    return status != QC_QUERY_INVALID;
 | 
						|
}
 | 
						|
 | 
						|
static inline bool qc_info_was_tokenized(qc_parse_result_t status)
 | 
						|
{
 | 
						|
    return status == QC_QUERY_TOKENIZED;
 | 
						|
}
 | 
						|
 | 
						|
static inline bool qc_info_was_parsed(qc_parse_result_t status)
 | 
						|
{
 | 
						|
    return status == QC_QUERY_PARSED;
 | 
						|
}
 | 
						|
 | 
						|
typedef enum qc_log_level
 | 
						|
{
 | 
						|
    QC_LOG_NOTHING = 0,
 | 
						|
    QC_LOG_NON_PARSED,
 | 
						|
    QC_LOG_NON_PARTIALLY_PARSED,
 | 
						|
    QC_LOG_NON_TOKENIZED,
 | 
						|
} qc_log_level_t;
 | 
						|
 | 
						|
typedef enum qc_parse_as
 | 
						|
{
 | 
						|
    QC_PARSE_AS_DEFAULT, // Parse as embedded lib does before 10.3
 | 
						|
    QC_PARSE_AS_103      // Parse as embedded lib does in 10.3
 | 
						|
} qc_parse_as_t;
 | 
						|
 | 
						|
/**
 | 
						|
 * Defines what a particular name should be mapped to.
 | 
						|
 */
 | 
						|
typedef struct qc_name_mapping
 | 
						|
{
 | 
						|
    const char* from;
 | 
						|
    const char* to;
 | 
						|
} QC_NAME_MAPPING;
 | 
						|
 | 
						|
static QC_NAME_MAPPING function_name_mappings_default[] =
 | 
						|
{
 | 
						|
    { NULL, NULL }
 | 
						|
};
 | 
						|
 | 
						|
static QC_NAME_MAPPING function_name_mappings_103[] =
 | 
						|
{
 | 
						|
    { "now", "current_timestamp" },
 | 
						|
    { NULL, NULL }
 | 
						|
};
 | 
						|
 | 
						|
// NOTE: Duplicate the information from function_name_mappings_103 here.
 | 
						|
static QC_NAME_MAPPING function_name_mappings_oracle[] =
 | 
						|
{
 | 
						|
    { "now", "current_timestamp" },
 | 
						|
    { "nvl", "ifnull" },
 | 
						|
    { NULL, NULL }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Contains information about a particular query.
 | 
						|
 */
 | 
						|
typedef struct qc_sqlite_info
 | 
						|
{
 | 
						|
    qc_parse_result_t status;                // The validity of the information in this structure.
 | 
						|
    uint32_t collect;                        // What information should be collected.
 | 
						|
    uint32_t collected;                      // What information has been collected.
 | 
						|
    const char* query;                       // The query passed to sqlite.
 | 
						|
    size_t query_len;                        // The length of the query.
 | 
						|
 | 
						|
    uint32_t type_mask;                      // The type mask of the query.
 | 
						|
    qc_query_op_t operation;                 // The operation in question.
 | 
						|
    bool has_clause;                         // Has WHERE or HAVING.
 | 
						|
    char** table_names;                      // Array of table names used in the query.
 | 
						|
    size_t table_names_len;                  // The used entries in table_names.
 | 
						|
    size_t table_names_capacity;             // The capacity of table_names.
 | 
						|
    char** table_fullnames;                  // Array of full (i.e. qualified) table names used in the query.
 | 
						|
    size_t table_fullnames_len;              // The used entries in table_fullnames.
 | 
						|
    size_t table_fullnames_capacity;         // The capacity of table_fullnames.
 | 
						|
    char* created_table_name;                // The name of a created table.
 | 
						|
    bool is_drop_table;                      // Is the query a DROP TABLE.
 | 
						|
    char** database_names;                   // Array of database names used in the query.
 | 
						|
    size_t database_names_len;               // The used entries in database_names.
 | 
						|
    size_t database_names_capacity;          // The capacity of database_names.
 | 
						|
    int keyword_1;                           // The first encountered keyword.
 | 
						|
    int keyword_2;                           // The second encountered keyword.
 | 
						|
    char* prepare_name;                      // The name of a prepared statement.
 | 
						|
    GWBUF* preparable_stmt;                  // The preparable statement.
 | 
						|
    QC_FIELD_INFO *field_infos;              // Pointer to array of QC_FIELD_INFOs.
 | 
						|
    size_t field_infos_len;                  // The used entries in field_infos.
 | 
						|
    size_t field_infos_capacity;             // The capacity of the field_infos array.
 | 
						|
    QC_FUNCTION_INFO *function_infos;        // Pointer to array of QC_FUNCTION_INFOs.
 | 
						|
    size_t function_infos_len;               // The used entries in function_infos.
 | 
						|
    size_t function_infos_capacity;          // The capacity of the function_infos array.
 | 
						|
    bool initializing;                       // Whether we are initializing sqlite3.
 | 
						|
    qc_sql_mode_t sql_mode;                  // The current sql_mode.
 | 
						|
    QC_NAME_MAPPING* function_name_mappings; // How function names should be mapped.
 | 
						|
} QC_SQLITE_INFO;
 | 
						|
 | 
						|
/**
 | 
						|
 * The state of qc_sqlite.
 | 
						|
 */
 | 
						|
static struct
 | 
						|
{
 | 
						|
    bool initialized;
 | 
						|
    bool setup;
 | 
						|
    qc_log_level_t log_level;
 | 
						|
    qc_sql_mode_t sql_mode;
 | 
						|
    qc_parse_as_t parse_as;
 | 
						|
    QC_NAME_MAPPING* function_name_mappings;
 | 
						|
} this_unit;
 | 
						|
 | 
						|
/**
 | 
						|
 * The qc_sqlite thread-specific state.
 | 
						|
 */
 | 
						|
static thread_local struct
 | 
						|
{
 | 
						|
    bool initialized;                        // Whether the thread specific data has been initialized.
 | 
						|
    sqlite3* db;                             // Thread specific database handle.
 | 
						|
    qc_sql_mode_t sql_mode;                  // What sql_mode is used.
 | 
						|
    QC_SQLITE_INFO* info;                    // The information for the current statement being classified.
 | 
						|
    uint32_t version_major;
 | 
						|
    uint32_t version_minor;
 | 
						|
    uint32_t version_patch;
 | 
						|
    QC_NAME_MAPPING* function_name_mappings; // How function names should be mapped.
 | 
						|
} this_thread;
 | 
						|
 | 
						|
/**
 | 
						|
 * HELPERS
 | 
						|
 */
 | 
						|
 | 
						|
typedef enum qc_token_position
 | 
						|
{
 | 
						|
    QC_TOKEN_MIDDLE, // In the middle or irrelevant, e.g.: "=" in "a = b".
 | 
						|
    QC_TOKEN_LEFT,   // To the left, e.g.: "a" in "a = b".
 | 
						|
    QC_TOKEN_RIGHT,  // To the right, e.g: "b" in "a = b".
 | 
						|
} qc_token_position_t;
 | 
						|
 | 
						|
static void buffer_object_free(void* data);
 | 
						|
static char** copy_string_array(char** strings, int* pn);
 | 
						|
static void enlarge_string_array(size_t n, size_t len, char*** ppzStrings, size_t* pCapacity);
 | 
						|
static bool ensure_query_is_parsed(GWBUF* query, uint32_t collect);
 | 
						|
static void free_field_infos(QC_FIELD_INFO* infos, size_t n_infos);
 | 
						|
static void free_string_array(char** sa);
 | 
						|
static QC_SQLITE_INFO* get_query_info(GWBUF* query, uint32_t collect);
 | 
						|
static QC_SQLITE_INFO* info_alloc(uint32_t collect);
 | 
						|
static void info_finish(QC_SQLITE_INFO* info);
 | 
						|
static void info_free(QC_SQLITE_INFO* info);
 | 
						|
static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info, uint32_t collect);
 | 
						|
static bool is_sequence_related_field(QC_SQLITE_INFO* info,
 | 
						|
                                      const char* database,
 | 
						|
                                      const char* table,
 | 
						|
                                      const char* column);
 | 
						|
static bool is_sequence_related_function(QC_SQLITE_INFO* info, const char* func_name);
 | 
						|
static void log_invalid_data(GWBUF* query, const char* message);
 | 
						|
static const char* map_function_name(QC_NAME_MAPPING* function_name_mappings, const char* name);
 | 
						|
static bool parse_query(GWBUF* query, uint32_t collect);
 | 
						|
static void parse_query_string(const char* query, size_t len);
 | 
						|
static bool query_is_parsed(GWBUF* query, uint32_t collect);
 | 
						|
static bool should_exclude(const char* zName, const ExprList* pExclude);
 | 
						|
static void update_field_info(QC_SQLITE_INFO* info,
 | 
						|
                              const char* database,
 | 
						|
                              const char* table,
 | 
						|
                              const char* column,
 | 
						|
                              uint32_t usage,
 | 
						|
                              const ExprList* pExclude);
 | 
						|
static void update_field_infos_from_expr(QC_SQLITE_INFO* info,
 | 
						|
                                         const struct Expr* pExpr,
 | 
						|
                                         uint32_t usage,
 | 
						|
                                         const ExprList* pExclude);
 | 
						|
static void update_field_infos(QC_SQLITE_INFO* info,
 | 
						|
                               int prev_token,
 | 
						|
                               const Expr* pExpr,
 | 
						|
                               uint32_t usage,
 | 
						|
                               qc_token_position_t pos,
 | 
						|
                               const ExprList* pExclude);
 | 
						|
static void update_field_infos_from_exprlist(QC_SQLITE_INFO* info,
 | 
						|
                                             const ExprList* pEList,
 | 
						|
                                             uint32_t usage,
 | 
						|
                                             const ExprList* pExclude);
 | 
						|
static void update_field_infos_from_idlist(QC_SQLITE_INFO* info,
 | 
						|
                                           const IdList* pIds,
 | 
						|
                                           uint32_t usage,
 | 
						|
                                           const ExprList* pExclude);
 | 
						|
static void update_field_infos_from_select(QC_SQLITE_INFO* info,
 | 
						|
                                           const Select* pSelect,
 | 
						|
                                           uint32_t usage,
 | 
						|
                                           const ExprList* pExclude);
 | 
						|
static void update_field_infos_from_with(QC_SQLITE_INFO* info,
 | 
						|
                                          const With* pWith);
 | 
						|
static void update_function_info(QC_SQLITE_INFO* info,
 | 
						|
                                 const char* name,
 | 
						|
                                 uint32_t usage);
 | 
						|
static void update_database_names(QC_SQLITE_INFO* info, const char* name);
 | 
						|
static void update_names(QC_SQLITE_INFO* info, const char* zDatabase, const char* zTable);
 | 
						|
static void update_names_from_srclist(QC_SQLITE_INFO* info, const SrcList* pSrc);
 | 
						|
 | 
						|
// Defined in parse.y
 | 
						|
extern void exposed_sqlite3ExprDelete(sqlite3 *db, Expr *pExpr);
 | 
						|
extern void exposed_sqlite3ExprListDelete(sqlite3 *db, ExprList *pList);
 | 
						|
extern void exposed_sqlite3IdListDelete(sqlite3 *db, IdList *pList);
 | 
						|
extern void exposed_sqlite3SrcListDelete(sqlite3 *db, SrcList *pList);
 | 
						|
extern void exposed_sqlite3SelectDelete(sqlite3 *db, Select *p);
 | 
						|
 | 
						|
extern void exposed_sqlite3BeginTrigger(Parse *pParse,
 | 
						|
                                        Token *pName1,
 | 
						|
                                        Token *pName2,
 | 
						|
                                        int tr_tm,
 | 
						|
                                        int op,
 | 
						|
                                        IdList *pColumns,
 | 
						|
                                        SrcList *pTableName,
 | 
						|
                                        Expr *pWhen,
 | 
						|
                                        int isTemp,
 | 
						|
                                        int noErr);
 | 
						|
extern void exposed_sqlite3FinishTrigger(Parse *pParse,
 | 
						|
                                         TriggerStep *pStepList,
 | 
						|
                                         Token *pAll);
 | 
						|
extern int exposed_sqlite3Dequote(char *z);
 | 
						|
extern int exposed_sqlite3EndTable(Parse*, Token*, Token*, u8, Select*);
 | 
						|
extern int exposed_sqlite3Select(Parse* pParse, Select* p, SelectDest* pDest);
 | 
						|
extern void exposed_sqlite3StartTable(Parse *pParse,   /* Parser context */
 | 
						|
                                      Token *pName1,   /* First part of the name of the table or view */
 | 
						|
                                      Token *pName2,   /* Second part of the name of the table or view */
 | 
						|
                                      int isTemp,      /* True if this is a TEMP table */
 | 
						|
                                      int isView,      /* True if this is a VIEW */
 | 
						|
                                      int isVirtual,   /* True if this is a VIRTUAL table */
 | 
						|
                                      int noErr);      /* Do nothing if table already exists */
 | 
						|
extern void maxscaleCollectInfoFromSelect(Parse*, Select*, int);
 | 
						|
 | 
						|
extern void maxscale_update_function_info(const char* name, uint32_t usage);
 | 
						|
 | 
						|
/**
 | 
						|
 * Used for freeing a QC_SQLITE_INFO object added to a GWBUF.
 | 
						|
 *
 | 
						|
 * @param object A pointer to a QC_SQLITE_INFO object.
 | 
						|
 */
 | 
						|
static void buffer_object_free(void* data)
 | 
						|
{
 | 
						|
    info_free((QC_SQLITE_INFO*) data);
 | 
						|
}
 | 
						|
 | 
						|
static char** copy_string_array(char** strings, int* pn)
 | 
						|
{
 | 
						|
    size_t n = 0;
 | 
						|
 | 
						|
    char** ss = strings;
 | 
						|
    *pn = 0;
 | 
						|
 | 
						|
    while (*ss)
 | 
						|
    {
 | 
						|
        ++ss;
 | 
						|
        ++(*pn);
 | 
						|
    }
 | 
						|
 | 
						|
    ss = (char**) MXS_MALLOC((*pn + 1) * sizeof(char*));
 | 
						|
    MXS_ABORT_IF_NULL(ss);
 | 
						|
 | 
						|
    ss[*pn] = 0;
 | 
						|
 | 
						|
    for (int i = 0; i < *pn; ++i)
 | 
						|
    {
 | 
						|
        ss[i] = MXS_STRDUP(strings[i]);
 | 
						|
        MXS_ABORT_IF_NULL(ss[i]);
 | 
						|
    }
 | 
						|
 | 
						|
    return ss;
 | 
						|
}
 | 
						|
 | 
						|
static void enlarge_string_array(size_t n, size_t len, char*** ppzStrings, size_t* pCapacity)
 | 
						|
{
 | 
						|
    if (len + n >= *pCapacity)
 | 
						|
    {
 | 
						|
        int capacity = *pCapacity ? *pCapacity * 2 : 4;
 | 
						|
 | 
						|
        *ppzStrings = (char**) MXS_REALLOC(*ppzStrings, capacity * sizeof(char**));
 | 
						|
        MXS_ABORT_IF_NULL(*ppzStrings);
 | 
						|
        *pCapacity = capacity;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static bool ensure_query_is_parsed(GWBUF* query, uint32_t collect)
 | 
						|
{
 | 
						|
    bool parsed = query_is_parsed(query, collect);
 | 
						|
 | 
						|
    if (!parsed)
 | 
						|
    {
 | 
						|
        parsed = parse_query(query, collect);
 | 
						|
    }
 | 
						|
 | 
						|
    return parsed;
 | 
						|
}
 | 
						|
 | 
						|
static void free_field_infos(QC_FIELD_INFO* infos, size_t n_infos)
 | 
						|
{
 | 
						|
    if (infos)
 | 
						|
    {
 | 
						|
        for (int i = 0; i < n_infos; ++i)
 | 
						|
        {
 | 
						|
            MXS_FREE(infos[i].database);
 | 
						|
            MXS_FREE(infos[i].table);
 | 
						|
            MXS_FREE(infos[i].column);
 | 
						|
        }
 | 
						|
 | 
						|
        MXS_FREE(infos);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void free_function_infos(QC_FUNCTION_INFO* infos, size_t n_infos)
 | 
						|
{
 | 
						|
    if (infos)
 | 
						|
    {
 | 
						|
        for (int i = 0; i < n_infos; ++i)
 | 
						|
        {
 | 
						|
            MXS_FREE(infos[i].name);
 | 
						|
        }
 | 
						|
 | 
						|
        MXS_FREE(infos);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void free_string_array(char** sa)
 | 
						|
{
 | 
						|
    if (sa)
 | 
						|
    {
 | 
						|
        char** s = sa;
 | 
						|
 | 
						|
        while (*s)
 | 
						|
        {
 | 
						|
            free(*s);
 | 
						|
            ++s;
 | 
						|
        }
 | 
						|
 | 
						|
        free(sa);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static QC_SQLITE_INFO* get_query_info(GWBUF* query, uint32_t collect)
 | 
						|
{
 | 
						|
    QC_SQLITE_INFO* info = NULL;
 | 
						|
 | 
						|
    if (ensure_query_is_parsed(query, collect))
 | 
						|
    {
 | 
						|
        info = (QC_SQLITE_INFO*) gwbuf_get_buffer_object_data(query, GWBUF_PARSING_INFO);
 | 
						|
        ss_dassert(info);
 | 
						|
    }
 | 
						|
 | 
						|
    return info;
 | 
						|
}
 | 
						|
 | 
						|
static QC_SQLITE_INFO* info_alloc(uint32_t collect)
 | 
						|
{
 | 
						|
    QC_SQLITE_INFO* info = MXS_MALLOC(sizeof(*info));
 | 
						|
    MXS_ABORT_IF_NULL(info);
 | 
						|
 | 
						|
    info_init(info, collect);
 | 
						|
 | 
						|
    return info;
 | 
						|
}
 | 
						|
 | 
						|
static void info_finish(QC_SQLITE_INFO* info)
 | 
						|
{
 | 
						|
    free_string_array(info->table_names);
 | 
						|
    free_string_array(info->table_fullnames);
 | 
						|
    free(info->created_table_name);
 | 
						|
    free_string_array(info->database_names);
 | 
						|
    free(info->prepare_name);
 | 
						|
    gwbuf_free(info->preparable_stmt);
 | 
						|
    free_field_infos(info->field_infos, info->field_infos_len);
 | 
						|
    free_function_infos(info->function_infos, info->function_infos_len);
 | 
						|
}
 | 
						|
 | 
						|
static void info_free(QC_SQLITE_INFO* info)
 | 
						|
{
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        info_finish(info);
 | 
						|
        free(info);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info, uint32_t collect)
 | 
						|
{
 | 
						|
    memset(info, 0, sizeof(*info));
 | 
						|
 | 
						|
    info->status = QC_QUERY_INVALID;
 | 
						|
    info->collect = collect;
 | 
						|
    info->collected = 0;
 | 
						|
 | 
						|
    info->type_mask = QUERY_TYPE_UNKNOWN;
 | 
						|
    info->operation = QUERY_OP_UNDEFINED;
 | 
						|
    info->has_clause = false;
 | 
						|
    info->table_names = NULL;
 | 
						|
    info->table_names_len = 0;
 | 
						|
    info->table_names_capacity = 0;
 | 
						|
    info->table_fullnames = NULL;
 | 
						|
    info->table_fullnames_len = 0;
 | 
						|
    info->table_fullnames_capacity = 0;
 | 
						|
    info->created_table_name = NULL;
 | 
						|
    info->is_drop_table = false;
 | 
						|
    info->database_names = NULL;
 | 
						|
    info->database_names_len = 0;
 | 
						|
    info->database_names_capacity = 0;
 | 
						|
    info->keyword_1 = 0; // Sqlite3 starts numbering tokens from 1, so 0 means
 | 
						|
    info->keyword_2 = 0; // that we have not seen a keyword.
 | 
						|
    info->prepare_name = NULL;
 | 
						|
    info->preparable_stmt = NULL;
 | 
						|
    info->field_infos = NULL;
 | 
						|
    info->field_infos_len = 0;
 | 
						|
    info->field_infos_capacity = 0;
 | 
						|
    info->function_infos = NULL;
 | 
						|
    info->function_infos_len = 0;
 | 
						|
    info->function_infos_capacity = 0;
 | 
						|
    info->initializing = false;
 | 
						|
    info->sql_mode = this_thread.sql_mode;
 | 
						|
    info->function_name_mappings = this_thread.function_name_mappings;
 | 
						|
 | 
						|
    return info;
 | 
						|
}
 | 
						|
 | 
						|
static void parse_query_string(const char* query, size_t len)
 | 
						|
{
 | 
						|
    sqlite3_stmt* stmt = NULL;
 | 
						|
    const char* tail = NULL;
 | 
						|
 | 
						|
    ss_dassert(this_thread.db);
 | 
						|
    int rc = sqlite3_prepare(this_thread.db, query, len, &stmt, &tail);
 | 
						|
 | 
						|
    const int max_len = 512; // Maximum length of logged statement.
 | 
						|
    const int l = (len > max_len ? max_len : len);
 | 
						|
    const char* suffix = (len > max_len ? "..." : "");
 | 
						|
    const char* format;
 | 
						|
 | 
						|
    if (this_thread.info->operation == QUERY_OP_EXPLAIN)
 | 
						|
    {
 | 
						|
        this_thread.info->status = QC_QUERY_PARSED;
 | 
						|
    }
 | 
						|
 | 
						|
    if (rc != SQLITE_OK)
 | 
						|
    {
 | 
						|
        if (qc_info_was_tokenized(this_thread.info->status))
 | 
						|
        {
 | 
						|
            format =
 | 
						|
                "Statement was classified only based on keywords "
 | 
						|
                "(Sqlite3 error: %s, %s): \"%.*s%s\"";
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            if (qc_info_was_parsed(this_thread.info->status))
 | 
						|
            {
 | 
						|
                format =
 | 
						|
                    "Statement was only partially parsed "
 | 
						|
                    "(Sqlite3 error: %s, %s): \"%.*s%s\"";
 | 
						|
 | 
						|
                // The status was set to QC_QUERY_PARSED, but sqlite3 returned an
 | 
						|
                // error. Most likely, query contains some excess unrecognized stuff.
 | 
						|
                this_thread.info->status = QC_QUERY_PARTIALLY_PARSED;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                format =
 | 
						|
                    "Statement was neither parsed nor recognized from keywords "
 | 
						|
                    "(Sqlite3 error: %s, %s): \"%.*s%s\"";
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (this_unit.log_level > QC_LOG_NOTHING)
 | 
						|
        {
 | 
						|
            bool log_warning = false;
 | 
						|
 | 
						|
            switch (this_unit.log_level)
 | 
						|
            {
 | 
						|
            case QC_LOG_NON_PARSED:
 | 
						|
                log_warning = this_thread.info->status < QC_QUERY_PARSED;
 | 
						|
                break;
 | 
						|
 | 
						|
            case QC_LOG_NON_PARTIALLY_PARSED:
 | 
						|
                log_warning = this_thread.info->status < QC_QUERY_PARTIALLY_PARSED;
 | 
						|
                break;
 | 
						|
 | 
						|
            case QC_LOG_NON_TOKENIZED:
 | 
						|
                log_warning = this_thread.info->status < QC_QUERY_TOKENIZED;
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
                ss_dassert(!true);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            if (log_warning)
 | 
						|
            {
 | 
						|
                MXS_WARNING(format, sqlite3_errstr(rc), sqlite3_errmsg(this_thread.db), l, query, suffix);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (!this_thread.info->initializing) // If we are initializing, the query will not be classified.
 | 
						|
    {
 | 
						|
        if (this_unit.log_level > QC_LOG_NOTHING)
 | 
						|
        {
 | 
						|
            if (qc_info_was_tokenized(this_thread.info->status))
 | 
						|
            {
 | 
						|
                // This suggests a callback from the parser into this module is not made.
 | 
						|
                format =
 | 
						|
                    "Statement was classified only based on keywords, "
 | 
						|
                    "even though the statement was parsed: \"%.*s%s\"";
 | 
						|
 | 
						|
                MXS_WARNING(format, l, query, suffix);
 | 
						|
            }
 | 
						|
            else if (!qc_info_was_parsed(this_thread.info->status))
 | 
						|
            {
 | 
						|
                // This suggests there are keywords that should be recognized but are not,
 | 
						|
                // a tentative classification cannot be (or is not) made using the keywords
 | 
						|
                // seen and/or a callback from the parser into this module is not made.
 | 
						|
                format = "Statement was parsed, but not classified: \"%.*s%s\"";
 | 
						|
 | 
						|
                MXS_WARNING(format, l, query, suffix);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (stmt)
 | 
						|
    {
 | 
						|
        sqlite3_finalize(stmt);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static bool parse_query(GWBUF* query, uint32_t collect)
 | 
						|
{
 | 
						|
    bool parsed = false;
 | 
						|
    ss_dassert(!query_is_parsed(query, collect));
 | 
						|
 | 
						|
    if (GWBUF_IS_CONTIGUOUS(query))
 | 
						|
    {
 | 
						|
        uint8_t* data = (uint8_t*) GWBUF_DATA(query);
 | 
						|
 | 
						|
        if ((GWBUF_LENGTH(query) >= MYSQL_HEADER_LEN + 1) &&
 | 
						|
            (GWBUF_LENGTH(query) == MYSQL_HEADER_LEN + MYSQL_GET_PAYLOAD_LEN(data)))
 | 
						|
        {
 | 
						|
            uint8_t command = MYSQL_GET_COMMAND(data);
 | 
						|
 | 
						|
            if ((command == MYSQL_COM_QUERY) || (command == MYSQL_COM_STMT_PREPARE))
 | 
						|
            {
 | 
						|
                QC_SQLITE_INFO* info =
 | 
						|
                    (QC_SQLITE_INFO*) gwbuf_get_buffer_object_data(query, GWBUF_PARSING_INFO);
 | 
						|
 | 
						|
                if (info)
 | 
						|
                {
 | 
						|
                    ss_dassert((~info->collect & collect) != 0);
 | 
						|
                    ss_dassert((~info->collected & collect) != 0);
 | 
						|
 | 
						|
                    // If we get here, then the statement has been parsed once, but
 | 
						|
                    // not all needed was collected. Now we turn on all blinkenlichts to
 | 
						|
                    // ensure that a statement is parsed at most twice.
 | 
						|
                    info->collect = QC_COLLECT_ALL;
 | 
						|
 | 
						|
                    // We also reset the collected keywords, so that code that behaves
 | 
						|
                    // differently depending on whether keywords have been seem or not
 | 
						|
                    // acts the same way on this second round.
 | 
						|
                    info->keyword_1 = 0;
 | 
						|
                    info->keyword_2 = 0;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    info = info_alloc(collect);
 | 
						|
 | 
						|
                    if (info)
 | 
						|
                    {
 | 
						|
                        // TODO: Add return value to gwbuf_add_buffer_object.
 | 
						|
                        gwbuf_add_buffer_object(query, GWBUF_PARSING_INFO, info, buffer_object_free);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (info)
 | 
						|
                {
 | 
						|
                    this_thread.info = info;
 | 
						|
 | 
						|
                    size_t len = MYSQL_GET_PAYLOAD_LEN(data) - 1; // Subtract 1 for packet type byte.
 | 
						|
 | 
						|
                    const char* s = (const char*) &data[MYSQL_HEADER_LEN + 1];
 | 
						|
 | 
						|
                    this_thread.info->query = s;
 | 
						|
                    this_thread.info->query_len = len;
 | 
						|
                    parse_query_string(s, len);
 | 
						|
                    this_thread.info->query = NULL;
 | 
						|
                    this_thread.info->query_len = 0;
 | 
						|
 | 
						|
                    if (command == MYSQL_COM_STMT_PREPARE)
 | 
						|
                    {
 | 
						|
                        info->type_mask |= QUERY_TYPE_PREPARE_STMT;
 | 
						|
                    }
 | 
						|
 | 
						|
                    info->collected = info->collect;
 | 
						|
 | 
						|
                    parsed = true;
 | 
						|
 | 
						|
                    this_thread.info = NULL;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    MXS_ERROR("Could not allocate structure for containing parse data.");
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                MXS_ERROR("The provided buffer does not contain a COM_QUERY, but a %s.",
 | 
						|
                          STRPACKETTYPE(MYSQL_GET_COMMAND(data)));
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            MXS_ERROR("Packet size %u, provided buffer is %ld.",
 | 
						|
                      MYSQL_HEADER_LEN + MYSQL_GET_PAYLOAD_LEN(data),
 | 
						|
                      GWBUF_LENGTH(query));
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("Provided buffer is not contiguous.");
 | 
						|
    }
 | 
						|
 | 
						|
    return parsed;
 | 
						|
}
 | 
						|
 | 
						|
static bool query_is_parsed(GWBUF* query, uint32_t collect)
 | 
						|
{
 | 
						|
    bool rc = query && GWBUF_IS_PARSED(query);
 | 
						|
 | 
						|
    if (rc)
 | 
						|
    {
 | 
						|
        QC_SQLITE_INFO* info = (QC_SQLITE_INFO*) gwbuf_get_buffer_object_data(query, GWBUF_PARSING_INFO);
 | 
						|
        ss_dassert(info);
 | 
						|
 | 
						|
        if ((~info->collected & collect) != 0)
 | 
						|
        {
 | 
						|
            // The statement has been parsed once, but the needed information
 | 
						|
            // was not collected at that time.
 | 
						|
            rc = false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return rc;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns whether a field is sequence related.
 | 
						|
 *
 | 
						|
 * @param info      Current info object
 | 
						|
 * @param database  The database/schema or NULL.
 | 
						|
 * @param table     The table or NULL.
 | 
						|
 * @param column    The column.
 | 
						|
 *
 | 
						|
 * @return True, if the field is sequence related, false otherwise.
 | 
						|
 */
 | 
						|
static bool is_sequence_related_field(QC_SQLITE_INFO* info,
 | 
						|
                                      const char* database,
 | 
						|
                                      const char* table,
 | 
						|
                                      const char* column)
 | 
						|
{
 | 
						|
    return is_sequence_related_function(info, column);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns whether a function is sequence related.
 | 
						|
 *
 | 
						|
 * @param info       Current info object
 | 
						|
 * @param func_name  A function name.
 | 
						|
 *
 | 
						|
 * @return True, if the function is sequence related, false otherwise.
 | 
						|
 */
 | 
						|
static bool is_sequence_related_function(QC_SQLITE_INFO* info, const char* func_name)
 | 
						|
{
 | 
						|
    bool rv = false;
 | 
						|
 | 
						|
    if (info->sql_mode == QC_SQL_MODE_ORACLE)
 | 
						|
    {
 | 
						|
        // In Oracle mode we ignore the pseudocolumns "currval" and "nextval".
 | 
						|
        // We also exclude "lastval", the 10.3 equivalent of "currval".
 | 
						|
        if ((strcasecmp(func_name, "currval") == 0) ||
 | 
						|
            (strcasecmp(func_name, "nextval") == 0) ||
 | 
						|
            (strcasecmp(func_name, "lastval") == 0))
 | 
						|
        {
 | 
						|
            rv = true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!rv && (this_unit.parse_as == QC_PARSE_AS_103))
 | 
						|
    {
 | 
						|
        if ((strcasecmp(func_name, "lastval") == 0) ||
 | 
						|
            (strcasecmp(func_name, "nextval") == 0))
 | 
						|
        {
 | 
						|
            rv = true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Logs information about invalid data.
 | 
						|
 *
 | 
						|
 * @param query   The query that could not be parsed.
 | 
						|
 * @param message What is being asked for.
 | 
						|
 */
 | 
						|
static void log_invalid_data(GWBUF* query, const char* message)
 | 
						|
{
 | 
						|
    // At this point the query should be contiguous, but better safe than sorry.
 | 
						|
 | 
						|
    if (GWBUF_LENGTH(query) >= MYSQL_HEADER_LEN + 1)
 | 
						|
    {
 | 
						|
        char *sql;
 | 
						|
        int length;
 | 
						|
 | 
						|
        if (modutil_extract_SQL(query, &sql, &length))
 | 
						|
        {
 | 
						|
            if (length > GWBUF_LENGTH(query) - MYSQL_HEADER_LEN - 1)
 | 
						|
            {
 | 
						|
                length = GWBUF_LENGTH(query) - MYSQL_HEADER_LEN - 1;
 | 
						|
            }
 | 
						|
 | 
						|
            MXS_INFO("Parsing the query failed, %s: %*s", message, length, sql);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Map a function name to another.
 | 
						|
 *
 | 
						|
 * @param function_name_mappings  The name mapping to use.
 | 
						|
 * @param from                    The function name to map.
 | 
						|
 *
 | 
						|
 * @param The mapped name, or @c from if the name is not mapped.
 | 
						|
 */
 | 
						|
static const char* map_function_name(QC_NAME_MAPPING* function_name_mappings, const char* from)
 | 
						|
{
 | 
						|
    QC_NAME_MAPPING* map = function_name_mappings;
 | 
						|
    const char* to = NULL;
 | 
						|
 | 
						|
    while (!to && map->from)
 | 
						|
    {
 | 
						|
        if (strcasecmp(from, map->from) == 0)
 | 
						|
        {
 | 
						|
            to = map->to;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            ++map;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return to ? to : from;
 | 
						|
}
 | 
						|
 | 
						|
static bool should_exclude(const char* zName, const ExprList* pExclude)
 | 
						|
{
 | 
						|
    int i;
 | 
						|
    for (i = 0; i < pExclude->nExpr; ++i)
 | 
						|
    {
 | 
						|
        const struct ExprList_item* item = &pExclude->a[i];
 | 
						|
 | 
						|
        // zName will contain a possible alias name. If the alias name
 | 
						|
        // is referred to in e.g. in a having, it need to be excluded
 | 
						|
        // from the affected fields. It's not a real field.
 | 
						|
        if (item->zName && (strcasecmp(item->zName, zName) == 0))
 | 
						|
        {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        Expr* pExpr = item->pExpr;
 | 
						|
 | 
						|
        if (pExpr->op == TK_EQ)
 | 
						|
        {
 | 
						|
            // We end up here e.g with "UPDATE t set t.col = 5 ..."
 | 
						|
            // So, we pick the left branch.
 | 
						|
            pExpr = pExpr->pLeft;
 | 
						|
        }
 | 
						|
 | 
						|
        while (pExpr->op == TK_DOT)
 | 
						|
        {
 | 
						|
            pExpr = pExpr->pRight;
 | 
						|
        }
 | 
						|
 | 
						|
        if (pExpr->op == TK_ID)
 | 
						|
        {
 | 
						|
            // We need to ensure that we do not report fields where there
 | 
						|
            // is only a difference in case. E.g.
 | 
						|
            //     SELECT A FROM tbl WHERE a = "foo";
 | 
						|
            // Affected fields is "A" and not "A a".
 | 
						|
            if (strcasecmp(pExpr->u.zToken, zName) == 0)
 | 
						|
            {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return i != pExclude->nExpr;
 | 
						|
}
 | 
						|
 | 
						|
static void update_field_info(QC_SQLITE_INFO* info,
 | 
						|
                              const char* database,
 | 
						|
                              const char* table,
 | 
						|
                              const char* column,
 | 
						|
                              uint32_t usage,
 | 
						|
                              const ExprList* pExclude)
 | 
						|
{
 | 
						|
    ss_dassert(column);
 | 
						|
 | 
						|
    if (is_sequence_related_field(info, database, table, column))
 | 
						|
    {
 | 
						|
        info->type_mask |= QUERY_TYPE_WRITE;
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!(info->collect & QC_COLLECT_FIELDS) || (info->collected & QC_COLLECT_FIELDS))
 | 
						|
    {
 | 
						|
        // If field information should not be collected, or if field information
 | 
						|
        // has already been collected, we just return.
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    QC_FIELD_INFO item = { (char*)database, (char*)table, (char*)column, usage };
 | 
						|
 | 
						|
    int 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 && pExclude && should_exclude(column, pExclude)))
 | 
						|
        {
 | 
						|
            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 = MXS_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 ? MXS_STRDUP(item.database) : NULL;
 | 
						|
        item.table = item.table ? MXS_STRDUP(item.table) : NULL;
 | 
						|
        ss_dassert(item.column);
 | 
						|
        item.column = MXS_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 update_function_info(QC_SQLITE_INFO* info,
 | 
						|
                                 const char* name,
 | 
						|
                                 uint32_t usage)
 | 
						|
{
 | 
						|
    ss_dassert(name);
 | 
						|
 | 
						|
    if (!(info->collect & QC_COLLECT_FUNCTIONS) || (info->collected & QC_COLLECT_FUNCTIONS))
 | 
						|
    {
 | 
						|
        // If function information should not be collected, or if function information
 | 
						|
        // has already been collected, we just return.
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    name = map_function_name(info->function_name_mappings, name);
 | 
						|
 | 
						|
    QC_FUNCTION_INFO item = { (char*)name, usage };
 | 
						|
 | 
						|
    int i;
 | 
						|
    for (i = 0; i < info->function_infos_len; ++i)
 | 
						|
    {
 | 
						|
        QC_FUNCTION_INFO* function_info = info->function_infos + i;
 | 
						|
 | 
						|
        if (strcasecmp(item.name, function_info->name) == 0)
 | 
						|
        {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    QC_FUNCTION_INFO* function_infos = NULL;
 | 
						|
 | 
						|
    if (i == info->function_infos_len) // If true, the function was not present already.
 | 
						|
    {
 | 
						|
        if (info->function_infos_len < info->function_infos_capacity)
 | 
						|
        {
 | 
						|
            function_infos = info->function_infos;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            size_t capacity = info->function_infos_capacity ? 2 * info->function_infos_capacity : 8;
 | 
						|
            function_infos = MXS_REALLOC(info->function_infos, capacity * sizeof(QC_FUNCTION_INFO));
 | 
						|
 | 
						|
            if (function_infos)
 | 
						|
            {
 | 
						|
                info->function_infos = function_infos;
 | 
						|
                info->function_infos_capacity = capacity;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        info->function_infos[i].usage |= usage;
 | 
						|
    }
 | 
						|
 | 
						|
    // If function_infos is NULL, then the function was found and has already been noted.
 | 
						|
    if (function_infos)
 | 
						|
    {
 | 
						|
        ss_dassert(item.name);
 | 
						|
        item.name = MXS_STRDUP(item.name);
 | 
						|
 | 
						|
        if (item.name)
 | 
						|
        {
 | 
						|
            function_infos[info->function_infos_len++] = item;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
extern void maxscale_update_function_info(const char* name, uint32_t usage)
 | 
						|
{
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
 | 
						|
    update_function_info(info, name, usage);
 | 
						|
}
 | 
						|
 | 
						|
static void update_field_infos_from_expr(QC_SQLITE_INFO* info,
 | 
						|
                                         const struct Expr* pExpr,
 | 
						|
                                         uint32_t usage,
 | 
						|
                                         const ExprList* pExclude)
 | 
						|
{
 | 
						|
    QC_FIELD_INFO item = {};
 | 
						|
 | 
						|
    if (pExpr->op == TK_ASTERISK)
 | 
						|
    {
 | 
						|
        item.column = "*";
 | 
						|
    }
 | 
						|
    else if (pExpr->op == TK_ID)
 | 
						|
    {
 | 
						|
        // select a from...
 | 
						|
        item.column = pExpr->u.zToken;
 | 
						|
    }
 | 
						|
    else if (pExpr->op == TK_DOT)
 | 
						|
    {
 | 
						|
        if (pExpr->pLeft->op == TK_ID &&
 | 
						|
            (pExpr->pRight->op == TK_ID || pExpr->pRight->op == TK_ASTERISK))
 | 
						|
        {
 | 
						|
            // select a.b from...
 | 
						|
            item.table = pExpr->pLeft->u.zToken;
 | 
						|
            if (pExpr->pRight->op == TK_ID)
 | 
						|
            {
 | 
						|
                item.column = pExpr->pRight->u.zToken;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                item.column = "*";
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if (pExpr->pLeft->op == TK_ID &&
 | 
						|
                 pExpr->pRight->op == TK_DOT &&
 | 
						|
                 pExpr->pRight->pLeft->op == TK_ID &&
 | 
						|
                 (pExpr->pRight->pRight->op == TK_ID || pExpr->pRight->pRight->op == TK_ASTERISK))
 | 
						|
        {
 | 
						|
            // select a.b.c from...
 | 
						|
            item.database = pExpr->pLeft->u.zToken;
 | 
						|
            item.table = pExpr->pRight->pLeft->u.zToken;
 | 
						|
            if (pExpr->pRight->pRight->op == TK_ID)
 | 
						|
            {
 | 
						|
                item.column = pExpr->pRight->pRight->u.zToken;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                item.column = "*";
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (item.column)
 | 
						|
    {
 | 
						|
        bool should_update = true;
 | 
						|
 | 
						|
        if ((pExpr->flags & EP_DblQuoted) == 0)
 | 
						|
        {
 | 
						|
            if ((strcasecmp(item.column, "true") == 0) || (strcasecmp(item.column, "false") == 0))
 | 
						|
            {
 | 
						|
                should_update = false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (should_update)
 | 
						|
        {
 | 
						|
            update_field_info(info, item.database, item.table, item.column, usage, pExclude);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void update_field_infos_from_with(QC_SQLITE_INFO* info,
 | 
						|
                                         const With* pWith)
 | 
						|
{
 | 
						|
    for (int i = 0; i < pWith->nCte; ++i)
 | 
						|
    {
 | 
						|
        const struct Cte* pCte = &pWith->a[i];
 | 
						|
 | 
						|
        if (pCte->pSelect)
 | 
						|
        {
 | 
						|
            update_field_infos_from_select(info, pCte->pSelect, QC_USED_IN_SUBSELECT, NULL);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static const char* get_token_symbol(int token)
 | 
						|
{
 | 
						|
    switch (token)
 | 
						|
    {
 | 
						|
    case TK_EQ:
 | 
						|
        return "=";
 | 
						|
 | 
						|
    case TK_GE:
 | 
						|
        return ">=";
 | 
						|
 | 
						|
    case TK_GT:
 | 
						|
        return ">";
 | 
						|
 | 
						|
    case TK_LE:
 | 
						|
        return "<=";
 | 
						|
 | 
						|
    case TK_LT:
 | 
						|
        return "<";
 | 
						|
 | 
						|
    case TK_NE:
 | 
						|
        return "<>";
 | 
						|
 | 
						|
 | 
						|
    case TK_BETWEEN:
 | 
						|
        return "between";
 | 
						|
 | 
						|
    case TK_BITAND:
 | 
						|
        return "&";
 | 
						|
 | 
						|
    case TK_BITOR:
 | 
						|
        return "|";
 | 
						|
 | 
						|
    case TK_CASE:
 | 
						|
        return "case";
 | 
						|
 | 
						|
    case TK_IN:
 | 
						|
        return "in";
 | 
						|
 | 
						|
    case TK_ISNULL:
 | 
						|
        return "isnull";
 | 
						|
 | 
						|
    case TK_MINUS:
 | 
						|
        return "-";
 | 
						|
 | 
						|
    case TK_NOTNULL:
 | 
						|
        return "isnotnull";
 | 
						|
 | 
						|
    case TK_PLUS:
 | 
						|
        return "+";
 | 
						|
 | 
						|
    case TK_REM:
 | 
						|
        return "%";
 | 
						|
 | 
						|
    case TK_SLASH:
 | 
						|
        return "/";
 | 
						|
 | 
						|
    case TK_STAR:
 | 
						|
        return "*";
 | 
						|
 | 
						|
    case TK_UMINUS:
 | 
						|
        return "-";
 | 
						|
 | 
						|
    default:
 | 
						|
        ss_dassert(!true);
 | 
						|
        return "";
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void update_field_infos(QC_SQLITE_INFO* info,
 | 
						|
                               int prev_token,
 | 
						|
                               const Expr* pExpr,
 | 
						|
                               uint32_t usage,
 | 
						|
                               qc_token_position_t pos,
 | 
						|
                               const ExprList* pExclude)
 | 
						|
{
 | 
						|
    const Expr* pLeft = pExpr->pLeft;
 | 
						|
    const Expr* pRight = pExpr->pRight;
 | 
						|
    const char* zToken = pExpr->u.zToken;
 | 
						|
 | 
						|
    bool ignore_exprlist = false;
 | 
						|
 | 
						|
    switch (pExpr->op)
 | 
						|
    {
 | 
						|
    case TK_ASTERISK: // select *
 | 
						|
        update_field_infos_from_expr(info, pExpr, usage, pExclude);
 | 
						|
        break;
 | 
						|
 | 
						|
    case TK_DOT: // select a.b ... select a.b.c
 | 
						|
        update_field_infos_from_expr(info, pExpr, usage, pExclude);
 | 
						|
        break;
 | 
						|
 | 
						|
    case TK_ID: // select a
 | 
						|
        update_field_infos_from_expr(info, pExpr, usage, pExclude);
 | 
						|
        break;
 | 
						|
 | 
						|
    case TK_VARIABLE:
 | 
						|
        {
 | 
						|
            if (zToken[0] == '@')
 | 
						|
            {
 | 
						|
                if (zToken[1] == '@')
 | 
						|
                {
 | 
						|
                    if ((prev_token == TK_EQ) && (pos == QC_TOKEN_LEFT))
 | 
						|
                    {
 | 
						|
                        info->type_mask |= QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        if ((strcasecmp(&zToken[2], "identity") == 0) ||
 | 
						|
                            (strcasecmp(&zToken[2], "last_insert_id") == 0))
 | 
						|
                        {
 | 
						|
                            info->type_mask |= QUERY_TYPE_MASTER_READ;
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        {
 | 
						|
                            info->type_mask |= QUERY_TYPE_SYSVAR_READ;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    if ((prev_token == TK_EQ) && (pos == QC_TOKEN_LEFT))
 | 
						|
                    {
 | 
						|
                        info->type_mask |= QUERY_TYPE_USERVAR_WRITE;
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        info->type_mask |= QUERY_TYPE_USERVAR_READ;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else if (zToken[0] != '?')
 | 
						|
            {
 | 
						|
                MXS_WARNING("%s reported as VARIABLE.", zToken);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        MXS_DEBUG("Token %d not handled explicitly.", pExpr->op);
 | 
						|
    // Fallthrough intended.
 | 
						|
    case TK_BETWEEN:
 | 
						|
    case TK_CASE:
 | 
						|
    case TK_EXISTS:
 | 
						|
    case TK_FUNCTION:
 | 
						|
    case TK_IN:
 | 
						|
    case TK_SELECT:
 | 
						|
        switch (pExpr->op)
 | 
						|
        {
 | 
						|
        case TK_EQ:
 | 
						|
            // We don't report "=" if it's not used in a specific context (SELECT, WHERE)
 | 
						|
            // and if it is used in SET. We also exclude it it in a context where a
 | 
						|
            // variable is set.
 | 
						|
            if (((usage != 0) && (usage != QC_USED_IN_SET)) &&
 | 
						|
                (!pExpr->pLeft || (pExpr->pLeft->op != TK_VARIABLE)))
 | 
						|
            {
 | 
						|
                update_function_info(info, get_token_symbol(pExpr->op), usage);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_GE:
 | 
						|
        case TK_GT:
 | 
						|
        case TK_LE:
 | 
						|
        case TK_LT:
 | 
						|
        case TK_NE:
 | 
						|
 | 
						|
        case TK_BETWEEN:
 | 
						|
        case TK_BITAND:
 | 
						|
        case TK_BITOR:
 | 
						|
        case TK_CASE:
 | 
						|
        case TK_IN:
 | 
						|
        case TK_ISNULL:
 | 
						|
        case TK_MINUS:
 | 
						|
        case TK_NOTNULL:
 | 
						|
        case TK_PLUS:
 | 
						|
        case TK_SLASH:
 | 
						|
        case TK_STAR:
 | 
						|
            update_function_info(info, get_token_symbol(pExpr->op), usage);
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_REM:
 | 
						|
            if (info->sql_mode == QC_SQL_MODE_ORACLE)
 | 
						|
            {
 | 
						|
                if ((pLeft && (pLeft->op == TK_ID)) &&
 | 
						|
                    (pRight && (pRight->op == TK_ID)) &&
 | 
						|
                    (strcasecmp(pLeft->u.zToken, "sql") == 0) &&
 | 
						|
                    (strcasecmp(pRight->u.zToken, "rowcount") == 0))
 | 
						|
                {
 | 
						|
                    char sqlrowcount[13]; // strlen("sql") + strlen("%") + strlen("rowcount") + 1
 | 
						|
                    sprintf(sqlrowcount, "%s%%%s", pLeft->u.zToken, pRight->u.zToken);
 | 
						|
 | 
						|
                    update_function_info(info, sqlrowcount, usage);
 | 
						|
 | 
						|
                    pLeft = NULL;
 | 
						|
                    pRight = NULL;
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    update_function_info(info, get_token_symbol(pExpr->op), usage);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                update_function_info(info, get_token_symbol(pExpr->op), usage);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_UMINUS:
 | 
						|
            switch (this_unit.parse_as)
 | 
						|
            {
 | 
						|
            case QC_PARSE_AS_DEFAULT:
 | 
						|
                update_function_info(info, get_token_symbol(pExpr->op), usage);
 | 
						|
                break;
 | 
						|
 | 
						|
            case QC_PARSE_AS_103:
 | 
						|
                // In MariaDB 10.3 a unary minus is not considered a function.
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
                ss_dassert(!true);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_FUNCTION:
 | 
						|
            if (zToken)
 | 
						|
            {
 | 
						|
                if (strcasecmp(zToken, "last_insert_id") == 0)
 | 
						|
                {
 | 
						|
                    info->type_mask |= (QUERY_TYPE_READ | QUERY_TYPE_MASTER_READ);
 | 
						|
                }
 | 
						|
                else if (is_sequence_related_function(info, zToken))
 | 
						|
                {
 | 
						|
                    info->type_mask |= QUERY_TYPE_WRITE;
 | 
						|
                    ignore_exprlist = true;
 | 
						|
                }
 | 
						|
                else if (!is_builtin_readonly_function(zToken,
 | 
						|
                                                       this_thread.version_major,
 | 
						|
                                                       this_thread.version_minor,
 | 
						|
                                                       this_thread.version_patch,
 | 
						|
                                                       info->sql_mode == QC_SQL_MODE_ORACLE))
 | 
						|
                {
 | 
						|
                    info->type_mask |= QUERY_TYPE_WRITE;
 | 
						|
                }
 | 
						|
 | 
						|
                // We exclude "row", because we cannot detect all rows the same
 | 
						|
                // way qc_mysqlembedded does.
 | 
						|
                if (!ignore_exprlist && (strcasecmp(zToken, "row") != 0))
 | 
						|
                {
 | 
						|
                    update_function_info(info, zToken, usage);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        if (pLeft)
 | 
						|
        {
 | 
						|
            update_field_infos(info, pExpr->op, pExpr->pLeft, usage, QC_TOKEN_LEFT, pExclude);
 | 
						|
        }
 | 
						|
 | 
						|
        if (pRight)
 | 
						|
        {
 | 
						|
            if (usage & QC_USED_IN_SET)
 | 
						|
            {
 | 
						|
                usage &= ~QC_USED_IN_SET;
 | 
						|
            }
 | 
						|
 | 
						|
            update_field_infos(info, pExpr->op, pExpr->pRight, usage, QC_TOKEN_RIGHT, pExclude);
 | 
						|
        }
 | 
						|
 | 
						|
        if (pExpr->x.pList)
 | 
						|
        {
 | 
						|
            switch (pExpr->op)
 | 
						|
            {
 | 
						|
            case TK_BETWEEN:
 | 
						|
            case TK_CASE:
 | 
						|
            case TK_FUNCTION:
 | 
						|
                if (!ignore_exprlist)
 | 
						|
                {
 | 
						|
                    update_field_infos_from_exprlist(info, pExpr->x.pList, usage, pExclude);
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
            case TK_EXISTS:
 | 
						|
            case TK_IN:
 | 
						|
            case TK_SELECT:
 | 
						|
                if (pExpr->flags & EP_xIsSelect)
 | 
						|
                {
 | 
						|
                    uint32_t sub_usage = usage;
 | 
						|
 | 
						|
                    sub_usage &= ~QC_USED_IN_SELECT;
 | 
						|
                    sub_usage |= QC_USED_IN_SUBSELECT;
 | 
						|
                    update_field_infos_from_select(info, pExpr->x.pSelect, sub_usage, pExclude);
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    update_field_infos_from_exprlist(info, pExpr->x.pList, usage, pExclude);
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void update_field_infos_from_exprlist(QC_SQLITE_INFO* info,
 | 
						|
                                             const ExprList* pEList,
 | 
						|
                                             uint32_t usage,
 | 
						|
                                             const ExprList* pExclude)
 | 
						|
{
 | 
						|
    for (int i = 0; i < pEList->nExpr; ++i)
 | 
						|
    {
 | 
						|
        struct ExprList_item* pItem = &pEList->a[i];
 | 
						|
 | 
						|
        update_field_infos(info, 0, pItem->pExpr, usage, QC_TOKEN_MIDDLE, pExclude);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void update_field_infos_from_idlist(QC_SQLITE_INFO* info,
 | 
						|
                                           const IdList* pIds,
 | 
						|
                                           uint32_t usage,
 | 
						|
                                           const ExprList* pExclude)
 | 
						|
{
 | 
						|
    for (int i = 0; i < pIds->nId; ++i)
 | 
						|
    {
 | 
						|
        struct IdList_item* pItem = &pIds->a[i];
 | 
						|
 | 
						|
        update_field_info(info, NULL, NULL, pItem->zName, usage, pExclude);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void update_field_infos_from_select(QC_SQLITE_INFO* info,
 | 
						|
                                           const Select* pSelect,
 | 
						|
                                           uint32_t usage,
 | 
						|
                                           const ExprList* pExclude)
 | 
						|
{
 | 
						|
    if (pSelect->pSrc)
 | 
						|
    {
 | 
						|
        const SrcList* pSrc = pSelect->pSrc;
 | 
						|
 | 
						|
        for (int i = 0; i < pSrc->nSrc; ++i)
 | 
						|
        {
 | 
						|
            if (pSrc->a[i].zName)
 | 
						|
            {
 | 
						|
                update_names(info, pSrc->a[i].zDatabase, pSrc->a[i].zName);
 | 
						|
            }
 | 
						|
 | 
						|
            if (pSrc->a[i].pSelect)
 | 
						|
            {
 | 
						|
                uint32_t sub_usage = usage;
 | 
						|
 | 
						|
                sub_usage &= ~QC_USED_IN_SELECT;
 | 
						|
                sub_usage |= QC_USED_IN_SUBSELECT;
 | 
						|
 | 
						|
                update_field_infos_from_select(info, pSrc->a[i].pSelect, sub_usage, pExclude);
 | 
						|
            }
 | 
						|
 | 
						|
#ifdef QC_COLLECT_NAMES_FROM_USING
 | 
						|
            // With this enabled, the affected fields of
 | 
						|
            //    select * from (t1 as t2 left join t1 as t3 using (a)), t1;
 | 
						|
            // will be "* a", otherwise "*". However, that "a" is used in the join
 | 
						|
            // does not reveal its value, right?
 | 
						|
            if (pSrc->a[i].pUsing)
 | 
						|
            {
 | 
						|
                update_field_infos_from_idlist(info, pSrc->a[i].pUsing, 0, pSelect->pEList);
 | 
						|
            }
 | 
						|
#endif
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (pSelect->pEList)
 | 
						|
    {
 | 
						|
        update_field_infos_from_exprlist(info, pSelect->pEList, usage, NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    if (pSelect->pWhere)
 | 
						|
    {
 | 
						|
        info->has_clause = true;
 | 
						|
        update_field_infos(info, 0, pSelect->pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pSelect->pEList);
 | 
						|
    }
 | 
						|
 | 
						|
    if (pSelect->pGroupBy)
 | 
						|
    {
 | 
						|
        update_field_infos_from_exprlist(info, pSelect->pGroupBy, QC_USED_IN_GROUP_BY, pSelect->pEList);
 | 
						|
    }
 | 
						|
 | 
						|
    if (pSelect->pHaving)
 | 
						|
    {
 | 
						|
        info->has_clause = true;
 | 
						|
#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.
 | 
						|
        update_field_infos(info, 0, pSelect->pHaving, 0, QC_TOKEN_MIDDLE, pSelect->pEList);
 | 
						|
#endif
 | 
						|
    }
 | 
						|
 | 
						|
    if (pSelect->pWith)
 | 
						|
    {
 | 
						|
        update_field_infos_from_with(info, pSelect->pWith);
 | 
						|
    }
 | 
						|
 | 
						|
    if ((pSelect->op == TK_UNION) && pSelect->pPrior)
 | 
						|
    {
 | 
						|
        update_field_infos_from_select(info, pSelect->pPrior, usage, pExclude);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void update_database_names(QC_SQLITE_INFO* info, const char* zDatabase)
 | 
						|
{
 | 
						|
    char* zCopy = MXS_STRDUP(zDatabase);
 | 
						|
    MXS_ABORT_IF_NULL(zCopy);
 | 
						|
    exposed_sqlite3Dequote(zCopy);
 | 
						|
 | 
						|
    enlarge_string_array(1, info->database_names_len,
 | 
						|
                         &info->database_names, &info->database_names_capacity);
 | 
						|
    info->database_names[info->database_names_len++] = zCopy;
 | 
						|
    info->database_names[info->database_names_len] = NULL;
 | 
						|
}
 | 
						|
 | 
						|
static void update_names(QC_SQLITE_INFO* info, const char* zDatabase, const char* zTable)
 | 
						|
{
 | 
						|
    if ((info->collect & QC_COLLECT_TABLES) && !(info->collected & QC_COLLECT_TABLES))
 | 
						|
    {
 | 
						|
        if (strcasecmp(zTable, "DUAL") != 0)
 | 
						|
        {
 | 
						|
            char* zCopy = MXS_STRDUP(zTable);
 | 
						|
            MXS_ABORT_IF_NULL(zCopy);
 | 
						|
            // TODO: Is this call really needed. Check also sqlite3Dequote.
 | 
						|
            exposed_sqlite3Dequote(zCopy);
 | 
						|
 | 
						|
            enlarge_string_array(1, info->table_names_len, &info->table_names, &info->table_names_capacity);
 | 
						|
            info->table_names[info->table_names_len++] = zCopy;
 | 
						|
            info->table_names[info->table_names_len] = NULL;
 | 
						|
 | 
						|
            if (zDatabase)
 | 
						|
            {
 | 
						|
                zCopy = MXS_MALLOC(strlen(zDatabase) + 1 + strlen(zTable) + 1);
 | 
						|
                MXS_ABORT_IF_NULL(zCopy);
 | 
						|
 | 
						|
                strcpy(zCopy, zDatabase);
 | 
						|
                strcat(zCopy, ".");
 | 
						|
                strcat(zCopy, zTable);
 | 
						|
                exposed_sqlite3Dequote(zCopy);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                zCopy = MXS_STRDUP(zCopy);
 | 
						|
                MXS_ABORT_IF_NULL(zCopy);
 | 
						|
            }
 | 
						|
 | 
						|
            enlarge_string_array(1, info->table_fullnames_len,
 | 
						|
                                 &info->table_fullnames, &info->table_fullnames_capacity);
 | 
						|
            info->table_fullnames[info->table_fullnames_len++] = zCopy;
 | 
						|
            info->table_fullnames[info->table_fullnames_len] = NULL;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if ((info->collect & QC_COLLECT_DATABASES) && !(info->collected & QC_COLLECT_DATABASES))
 | 
						|
    {
 | 
						|
        if (zDatabase)
 | 
						|
        {
 | 
						|
            update_database_names(info, zDatabase);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void update_names_from_srclist(QC_SQLITE_INFO* info, const SrcList* pSrc)
 | 
						|
{
 | 
						|
    for (int i = 0; i < pSrc->nSrc; ++i)
 | 
						|
    {
 | 
						|
        if (pSrc->a[i].zName)
 | 
						|
        {
 | 
						|
            update_names(info, pSrc->a[i].zDatabase, pSrc->a[i].zName);
 | 
						|
        }
 | 
						|
 | 
						|
        if (pSrc->a[i].pSelect && pSrc->a[i].pSelect->pSrc)
 | 
						|
        {
 | 
						|
            update_names_from_srclist(info, pSrc->a[i].pSelect->pSrc);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 *
 | 
						|
 * SQLITE
 | 
						|
 *
 | 
						|
 * These functions are called from sqlite.
 | 
						|
 */
 | 
						|
 | 
						|
void mxs_sqlite3AlterFinishAddColumn(Parse* pParse, Token* pToken)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
    info->operation = QUERY_OP_ALTER;
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3AlterBeginAddColumn(Parse* pParse, SrcList* pSrcList)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    update_names_from_srclist(info, pSrcList);
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pSrcList);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3Analyze(Parse* pParse, SrcList* pSrcList)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
 | 
						|
    update_names_from_srclist(info, pSrcList);
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pSrcList);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3BeginTransaction(Parse* pParse, int token, int type)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    if ((info->sql_mode != QC_SQL_MODE_ORACLE) || (token == TK_START))
 | 
						|
    {
 | 
						|
        info->status = QC_QUERY_PARSED;
 | 
						|
        info->type_mask = QUERY_TYPE_BEGIN_TRX | type;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3BeginTrigger(Parse *pParse,      /* The parse context of the CREATE TRIGGER statement */
 | 
						|
                             Token *pName1,      /* The name of the trigger */
 | 
						|
                             Token *pName2,      /* The name of the trigger */
 | 
						|
                             int tr_tm,          /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */
 | 
						|
                             int op,             /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
 | 
						|
                             IdList *pColumns,   /* column list if this is an UPDATE OF trigger */
 | 
						|
                             SrcList *pTableName,/* The name of the table/view the trigger applies to */
 | 
						|
                             Expr *pWhen,        /* WHEN clause */
 | 
						|
                             int isTemp,         /* True if the TEMPORARY keyword is present */
 | 
						|
                             int noErr)          /* Suppress errors if the trigger already exists */
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
 | 
						|
    if (pTableName)
 | 
						|
    {
 | 
						|
        for (int i = 0; i < pTableName->nAlloc; ++i)
 | 
						|
        {
 | 
						|
            struct SrcList_item* pItem = &pTableName->a[i];
 | 
						|
 | 
						|
            if (pItem->zName)
 | 
						|
            {
 | 
						|
                update_names(info, pItem->zDatabase, pItem->zName);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // We need to call this, otherwise finish trigger will not be called.
 | 
						|
    exposed_sqlite3BeginTrigger(pParse, pName1, pName2, tr_tm, op, pColumns,
 | 
						|
                                pTableName, pWhen, isTemp, noErr);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3CommitTransaction(Parse* pParse)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_COMMIT;
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3CreateIndex(Parse *pParse,     /* All information about this parse */
 | 
						|
                            Token *pName1,     /* First part of index name. May be NULL */
 | 
						|
                            Token *pName2,     /* Second part of index name. May be NULL */
 | 
						|
                            SrcList *pTblName, /* Table to index. Use pParse->pNewTable if 0 */
 | 
						|
                            ExprList *pList,   /* A list of columns to be indexed */
 | 
						|
                            int onError,       /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
 | 
						|
                            Token *pStart,     /* The CREATE token that begins this statement */
 | 
						|
                            Expr *pPIWhere,    /* WHERE clause for partial indices */
 | 
						|
                            int sortOrder,     /* Sort order of primary key when pList==NULL */
 | 
						|
                            int ifNotExist)    /* Omit error if index already exists */
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
    info->operation = QUERY_OP_CREATE;
 | 
						|
 | 
						|
    if (pTblName)
 | 
						|
    {
 | 
						|
        update_names_from_srclist(info, pTblName);
 | 
						|
    }
 | 
						|
    else if (pParse->pNewTable)
 | 
						|
    {
 | 
						|
        update_names(info, NULL, pParse->pNewTable->zName);
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3ExprDelete(pParse->db, pPIWhere);
 | 
						|
    exposed_sqlite3ExprListDelete(pParse->db, pList);
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pTblName);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3CreateView(Parse *pParse,     /* The parsing context */
 | 
						|
                           Token *pBegin,     /* The CREATE token that begins the statement */
 | 
						|
                           Token *pName1,     /* The token that holds the name of the view */
 | 
						|
                           Token *pName2,     /* The token that holds the name of the view */
 | 
						|
                           ExprList *pCNames, /* Optional list of view column names */
 | 
						|
                           Select *pSelect,   /* A SELECT statement that will become the new view */
 | 
						|
                           int isTemp,        /* TRUE for a TEMPORARY view */
 | 
						|
                           int noErr)         /* Suppress error messages if VIEW already exists */
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
    info->operation = QUERY_OP_CREATE;
 | 
						|
 | 
						|
    const Token* pName = pName2->z ? pName2 : pName1;
 | 
						|
    const Token* pDatabase = pName2->z ? pName1 : NULL;
 | 
						|
 | 
						|
    char name[pName->n + 1];
 | 
						|
    strncpy(name, pName->z, pName->n);
 | 
						|
    name[pName->n] = 0;
 | 
						|
 | 
						|
    if (pDatabase)
 | 
						|
    {
 | 
						|
        char database[pDatabase->n + 1];
 | 
						|
        strncpy(database, pDatabase->z, pDatabase->n);
 | 
						|
        database[pDatabase->n] = 0;
 | 
						|
 | 
						|
        update_names(info, database, name);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        update_names(info, NULL, name);
 | 
						|
    }
 | 
						|
 | 
						|
    if (pSelect)
 | 
						|
    {
 | 
						|
        update_field_infos_from_select(info, pSelect, QC_USED_IN_SELECT, NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3ExprListDelete(pParse->db, pCNames);
 | 
						|
    // pSelect is deleted in parse.y
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3DeleteFrom(Parse* pParse, SrcList* pTabList, Expr* pWhere, SrcList* pUsing)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
 | 
						|
    if (info->operation != QUERY_OP_EXPLAIN)
 | 
						|
    {
 | 
						|
        info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
        info->operation = QUERY_OP_DELETE;
 | 
						|
        info->has_clause = pWhere ? true : false;
 | 
						|
 | 
						|
        if (pUsing)
 | 
						|
        {
 | 
						|
            // Walk through the using declaration and update
 | 
						|
            // table and database names.
 | 
						|
            for (int i = 0; i < pUsing->nSrc; ++i)
 | 
						|
            {
 | 
						|
                struct SrcList_item* pItem = &pUsing->a[i];
 | 
						|
 | 
						|
                update_names(info, pItem->zDatabase, pItem->zName);
 | 
						|
            }
 | 
						|
 | 
						|
            // Walk through the tablenames while excluding alias
 | 
						|
            // names from the using declaration.
 | 
						|
            for (int i = 0; i < pTabList->nSrc; ++i)
 | 
						|
            {
 | 
						|
                const struct SrcList_item* pTable = &pTabList->a[i];
 | 
						|
                ss_dassert(pTable->zName);
 | 
						|
                int j = 0;
 | 
						|
                bool isSame = false;
 | 
						|
 | 
						|
                do
 | 
						|
                {
 | 
						|
                    struct SrcList_item* pItem = &pUsing->a[j++];
 | 
						|
 | 
						|
                    if (strcasecmp(pTable->zName, pItem->zName) == 0)
 | 
						|
                    {
 | 
						|
                        isSame = true;
 | 
						|
                    }
 | 
						|
                    else if (pItem->zAlias && (strcasecmp(pTable->zName, pItem->zAlias) == 0))
 | 
						|
                    {
 | 
						|
                        isSame = true;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                while (!isSame && (j < pUsing->nSrc));
 | 
						|
 | 
						|
                if (!isSame)
 | 
						|
                {
 | 
						|
                    // No alias name, update the table name.
 | 
						|
                    update_names(info, pTable->zDatabase, pTable->zName);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            update_names_from_srclist(info, pTabList);
 | 
						|
        }
 | 
						|
 | 
						|
        if (pWhere)
 | 
						|
        {
 | 
						|
            update_field_infos(info, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, 0);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3ExprDelete(pParse->db, pWhere);
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pTabList);
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pUsing);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3DropIndex(Parse* pParse, SrcList* pName, SrcList* pTable, int bits)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
    info->operation = QUERY_OP_DROP;
 | 
						|
 | 
						|
    update_names_from_srclist(info, pTable);
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pName);
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pTable);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr, int isTemp)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
    if (!isTemp)
 | 
						|
    {
 | 
						|
        info->type_mask |= QUERY_TYPE_COMMIT;
 | 
						|
    }
 | 
						|
    info->operation = QUERY_OP_DROP;
 | 
						|
    if (!isView)
 | 
						|
    {
 | 
						|
        info->is_drop_table = true;
 | 
						|
    }
 | 
						|
    update_names_from_srclist(info, pName);
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pName);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3EndTable(Parse *pParse,    /* Parse context */
 | 
						|
                         Token *pCons,     /* The ',' token after the last column defn. */
 | 
						|
                         Token *pEnd,      /* The ')' before options in the CREATE TABLE */
 | 
						|
                         u8 tabOpts,       /* Extra table options. Usually 0. */
 | 
						|
                         Select *pSelect,  /* Select from a "CREATE ... AS SELECT" */
 | 
						|
                         SrcList* pOldTable) /* The old table in "CREATE ... LIKE OldTable" */
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    if (!info->initializing)
 | 
						|
    {
 | 
						|
        if (pSelect)
 | 
						|
        {
 | 
						|
            update_field_infos_from_select(info, pSelect, QC_USED_IN_SELECT, NULL);
 | 
						|
        }
 | 
						|
        else if (pOldTable)
 | 
						|
        {
 | 
						|
            update_names_from_srclist(info, pOldTable);
 | 
						|
            exposed_sqlite3SrcListDelete(pParse->db, pOldTable);
 | 
						|
        }
 | 
						|
 | 
						|
        // pSelect is deleted in parse.y
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        exposed_sqlite3EndTable(pParse, pCons, pEnd, tabOpts, pSelect);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3FinishTrigger(Parse *pParse,          /* Parser context */
 | 
						|
                              TriggerStep *pStepList, /* The triggered program */
 | 
						|
                              Token *pAll)            /* Token that describes the complete CREATE TRIGGER */
 | 
						|
{
 | 
						|
    exposed_sqlite3FinishTrigger(pParse, pStepList, pAll);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3Insert(Parse* pParse,
 | 
						|
                       SrcList* pTabList,
 | 
						|
                       Select* pSelect,
 | 
						|
                       IdList* pColumns,
 | 
						|
                       int onError,
 | 
						|
                       ExprList* pSet)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
 | 
						|
    if (info->operation != QUERY_OP_EXPLAIN)
 | 
						|
    {
 | 
						|
        info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
        info->operation = QUERY_OP_INSERT;
 | 
						|
        ss_dassert(pTabList);
 | 
						|
        ss_dassert(pTabList->nSrc >= 1);
 | 
						|
        update_names_from_srclist(info, pTabList);
 | 
						|
 | 
						|
        if (pColumns)
 | 
						|
        {
 | 
						|
            update_field_infos_from_idlist(info, pColumns, 0, NULL);
 | 
						|
        }
 | 
						|
 | 
						|
        if (pSelect)
 | 
						|
        {
 | 
						|
            uint32_t usage;
 | 
						|
 | 
						|
            if (pSelect->selFlags & SF_Values) // Synthesized from VALUES clause
 | 
						|
            {
 | 
						|
                usage = 0;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                usage = QC_USED_IN_SELECT;
 | 
						|
            }
 | 
						|
 | 
						|
            update_field_infos_from_select(info, pSelect, usage, NULL);
 | 
						|
        }
 | 
						|
 | 
						|
        if (pSet)
 | 
						|
        {
 | 
						|
            update_field_infos_from_exprlist(info, pSet, 0, NULL);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pTabList);
 | 
						|
    exposed_sqlite3IdListDelete(pParse->db, pColumns);
 | 
						|
    exposed_sqlite3ExprListDelete(pParse->db, pSet);
 | 
						|
    // pSelect is deleted in parse.y
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3RollbackTransaction(Parse* pParse)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_ROLLBACK;
 | 
						|
}
 | 
						|
 | 
						|
int mxs_sqlite3Select(Parse* pParse, Select* p, SelectDest* pDest)
 | 
						|
{
 | 
						|
    int rc = -1;
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    if (!info->initializing)
 | 
						|
    {
 | 
						|
        info->status = QC_QUERY_PARSED;
 | 
						|
 | 
						|
        if (info->operation != QUERY_OP_EXPLAIN)
 | 
						|
        {
 | 
						|
            info->operation = QUERY_OP_SELECT;
 | 
						|
 | 
						|
            maxscaleCollectInfoFromSelect(pParse, p, 0);
 | 
						|
        }
 | 
						|
        // NOTE: By convention, the select is deleted in parse.y.
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        rc = exposed_sqlite3Select(pParse, p, pDest);
 | 
						|
    }
 | 
						|
 | 
						|
    return rc;
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3StartTable(Parse *pParse,   /* Parser context */
 | 
						|
                           Token *pName1,   /* First part of the name of the table or view */
 | 
						|
                           Token *pName2,   /* Second part of the name of the table or view */
 | 
						|
                           int isTemp,      /* True if this is a TEMP table */
 | 
						|
                           int isView,      /* True if this is a VIEW */
 | 
						|
                           int isVirtual,   /* True if this is a VIRTUAL table */
 | 
						|
                           int noErr)       /* Do nothing if table already exists */
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    if (!info->initializing)
 | 
						|
    {
 | 
						|
        info->status = QC_QUERY_PARSED;
 | 
						|
        info->operation = QUERY_OP_CREATE;
 | 
						|
        info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
 | 
						|
        if (isTemp)
 | 
						|
        {
 | 
						|
            info->type_mask |= QUERY_TYPE_CREATE_TMP_TABLE;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            info->type_mask |= QUERY_TYPE_COMMIT;
 | 
						|
        }
 | 
						|
 | 
						|
        const Token* pName = pName2->z ? pName2 : pName1;
 | 
						|
        const Token* pDatabase = pName2->z ? pName1 : NULL;
 | 
						|
 | 
						|
        char name[pName->n + 1];
 | 
						|
        strncpy(name, pName->z, pName->n);
 | 
						|
        name[pName->n] = 0;
 | 
						|
 | 
						|
        if (pDatabase)
 | 
						|
        {
 | 
						|
            char database[pDatabase->n + 1];
 | 
						|
            strncpy(database, pDatabase->z, pDatabase->n);
 | 
						|
            database[pDatabase->n] = 0;
 | 
						|
 | 
						|
            update_names(info, database, name);
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            update_names(info, NULL, name);
 | 
						|
        }
 | 
						|
 | 
						|
        if (info->collect & QC_COLLECT_TABLES)
 | 
						|
        {
 | 
						|
            // If information is collected in several passes, then we may
 | 
						|
            // this information already.
 | 
						|
            if (!info->created_table_name)
 | 
						|
            {
 | 
						|
                info->created_table_name = MXS_STRDUP(info->table_names[0]);
 | 
						|
                MXS_ABORT_IF_NULL(info->created_table_name);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                ss_dassert(info->collect != info->collected);
 | 
						|
                ss_dassert(strcmp(info->created_table_name, info->table_names[0]) == 0);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        exposed_sqlite3StartTable(pParse, pName1, pName2, isTemp, isView, isVirtual, noErr);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3Update(Parse* pParse, SrcList* pTabList, ExprList* pChanges, Expr* pWhere, int onError)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
 | 
						|
    if (info->operation != QUERY_OP_EXPLAIN)
 | 
						|
    {
 | 
						|
        info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
        info->operation = QUERY_OP_UPDATE;
 | 
						|
        update_names_from_srclist(info, pTabList);
 | 
						|
        info->has_clause = (pWhere ? true : false);
 | 
						|
 | 
						|
        if (pChanges)
 | 
						|
        {
 | 
						|
            for (int i = 0; i < pChanges->nExpr; ++i)
 | 
						|
            {
 | 
						|
                struct ExprList_item* pItem = &pChanges->a[i];
 | 
						|
 | 
						|
                update_field_infos(info, 0, pItem->pExpr, QC_USED_IN_SET, QC_TOKEN_MIDDLE, NULL);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (pWhere)
 | 
						|
        {
 | 
						|
            update_field_infos(info, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pChanges);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pTabList);
 | 
						|
    exposed_sqlite3ExprListDelete(pParse->db, pChanges);
 | 
						|
    exposed_sqlite3ExprDelete(pParse->db, pWhere);
 | 
						|
}
 | 
						|
 | 
						|
void mxs_sqlite3Savepoint(Parse *pParse, int op, Token *pName)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleCollectInfoFromSelect(Parse* pParse, Select* pSelect, int sub_select)
 | 
						|
{
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    if (pSelect->pInto)
 | 
						|
    {
 | 
						|
        // If there's a single variable, then it's a write.
 | 
						|
        // mysql embedded considers it a system var write.
 | 
						|
        info->type_mask = QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
 | 
						|
        // Also INTO {OUTFILE|DUMPFILE} will be typed as QUERY_TYPE_GSYSVAR_WRITE.
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        info->type_mask = QUERY_TYPE_READ;
 | 
						|
    }
 | 
						|
 | 
						|
    uint32_t usage = sub_select ? QC_USED_IN_SUBSELECT : QC_USED_IN_SELECT;
 | 
						|
 | 
						|
    update_field_infos_from_select(info, pSelect, usage, NULL);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleAlterTable(Parse *pParse,            /* Parser context. */
 | 
						|
                        mxs_alter_t command,
 | 
						|
                        SrcList *pSrc,            /* The table to rename. */
 | 
						|
                        Token *pName)             /* The new table name (RENAME). */
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
    info->operation = QUERY_OP_ALTER;
 | 
						|
 | 
						|
    switch (command)
 | 
						|
    {
 | 
						|
    case MXS_ALTER_DISABLE_KEYS:
 | 
						|
        update_names_from_srclist(info, pSrc);
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_ALTER_ENABLE_KEYS:
 | 
						|
        update_names_from_srclist(info, pSrc);
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_ALTER_RENAME:
 | 
						|
        update_names_from_srclist(info, pSrc);
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        ;
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pSrc);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleCall(Parse* pParse, SrcList* pName, ExprList* pExprList)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
 | 
						|
    if (pExprList)
 | 
						|
    {
 | 
						|
        update_field_infos_from_exprlist(info, pExprList, 0, NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pName);
 | 
						|
    exposed_sqlite3ExprListDelete(pParse->db, pExprList);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleCheckTable(Parse* pParse, SrcList* pTables)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
 | 
						|
    update_names_from_srclist(info, pTables);
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pTables);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleCreateSequence(Parse* pParse, Token* pDatabase, Token* pTable)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
 | 
						|
    const char* zDatabase = NULL;
 | 
						|
    char database[pDatabase ? pDatabase->n + 1 : 1];
 | 
						|
 | 
						|
    if (pDatabase)
 | 
						|
    {
 | 
						|
        strncpy(database, pDatabase->z, pDatabase->n);
 | 
						|
        database[pDatabase->n] = 0;
 | 
						|
 | 
						|
        zDatabase = database;
 | 
						|
    }
 | 
						|
 | 
						|
    char table[pTable->n + 1];
 | 
						|
    strncpy(table, pTable->z, pTable->n);
 | 
						|
    table[pTable->n] = 0;
 | 
						|
 | 
						|
    update_names(info, zDatabase, table);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleComment()
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    if (info->status == QC_QUERY_INVALID)
 | 
						|
    {
 | 
						|
        info->status = QC_QUERY_PARSED;
 | 
						|
        info->type_mask = QUERY_TYPE_READ;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleDeclare(Parse* pParse)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    if (info->sql_mode != QC_SQL_MODE_ORACLE)
 | 
						|
    {
 | 
						|
        info->status = QC_QUERY_INVALID;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleDeallocate(Parse* pParse, Token* pName)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
 | 
						|
    // If information is collected in several passes, then we may
 | 
						|
    // this information already.
 | 
						|
    if (!info->prepare_name)
 | 
						|
    {
 | 
						|
        info->prepare_name = MXS_MALLOC(pName->n + 1);
 | 
						|
        if (info->prepare_name)
 | 
						|
        {
 | 
						|
            memcpy(info->prepare_name, pName->z, pName->n);
 | 
						|
            info->prepare_name[pName->n] = 0;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        ss_dassert(info->collect != info->collected);
 | 
						|
        ss_dassert(strncmp(info->prepare_name, pName->z, pName->n) == 0);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleDo(Parse* pParse, ExprList* pEList)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_READ | QUERY_TYPE_WRITE);
 | 
						|
 | 
						|
    exposed_sqlite3ExprListDelete(pParse->db, pEList);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleDrop(Parse* pParse, int what, Token* pDatabase, Token* pName)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
    info->operation = QUERY_OP_DROP;
 | 
						|
 | 
						|
    if (what == MXS_DROP_SEQUENCE)
 | 
						|
    {
 | 
						|
        const char* zDatabase = NULL;
 | 
						|
        char database[pDatabase ? pDatabase->n + 1 : 1];
 | 
						|
 | 
						|
        if (pDatabase)
 | 
						|
        {
 | 
						|
            strncpy(database, pDatabase->z, pDatabase->n);
 | 
						|
            database[pDatabase->n] = 0;
 | 
						|
 | 
						|
            zDatabase = database;
 | 
						|
        }
 | 
						|
 | 
						|
        char table[pName->n + 1];
 | 
						|
        strncpy(table, pName->z, pName->n);
 | 
						|
        table[pName->n] = 0;
 | 
						|
 | 
						|
        update_names(info, zDatabase, table);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleExecute(Parse* pParse, Token* pName, int type_mask)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | type_mask);
 | 
						|
    info->operation = QUERY_OP_EXECUTE;
 | 
						|
 | 
						|
    // If information is collected in several passes, then we may
 | 
						|
    // this information already.
 | 
						|
    if (!info->prepare_name)
 | 
						|
    {
 | 
						|
        info->prepare_name = MXS_MALLOC(pName->n + 1);
 | 
						|
        if (info->prepare_name)
 | 
						|
        {
 | 
						|
            memcpy(info->prepare_name, pName->z, pName->n);
 | 
						|
            info->prepare_name[pName->n] = 0;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        ss_dassert(info->collect != info->collected);
 | 
						|
        ss_dassert(strncmp(info->prepare_name, pName->z, pName->n) == 0);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static int32_t type_check_dynamic_string(const Expr* pExpr)
 | 
						|
{
 | 
						|
    int32_t type_mask = 0;
 | 
						|
 | 
						|
    if (pExpr)
 | 
						|
    {
 | 
						|
        switch (pExpr->op)
 | 
						|
        {
 | 
						|
        case TK_CONCAT:
 | 
						|
            type_mask |= type_check_dynamic_string(pExpr->pLeft);
 | 
						|
            type_mask |= type_check_dynamic_string(pExpr->pRight);
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_VARIABLE:
 | 
						|
            ss_dassert(pExpr->u.zToken);
 | 
						|
            {
 | 
						|
                const char* zToken = pExpr->u.zToken;
 | 
						|
                if (zToken[0] == '@')
 | 
						|
                {
 | 
						|
                    if (zToken[1] == '@')
 | 
						|
                    {
 | 
						|
                        type_mask |= QUERY_TYPE_SYSVAR_READ;
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        type_mask |= QUERY_TYPE_USERVAR_READ;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return type_mask;
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleExecuteImmediate(Parse* pParse, Token* pName, ExprSpan* pExprSpan, int type_mask)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    if (info->sql_mode == QC_SQL_MODE_ORACLE)
 | 
						|
    {
 | 
						|
        // This should be "EXECUTE IMMEDIATE ...", but as "IMMEDIATE" is not
 | 
						|
        // checked by the parser we do it here.
 | 
						|
 | 
						|
        static const char IMMEDIATE[] = "IMMEDIATE";
 | 
						|
 | 
						|
        if ((pName->n == sizeof(IMMEDIATE) - 1) && (strncasecmp(pName->z, IMMEDIATE, pName->n)) == 0)
 | 
						|
        {
 | 
						|
            info->status = QC_QUERY_PARSED;
 | 
						|
            info->type_mask = (QUERY_TYPE_WRITE | type_mask);
 | 
						|
            info->type_mask |= type_check_dynamic_string(pExprSpan->pExpr);
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            info->status = QC_QUERY_INVALID;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        info->status = QC_QUERY_INVALID;
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3ExprDelete(pParse->db, pExprSpan->pExpr);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleExplain(Parse* pParse, Token* pNext)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_READ;
 | 
						|
    info->operation = QUERY_OP_SHOW;
 | 
						|
 | 
						|
    if (pNext)
 | 
						|
    {
 | 
						|
        if (pNext->z)
 | 
						|
        {
 | 
						|
            const char EXTENDED[]   = "EXTENDED";
 | 
						|
            const char PARTITIONS[] = "PARTITIONS";
 | 
						|
            const char FORMAT[]     = "FORMAT";
 | 
						|
            const char FOR[]        = "FOR";
 | 
						|
 | 
						|
#define MATCHES_KEYWORD(t, k)  ((t->n == sizeof(k) - 1) && (strncasecmp(t->z, k, t->n) == 0))
 | 
						|
 | 
						|
            if (MATCHES_KEYWORD(pNext, EXTENDED) ||
 | 
						|
                MATCHES_KEYWORD(pNext, PARTITIONS) ||
 | 
						|
                MATCHES_KEYWORD(pNext, FORMAT) ||
 | 
						|
                MATCHES_KEYWORD(pNext, FOR))
 | 
						|
            {
 | 
						|
                info->operation = QUERY_OP_EXPLAIN;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleFlush(Parse* pParse, Token* pWhat)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleHandler(Parse* pParse, mxs_handler_t type, SrcList* pFullName, Token* pName)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
 | 
						|
    switch (type)
 | 
						|
    {
 | 
						|
    case MXS_HANDLER_OPEN:
 | 
						|
        {
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
 | 
						|
            ss_dassert(pFullName->nSrc == 1);
 | 
						|
            const struct SrcList_item* pItem = &pFullName->a[0];
 | 
						|
 | 
						|
            update_names(info, pItem->zDatabase, pItem->zName);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_HANDLER_CLOSE:
 | 
						|
        {
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
 | 
						|
            char zName[pName->n + 1];
 | 
						|
            strncpy(zName, pName->z, pName->n);
 | 
						|
            zName[pName->n] = 0;
 | 
						|
 | 
						|
            update_names(info, "*any*", zName);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        ss_dassert(!true);
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pFullName);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleLoadData(Parse* pParse, SrcList* pFullName)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
    info->operation = QUERY_OP_LOAD;
 | 
						|
 | 
						|
    if (pFullName)
 | 
						|
    {
 | 
						|
        update_names_from_srclist(info, pFullName);
 | 
						|
 | 
						|
        exposed_sqlite3SrcListDelete(pParse->db, pFullName);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleLock(Parse* pParse, mxs_lock_t type, SrcList* pTables)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
 | 
						|
    if (pTables)
 | 
						|
    {
 | 
						|
        update_names_from_srclist(info, pTables);
 | 
						|
 | 
						|
        exposed_sqlite3SrcListDelete(pParse->db, pTables);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
int maxscaleTranslateKeyword(int token)
 | 
						|
{
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    switch (token)
 | 
						|
    {
 | 
						|
    case TK_CHARSET:
 | 
						|
    case TK_DO:
 | 
						|
    case TK_HANDLER:
 | 
						|
        if (info->sql_mode == QC_SQL_MODE_ORACLE)
 | 
						|
        {
 | 
						|
            // The keyword is translated, but only if it not used
 | 
						|
            // as the first keyword. Matters for DO and HANDLER.
 | 
						|
            if (info->keyword_1)
 | 
						|
            {
 | 
						|
                token = TK_ID;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
    return token;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Register the tokenization of a keyword.
 | 
						|
 *
 | 
						|
 * @param token A keyword code (check generated parse.h)
 | 
						|
 *
 | 
						|
 * @return Non-zero if all input should be consumed, 0 otherwise.
 | 
						|
 */
 | 
						|
int maxscaleKeyword(int token)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    int rv = 0;
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    // This function is called for every keyword the sqlite3 parser encounters.
 | 
						|
    // We will store in info->keyword_{1|2} the first and second keyword that
 | 
						|
    // are encountered, and when they _are_ encountered, we make an educated
 | 
						|
    // deduction about the statement. We can make that deduction only the first
 | 
						|
    // (and second) time we see a keyword, so that we don't get confused by a
 | 
						|
    // statement like "CREATE TABLE ... AS SELECT ...".
 | 
						|
    // Since info->keyword_{1|2} is initialized with 0, well, if it is 0 then
 | 
						|
    // we have not seen the {1st|2nd} keyword yet.
 | 
						|
 | 
						|
    if (!info->keyword_1)
 | 
						|
    {
 | 
						|
        info->keyword_1 = token;
 | 
						|
 | 
						|
        switch (info->keyword_1)
 | 
						|
        {
 | 
						|
        case TK_ALTER:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
            info->operation = QUERY_OP_ALTER;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_BEGIN:
 | 
						|
        case TK_DECLARE:
 | 
						|
        case TK_FOR:
 | 
						|
            if (info->sql_mode == QC_SQL_MODE_ORACLE)
 | 
						|
            {
 | 
						|
                // The beginning of a BLOCK. We'll assume it is in a single
 | 
						|
                // COM_QUERY packet and hence one GWBUF.
 | 
						|
                info->status = QC_QUERY_TOKENIZED;
 | 
						|
                info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
                // Return non-0 to cause the entire input to be consumed.
 | 
						|
                rv = 1;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_CALL:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_CREATE:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
            info->operation = QUERY_OP_CREATE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_DELETE:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            info->operation = QUERY_OP_DELETE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_DESC:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_READ;
 | 
						|
            info->operation = QUERY_OP_EXPLAIN;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_DROP:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
            info->operation = QUERY_OP_DROP;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_EXECUTE:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_EXPLAIN:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_READ;
 | 
						|
            info->operation = QUERY_OP_EXPLAIN;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_GRANT:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
            info->operation = QUERY_OP_GRANT;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_HANDLER:
 | 
						|
            info->status = QUERY_TYPE_WRITE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_INSERT:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            info->operation = QUERY_OP_INSERT;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_LOCK:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_PREPARE:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_PREPARE_NAMED_STMT;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_REPLACE:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            info->operation = QUERY_OP_INSERT;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_REVOKE:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
            info->operation = QUERY_OP_REVOKE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_SELECT:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_READ;
 | 
						|
            info->operation = QUERY_OP_SELECT;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_SET:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_SHOW:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_READ;
 | 
						|
            info->operation = QUERY_OP_SHOW;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_START:
 | 
						|
            // Will produce the right info for START SLAVE.
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_UNLOCK:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_UPDATE:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            info->operation = QUERY_OP_UPDATE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_TRUNCATE:
 | 
						|
            info->status = QC_QUERY_TOKENIZED;
 | 
						|
            info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            ;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (!info->keyword_2)
 | 
						|
    {
 | 
						|
        info->keyword_2 = token;
 | 
						|
 | 
						|
        switch (info->keyword_1)
 | 
						|
        {
 | 
						|
        case TK_CHECK:
 | 
						|
            if (info->keyword_2 == TK_TABLE)
 | 
						|
            {
 | 
						|
                info->status = QC_QUERY_TOKENIZED;
 | 
						|
                info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_DEALLOCATE:
 | 
						|
            if (info->keyword_2 == TK_PREPARE)
 | 
						|
            {
 | 
						|
                info->status = QC_QUERY_TOKENIZED;
 | 
						|
                info->type_mask = QUERY_TYPE_SESSION_WRITE;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_LOAD:
 | 
						|
            if (info->keyword_2 == TK_DATA)
 | 
						|
            {
 | 
						|
                info->status = QC_QUERY_TOKENIZED;
 | 
						|
                info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
                info->operation = QUERY_OP_LOAD;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_RENAME:
 | 
						|
            if (info->keyword_2 == TK_TABLE)
 | 
						|
            {
 | 
						|
                info->status = QC_QUERY_TOKENIZED;
 | 
						|
                info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_START:
 | 
						|
            switch (info->keyword_2)
 | 
						|
            {
 | 
						|
            case TK_TRANSACTION:
 | 
						|
                info->status = QC_QUERY_TOKENIZED;
 | 
						|
                info->type_mask = QUERY_TYPE_BEGIN_TRX;
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case TK_SHOW:
 | 
						|
            switch (info->keyword_2)
 | 
						|
            {
 | 
						|
            case TK_DATABASES_KW:
 | 
						|
                info->status = QC_QUERY_TOKENIZED;
 | 
						|
                info->type_mask = QUERY_TYPE_SHOW_DATABASES;
 | 
						|
                break;
 | 
						|
 | 
						|
            case TK_TABLES:
 | 
						|
                info->status = QC_QUERY_TOKENIZED;
 | 
						|
                info->type_mask = QUERY_TYPE_SHOW_TABLES;
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleRenameTable(Parse* pParse, SrcList* pTables)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT;
 | 
						|
 | 
						|
    for (int i = 0; i < pTables->nSrc; ++i)
 | 
						|
    {
 | 
						|
        const struct SrcList_item* pItem = &pTables->a[i];
 | 
						|
 | 
						|
        ss_dassert(pItem->zName);
 | 
						|
        ss_dassert(pItem->zAlias);
 | 
						|
 | 
						|
        update_names(info, pItem->zDatabase, pItem->zName);
 | 
						|
        update_names(info, NULL, pItem->zAlias); // The new name is passed in the alias field.
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3SrcListDelete(pParse->db, pTables);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns some string from an expression.
 | 
						|
 *
 | 
						|
 * @param pExpr An expression.
 | 
						|
 *
 | 
						|
 * @return Some string referred to in pExpr.
 | 
						|
 */
 | 
						|
static const char* find_one_string(Expr* pExpr)
 | 
						|
{
 | 
						|
    const char* z = NULL;
 | 
						|
 | 
						|
    if (pExpr->op == TK_STRING)
 | 
						|
    {
 | 
						|
        ss_dassert(pExpr->u.zToken);
 | 
						|
        z = pExpr->u.zToken;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!z && pExpr->pLeft)
 | 
						|
    {
 | 
						|
        z = find_one_string(pExpr->pLeft);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!z && pExpr->pRight)
 | 
						|
    {
 | 
						|
        z = find_one_string(pExpr->pRight);
 | 
						|
    }
 | 
						|
 | 
						|
    return z;
 | 
						|
}
 | 
						|
 | 
						|
void maxscalePrepare(Parse* pParse, Token* pName, Expr* pStmt)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    // If the mode is MODE_ORACLE then if expression contains simply a string
 | 
						|
    // we can conclude that the statement has been fully parsed, because it will
 | 
						|
    // be sensible to parse the preparable statement. Otherwise we mark the
 | 
						|
    // statement as having been partially parsed, since the preparable statement
 | 
						|
    // will not contain the full statement.
 | 
						|
    if (info->sql_mode == QC_SQL_MODE_ORACLE)
 | 
						|
    {
 | 
						|
        if (pStmt->op == TK_STRING)
 | 
						|
        {
 | 
						|
            info->status = QC_QUERY_PARSED;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            info->status = QC_QUERY_PARTIALLY_PARSED;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        // If the mode is not MODE_ORACLE, then only a string is acceptable.
 | 
						|
        if (pStmt->op == TK_STRING)
 | 
						|
        {
 | 
						|
            info->status = QC_QUERY_PARSED;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            info->status = QC_QUERY_INVALID;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    info->type_mask = QUERY_TYPE_PREPARE_NAMED_STMT;
 | 
						|
 | 
						|
    // If information is collected in several passes, then we may
 | 
						|
    // this information already.
 | 
						|
    if (!info->prepare_name)
 | 
						|
    {
 | 
						|
        info->prepare_name = MXS_MALLOC(pName->n + 1);
 | 
						|
        if (info->prepare_name)
 | 
						|
        {
 | 
						|
            memcpy(info->prepare_name, pName->z, pName->n);
 | 
						|
            info->prepare_name[pName->n] = 0;
 | 
						|
        }
 | 
						|
 | 
						|
        // If the expression just contains a string, then zStmt will
 | 
						|
        // be that string. Otherwise it will be _some_ string from the
 | 
						|
        // expression. In the latter case we've already marked the result
 | 
						|
        // to have been partially parsed.
 | 
						|
        const char* zStmt = find_one_string(pStmt);
 | 
						|
 | 
						|
        if (zStmt)
 | 
						|
        {
 | 
						|
            size_t preparable_stmt_len = zStmt ? strlen(zStmt) : 0;
 | 
						|
            size_t payload_len = 1 + preparable_stmt_len;
 | 
						|
            size_t packet_len = MYSQL_HEADER_LEN + payload_len;
 | 
						|
 | 
						|
            info->preparable_stmt = gwbuf_alloc(packet_len);
 | 
						|
 | 
						|
            if (info->preparable_stmt)
 | 
						|
            {
 | 
						|
                uint8_t* ptr = GWBUF_DATA(info->preparable_stmt);
 | 
						|
                // Payload length
 | 
						|
                *ptr++ = payload_len;
 | 
						|
                *ptr++ = (payload_len >> 8);
 | 
						|
                *ptr++ = (payload_len >> 16);
 | 
						|
                // Sequence id
 | 
						|
                *ptr++ = 0x00;
 | 
						|
                // Command
 | 
						|
                *ptr++ = MYSQL_COM_QUERY;
 | 
						|
 | 
						|
                memcpy(ptr, zStmt, preparable_stmt_len);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            info->status = QC_QUERY_INVALID;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        ss_dassert(info->collect != info->collected);
 | 
						|
        ss_dassert(strncmp(info->prepare_name, pName->z, pName->n) == 0);
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3ExprDelete(pParse->db, pStmt);
 | 
						|
}
 | 
						|
 | 
						|
void maxscalePrivileges(Parse* pParse, int kind)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
 | 
						|
    switch (kind)
 | 
						|
    {
 | 
						|
    case TK_GRANT:
 | 
						|
        info->operation = QUERY_OP_GRANT;
 | 
						|
        break;
 | 
						|
 | 
						|
    case TK_REVOKE:
 | 
						|
        info->operation = QUERY_OP_REVOKE;
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        ss_dassert(!true);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static int string_to_truth(const char* s)
 | 
						|
{
 | 
						|
    int truth = -1;
 | 
						|
 | 
						|
    if ((strcasecmp(s, "true") == 0) || (strcasecmp(s, "on") == 0))
 | 
						|
    {
 | 
						|
        truth = 1;
 | 
						|
    }
 | 
						|
    else if ((strcasecmp(s, "false") == 0) || (strcasecmp(s, "off") == 0))
 | 
						|
    {
 | 
						|
        truth = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    return truth;
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = 0; // Reset what was set in maxscaleKeyword
 | 
						|
 | 
						|
    switch (kind)
 | 
						|
    {
 | 
						|
    case MXS_SET_TRANSACTION:
 | 
						|
        if ((scope == TK_GLOBAL) || (scope == TK_SESSION))
 | 
						|
        {
 | 
						|
            info->type_mask = QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            ss_dassert(scope == 0);
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SET_VARIABLES:
 | 
						|
        {
 | 
						|
            for (int i = 0; i < pList->nExpr; ++i)
 | 
						|
            {
 | 
						|
                const struct ExprList_item* pItem = &pList->a[i];
 | 
						|
 | 
						|
                switch (pItem->pExpr->op)
 | 
						|
                {
 | 
						|
                case TK_CHARACTER:
 | 
						|
                case TK_NAMES:
 | 
						|
                    info->type_mask |= QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
                    break;
 | 
						|
 | 
						|
                case TK_EQ:
 | 
						|
                    {
 | 
						|
                        const Expr* pEq = pItem->pExpr;
 | 
						|
                        const Expr* pVariable;
 | 
						|
                        const Expr* pValue = pEq->pRight;
 | 
						|
 | 
						|
                        // pEq->pLeft is either TK_DOT, TK_VARIABLE or TK_ID. If it's TK_DOT,
 | 
						|
                        // then pEq->pLeft->pLeft is either TK_VARIABLE or TK_ID and pEq->pLeft->pRight
 | 
						|
                        // is either TK_DOT, TK_VARIABLE or TK_ID.
 | 
						|
 | 
						|
                        // Find the left-most part.
 | 
						|
                        pVariable = pEq->pLeft;
 | 
						|
                        while (pVariable->op == TK_DOT)
 | 
						|
                        {
 | 
						|
                            pVariable = pVariable->pLeft;
 | 
						|
                            ss_dassert(pVariable);
 | 
						|
                        }
 | 
						|
 | 
						|
                        // Check what kind of variable it is.
 | 
						|
                        size_t n_at = 0;
 | 
						|
                        const char* zName = pVariable->u.zToken;
 | 
						|
 | 
						|
                        while (*zName == '@')
 | 
						|
                        {
 | 
						|
                            ++n_at;
 | 
						|
                            ++zName;
 | 
						|
                        }
 | 
						|
 | 
						|
                        if (n_at == 1)
 | 
						|
                        {
 | 
						|
                            info->type_mask |= QUERY_TYPE_USERVAR_WRITE;
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        {
 | 
						|
                            info->type_mask |= QUERY_TYPE_GSYSVAR_WRITE;
 | 
						|
                        }
 | 
						|
 | 
						|
                        // Set pVariable to point to the rightmost part of the name.
 | 
						|
                        pVariable = pEq->pLeft;
 | 
						|
                        while (pVariable->op == TK_DOT)
 | 
						|
                        {
 | 
						|
                            pVariable = pVariable->pRight;
 | 
						|
                        }
 | 
						|
 | 
						|
                        ss_dassert((pVariable->op == TK_VARIABLE) || (pVariable->op == TK_ID));
 | 
						|
 | 
						|
                        if (n_at != 1)
 | 
						|
                        {
 | 
						|
                            // If it's not a user-variable we need to check whether it might
 | 
						|
                            // be 'autocommit'.
 | 
						|
                            const char* zName = pVariable->u.zToken;
 | 
						|
 | 
						|
                            while (*zName == '@')
 | 
						|
                            {
 | 
						|
                                ++zName;
 | 
						|
                            }
 | 
						|
 | 
						|
                            // As pVariable points to the rightmost part, we'll catch both
 | 
						|
                            // "autocommit" and "@@global.autocommit".
 | 
						|
                            if (strcasecmp(zName, "autocommit") == 0)
 | 
						|
                            {
 | 
						|
                                int enable = -1;
 | 
						|
 | 
						|
                                switch (pValue->op)
 | 
						|
                                {
 | 
						|
                                case TK_INTEGER:
 | 
						|
                                    if (pValue->u.iValue == 1)
 | 
						|
                                    {
 | 
						|
                                        enable = 1;
 | 
						|
                                    }
 | 
						|
                                    else if (pValue->u.iValue == 0)
 | 
						|
                                    {
 | 
						|
                                        enable = 0;
 | 
						|
                                    }
 | 
						|
                                    break;
 | 
						|
 | 
						|
                                case TK_ID:
 | 
						|
                                    enable = string_to_truth(pValue->u.zToken);
 | 
						|
                                    break;
 | 
						|
 | 
						|
                                default:
 | 
						|
                                    break;
 | 
						|
                                }
 | 
						|
 | 
						|
                                switch (enable)
 | 
						|
                                {
 | 
						|
                                case 0:
 | 
						|
                                    info->type_mask |= QUERY_TYPE_BEGIN_TRX;
 | 
						|
                                    info->type_mask |= QUERY_TYPE_DISABLE_AUTOCOMMIT;
 | 
						|
                                    break;
 | 
						|
 | 
						|
                                case 1:
 | 
						|
                                    info->type_mask |= QUERY_TYPE_ENABLE_AUTOCOMMIT;
 | 
						|
                                    info->type_mask |= QUERY_TYPE_COMMIT;
 | 
						|
                                    break;
 | 
						|
 | 
						|
                                default:
 | 
						|
                                    break;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
 | 
						|
                        if (pValue->op == TK_SELECT)
 | 
						|
                        {
 | 
						|
                            update_field_infos_from_select(info, pValue->x.pSelect,
 | 
						|
                                                           QC_USED_IN_SUBSELECT, NULL);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
 | 
						|
                default:
 | 
						|
                    ss_dassert(!true);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        ss_dassert(!true);
 | 
						|
    }
 | 
						|
 | 
						|
    exposed_sqlite3ExprListDelete(pParse->db, pList);
 | 
						|
}
 | 
						|
 | 
						|
extern void maxscaleShow(Parse* pParse, MxsShow* pShow)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->operation = QUERY_OP_SHOW;
 | 
						|
 | 
						|
    uint32_t u = QC_USED_IN_SELECT;
 | 
						|
 | 
						|
    switch (pShow->what)
 | 
						|
    {
 | 
						|
    case MXS_SHOW_COLUMNS:
 | 
						|
        info->type_mask = QUERY_TYPE_READ;
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_CREATE_SEQUENCE:
 | 
						|
        info->type_mask = QUERY_TYPE_READ;
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_CREATE_VIEW:
 | 
						|
        info->type_mask = QUERY_TYPE_READ;
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_CREATE_TABLE:
 | 
						|
        info->type_mask = QUERY_TYPE_READ;
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_DATABASES:
 | 
						|
        info->type_mask = QUERY_TYPE_SHOW_DATABASES;
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_INDEX:
 | 
						|
    case MXS_SHOW_INDEXES:
 | 
						|
    case MXS_SHOW_KEYS:
 | 
						|
        info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_TABLE_STATUS:
 | 
						|
        info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_STATUS:
 | 
						|
        switch (pShow->data)
 | 
						|
        {
 | 
						|
        case MXS_SHOW_VARIABLES_GLOBAL:
 | 
						|
        case MXS_SHOW_VARIABLES_SESSION:
 | 
						|
        case MXS_SHOW_VARIABLES_UNSPECIFIED:
 | 
						|
            info->type_mask = QUERY_TYPE_READ;
 | 
						|
            break;
 | 
						|
 | 
						|
        case MXS_SHOW_STATUS_MASTER:
 | 
						|
            info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
            break;
 | 
						|
 | 
						|
        case MXS_SHOW_STATUS_SLAVE:
 | 
						|
            info->type_mask = QUERY_TYPE_READ;
 | 
						|
            break;
 | 
						|
 | 
						|
        case MXS_SHOW_STATUS_ALL_SLAVES:
 | 
						|
            info->type_mask = QUERY_TYPE_READ;
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            info->type_mask = QUERY_TYPE_READ;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_TABLES:
 | 
						|
        info->type_mask = QUERY_TYPE_SHOW_TABLES;
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_VARIABLES:
 | 
						|
        if (pShow->data == MXS_SHOW_VARIABLES_GLOBAL)
 | 
						|
        {
 | 
						|
            info->type_mask = QUERY_TYPE_GSYSVAR_READ;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            info->type_mask = QUERY_TYPE_SYSVAR_READ;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case MXS_SHOW_WARNINGS:
 | 
						|
        // qc_mysqliembedded claims this.
 | 
						|
        info->type_mask = QUERY_TYPE_WRITE;
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        ss_dassert(!true);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleTruncate(Parse* pParse, Token* pDatabase, Token* pName)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | 
						|
    info->operation = QUERY_OP_TRUNCATE;
 | 
						|
 | 
						|
    char* zDatabase;
 | 
						|
 | 
						|
    char database[pDatabase ? pDatabase->n + 1 : 0];
 | 
						|
    if (pDatabase)
 | 
						|
    {
 | 
						|
        strncpy(database, pDatabase->z, pDatabase->n);
 | 
						|
        database[pDatabase->n] = 0;
 | 
						|
        zDatabase = database;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        zDatabase = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    char name[pName->n + 1];
 | 
						|
    strncpy(name, pName->z, pName->n);
 | 
						|
    name[pName->n] = 0;
 | 
						|
 | 
						|
    update_names(info, zDatabase, name);
 | 
						|
}
 | 
						|
 | 
						|
void maxscaleUse(Parse* pParse, Token* pToken)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = this_thread.info;
 | 
						|
    ss_dassert(info);
 | 
						|
 | 
						|
    info->status = QC_QUERY_PARSED;
 | 
						|
    info->type_mask = QUERY_TYPE_SESSION_WRITE;
 | 
						|
    info->operation = QUERY_OP_CHANGE_DB;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * API
 | 
						|
 */
 | 
						|
static int32_t qc_sqlite_setup(qc_sql_mode_t sql_mode, const char* args);
 | 
						|
static int32_t qc_sqlite_process_init(void);
 | 
						|
static void qc_sqlite_process_end(void);
 | 
						|
static int32_t qc_sqlite_thread_init(void);
 | 
						|
static void qc_sqlite_thread_end(void);
 | 
						|
static int32_t qc_sqlite_parse(GWBUF* query, uint32_t collect, int32_t* result);
 | 
						|
static int32_t qc_sqlite_get_type_mask(GWBUF* query, uint32_t* typemask);
 | 
						|
static int32_t qc_sqlite_get_operation(GWBUF* query, int32_t* op);
 | 
						|
static int32_t qc_sqlite_get_created_table_name(GWBUF* query, char** name);
 | 
						|
static int32_t qc_sqlite_is_drop_table_query(GWBUF* query, int32_t* is_drop_table);
 | 
						|
static int32_t qc_sqlite_get_table_names(GWBUF* query, int32_t fullnames, char*** names, int* tblsize);
 | 
						|
static int32_t qc_sqlite_get_canonical(GWBUF* query, char** canonical);
 | 
						|
static int32_t qc_sqlite_query_has_clause(GWBUF* query, int32_t* has_clause);
 | 
						|
static int32_t qc_sqlite_get_database_names(GWBUF* query, char*** names, int* sizep);
 | 
						|
static int32_t qc_sqlite_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt);
 | 
						|
static void qc_sqlite_set_server_version(uint64_t version);
 | 
						|
static void qc_sqlite_get_server_version(uint64_t* version);
 | 
						|
static int32_t qc_sqlite_get_sql_mode(qc_sql_mode_t* sql_mode);
 | 
						|
static int32_t qc_sqlite_set_sql_mode(qc_sql_mode_t sql_mode);
 | 
						|
 | 
						|
static bool get_key_and_value(char* arg, const char** pkey, const char** pvalue)
 | 
						|
{
 | 
						|
    char* p = strchr(arg, '=');
 | 
						|
 | 
						|
    if (p)
 | 
						|
    {
 | 
						|
        *p = 0;
 | 
						|
 | 
						|
        *pkey = trim(arg);
 | 
						|
        *pvalue = trim(p + 1);
 | 
						|
    }
 | 
						|
 | 
						|
    return p != NULL;
 | 
						|
}
 | 
						|
 | 
						|
static const char ARG_LOG_UNRECOGNIZED_STATEMENTS[] = "log_unrecognized_statements";
 | 
						|
static const char ARG_PARSE_AS[] = "parse_as";
 | 
						|
 | 
						|
static int32_t qc_sqlite_setup(qc_sql_mode_t sql_mode, const char* cargs)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    assert(!this_unit.setup);
 | 
						|
 | 
						|
    qc_log_level_t log_level = QC_LOG_NOTHING;
 | 
						|
    qc_parse_as_t parse_as = (sql_mode == QC_SQL_MODE_ORACLE) ? QC_PARSE_AS_103 : QC_PARSE_AS_DEFAULT;
 | 
						|
    QC_NAME_MAPPING* function_name_mappings = function_name_mappings_default;
 | 
						|
 | 
						|
    if (cargs)
 | 
						|
    {
 | 
						|
        char args[strlen(cargs) + 1];
 | 
						|
        strcpy(args, cargs);
 | 
						|
 | 
						|
        char *p1;
 | 
						|
        char *token = strtok_r(args, ",", &p1);
 | 
						|
 | 
						|
        while (token)
 | 
						|
        {
 | 
						|
            const char* key;
 | 
						|
            const char* value;
 | 
						|
 | 
						|
            if (get_key_and_value(token, &key, &value))
 | 
						|
            {
 | 
						|
                if (strcmp(key, ARG_LOG_UNRECOGNIZED_STATEMENTS) == 0)
 | 
						|
                {
 | 
						|
                    char *end;
 | 
						|
 | 
						|
                    long l = strtol(value, &end, 0);
 | 
						|
 | 
						|
                    if ((*end == 0) && (l >= QC_LOG_NOTHING) && (l <= QC_LOG_NON_TOKENIZED))
 | 
						|
                    {
 | 
						|
                        log_level = l;
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        MXS_WARNING("'%s' is not a number between %d and %d.",
 | 
						|
                                    value, QC_LOG_NOTHING, QC_LOG_NON_TOKENIZED);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                else if (strcmp(key, ARG_PARSE_AS) == 0)
 | 
						|
                {
 | 
						|
                    if (strcmp(value, "10.3") == 0)
 | 
						|
                    {
 | 
						|
                        parse_as = QC_PARSE_AS_103;
 | 
						|
                        MXS_NOTICE("Parsing as 10.3.");
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        MXS_WARNING("'%s' is not a recognized value for '%s'. "
 | 
						|
                                    "Parsing as pre-10.3.", value, key);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    MXS_WARNING("'%s' is not a recognized argument.", key);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                MXS_WARNING("'%s' is not a recognized argument string.", args);
 | 
						|
            }
 | 
						|
 | 
						|
            token = strtok_r(NULL, ",", &p1);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (sql_mode == QC_SQL_MODE_ORACLE)
 | 
						|
    {
 | 
						|
        function_name_mappings = function_name_mappings_oracle;
 | 
						|
    }
 | 
						|
    else if (parse_as == QC_PARSE_AS_103)
 | 
						|
    {
 | 
						|
        function_name_mappings = function_name_mappings_103;
 | 
						|
    }
 | 
						|
 | 
						|
    this_unit.setup = true;
 | 
						|
    this_unit.log_level = log_level;
 | 
						|
    this_unit.sql_mode = sql_mode;
 | 
						|
    this_unit.parse_as = parse_as;
 | 
						|
    this_unit.function_name_mappings = function_name_mappings;
 | 
						|
 | 
						|
    return this_unit.setup ? QC_RESULT_OK : QC_RESULT_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_process_init(void)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    assert(this_unit.setup);
 | 
						|
    assert(!this_unit.initialized);
 | 
						|
 | 
						|
    if (sqlite3_initialize() == 0)
 | 
						|
    {
 | 
						|
        init_builtin_functions();
 | 
						|
 | 
						|
        this_unit.initialized = true;
 | 
						|
 | 
						|
        if (qc_sqlite_thread_init() == 0)
 | 
						|
        {
 | 
						|
            if (this_unit.log_level != QC_LOG_NOTHING)
 | 
						|
            {
 | 
						|
                const char* message;
 | 
						|
 | 
						|
                switch (this_unit.log_level)
 | 
						|
                {
 | 
						|
                case QC_LOG_NON_PARSED:
 | 
						|
                    message = "Statements that cannot be parsed completely are logged.";
 | 
						|
                    break;
 | 
						|
 | 
						|
                case QC_LOG_NON_PARTIALLY_PARSED:
 | 
						|
                    message = "Statements that cannot even be partially parsed are logged.";
 | 
						|
                    break;
 | 
						|
 | 
						|
                case QC_LOG_NON_TOKENIZED:
 | 
						|
                    message = "Statements that cannot even be classified by keyword matching are logged.";
 | 
						|
                    break;
 | 
						|
 | 
						|
                default:
 | 
						|
                    ss_dassert(!true);
 | 
						|
                }
 | 
						|
 | 
						|
                MXS_NOTICE("%s", message);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            this_unit.initialized = false;
 | 
						|
 | 
						|
            sqlite3_shutdown();
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("Failed to initialize sqlite3.");
 | 
						|
    }
 | 
						|
 | 
						|
    return this_unit.initialized ? QC_RESULT_OK : QC_RESULT_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
static void qc_sqlite_process_end(void)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
 | 
						|
    finish_builtin_functions();
 | 
						|
 | 
						|
    qc_sqlite_thread_end();
 | 
						|
 | 
						|
    sqlite3_shutdown();
 | 
						|
    this_unit.initialized = false;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_thread_init(void)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(!this_thread.initialized);
 | 
						|
 | 
						|
    // TODO: It may be sufficient to have a single in-memory database for all threads.
 | 
						|
    int rc = sqlite3_open(":memory:", &this_thread.db);
 | 
						|
    if (rc == SQLITE_OK)
 | 
						|
    {
 | 
						|
        this_thread.sql_mode = this_unit.sql_mode;
 | 
						|
        this_thread.function_name_mappings = this_unit.function_name_mappings;
 | 
						|
        this_thread.initialized = true;
 | 
						|
 | 
						|
        MXS_INFO("In-memory sqlite database successfully opened for thread %lu.",
 | 
						|
                 (unsigned long) pthread_self());
 | 
						|
 | 
						|
        QC_SQLITE_INFO* info = info_alloc(QC_COLLECT_ALL);
 | 
						|
 | 
						|
        if (info)
 | 
						|
        {
 | 
						|
            this_thread.info = info;
 | 
						|
 | 
						|
            // With this statement we cause sqlite3 to initialize itself, so that it
 | 
						|
            // is not done as part of the actual classification of data.
 | 
						|
            const char* s = "CREATE TABLE __maxscale__internal__ (field int UNIQUE)";
 | 
						|
            size_t len = strlen(s);
 | 
						|
 | 
						|
            this_thread.info->query = s;
 | 
						|
            this_thread.info->query_len = len;
 | 
						|
            this_thread.info->initializing = true;
 | 
						|
            parse_query_string(s, len);
 | 
						|
            this_thread.info->initializing = false;
 | 
						|
            this_thread.info->query = NULL;
 | 
						|
            this_thread.info->query_len = 0;
 | 
						|
 | 
						|
            info_free(this_thread.info);
 | 
						|
            this_thread.info = NULL;
 | 
						|
 | 
						|
            this_thread.initialized = true;
 | 
						|
            this_thread.version_major = 0;
 | 
						|
            this_thread.version_minor = 0;
 | 
						|
            this_thread.version_patch = 0;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            sqlite3_close(this_thread.db);
 | 
						|
            this_thread.db = NULL;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("Failed to open in-memory sqlite database for thread %lu: %d, %s",
 | 
						|
                  (unsigned long) pthread_self(), rc, sqlite3_errstr(rc));
 | 
						|
    }
 | 
						|
 | 
						|
    return this_thread.initialized ? QC_RESULT_OK : QC_RESULT_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
static void qc_sqlite_thread_end(void)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    ss_dassert(this_thread.db);
 | 
						|
    int rc = sqlite3_close(this_thread.db);
 | 
						|
 | 
						|
    if (rc != SQLITE_OK)
 | 
						|
    {
 | 
						|
        MXS_WARNING("The closing of the thread specific sqlite database failed: %d, %s",
 | 
						|
                    rc, sqlite3_errstr(rc));
 | 
						|
    }
 | 
						|
 | 
						|
    this_thread.db = NULL;
 | 
						|
    this_thread.initialized = false;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_parse(GWBUF* query, uint32_t collect, int32_t* result)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, collect);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        *result = info->status;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        *result = QC_QUERY_INVALID;
 | 
						|
    }
 | 
						|
 | 
						|
    return info ? QC_RESULT_OK : QC_RESULT_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_get_type_mask(GWBUF* query, uint32_t* type_mask)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *type_mask = QUERY_TYPE_UNKNOWN;
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_ESSENTIALS);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            *type_mask = info->type_mask;
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report query type");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_get_operation(GWBUF* query, int32_t* op)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *op = QUERY_OP_UNDEFINED;
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_ESSENTIALS);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            *op = info->operation;
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report query operation");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_get_created_table_name(GWBUF* query, char** created_table_name)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *created_table_name = NULL;
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_TABLES);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            if (info->created_table_name)
 | 
						|
            {
 | 
						|
                *created_table_name = MXS_STRDUP(info->created_table_name);
 | 
						|
                MXS_ABORT_IF_NULL(created_table_name);
 | 
						|
                rv = QC_RESULT_OK;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report created tables");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_is_drop_table_query(GWBUF* query, int32_t* is_drop_table)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *is_drop_table = 0;
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_ESSENTIALS);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            *is_drop_table = info->is_drop_table;
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report whether query is drop table");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_get_table_names(GWBUF* query,
 | 
						|
                                         int32_t fullnames,
 | 
						|
                                         char*** table_names,
 | 
						|
                                         int32_t* tblsize)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *table_names = NULL;
 | 
						|
    *tblsize = 0;
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_TABLES);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            if (fullnames)
 | 
						|
            {
 | 
						|
                *table_names = info->table_fullnames;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                *table_names = info->table_names;
 | 
						|
            }
 | 
						|
 | 
						|
            if (*table_names)
 | 
						|
            {
 | 
						|
                *table_names = copy_string_array(*table_names, tblsize);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                *tblsize = 0;
 | 
						|
            }
 | 
						|
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report what tables are accessed");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_get_canonical(GWBUF* query, char** canonical)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *canonical = NULL;
 | 
						|
 | 
						|
    MXS_ERROR("qc_get_canonical not implemented yet.");
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_query_has_clause(GWBUF* query, int32_t* has_clause)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *has_clause = false;
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_ESSENTIALS);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            *has_clause = info->has_clause;
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report whether the query has a where clause");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_get_database_names(GWBUF* query, char*** database_names, int* sizep)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *database_names = NULL;
 | 
						|
    *sizep = 0;
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_DATABASES);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            if (info->database_names)
 | 
						|
            {
 | 
						|
                *database_names = copy_string_array(info->database_names, sizep);
 | 
						|
            }
 | 
						|
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report what databases are accessed");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t qc_sqlite_get_prepare_name(GWBUF* query, char** prepare_name)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *prepare_name = NULL;
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_ESSENTIALS);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            if (info->prepare_name)
 | 
						|
            {
 | 
						|
                *prepare_name = MXS_STRDUP(info->prepare_name);
 | 
						|
            }
 | 
						|
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report the name of a prepared statement");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
int32_t qc_sqlite_get_field_info(GWBUF* query, const QC_FIELD_INFO** infos, uint32_t* n_infos)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *infos = NULL;
 | 
						|
    *n_infos = 0;
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_FIELDS);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            *infos = info->field_infos;
 | 
						|
            *n_infos = info->field_infos_len;
 | 
						|
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report field info");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
int32_t qc_sqlite_get_function_info(GWBUF* query, const QC_FUNCTION_INFO** infos, uint32_t* n_infos)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *infos = NULL;
 | 
						|
    *n_infos = 0;
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(query, QC_COLLECT_FUNCTIONS);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            *infos = info->function_infos;
 | 
						|
            *n_infos = info->function_infos_len;
 | 
						|
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(query, "cannot report field info");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
int32_t qc_sqlite_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
    int32_t rv = QC_RESULT_ERROR;
 | 
						|
    ss_dassert(this_unit.initialized);
 | 
						|
    ss_dassert(this_thread.initialized);
 | 
						|
 | 
						|
    *preparable_stmt = NULL;
 | 
						|
 | 
						|
    QC_SQLITE_INFO* info = get_query_info(stmt, QC_COLLECT_ESSENTIALS);
 | 
						|
 | 
						|
    if (info)
 | 
						|
    {
 | 
						|
        if (qc_info_is_valid(info->status))
 | 
						|
        {
 | 
						|
            *preparable_stmt = info->preparable_stmt;
 | 
						|
            rv = QC_RESULT_OK;
 | 
						|
        }
 | 
						|
        else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
 | 
						|
        {
 | 
						|
            log_invalid_data(stmt, "cannot report field info");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        MXS_ERROR("The query could not be parsed. Response not valid.");
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
static void qc_sqlite_set_server_version(uint64_t version)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    uint32_t major = version / 10000;
 | 
						|
    uint32_t minor = (version - major * 10000) / 100;
 | 
						|
    uint32_t patch = version - major * 10000 - minor * 100;
 | 
						|
 | 
						|
    this_thread.version_major = major;
 | 
						|
    this_thread.version_minor = minor;
 | 
						|
    this_thread.version_patch = patch;
 | 
						|
}
 | 
						|
 | 
						|
static void qc_sqlite_get_server_version(uint64_t* version)
 | 
						|
{
 | 
						|
    QC_TRACE();
 | 
						|
 | 
						|
    *version = this_thread.version_major * 10000 + this_thread.version_minor * 100 + this_thread.version_patch;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
int32_t qc_sqlite_get_sql_mode(qc_sql_mode_t* sql_mode)
 | 
						|
{
 | 
						|
    *sql_mode = this_thread.sql_mode;
 | 
						|
    return QC_RESULT_OK;
 | 
						|
}
 | 
						|
 | 
						|
int32_t qc_sqlite_set_sql_mode(qc_sql_mode_t sql_mode)
 | 
						|
{
 | 
						|
    int32_t rv = QC_RESULT_OK;
 | 
						|
 | 
						|
    switch (sql_mode)
 | 
						|
    {
 | 
						|
    case QC_SQL_MODE_DEFAULT:
 | 
						|
        this_thread.sql_mode = sql_mode;
 | 
						|
        if (this_unit.parse_as == QC_PARSE_AS_103)
 | 
						|
        {
 | 
						|
            this_thread.function_name_mappings = function_name_mappings_103;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            this_thread.function_name_mappings = function_name_mappings_default;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
 | 
						|
    case QC_SQL_MODE_ORACLE:
 | 
						|
        this_thread.sql_mode = sql_mode;
 | 
						|
        this_thread.function_name_mappings = function_name_mappings_oracle;
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        rv = QC_RESULT_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * EXPORTS
 | 
						|
 */
 | 
						|
 | 
						|
MXS_MODULE* MXS_CREATE_MODULE()
 | 
						|
{
 | 
						|
    static QUERY_CLASSIFIER qc =
 | 
						|
    {
 | 
						|
        qc_sqlite_setup,
 | 
						|
        qc_sqlite_process_init,
 | 
						|
        qc_sqlite_process_end,
 | 
						|
        qc_sqlite_thread_init,
 | 
						|
        qc_sqlite_thread_end,
 | 
						|
        qc_sqlite_parse,
 | 
						|
        qc_sqlite_get_type_mask,
 | 
						|
        qc_sqlite_get_operation,
 | 
						|
        qc_sqlite_get_created_table_name,
 | 
						|
        qc_sqlite_is_drop_table_query,
 | 
						|
        qc_sqlite_get_table_names,
 | 
						|
        NULL,
 | 
						|
        qc_sqlite_query_has_clause,
 | 
						|
        qc_sqlite_get_database_names,
 | 
						|
        qc_sqlite_get_prepare_name,
 | 
						|
        qc_sqlite_get_field_info,
 | 
						|
        qc_sqlite_get_function_info,
 | 
						|
        qc_sqlite_get_preparable_stmt,
 | 
						|
        qc_sqlite_set_server_version,
 | 
						|
        qc_sqlite_get_server_version,
 | 
						|
        qc_sqlite_get_sql_mode,
 | 
						|
        qc_sqlite_set_sql_mode,
 | 
						|
    };
 | 
						|
 | 
						|
    static MXS_MODULE info =
 | 
						|
    {
 | 
						|
        MXS_MODULE_API_QUERY_CLASSIFIER,
 | 
						|
        MXS_MODULE_BETA_RELEASE,
 | 
						|
        QUERY_CLASSIFIER_VERSION,
 | 
						|
        "Query classifier using sqlite.",
 | 
						|
        "V1.0.0",
 | 
						|
        MXS_NO_MODULE_CAPABILITIES,
 | 
						|
        &qc,
 | 
						|
        qc_sqlite_process_init,
 | 
						|
        qc_sqlite_process_end,
 | 
						|
        qc_sqlite_thread_init,
 | 
						|
        qc_sqlite_thread_end,
 | 
						|
        {
 | 
						|
            {MXS_END_MODULE_PARAMS}
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    return &info;
 | 
						|
}
 |