MXS-1337 Move more functions to QcSqliteInfo
This commit is contained in:
@ -175,22 +175,7 @@ static bool parse_query(GWBUF* query, uint32_t collect);
|
|||||||
static void parse_query_string(const char* query, int len, bool suppress_logging);
|
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 query_is_parsed(GWBUF* query, uint32_t collect);
|
||||||
static bool should_exclude(const char* zName, const ExprList* pExclude);
|
static bool should_exclude(const char* zName, const ExprList* pExclude);
|
||||||
static void update_field_infos_from_expr(QcSqliteInfo* info,
|
static const char* get_token_symbol(int token);
|
||||||
QcAliases* pAliases,
|
|
||||||
const Expr* pExpr,
|
|
||||||
uint32_t usage,
|
|
||||||
const ExprList* pExclude);
|
|
||||||
static void update_field_infos(QcSqliteInfo* info,
|
|
||||||
QcAliases* pAliases,
|
|
||||||
int prev_token,
|
|
||||||
const Expr* pExpr,
|
|
||||||
uint32_t usage,
|
|
||||||
qc_token_position_t pos,
|
|
||||||
const ExprList* pExclude);
|
|
||||||
static void update_function_info(QcSqliteInfo* info,
|
|
||||||
const char* name,
|
|
||||||
uint32_t usage);
|
|
||||||
static void update_names_from_srclist(QcSqliteInfo* info, QcAliases* pAliases, const SrcList* pSrc);
|
|
||||||
|
|
||||||
// Defined in parse.y
|
// Defined in parse.y
|
||||||
extern "C"
|
extern "C"
|
||||||
@ -765,6 +750,314 @@ public:
|
|||||||
return truth;
|
return truth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update_field_infos(QcAliases* pAliases,
|
||||||
|
int prev_token,
|
||||||
|
const Expr* pExpr,
|
||||||
|
uint32_t usage,
|
||||||
|
qc_token_position_t pos,
|
||||||
|
const ExprList* pExclude)
|
||||||
|
{
|
||||||
|
const Expr* pLeft = pExpr->pLeft;
|
||||||
|
const Expr* pRight = pExpr->pRight;
|
||||||
|
const char* zToken = pExpr->u.zToken;
|
||||||
|
|
||||||
|
bool ignore_exprlist = false;
|
||||||
|
|
||||||
|
switch (pExpr->op)
|
||||||
|
{
|
||||||
|
case TK_ASTERISK: // select *
|
||||||
|
update_field_infos_from_expr(pAliases, pExpr, usage, pExclude);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_DOT: // select a.b ... select a.b.c
|
||||||
|
update_field_infos_from_expr(pAliases, pExpr, usage, pExclude);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_ID: // select a
|
||||||
|
update_field_infos_from_expr(pAliases, pExpr, usage, pExclude);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_VARIABLE:
|
||||||
|
{
|
||||||
|
if (zToken[0] == '@')
|
||||||
|
{
|
||||||
|
if (zToken[1] == '@')
|
||||||
|
{
|
||||||
|
if ((prev_token == TK_EQ) && (pos == QC_TOKEN_LEFT))
|
||||||
|
{
|
||||||
|
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:
|
||||||
|
// We don't report "=" if it's not used in a specific context (SELECT, WHERE)
|
||||||
|
// and if it is used in SET. We also exclude it it in a context where a
|
||||||
|
// variable is set.
|
||||||
|
if (((usage != 0) && (usage != QC_USED_IN_SET)) &&
|
||||||
|
(!pExpr->pLeft || (pExpr->pLeft->op != TK_VARIABLE)))
|
||||||
|
{
|
||||||
|
update_function_info(get_token_symbol(pExpr->op), usage);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_GE:
|
||||||
|
case TK_GT:
|
||||||
|
case TK_LE:
|
||||||
|
case TK_LT:
|
||||||
|
case TK_NE:
|
||||||
|
|
||||||
|
case TK_BETWEEN:
|
||||||
|
case TK_BITAND:
|
||||||
|
case TK_BITOR:
|
||||||
|
case TK_CASE:
|
||||||
|
case TK_IN:
|
||||||
|
case TK_ISNULL:
|
||||||
|
case TK_MINUS:
|
||||||
|
case TK_NOTNULL:
|
||||||
|
case TK_PLUS:
|
||||||
|
case TK_SLASH:
|
||||||
|
case TK_STAR:
|
||||||
|
update_function_info(get_token_symbol(pExpr->op), usage);
|
||||||
|
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(sqlrowcount, usage);
|
||||||
|
|
||||||
|
pLeft = NULL;
|
||||||
|
pRight = NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
update_function_info(get_token_symbol(pExpr->op), usage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
update_function_info(get_token_symbol(pExpr->op), usage);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_UMINUS:
|
||||||
|
switch (this_unit.parse_as)
|
||||||
|
{
|
||||||
|
case QC_PARSE_AS_DEFAULT:
|
||||||
|
update_function_info(get_token_symbol(pExpr->op), usage);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QC_PARSE_AS_103:
|
||||||
|
// In MariaDB 10.3 a unary minus is not considered a function.
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ss_dassert(!true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_FUNCTION:
|
||||||
|
if (zToken)
|
||||||
|
{
|
||||||
|
if (strcasecmp(zToken, "last_insert_id") == 0)
|
||||||
|
{
|
||||||
|
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(zToken, usage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pLeft)
|
||||||
|
{
|
||||||
|
update_field_infos(pAliases, pExpr->op, pExpr->pLeft, usage, QC_TOKEN_LEFT, pExclude);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pRight)
|
||||||
|
{
|
||||||
|
if (usage & QC_USED_IN_SET)
|
||||||
|
{
|
||||||
|
usage &= ~QC_USED_IN_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_field_infos(pAliases, pExpr->op, pExpr->pRight, usage, QC_TOKEN_RIGHT, pExclude);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pExpr->x.pList)
|
||||||
|
{
|
||||||
|
switch (pExpr->op)
|
||||||
|
{
|
||||||
|
case TK_BETWEEN:
|
||||||
|
case TK_CASE:
|
||||||
|
case TK_FUNCTION:
|
||||||
|
if (!ignore_exprlist)
|
||||||
|
{
|
||||||
|
update_field_infos_from_exprlist(pAliases, pExpr->x.pList, usage, pExclude);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TK_EXISTS:
|
||||||
|
case TK_IN:
|
||||||
|
case TK_SELECT:
|
||||||
|
if (pExpr->flags & EP_xIsSelect)
|
||||||
|
{
|
||||||
|
uint32_t sub_usage = usage;
|
||||||
|
|
||||||
|
sub_usage &= ~QC_USED_IN_SELECT;
|
||||||
|
sub_usage |= QC_USED_IN_SUBSELECT;
|
||||||
|
update_field_infos_from_subselect(pAliases, pExpr->x.pSelect, sub_usage, pExclude);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
update_field_infos_from_exprlist(pAliases, pExpr->x.pList, usage, pExclude);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_field_infos_from_expr(QcAliases* pAliases,
|
||||||
|
const Expr* pExpr,
|
||||||
|
uint32_t usage,
|
||||||
|
const ExprList* pExclude)
|
||||||
|
{
|
||||||
|
QC_FIELD_INFO item = {};
|
||||||
|
|
||||||
|
if (pExpr->op == TK_ASTERISK)
|
||||||
|
{
|
||||||
|
item.column = (char*)"*";
|
||||||
|
}
|
||||||
|
else if (pExpr->op == TK_ID)
|
||||||
|
{
|
||||||
|
// select a from...
|
||||||
|
item.column = pExpr->u.zToken;
|
||||||
|
}
|
||||||
|
else if (pExpr->op == TK_DOT)
|
||||||
|
{
|
||||||
|
if (pExpr->pLeft->op == TK_ID &&
|
||||||
|
(pExpr->pRight->op == TK_ID || pExpr->pRight->op == TK_ASTERISK))
|
||||||
|
{
|
||||||
|
// select a.b from...
|
||||||
|
item.table = pExpr->pLeft->u.zToken;
|
||||||
|
if (pExpr->pRight->op == TK_ID)
|
||||||
|
{
|
||||||
|
item.column = pExpr->pRight->u.zToken;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.column = (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...
|
||||||
|
item.database = pExpr->pLeft->u.zToken;
|
||||||
|
item.table = pExpr->pRight->pLeft->u.zToken;
|
||||||
|
if (pExpr->pRight->pRight->op == TK_ID)
|
||||||
|
{
|
||||||
|
item.column = pExpr->pRight->pRight->u.zToken;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.column = (char*)"*";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.column)
|
||||||
|
{
|
||||||
|
bool should_update = true;
|
||||||
|
|
||||||
|
if ((pExpr->flags & EP_DblQuoted) == 0)
|
||||||
|
{
|
||||||
|
if ((strcasecmp(item.column, "true") == 0) || (strcasecmp(item.column, "false") == 0))
|
||||||
|
{
|
||||||
|
should_update = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_update)
|
||||||
|
{
|
||||||
|
update_field_info(pAliases, item.database, item.table, item.column, usage, pExclude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void update_field_infos_from_exprlist(QcAliases* pAliases,
|
void update_field_infos_from_exprlist(QcAliases* pAliases,
|
||||||
const ExprList* pEList,
|
const ExprList* pEList,
|
||||||
uint32_t usage,
|
uint32_t usage,
|
||||||
@ -774,7 +1067,7 @@ public:
|
|||||||
{
|
{
|
||||||
ExprList::ExprList_item* pItem = &pEList->a[i];
|
ExprList::ExprList_item* pItem = &pEList->a[i];
|
||||||
|
|
||||||
update_field_infos(this, pAliases, 0, pItem->pExpr, usage, QC_TOKEN_MIDDLE, pExclude);
|
update_field_infos(pAliases, 0, pItem->pExpr, usage, QC_TOKEN_MIDDLE, pExclude);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -838,7 +1131,7 @@ public:
|
|||||||
if (pSelect->pWhere)
|
if (pSelect->pWhere)
|
||||||
{
|
{
|
||||||
m_has_clause = true;
|
m_has_clause = true;
|
||||||
update_field_infos(this, &aliases,
|
update_field_infos(&aliases,
|
||||||
0, pSelect->pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pSelect->pEList);
|
0, pSelect->pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pSelect->pEList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -854,7 +1147,7 @@ public:
|
|||||||
#if defined(COLLECT_HAVING_AS_WELL)
|
#if defined(COLLECT_HAVING_AS_WELL)
|
||||||
// A HAVING clause can only refer to fields that already have been
|
// A HAVING clause can only refer to fields that already have been
|
||||||
// mentioned. Consequently, they need not be collected.
|
// mentioned. Consequently, they need not be collected.
|
||||||
update_field_infos(this, aliases, 0, pSelect->pHaving, 0, QC_TOKEN_MIDDLE, pSelect->pEList);
|
update_field_infos(aliases, 0, pSelect->pHaving, 0, QC_TOKEN_MIDDLE, pSelect->pEList);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -909,6 +1202,71 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update_function_info(const char* name, uint32_t usage)
|
||||||
|
{
|
||||||
|
ss_dassert(name);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = map_function_name(m_pFunction_name_mappings, name);
|
||||||
|
|
||||||
|
QC_FUNCTION_INFO item = { (char*)name, usage };
|
||||||
|
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < m_function_infos_len; ++i)
|
||||||
|
{
|
||||||
|
QC_FUNCTION_INFO* function_info = m_pFunction_infos + i;
|
||||||
|
|
||||||
|
if (strcasecmp(item.name, function_info->name) == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QC_FUNCTION_INFO* function_infos = NULL;
|
||||||
|
|
||||||
|
if (i == m_function_infos_len) // If true, the function was not present already.
|
||||||
|
{
|
||||||
|
if (m_function_infos_len < m_function_infos_capacity)
|
||||||
|
{
|
||||||
|
function_infos = m_pFunction_infos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t capacity = m_function_infos_capacity ? 2 * m_function_infos_capacity : 8;
|
||||||
|
function_infos = (QC_FUNCTION_INFO*)MXS_REALLOC(m_pFunction_infos,
|
||||||
|
capacity * sizeof(QC_FUNCTION_INFO));
|
||||||
|
|
||||||
|
if (function_infos)
|
||||||
|
{
|
||||||
|
m_pFunction_infos = function_infos;
|
||||||
|
m_function_infos_capacity = capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pFunction_infos[i].usage |= usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If function_infos is NULL, then the function was found and has already been noted.
|
||||||
|
if (function_infos)
|
||||||
|
{
|
||||||
|
ss_dassert(item.name);
|
||||||
|
item.name = MXS_STRDUP(item.name);
|
||||||
|
|
||||||
|
if (item.name)
|
||||||
|
{
|
||||||
|
function_infos[m_function_infos_len++] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// sqlite3 callbacks
|
// sqlite3 callbacks
|
||||||
//
|
//
|
||||||
@ -1136,7 +1494,7 @@ public:
|
|||||||
|
|
||||||
if (pWhere)
|
if (pWhere)
|
||||||
{
|
{
|
||||||
update_field_infos(this, &aliases, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, 0);
|
update_field_infos(&aliases, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1359,14 +1717,14 @@ public:
|
|||||||
{
|
{
|
||||||
ExprList::ExprList_item* pItem = &pChanges->a[i];
|
ExprList::ExprList_item* pItem = &pChanges->a[i];
|
||||||
|
|
||||||
update_field_infos(this, &aliases,
|
update_field_infos(&aliases,
|
||||||
0, pItem->pExpr, QC_USED_IN_SET, QC_TOKEN_MIDDLE, NULL);
|
0, pItem->pExpr, QC_USED_IN_SET, QC_TOKEN_MIDDLE, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pWhere)
|
if (pWhere)
|
||||||
{
|
{
|
||||||
update_field_infos(this, &aliases, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pChanges);
|
update_field_infos(&aliases, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pChanges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3104,149 +3462,12 @@ static bool should_exclude(const char* zName, const ExprList* pExclude)
|
|||||||
return i != pExclude->nExpr;
|
return i != pExclude->nExpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_function_info(QcSqliteInfo* info,
|
|
||||||
const char* name,
|
|
||||||
uint32_t usage)
|
|
||||||
{
|
|
||||||
ss_dassert(name);
|
|
||||||
|
|
||||||
if (!(info->m_collect & QC_COLLECT_FUNCTIONS) || (info->m_collected & QC_COLLECT_FUNCTIONS))
|
|
||||||
{
|
|
||||||
// If function information should not be collected, or if function information
|
|
||||||
// has already been collected, we just return.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = map_function_name(info->m_pFunction_name_mappings, name);
|
|
||||||
|
|
||||||
QC_FUNCTION_INFO item = { (char*)name, usage };
|
|
||||||
|
|
||||||
size_t i;
|
|
||||||
for (i = 0; i < info->m_function_infos_len; ++i)
|
|
||||||
{
|
|
||||||
QC_FUNCTION_INFO* function_info = info->m_pFunction_infos + i;
|
|
||||||
|
|
||||||
if (strcasecmp(item.name, function_info->name) == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QC_FUNCTION_INFO* function_infos = NULL;
|
|
||||||
|
|
||||||
if (i == info->m_function_infos_len) // If true, the function was not present already.
|
|
||||||
{
|
|
||||||
if (info->m_function_infos_len < info->m_function_infos_capacity)
|
|
||||||
{
|
|
||||||
function_infos = info->m_pFunction_infos;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
size_t capacity = info->m_function_infos_capacity ? 2 * info->m_function_infos_capacity : 8;
|
|
||||||
function_infos = (QC_FUNCTION_INFO*)MXS_REALLOC(info->m_pFunction_infos,
|
|
||||||
capacity * sizeof(QC_FUNCTION_INFO));
|
|
||||||
|
|
||||||
if (function_infos)
|
|
||||||
{
|
|
||||||
info->m_pFunction_infos = function_infos;
|
|
||||||
info->m_function_infos_capacity = capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info->m_pFunction_infos[i].usage |= usage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If function_infos is NULL, then the function was found and has already been noted.
|
|
||||||
if (function_infos)
|
|
||||||
{
|
|
||||||
ss_dassert(item.name);
|
|
||||||
item.name = MXS_STRDUP(item.name);
|
|
||||||
|
|
||||||
if (item.name)
|
|
||||||
{
|
|
||||||
function_infos[info->m_function_infos_len++] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void maxscale_update_function_info(const char* name, uint32_t usage)
|
extern void maxscale_update_function_info(const char* name, uint32_t usage)
|
||||||
{
|
{
|
||||||
QcSqliteInfo* info = this_thread.pInfo;
|
QcSqliteInfo* pInfo = this_thread.pInfo;
|
||||||
|
ss_dassert(pInfo);
|
||||||
|
|
||||||
update_function_info(info, name, usage);
|
pInfo->update_function_info(name, usage);
|
||||||
}
|
|
||||||
|
|
||||||
static void update_field_infos_from_expr(QcSqliteInfo* info,
|
|
||||||
QcAliases* pAliases,
|
|
||||||
const Expr* pExpr,
|
|
||||||
uint32_t usage,
|
|
||||||
const ExprList* pExclude)
|
|
||||||
{
|
|
||||||
QC_FIELD_INFO item = {};
|
|
||||||
|
|
||||||
if (pExpr->op == TK_ASTERISK)
|
|
||||||
{
|
|
||||||
item.column = (char*)"*";
|
|
||||||
}
|
|
||||||
else if (pExpr->op == TK_ID)
|
|
||||||
{
|
|
||||||
// select a from...
|
|
||||||
item.column = pExpr->u.zToken;
|
|
||||||
}
|
|
||||||
else if (pExpr->op == TK_DOT)
|
|
||||||
{
|
|
||||||
if (pExpr->pLeft->op == TK_ID &&
|
|
||||||
(pExpr->pRight->op == TK_ID || pExpr->pRight->op == TK_ASTERISK))
|
|
||||||
{
|
|
||||||
// select a.b from...
|
|
||||||
item.table = pExpr->pLeft->u.zToken;
|
|
||||||
if (pExpr->pRight->op == TK_ID)
|
|
||||||
{
|
|
||||||
item.column = pExpr->pRight->u.zToken;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item.column = (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...
|
|
||||||
item.database = pExpr->pLeft->u.zToken;
|
|
||||||
item.table = pExpr->pRight->pLeft->u.zToken;
|
|
||||||
if (pExpr->pRight->pRight->op == TK_ID)
|
|
||||||
{
|
|
||||||
item.column = pExpr->pRight->pRight->u.zToken;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item.column = (char*)"*";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.column)
|
|
||||||
{
|
|
||||||
bool should_update = true;
|
|
||||||
|
|
||||||
if ((pExpr->flags & EP_DblQuoted) == 0)
|
|
||||||
{
|
|
||||||
if ((strcasecmp(item.column, "true") == 0) || (strcasecmp(item.column, "false") == 0))
|
|
||||||
{
|
|
||||||
should_update = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (should_update)
|
|
||||||
{
|
|
||||||
info->update_field_info(pAliases, item.database, item.table, item.column, usage, pExclude);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* get_token_symbol(int token)
|
static const char* get_token_symbol(int token)
|
||||||
@ -3317,245 +3538,6 @@ static const char* get_token_symbol(int token)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_field_infos(QcSqliteInfo* info,
|
|
||||||
QcAliases* pAliases,
|
|
||||||
int prev_token,
|
|
||||||
const Expr* pExpr,
|
|
||||||
uint32_t usage,
|
|
||||||
qc_token_position_t pos,
|
|
||||||
const ExprList* pExclude)
|
|
||||||
{
|
|
||||||
const Expr* pLeft = pExpr->pLeft;
|
|
||||||
const Expr* pRight = pExpr->pRight;
|
|
||||||
const char* zToken = pExpr->u.zToken;
|
|
||||||
|
|
||||||
bool ignore_exprlist = false;
|
|
||||||
|
|
||||||
switch (pExpr->op)
|
|
||||||
{
|
|
||||||
case TK_ASTERISK: // select *
|
|
||||||
update_field_infos_from_expr(info, pAliases, pExpr, usage, pExclude);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TK_DOT: // select a.b ... select a.b.c
|
|
||||||
update_field_infos_from_expr(info, pAliases, pExpr, usage, pExclude);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TK_ID: // select a
|
|
||||||
update_field_infos_from_expr(info, pAliases, pExpr, usage, pExclude);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TK_VARIABLE:
|
|
||||||
{
|
|
||||||
if (zToken[0] == '@')
|
|
||||||
{
|
|
||||||
if (zToken[1] == '@')
|
|
||||||
{
|
|
||||||
if ((prev_token == TK_EQ) && (pos == QC_TOKEN_LEFT))
|
|
||||||
{
|
|
||||||
info->m_type_mask |= QUERY_TYPE_GSYSVAR_WRITE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((strcasecmp(&zToken[2], "identity") == 0) ||
|
|
||||||
(strcasecmp(&zToken[2], "last_insert_id") == 0))
|
|
||||||
{
|
|
||||||
info->m_type_mask |= QUERY_TYPE_MASTER_READ;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info->m_type_mask |= QUERY_TYPE_SYSVAR_READ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((prev_token == TK_EQ) && (pos == QC_TOKEN_LEFT))
|
|
||||||
{
|
|
||||||
info->m_type_mask |= QUERY_TYPE_USERVAR_WRITE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info->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:
|
|
||||||
// We don't report "=" if it's not used in a specific context (SELECT, WHERE)
|
|
||||||
// and if it is used in SET. We also exclude it it in a context where a
|
|
||||||
// variable is set.
|
|
||||||
if (((usage != 0) && (usage != QC_USED_IN_SET)) &&
|
|
||||||
(!pExpr->pLeft || (pExpr->pLeft->op != TK_VARIABLE)))
|
|
||||||
{
|
|
||||||
update_function_info(info, get_token_symbol(pExpr->op), usage);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TK_GE:
|
|
||||||
case TK_GT:
|
|
||||||
case TK_LE:
|
|
||||||
case TK_LT:
|
|
||||||
case TK_NE:
|
|
||||||
|
|
||||||
case TK_BETWEEN:
|
|
||||||
case TK_BITAND:
|
|
||||||
case TK_BITOR:
|
|
||||||
case TK_CASE:
|
|
||||||
case TK_IN:
|
|
||||||
case TK_ISNULL:
|
|
||||||
case TK_MINUS:
|
|
||||||
case TK_NOTNULL:
|
|
||||||
case TK_PLUS:
|
|
||||||
case TK_SLASH:
|
|
||||||
case TK_STAR:
|
|
||||||
update_function_info(info, get_token_symbol(pExpr->op), usage);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TK_REM:
|
|
||||||
if (info->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(info, sqlrowcount, usage);
|
|
||||||
|
|
||||||
pLeft = NULL;
|
|
||||||
pRight = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
update_function_info(info, get_token_symbol(pExpr->op), usage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
update_function_info(info, get_token_symbol(pExpr->op), usage);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TK_UMINUS:
|
|
||||||
switch (this_unit.parse_as)
|
|
||||||
{
|
|
||||||
case QC_PARSE_AS_DEFAULT:
|
|
||||||
update_function_info(info, get_token_symbol(pExpr->op), usage);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QC_PARSE_AS_103:
|
|
||||||
// In MariaDB 10.3 a unary minus is not considered a function.
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
ss_dassert(!true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TK_FUNCTION:
|
|
||||||
if (zToken)
|
|
||||||
{
|
|
||||||
if (strcasecmp(zToken, "last_insert_id") == 0)
|
|
||||||
{
|
|
||||||
info->m_type_mask |= (QUERY_TYPE_READ | QUERY_TYPE_MASTER_READ);
|
|
||||||
}
|
|
||||||
else if (info->is_sequence_related_function(zToken))
|
|
||||||
{
|
|
||||||
info->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,
|
|
||||||
info->m_sql_mode == QC_SQL_MODE_ORACLE))
|
|
||||||
{
|
|
||||||
info->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(info, zToken, usage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pLeft)
|
|
||||||
{
|
|
||||||
update_field_infos(info, pAliases, pExpr->op, pExpr->pLeft, usage, QC_TOKEN_LEFT, pExclude);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pRight)
|
|
||||||
{
|
|
||||||
if (usage & QC_USED_IN_SET)
|
|
||||||
{
|
|
||||||
usage &= ~QC_USED_IN_SET;
|
|
||||||
}
|
|
||||||
|
|
||||||
update_field_infos(info, pAliases, pExpr->op, pExpr->pRight, usage, QC_TOKEN_RIGHT, pExclude);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pExpr->x.pList)
|
|
||||||
{
|
|
||||||
switch (pExpr->op)
|
|
||||||
{
|
|
||||||
case TK_BETWEEN:
|
|
||||||
case TK_CASE:
|
|
||||||
case TK_FUNCTION:
|
|
||||||
if (!ignore_exprlist)
|
|
||||||
{
|
|
||||||
info->update_field_infos_from_exprlist(pAliases, pExpr->x.pList, usage, pExclude);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TK_EXISTS:
|
|
||||||
case TK_IN:
|
|
||||||
case TK_SELECT:
|
|
||||||
if (pExpr->flags & EP_xIsSelect)
|
|
||||||
{
|
|
||||||
uint32_t sub_usage = usage;
|
|
||||||
|
|
||||||
sub_usage &= ~QC_USED_IN_SELECT;
|
|
||||||
sub_usage |= QC_USED_IN_SUBSELECT;
|
|
||||||
info->update_field_infos_from_subselect(pAliases, pExpr->x.pSelect, sub_usage, pExclude);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info->update_field_infos_from_exprlist(pAliases, pExpr->x.pList, usage, pExclude);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* SQLITE
|
* SQLITE
|
||||||
|
Reference in New Issue
Block a user