diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index 589687b3c..d4ddabe2c 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -118,6 +118,15 @@ typedef struct qc_field_info uint32_t usage; /** Bitfield denoting where the column appears. */ } QC_FIELD_INFO; +/** + * QC_FUNCTION_INFO contains information about a function used in a statement. + */ +typedef struct qc_function_info +{ + char* name; /** Name of function. */ + uint32_t usage; /** Bitfield denoting where the column appears. */ +} QC_FUNCTION_INFO; + /** * QUERY_CLASSIFIER defines the object a query classifier plugin must * implement and return. @@ -150,6 +159,7 @@ typedef struct query_classifier char* (*qc_get_prepare_name)(GWBUF* stmt); qc_query_op_t (*qc_get_prepare_operation)(GWBUF* stmt); void (*qc_get_field_info)(GWBUF* stmt, const QC_FIELD_INFO** infos, size_t* n_infos); + void (*qc_get_function_info)(GWBUF* stmt, const QC_FUNCTION_INFO** infos, size_t* n_infos); } QUERY_CLASSIFIER; /** @@ -297,6 +307,21 @@ char* qc_field_usage_mask_to_string(uint32_t usage_mask); */ void qc_get_field_info(GWBUF* stmt, const QC_FIELD_INFO** infos, size_t* n_infos); +/** + * Returns information about function usage. + * + * @param stmt A buffer containing a COM_QUERY packet. + * @param infos Pointer to pointer that after the call will point to an + * array of QC_FUNCTION_INFO:s. + * @param n_infos Pointer to size_t variable where the number of items + * in @c infos will be returned. + * + * @note The returned array belongs to the GWBUF and remains valid for as + * long as the GWBUF is valid. If the data is needed for longer than + * that, it must be copied. + */ +void qc_get_function_info(GWBUF* stmt, const QC_FUNCTION_INFO** infos, size_t* n_infos); + /** * Returns the statement, with literals replaced with question marks. * diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index cc90cb5cb..5f3d4c071 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -2432,6 +2432,19 @@ void qc_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, size_t* n_infos) *n_infos = pi->field_infos_len; } +void qc_get_function_info(GWBUF* buf, const QC_FUNCTION_INFO** infos, size_t* n_infos) +{ + *infos = NULL; + *n_infos = 0; + + if (!ensure_query_is_parsed(buf)) + { + return; + } + + // TODO: Implement functionality. +} + namespace { @@ -2584,6 +2597,7 @@ MXS_MODULE* MXS_CREATE_MODULE() qc_get_prepare_name, qc_get_prepare_operation, qc_get_field_info, + qc_get_function_info }; static MXS_MODULE info = diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index e63d8078f..ef98945f3 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -3181,6 +3181,36 @@ void qc_sqlite_get_field_info(GWBUF* query, const QC_FIELD_INFO** infos, size_t* } } +void qc_sqlite_get_function_info(GWBUF* query, const QC_FUNCTION_INFO** infos, size_t* n_infos) +{ + QC_TRACE(); + ss_dassert(this_unit.initialized); + ss_dassert(this_thread.initialized); + + *infos = NULL; + *n_infos = 0; + + QC_SQLITE_INFO* info = get_query_info(query); + + if (info) + { + if (qc_info_is_valid(info->status)) + { + // TODO: Implement functionality. + *infos = NULL; + *n_infos = 0; + } + 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."); + } +} + /** * EXPORTS */ @@ -3207,6 +3237,7 @@ MXS_MODULE* MXS_CREATE_MODULE() qc_sqlite_get_prepare_name, qc_sqlite_get_prepare_operation, qc_sqlite_get_field_info, + qc_sqlite_get_function_info, }; static MXS_MODULE info = diff --git a/query_classifier/test/compare.cc b/query_classifier/test/compare.cc index 40e558700..97dcbee43 100644 --- a/query_classifier/test/compare.cc +++ b/query_classifier/test/compare.cc @@ -1060,6 +1060,132 @@ bool compare_get_field_info(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, } +class QcFunctionInfo +{ +public: + QcFunctionInfo(const QC_FUNCTION_INFO& info) + : m_name(info.name) + , m_usage(info.usage) + {} + + bool eq(const QcFunctionInfo& rhs) const + { + return + m_name == rhs.m_name && + m_usage == rhs.m_usage; + } + + bool lt(const QcFunctionInfo& rhs) const + { + bool rv = false; + + if (m_name < rhs.m_name) + { + rv = true; + } + else if (m_name > rhs.m_name) + { + rv = false; + } + else + { + rv = (m_usage < rhs.m_usage); + } + + return rv; + } + + void print(ostream& out) const + { + out << m_name; + + out << "("; + char* s = qc_field_usage_mask_to_string(m_usage); + out << s; + free(s); + out << ")"; + } + +private: + std::string m_name; + uint32_t m_usage; +}; + +ostream& operator << (ostream& out, const QcFunctionInfo& x) +{ + x.print(out); + return out; +} + +ostream& operator << (ostream& out, std::set& x) +{ + std::set::iterator i = x.begin(); + std::set::iterator end = x.end(); + + while (i != end) + { + out << *i++; + if (i != end) + { + out << " "; + } + } + + return out; +} + +bool operator < (const QcFunctionInfo& lhs, const QcFunctionInfo& rhs) +{ + return lhs.lt(rhs); +} + +bool operator == (const QcFunctionInfo& lhs, const QcFunctionInfo& rhs) +{ + return lhs.eq(rhs); +} + +bool compare_get_function_info(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, + QUERY_CLASSIFIER* pClassifier2, GWBUF* pCopy2) +{ + bool success = false; + const char HEADING[] = "qc_get_function_info : "; + + const QC_FUNCTION_INFO* infos1; + const QC_FUNCTION_INFO* infos2; + size_t n_infos1; + size_t n_infos2; + + pClassifier1->qc_get_function_info(pCopy1, &infos1, &n_infos1); + pClassifier2->qc_get_function_info(pCopy2, &infos2, &n_infos2); + + stringstream ss; + ss << HEADING; + + int i; + + std::set f1; + f1.insert(infos1, infos1 + n_infos1); + + std::set f2; + f2.insert(infos2, infos2 + n_infos2); + + if (f1 == f2) + { + ss << "Ok : "; + ss << f1; + success = true; + } + else + { + ss << "ERR: " << f1 << " != " << f2; + } + + report(success, ss.str()); + + return success; +} + + bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, const string& s) { GWBUF* pCopy1 = create_gwbuf(s); @@ -1080,6 +1206,7 @@ bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, con 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); + errors += !compare_get_function_info(pClassifier1, pCopy1, pClassifier2, pCopy2); gwbuf_free(pCopy1); gwbuf_free(pCopy2); diff --git a/server/core/query_classifier.c b/server/core/query_classifier.c index 4a0e2ecb8..1da2de2e9 100644 --- a/server/core/query_classifier.c +++ b/server/core/query_classifier.c @@ -217,6 +217,14 @@ void qc_get_field_info(GWBUF* query, const QC_FIELD_INFO** infos, size_t* n_info classifier->qc_get_field_info(query, infos, n_infos); } +void qc_get_function_info(GWBUF* query, const QC_FUNCTION_INFO** infos, size_t* n_infos) +{ + QC_TRACE(); + ss_dassert(classifier); + + classifier->qc_get_function_info(query, infos, n_infos); +} + char** qc_get_database_names(GWBUF* query, int* sizep) { QC_TRACE();