/* * 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: 2019-07-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 #include #include #include #include #include #include #include #include #include #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; } /** * 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_SQLITE_INFO; 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; typedef enum qc_sql_mode { QC_SQL_MODE_DEFAULT, QC_SQL_MODE_ORACLE } qc_sql_mode_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 } }; /** * 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; sqlite3* db; // Thread specific database handle. QC_SQLITE_INFO* info; } 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(const char* database, const char* table, const char* column); static bool is_sequence_related_function(const char* func_name); static void log_invalid_data(GWBUF* query, const char* message); static const char* map_function_name(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_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; 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 blinkelichts to // ensure that a statement is parsed at most twice. info->collect = QC_COLLECT_ALL; } 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 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(const char* database, const char* table, const char* column) { return is_sequence_related_function(column); } /** * Returns whether a function is sequence related. * * @param func_name A function. * * @return True, if the function is sequence related, false otherwise. */ static bool is_sequence_related_function(const char* func_name) { bool rv = false; if (this_unit.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); } } } static const char* map_function_name(const char* from) { QC_NAME_MAPPING* map = this_unit.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(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(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 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 (this_unit.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(zToken)) { info->type_mask |= QUERY_TYPE_WRITE; ignore_exprlist = true; } else if (!is_builtin_readonly_function(zToken, this_unit.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 } } 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 ((this_unit.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 (this_unit.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); // 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 (this_unit.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) { switch (token) { case TK_CHARSET: case TK_DO: case TK_HANDLER: if (this_unit.sql_mode == QC_SQL_MODE_ORACLE) { 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 (this_unit.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 (this_unit.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(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 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 const char ARG_SQL_MODE[] = "sql_mode"; static int32_t qc_sqlite_setup(const char* cargs) { QC_TRACE(); assert(!this_unit.setup); qc_log_level_t log_level = QC_LOG_NOTHING; qc_sql_mode_t sql_mode = QC_SQL_MODE_DEFAULT; qc_parse_as_t parse_as = 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 if (strcmp(key, ARG_SQL_MODE) == 0) { if (strcmp(value, "MODE_ORACLE") == 0) { sql_mode = QC_SQL_MODE_ORACLE; MXS_NOTICE("Expecting Oracle SQL."); parse_as = QC_PARSE_AS_103; MXS_NOTICE("Parsing as 10.3."); } else { MXS_WARNING("Unknown value \"%s\" for key \"%s\"", 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.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; } 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; } /** * 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, }; static MXS_MODULE info = { MXS_MODULE_API_QUERY_CLASSIFIER, MXS_MODULE_BETA_RELEASE, QUERY_CLASSIFIER_VERSION, "Query classifier using sqlite.", "V1.0.0", &qc, qc_sqlite_process_init, qc_sqlite_process_end, qc_sqlite_thread_init, qc_sqlite_thread_end, { {MXS_END_MODULE_PARAMS} } }; return &info; }