Johan Wikman 7b001994b4 MXS-1978 Change qc_sqlite behaviour and update test
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.
2018-11-01 11:24:44 +02:00

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;
}
}