 7b001994b4
			
		
	
	7b001994b4
	
	
	
		
			
			A statement like SELECT ... INTO OUTFILE|DUMPFILE ... is now classified as a QUERY_TYPE_WRITE, instead of as QUERY_TYPE_GSYSVAR_WRITE so that it will be sent only to the master.
		
			
				
	
	
		
			5179 lines
		
	
	
		
			156 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			5179 lines
		
	
	
		
			156 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: 2022-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.
 | |
|  */
 | |
| 
 | |
| // NOTE: qc_sqlite used to be C. So as to be able to use STL collections,
 | |
| // NOTE: it has been ported to C++. However, the porting is only partial,
 | |
| // NOTE: which is the reason why there is a mix of C-style and C++-style
 | |
| // NOTE: approaches.
 | |
| 
 | |
| #define MXS_MODULE_NAME "qc_sqlite"
 | |
| #include <sqliteInt.h>
 | |
| 
 | |
| #include <signal.h>
 | |
| #include <string.h>
 | |
| #include <algorithm>
 | |
| #include <map>
 | |
| #include <new>
 | |
| #include <string>
 | |
| #include <vector>
 | |
| #include <mutex>
 | |
| 
 | |
| #include <maxscale/alloc.h>
 | |
| #include <maxscale/log.h>
 | |
| #include <maxscale/modinfo.h>
 | |
| #include <maxscale/modutil.h>
 | |
| #include <maxscale/protocol/mysql.h>
 | |
| #include <maxscale/query_classifier.h>
 | |
| #include <maxscale/utils.h>
 | |
| 
 | |
| #include "builtin_functions.h"
 | |
| 
 | |
| using std::vector;
 | |
| 
 | |
| // #define QC_TRACE_ENABLED
 | |
| #undef QC_TRACE_ENABLED
 | |
| 
 | |
| #if defined (QC_TRACE_ENABLED)
 | |
| #define QC_TRACE() MXS_NOTICE(__func__)
 | |
| #else
 | |
| #define QC_TRACE()
 | |
| #endif
 | |
| 
 | |
| #define QC_EXCEPTION_GUARD(statement) \
 | |
|     do {try {statement;} \
 | |
|         catch (const std::bad_alloc&) { \
 | |
|             MXS_OOM(); pInfo->m_status = QC_QUERY_INVALID;} \
 | |
|         catch (const std::exception& x) { \
 | |
|             MXS_ERROR("Caught standard exception: %s", x.what()); pInfo->m_status = QC_QUERY_INVALID;} \
 | |
|         catch (...) { \
 | |
|             MXS_ERROR("Caught unknown exception."); pInfo->m_status = QC_QUERY_INVALID;}} while (false)
 | |
| 
 | |
| 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[] =
 | |
| {
 | |
|     // NOTE: If something is added here, add it to function_name_mappings_oracle as well.
 | |
|     {"now", "current_timestamp"},
 | |
|     {NULL,  NULL               }
 | |
| };
 | |
| 
 | |
| static QC_NAME_MAPPING function_name_mappings_oracle[] =
 | |
| {
 | |
|     {"now", "current_timestamp"},
 | |
|     {"nvl", "ifnull"           },
 | |
|     {NULL,  NULL               }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Stores alias information. The key in the mapping is the alias name,
 | |
|  * and an instance of this struct contains the actual table/database.
 | |
|  *
 | |
|  * zDatabase and zTable point to memory that belongs to QcSqliteInfo
 | |
|  * so they can be simply copied in all contexts.
 | |
|  */
 | |
| 
 | |
| struct QcAliasValue
 | |
| {
 | |
|     QcAliasValue(const char* zD, const char* zT)
 | |
|         : zDatabase(zD)
 | |
|         , zTable(zT)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     const char* zDatabase;
 | |
|     const char* zTable;
 | |
| };
 | |
| 
 | |
| typedef std::map<std::string, QcAliasValue> QcAliases;
 | |
| 
 | |
| /**
 | |
|  * 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* pFunction_name_mappings;
 | |
|     std::mutex       lock;
 | |
| } this_unit;
 | |
| 
 | |
| /**
 | |
|  * The qc_sqlite thread-specific state.
 | |
|  */
 | |
| class QcSqliteInfo;
 | |
| 
 | |
| static thread_local struct
 | |
| {
 | |
|     bool             initialized;           // Whether the thread specific data has been initialized.
 | |
|     sqlite3*         pDb;                   // Thread specific database handle.
 | |
|     qc_sql_mode_t    sql_mode;              // What sql_mode is used.
 | |
|     QcSqliteInfo*    pInfo;                 // The information for the current statement being classified.
 | |
|     uint64_t         version;               // Encoded version number
 | |
|     uint32_t         version_major;
 | |
|     uint32_t         version_minor;
 | |
|     uint32_t         version_patch;
 | |
|     QC_NAME_MAPPING* pFunction_name_mappings;   // How function names should be mapped.
 | |
| } this_thread;
 | |
| 
 | |
| const uint64_t VERSION_103 = 10 * 10000 + 3 * 100;
 | |
| 
 | |
| /**
 | |
|  * 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 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        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, int len, bool suppress_logging);
 | |
| static bool        query_is_parsed(GWBUF* query, uint32_t collect);
 | |
| static bool        should_exclude(const char* zName, const ExprList* pExclude);
 | |
| static const char* get_token_symbol(int token);
 | |
| 
 | |
| // Defined in parse.y
 | |
| extern "C"
 | |
| {
 | |
| 
 | |
|     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 void exposed_sqlite3Insert(Parse* pParse,
 | |
|                                       SrcList* pTabList,
 | |
|                                       Select*  pSelect,
 | |
|                                       IdList*  pColumns,
 | |
|                                       int onError);
 | |
|     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 exposed_sqlite3Update(Parse* pParse,
 | |
|                                       SrcList* pTabList,
 | |
|                                       ExprList* pChanges,
 | |
|                                       Expr* pWhere,
 | |
|                                       int   onError);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Contains information about a particular query.
 | |
|  */
 | |
| class QcSqliteInfo : public QC_STMT_INFO
 | |
| {
 | |
|     QcSqliteInfo(const QcSqliteInfo&);
 | |
|     QcSqliteInfo& operator=(const QcSqliteInfo&);
 | |
| 
 | |
| public:
 | |
|     void inc_ref()
 | |
|     {
 | |
|         mxb_assert(m_refs > 0);
 | |
|         ++m_refs;
 | |
|     }
 | |
| 
 | |
|     void dec_ref()
 | |
|     {
 | |
|         mxb_assert(m_refs > 0);
 | |
|         if (--m_refs == 0)
 | |
|         {
 | |
|             delete this;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static QcSqliteInfo* create(uint32_t collect)
 | |
|     {
 | |
|         QcSqliteInfo* pInfo = new(std::nothrow) QcSqliteInfo(collect);
 | |
|         mxb_assert(pInfo);
 | |
|         return pInfo;
 | |
|     }
 | |
| 
 | |
|     static QcSqliteInfo* get(GWBUF* pStmt, uint32_t collect)
 | |
|     {
 | |
|         QcSqliteInfo* pInfo = NULL;
 | |
| 
 | |
|         if (ensure_query_is_parsed(pStmt, collect))
 | |
|         {
 | |
|             pInfo = (QcSqliteInfo*) gwbuf_get_buffer_object_data(pStmt, GWBUF_PARSING_INFO);
 | |
|             mxb_assert(pInfo);
 | |
|         }
 | |
| 
 | |
|         return pInfo;
 | |
|     }
 | |
| 
 | |
|     static void finish_field_info(QC_FIELD_INFO& info)
 | |
|     {
 | |
|         MXS_FREE(info.database);
 | |
|         MXS_FREE(info.table);
 | |
|         MXS_FREE(info.column);
 | |
|     }
 | |
| 
 | |
|     static void finish_function_info(QC_FUNCTION_INFO& info)
 | |
|     {
 | |
|         MXS_FREE(info.name);
 | |
| 
 | |
|         std::for_each(info.fields, info.fields + info.n_fields, finish_field_info);
 | |
|     }
 | |
| 
 | |
|     bool is_valid() const
 | |
|     {
 | |
|         return m_status != QC_QUERY_INVALID;
 | |
|     }
 | |
| 
 | |
|     bool get_type_mask(uint32_t* pType_mask) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *pType_mask = m_type_mask;
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool get_operation(int32_t* pOp) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *pOp = m_operation;
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool get_created_table_name(char** pzCreated_table_name) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             if (m_zCreated_table_name)
 | |
|             {
 | |
|                 *pzCreated_table_name = MXS_STRDUP(m_zCreated_table_name);
 | |
|                 MXS_ABORT_IF_NULL(*pzCreated_table_name);
 | |
|             }
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool is_drop_table_query(int32_t* pIs_drop_table)
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *pIs_drop_table = m_is_drop_table;
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool get_table_names(int32_t fullnames, char*** ppzTable_names, int32_t* pnTable_names) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             const vector<char*>* pNames;
 | |
| 
 | |
|             if (fullnames)
 | |
|             {
 | |
|                 pNames = &m_table_fullnames;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 pNames = &m_table_names;
 | |
|             }
 | |
| 
 | |
|             *pnTable_names = pNames->size();
 | |
| 
 | |
|             if (*pnTable_names)
 | |
|             {
 | |
|                 *ppzTable_names = copy_string_array(*pNames);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 *ppzTable_names = NULL;
 | |
|             }
 | |
| 
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool query_has_clause(int32_t* pHas_clause) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *pHas_clause = m_has_clause;
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool get_database_names(char*** ppzDatabase_names, int* pnDatabase_names) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *pnDatabase_names = m_database_names.size();
 | |
| 
 | |
|             if (*pnDatabase_names)
 | |
|             {
 | |
|                 *ppzDatabase_names = copy_string_array(m_database_names);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 *ppzDatabase_names = NULL;
 | |
|             }
 | |
| 
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool get_prepare_name(char** pzPrepare_name) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *pzPrepare_name = NULL;
 | |
| 
 | |
|             if (m_zPrepare_name)
 | |
|             {
 | |
|                 *pzPrepare_name = MXS_STRDUP(m_zPrepare_name);
 | |
|                 MXS_ABORT_IF_NULL(*pzPrepare_name);
 | |
|             }
 | |
| 
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool get_field_info(const QC_FIELD_INFO** ppInfos, uint32_t* pnInfos) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *ppInfos = m_field_infos.size() ? &m_field_infos[0] : NULL;
 | |
|             *pnInfos = m_field_infos.size();
 | |
| 
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool get_function_info(const QC_FUNCTION_INFO** ppInfos, uint32_t* pnInfos) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *ppInfos = m_function_infos.size() ? &m_function_infos[0] : NULL;
 | |
|             *pnInfos = m_function_infos.size();
 | |
| 
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     bool get_preparable_stmt(GWBUF** ppPreparable_stmt) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (is_valid())
 | |
|         {
 | |
|             *ppPreparable_stmt = m_pPreparable_stmt;
 | |
|             rv = true;
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     // PUBLIC for now at least.
 | |
| 
 | |
|     /**
 | |
|      * Returns whether sequence related functions should be checked for.
 | |
|      *
 | |
|      * Only if we are in Oracle mode or parsing as 10.3 we need to check.
 | |
|      *
 | |
|      * @return True, if they need to be checked for, false otherwise.
 | |
|      */
 | |
|     bool must_check_sequence_related_functions() const
 | |
|     {
 | |
|         return (m_sql_mode == QC_SQL_MODE_ORACLE)
 | |
|                || (this_unit.parse_as == QC_PARSE_AS_103)
 | |
|                || (this_thread.version >= VERSION_103);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns whether fields should be collected.
 | |
|      *
 | |
|      * @return True, if should be, false otherwise.
 | |
|      */
 | |
|     bool must_collect_fields() const
 | |
|     {
 | |
|         // We must collect if fields should be collected and they have not
 | |
|         // been collected yet.
 | |
|         return (m_collect & QC_COLLECT_FIELDS) && !(m_collected & QC_COLLECT_FIELDS);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns whether a function is sequence related.
 | |
|      *
 | |
|      * @param zFunc_name  A function name.
 | |
|      *
 | |
|      * @return True, if the function is sequence related, false otherwise.
 | |
|      */
 | |
|     bool is_sequence_related_function(const char* zFunc_name) const
 | |
|     {
 | |
|         bool rv = false;
 | |
| 
 | |
|         if (m_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(zFunc_name, "currval") == 0)
 | |
|                 || (strcasecmp(zFunc_name, "nextval") == 0)
 | |
|                 || (strcasecmp(zFunc_name, "lastval") == 0))
 | |
|             {
 | |
|                 rv = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!rv && ((this_unit.parse_as == QC_PARSE_AS_103) || (this_thread.version >= VERSION_103)))
 | |
|         {
 | |
|             if ((strcasecmp(zFunc_name, "lastval") == 0)
 | |
|                 || (strcasecmp(zFunc_name, "nextval") == 0))
 | |
|             {
 | |
|                 rv = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns whether a field is sequence related.
 | |
|      *
 | |
|      * @param zDatabase  The database/schema or NULL.
 | |
|      * @param zTable     The table or NULL.
 | |
|      * @param zColumn    The column.
 | |
|      *
 | |
|      * @return True, if the field is sequence related, false otherwise.
 | |
|      */
 | |
|     bool is_sequence_related_field(const char* zDatabase,
 | |
|                                    const char* zTable,
 | |
|                                    const char* zColumn) const
 | |
|     {
 | |
|         return is_sequence_related_function(zColumn);
 | |
|     }
 | |
| 
 | |
|     static void honour_aliases(const QcAliases* pAliases,
 | |
|                                const char** pzDatabase,
 | |
|                                const char** pzTable)
 | |
|     {
 | |
|         const char*& zDatabase = *pzDatabase;
 | |
|         const char*& zTable = *pzTable;
 | |
| 
 | |
|         if (!zDatabase && zTable && pAliases)
 | |
|         {
 | |
|             QcAliases::const_iterator i = pAliases->find(zTable);
 | |
| 
 | |
|             if (i != pAliases->end())
 | |
|             {
 | |
|                 const QcAliasValue& value = i->second;
 | |
| 
 | |
|                 zDatabase = value.zDatabase;
 | |
|                 zTable = value.zTable;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // QC_FIELD_NAME or QC_FIELD_INFO
 | |
|     template<class T>
 | |
|     class MatchFieldName : public std::unary_function<T, bool>
 | |
|     {
 | |
|     public:
 | |
|         MatchFieldName(const char* zDatabase,
 | |
|                        const char* zTable,
 | |
|                        const char* zColumn)
 | |
|             : m_zDatabase(zDatabase)
 | |
|             , m_zTable(zTable)
 | |
|             , m_zColumn(zColumn)
 | |
|         {
 | |
|             mxb_assert(zColumn);
 | |
|         }
 | |
| 
 | |
|         bool operator()(const T& t)
 | |
|         {
 | |
|             bool rv = false;
 | |
| 
 | |
|             if (strcasecmp(m_zColumn, t.column) == 0)
 | |
|             {
 | |
|                 if (!m_zTable && !t.table)
 | |
|                 {
 | |
|                     mxb_assert(!m_zDatabase && !t.database);
 | |
|                     rv = true;
 | |
|                 }
 | |
|                 else if (m_zTable && t.table && (strcasecmp(m_zTable, t.table) == 0))
 | |
|                 {
 | |
|                     if (!m_zDatabase && !t.database)
 | |
|                     {
 | |
|                         rv = true;
 | |
|                     }
 | |
|                     else if (m_zDatabase
 | |
|                              && t.database
 | |
|                              && (strcasecmp(m_zDatabase, t.database) == 0))
 | |
|                     {
 | |
|                         rv = true;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return rv;
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         const char* m_zDatabase;
 | |
|         const char* m_zTable;
 | |
|         const char* m_zColumn;
 | |
|     };
 | |
| 
 | |
|     void update_field_info(const QcAliases* pAliases,
 | |
|                            const char* zDatabase,
 | |
|                            const char* zTable,
 | |
|                            const char* zColumn,
 | |
|                            const ExprList* pExclude)
 | |
|     {
 | |
|         mxb_assert(zColumn);
 | |
| 
 | |
|         // NOTE: This must be first, so that the type mask is properly updated
 | |
|         // NOTE: in case zColumn is "currval" etc.
 | |
|         if (must_check_sequence_related_functions()
 | |
|             && is_sequence_related_field(zDatabase, zTable, zColumn))
 | |
|         {
 | |
|             m_type_mask |= QUERY_TYPE_WRITE;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (!must_collect_fields())
 | |
|         {
 | |
|             // If field information should not be collected, or if field information
 | |
|             // has already been collected, we just return.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         honour_aliases(pAliases, &zDatabase, &zTable);
 | |
| 
 | |
|         MatchFieldName<QC_FIELD_INFO> predicate(zDatabase, zTable, zColumn);
 | |
| 
 | |
|         vector<QC_FIELD_INFO>::iterator i = find_if(m_field_infos.begin(),
 | |
|                                                     m_field_infos.end(),
 | |
|                                                     predicate);
 | |
| 
 | |
|         if (i == m_field_infos.end())   // 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 (!(zColumn && !zTable && !zDatabase && pExclude && should_exclude(zColumn, pExclude)))
 | |
|             {
 | |
|                 QC_FIELD_INFO item;
 | |
| 
 | |
|                 item.database = zDatabase ? MXS_STRDUP(zDatabase) : NULL;
 | |
|                 item.table = zTable ? MXS_STRDUP(zTable) : NULL;
 | |
|                 mxb_assert(zColumn);
 | |
|                 item.column = MXS_STRDUP(zColumn);
 | |
| 
 | |
|                 // We are happy if we at least could dup the column.
 | |
| 
 | |
|                 if (item.column)
 | |
|                 {
 | |
|                     m_field_infos.push_back(item);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void update_names(const char* zDatabase, const char* zTable, const char* zAlias, QcAliases* pAliases)
 | |
|     {
 | |
|         mxb_assert(zTable);
 | |
| 
 | |
|         bool should_collect_alias = pAliases && zAlias && should_collect(QC_COLLECT_FIELDS);
 | |
|         bool should_collect_table = should_collect_alias || should_collect(QC_COLLECT_TABLES);
 | |
|         bool should_collect_database = zDatabase
 | |
|             && (should_collect_alias || should_collect(QC_COLLECT_DATABASES));
 | |
| 
 | |
|         if (should_collect_table || should_collect_database)
 | |
|         {
 | |
|             const char* zCollected_database = NULL;
 | |
|             const char* zCollected_table = NULL;
 | |
| 
 | |
|             size_t nDatabase = zDatabase ? strlen(zDatabase) : 0;
 | |
|             size_t nTable = zTable ? strlen(zTable) : 0;
 | |
| 
 | |
|             char database[nDatabase + 1];
 | |
|             char table[nTable + 1];
 | |
| 
 | |
|             if (zDatabase)
 | |
|             {
 | |
|                 strcpy(database, zDatabase);
 | |
|                 exposed_sqlite3Dequote(database);
 | |
|             }
 | |
| 
 | |
|             if (should_collect_table)
 | |
|             {
 | |
|                 if (strcasecmp(zTable, "DUAL") != 0)
 | |
|                 {
 | |
|                     strcpy(table, zTable);
 | |
|                     exposed_sqlite3Dequote(table);
 | |
| 
 | |
|                     zCollected_table = update_table_names(database, nDatabase, table, nTable);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (should_collect_database)
 | |
|             {
 | |
|                 zCollected_database = update_database_names(database);
 | |
|             }
 | |
| 
 | |
|             if (pAliases && zCollected_table && zAlias)
 | |
|             {
 | |
|                 QcAliasValue value(zCollected_database, zCollected_table);
 | |
| 
 | |
|                 pAliases->insert(QcAliases::value_type(zAlias, value));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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:
 | |
|                 mxb_assert(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;
 | |
|     }
 | |
| 
 | |
|     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 update_field_infos(QcAliases* pAliases,
 | |
|                             int prev_token,
 | |
|                             const Expr* pExpr,
 | |
|                             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(pAliases, pExpr, pExclude);
 | |
|             break;
 | |
| 
 | |
|         case TK_DOT:    // select a.b ... select a.b.c
 | |
|             update_field_infos_from_expr(pAliases, pExpr, pExclude);
 | |
|             break;
 | |
| 
 | |
|         case TK_ID:     // select a
 | |
|             update_field_infos_from_expr(pAliases, pExpr, pExclude);
 | |
|             break;
 | |
| 
 | |
|         case TK_VARIABLE:
 | |
|             {
 | |
|                 if (zToken[0] == '@')
 | |
|                 {
 | |
|                     if (zToken[1] == '@')
 | |
|                     {
 | |
|                         // TODO: This should actually be "... && (m_operation == QUERY_OP_SET)"
 | |
|                         // TODO: but there is no QUERY_OP_SET at the moment.
 | |
|                         if ((prev_token == TK_EQ) && (pos == QC_TOKEN_LEFT)
 | |
|                             && (m_operation != QUERY_OP_SELECT))
 | |
|                         {
 | |
|                             m_type_mask |= QUERY_TYPE_GSYSVAR_WRITE;
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             if ((strcasecmp(&zToken[2], "identity") == 0)
 | |
|                                 || (strcasecmp(&zToken[2], "last_insert_id") == 0))
 | |
|                             {
 | |
|                                 m_type_mask |= QUERY_TYPE_MASTER_READ;
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 m_type_mask |= QUERY_TYPE_SYSVAR_READ;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         if ((prev_token == TK_EQ) && (pos == QC_TOKEN_LEFT))
 | |
|                         {
 | |
|                             m_type_mask |= QUERY_TYPE_USERVAR_WRITE;
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             m_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:
 | |
|             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:
 | |
|                 {
 | |
|                     int i = update_function_info(pAliases,
 | |
|                                                  get_token_symbol(pExpr->op),
 | |
|                                                  pExclude);
 | |
| 
 | |
|                     if (i != -1)
 | |
|                     {
 | |
|                         vector<QC_FIELD_INFO>& fields = m_function_field_usage[i];
 | |
| 
 | |
|                         if (pExpr->pLeft)
 | |
|                         {
 | |
|                             update_function_fields(pAliases, pExpr->pLeft, pExclude, fields);
 | |
|                         }
 | |
| 
 | |
|                         if (pExpr->pRight)
 | |
|                         {
 | |
|                             update_function_fields(pAliases, pExpr->pRight, pExclude, fields);
 | |
|                         }
 | |
| 
 | |
|                         if (fields.size() != 0)
 | |
|                         {
 | |
|                             QC_FUNCTION_INFO& info = m_function_infos[i];
 | |
| 
 | |
|                             info.fields = &fields[0];
 | |
|                             info.n_fields = fields.size();
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_REM:
 | |
|                 if (m_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(pAliases, sqlrowcount, pExclude);
 | |
| 
 | |
|                         pLeft = NULL;
 | |
|                         pRight = NULL;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         update_function_info(pAliases, get_token_symbol(pExpr->op), pExclude);
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     update_function_info(pAliases, get_token_symbol(pExpr->op), pExclude);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_UMINUS:
 | |
|                 switch (this_unit.parse_as)
 | |
|                 {
 | |
|                 case QC_PARSE_AS_DEFAULT:
 | |
|                     update_function_info(pAliases, get_token_symbol(pExpr->op), pExclude);
 | |
|                     break;
 | |
| 
 | |
|                 case QC_PARSE_AS_103:
 | |
|                     // In MariaDB 10.3 a unary minus is not considered a function.
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     mxb_assert(!true);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_FUNCTION:
 | |
|                 if (zToken)
 | |
|                 {
 | |
|                     if (strcasecmp(zToken, "last_insert_id") == 0)
 | |
|                     {
 | |
|                         m_type_mask |= (QUERY_TYPE_READ | QUERY_TYPE_MASTER_READ);
 | |
|                     }
 | |
|                     else if (is_sequence_related_function(zToken))
 | |
|                     {
 | |
|                         m_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,
 | |
|                                                            m_sql_mode == QC_SQL_MODE_ORACLE))
 | |
|                     {
 | |
|                         m_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(pAliases, zToken, pExpr->x.pList, pExclude);
 | |
|                     }
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if (pLeft)
 | |
|             {
 | |
|                 update_field_infos(pAliases, pExpr->op, pExpr->pLeft, QC_TOKEN_LEFT, pExclude);
 | |
|             }
 | |
| 
 | |
|             if (pRight)
 | |
|             {
 | |
|                 update_field_infos(pAliases, pExpr->op, pExpr->pRight, QC_TOKEN_RIGHT, pExclude);
 | |
|             }
 | |
| 
 | |
|             if (pExpr->x.pList)
 | |
|             {
 | |
|                 switch (pExpr->op)
 | |
|                 {
 | |
|                 case TK_FUNCTION:
 | |
|                     if (!ignore_exprlist)
 | |
|                     {
 | |
|                         update_field_infos_from_exprlist(pAliases, pExpr->x.pList, pExclude);
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 case TK_BETWEEN:
 | |
|                 case TK_CASE:
 | |
|                 case TK_EXISTS:
 | |
|                 case TK_IN:
 | |
|                 case TK_SELECT:
 | |
|                     {
 | |
|                         const char* zName = NULL;
 | |
| 
 | |
|                         switch (pExpr->op)
 | |
|                         {
 | |
|                         case TK_BETWEEN:
 | |
|                         case TK_CASE:
 | |
|                         case TK_IN:
 | |
|                             zName = get_token_symbol(pExpr->op);
 | |
|                             break;
 | |
|                         }
 | |
| 
 | |
|                         if (pExpr->flags & EP_xIsSelect)
 | |
|                         {
 | |
|                             mxb_assert(pAliases);
 | |
|                             update_field_infos_from_subselect(*pAliases, pExpr->x.pSelect, pExclude);
 | |
| 
 | |
| 
 | |
|                             if (zName)
 | |
|                             {
 | |
|                                 update_function_info(pAliases,
 | |
|                                                      zName,
 | |
|                                                      pExpr->x.pSelect->pEList,
 | |
|                                                      pExclude);
 | |
|                             }
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             update_field_infos_from_exprlist(pAliases, pExpr->x.pList, pExclude);
 | |
| 
 | |
|                             if (zName)
 | |
|                             {
 | |
|                                 update_function_info(pAliases,
 | |
|                                                      zName,
 | |
|                                                      pExpr->x.pList,
 | |
|                                                      pExclude);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static bool get_field_name(const Expr* pExpr,
 | |
|                                const char** pzDatabase,
 | |
|                                const char** pzTable,
 | |
|                                const char** pzColumn)
 | |
|     {
 | |
|         const char*& zDatabase = *pzDatabase;
 | |
|         const char*& zTable = *pzTable;
 | |
|         const char*& zColumn = *pzColumn;
 | |
| 
 | |
|         zDatabase = NULL;
 | |
|         zTable = NULL;
 | |
|         zColumn = NULL;
 | |
| 
 | |
|         if (pExpr->op == TK_ASTERISK)
 | |
|         {
 | |
|             zColumn = (char*)"*";
 | |
|         }
 | |
|         else if (pExpr->op == TK_ID)
 | |
|         {
 | |
|             // select a from...
 | |
|             zColumn = 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...
 | |
|                 zTable = pExpr->pLeft->u.zToken;
 | |
|                 if (pExpr->pRight->op == TK_ID)
 | |
|                 {
 | |
|                     zColumn = pExpr->pRight->u.zToken;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     zColumn = (char*)"*";
 | |
|                 }
 | |
|             }
 | |
|             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...
 | |
|                 zDatabase = pExpr->pLeft->u.zToken;
 | |
|                 zTable = pExpr->pRight->pLeft->u.zToken;
 | |
|                 if (pExpr->pRight->pRight->op == TK_ID)
 | |
|                 {
 | |
|                     zColumn = pExpr->pRight->pRight->u.zToken;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     zColumn = (char*)"*";
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (zColumn)
 | |
|         {
 | |
|             if ((pExpr->flags & EP_DblQuoted) == 0)
 | |
|             {
 | |
|                 if ((strcasecmp(zColumn, "true") == 0) || (strcasecmp(zColumn, "false") == 0))
 | |
|                 {
 | |
|                     zDatabase = NULL;
 | |
|                     zTable = NULL;
 | |
|                     zColumn = NULL;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return zColumn != NULL;
 | |
|     }
 | |
| 
 | |
|     void update_field_infos_from_expr(QcAliases* pAliases,
 | |
|                                       const Expr* pExpr,
 | |
|                                       const ExprList* pExclude)
 | |
|     {
 | |
|         const char* zDatabase;
 | |
|         const char* zTable;
 | |
|         const char* zColumn;
 | |
| 
 | |
|         if (must_check_sequence_related_functions() || must_collect_fields())
 | |
|         {
 | |
|             if (get_field_name(pExpr, &zDatabase, &zTable, &zColumn))
 | |
|             {
 | |
|                 update_field_info(pAliases, zDatabase, zTable, zColumn, pExclude);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void update_field_infos_from_exprlist(QcAliases* pAliases,
 | |
|                                           const ExprList* pEList,
 | |
|                                           const ExprList* pExclude)
 | |
|     {
 | |
|         for (int i = 0; i < pEList->nExpr; ++i)
 | |
|         {
 | |
|             ExprList::ExprList_item* pItem = &pEList->a[i];
 | |
| 
 | |
|             update_field_infos(pAliases, 0, pItem->pExpr, QC_TOKEN_MIDDLE, pExclude);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void update_field_infos_from_idlist(QcAliases* pAliases,
 | |
|                                         const IdList* pIds,
 | |
|                                         const ExprList* pExclude)
 | |
|     {
 | |
|         if (must_check_sequence_related_functions() || must_collect_fields())
 | |
|         {
 | |
|             for (int i = 0; i < pIds->nId; ++i)
 | |
|             {
 | |
|                 IdList::IdList_item* pItem = &pIds->a[i];
 | |
| 
 | |
|                 update_field_info(pAliases, NULL, NULL, pItem->zName, pExclude);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     enum compound_approach_t
 | |
|     {
 | |
|         ANALYZE_COMPOUND_SELECTS,
 | |
|         IGNORE_COMPOUND_SELECTS
 | |
|     };
 | |
| 
 | |
|     void update_field_infos_from_select(QcAliases& aliases,
 | |
|                                         const Select* pSelect,
 | |
|                                         const ExprList* pExclude,
 | |
|                                         compound_approach_t compound_approach = ANALYZE_COMPOUND_SELECTS)
 | |
|     {
 | |
|         if (pSelect->pSrc)
 | |
|         {
 | |
|             const SrcList* pSrc = pSelect->pSrc;
 | |
| 
 | |
|             for (int i = 0; i < pSrc->nSrc; ++i)
 | |
|             {
 | |
|                 if (pSrc->a[i].zName)
 | |
|                 {
 | |
|                     update_names(pSrc->a[i].zDatabase, pSrc->a[i].zName, pSrc->a[i].zAlias, &aliases);
 | |
|                 }
 | |
| 
 | |
|                 if (pSrc->a[i].pSelect)
 | |
|                 {
 | |
|                     update_field_infos_from_select(aliases, pSrc->a[i].pSelect, 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(this, aliases, pSrc->a[i].pUsing, 0, pSelect->pEList);
 | |
|                 }
 | |
| #endif
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (pSelect->pEList)
 | |
|         {
 | |
|             update_field_infos_from_exprlist(&aliases, pSelect->pEList, NULL);
 | |
|         }
 | |
| 
 | |
|         if (pSelect->pWhere)
 | |
|         {
 | |
|             m_has_clause = true;
 | |
|             update_field_infos(&aliases,
 | |
|                                0,
 | |
|                                pSelect->pWhere,
 | |
|                                QC_TOKEN_MIDDLE,
 | |
|                                pSelect->pEList);
 | |
|         }
 | |
| 
 | |
|         if (pSelect->pGroupBy)
 | |
|         {
 | |
|             update_field_infos_from_exprlist(&aliases,
 | |
|                                              pSelect->pGroupBy,
 | |
|                                              pSelect->pEList);
 | |
|         }
 | |
| 
 | |
|         if (pSelect->pHaving)
 | |
|         {
 | |
|             m_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(aliases, 0, pSelect->pHaving, 0, QC_TOKEN_MIDDLE, pSelect->pEList);
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         if (pSelect->pWith)
 | |
|         {
 | |
|             update_field_infos_from_with(&aliases, pSelect->pWith);
 | |
|         }
 | |
| 
 | |
|         if (compound_approach == ANALYZE_COMPOUND_SELECTS)
 | |
|         {
 | |
|             if (((pSelect->op == TK_UNION) || (pSelect->op == TK_ALL)) && pSelect->pPrior)
 | |
|             {
 | |
|                 const Select* pPrior = pSelect->pPrior;
 | |
| 
 | |
|                 while (pPrior)
 | |
|                 {
 | |
|                     update_field_infos_from_subselect(aliases,
 | |
|                                                       pPrior,
 | |
|                                                       pExclude,
 | |
|                                                       IGNORE_COMPOUND_SELECTS);
 | |
|                     pPrior = pPrior->pPrior;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void update_field_infos_from_subselect(const QcAliases& existing_aliases,
 | |
|                                            const Select* pSelect,
 | |
|                                            const ExprList* pExclude,
 | |
|                                            compound_approach_t compound_approach = ANALYZE_COMPOUND_SELECTS)
 | |
|     {
 | |
|         QcAliases aliases(existing_aliases);
 | |
| 
 | |
|         update_field_infos_from_select(aliases, pSelect, pExclude, compound_approach);
 | |
|     }
 | |
| 
 | |
|     void update_field_infos_from_with(QcAliases* pAliases, const With* pWith)
 | |
|     {
 | |
|         for (int i = 0; i < pWith->nCte; ++i)
 | |
|         {
 | |
|             const With::Cte* pCte = &pWith->a[i];
 | |
| 
 | |
|             if (pCte->pSelect)
 | |
|             {
 | |
|                 mxb_assert(pAliases);
 | |
|                 update_field_infos_from_subselect(*pAliases, pCte->pSelect, NULL);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void update_names_from_srclist(QcAliases* pAliases,
 | |
|                                    const SrcList* pSrc)
 | |
|     {
 | |
|         for (int i = 0; i < pSrc->nSrc; ++i)
 | |
|         {
 | |
|             if (pSrc->a[i].zName)
 | |
|             {
 | |
|                 update_names(pSrc->a[i].zDatabase, pSrc->a[i].zName, pSrc->a[i].zAlias, pAliases);
 | |
|             }
 | |
| 
 | |
|             if (pSrc->a[i].pSelect && pSrc->a[i].pSelect->pSrc)
 | |
|             {
 | |
|                 update_names_from_srclist(pAliases, pSrc->a[i].pSelect->pSrc);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static void update_function_fields(const QcAliases* pAliases,
 | |
|                                        const char* zDatabase,
 | |
|                                        const char* zTable,
 | |
|                                        const char* zColumn,
 | |
|                                        vector<QC_FIELD_INFO>& fields)
 | |
|     {
 | |
|         mxb_assert(zColumn);
 | |
| 
 | |
|         honour_aliases(pAliases, &zDatabase, &zTable);
 | |
| 
 | |
|         MatchFieldName<QC_FIELD_INFO> predicate(zDatabase, zTable, zColumn);
 | |
| 
 | |
|         vector<QC_FIELD_INFO>::iterator i = find_if(fields.begin(), fields.end(), predicate);
 | |
| 
 | |
|         if (i == fields.end())      // Not present
 | |
|         {
 | |
|             // TODO: Add exclusion?
 | |
|             QC_FIELD_INFO item;
 | |
| 
 | |
|             item.database = zDatabase ? MXS_STRDUP(zDatabase) : NULL;
 | |
|             item.table = zTable ? MXS_STRDUP(zTable) : NULL;
 | |
|             item.column = MXS_STRDUP(zColumn);
 | |
| 
 | |
|             if (item.column)
 | |
|             {
 | |
|                 fields.push_back(item);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static void update_function_fields(const QcAliases* pAliases,
 | |
|                                        const Expr* pExpr,
 | |
|                                        const ExprList* pExclude,
 | |
|                                        vector<QC_FIELD_INFO>& fields)
 | |
|     {
 | |
|         const char* zDatabase;
 | |
|         const char* zTable;
 | |
|         const char* zColumn;
 | |
| 
 | |
|         if (get_field_name(pExpr, &zDatabase, &zTable, &zColumn))
 | |
|         {
 | |
|             if (!zDatabase && !zTable && pExclude)
 | |
|             {
 | |
|                 for (int i = 0; i < pExclude->nExpr; ++i)
 | |
|                 {
 | |
|                     ExprList::ExprList_item* pItem = &pExclude->a[i];
 | |
| 
 | |
|                     if (pItem->zName && (strcasecmp(pItem->zName, zColumn) == 0))
 | |
|                     {
 | |
|                         get_field_name(pItem->pExpr, &zDatabase, &zTable, &zColumn);
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (zColumn)
 | |
|             {
 | |
|                 update_function_fields(pAliases, zDatabase, zTable, zColumn, fields);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static void update_function_fields(const QcAliases* pAliases,
 | |
|                                        const ExprList*  pEList,
 | |
|                                        const ExprList*  pExclude,
 | |
|                                        vector<QC_FIELD_INFO>& fields)
 | |
|     {
 | |
|         for (int i = 0; i < pEList->nExpr; ++i)
 | |
|         {
 | |
|             ExprList::ExprList_item* pItem = &pEList->a[i];
 | |
| 
 | |
|             update_function_fields(pAliases, pItem->pExpr, pExclude, fields);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     int update_function_info(const QcAliases* pAliases,
 | |
|                              const char* name,
 | |
|                              const Expr* pExpr,
 | |
|                              const ExprList* pEList,
 | |
|                              const ExprList* pExclude)
 | |
| 
 | |
|     {
 | |
|         mxb_assert(name);
 | |
|         mxb_assert((!pExpr && !pEList) || (pExpr && !pEList) || (!pExpr && pEList));
 | |
| 
 | |
|         if (!(m_collect & QC_COLLECT_FUNCTIONS) || (m_collected & QC_COLLECT_FUNCTIONS))
 | |
|         {
 | |
|             // If function information should not be collected, or if function information
 | |
|             // has already been collected, we just return.
 | |
|             return -1;
 | |
|         }
 | |
| 
 | |
|         name = map_function_name(m_pFunction_name_mappings, name);
 | |
| 
 | |
|         QC_FUNCTION_INFO item = {(char*)name};
 | |
| 
 | |
|         size_t i;
 | |
|         for (i = 0; i < m_function_infos.size(); ++i)
 | |
|         {
 | |
|             QC_FUNCTION_INFO& function_info = m_function_infos[i];
 | |
| 
 | |
|             if (strcasecmp(item.name, function_info.name) == 0)
 | |
|             {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (i == m_function_infos.size())   // If true, the function was not present already.
 | |
|         {
 | |
|             mxb_assert(item.name);
 | |
|             item.name = MXS_STRDUP(item.name);
 | |
| 
 | |
|             if (item.name)
 | |
|             {
 | |
|                 m_function_infos.reserve(m_function_infos.size() + 1);
 | |
|                 m_function_field_usage.reserve(m_function_field_usage.size() + 1);
 | |
| 
 | |
|                 m_function_infos.push_back(item);
 | |
|                 m_function_field_usage.resize(m_function_field_usage.size() + 1);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (pExpr || pEList)
 | |
|         {
 | |
|             vector<QC_FIELD_INFO>& fields = m_function_field_usage[i];
 | |
| 
 | |
|             if (pExpr)
 | |
|             {
 | |
|                 update_function_fields(pAliases, pExpr, pExclude, fields);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 update_function_fields(pAliases, pEList, pExclude, fields);
 | |
|             }
 | |
| 
 | |
|             QC_FUNCTION_INFO& info = m_function_infos[i];
 | |
| 
 | |
|             if (fields.size() != 0)
 | |
|             {
 | |
|                 info.fields = &fields[0];
 | |
|                 info.n_fields = fields.size();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return i;
 | |
|     }
 | |
| 
 | |
|     int update_function_info(const QcAliases* pAliases,
 | |
|                              const char* name,
 | |
|                              const Expr* pExpr,
 | |
|                              const ExprList* pExclude)
 | |
| 
 | |
|     {
 | |
|         return update_function_info(pAliases, name, pExpr, NULL, pExclude);
 | |
|     }
 | |
| 
 | |
|     int update_function_info(const QcAliases* pAliases,
 | |
|                              const char* name,
 | |
|                              const ExprList* pEList,
 | |
|                              const ExprList* pExclude)
 | |
|     {
 | |
|         return update_function_info(pAliases, name, NULL, pEList, pExclude);
 | |
|     }
 | |
| 
 | |
|     int update_function_info(const QcAliases* pAliases,
 | |
|                              const char* name,
 | |
|                              const ExprList* pExclude)
 | |
|     {
 | |
|         return update_function_info(pAliases, name, NULL, NULL, pExclude);
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // sqlite3 callbacks
 | |
|     //
 | |
| 
 | |
|     void mxs_sqlite3AlterFinishAddColumn(Parse* pParse, Token* pToken)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|         m_operation = QUERY_OP_ALTER;
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3AlterBeginAddColumn(Parse* pParse, SrcList* pSrcList)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         update_names_from_srclist(NULL, pSrcList);
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pSrcList);
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3Analyze(Parse* pParse, SrcList* pSrcList)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
| 
 | |
|         update_names_from_srclist(NULL, pSrcList);
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pSrcList);
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3BeginTransaction(Parse* pParse, int token, int type)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         if ((m_sql_mode != QC_SQL_MODE_ORACLE) || (token == TK_START))
 | |
|         {
 | |
|             m_status = QC_QUERY_PARSED;
 | |
|             m_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 */
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
| 
 | |
|         if (pTableName)
 | |
|         {
 | |
|             for (size_t i = 0; i < pTableName->nAlloc; ++i)
 | |
|             {
 | |
|                 const SrcList::SrcList_item* pItem = &pTableName->a[i];
 | |
| 
 | |
|                 if (pItem->zName)
 | |
|                 {
 | |
|                     update_names(pItem->zDatabase, pItem->zName, pItem->zAlias, NULL);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // 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)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_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 */
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|         m_operation = QUERY_OP_CREATE;
 | |
| 
 | |
|         if (pTblName)
 | |
|         {
 | |
|             update_names_from_srclist(NULL, pTblName);
 | |
|         }
 | |
|         else if (pParse->pNewTable)
 | |
|         {
 | |
|             update_names(NULL, pParse->pNewTable->zName, NULL, NULL);
 | |
|         }
 | |
| 
 | |
|         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 */
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|         m_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;
 | |
| 
 | |
|         QcAliases aliases;
 | |
| 
 | |
|         if (pDatabase)
 | |
|         {
 | |
|             char database[pDatabase->n + 1];
 | |
|             strncpy(database, pDatabase->z, pDatabase->n);
 | |
|             database[pDatabase->n] = 0;
 | |
| 
 | |
|             update_names(database, name, NULL, &aliases);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             update_names(NULL, name, NULL, &aliases);
 | |
|         }
 | |
| 
 | |
|         if (pSelect)
 | |
|         {
 | |
|             update_field_infos_from_select(aliases, pSelect, NULL);
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3ExprListDelete(pParse->db, pCNames);
 | |
|         // pSelect is deleted in parse.y
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3DeleteFrom(Parse* pParse, SrcList* pTabList, Expr* pWhere, SrcList* pUsing)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
| 
 | |
|         if (m_operation != QUERY_OP_EXPLAIN)
 | |
|         {
 | |
|             m_type_mask = QUERY_TYPE_WRITE;
 | |
|             m_operation = QUERY_OP_DELETE;
 | |
|             m_has_clause = pWhere ? true : false;
 | |
| 
 | |
|             QcAliases aliases;
 | |
| 
 | |
|             if (pUsing)
 | |
|             {
 | |
|                 // Walk through the using declaration and update
 | |
|                 // table and database names.
 | |
|                 for (int i = 0; i < pUsing->nSrc; ++i)
 | |
|                 {
 | |
|                     const SrcList::SrcList_item* pItem = &pUsing->a[i];
 | |
| 
 | |
|                     update_names(pItem->zDatabase, pItem->zName, pItem->zAlias, &aliases);
 | |
|                 }
 | |
| 
 | |
|                 // Walk through the tablenames while excluding alias
 | |
|                 // names from the using declaration.
 | |
|                 for (int i = 0; i < pTabList->nSrc; ++i)
 | |
|                 {
 | |
|                     const SrcList::SrcList_item* pTable = &pTabList->a[i];
 | |
|                     mxb_assert(pTable->zName);
 | |
|                     int j = 0;
 | |
|                     bool isSame = false;
 | |
| 
 | |
|                     do
 | |
|                     {
 | |
|                         SrcList::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(pTable->zDatabase, pTable->zName, NULL, &aliases);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 update_names_from_srclist(&aliases, pTabList);
 | |
|             }
 | |
| 
 | |
|             if (pWhere)
 | |
|             {
 | |
|                 update_field_infos(&aliases, 0, pWhere, 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)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|         m_operation = QUERY_OP_DROP;
 | |
| 
 | |
|         update_names_from_srclist(NULL, pTable);
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pName);
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pTable);
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3DropTable(Parse* pParse, SrcList* pName, int isView, int noErr, int isTemp)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_WRITE;
 | |
|         if (!isTemp)
 | |
|         {
 | |
|             m_type_mask |= QUERY_TYPE_COMMIT;
 | |
|         }
 | |
|         m_operation = QUERY_OP_DROP;
 | |
|         if (!isView)
 | |
|         {
 | |
|             m_is_drop_table = true;
 | |
|         }
 | |
|         update_names_from_srclist(NULL, 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" */
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         if (pSelect)
 | |
|         {
 | |
|             QcAliases aliases;
 | |
|             update_field_infos_from_select(aliases, pSelect, NULL);
 | |
|         }
 | |
|         else if (pOldTable)
 | |
|         {
 | |
|             update_names_from_srclist(NULL, pOldTable);
 | |
|             exposed_sqlite3SrcListDelete(pParse->db, pOldTable);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3Insert(Parse* pParse,
 | |
|                            SrcList* pTabList,
 | |
|                            Select*  pSelect,
 | |
|                            IdList*  pColumns,
 | |
|                            int onError,
 | |
|                            ExprList* pSet)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
| 
 | |
|         if (m_operation != QUERY_OP_EXPLAIN)
 | |
|         {
 | |
|             m_type_mask = QUERY_TYPE_WRITE;
 | |
|             m_operation = QUERY_OP_INSERT;
 | |
|             mxb_assert(pTabList);
 | |
|             mxb_assert(pTabList->nSrc >= 1);
 | |
| 
 | |
|             QcAliases aliases;
 | |
| 
 | |
|             update_names_from_srclist(&aliases, pTabList);
 | |
| 
 | |
|             if (pColumns)
 | |
|             {
 | |
|                 update_field_infos_from_idlist(&aliases, pColumns, NULL);
 | |
| 
 | |
|                 int i = update_function_info(&aliases, "=", NULL);
 | |
| 
 | |
|                 if (i != -1)
 | |
|                 {
 | |
|                     vector<QC_FIELD_INFO>& fields = m_function_field_usage[i];
 | |
| 
 | |
|                     for (int j = 0; j < pColumns->nId; ++j)
 | |
|                     {
 | |
|                         update_function_fields(&aliases, NULL, NULL, pColumns->a[j].zName, fields);
 | |
|                     }
 | |
| 
 | |
|                     if (fields.size() != 0)
 | |
|                     {
 | |
|                         QC_FUNCTION_INFO& info = m_function_infos[i];
 | |
| 
 | |
|                         info.fields = &fields[0];
 | |
|                         info.n_fields = fields.size();
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (pSelect)
 | |
|             {
 | |
|                 update_field_infos_from_select(aliases, pSelect, NULL);
 | |
|             }
 | |
| 
 | |
|             if (pSet)
 | |
|             {
 | |
|                 update_field_infos_from_exprlist(&aliases, pSet, NULL);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pTabList);
 | |
|         exposed_sqlite3IdListDelete(pParse->db, pColumns);
 | |
|         exposed_sqlite3ExprListDelete(pParse->db, pSet);
 | |
|         exposed_sqlite3SelectDelete(pParse->db, pSelect);
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3RollbackTransaction(Parse* pParse)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_ROLLBACK;
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3Select(Parse* pParse, Select* p, SelectDest* pDest)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
| 
 | |
|         if (m_operation != QUERY_OP_EXPLAIN)
 | |
|         {
 | |
|             m_operation = QUERY_OP_SELECT;
 | |
| 
 | |
|             maxscaleCollectInfoFromSelect(pParse, p, 0);
 | |
|         }
 | |
|         // NOTE: By convention, the select is deleted in parse.y.
 | |
|     }
 | |
| 
 | |
|     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 */
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_operation = QUERY_OP_CREATE;
 | |
|         m_type_mask = QUERY_TYPE_WRITE;
 | |
| 
 | |
|         if (isTemp)
 | |
|         {
 | |
|             m_type_mask |= QUERY_TYPE_CREATE_TMP_TABLE;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             m_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(database, name, NULL, NULL);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             update_names(NULL, name, NULL, NULL);
 | |
|         }
 | |
| 
 | |
|         if (m_collect & QC_COLLECT_TABLES)
 | |
|         {
 | |
|             // If information is collected in several passes, then we may
 | |
|             // this information already.
 | |
|             if (!m_zCreated_table_name)
 | |
|             {
 | |
|                 m_zCreated_table_name = MXS_STRDUP(m_table_names[0]);
 | |
|                 MXS_ABORT_IF_NULL(m_zCreated_table_name);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 mxb_assert(m_collect != m_collected);
 | |
|                 mxb_assert(strcmp(m_zCreated_table_name, m_table_names[0]) == 0);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void mxs_sqlite3Update(Parse* pParse, SrcList* pTabList, ExprList* pChanges, Expr* pWhere, int onError)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
| 
 | |
|         if (m_operation != QUERY_OP_EXPLAIN)
 | |
|         {
 | |
|             QcAliases aliases;
 | |
| 
 | |
|             m_type_mask = QUERY_TYPE_WRITE;
 | |
|             m_operation = QUERY_OP_UPDATE;
 | |
|             update_names_from_srclist(&aliases, pTabList);
 | |
|             m_has_clause = (pWhere ? true : false);
 | |
| 
 | |
|             if (pChanges)
 | |
|             {
 | |
|                 for (int i = 0; i < pChanges->nExpr; ++i)
 | |
|                 {
 | |
|                     ExprList::ExprList_item* pItem = &pChanges->a[i];
 | |
| 
 | |
|                     update_field_infos(&aliases,
 | |
|                                        0,
 | |
|                                        pItem->pExpr,
 | |
|                                        QC_TOKEN_MIDDLE,
 | |
|                                        NULL);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (pWhere)
 | |
|             {
 | |
|                 update_field_infos(&aliases, 0, pWhere, 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)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_WRITE;
 | |
|     }
 | |
| 
 | |
|     void maxscaleCollectInfoFromSelect(Parse* pParse, Select* pSelect, int sub_select)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         if (pSelect->pInto)
 | |
|         {
 | |
|             const ExprList* pInto = pSelect->pInto;
 | |
|             mxb_assert(pInto->nExpr >= 1);
 | |
| 
 | |
|             if ((pInto->nExpr == 1)
 | |
|                 && (pInto->a[0].zName)
 | |
|                 && ((strcmp(pInto->a[0].zName, ":DUMPFILE:") == 0)
 | |
|                     || (strcmp(pInto->a[0].zName, ":OUTFILE:") == 0)))
 | |
|             {
 | |
|                 // If there is exactly one expression that has a name that is either
 | |
|                 // ":DUMPFILE:" or ":OUTFILE:" then it's a SELECT ... INTO OUTFILE|DUMPFILE
 | |
|                 // and the statement needs to go to master.
 | |
|                 // See in parse.y, the rule for select_into.
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // If there's a single variable, then it's a write.
 | |
|                 // mysql embedded considers it a system var write.
 | |
|                 m_type_mask = QUERY_TYPE_GSYSVAR_WRITE;
 | |
|             }
 | |
| 
 | |
|             // Also INTO {OUTFILE|DUMPFILE} will be typed as QUERY_TYPE_GSYSVAR_WRITE.
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // Only if the type has explicitly been set to QUERY_TYPE_WRITE
 | |
|             // we don't force it to QUERY_TYPE_READ but with other bits we do.
 | |
|             // This is something of kludge to ensure continued compatibility
 | |
|             // with qc_mysqlembedded.
 | |
|             if (m_type_mask != QUERY_TYPE_WRITE)
 | |
|             {
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         QcAliases aliases;
 | |
|         update_field_infos_from_select(aliases, pSelect, NULL);
 | |
|     }
 | |
| 
 | |
|     void maxscaleAlterTable(Parse* pParse,              /* Parser context. */
 | |
|                             mxs_alter_t command,
 | |
|                             SrcList* pSrc,              /* The table to rename. */
 | |
|                             Token*   pName)             /* The new table name (RENAME). */
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|         m_operation = QUERY_OP_ALTER;
 | |
| 
 | |
|         switch (command)
 | |
|         {
 | |
|         case MXS_ALTER_DISABLE_KEYS:
 | |
|             update_names_from_srclist(NULL, pSrc);
 | |
|             break;
 | |
| 
 | |
|         case MXS_ALTER_ENABLE_KEYS:
 | |
|             update_names_from_srclist(NULL, pSrc);
 | |
|             break;
 | |
| 
 | |
|         case MXS_ALTER_RENAME:
 | |
|             update_names_from_srclist(NULL, pSrc);
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             ;
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pSrc);
 | |
|     }
 | |
| 
 | |
|     void maxscaleCall(Parse* pParse, SrcList* pName, ExprList* pExprList)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_WRITE;
 | |
|         m_operation = QUERY_OP_CALL;
 | |
| 
 | |
|         if (pExprList)
 | |
|         {
 | |
|             QcAliases aliases;
 | |
|             update_field_infos_from_exprlist(&aliases, pExprList, NULL);
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pName);
 | |
|         exposed_sqlite3ExprListDelete(pParse->db, pExprList);
 | |
|     }
 | |
| 
 | |
|     void maxscaleCheckTable(Parse* pParse, SrcList* pTables)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
| 
 | |
|         update_names_from_srclist(NULL, pTables);
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pTables);
 | |
|     }
 | |
| 
 | |
|     void maxscaleCreateSequence(Parse* pParse, Token* pDatabase, Token* pTable)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_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(zDatabase, table, NULL, NULL);
 | |
|     }
 | |
| 
 | |
|     int maxscaleComment()
 | |
|     {
 | |
|         // We are regularily parsing if the thread has been initialized.
 | |
|         // In that case # should be interpreted as the start of a comment,
 | |
|         // otherwise it should not.
 | |
|         int regular_parsing = false;
 | |
| 
 | |
|         if (this_thread.initialized)
 | |
|         {
 | |
|             regular_parsing = true;
 | |
| 
 | |
|             if (m_status == QC_QUERY_INVALID)
 | |
|             {
 | |
|                 m_status = QC_QUERY_PARSED;
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return regular_parsing;
 | |
|     }
 | |
| 
 | |
|     void maxscaleDeclare(Parse* pParse)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         if (m_sql_mode != QC_SQL_MODE_ORACLE)
 | |
|         {
 | |
|             m_status = QC_QUERY_INVALID;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void maxscaleDeallocate(Parse* pParse, Token* pName)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_DEALLOC_PREPARE;
 | |
| 
 | |
|         // If information is collected in several passes, then we may
 | |
|         // this information already.
 | |
|         if (!m_zPrepare_name)
 | |
|         {
 | |
|             m_zPrepare_name = (char*)MXS_MALLOC(pName->n + 1);
 | |
|             if (m_zPrepare_name)
 | |
|             {
 | |
|                 memcpy(m_zPrepare_name, pName->z, pName->n);
 | |
|                 m_zPrepare_name[pName->n] = 0;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             mxb_assert(m_collect != m_collected);
 | |
|             mxb_assert(strncmp(m_zPrepare_name, pName->z, pName->n) == 0);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void maxscaleDo(Parse* pParse, ExprList* pEList)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_READ | QUERY_TYPE_WRITE);
 | |
| 
 | |
|         exposed_sqlite3ExprListDelete(pParse->db, pEList);
 | |
|     }
 | |
| 
 | |
|     void maxscaleDrop(Parse* pParse, int what, Token* pDatabase, Token* pName)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|         m_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(zDatabase, table, NULL, NULL);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void maxscaleExecute(Parse* pParse, Token* pName, int type_mask)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | type_mask);
 | |
|         m_operation = QUERY_OP_EXECUTE;
 | |
| 
 | |
|         // If information is collected in several passes, then we may
 | |
|         // this information already.
 | |
|         if (!m_zPrepare_name)
 | |
|         {
 | |
|             m_zPrepare_name = (char*)MXS_MALLOC(pName->n + 1);
 | |
|             if (m_zPrepare_name)
 | |
|             {
 | |
|                 memcpy(m_zPrepare_name, pName->z, pName->n);
 | |
|                 m_zPrepare_name[pName->n] = 0;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             mxb_assert(m_collect != m_collected);
 | |
|             mxb_assert(strncmp(m_zPrepare_name, pName->z, pName->n) == 0);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void maxscaleExecuteImmediate(Parse* pParse, Token* pName, ExprSpan* pExprSpan, int type_mask)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         if (m_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)
 | |
|             {
 | |
|                 m_status = QC_QUERY_PARSED;
 | |
|                 m_type_mask = (QUERY_TYPE_WRITE | type_mask);
 | |
|                 m_type_mask |= type_check_dynamic_string(pExprSpan->pExpr);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 m_status = QC_QUERY_INVALID;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             m_status = QC_QUERY_INVALID;
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3ExprDelete(pParse->db, pExprSpan->pExpr);
 | |
|     }
 | |
| 
 | |
|     void maxscaleExplain(Parse* pParse, Token* pNext)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_READ;
 | |
|         m_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))
 | |
|                 {
 | |
|                     m_operation = QUERY_OP_EXPLAIN;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void maxscaleFlush(Parse* pParse, Token* pWhat)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|     }
 | |
| 
 | |
|     void maxscaleHandler(Parse* pParse, mxs_handler_t type, SrcList* pFullName, Token* pName)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
| 
 | |
|         switch (type)
 | |
|         {
 | |
|         case MXS_HANDLER_OPEN:
 | |
|             {
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
| 
 | |
|                 mxb_assert(pFullName->nSrc == 1);
 | |
|                 const SrcList::SrcList_item* pItem = &pFullName->a[0];
 | |
| 
 | |
|                 update_names(pItem->zDatabase, pItem->zName, pItem->zAlias, NULL);
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case MXS_HANDLER_CLOSE:
 | |
|             {
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
| 
 | |
|                 char zName[pName->n + 1];
 | |
|                 strncpy(zName, pName->z, pName->n);
 | |
|                 zName[pName->n] = 0;
 | |
| 
 | |
|                 update_names("*any*", zName, NULL, NULL);
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             mxb_assert(!true);
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pFullName);
 | |
|     }
 | |
| 
 | |
|     void maxscaleLoadData(Parse* pParse, SrcList* pFullName, int local)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_WRITE;
 | |
|         m_operation = local ? QUERY_OP_LOAD_LOCAL : QUERY_OP_LOAD;
 | |
| 
 | |
|         if (pFullName)
 | |
|         {
 | |
|             update_names_from_srclist(NULL, pFullName);
 | |
| 
 | |
|             exposed_sqlite3SrcListDelete(pParse->db, pFullName);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void maxscaleLock(Parse* pParse, mxs_lock_t type, SrcList* pTables)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_WRITE;
 | |
| 
 | |
|         if (pTables)
 | |
|         {
 | |
|             update_names_from_srclist(NULL, pTables);
 | |
| 
 | |
|             exposed_sqlite3SrcListDelete(pParse->db, pTables);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     int maxscaleTranslateKeyword(int token)
 | |
|     {
 | |
|         switch (token)
 | |
|         {
 | |
|         case TK_CHARSET:
 | |
|         case TK_DO:
 | |
|         case TK_HANDLER:
 | |
|             if (m_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 (m_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)
 | |
|     {
 | |
|         int rv = 0;
 | |
| 
 | |
|         // This function is called for every keyword the sqlite3 parser encounters.
 | |
|         // We will store in m_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 m_keyword_{1|2} is initialized with 0, well, if it is 0 then
 | |
|         // we have not seen the {1st|2nd} keyword yet.
 | |
| 
 | |
|         if (!m_keyword_1)
 | |
|         {
 | |
|             m_keyword_1 = token;
 | |
| 
 | |
|             switch (m_keyword_1)
 | |
|             {
 | |
|             case TK_ALTER:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|                 m_operation = QUERY_OP_ALTER;
 | |
|                 break;
 | |
| 
 | |
|             case TK_BEGIN:
 | |
|             case TK_DECLARE:
 | |
|             case TK_FOR:
 | |
|                 if (m_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.
 | |
|                     m_status = QC_QUERY_TOKENIZED;
 | |
|                     m_type_mask = QUERY_TYPE_WRITE;
 | |
|                     // Return non-0 to cause the entire input to be consumed.
 | |
|                     rv = 1;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_CALL:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_CREATE:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|                 m_operation = QUERY_OP_CREATE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_DELETE:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 m_operation = QUERY_OP_DELETE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_DESC:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|                 m_operation = QUERY_OP_EXPLAIN;
 | |
|                 break;
 | |
| 
 | |
|             case TK_DROP:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|                 m_operation = QUERY_OP_DROP;
 | |
|                 break;
 | |
| 
 | |
|             case TK_EXECUTE:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_EXPLAIN:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|                 m_operation = QUERY_OP_EXPLAIN;
 | |
|                 break;
 | |
| 
 | |
|             case TK_GRANT:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|                 m_operation = QUERY_OP_GRANT;
 | |
|                 break;
 | |
| 
 | |
|             case TK_HANDLER:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_INSERT:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 m_operation = QUERY_OP_INSERT;
 | |
|                 break;
 | |
| 
 | |
|             case TK_LOCK:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_PREPARE:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_PREPARE_NAMED_STMT;
 | |
|                 break;
 | |
| 
 | |
|             case TK_REPLACE:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 m_operation = QUERY_OP_INSERT;
 | |
|                 break;
 | |
| 
 | |
|             case TK_REVOKE:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|                 m_operation = QUERY_OP_REVOKE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_SELECT:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|                 m_operation = QUERY_OP_SELECT;
 | |
|                 break;
 | |
| 
 | |
|             case TK_SET:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_GSYSVAR_WRITE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_SHOW:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|                 m_operation = QUERY_OP_SHOW;
 | |
|                 break;
 | |
| 
 | |
|             case TK_START:
 | |
|                 // Will produce the right info for START SLAVE.
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_UNLOCK:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_UPDATE:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 m_operation = QUERY_OP_UPDATE;
 | |
|                 break;
 | |
| 
 | |
|             case TK_TRUNCATE:
 | |
|                 m_status = QC_QUERY_TOKENIZED;
 | |
|                 m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 ;
 | |
|             }
 | |
|         }
 | |
|         else if (!m_keyword_2)
 | |
|         {
 | |
|             m_keyword_2 = token;
 | |
| 
 | |
|             switch (m_keyword_1)
 | |
|             {
 | |
|             case TK_CHECK:
 | |
|                 if (m_keyword_2 == TK_TABLE)
 | |
|                 {
 | |
|                     m_status = QC_QUERY_TOKENIZED;
 | |
|                     m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_DEALLOCATE:
 | |
|                 if (m_keyword_2 == TK_PREPARE)
 | |
|                 {
 | |
|                     m_status = QC_QUERY_TOKENIZED;
 | |
|                     m_type_mask = QUERY_TYPE_SESSION_WRITE;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_LOAD:
 | |
|                 if (m_keyword_2 == TK_DATA)
 | |
|                 {
 | |
|                     m_status = QC_QUERY_TOKENIZED;
 | |
|                     m_type_mask = QUERY_TYPE_WRITE;
 | |
|                     m_operation = QUERY_OP_LOAD;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_RENAME:
 | |
|                 if (m_keyword_2 == TK_TABLE)
 | |
|                 {
 | |
|                     m_status = QC_QUERY_TOKENIZED;
 | |
|                     m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_START:
 | |
|                 switch (m_keyword_2)
 | |
|                 {
 | |
|                 case TK_TRANSACTION:
 | |
|                     m_status = QC_QUERY_TOKENIZED;
 | |
|                     m_type_mask = QUERY_TYPE_BEGIN_TRX;
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     break;
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case TK_SHOW:
 | |
|                 switch (m_keyword_2)
 | |
|                 {
 | |
|                 case TK_DATABASES_KW:
 | |
|                     m_status = QC_QUERY_TOKENIZED;
 | |
|                     m_type_mask = QUERY_TYPE_SHOW_DATABASES;
 | |
|                     break;
 | |
| 
 | |
|                 case TK_TABLES:
 | |
|                     m_status = QC_QUERY_TOKENIZED;
 | |
|                     m_type_mask = QUERY_TYPE_SHOW_TABLES;
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     void maxscaleRenameTable(Parse* pParse, SrcList* pTables)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT;
 | |
| 
 | |
|         for (int i = 0; i < pTables->nSrc; ++i)
 | |
|         {
 | |
|             const SrcList::SrcList_item* pItem = &pTables->a[i];
 | |
| 
 | |
|             mxb_assert(pItem->zName);
 | |
|             mxb_assert(pItem->zAlias);
 | |
| 
 | |
|             update_names(pItem->zDatabase, pItem->zName, NULL, NULL);
 | |
|             update_names(NULL, pItem->zAlias, NULL, NULL);      // The new name is passed in the alias field.
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3SrcListDelete(pParse->db, pTables);
 | |
|     }
 | |
| 
 | |
|     void maxscalePrepare(Parse* pParse, Token* pName, Expr* pStmt)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         switch (pStmt->op)
 | |
|         {
 | |
|         case TK_STRING:
 | |
|         case TK_VARIABLE:
 | |
|             m_status = QC_QUERY_PARSED;
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             m_status = QC_QUERY_PARTIALLY_PARSED;
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         m_type_mask = QUERY_TYPE_PREPARE_NAMED_STMT;
 | |
| 
 | |
|         // If information is collected in several passes, then we may
 | |
|         // this information already.
 | |
|         if (!m_zPrepare_name)
 | |
|         {
 | |
|             m_zPrepare_name = (char*)MXS_MALLOC(pName->n + 1);
 | |
|             if (m_zPrepare_name)
 | |
|             {
 | |
|                 memcpy(m_zPrepare_name, pName->z, pName->n);
 | |
|                 m_zPrepare_name[pName->n] = 0;
 | |
|             }
 | |
| 
 | |
|             if (pStmt->op == TK_STRING)
 | |
|             {
 | |
|                 const char* zStmt = pStmt->u.zToken;
 | |
|                 mxb_assert(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;
 | |
| 
 | |
|                 m_pPreparable_stmt = gwbuf_alloc(packet_len);
 | |
| 
 | |
|                 if (m_pPreparable_stmt)
 | |
|                 {
 | |
|                     uint8_t* ptr = GWBUF_DATA(m_pPreparable_stmt);
 | |
|                     // Payload length
 | |
|                     *ptr++ = payload_len;
 | |
|                     *ptr++ = (payload_len >> 8);
 | |
|                     *ptr++ = (payload_len >> 16);
 | |
|                     // Sequence id
 | |
|                     *ptr++ = 0x00;
 | |
|                     // Command
 | |
|                     *ptr++ = MXS_COM_QUERY;
 | |
| 
 | |
|                     memcpy(ptr, zStmt, preparable_stmt_len);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             mxb_assert(m_collect != m_collected);
 | |
|             mxb_assert(strncmp(m_zPrepare_name, pName->z, pName->n) == 0);
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3ExprDelete(pParse->db, pStmt);
 | |
|     }
 | |
| 
 | |
|     void maxscalePrivileges(Parse* pParse, int kind)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
| 
 | |
|         switch (kind)
 | |
|         {
 | |
|         case TK_GRANT:
 | |
|             m_operation = QUERY_OP_GRANT;
 | |
|             break;
 | |
| 
 | |
|         case TK_REVOKE:
 | |
|             m_operation = QUERY_OP_REVOKE;
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             mxb_assert(!true);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = 0;    // Reset what was set in maxscaleKeyword
 | |
| 
 | |
|         switch (kind)
 | |
|         {
 | |
|         case MXS_SET_TRANSACTION:
 | |
|             if ((scope == TK_GLOBAL) || (scope == TK_SESSION))
 | |
|             {
 | |
|                 m_type_mask = QUERY_TYPE_GSYSVAR_WRITE;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 mxb_assert(scope == 0);
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case MXS_SET_VARIABLES:
 | |
|             {
 | |
|                 for (int i = 0; i < pList->nExpr; ++i)
 | |
|                 {
 | |
|                     const ExprList::ExprList_item* pItem = &pList->a[i];
 | |
| 
 | |
|                     switch (pItem->pExpr->op)
 | |
|                     {
 | |
|                     case TK_CHARACTER:
 | |
|                     case TK_NAMES:
 | |
|                         m_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;
 | |
|                                 mxb_assert(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)
 | |
|                             {
 | |
|                                 m_type_mask |= QUERY_TYPE_USERVAR_WRITE;
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 m_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;
 | |
|                             }
 | |
| 
 | |
|                             mxb_assert((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:
 | |
|                                         m_type_mask |= QUERY_TYPE_BEGIN_TRX;
 | |
|                                         m_type_mask |= QUERY_TYPE_DISABLE_AUTOCOMMIT;
 | |
|                                         break;
 | |
| 
 | |
|                                     case 1:
 | |
|                                         m_type_mask |= QUERY_TYPE_ENABLE_AUTOCOMMIT;
 | |
|                                         m_type_mask |= QUERY_TYPE_COMMIT;
 | |
|                                         break;
 | |
| 
 | |
|                                     default:
 | |
|                                         break;
 | |
|                                     }
 | |
|                                 }
 | |
|                             }
 | |
| 
 | |
|                             if (pValue->op == TK_SELECT)
 | |
|                             {
 | |
|                                 QcAliases aliases;
 | |
|                                 update_field_infos_from_select(aliases, pValue->x.pSelect, NULL);
 | |
|                             }
 | |
|                         }
 | |
|                         break;
 | |
| 
 | |
|                     default:
 | |
|                         mxb_assert(!true);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             mxb_assert(!true);
 | |
|         }
 | |
| 
 | |
|         exposed_sqlite3ExprListDelete(pParse->db, pList);
 | |
|     }
 | |
| 
 | |
|     void maxscaleShow(Parse* pParse, MxsShow* pShow)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_operation = QUERY_OP_SHOW;
 | |
| 
 | |
|         switch (pShow->what)
 | |
|         {
 | |
|         case MXS_SHOW_COLUMNS:
 | |
|             m_type_mask = QUERY_TYPE_READ;
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_CREATE_SEQUENCE:
 | |
|             m_type_mask = QUERY_TYPE_READ;
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_CREATE_VIEW:
 | |
|             m_type_mask = QUERY_TYPE_READ;
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_CREATE_TABLE:
 | |
|             m_type_mask = QUERY_TYPE_READ;
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_DATABASES:
 | |
|             m_type_mask = QUERY_TYPE_SHOW_DATABASES;
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_INDEX:
 | |
|         case MXS_SHOW_INDEXES:
 | |
|         case MXS_SHOW_KEYS:
 | |
|             m_type_mask = QUERY_TYPE_WRITE;
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_TABLE_STATUS:
 | |
|             m_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:
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|                 break;
 | |
| 
 | |
|             case MXS_SHOW_STATUS_MASTER:
 | |
|                 m_type_mask = QUERY_TYPE_WRITE;
 | |
|                 break;
 | |
| 
 | |
|             case MXS_SHOW_STATUS_SLAVE:
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|                 break;
 | |
| 
 | |
|             case MXS_SHOW_STATUS_ALL_SLAVES:
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 m_type_mask = QUERY_TYPE_READ;
 | |
|                 break;
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_TABLES:
 | |
|             m_type_mask = QUERY_TYPE_SHOW_TABLES;
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_VARIABLES:
 | |
|             if (pShow->data == MXS_SHOW_VARIABLES_GLOBAL)
 | |
|             {
 | |
|                 m_type_mask = QUERY_TYPE_GSYSVAR_READ;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 m_type_mask = QUERY_TYPE_SYSVAR_READ;
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case MXS_SHOW_WARNINGS:
 | |
|             // qc_mysqliembedded claims this.
 | |
|             m_type_mask = QUERY_TYPE_WRITE;
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             mxb_assert(!true);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void maxscaleTruncate(Parse* pParse, Token* pDatabase, Token* pName)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = (QUERY_TYPE_WRITE | QUERY_TYPE_COMMIT);
 | |
|         m_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(zDatabase, name, NULL, NULL);
 | |
|     }
 | |
| 
 | |
|     void maxscaleUse(Parse* pParse, Token* pToken)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
| 
 | |
|         m_status = QC_QUERY_PARSED;
 | |
|         m_type_mask = QUERY_TYPE_SESSION_WRITE;
 | |
|         m_operation = QUERY_OP_CHANGE_DB;
 | |
|     }
 | |
| 
 | |
|     void set_type_mask(uint32_t type_mask)
 | |
|     {
 | |
|         mxb_assert(this_thread.initialized);
 | |
|         m_type_mask = type_mask;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     QcSqliteInfo(uint32_t cllct)
 | |
|         : m_refs(1)
 | |
|         , m_status(QC_QUERY_INVALID)
 | |
|         , m_collect(cllct)
 | |
|         , m_collected(0)
 | |
|         , m_pQuery(NULL)
 | |
|         , m_nQuery(0)
 | |
|         , m_type_mask(QUERY_TYPE_UNKNOWN)
 | |
|         , m_operation(QUERY_OP_UNDEFINED)
 | |
|         , m_has_clause(false)
 | |
|         , m_zCreated_table_name(NULL)
 | |
|         , m_is_drop_table(false)
 | |
|         , m_keyword_1(0)
 | |
|         ,               // Sqlite3 starts numbering tokens from 1, so 0 means
 | |
|         m_keyword_2(0)
 | |
|         ,               // that we have not seen a keyword.
 | |
|         m_zPrepare_name(NULL)
 | |
|         , m_pPreparable_stmt(NULL)
 | |
|         , m_sql_mode(this_thread.sql_mode)
 | |
|         , m_pFunction_name_mappings(this_thread.pFunction_name_mappings)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     ~QcSqliteInfo()
 | |
|     {
 | |
|         mxb_assert(m_refs == 0);
 | |
| 
 | |
|         std::for_each(m_table_names.begin(), m_table_names.end(), mxs_free);
 | |
|         std::for_each(m_table_fullnames.begin(), m_table_fullnames.end(), mxs_free);
 | |
|         free(m_zCreated_table_name);
 | |
|         std::for_each(m_database_names.begin(), m_database_names.end(), mxs_free);
 | |
|         free(m_zPrepare_name);
 | |
|         gwbuf_free(m_pPreparable_stmt);
 | |
|         std::for_each(m_field_infos.begin(), m_field_infos.end(), finish_field_info);
 | |
|         std::for_each(m_function_infos.begin(), m_function_infos.end(), finish_function_info);
 | |
| 
 | |
|         // Data in m_function_field_usage is freed in finish_function_info().
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     bool should_collect(qc_collect_info_t collect) const
 | |
|     {
 | |
|         return (m_collect & collect) && !(m_collected & collect);
 | |
|     }
 | |
| 
 | |
|     static void free_field_infos(QC_FIELD_INFO* pInfos, size_t nInfos)
 | |
|     {
 | |
|         if (pInfos)
 | |
|         {
 | |
|             for (size_t i = 0; i < nInfos; ++i)
 | |
|             {
 | |
|                 MXS_FREE(pInfos[i].database);
 | |
|                 MXS_FREE(pInfos[i].table);
 | |
|                 MXS_FREE(pInfos[i].column);
 | |
|             }
 | |
| 
 | |
|             MXS_FREE(pInfos);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static void free_function_infos(QC_FUNCTION_INFO* pInfos, size_t nInfos)
 | |
|     {
 | |
|         if (pInfos)
 | |
|         {
 | |
|             for (size_t i = 0; i < nInfos; ++i)
 | |
|             {
 | |
|                 MXS_FREE(pInfos[i].name);
 | |
|             }
 | |
| 
 | |
|             MXS_FREE(pInfos);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static void free_string_array(char** pzArray)
 | |
|     {
 | |
|         if (pzArray)
 | |
|         {
 | |
|             char** pz = pzArray;
 | |
| 
 | |
|             while (*pz)
 | |
|             {
 | |
|                 free(*pz);
 | |
|                 ++pz;
 | |
|             }
 | |
| 
 | |
|             free(pzArray);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static char** copy_string_array(const vector<char*>& strings)
 | |
|     {
 | |
|         size_t n = strings.size();
 | |
| 
 | |
|         char** pz = (char**) MXS_MALLOC((n + 1) * sizeof(char*));
 | |
|         MXS_ABORT_IF_NULL(pz);
 | |
| 
 | |
|         pz[n] = 0;
 | |
| 
 | |
|         for (size_t i = 0; i < n; ++i)
 | |
|         {
 | |
|             pz[i] = MXS_STRDUP(strings[i]);
 | |
|             MXS_ABORT_IF_NULL(pz[i]);
 | |
|         }
 | |
| 
 | |
|         return pz;
 | |
|     }
 | |
| 
 | |
|     const char* table_name_collected(const char* zTable)
 | |
|     {
 | |
|         size_t i = 0;
 | |
| 
 | |
|         while ((i < m_table_names.size()) && (strcmp(m_table_names[i], zTable) != 0))
 | |
|         {
 | |
|             ++i;
 | |
|         }
 | |
| 
 | |
|         return (i != m_table_names.size()) ? m_table_names[i] : NULL;
 | |
|     }
 | |
| 
 | |
|     const char* table_fullname_collected(const char* zTable)
 | |
|     {
 | |
|         size_t i = 0;
 | |
| 
 | |
|         while ((i < m_table_fullnames.size()) && (strcmp(m_table_fullnames[i], zTable) != 0))
 | |
|         {
 | |
|             ++i;
 | |
|         }
 | |
| 
 | |
|         return (i != m_table_fullnames.size()) ? m_table_fullnames[i] : NULL;
 | |
|     }
 | |
| 
 | |
|     const char* database_name_collected(const char* zDatabase)
 | |
|     {
 | |
|         size_t i = 0;
 | |
| 
 | |
|         while ((i < m_database_names.size()) && (strcmp(m_database_names[i], zDatabase) != 0))
 | |
|         {
 | |
|             ++i;
 | |
|         }
 | |
| 
 | |
|         return (i != m_database_names.size()) ? m_database_names[i] : NULL;
 | |
|     }
 | |
| 
 | |
|     const char* update_table_names(const char* zDatabase,
 | |
|                                    size_t nDatabase,
 | |
|                                    const char* zTable,
 | |
|                                    size_t nTable)
 | |
|     {
 | |
|         mxb_assert(zTable && nTable);
 | |
| 
 | |
|         const char* zCollected_table = table_name_collected(zTable);
 | |
| 
 | |
|         if (!zCollected_table)
 | |
|         {
 | |
|             char* zCopy = MXS_STRDUP_A(zTable);
 | |
| 
 | |
|             m_table_names.push_back(zCopy);
 | |
| 
 | |
|             zCollected_table = zCopy;
 | |
|         }
 | |
| 
 | |
|         char fullname[nDatabase + 1 + nTable + 1];
 | |
| 
 | |
|         if (nDatabase)
 | |
|         {
 | |
|             strcpy(fullname, zDatabase);
 | |
|             strcat(fullname, ".");
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             fullname[0] = 0;
 | |
|         }
 | |
| 
 | |
|         strcat(fullname, zTable);
 | |
| 
 | |
|         if (!table_fullname_collected(fullname))
 | |
|         {
 | |
|             char* zCopy = MXS_STRDUP_A(fullname);
 | |
| 
 | |
|             m_table_fullnames.push_back(zCopy);
 | |
|         }
 | |
| 
 | |
|         return zCollected_table;
 | |
|     }
 | |
| 
 | |
|     const char* update_database_names(const char* zDatabase)
 | |
|     {
 | |
|         mxb_assert(zDatabase);
 | |
|         mxb_assert(strlen(zDatabase) != 0);
 | |
| 
 | |
|         const char* zCollected_database = database_name_collected(zDatabase);
 | |
| 
 | |
|         if (!zCollected_database)
 | |
|         {
 | |
|             char* zCopy = MXS_STRDUP_A(zDatabase);
 | |
| 
 | |
|             m_database_names.push_back(zCopy);
 | |
| 
 | |
|             zCollected_database = zCopy;
 | |
|         }
 | |
| 
 | |
|         return zCollected_database;
 | |
|     }
 | |
| 
 | |
| public:
 | |
|     // TODO: Make these private once everything's been updated.
 | |
|     int32_t m_refs;                             // The reference count.
 | |
|     qc_parse_result_t m_status;                 // The validity of the information in this structure.
 | |
|     uint32_t m_collect;                         // What information should be collected.
 | |
|     uint32_t m_collected;                       // What information has been collected.
 | |
|     const char* m_pQuery;                       // The query passed to sqlite.
 | |
|     size_t m_nQuery;                            // The length of the query.
 | |
| 
 | |
|     uint32_t m_type_mask;                                   // The type mask of the query.
 | |
|     qc_query_op_t m_operation;                              // The operation in question.
 | |
|     bool m_has_clause;                                      // Has WHERE or HAVING.
 | |
|     vector<char*> m_table_names;                            // Vector of table names used in the query.
 | |
|     vector<char*> m_table_fullnames;                        // Vector of qualified table names used in the
 | |
|                                                             // query.
 | |
|     char* m_zCreated_table_name;                            // The name of a created table.
 | |
|     bool m_is_drop_table;                                   // Is the query a DROP TABLE.
 | |
|     vector<char*> m_database_names;                         // Vector of database names used in the query.
 | |
|     int m_keyword_1;                                        // The first encountered keyword.
 | |
|     int m_keyword_2;                                        // The second encountered keyword.
 | |
|     char* m_zPrepare_name;                                  // The name of a prepared statement.
 | |
|     GWBUF* m_pPreparable_stmt;                              // The preparable statement.
 | |
|     vector<QC_FIELD_INFO> m_field_infos;                    // Vector of fields used by the statement.
 | |
|     vector<QC_FUNCTION_INFO> m_function_infos;              // Vector of functions used by the statement.
 | |
|     vector<vector<QC_FIELD_INFO>> m_function_field_usage;   // Vector of vector fields used by functions
 | |
|                                                             // of the statement. Data referred to from
 | |
|                                                             // m_function_infos
 | |
|     size_t m_function_infos_len;                            // The used entries in function_infos.
 | |
|     size_t m_function_infos_capacity;                       // The capacity of the function_infos array.
 | |
|     qc_sql_mode_t m_sql_mode;                               // The current sql_mode.
 | |
|     QC_NAME_MAPPING* m_pFunction_name_mappings;             // How function names should be mapped.
 | |
| };
 | |
| 
 | |
| extern "C"
 | |
| {
 | |
| 
 | |
|     extern void mxs_sqlite3AlterFinishAddColumn(Parse*, Token*);
 | |
|     extern void mxs_sqlite3AlterBeginAddColumn(Parse*, SrcList*);
 | |
|     extern void mxs_sqlite3Analyze(Parse*, SrcList*);
 | |
|     extern void mxs_sqlite3BeginTransaction(Parse*, int token, int type);
 | |
|     extern void mxs_sqlite3CommitTransaction(Parse*);
 | |
|     extern void mxs_sqlite3CreateIndex(Parse*,
 | |
|                                        Token*,
 | |
|                                        Token*,
 | |
|                                        SrcList*,
 | |
|                                        ExprList*,
 | |
|                                        int,
 | |
|                                        Token*,
 | |
|                                        Expr*,
 | |
|                                        int,
 | |
|                                        int);
 | |
|     extern void mxs_sqlite3BeginTrigger(Parse*,
 | |
|                                         Token*,
 | |
|                                         Token*,
 | |
|                                         int,
 | |
|                                         int,
 | |
|                                         IdList*,
 | |
|                                         SrcList*,
 | |
|                                         Expr*,
 | |
|                                         int,
 | |
|                                         int);
 | |
|     extern void mxs_sqlite3FinishTrigger(Parse*, TriggerStep*, Token*);
 | |
|     extern void mxs_sqlite3CreateView(Parse*, Token*, Token*, Token*, ExprList*, Select*, int, int);
 | |
|     extern void mxs_sqlite3DeleteFrom(Parse* pParse, SrcList* pTabList, Expr* pWhere, SrcList* pUsing);
 | |
|     extern void mxs_sqlite3DropIndex(Parse*, SrcList*, SrcList*, int);
 | |
|     extern void mxs_sqlite3DropTable(Parse*, SrcList*, int, int, int);
 | |
|     extern void mxs_sqlite3EndTable(Parse*, Token*, Token*, u8, Select*, SrcList*);
 | |
|     extern void mxs_sqlite3Insert(Parse*, SrcList*, Select*, IdList*, int, ExprList*);
 | |
|     extern void mxs_sqlite3RollbackTransaction(Parse*);
 | |
|     extern void mxs_sqlite3Savepoint(Parse* pParse, int op, Token* pName);
 | |
|     extern int  mxs_sqlite3Select(Parse*, Select*, SelectDest*);
 | |
|     extern void mxs_sqlite3StartTable(Parse*, Token*, Token*, int, int, int, int);
 | |
|     extern void mxs_sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
 | |
| 
 | |
|     extern void maxscaleCollectInfoFromSelect(Parse*, Select*, int);
 | |
| 
 | |
|     extern void maxscaleAlterTable(Parse*, mxs_alter_t command, SrcList*, Token*);
 | |
|     extern void maxscaleCall(Parse*, SrcList* pName, ExprList* pExprList);
 | |
|     extern void maxscaleCheckTable(Parse*, SrcList* pTables);
 | |
|     extern void maxscaleCreateSequence(Parse*, Token* pDatabase, Token* pTable);
 | |
|     extern void maxscaleDeclare(Parse* pParse);
 | |
|     extern void maxscaleDeallocate(Parse*, Token* pName);
 | |
|     extern void maxscaleDo(Parse*, ExprList* pEList);
 | |
|     extern void maxscaleDrop(Parse*, int what, Token* pDatabase, Token* pName);
 | |
|     extern void maxscaleExecute(Parse*, Token* pName, int type_mask);
 | |
|     extern void maxscaleExecuteImmediate(Parse*, Token* pName, ExprSpan* pExprSpan, int type_mask);
 | |
|     extern void maxscaleExplain(Parse*, Token* pNext);
 | |
|     extern void maxscaleFlush(Parse*, Token* pWhat);
 | |
|     extern void maxscaleHandler(Parse*, mxs_handler_t, SrcList* pFullName, Token* pName);
 | |
|     extern void maxscaleLoadData(Parse*, SrcList* pFullName, int local);
 | |
|     extern void maxscaleLock(Parse*, mxs_lock_t, SrcList*);
 | |
|     extern void maxscalePrepare(Parse*, Token* pName, Expr* pStmt);
 | |
|     extern void maxscalePrivileges(Parse*, int kind);
 | |
|     extern void maxscaleRenameTable(Parse*, SrcList* pTables);
 | |
|     extern void maxscaleSet(Parse*, int scope, mxs_set_t kind, ExprList*);
 | |
|     extern void maxscaleShow(Parse*, MxsShow* pShow);
 | |
|     extern void maxscaleTruncate(Parse*, Token* pDatabase, Token* pName);
 | |
|     extern void maxscaleUse(Parse*, Token*);
 | |
| 
 | |
|     extern void maxscale_update_function_info(const char* name, const Expr* pExpr);
 | |
|     // 'unsigned int' and not 'uint32_t' because 'uint32_t' is unknown in sqlite3 context.
 | |
|     extern void maxscale_set_type_mask(unsigned int type_mask);
 | |
| 
 | |
|     extern void maxscaleComment();
 | |
|     extern int  maxscaleKeyword(int token);
 | |
|     extern int  maxscaleTranslateKeyword(int token);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Used for freeing a QcSqliteInfo object added to a GWBUF.
 | |
|  *
 | |
|  * @param object A pointer to a QcSqliteInfo object.
 | |
|  */
 | |
| static void buffer_object_free(void* pData)
 | |
| {
 | |
|     QcSqliteInfo* pInfo = static_cast<QcSqliteInfo*>(pData);
 | |
|     pInfo->dec_ref();
 | |
| }
 | |
| 
 | |
| 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 parse_query_string(const char* query, int len, bool suppress_logging)
 | |
| {
 | |
|     sqlite3_stmt* stmt = NULL;
 | |
|     const char* tail = NULL;
 | |
| 
 | |
|     mxb_assert(this_thread.pDb);
 | |
|     int rc = sqlite3_prepare(this_thread.pDb, 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.pInfo->m_operation == QUERY_OP_EXPLAIN)
 | |
|     {
 | |
|         this_thread.pInfo->m_status = QC_QUERY_PARSED;
 | |
|     }
 | |
| 
 | |
|     if (rc != SQLITE_OK)
 | |
|     {
 | |
|         if (qc_info_was_tokenized(this_thread.pInfo->m_status))
 | |
|         {
 | |
|             format =
 | |
|                 "Statement was classified only based on keywords "
 | |
|                 "(Sqlite3 error: %s, %s): \"%.*s%s\"";
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             if (qc_info_was_parsed(this_thread.pInfo->m_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.pInfo->m_status = QC_QUERY_PARTIALLY_PARSED;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 format =
 | |
|                     "Statement was neither parsed nor recognized from keywords "
 | |
|                     "(Sqlite3 error: %s, %s): \"%.*s%s\"";
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!suppress_logging)
 | |
|         {
 | |
|             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.pInfo->m_status < QC_QUERY_PARSED;
 | |
|                     break;
 | |
| 
 | |
|                 case QC_LOG_NON_PARTIALLY_PARSED:
 | |
|                     log_warning = this_thread.pInfo->m_status < QC_QUERY_PARTIALLY_PARSED;
 | |
|                     break;
 | |
| 
 | |
|                 case QC_LOG_NON_TOKENIZED:
 | |
|                     log_warning = this_thread.pInfo->m_status < QC_QUERY_TOKENIZED;
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     mxb_assert(!true);
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 if (log_warning)
 | |
|                 {
 | |
|                     MXS_WARNING(format,
 | |
|                                 sqlite3_errstr(rc),
 | |
|                                 sqlite3_errmsg(this_thread.pDb),
 | |
|                                 l,
 | |
|                                 query,
 | |
|                                 suffix);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else if (this_thread.initialized)   // If we are initializing, the query will not be classified.
 | |
|     {
 | |
|         if (!suppress_logging && (this_unit.log_level > QC_LOG_NOTHING))
 | |
|         {
 | |
|             if (qc_info_was_tokenized(this_thread.pInfo->m_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.pInfo->m_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;
 | |
|     mxb_assert(!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 == MXS_COM_QUERY) || (command == MXS_COM_STMT_PREPARE))
 | |
|             {
 | |
|                 bool suppress_logging = false;
 | |
| 
 | |
|                 QcSqliteInfo* pInfo =
 | |
|                     (QcSqliteInfo*) gwbuf_get_buffer_object_data(query, GWBUF_PARSING_INFO);
 | |
| 
 | |
|                 if (pInfo)
 | |
|                 {
 | |
|                     mxb_assert((~pInfo->m_collect & collect) != 0);
 | |
|                     mxb_assert((~pInfo->m_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.
 | |
|                     pInfo->m_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.
 | |
|                     pInfo->m_keyword_1 = 0;
 | |
|                     pInfo->m_keyword_2 = 0;
 | |
| 
 | |
|                     // And turn off logging. Any parsing issues were logged on the first round.
 | |
|                     suppress_logging = true;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     pInfo = QcSqliteInfo::create(collect);
 | |
| 
 | |
|                     if (pInfo)
 | |
|                     {
 | |
|                         // TODO: Add return value to gwbuf_add_buffer_object.
 | |
|                         gwbuf_add_buffer_object(query, GWBUF_PARSING_INFO, pInfo, buffer_object_free);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (pInfo)
 | |
|                 {
 | |
|                     this_thread.pInfo = pInfo;
 | |
| 
 | |
|                     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.pInfo->m_pQuery = s;
 | |
|                     this_thread.pInfo->m_nQuery = len;
 | |
|                     parse_query_string(s, len, suppress_logging);
 | |
|                     this_thread.pInfo->m_pQuery = NULL;
 | |
|                     this_thread.pInfo->m_nQuery = 0;
 | |
| 
 | |
|                     if (command == MXS_COM_STMT_PREPARE)
 | |
|                     {
 | |
|                         pInfo->m_type_mask |= QUERY_TYPE_PREPARE_STMT;
 | |
|                     }
 | |
| 
 | |
|                     pInfo->m_collected = pInfo->m_collect;
 | |
| 
 | |
|                     parsed = true;
 | |
| 
 | |
|                     this_thread.pInfo = 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)));
 | |
|                 mxb_assert(!true);
 | |
|             }
 | |
|         }
 | |
|         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)
 | |
|     {
 | |
|         QcSqliteInfo* pInfo = (QcSqliteInfo*) gwbuf_get_buffer_object_data(query, GWBUF_PARSING_INFO);
 | |
|         mxb_assert(pInfo);
 | |
| 
 | |
|         if ((~pInfo->m_collected & collect) != 0)
 | |
|         {
 | |
|             // The statement has been parsed once, but the needed information
 | |
|             // was not collected at that time.
 | |
|             rc = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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 > (int)GWBUF_LENGTH(query) - MYSQL_HEADER_LEN - 1)
 | |
|             {
 | |
|                 length = (int)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 ExprList::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;
 | |
| }
 | |
| 
 | |
| extern void maxscale_update_function_info(const char* name, const Expr* pExpr)
 | |
| {
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     pInfo->update_function_info(NULL, name, pExpr, NULL);
 | |
| }
 | |
| 
 | |
| extern void maxscale_set_type_mask(unsigned int type_mask)
 | |
| {
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     pInfo->set_type_mask(type_mask);
 | |
| }
 | |
| 
 | |
| 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:
 | |
|         mxb_assert(!true);
 | |
|         return "";
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *
 | |
|  * SQLITE
 | |
|  *
 | |
|  * These functions are called from sqlite.
 | |
|  */
 | |
| 
 | |
| void mxs_sqlite3AlterFinishAddColumn(Parse* pParse, Token* pToken)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3AlterFinishAddColumn(pParse, pToken));
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3AlterBeginAddColumn(Parse* pParse, SrcList* pSrcList)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3AlterBeginAddColumn(pParse, pSrcList));
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3Analyze(Parse* pParse, SrcList* pSrcList)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3Analyze(pParse, pSrcList));
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3BeginTransaction(Parse* pParse, int token, int type)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3BeginTransaction(pParse, token, 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();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3BeginTrigger(pParse,
 | |
|                                                       pName1,
 | |
|                                                       pName2,
 | |
|                                                       tr_tm,
 | |
|                                                       op,
 | |
|                                                       pColumns,
 | |
|                                                       pTableName,
 | |
|                                                       pWhen,
 | |
|                                                       isTemp,
 | |
|                                                       noErr));
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3CommitTransaction(Parse* pParse)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3CommitTransaction(pParse));
 | |
| }
 | |
| 
 | |
| 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();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3CreateIndex(pParse,
 | |
|                                                      pName1,
 | |
|                                                      pName2,
 | |
|                                                      pTblName,
 | |
|                                                      pList,
 | |
|                                                      onError,
 | |
|                                                      pStart,
 | |
|                                                      pPIWhere,
 | |
|                                                      sortOrder,
 | |
|                                                      ifNotExist));
 | |
| }
 | |
| 
 | |
| 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();
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3CreateView(pParse,
 | |
|                                                     pBegin,
 | |
|                                                     pName1,
 | |
|                                                     pName2,
 | |
|                                                     pCNames,
 | |
|                                                     pSelect,
 | |
|                                                     isTemp,
 | |
|                                                     noErr));
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3DeleteFrom(Parse* pParse, SrcList* pTabList, Expr* pWhere, SrcList* pUsing)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3DeleteFrom(pParse, pTabList, pWhere, pUsing));
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3DropIndex(Parse* pParse, SrcList* pName, SrcList* pTable, int bits)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3DropIndex(pParse, pName, pTable, bits));
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3DropTable(Parse* pParse, SrcList* pName, int isView, int noErr, int isTemp)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3DropTable(pParse, pName, isView, noErr, isTemp));
 | |
| }
 | |
| 
 | |
| 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();
 | |
| 
 | |
|     if (this_thread.initialized)
 | |
|     {
 | |
|         QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|         mxb_assert(pInfo);
 | |
| 
 | |
|         QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3EndTable(pParse, pCons, pEnd, tabOpts, pSelect, pOldTable));
 | |
|     }
 | |
|     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 */
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     exposed_sqlite3FinishTrigger(pParse, pStepList, pAll);
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3Insert(Parse* pParse,
 | |
|                        SrcList* pTabList,
 | |
|                        Select*  pSelect,
 | |
|                        IdList*  pColumns,
 | |
|                        int onError,
 | |
|                        ExprList* pSet)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     if (this_thread.initialized)
 | |
|     {
 | |
|         QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|         mxb_assert(pInfo);
 | |
| 
 | |
|         QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3Insert(pParse, pTabList, pSelect, pColumns, onError, pSet));
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         exposed_sqlite3ExprListDelete(pParse->db, pSet);
 | |
|         exposed_sqlite3Insert(pParse, pTabList, pSelect, pColumns, onError);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void mxs_sqlite3RollbackTransaction(Parse* pParse)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3RollbackTransaction(pParse));
 | |
| }
 | |
| 
 | |
| int mxs_sqlite3Select(Parse* pParse, Select* p, SelectDest* pDest)
 | |
| {
 | |
|     int rc = -1;
 | |
|     QC_TRACE();
 | |
| 
 | |
|     if (this_thread.initialized)
 | |
|     {
 | |
|         QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|         mxb_assert(pInfo);
 | |
| 
 | |
|         QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3Select(pParse, p, pDest));
 | |
|     }
 | |
|     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();
 | |
| 
 | |
|     if (this_thread.initialized)
 | |
|     {
 | |
|         QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|         mxb_assert(pInfo);
 | |
| 
 | |
|         QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3StartTable(pParse,
 | |
|                                                         pName1,
 | |
|                                                         pName2,
 | |
|                                                         isTemp,
 | |
|                                                         isView,
 | |
|                                                         isVirtual,
 | |
|                                                         noErr));
 | |
|     }
 | |
|     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();
 | |
| 
 | |
|     if (this_thread.initialized)
 | |
|     {
 | |
|         QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|         mxb_assert(pInfo);
 | |
| 
 | |
|         QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3Update(pParse, pTabList, pChanges, pWhere, onError));
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         // NOTE: Basically we should call
 | |
|         // NOTE:
 | |
|         // NOTE: exposed_sqlite3Update(pParse, pTabList, pChanges, pWhere, onError);
 | |
|         // NOTE:
 | |
|         // NOTE: However, for whatever reason sqlite3 thinks there is some problem.
 | |
|         // NOTE: As this final update is not needed, we simply ignore it. That's
 | |
|         // NOTE: what always has been done but now it is explicit.
 | |
| 
 | |
|         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();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->mxs_sqlite3Savepoint(pParse, op, pName));
 | |
| }
 | |
| 
 | |
| void maxscaleCollectInfoFromSelect(Parse* pParse, Select* pSelect, int sub_select)
 | |
| {
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleCollectInfoFromSelect(pParse, pSelect, sub_select));
 | |
| }
 | |
| 
 | |
| 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();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleAlterTable(pParse, command, pSrc, pName));
 | |
| }
 | |
| 
 | |
| void maxscaleCall(Parse* pParse, SrcList* pName, ExprList* pExprList)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleCall(pParse, pName, pExprList));
 | |
| }
 | |
| 
 | |
| void maxscaleCheckTable(Parse* pParse, SrcList* pTables)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleCheckTable(pParse, pTables));
 | |
| }
 | |
| 
 | |
| void maxscaleCreateSequence(Parse* pParse, Token* pDatabase, Token* pTable)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleCreateSequence(pParse, pDatabase, pTable));
 | |
| }
 | |
| 
 | |
| void maxscaleComment()
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleComment());
 | |
| }
 | |
| 
 | |
| void maxscaleDeclare(Parse* pParse)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleDeclare(pParse));
 | |
| }
 | |
| 
 | |
| void maxscaleDeallocate(Parse* pParse, Token* pName)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleDeallocate(pParse, pName));
 | |
| }
 | |
| 
 | |
| void maxscaleDo(Parse* pParse, ExprList* pEList)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleDo(pParse, pEList));
 | |
| }
 | |
| 
 | |
| void maxscaleDrop(Parse* pParse, int what, Token* pDatabase, Token* pName)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleDrop(pParse, what, pDatabase, pName));
 | |
| }
 | |
| 
 | |
| void maxscaleExecute(Parse* pParse, Token* pName, int type_mask)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleExecute(pParse, pName, type_mask));
 | |
| }
 | |
| 
 | |
| void maxscaleExecuteImmediate(Parse* pParse, Token* pName, ExprSpan* pExprSpan, int type_mask)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleExecuteImmediate(pParse, pName, pExprSpan, type_mask));
 | |
| }
 | |
| 
 | |
| void maxscaleExplain(Parse* pParse, Token* pNext)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleExplain(pParse, pNext));
 | |
| }
 | |
| 
 | |
| void maxscaleFlush(Parse* pParse, Token* pWhat)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleFlush(pParse, pWhat));
 | |
| }
 | |
| 
 | |
| void maxscaleHandler(Parse* pParse, mxs_handler_t type, SrcList* pFullName, Token* pName)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleHandler(pParse, type, pFullName, pName));
 | |
| }
 | |
| 
 | |
| void maxscaleLoadData(Parse* pParse, SrcList* pFullName, int local)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleLoadData(pParse, pFullName, local));
 | |
| }
 | |
| 
 | |
| void maxscaleLock(Parse* pParse, mxs_lock_t type, SrcList* pTables)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleLock(pParse, type, pTables));
 | |
| }
 | |
| 
 | |
| int maxscaleTranslateKeyword(int token)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(token = pInfo->maxscaleTranslateKeyword(token));
 | |
| 
 | |
|     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();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(token = pInfo->maxscaleKeyword(token));
 | |
| 
 | |
|     return token;
 | |
| }
 | |
| 
 | |
| void maxscaleRenameTable(Parse* pParse, SrcList* pTables)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleRenameTable(pParse, pTables));
 | |
| }
 | |
| 
 | |
| void maxscalePrepare(Parse* pParse, Token* pName, Expr* pStmt)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscalePrepare(pParse, pName, pStmt));
 | |
| }
 | |
| 
 | |
| void maxscalePrivileges(Parse* pParse, int kind)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscalePrivileges(pParse, kind));
 | |
| }
 | |
| 
 | |
| void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleSet(pParse, scope, kind, pList));
 | |
| }
 | |
| 
 | |
| void maxscaleShow(Parse* pParse, MxsShow* pShow)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleShow(pParse, pShow));
 | |
| }
 | |
| 
 | |
| void maxscaleTruncate(Parse* pParse, Token* pDatabase, Token* pName)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleTruncate(pParse, pDatabase, pName));
 | |
| }
 | |
| 
 | |
| void maxscaleUse(Parse* pParse, Token* pToken)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     QcSqliteInfo* pInfo = this_thread.pInfo;
 | |
|     mxb_assert(pInfo);
 | |
| 
 | |
|     QC_EXCEPTION_GUARD(pInfo->maxscaleUse(pParse, pToken));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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 QC_STMT_INFO* qc_sqlite_info_dup(QC_STMT_INFO* info);
 | |
| static void          qc_sqlite_info_close(QC_STMT_INFO* info);
 | |
| 
 | |
| 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 = static_cast<qc_log_level_t>(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.pFunction_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 (this_unit.log_level != QC_LOG_NOTHING)
 | |
|         {
 | |
|             const char* message = NULL;
 | |
| 
 | |
|             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:
 | |
|                 mxb_assert(!true);
 | |
|             }
 | |
| 
 | |
|             MXS_NOTICE("%s", message);
 | |
|         }
 | |
|     }
 | |
|     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();
 | |
|     mxb_assert(this_unit.initialized);
 | |
| 
 | |
|     finish_builtin_functions();
 | |
| 
 | |
|     sqlite3_shutdown();
 | |
|     this_unit.initialized = false;
 | |
| }
 | |
| 
 | |
| static int32_t qc_sqlite_thread_init(void)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(!this_thread.initialized);
 | |
| 
 | |
|     // Thread initialization must be done behind a global lock. SQLite can perform
 | |
|     // global initialization which has a data race in the page cache code.
 | |
|     // TODO: Figure out why this happens
 | |
|     std::lock_guard<std::mutex> guard(this_unit.lock);
 | |
| 
 | |
|     // TODO: It may be sufficient to have a single in-memory database for all threads.
 | |
|     int rc = sqlite3_open(":memory:", &this_thread.pDb);
 | |
|     if (rc == SQLITE_OK)
 | |
|     {
 | |
|         this_thread.sql_mode = this_unit.sql_mode;
 | |
|         this_thread.pFunction_name_mappings = this_unit.pFunction_name_mappings;
 | |
| 
 | |
|         MXS_INFO("In-memory sqlite database successfully opened for thread %lu.",
 | |
|                  (unsigned long) pthread_self());
 | |
| 
 | |
|         QcSqliteInfo* pInfo = QcSqliteInfo::create(QC_COLLECT_ALL);
 | |
| 
 | |
|         if (pInfo)
 | |
|         {
 | |
|             this_thread.pInfo = pInfo;
 | |
| 
 | |
|             // 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);
 | |
| 
 | |
|             bool suppress_logging = false;
 | |
| 
 | |
|             this_thread.pInfo->m_pQuery = s;
 | |
|             this_thread.pInfo->m_nQuery = len;
 | |
|             parse_query_string(s, len, suppress_logging);
 | |
|             this_thread.pInfo->m_pQuery = NULL;
 | |
|             this_thread.pInfo->m_nQuery = 0;
 | |
| 
 | |
|             this_thread.pInfo->dec_ref();
 | |
|             this_thread.pInfo = 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.pDb);
 | |
|             this_thread.pDb = 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();
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     mxb_assert(this_thread.pDb);
 | |
|     std::lock_guard<std::mutex> guard(this_unit.lock);
 | |
|     int rc = sqlite3_close(this_thread.pDb);
 | |
| 
 | |
|     if (rc != SQLITE_OK)
 | |
|     {
 | |
|         MXS_WARNING("The closing of the thread specific sqlite database failed: %d, %s",
 | |
|                     rc,
 | |
|                     sqlite3_errstr(rc));
 | |
|     }
 | |
| 
 | |
|     this_thread.pDb = NULL;
 | |
|     this_thread.initialized = false;
 | |
| }
 | |
| 
 | |
| static int32_t qc_sqlite_parse(GWBUF* pStmt, uint32_t collect, int32_t* pResult)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, collect);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         *pResult = pInfo->m_status;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         *pResult = QC_QUERY_INVALID;
 | |
|     }
 | |
| 
 | |
|     return pInfo ? QC_RESULT_OK : QC_RESULT_ERROR;
 | |
| }
 | |
| 
 | |
| static int32_t qc_sqlite_get_type_mask(GWBUF* pStmt, uint32_t* pType_mask)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *pType_mask = QUERY_TYPE_UNKNOWN;
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_ESSENTIALS);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_type_mask(pType_mask))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "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* pStmt, int32_t* pOp)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *pOp = QUERY_OP_UNDEFINED;
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_ESSENTIALS);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_operation(pOp))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "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* pStmt, char** pzCreated_table_name)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *pzCreated_table_name = NULL;
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_TABLES);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_created_table_name(pzCreated_table_name))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "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* pStmt, int32_t* pIs_drop_table)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *pIs_drop_table = 0;
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_ESSENTIALS);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->is_drop_table_query(pIs_drop_table))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "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* pStmt,
 | |
|                                          int32_t fullnames,
 | |
|                                          char*** ppzTable_names,
 | |
|                                          int32_t* pnTable_names)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *ppzTable_names = NULL;
 | |
|     *pnTable_names = 0;
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_TABLES);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_table_names(fullnames, ppzTable_names, pnTable_names))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "cannot report what tables are accessed");
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("The pStmt could not be parsed. Response not valid.");
 | |
|     }
 | |
| 
 | |
|     return rv;
 | |
| }
 | |
| 
 | |
| static int32_t qc_sqlite_get_canonical(GWBUF* pStmt, char** pzCanonical)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *pzCanonical = NULL;
 | |
| 
 | |
|     MXS_ERROR("qc_get_canonical not implemented yet.");
 | |
| 
 | |
|     return rv;
 | |
| }
 | |
| 
 | |
| static int32_t qc_sqlite_query_has_clause(GWBUF* pStmt, int32_t* pHas_clause)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *pHas_clause = 0;
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_ESSENTIALS);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->query_has_clause(pHas_clause))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "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* pStmt, char*** ppzDatabase_names, int* pnDatabase_names)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *ppzDatabase_names = NULL;
 | |
|     *pnDatabase_names = 0;
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_DATABASES);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_database_names(ppzDatabase_names, pnDatabase_names))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "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* pStmt, char** pzPrepare_name)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *pzPrepare_name = NULL;
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_ESSENTIALS);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_prepare_name(pzPrepare_name))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "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* pStmt, const QC_FIELD_INFO** ppInfos, uint32_t* pnInfos)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *ppInfos = NULL;
 | |
|     *pnInfos = 0;
 | |
| 
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_FIELDS);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_field_info(ppInfos, pnInfos))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "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* pStmt, const QC_FUNCTION_INFO** ppInfos, uint32_t* pnInfos)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *ppInfos = NULL;
 | |
|     *pnInfos = 0;
 | |
| 
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_FUNCTIONS);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_function_info(ppInfos, pnInfos))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "cannot report function info");
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("The query could not be parsed. Response not valid.");
 | |
|     }
 | |
| 
 | |
|     return rv;
 | |
| }
 | |
| 
 | |
| int32_t qc_sqlite_get_preparable_stmt(GWBUF* pStmt, GWBUF** pzPreparable_stmt)
 | |
| {
 | |
|     QC_TRACE();
 | |
|     int32_t rv = QC_RESULT_ERROR;
 | |
|     mxb_assert(this_unit.initialized);
 | |
|     mxb_assert(this_thread.initialized);
 | |
| 
 | |
|     *pzPreparable_stmt = NULL;
 | |
| 
 | |
|     QcSqliteInfo* pInfo = QcSqliteInfo::get(pStmt, QC_COLLECT_ESSENTIALS);
 | |
| 
 | |
|     if (pInfo)
 | |
|     {
 | |
|         if (pInfo->get_preparable_stmt(pzPreparable_stmt))
 | |
|         {
 | |
|             rv = QC_RESULT_OK;
 | |
|         }
 | |
|         else if (mxs_log_is_priority_enabled(LOG_INFO))
 | |
|         {
 | |
|             log_invalid_data(pStmt, "cannot report preperable statement");
 | |
|         }
 | |
|     }
 | |
|     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 = version;
 | |
|     this_thread.version_major = major;
 | |
|     this_thread.version_minor = minor;
 | |
|     this_thread.version_patch = patch;
 | |
| }
 | |
| 
 | |
| static void qc_sqlite_get_server_version(uint64_t* pVersion)
 | |
| {
 | |
|     QC_TRACE();
 | |
| 
 | |
|     *pVersion = this_thread.version;
 | |
| }
 | |
| 
 | |
| 
 | |
| int32_t qc_sqlite_get_sql_mode(qc_sql_mode_t* pSql_mode)
 | |
| {
 | |
|     *pSql_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.pFunction_name_mappings = function_name_mappings_103;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             this_thread.pFunction_name_mappings = function_name_mappings_default;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case QC_SQL_MODE_ORACLE:
 | |
|         this_thread.sql_mode = sql_mode;
 | |
|         this_thread.pFunction_name_mappings = function_name_mappings_oracle;
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         rv = QC_RESULT_ERROR;
 | |
|     }
 | |
| 
 | |
|     return rv;
 | |
| }
 | |
| 
 | |
| QC_STMT_INFO* qc_sqlite_info_dup(QC_STMT_INFO* info)
 | |
| {
 | |
|     static_cast<QcSqliteInfo*>(info)->inc_ref();
 | |
|     return info;
 | |
| }
 | |
| 
 | |
| void qc_sqlite_info_close(QC_STMT_INFO* info)
 | |
| {
 | |
|     static_cast<QcSqliteInfo*>(info)->dec_ref();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * EXPORTS
 | |
|  */
 | |
| 
 | |
| extern "C"
 | |
| {
 | |
| 
 | |
|     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,
 | |
|             qc_sqlite_info_dup,
 | |
|             qc_sqlite_info_close,
 | |
|         };
 | |
| 
 | |
|         static MXS_MODULE info =
 | |
|         {
 | |
|             MXS_MODULE_API_QUERY_CLASSIFIER,
 | |
|             MXS_MODULE_GA,
 | |
|             MXS_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;
 | |
|     }
 | |
| }
 |