diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index ea48b0a39..05dc33d3f 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -2153,6 +2153,78 @@ void maxscaleExecute(Parse* pParse, Token* pName, int type_mask) } } +static int32_t type_check_dynamic_string(const Expr* pExpr) +{ + int32_t type_mask = 0; + + if (pExpr) + { + switch (pExpr->op) + { + case TK_CONCAT: + type_mask |= type_check_dynamic_string(pExpr->pLeft); + type_mask |= type_check_dynamic_string(pExpr->pRight); + break; + + case TK_VARIABLE: + ss_dassert(pExpr->u.zToken); + { + const char* zToken = pExpr->u.zToken; + if (zToken[0] == '@') + { + if (zToken[1] == '@') + { + type_mask |= QUERY_TYPE_SYSVAR_READ; + } + else + { + type_mask |= QUERY_TYPE_USERVAR_READ; + } + } + } + break; + + default: + break; + } + } + + return type_mask; +} + +void maxscaleExecuteImmediate(Parse* pParse, Token* pName, ExprSpan* pExprSpan, int type_mask) +{ + QC_TRACE(); + + QC_SQLITE_INFO* info = this_thread.info; + ss_dassert(info); + + if (this_unit.sql_mode == QC_SQL_MODE_ORACLE) + { + // This should be "EXECUTE IMMEDIATE ...", but as "IMMEDIATE" is not + // checked by the parser we do it here. + + static const char IMMEDIATE[] = "IMMEDIATE"; + + if ((pName->n == sizeof(IMMEDIATE) - 1) && (strncasecmp(pName->z, IMMEDIATE, pName->n)) == 0) + { + info->status = QC_QUERY_PARSED; + info->type_mask = (QUERY_TYPE_WRITE | type_mask); + info->type_mask |= type_check_dynamic_string(pExprSpan->pExpr); + } + else + { + info->status = QC_QUERY_INVALID; + } + } + else + { + info->status = QC_QUERY_INVALID; + } + + exposed_sqlite3ExprDelete(pParse->db, pExprSpan->pExpr); +} + void maxscaleExplain(Parse* pParse, Token* pNext) { QC_TRACE(); @@ -2519,14 +2591,72 @@ void maxscaleRenameTable(Parse* pParse, SrcList* pTables) exposed_sqlite3SrcListDelete(pParse->db, pTables); } -void maxscalePrepare(Parse* pParse, Token* pName, Token* pStmt) +/** + * Returns some string from an expression. + * + * @param pExpr An expression. + * + * @return Some string referred to in pExpr. + */ +static const char* find_one_string(Expr* pExpr) +{ + const char* z = NULL; + + if (pExpr->op == TK_STRING) + { + ss_dassert(pExpr->u.zToken); + z = pExpr->u.zToken; + } + + if (!z && pExpr->pLeft) + { + z = find_one_string(pExpr->pLeft); + } + + if (!z && pExpr->pRight) + { + z = find_one_string(pExpr->pRight); + } + + return z; +} + +void maxscalePrepare(Parse* pParse, Token* pName, Expr* pStmt) { QC_TRACE(); QC_SQLITE_INFO* info = this_thread.info; ss_dassert(info); - info->status = QC_QUERY_PARSED; + // If the mode is MODE_ORACLE then if expression contains simply a string + // we can conclude that the statement has been fully parsed, because it will + // be sensible to parse the preparable statement. Otherwise we mark the + // statement as having been partially parsed, since the preparable statement + // will not contain the full statement. + if (this_unit.sql_mode == QC_SQL_MODE_ORACLE) + { + if (pStmt->op == TK_STRING) + { + info->status = QC_QUERY_PARSED; + } + else + { + info->status = QC_QUERY_PARTIALLY_PARSED; + } + } + else + { + // If the mode is not MODE_ORACLE, then only a string is acceptable. + if (pStmt->op == TK_STRING) + { + info->status = QC_QUERY_PARSED; + } + else + { + info->status = QC_QUERY_INVALID; + } + } + info->type_mask = QUERY_TYPE_PREPARE_NAMED_STMT; // If information is collected in several passes, then we may @@ -2540,25 +2670,38 @@ void maxscalePrepare(Parse* pParse, Token* pName, Token* pStmt) info->prepare_name[pName->n] = 0; } - size_t preparable_stmt_len = pStmt->n - 2; - size_t payload_len = 1 + preparable_stmt_len; - size_t packet_len = MYSQL_HEADER_LEN + payload_len; + // If the expression just contains a string, then zStmt will + // be that string. Otherwise it will be _some_ string from the + // expression. In the latter case we've already marked the result + // to have been partially parsed. + const char* zStmt = find_one_string(pStmt); - info->preparable_stmt = gwbuf_alloc(packet_len); - - if (info->preparable_stmt) + if (zStmt) { - uint8_t* ptr = GWBUF_DATA(info->preparable_stmt); - // Payload length - *ptr++ = payload_len; - *ptr++ = (payload_len >> 8); - *ptr++ = (payload_len >> 16); - // Sequence id - *ptr++ = 0x00; - // Command - *ptr++ = MYSQL_COM_QUERY; + 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; - memcpy(ptr, pStmt->z + 1, pStmt->n - 2); + info->preparable_stmt = gwbuf_alloc(packet_len); + + if (info->preparable_stmt) + { + uint8_t* ptr = GWBUF_DATA(info->preparable_stmt); + // Payload length + *ptr++ = payload_len; + *ptr++ = (payload_len >> 8); + *ptr++ = (payload_len >> 16); + // Sequence id + *ptr++ = 0x00; + // Command + *ptr++ = MYSQL_COM_QUERY; + + memcpy(ptr, zStmt, preparable_stmt_len); + } + } + else + { + info->status = QC_QUERY_INVALID; } } else @@ -2566,6 +2709,8 @@ void maxscalePrepare(Parse* pParse, Token* pName, Token* pStmt) ss_dassert(info->collect != info->collected); ss_dassert(strncmp(info->prepare_name, pName->z, pName->n) == 0); } + + exposed_sqlite3ExprDelete(pParse->db, pStmt); } void maxscalePrivileges(Parse* pParse, int kind) diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index 00a6481ec..7eadc9df1 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -114,12 +114,13 @@ extern void maxscaleDeallocate(Parse*, Token* pName); extern void maxscaleDo(Parse*, ExprList* pEList); extern void maxscaleDrop(Parse*, MxsDrop* pDrop); 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); extern void maxscaleLock(Parse*, mxs_lock_t, SrcList*); -extern void maxscalePrepare(Parse*, Token* pName, Token* pStmt); +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*); @@ -2857,9 +2858,9 @@ cmd ::= prepare. cmd ::= execute. cmd ::= deallocate. -prepare ::= PREPARE nm(X) FROM STRING(Y). +prepare ::= PREPARE nm(X) FROM expr(Y). { - maxscalePrepare(pParse, &X, &Y); + maxscalePrepare(pParse, &X, Y.pExpr); } %type execute_variable {int} @@ -2881,6 +2882,10 @@ execute ::= EXECUTE nm(X) execute_variables_opt(Y). { maxscaleExecute(pParse, &X, Y); } +execute ::= EXECUTE id(X) expr(Y) execute_variables_opt(Z). { + maxscaleExecuteImmediate(pParse, &X, &Y, Z); +} + dod ::= DEALLOCATE. dod ::= DROP.