From fa5a85858289228fe5cb025f00ff230329188adf Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 3 Nov 2016 13:38:28 +0200 Subject: [PATCH] MXS-884: Implement qc_get_fields_infos. We now collect more information about a particular field and then, if necessary, copy the data over into affected_fields if someone is interested in that. The comparison program is extended as well, but qc_get_fields_infos() is not tested, because qc_mysqlembedded does not implement this yet. --- query_classifier/qc_sqlite/qc_sqlite.c | 463 +++++++++++++++++-------- query_classifier/test/compare.cc | 118 +++++++ 2 files changed, 443 insertions(+), 138 deletions(-) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index d73d06bea..e7d50bab1 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -61,8 +61,6 @@ typedef struct qc_sqlite_info uint32_t types; // The types of the query. qc_query_op_t operation; // The operation in question. char* affected_fields; // The affected fields. - size_t affected_fields_len; // The used length of affected_fields. - size_t affected_fields_capacity; // The capacity of affected_fields. bool is_real_query; // SELECT, UPDATE, INSERT, DELETE or a variation. bool has_clause; // Has WHERE or HAVING. char** table_names; // Array of table names used in the query. @@ -82,6 +80,9 @@ typedef struct qc_sqlite_info qc_query_op_t prepare_operation; // The operation of a prepared statement. char* preparable_stmt; // The preparable statement. size_t preparable_stmt_length; // The length of the preparable statement. + QC_FIELD_INFO *field_infos; // Pointer to array of QC_FIELD_INFOs. + size_t field_infos_len; // The used entries in field_infos. + size_t field_infos_capacity; // The capacity of the field_infos array. } QC_SQLITE_INFO; typedef enum qc_log_level @@ -123,11 +124,11 @@ typedef enum qc_token_position QC_TOKEN_RIGHT, // To the right, e.g: "b" in "a = b". } qc_token_position_t; -static void append_affected_field(QC_SQLITE_INFO* info, const char* s); static void buffer_object_free(void* data); static char** copy_string_array(char** strings, int* pn); static void enlarge_string_array(size_t n, size_t len, char*** ppzStrings, size_t* pCapacity); static bool ensure_query_is_parsed(GWBUF* query); +static void free_field_infos(QC_FIELD_INFO* infos, size_t n_infos); static void free_string_array(char** sa); static QC_SQLITE_INFO* get_query_info(GWBUF* query); static QC_SQLITE_INFO* info_alloc(void); @@ -140,17 +141,17 @@ static bool parse_query(GWBUF* query); static void parse_query_string(const char* query, size_t len); static bool query_is_parsed(GWBUF* query); static bool should_exclude(const char* zName, const ExprList* pExclude); -static void update_affected_fields(QC_SQLITE_INFO* info, - int prev_token, - const Expr* pExpr, - qc_token_position_t pos, - const ExprList* pExclude); -static void update_affected_fields_from_exprlist(QC_SQLITE_INFO* info, - const ExprList* pEList, const ExprList* pExclude); -static void update_affected_fields_from_idlist(QC_SQLITE_INFO* info, - const IdList* pIds, const ExprList* pExclude); -static void update_affected_fields_from_select(QC_SQLITE_INFO* info, - const Select* pSelect, const ExprList* pExclude); +static void update_fields_infos(QC_SQLITE_INFO* info, + int prev_token, + const Expr* pExpr, + qc_token_position_t pos, + const ExprList* pExclude); +static void update_fields_infos_from_exprlist(QC_SQLITE_INFO* info, + const ExprList* pEList, const ExprList* pExclude); +static void update_fields_infos_from_idlist(QC_SQLITE_INFO* info, + const IdList* pIds, const ExprList* pExclude); +static void update_fields_infos_from_select(QC_SQLITE_INFO* info, + const Select* pSelect, const ExprList* pExclude); static void update_database_names(QC_SQLITE_INFO* info, const char* name); static void update_names(QC_SQLITE_INFO* info, const char* zDatabase, const char* zTable); static void update_names_from_srclist(QC_SQLITE_INFO* info, const SrcList* pSrc); @@ -248,7 +249,7 @@ static bool ensure_query_is_parsed(GWBUF* query) return parsed; } -void free_field_infos(QC_FIELD_INFO* infos, size_t n_infos) +static void free_field_infos(QC_FIELD_INFO* infos, size_t n_infos) { if (infos) { @@ -311,6 +312,7 @@ static void info_finish(QC_SQLITE_INFO* info) free_string_array(info->database_names); free(info->prepare_name); free(info->preparable_stmt); + free_field_infos(info->field_infos, info->field_infos_len); } static void info_free(QC_SQLITE_INFO* info) @@ -331,8 +333,6 @@ static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info) info->types = QUERY_TYPE_UNKNOWN; info->operation = QUERY_OP_UNDEFINED; info->affected_fields = NULL; - info->affected_fields_len = 0; - info->affected_fields_capacity = 0; info->is_real_query = false; info->has_clause = false; info->table_names = NULL; @@ -352,6 +352,9 @@ static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info) info->prepare_operation = QUERY_OP_UNDEFINED; info->preparable_stmt = NULL; info->preparable_stmt_length = 0; + info->field_infos = NULL; + info->field_infos_len = 0; + info->field_infos_capacity = 0; return info; } @@ -643,42 +646,6 @@ static void log_invalid_data(GWBUF* query, const char* message) } } -static void append_affected_field(QC_SQLITE_INFO* info, const char* s) -{ - size_t len = strlen(s); - size_t required_len = info->affected_fields_len + len + 1; // 1 for NULL - - if (info->affected_fields_len != 0) - { - required_len += 1; // " " between fields - } - - if (required_len > info->affected_fields_capacity) - { - if (info->affected_fields_capacity == 0) - { - info->affected_fields_capacity = 32; - } - - while (required_len > info->affected_fields_capacity) - { - info->affected_fields_capacity *= 2; - } - - info->affected_fields = MXS_REALLOC(info->affected_fields, info->affected_fields_capacity); - MXS_ABORT_IF_NULL(info->affected_fields); - } - - if (info->affected_fields_len != 0) - { - strcpy(info->affected_fields + info->affected_fields_len, " "); - info->affected_fields_len += 1; - } - - strcpy(info->affected_fields + info->affected_fields_len, s); - info->affected_fields_len += len; -} - static bool should_exclude(const char* zName, const ExprList* pExclude) { int i; @@ -714,36 +681,181 @@ static bool should_exclude(const char* zName, const ExprList* pExclude) return i != pExclude->nExpr; } -static void update_affected_fields(QC_SQLITE_INFO* info, - int prev_token, - const Expr* pExpr, - qc_token_position_t pos, - const ExprList* pExclude) +static void update_field_infos(QC_SQLITE_INFO* info, + const char* database, + const char* table, + const char* column, + const ExprList* pExclude) +{ + ss_dassert(column); + + // 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 (column && !table && !database && pExclude && should_exclude(column, pExclude)) + { + return; + } + + QC_FIELD_INFO item = { (char*)database, (char*)table, (char*)column }; + + int i; + for (i = 0; i < info->field_infos_len; ++i) + { + QC_FIELD_INFO* field_info = info->field_infos + i; + + if (strcasecmp(item.column, field_info->column) == 0) + { + if (!item.table && !field_info->table) + { + ss_dassert(!item.database && !field_info->database); + break; + } + else if (item.table && field_info->table && (strcmp(item.table, field_info->table) == 0)) + { + if (!item.database && !field_info->database) + { + break; + } + else if (item.database && + field_info->database && + (strcmp(item.database, field_info->database) == 0)) + { + break; + } + } + } + } + + QC_FIELD_INFO* field_infos = NULL; + + if (i == info->field_infos_len) // If true, the field was not present already. + { + if (info->field_infos_len < info->field_infos_capacity) + { + field_infos = info->field_infos; + } + else + { + size_t capacity = info->field_infos_capacity ? 2 * info->field_infos_capacity : 8; + field_infos = MXS_REALLOC(info->field_infos, capacity * sizeof(QC_FIELD_INFO)); + + if (field_infos) + { + info->field_infos = field_infos; + info->field_infos_capacity = capacity; + } + } + } + + // If field_infos is NULL, then the field was found and has already been noted. + if (field_infos) + { + item.database = item.database ? MXS_STRDUP(item.database) : NULL; + item.table = item.table ? MXS_STRDUP(item.table) : NULL; + ss_dassert(item.column); + item.column = MXS_STRDUP(item.column); + + // We are happy if we at least could dup the column. + + if (item.column) + { + field_infos[info->field_infos_len++] = item; + } + } +} + +static void update_field_infos_from_expr(QC_SQLITE_INFO* info, + const struct Expr* pExpr, + const ExprList* pExclude) +{ + QC_FIELD_INFO item = {}; + + if (pExpr->op == TK_ASTERISK) + { + item.column = "*"; + } + 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 = "*"; + } + } + 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 = "*"; + } + } + } + + 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_infos(info, item.database, item.table, item.column, pExclude); + } + } +} + + +static void update_fields_infos(QC_SQLITE_INFO* info, + int prev_token, + const Expr* pExpr, + qc_token_position_t pos, + const ExprList* pExclude) { const char* zToken = pExpr->u.zToken; switch (pExpr->op) { - case TK_ASTERISK: // "select *" - append_affected_field(info, "*"); + case TK_ASTERISK: // select * + update_field_infos_from_expr(info, pExpr, pExclude); break; - case TK_DOT: - // In case of "X.Y" qc_mysqlembedded returns "Y". - update_affected_fields(info, TK_DOT, pExpr->pRight, QC_TOKEN_RIGHT, pExclude); + case TK_DOT: // select a.b ... select a.b.c + update_field_infos_from_expr(info, pExpr, pExclude); break; - case TK_ID: - if ((pExpr->flags & EP_DblQuoted) == 0) - { - if ((strcasecmp(zToken, "true") != 0) && (strcasecmp(zToken, "false") != 0)) - { - if (!pExclude || !should_exclude(zToken, pExclude)) - { - append_affected_field(info, zToken); - } - } - } + case TK_ID: // select a + update_field_infos_from_expr(info, pExpr, pExclude); break; case TK_VARIABLE: @@ -804,12 +916,12 @@ static void update_affected_fields(QC_SQLITE_INFO* info, if (pExpr->pLeft) { - update_affected_fields(info, pExpr->op, pExpr->pLeft, QC_TOKEN_LEFT, pExclude); + update_fields_infos(info, pExpr->op, pExpr->pLeft, QC_TOKEN_LEFT, pExclude); } if (pExpr->pRight) { - update_affected_fields(info, pExpr->op, pExpr->pRight, QC_TOKEN_RIGHT, pExclude); + update_fields_infos(info, pExpr->op, pExpr->pRight, QC_TOKEN_RIGHT, pExclude); } if (pExpr->x.pList) @@ -819,7 +931,7 @@ static void update_affected_fields(QC_SQLITE_INFO* info, case TK_BETWEEN: case TK_CASE: case TK_FUNCTION: - update_affected_fields_from_exprlist(info, pExpr->x.pList, pExclude); + update_fields_infos_from_exprlist(info, pExpr->x.pList, pExclude); break; case TK_EXISTS: @@ -827,11 +939,11 @@ static void update_affected_fields(QC_SQLITE_INFO* info, case TK_SELECT: if (pExpr->flags & EP_xIsSelect) { - update_affected_fields_from_select(info, pExpr->x.pSelect, pExclude); + update_fields_infos_from_select(info, pExpr->x.pSelect, pExclude); } else { - update_affected_fields_from_exprlist(info, pExpr->x.pList, pExclude); + update_fields_infos_from_exprlist(info, pExpr->x.pList, pExclude); } break; } @@ -840,19 +952,19 @@ static void update_affected_fields(QC_SQLITE_INFO* info, } } -static void update_affected_fields_from_exprlist(QC_SQLITE_INFO* info, - const ExprList* pEList, - const ExprList* pExclude) +static void update_fields_infos_from_exprlist(QC_SQLITE_INFO* info, + const ExprList* pEList, + const ExprList* pExclude) { for (int i = 0; i < pEList->nExpr; ++i) { struct ExprList_item* pItem = &pEList->a[i]; - update_affected_fields(info, 0, pItem->pExpr, QC_TOKEN_MIDDLE, pExclude); + update_fields_infos(info, 0, pItem->pExpr, QC_TOKEN_MIDDLE, pExclude); } } -static void update_affected_fields_from_idlist(QC_SQLITE_INFO* info, +static void update_fields_infos_from_idlist(QC_SQLITE_INFO* info, const IdList* pIds, const ExprList* pExclude) { @@ -860,14 +972,11 @@ static void update_affected_fields_from_idlist(QC_SQLITE_INFO* info, { struct IdList_item* pItem = &pIds->a[i]; - if (!pExclude || !should_exclude(pItem->zName, pExclude)) - { - append_affected_field(info, pItem->zName); - } + update_field_infos(info, NULL, NULL, pItem->zName, pExclude); } } -static void update_affected_fields_from_select(QC_SQLITE_INFO* info, +static void update_fields_infos_from_select(QC_SQLITE_INFO* info, const Select* pSelect, const ExprList* pExclude) { @@ -885,7 +994,7 @@ static void update_affected_fields_from_select(QC_SQLITE_INFO* info, if (pSrc->a[i].pSelect) { - update_affected_fields_from_select(info, pSrc->a[i].pSelect, pExclude); + update_fields_infos_from_select(info, pSrc->a[i].pSelect, pExclude); } #ifdef QC_COLLECT_NAMES_FROM_USING @@ -895,7 +1004,7 @@ static void update_affected_fields_from_select(QC_SQLITE_INFO* info, // does not reveal its value, right? if (pSrc->a[i].pUsing) { - update_affected_fields_from_idlist(info, pSrc->a[i].pUsing, pSelect->pEList); + update_fields_infos_from_idlist(info, pSrc->a[i].pUsing, pSelect->pEList); } #endif } @@ -903,24 +1012,24 @@ static void update_affected_fields_from_select(QC_SQLITE_INFO* info, if (pSelect->pEList) { - update_affected_fields_from_exprlist(info, pSelect->pEList, NULL); + update_fields_infos_from_exprlist(info, pSelect->pEList, NULL); } if (pSelect->pWhere) { info->has_clause = true; - update_affected_fields(info, 0, pSelect->pWhere, QC_TOKEN_MIDDLE, pSelect->pEList); + update_fields_infos(info, 0, pSelect->pWhere, QC_TOKEN_MIDDLE, pSelect->pEList); } if (pSelect->pGroupBy) { - update_affected_fields_from_exprlist(info, pSelect->pGroupBy, pSelect->pEList); + update_fields_infos_from_exprlist(info, pSelect->pGroupBy, pSelect->pEList); } if (pSelect->pHaving) { info->has_clause = true; - update_affected_fields(info, 0, pSelect->pHaving, QC_TOKEN_MIDDLE, pSelect->pEList); + update_fields_infos(info, 0, pSelect->pHaving, QC_TOKEN_MIDDLE, pSelect->pEList); } } @@ -1165,7 +1274,7 @@ void mxs_sqlite3CreateView(Parse *pParse, /* The parsing context */ if (pSelect) { - update_affected_fields_from_select(info, pSelect, NULL); + update_fields_infos_from_select(info, pSelect, NULL); info->is_real_query = false; } @@ -1235,7 +1344,7 @@ void mxs_sqlite3DeleteFrom(Parse* pParse, SrcList* pTabList, Expr* pWhere, SrcLi if (pWhere) { - update_affected_fields(info, 0, pWhere, QC_TOKEN_MIDDLE, 0); + update_fields_infos(info, 0, pWhere, QC_TOKEN_MIDDLE, 0); } exposed_sqlite3ExprDelete(pParse->db, pWhere); @@ -1299,7 +1408,7 @@ void mxs_sqlite3EndTable(Parse *pParse, /* Parse context */ { if (pSelect) { - update_affected_fields_from_select(info, pSelect, NULL); + update_fields_infos_from_select(info, pSelect, NULL); info->is_real_query = false; } else if (pOldTable) @@ -1345,17 +1454,17 @@ void mxs_sqlite3Insert(Parse* pParse, if (pColumns) { - update_affected_fields_from_idlist(info, pColumns, NULL); + update_fields_infos_from_idlist(info, pColumns, NULL); } if (pSelect) { - update_affected_fields_from_select(info, pSelect, NULL); + update_fields_infos_from_select(info, pSelect, NULL); } if (pSet) { - update_affected_fields_from_exprlist(info, pSet, NULL); + update_fields_infos_from_exprlist(info, pSet, NULL); } exposed_sqlite3SrcListDelete(pParse->db, pTabList); @@ -1479,18 +1588,13 @@ void mxs_sqlite3Update(Parse* pParse, SrcList* pTabList, ExprList* pChanges, Exp { struct ExprList_item* pItem = &pChanges->a[i]; - if (pItem->zName) - { - append_affected_field(info, pItem->zName); - } - - update_affected_fields(info, 0, pItem->pExpr, QC_TOKEN_MIDDLE, NULL); + update_fields_infos(info, 0, pItem->pExpr, QC_TOKEN_MIDDLE, NULL); } } if (pWhere) { - update_affected_fields(info, 0, pWhere, QC_TOKEN_MIDDLE, NULL); + update_fields_infos(info, 0, pWhere, QC_TOKEN_MIDDLE, NULL); } exposed_sqlite3SrcListDelete(pParse->db, pTabList); @@ -1516,7 +1620,7 @@ void maxscaleCollectInfoFromSelect(Parse* pParse, Select* pSelect) info->types = QUERY_TYPE_READ; } - update_affected_fields_from_select(info, pSelect, NULL); + update_fields_infos_from_select(info, pSelect, NULL); } void maxscaleAlterTable(Parse *pParse, /* Parser context. */ @@ -1668,9 +1772,12 @@ void maxscaleExplain(Parse* pParse, SrcList* pName) info->status = QC_QUERY_PARSED; info->types = QUERY_TYPE_READ; update_names(info, "information_schema", "COLUMNS"); - append_affected_field(info, - "COLUMN_DEFAULT COLUMN_KEY COLUMN_NAME " - "COLUMN_TYPE EXTRA IS_NULLABLE"); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_KEY", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_NAME", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_TYPE", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "EXTRA", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "IS_NULLABLE", NULL); exposed_sqlite3SrcListDelete(pParse->db, pName); } @@ -2190,7 +2297,7 @@ void maxscaleSet(Parse* pParse, int scope, mxs_set_t kind, ExprList* pList) if (pValue->op == TK_SELECT) { - update_affected_fields_from_select(info, pValue->x.pSelect, NULL); + update_fields_infos_from_select(info, pValue->x.pSelect, NULL); info->is_real_query = false; // TODO: This is what qc_mysqlembedded claims. } } @@ -2246,16 +2353,24 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) update_names(info, "information_schema", "COLUMNS"); if (pShow->data == MXS_SHOW_COLUMNS_FULL) { - append_affected_field(info, - "COLLATION_NAME COLUMN_COMMENT COLUMN_DEFAULT " - "COLUMN_KEY COLUMN_NAME COLUMN_TYPE EXTRA " - "IS_NULLABLE PRIVILEGES"); + update_field_infos(info, "information_schema", "COLUMNS", "COLLATION_NAME", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_COMMENT", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_KEY", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_NAME", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_TYPE", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "EXTRA", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "IS_NULLABLE", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "PRIVILEGES", NULL); } else { - append_affected_field(info, - "COLUMN_DEFAULT COLUMN_KEY COLUMN_NAME " - "COLUMN_TYPE EXTRA IS_NULLABLE"); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_KEY", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_NAME", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "COLUMN_TYPE", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "EXTRA", NULL); + update_field_infos(info, "information_schema", "COLUMNS", "IS_NULLABLE", NULL); } } break; @@ -2278,7 +2393,7 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) { info->types = QUERY_TYPE_SHOW_DATABASES; update_names(info, "information_schema", "SCHEMATA"); - append_affected_field(info, "SCHEMA_NAME"); + update_field_infos(info, "information_schema", "SCHEMATA", "SCHEMA_NAME", NULL); } break; @@ -2288,10 +2403,19 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) { info->types = QUERY_TYPE_WRITE; update_names(info, "information_schema", "STATISTICS"); - append_affected_field(info, - "CARDINALITY COLLATION COLUMN_NAME COMMENT INDEX_COMMENT " - "INDEX_NAME INDEX_TYPE NON_UNIQUE NULLABLE PACKED SEQ_IN_INDEX " - "SUB_PART TABLE_NAME"); + update_field_infos(info, "information_schema", "STATISTICS", "CARDINALITY", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "COLLATION", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "COLUMN_NAME", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "COMMENT", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "INDEX_COMMENT", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "INDEX_NAME", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "INDEX_TYPE", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "NON_UNIQUE", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "NULLABLE", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "PACKED", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "SEQ_IN_INDEX", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "SUB_PART", NULL); + update_field_infos(info, "information_schema", "STATISTICS", "TABLE_NAME", NULL); } break; @@ -2299,12 +2423,24 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) { info->types = QUERY_TYPE_WRITE; update_names(info, "information_schema", "TABLES"); - append_affected_field(info, - "AUTO_INCREMENT AVG_ROW_LENGTH CHECKSUM CHECK_TIME " - "CREATE_OPTIONS CREATE_TIME DATA_FREE DATA_LENGTH " - "ENGINE INDEX_LENGTH MAX_DATA_LENGTH ROW_FORMAT " - "TABLE_COLLATION TABLE_COMMENT TABLE_NAME " - "TABLE_ROWS UPDATE_TIME VERSION"); + update_field_infos(info, "information_schema", "TABLES", "AUTO_INCREMENT", NULL); + update_field_infos(info, "information_schema", "TABLES", "AVG_ROW_LENGTH", NULL); + update_field_infos(info, "information_schema", "TABLES", "CHECKSUM", NULL); + update_field_infos(info, "information_schema", "TABLES", "CHECK_TIME", NULL); + update_field_infos(info, "information_schema", "TABLES", "CREATE_OPTIONS", NULL); + update_field_infos(info, "information_schema", "TABLES", "CREATE_TIME", NULL); + update_field_infos(info, "information_schema", "TABLES", "DATA_FREE", NULL); + update_field_infos(info, "information_schema", "TABLES", "DATA_LENGTH", NULL); + update_field_infos(info, "information_schema", "TABLES", "ENGINE", NULL); + update_field_infos(info, "information_schema", "TABLES", "INDEX_LENGTH", NULL); + update_field_infos(info, "information_schema", "TABLES", "MAX_DATA_LENGTH", NULL); + update_field_infos(info, "information_schema", "TABLES", "ROW_FORMAT", NULL); + update_field_infos(info, "information_schema", "TABLES", "TABLE_COLLATION", NULL); + update_field_infos(info, "information_schema", "TABLES", "TABLE_COMMENT", NULL); + update_field_infos(info, "information_schema", "TABLES", "TABLE_NAME", NULL); + update_field_infos(info, "information_schema", "TABLES", "TABLE_ROWS", NULL); + update_field_infos(info, "information_schema", "TABLES", "UPDATE_TIME", NULL); + update_field_infos(info, "information_schema", "TABLES", "VERSION", NULL); } break; @@ -2318,7 +2454,8 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) // TODO: qc_mysqlembedded does not set the type bit. info->types = QUERY_TYPE_UNKNOWN; update_names(info, "information_schema", "SESSION_STATUS"); - append_affected_field(info, "VARIABLE_NAME VARIABLE_VALUE"); + update_field_infos(info, "information_schema", "SESSION_STATUS", "VARIABLE_NAME", NULL); + update_field_infos(info, "information_schema", "SESSION_STATUS", "VARIABLE_VALUE", NULL); break; case MXS_SHOW_STATUS_MASTER: @@ -2343,7 +2480,7 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) { info->types = QUERY_TYPE_SHOW_TABLES; update_names(info, "information_schema", "TABLE_NAMES"); - append_affected_field(info, "TABLE_NAME"); + update_field_infos(info, "information_schema", "TABLE_NAMES", "TABLE_NAME", NULL); } break; @@ -2358,7 +2495,8 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) info->types = QUERY_TYPE_SYSVAR_READ; } update_names(info, "information_schema", "SESSION_VARIABLES"); - append_affected_field(info, "VARIABLE_NAME VARIABLE_VALUE"); + update_field_infos(info, "information_schema", "SESSION_STATUS", "VARIABLE_NAME", NULL); + update_field_infos(info, "information_schema", "SESSION_STATUS", "VARIABLE_VALUE", NULL); } break; @@ -2856,7 +2994,39 @@ static char* qc_sqlite_get_affected_fields(GWBUF* query) { if (qc_info_is_valid(info->status)) { - affected_fields = info->affected_fields; + if (!info->affected_fields) + { + if (info->field_infos_len != 0) + { + // The first time qc_sqlite_get_affected_fields() is called + // we copy the column data from info->fields_infos into + // info->affected_fields. + QC_FIELD_INFO* fis = info->field_infos; + size_t fis_len = info->field_infos_len; + size_t buflen = 0; + + for (size_t i = 0; i < fis_len; ++i) + { + buflen += strlen(fis[i].column); + buflen += 1; + } + + buflen += 1; + + affected_fields = MXS_MALLOC(buflen); + MXS_ABORT_IF_NULL(affected_fields); + + affected_fields[0] = 0; + + for (size_t i = 0; i < fis_len; ++i) + { + strcat(affected_fields, fis[i].column); + strcat(affected_fields, " "); + } + + info->affected_fields = affected_fields; + } + } } else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { @@ -2969,16 +3139,33 @@ static qc_query_op_t qc_sqlite_get_prepare_operation(GWBUF* query) return op; } -void qc_sqlite_get_field_info(GWBUF* stmt, const QC_FIELD_INFO** infos, size_t* n_infos) +void qc_sqlite_get_field_info(GWBUF* query, const QC_FIELD_INFO** infos, size_t* n_infos) { QC_TRACE(); ss_dassert(this_unit.initialized); ss_dassert(this_thread.initialized); - MXS_ERROR("qc_get_field_info not implemented yet."); - *infos = NULL; *n_infos = 0; + + QC_SQLITE_INFO* info = get_query_info(query); + + if (info) + { + if (qc_info_is_valid(info->status)) + { + *infos = info->field_infos; + *n_infos = info->field_infos_len; + } + else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) + { + log_invalid_data(query, "cannot report field info"); + } + } + else + { + MXS_ERROR("The query could not be parsed. Response not valid."); + } } /** diff --git a/query_classifier/test/compare.cc b/query_classifier/test/compare.cc index 99fd1f7ba..3f00439b4 100644 --- a/query_classifier/test/compare.cc +++ b/query_classifier/test/compare.cc @@ -871,6 +871,123 @@ bool compare_get_prepare_operation(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1 return success; } +bool operator == (const QC_FIELD_INFO& lhs, const QC_FIELD_INFO& rhs) +{ + bool rv = false; + if (lhs.column && rhs.column && (strcasecmp(lhs.column, rhs.column) == 0)) + { + if (!lhs.table && !rhs.table) + { + rv = true; + } + else if (lhs.table && rhs.table && (strcmp(lhs.table, rhs.table) == 0)) + { + if (!lhs.database && !rhs.database) + { + rv = true; + } + else if (lhs.database && rhs.database && (strcmp(lhs.database, rhs.database) == 0)) + { + rv = true; + } + } + } + + return rv; +} + +ostream& operator << (ostream& out, const QC_FIELD_INFO& x) +{ + if (x.database) + { + out << x.database; + out << "."; + ss_dassert(x.table); + } + + if (x.table) + { + out << x.table; + out << "."; + } + + ss_dassert(x.column); + out << x.column; + + return out; +} + +bool are_equal(const QC_FIELD_INFO* fields1, size_t n_fields1, + const QC_FIELD_INFO* fields2, size_t n_fields2) +{ + bool rv = (n_fields1 == n_fields2); + + if (rv) + { + size_t i = 0; + while (rv && (i < n_fields1)) + { + rv = *fields1 == *fields2; + ++i; + } + } + + return rv; +} + +ostream& print(ostream& out, const QC_FIELD_INFO* fields, size_t n_fields) +{ + size_t i = 0; + while (i < n_fields) + { + out << fields[i++]; + + if (i != n_fields) + { + out << " "; + } + } + + return out; +} + +bool compare_get_field_info(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, + QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) +{ + bool success = false; + const char HEADING[] = "qc_get_field_info : "; + + const QC_FIELD_INFO* infos1; + const QC_FIELD_INFO* infos2; + size_t n_infos1; + size_t n_infos2; + + pClassifier1->qc_get_field_info(pCopy1, &infos1, &n_infos1); + pClassifier2->qc_get_field_info(pCopy2, &infos2, &n_infos2); + + stringstream ss; + ss << HEADING; + + if (are_equal(infos1, n_infos1, infos2, n_infos2)) + { + ss << "Ok : "; + print(ss, infos1, n_infos1); + success = true; + } + else + { + ss << "ERR: "; + print(ss, infos1, n_infos1); + ss << " != "; + print(ss, infos2, n_infos2); + } + + report(success, ss.str()); + + return success; +} + + bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, const string& s) { GWBUF* pCopy1 = create_gwbuf(s); @@ -891,6 +1008,7 @@ bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, con errors += !compare_get_database_names(pClassifier1, pCopy1, pClassifier2, pCopy2); errors += !compare_get_prepare_name(pClassifier1, pCopy1, pClassifier2, pCopy2); errors += !compare_get_prepare_operation(pClassifier1, pCopy1, pClassifier2, pCopy2); + //errors += !compare_get_field_info(pClassifier1, pCopy1, pClassifier2, pCopy2); gwbuf_free(pCopy1); gwbuf_free(pCopy2);