diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index 3fed16c1a..cb0534104 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -1807,13 +1807,30 @@ int32_t qc_mysql_query_has_clause(GWBUF* buf, int32_t* has_clause) if (lex) { - if (!lex->describe && !is_show_command(lex->sql_command)) + int cmd = lex->sql_command; + + if (!lex->describe + && !is_show_command(cmd) + && (cmd != SQLCOM_ALTER_PROCEDURE) + && (cmd != SQLCOM_ALTER_TABLE) + && (cmd != SQLCOM_CALL) + && (cmd != SQLCOM_CREATE_PROCEDURE) + && (cmd != SQLCOM_CREATE_TABLE) + && (cmd != SQLCOM_DROP_FUNCTION) + && (cmd != SQLCOM_DROP_PROCEDURE) + && (cmd != SQLCOM_DROP_TABLE) + && (cmd != SQLCOM_DROP_VIEW) + && (cmd != SQLCOM_FLUSH) + && (cmd != SQLCOM_ROLLBACK) + ) { SELECT_LEX* current = lex->all_selects_list; while (current && !*has_clause) { - if (current->where || current->having) + if (current->where || current->having || + ((cmd == SQLCOM_SELECT || cmd == SQLCOM_DELETE || cmd == SQLCOM_UPDATE) + && current->select_limit)) { *has_clause = true; } @@ -2934,6 +2951,7 @@ typedef enum collect_source COLLECT_WHERE, COLLECT_HAVING, COLLECT_GROUP_BY, + COLLECT_ORDER_BY } collect_source_t; static void update_field_infos(parsing_info_t* pi, @@ -2974,6 +2992,7 @@ static bool should_function_be_ignored(parsing_info_t* pi, const char* func_name || (strcasecmp(func_name, "get_user_var") == 0) || (strcasecmp(func_name, "get_system_var") == 0) || (strcasecmp(func_name, "not") == 0) + || (strcasecmp(func_name, "collate") == 0) || (strcasecmp(func_name, "set_user_var") == 0) || (strcasecmp(func_name, "set_system_var") == 0)) { @@ -3327,6 +3346,19 @@ static void update_field_infos(parsing_info_t* pi, } } + if (select->order_list.first) + { + ORDER* order = select->order_list.first; + while (order) + { + Item* item = *order->item; + + update_field_infos(pi, select, COLLECT_ORDER_BY, item, &select->item_list); + + order = order->next; + } + } + if (select->where) { update_field_infos(pi, diff --git a/query_classifier/qc_sqlite/builtin_functions.c b/query_classifier/qc_sqlite/builtin_functions.c index fdcbabfdd..330881df0 100644 --- a/query_classifier/qc_sqlite/builtin_functions.c +++ b/query_classifier/qc_sqlite/builtin_functions.c @@ -87,7 +87,7 @@ static const char* BUILTIN_FUNCTIONS[] = "subtime", "sysdate", "time", - "timediff" + "timediff", "timestamp", "timestampadd", "timestampdiff", diff --git a/query_classifier/qc_sqlite/qc_sqlite.cc b/query_classifier/qc_sqlite/qc_sqlite.cc index c1f61c2e6..f82880190 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.cc +++ b/query_classifier/qc_sqlite/qc_sqlite.cc @@ -1264,6 +1264,18 @@ public: const ExprList* pExclude, compound_approach_t compound_approach = ANALYZE_COMPOUND_SELECTS) { + if (pSelect->pLimit) + { + // In case there is an ORDER BY statement without a LIMIT, which is + // not accepted by sqlite, a pseudo LIMIT with the value of -1 is injected. + // We need to detect that so as not to incorrectly claim that there is + // a clause. See maxscale_create_pseudo_limit() in parse.y. + if (pSelect->pLimit->op != TK_INTEGER || pSelect->pLimit->u.iValue != -1) + { + m_has_clause = true; + } + } + if (pSelect->pSrc) { const SrcList* pSrc = pSelect->pSrc; @@ -1330,6 +1342,14 @@ public: #endif } + if (pSelect->pOrderBy) + { + update_field_infos_from_exprlist(&aliases, + context, + pSelect->pOrderBy, + pSelect->pEList); + } + if (pSelect->pWith) { update_field_infos_from_with(&aliases, context, pSelect->pWith); @@ -1872,6 +1892,9 @@ public: QcAliases aliases; uint32_t context = 0; update_field_infos_from_select(aliases, context, pSelect, NULL); + + // Non-sensical to claim that a "CREATE ... SELECT ... WHERE ..." statement has a clause. + m_has_clause = false; } else if (pOldTable) { @@ -2038,7 +2061,7 @@ public: m_type_mask = QUERY_TYPE_WRITE; m_operation = QUERY_OP_UPDATE; update_names_from_srclist(&aliases, pTabList); - m_has_clause = (pWhere ? true : false); + m_has_clause = ((pWhere && pWhere->op != TK_IN) ? true : false); if (pChanges) { 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 5f26b05ef..c1e1cd542 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -135,6 +135,8 @@ extern void maxscaleUse(Parse*, Token*); extern void maxscale_update_function_info(const char* name, const Expr* pExpr); extern void maxscale_set_type_mask(unsigned int type_mask); +static Expr* maxscale_create_pseudo_limit(Parse* pParse, Token* pToken, ExprSpan* pLimit); + // Exposed utility functions void exposed_sqlite3ExprDelete(sqlite3 *db, Expr *pExpr) { @@ -1528,12 +1530,14 @@ cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W) sqlite3WithPush(pParse, C, 1); sqlite3SrcListIndexedBy(pParse, X, &I); #ifdef MAXSCALE - // We are not interested in the order by or limit information. - // Thus we simply delete it. sqlite also has some limitations, which - // will not bother us if we hide the information from it. - sqlite3ExprListDelete(pParse->db, O); - sqlite3ExprDelete(pParse->db, L.pLimit); - sqlite3ExprDelete(pParse->db, L.pOffset); + Token token; + ExprSpan limit; + if (O && !L.pLimit) { + L.pLimit = maxscale_create_pseudo_limit(pParse, &token, &limit); + sqlite3ExprDelete(pParse->db, L.pOffset); + L.pOffset = 0; + } + W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "DELETE"); mxs_sqlite3DeleteFrom(pParse,X,W,0); #else W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "DELETE"); @@ -1690,12 +1694,15 @@ cmd ::= with(C) UPDATE orconf(R) fullname(X) indexed_opt(I) SET setlist(Y) sqlite3SrcListIndexedBy(pParse, X, &I); sqlite3ExprListCheckLength(pParse,Y,"set list"); #ifdef MAXSCALE - // We are not interested in the order by or limit information. - // Thus we simply delete it. sqlite also has some limitations, which - // will not bother us if we hide the information from it. - sqlite3ExprListDelete(pParse->db, O); - sqlite3ExprDelete(pParse->db, L.pLimit); - sqlite3ExprDelete(pParse->db, L.pOffset); + Token token; + ExprSpan limit; + if (O && !L.pLimit) { + L.pLimit = maxscale_create_pseudo_limit(pParse, &token, &limit); + sqlite3ExprDelete(pParse->db, L.pOffset); + L.pOffset = 0; + } + + W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "UPDATE"); mxs_sqlite3Update(pParse,X,Y,W,R); #else W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "UPDATE"); @@ -3525,3 +3532,21 @@ cmd ::= DECLARE. { } %endif + +%include { + +static Expr* maxscale_create_pseudo_limit(Parse* pParse, Token* pToken, ExprSpan* pLimit) +{ + // sqlite3 does not accept a ORDER BY without LIMIT, but MariaDB + // does. Thus, to make sqlite3LimitWhere return non-NULL we need + // to inject a LIMIT if there is none. We use a value of -1 to + // tell update_field_infos_from_select() that this LIMIT should + // not be counted as a limiting clause. + static char one[] = "-1"; + pToken->z = one; + pToken->n = 1; + spanExpr(pLimit, pParse, TK_INTEGER, pToken); + return pLimit->pExpr; +} + +} diff --git a/query_classifier/test/classify.cc b/query_classifier/test/classify.cc index 146238eb7..23ed71b1a 100644 --- a/query_classifier/test/classify.cc +++ b/query_classifier/test/classify.cc @@ -76,6 +76,10 @@ char* get_types_as_string(uint32_t types) { s = append(s, "QUERY_TYPE_SESSION_WRITE", &len); } + if (types & QUERY_TYPE_USERVAR_WRITE) + { + s = append(s, "QUERY_TYPE_USERVAR_WRITE", &len); + } if (types & QUERY_TYPE_USERVAR_READ) { s = append(s, "QUERY_TYPE_USERVAR_READ", &len); diff --git a/query_classifier/test/expected.sql b/query_classifier/test/expected.sql index ce7ec5ff2..58637a910 100644 --- a/query_classifier/test/expected.sql +++ b/query_classifier/test/expected.sql @@ -28,3 +28,7 @@ QUERY_TYPE_WRITE QUERY_TYPE_WRITE QUERY_TYPE_WRITE QUERY_TYPE_GSYSVAR_WRITE +QUERY_TYPE_READ +QUERY_TYPE_READ +QUERY_TYPE_USERVAR_WRITE +QUERY_TYPE_READ|QUERY_TYPE_MASTER_READ diff --git a/query_classifier/test/input.sql b/query_classifier/test/input.sql index 3575bcb40..43fc0af57 100644 --- a/query_classifier/test/input.sql +++ b/query_classifier/test/input.sql @@ -25,6 +25,10 @@ SELECT IS_USED_LOCK('lock1'); SELECT RELEASE_LOCK('lock1'); deallocate prepare select_stmt; SELECT a FROM tbl FOR UPDATE; -SELECT a INTO OUTFILE 'out.txt' -SELECT a INTO DUMPFILE 'dump.txt' +SELECT a INTO OUTFILE 'out.txt'; +SELECT a INTO DUMPFILE 'dump.txt'; SELECT a INTO @var; +select timediff(cast('2004-12-30 12:00:00' as time), '12:00:00'); +(select 1 as a from t1) union all (select 1 from dual) limit 1; +SET @saved_cs_client= @@character_set_client; +SELECT 1 AS c1 FROM t1 ORDER BY ( SELECT 1 AS c2 FROM t1 GROUP BY GREATEST(LAST_INSERT_ID(), t1.a) ORDER BY GREATEST(LAST_INSERT_ID(), t1.a) LIMIT 1);