xapi/crud.cc (376 lines of code) (raw):

/* * Copyright (c) 2016, 2024, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, as * published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * * Without limiting anything contained in the foregoing, this file, * which is part of Connector/C++, is also subject to the * Universal FOSS Exception, version 1.0, a copy of which can be found at * https://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <mysqlx/xapi.h> #include "mysqlx_cc_internal.h" #include "../common/op_impl.h" using std::string; using namespace mysqlx::common; Value get_value(int64_t type, va_list &args) { switch (type) { case MYSQLX_TYPE_NULL: return {}; case MYSQLX_TYPE_SINT: return va_arg(args, int64_t); case MYSQLX_TYPE_UINT: return va_arg(args, uint64_t); case MYSQLX_TYPE_FLOAT: case MYSQLX_TYPE_DOUBLE: // With variable parameters a float value is promoted to a double return va_arg(args, double); case MYSQLX_TYPE_BOOL: // With variable parameters a bool value is promoted to int return (bool)(va_arg(args, int) != 0); case MYSQLX_TYPE_STRING: // TODO: utf8 conversion return std::string(va_arg(args, char*)); case MYSQLX_TYPE_BYTES: { cdk::byte *cb = va_arg(args, cdk::byte*); return { cb, va_arg(args, size_t) }; } case MYSQLX_TYPE_EXPR: return Value::Access::mk_expr(va_arg(args, char*)); default: throw_error("Unknown data type in variable argument list."); return Value(); // quiet compile warnings } } Value get_value(va_list &args) { int64_t type = (int64_t)va_arg(args, void*); if (0 == type) throw std::out_of_range("end of variable argument list"); return get_value(type, args); } /* Member function for binding values for parametrized SQL queries. This function should only be called by mysqlx_stmt_bind() PARAMETERS: args - variable list of parameters that follow as (mysqlx_data_type_t)type, value, ..., 0 The list is closed by 0 value for type. NOTE: the list is already initialized in mysqlx_stmt_bind(), so no need to call va_start()/va_end() in here. RETURN: RESULT_OK - on success RESULT_ERROR - on error NOTE: Each new call resets the binds set by the previous call to mysqlx_stmt_t::sql_bind() */ int mysqlx_stmt_struct::sql_bind(va_list &args) { auto *impl = get_impl<OP_SQL>(this); // For variadic parameters mysqlx_data_structype_t is used as value of void* pointer int64_t type = (int64_t)va_arg(args, void*); do { impl->add_param(get_value(type, args)); type = (int64_t)va_arg(args, void*); // Spin in the loop until finding the end of the list or on an error } while (0 != (int)type); return RESULT_OK; } int mysqlx_stmt_struct::sql_bind(cdk::string s) { assert(OP_SQL == m_op_type); assert(m_impl); get_impl<OP_SQL>(this)->add_param(std::string(s)); return RESULT_OK; } /* Member function for binding values for parametrized CRUD queries. This function should only be called by mysqlx_stmt_bind() PARAMETERS: args - variable list of parameters. param_name, (mysqlx_data_structype_t)type, value, ..., PARAM_END The list is closed by PARAM_END value NOTE: the list is already initialized in mysqlx_stmt_bind(), so no need to call va_start()/va_end() in here. RETURN: RESULT_OK - on success RESULT_ERROR - on error NOTE: Each new call resets the binds set by the previous call to mysqlx_stmt_struct::param_bind() */ int mysqlx_stmt_struct::param_bind(va_list &args) { using BImpl = Bind_if; BImpl *impl = get_impl<BImpl>(this); // For variadic parameters mysqlx_data_structype_t is used as value of void* pointer char *param_name_utf8 = 0; while((param_name_utf8 = va_arg(args, char*)) != NULL) { cdk::string param_name(param_name_utf8); impl->add_param(param_name, get_value(args)); } return RESULT_OK; } int mysqlx_stmt_struct::add_columns(va_list &args) { if (m_op_type != OP_INSERT) { m_error.set("Wrong operation type. Only INSERT and ADD are supported.", 0); return RESULT_ERROR; } auto *impl = get_impl<OP_INSERT>(this); // TODO: Error if no columns given? impl->clear_columns(); const char *col_name_utf8 = va_arg(args, char*); while (col_name_utf8) { cdk::string col_name(col_name_utf8); impl->add_column(col_name); col_name_utf8 = va_arg(args, char*); } return RESULT_OK; } /* Member function for adding row values CRUD ADD. PARAMETERS: get_columns - flag indicating if the column information is present inside args list args - variable list of parameters that contains row data, but also can have column names (see get_columns parameter) NOTE: the list is already initialized in the upper level, so no need to call va_start()/va_end() in here. RETURN: RESULT_OK - on success RESULT_ERROR - on error NOTE: Each new call resets the column and row values */ int mysqlx_stmt_struct::add_row(bool get_columns, va_list &args) { if (m_op_type != OP_INSERT) // && m_op_type != OP_ADD) { m_error.set("Wrong operation type. Only INSERT and ADD are supported.", 0); return RESULT_ERROR; } auto *impl = get_impl<OP_INSERT>(this); // For variadic parameters mysqlx_data_structype_t is used as value of void* pointer int64_t type; char *col_name_utf8 = NULL; Row_impl<> row; cdk::col_count_t col = 0; /* Getting column name only if the flag is set. Otherwise do not attempt it to avoid the stack corruption. */ // TODO: Error if no values are given? while((!get_columns || (col_name_utf8 = va_arg(args, char*)) != NULL) && ((type = (int64_t)va_arg(args, void*)) != 0)) { if (get_columns) { cdk::string col_name(col_name_utf8); impl->add_column(col_name); } row.set(col++, get_value(type, args)); } impl->add_row(row); return RESULT_OK; } int mysqlx_stmt_struct::add_projections(va_list &args) { char *item_utf8 = NULL; if (m_op_type != OP_SELECT && m_op_type != OP_FIND) { m_error.set("Wrong operation type. Only SELECT and FIND are supported.", 0); return RESULT_ERROR; } auto *impl = get_impl<Proj_if>(this); // TODO: Error if no projections passed? while ((item_utf8 = (char*)va_arg(args, char*)) != NULL ) { cdk::string item(item_utf8); if (OP_FIND == m_op_type) { // For find we expect single item with document expression. impl->set_proj(item); return RESULT_OK; } impl->add_proj(item); } return RESULT_OK; } int mysqlx_stmt_struct::add_coll_modify_values(va_list &args, mysqlx_modify_op modify_type) { char *path_utf8 = NULL; if (m_op_type != OP_MODIFY) { set_diagnostic("Wrong operation type. Only MODIFY is supported.", 0); return RESULT_ERROR; } using MImpl = Collection_modify_if; MImpl *impl = get_impl<MImpl>(this); MImpl::Operation op = MImpl::SET; switch (modify_type) { case MODIFY_SET: op = MImpl::SET; break; case MODIFY_UNSET: op = MImpl::UNSET; break; case MODIFY_ARRAY_INSERT: op = MImpl::ARRAY_INSERT; break; case MODIFY_ARRAY_APPEND: op = MImpl::ARRAY_APPEND; break; case MODIFY_ARRAY_DELETE: op = MImpl::ARRAY_DELETE; break; case MODIFY_MERGE_PATCH: op = MImpl::MERGE_PATCH; break; } int rc = RESULT_ERROR; while ((path_utf8 = (char*)va_arg(args, char*)) != NULL ) { cdk::string path(path_utf8); rc = RESULT_OK; if (modify_type == MODIFY_UNSET || modify_type == MODIFY_ARRAY_DELETE) { impl->add_operation(op, path); continue; } else if (modify_type == MODIFY_MERGE_PATCH) { /* Note: in this case path contains the patch to be applied, which should be trated as an expression, not a literal string. */ impl->add_operation(op, "$", Value::Access::mk_expr(path)); // For merge only one item is expected return RESULT_OK; } impl->add_operation(op, path, get_value(args)); } if (rc == RESULT_ERROR) set_diagnostic("No modifications specified for MODIFY operation.", 0); return rc; } int mysqlx_stmt_struct::add_table_update_values(va_list &args) { char *column_utf8 = NULL; if (m_op_type != OP_UPDATE) { m_error.set("Wrong operation type. Only UPDATE is supported.", 0); return RESULT_ERROR; } using UImpl = Table_update_if; UImpl *impl = get_impl<UImpl>(this); int rc = RESULT_ERROR; while ((column_utf8 = (char*)va_arg(args, char*)) != NULL ) { cdk::string column(column_utf8); rc = RESULT_OK; impl->add_set(column, get_value(args)); } if (rc == RESULT_ERROR) set_diagnostic("No modifications specified for UPDATE operation.", 0); return rc; } /* Set WHERE for statement operation PARAMETERS: where_expr - character string containing WHERE clause, which will be parsed as required RETURN: RESULT_OK - on success RESULT_ERROR - on error NOTE: each call to this function replaces previously set WHERE */ #define OP_WHERE_LIST(X) \ X(SELECT) X(DELETE) X(UPDATE) X(FIND) X(MODIFY) X(REMOVE) #define OP_CASE(X) case OP_##X: int mysqlx_stmt_struct::set_where(const char *where_expr) { cdk::string expr; // passing NULL or empty string means "no restrictions" if (!where_expr || !*where_expr) return RESULT_OK; expr = where_expr; #define SET_WHERE(X) \ case OP_##X: get_impl<OP_##X>(this)->set_where(expr); break; switch (m_op_type) { OP_WHERE_LIST(SET_WHERE) default: throw Mysqlx_exception(MYSQLX_ERROR_OP_NOT_SUPPORTED); } return RESULT_OK; } template <mysqlx_op_t OP> void set_row_locking_helper( typename stmt_traits<OP>::Impl *impl, mysqlx_row_locking_enum row_locking, mysqlx_lock_contention_t locking_contention ) { assert(impl); if (ROW_LOCK_NONE == row_locking) return impl->clear_lock_mode(); impl->set_lock_mode(Lock_mode(unsigned(row_locking)), Lock_contention(unsigned(locking_contention))); } void mysqlx_stmt_struct::set_row_locking( mysqlx_row_locking_t row_locking, mysqlx_lock_contention_t lock_contention) { switch (m_op_type) { case OP_SELECT: set_row_locking_helper<OP_SELECT>(get_impl<OP_SELECT>(this), row_locking, lock_contention); break; case OP_FIND: set_row_locking_helper<OP_FIND>(get_impl<OP_FIND>(this), row_locking, lock_contention); break; default: throw Mysqlx_exception(MYSQLX_ERROR_OP_NOT_SUPPORTED); } } int mysqlx_stmt_struct::add_group_by(va_list &args) { const char *group_by_utf8; while ((group_by_utf8 = va_arg(args, char*)) != NULL) { switch (m_op_type) { case OP_SELECT: case OP_FIND: break; default: throw Mysqlx_exception(MYSQLX_ERROR_OP_NOT_SUPPORTED); } } using GImpl = Group_by_if; GImpl *impl = get_impl<GImpl>(this); cdk::string group_by(group_by_utf8); impl->add_group_by(group_by); return RESULT_OK; } /* Set HAVING for statement operation PARAMETERS: having_expr - character string containing HAVING clause, which will be parsed as required RETURN: RESULT_OK - on success RESULT_ERROR - on error NOTE: each call to this function replaces previously set HAVING */ int mysqlx_stmt_struct::set_having(const char *having_expr_utf8) { assert(having_expr_utf8); switch (m_op_type) { case OP_SELECT: case OP_FIND: break; default: throw Mysqlx_exception(MYSQLX_ERROR_OP_NOT_SUPPORTED); } if (!having_expr_utf8 || !*having_expr_utf8) throw Mysqlx_exception("Empty having expression"); using HImpl = Having_if; HImpl *impl = get_impl<HImpl>(this); cdk::string having_expr(having_expr_utf8); impl->set_having(having_expr); return RESULT_OK; } /* Set LIMIT for CRUD operation PARAMETERS: row_count - the number of result rows to return offset - the number of rows to skip before starting counting RETURN: RESULT_OK - on success RESULT_ERROR - on error NOTE: each call to this function replaces previously set LIMIT */ int mysqlx_stmt_struct::set_limit(cdk::row_count_t row_count, cdk::row_count_t offset) { switch (m_op_type) { OP_WHERE_LIST(OP_CASE) break; default: throw Mysqlx_exception(MYSQLX_ERROR_OP_NOT_SUPPORTED); } using LImpl = Limit_if; LImpl *impl = get_impl<LImpl>(this); impl->set_limit(row_count); if (offset != 0) impl->set_offset(offset); return RESULT_OK; } /* Set one item in ORDER BY for CRUD operation PARAMETERS: order - the character string expression describing ONE item direction - sort direction RETURN: RESULT_OK - on success RESULT_ERROR - on error NOTE: each call to this function adds a new item to ORDER BY list */ int mysqlx_stmt_struct::add_order_by(va_list &args) { switch (m_op_type) { OP_WHERE_LIST(OP_CASE) break; default: throw Mysqlx_exception(MYSQLX_ERROR_OP_NOT_SUPPORTED); } using SImpl = Sort_if; SImpl *impl = get_impl<SImpl>(this); char *item_utf8 = NULL; mysqlx_sort_direction_enum sort_direction; do { item_utf8 = va_arg(args, char*); if (item_utf8 && *item_utf8) { cdk::string item(item_utf8); // mysqlx_sort_direction_t is promoted to int sort_direction = (mysqlx_sort_direction_enum)va_arg(args, int); impl->add_sort(item, SORT_ORDER_ASC == sort_direction ? SImpl::ASC : SImpl::DESC ); } } while (item_utf8 && *item_utf8); return RESULT_OK; } int mysqlx_stmt_struct::add_document(const char *json_doc) { assert(json_doc && *json_doc); if (m_op_type != OP_ADD) { set_diagnostic("Wrong operation type. Only ADD is supported.", 0); return RESULT_ERROR; } if (!json_doc || !(*json_doc)) throw Mysqlx_exception("Missing JSON data for ADD operation."); auto *impl = get_impl<OP_ADD>(this); impl->add_json(json_doc); return RESULT_OK; } int mysqlx_stmt_struct::add_multiple_documents(va_list &args) { // Note: we report error if no documents were passed int rc = RESULT_ERROR; const char *json_doc; while ((json_doc = va_arg(args, char*)) != NULL) { rc = add_document(json_doc); if (rc != RESULT_OK) return RESULT_ERROR; } if (rc == RESULT_ERROR) set_diagnostic("No documents specified for ADD operation.", 0); return rc; } template <class T> uint64_t get_count(T &obj) { mysqlx_session_struct &sess = obj.get_session(); mysqlx_stmt_struct *stmt = sess.new_stmt<OP_SELECT>(obj); if (!stmt) throw_error("Failed to create statement"); if (RESULT_OK != mysqlx_set_items(stmt, "COUNT(*)", PARAM_END)) throw_error("Failed to bind parameter"); return stmt->exec()->read_row()->get(0).get_uint(); } uint64_t mysqlx_collection_struct::count() { return get_count(*this); } uint64_t mysqlx_table_struct::count() { return get_count(*this); }