From b8d601aab20c34e2025fcb2bb2256fac25cb870b Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 4 Jun 2019 16:51:53 +0300 Subject: [PATCH] MXS-2250 Reveal table information for 'DESCRIBE tbl' When a statement like 'DESCRIBE tbl' is classified, the table name will now be available so that a router can check whether the table is a temporary one. In that case, the statement must be sent to the master. --- .../qc_mysqlembedded/qc_mysqlembedded.cc | 6 +- query_classifier/qc_sqlite/qc_sqlite.cc | 104 ++++++++++-------- .../qc_sqlite/sqlite-src-3110100/src/parse.y | 29 +++-- .../sqlite-src-3110100/tool/mkkeywordhash.c | 4 + query_classifier/test/compare.cc | 4 + query_classifier/test/create.test | 3 +- 6 files changed, 93 insertions(+), 57 deletions(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index 7689c99d1..477a9e3b0 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -1512,7 +1512,7 @@ int32_t qc_mysql_get_table_names(GWBUF* querybuf, int32_t fullnames, char*** tab goto retblock; } - if (lex->describe || is_show_command(lex->sql_command)) + if (lex->describe || (is_show_command(lex->sql_command) && !(lex->sql_command == SQLCOM_SHOW_FIELDS))) { goto retblock; } @@ -1850,7 +1850,9 @@ int32_t qc_mysql_get_database_names(GWBUF* querybuf, char*** databasesp, int* si goto retblock; } - if (lex->describe || (is_show_command(lex->sql_command) && !(lex->sql_command == SQLCOM_SHOW_TABLES))) + if (lex->describe || (is_show_command(lex->sql_command) + && !(lex->sql_command == SQLCOM_SHOW_TABLES) + && !(lex->sql_command == SQLCOM_SHOW_FIELDS))) { goto retblock; } diff --git a/query_classifier/qc_sqlite/qc_sqlite.cc b/query_classifier/qc_sqlite/qc_sqlite.cc index ca8545eff..5d09b5f45 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.cc +++ b/query_classifier/qc_sqlite/qc_sqlite.cc @@ -738,7 +738,7 @@ public: if (should_collect_database) { - zCollected_database = update_database_names(database); + zCollected_database = update_database_names(database, nDatabase); } if (pAliases && zCollected_table && zAlias) @@ -2273,11 +2273,7 @@ public: // TODO: certain what a returned database actually refers to // TODO: so better not to provide a name until there is a // TODO: specific op. - char database[pDatabase->n + 1]; - strncpy(database, pDatabase->z, pDatabase->n); - database[pDatabase->n] = 0; - - update_database_names(database); + update_database_names(pDatabase->z, pDatabase->n); #endif } break; @@ -2361,7 +2357,7 @@ public: exposed_sqlite3ExprDelete(pParse->db, pExprSpan->pExpr); } - void maxscaleExplain(Parse* pParse, Token* pNext) + void maxscaleExplainTable(Parse* pParse, SrcList* pList) { mxb_assert(this_thread.initialized); @@ -2369,28 +2365,24 @@ public: m_type_mask = QUERY_TYPE_READ; m_operation = QUERY_OP_SHOW; - if (pNext) + for (int i = 0; i < pList->nSrc; ++i) { - if (pNext->z) + if (pList->a[i].zName) { - 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; - } + update_names(pList->a[i].zDatabase, pList->a[i].zName, pList->a[i].zAlias, nullptr); } } } + void maxscaleExplain(Parse* pParse) + { + mxb_assert(this_thread.initialized); + + m_status = QC_QUERY_PARSED; + m_type_mask = QUERY_TYPE_READ; + m_operation = QUERY_OP_EXPLAIN; + } + void maxscaleFlush(Parse* pParse, Token* pWhat) { mxb_assert(this_thread.initialized); @@ -3023,7 +3015,21 @@ public: switch (pShow->what) { case MXS_SHOW_COLUMNS: - m_type_mask = QUERY_TYPE_READ; + { + m_type_mask = QUERY_TYPE_READ; + const char* zDatabase = nullptr; + size_t nDatabase = 0; + + if (pShow->pDatabase) + { + zDatabase = pShow->pDatabase->z; + nDatabase = pShow->pDatabase->n; + + update_database_names(zDatabase, nDatabase); + } + + update_table_names(zDatabase, nDatabase, pShow->pName->z, pShow->pName->n); + } break; case MXS_SHOW_CREATE_SEQUENCE: @@ -3083,11 +3089,7 @@ public: m_type_mask = QUERY_TYPE_SHOW_TABLES; if (pShow->pDatabase->z) { - char db[pShow->pDatabase->n + 1]; - strncpy(db, pShow->pDatabase->z, pShow->pDatabase->n); - db[pShow->pDatabase->n] = 0; - - update_database_names(db); + update_database_names(pShow->pDatabase->z, pShow->pDatabase->n); } break; @@ -3272,11 +3274,13 @@ private: return pz; } - const char* table_name_collected(const char* zTable) + const char* table_name_collected(const char* zTable, size_t nTable) { size_t i = 0; - while ((i < m_table_names.size()) && (strcmp(m_table_names[i], zTable) != 0)) + while ((i < m_table_names.size()) + && (strlen(m_table_names[i]) != nTable + || (strncmp(m_table_names[i], zTable, nTable) != 0))) { ++i; } @@ -3296,11 +3300,13 @@ private: return (i != m_table_fullnames.size()) ? m_table_fullnames[i] : NULL; } - const char* database_name_collected(const char* zDatabase) + const char* database_name_collected(const char* zDatabase, size_t nDatabase) { size_t i = 0; - while ((i < m_database_names.size()) && (strcmp(m_database_names[i], zDatabase) != 0)) + while ((i < m_database_names.size()) + && (strlen(m_database_names[i]) != nDatabase + || (strncmp(m_database_names[i], zDatabase, nDatabase) != 0))) { ++i; } @@ -3315,11 +3321,11 @@ private: { mxb_assert(zTable && nTable); - const char* zCollected_table = table_name_collected(zTable); + const char* zCollected_table = table_name_collected(zTable, nTable); if (!zCollected_table) { - char* zCopy = MXS_STRDUP_A(zTable); + char* zCopy = MXS_STRNDUP_A(zTable, nTable); m_table_names.push_back(zCopy); @@ -3330,7 +3336,8 @@ private: if (nDatabase) { - strcpy(fullname, zDatabase); + strncpy(fullname, zDatabase, nDatabase); + fullname[nDatabase] = 0; strcat(fullname, "."); } else @@ -3338,7 +3345,7 @@ private: fullname[0] = 0; } - strcat(fullname, zTable); + strncat(fullname, zTable, nTable); if (!table_fullname_collected(fullname)) { @@ -3350,16 +3357,16 @@ private: return zCollected_table; } - const char* update_database_names(const char* zDatabase) + const char* update_database_names(const char* zDatabase, size_t nDatabase) { mxb_assert(zDatabase); mxb_assert(strlen(zDatabase) != 0); - const char* zCollected_database = database_name_collected(zDatabase); + const char* zCollected_database = database_name_collected(zDatabase, nDatabase); if (!zCollected_database) { - char* zCopy = MXS_STRDUP_A(zDatabase); + char* zCopy = MXS_STRNDUP_A(zDatabase, nDatabase); m_database_names.push_back(zCopy); @@ -3456,7 +3463,8 @@ extern "C" 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 maxscaleExplainTable(Parse*, SrcList* pList); + extern void maxscaleExplain(Parse*); 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); @@ -4381,14 +4389,24 @@ void maxscaleExecuteImmediate(Parse* pParse, Token* pName, ExprSpan* pExprSpan, QC_EXCEPTION_GUARD(pInfo->maxscaleExecuteImmediate(pParse, pName, pExprSpan, type_mask)); } -void maxscaleExplain(Parse* pParse, Token* pNext) +void maxscaleExplainTable(Parse* pParse, SrcList* pList) { QC_TRACE(); QcSqliteInfo* pInfo = this_thread.pInfo; mxb_assert(pInfo); - QC_EXCEPTION_GUARD(pInfo->maxscaleExplain(pParse, pNext)); + QC_EXCEPTION_GUARD(pInfo->maxscaleExplainTable(pParse, pList)); +} + +void maxscaleExplain(Parse* pParse) +{ + QC_TRACE(); + + QcSqliteInfo* pInfo = this_thread.pInfo; + mxb_assert(pInfo); + + QC_EXCEPTION_GUARD(pInfo->maxscaleExplain(pParse)); } void maxscaleFlush(Parse* pParse, Token* pWhat) 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 917b68f68..72690b5d1 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -117,7 +117,8 @@ 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 maxscaleExplainTable(Parse*, SrcList* pList); +extern void maxscaleExplain(Parse*); 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); @@ -302,13 +303,16 @@ ecmd ::= oracle_assignment SEMI. explain_kw ::= EXPLAIN. // Also covers DESCRIBE explain_kw ::= DESC. -explain ::= explain_kw. { pParse->explain = 1; } +explain ::= explain_kw tbl_name(A). { pParse->explain = 1; maxscaleExplainTable(pParse, A); } +explain_type ::= . +explain_type ::= EXTENDED. +explain_type ::= PARTITIONS. // deferred_id is defined later, after the id token_class has been defined. -explain ::= explain_kw deferred_id(A). { maxscaleExplain(pParse, &A); } -explain ::= explain_kw deferred_id(A) DOT deferred_id. { maxscaleExplain(pParse, &A); } -ecmd ::= explain FOR(A) deferred_id INTEGER SEMI. { // FOR CONNECTION connection_id +explain_type ::= FORMAT TK_EQ deferred_id. // FORMAT = {TRADITIONAL|JSON} + +explain ::= explain_kw explain_type FOR CONNECTION INTEGER. { // FOR CONNECTION connection_id pParse->explain = 1; - maxscaleExplain(pParse, &A); + maxscaleExplain(pParse); } %endif %ifndef SQLITE_OMIT_EXPLAIN @@ -614,10 +618,10 @@ columnid(A) ::= nm(X). { // TODO: BINARY is a reserved word and should not automatically convert into an identifer. // TODO: However, if not here then rules such as CAST need to be modified. BINARY - /*CASCADE*/ CAST CLOSE COLUMNKW COLUMNS COMMENT CONCURRENT /*CONFLICT*/ + /*CASCADE*/ CAST CLOSE COLUMNKW COLUMNS COMMENT CONCURRENT /*CONFLICT*/ CONNECTION DATA DATABASE DEALLOCATE DEFERRED /*DESC*/ /*DETACH*/ DUMPFILE - /*EACH*/ END ENGINE ENUM EXCLUSIVE /*EXPLAIN*/ - FIRST FLUSH /*FOR*/ FORMAT + /*EACH*/ END ENGINE ENUM EXCLUSIVE /*EXPLAIN*/ EXTENDED + FIELDS FIRST FLUSH /*FOR*/ FORMAT GLOBAL // TODO: IF is a reserved word and should not automatically convert into an identifer. IF IMMEDIATE INITIALLY INSTEAD @@ -627,7 +631,7 @@ columnid(A) ::= nm(X). { NAMES NEXT NO OF OFFSET OPEN - PREVIOUS + PARTITIONS PREVIOUS QUICK RAISE RECURSIVE /*REINDEX*/ RELEASE /*RENAME*/ /*REPLACE*/ RESTRICT ROLLBACK ROLLUP ROW SAVEPOINT SELECT_OPTIONS_KW /*SEQUENCE*/ SLAVE /*START*/ STATEMENT STATUS @@ -3241,7 +3245,10 @@ like_or_where_opt ::= WHERE expr. %type show {MxsShow} -show(A) ::= SHOW full_opt(X) COLUMNS from_or_in nm(Y) dbnm(Z) from_or_in_db_opt(W) like_or_where_opt . { +columns_or_fields ::= COLUMNS. +columns_or_fields ::= FIELDS. + +show(A) ::= SHOW full_opt(X) columns_or_fields from_or_in nm(Y) dbnm(Z) from_or_in_db_opt(W) like_or_where_opt . { A.what = MXS_SHOW_COLUMNS; A.data = X; if (Z.z) { diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/tool/mkkeywordhash.c b/query_classifier/qc_sqlite/sqlite-src-3110100/tool/mkkeywordhash.c index a8746a7df..bd813c1bc 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/tool/mkkeywordhash.c +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/tool/mkkeywordhash.c @@ -203,6 +203,7 @@ static Keyword aKeywordTable[] = { { "CONFLICT", "TK_CONFLICT", CONFLICT }, #endif #ifdef MAXSCALE + { "CONNECTION", "TK_CONNECTION", ALWAYS }, { "CONCURRENT", "TK_CONCURRENT", ALWAYS }, #endif { "CONSTRAINT", "TK_CONSTRAINT", ALWAYS }, @@ -260,6 +261,7 @@ static Keyword aKeywordTable[] = { #ifdef MAXSCALE { "EXECUTE", "TK_EXECUTE", ALWAYS }, { "EXCLUDE", "TK_EXCLUDE", ALWAYS }, + { "EXTENDED", "TK_EXTENDED", ALWAYS }, #endif { "EXISTS", "TK_EXISTS", ALWAYS }, { "EXPLAIN", "TK_EXPLAIN", EXPLAIN }, @@ -267,6 +269,7 @@ static Keyword aKeywordTable[] = { { "FAIL", "TK_FAIL", CONFLICT|TRIGGER }, #endif #ifdef MAXSCALE + { "FIELDS", "TK_FIELDS", ALWAYS }, { "FIRST", "TK_FIRST", ALWAYS }, { "FLUSH", "TK_FLUSH", ALWAYS }, { "FOLLOWING", "TK_FOLLOWING", ALWAYS }, @@ -376,6 +379,7 @@ static Keyword aKeywordTable[] = { #endif #ifdef MAXSCALE { "PARTITION", "TK_PARTITION", ALWAYS }, + { "PARTITIONS", "TK_PARTITIONS", ALWAYS }, { "PERSISTENT", "TK_PERSISTENT", ALWAYS }, #endif #ifndef MAXSCALE diff --git a/query_classifier/test/compare.cc b/query_classifier/test/compare.cc index d2f54f998..27fddba63 100644 --- a/query_classifier/test/compare.cc +++ b/query_classifier/test/compare.cc @@ -37,11 +37,15 @@ using std::ostream; using std::string; using std::stringstream; +#if !defined(MYSQL_VERSION_MAJOR) +#define USING_MARIADB_103 +#else #if MYSQL_VERSION_MAJOR == 10 && MYSQL_VERSION_MINOR == 3 #define USING_MARIADB_103 #else #undef USING_MARIADB_103 #endif +#endif namespace { diff --git a/query_classifier/test/create.test b/query_classifier/test/create.test index eac0ad06b..4d6d62f2c 100644 --- a/query_classifier/test/create.test +++ b/query_classifier/test/create.test @@ -981,7 +981,8 @@ set names utf8; # MXS: qc_get_type : ERR: QUERY_TYPE_WRITE != QUERY_TYPE_WRITE|QUERY_TYPE_COMMIT # MXS: qc_get_operation : ERR: QUERY_OP_UNDEFINED != QUERY_OP_CREATE # MXS: qc_mysqlembedded sees the type as SQLCOM_END when figuring out the type. -use имя_базы_в_кодировке_утф8_длиной_больше_чем_45; +# MXS: use имя_базы_в_кодировке_утф8_длиной_больше_чем_45; +# MXS: qc_get_database_names : ERR: Ð != имя_базы_в_кодировке_утф8_длиной_больше_чем_45 select database(); use test;