Server IP : 172.67.216.182 / Your IP : 172.71.124.7 Web Server : Apache System : Linux krdc-ubuntu-s-2vcpu-4gb-amd-blr1-01.localdomain 5.15.0-142-generic #152-Ubuntu SMP Mon May 19 10:54:31 UTC 2025 x86_64 User : www ( 1000) PHP Version : 7.4.33 Disable Function : passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /www/server/mysql/src/rapid/plugin/x/src/ |
Upload File : |
/* * Copyright (c) 2015, 2023, 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 also distributed 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 included with MySQL. * * 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 "admin_cmd_handler.h" #include "xpl_error.h" #include "sql_data_context.h" #include "query_string_builder.h" #include "mysql/service_my_snprintf.h" #include "ngs/protocol/row_builder.h" #include "sql_data_result.h" #include "ngs/mysqlx/getter_any.h" #include "sha1.h" #include "password.h" #include "mysqlx_resultset.pb.h" #include "mysqlx_datatypes.pb.h" #include "mysqlx_sql.pb.h" #include "xpl_regex.h" #include "xpl_session.h" #include "xpl_log.h" #include "xpl_server.h" #include <algorithm> namespace { struct Index_field_traits { bool is_binary; bool unsigned_allowed; bool unquote; bool prefix_len_allowed; std::string v_col_prefix; Index_field_traits(bool b, bool ua, bool u, bool pa, const std::string &pref) : is_binary(b), unsigned_allowed(ua), unquote(u), prefix_len_allowed(pa), v_col_prefix(pref) {} Index_field_traits() : is_binary(false), unsigned_allowed(false), unquote(false), prefix_len_allowed(false), v_col_prefix("") {} }; inline std::string to_lower(std::string src) { std::transform(src.begin(), src.end(), src.begin(), ::tolower); return src; } } // namespace const xpl::Admin_command_handler::Command_handler xpl::Admin_command_handler::m_command_handler; xpl::Admin_command_handler::Command_handler::Command_handler() { (*this)["ping"] = &Admin_command_handler::ping; (*this)["list_clients"] = &Admin_command_handler::list_clients; (*this)["kill_client"] = &Admin_command_handler::kill_client; (*this)["create_collection"] = &Admin_command_handler::create_collection; (*this)["drop_collection"] = &Admin_command_handler::drop_collection; (*this)["ensure_collection"] = &Admin_command_handler::ensure_collection; (*this)["create_collection_index"] = &Admin_command_handler::create_collection_index; (*this)["drop_collection_index"] = &Admin_command_handler::drop_collection_index; (*this)["list_objects"] = &Admin_command_handler::list_objects; (*this)["enable_notices"] = &Admin_command_handler::enable_notices; (*this)["disable_notices"] = &Admin_command_handler::disable_notices; (*this)["list_notices"] = &Admin_command_handler::list_notices; } ngs::Error_code xpl::Admin_command_handler::Command_handler::execute(Admin_command_handler *admin, const std::string &namespace_, const std::string &command, Command_arguments &args) const { const_iterator iter = find(command); if (iter == end()) return ngs::Error(ER_X_INVALID_ADMIN_COMMAND, "Invalid %s command %s", namespace_.c_str(), command.c_str()); try { return (admin->*(iter->second))(args); } catch (std::exception &e) { log_error("Error executing admin command %s: %s", command.c_str(), e.what()); return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement"); } } xpl::Admin_command_handler::Admin_command_handler(Session &session) : m_session(session), m_da(session.data_context()), m_options(session.options()) {} ngs::Error_code xpl::Admin_command_handler::execute(const std::string &namespace_, const std::string &command, Command_arguments &args) { if (m_da.password_expired()) return ngs::Error(ER_MUST_CHANGE_PASSWORD, "You must reset your password using ALTER USER statement before executing this statement."); if (command.empty()) { log_error("Error executing empty admin command"); return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement"); } return m_command_handler.execute(this, namespace_, to_lower(command), args); } /* Stmt: ping * No arguments required */ ngs::Error_code xpl::Admin_command_handler::ping(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_ping>(); ngs::Error_code error = args.end(); if (error) return error; m_da.proto().send_exec_ok(); return ngs::Success(); } namespace { struct Client_data_ { uint64_t id; std::string user; std::string host; uint64_t session; bool has_session; Client_data_() : id(0), session(0), has_session(false) {} }; void get_client_data(std::vector<Client_data_> &clients_data, xpl::Session &requesting_session, xpl::Sql_data_context &da, ngs::Client_ptr &client) { ngs::shared_ptr<xpl::Session> session(ngs::static_pointer_cast<xpl::Session>(client->session())); Client_data_ c; if (session) { const std::string user = session->is_ready() ? session->data_context().get_authenticated_user_name() : ""; if (requesting_session.can_see_user(user)) { c.id = static_cast<long>(client->client_id_num()); c.host = client->client_hostname(); if (!user.empty()) { c.user = user; c.session = session->data_context().mysql_session_id(); c.has_session = true; } clients_data.push_back(c); } } else if (da.has_authenticated_user_a_super_priv()) { c.id = static_cast<long>(client->client_id_num()); c.host = client->client_hostname(); clients_data.push_back(c); } } } // namespace /* Stmt: list_clients * No arguments required */ ngs::Error_code xpl::Admin_command_handler::list_clients(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_list_clients>(); ngs::Error_code error = args.end(); if (error) return error; std::vector<Client_data_> clients; { Server::Server_ref server(Server::get_instance()); if (server) { Mutex_lock lock((*server)->server().get_client_exit_mutex()); std::vector<ngs::Client_ptr> client_list; (*server)->server().get_client_list().get_all_clients(client_list); clients.reserve(client_list.size()); std::for_each(client_list.begin(), client_list.end(), ngs::bind(get_client_data, ngs::ref(clients), ngs::ref(m_session), ngs::ref(m_da), ngs::placeholders::_1)); } } ngs::Protocol_encoder &proto(m_da.proto()); proto.send_column_metadata("", "", "", "", "client_id", "", 0, Mysqlx::Resultset::ColumnMetaData::UINT, 0, 0, 0); proto.send_column_metadata("", "", "", "", "user", "", 0, Mysqlx::Resultset::ColumnMetaData::BYTES, 0, 0, 0); proto.send_column_metadata("", "", "", "", "host", "", 0, Mysqlx::Resultset::ColumnMetaData::BYTES, 0, 0, 0); proto.send_column_metadata("", "", "", "", "sql_session", "", 0, Mysqlx::Resultset::ColumnMetaData::UINT, 0, 0, 0); for (std::vector<Client_data_>::const_iterator it = clients.begin(); it != clients.end(); ++it) { proto.start_row(); proto.row_builder().add_longlong_field(it->id, true); if (it->user.empty()) proto.row_builder().add_null_field(); else proto.row_builder().add_string_field(it->user.c_str(), it->user.length(), NULL); if (it->host.empty()) proto.row_builder().add_null_field(); else proto.row_builder().add_string_field(it->host.c_str(), it->host.length(), NULL); if (!it->has_session) proto.row_builder().add_null_field(); else proto.row_builder().add_longlong_field(it->session, true); proto.send_row(); } proto.send_result_fetch_done(); proto.send_exec_ok(); return ngs::Success(); } /* Stmt: kill_client * Required arguments: * - id: bigint - the client identification number */ ngs::Error_code xpl::Admin_command_handler::kill_client(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_kill_client>(); uint64_t cid = 0; ngs::Error_code error = args.uint_arg("id", cid).end(); if (error) return error; { xpl::Server::Server_ref server(Server::get_instance()); if (server) error = (*server)->kill_client(cid, m_session); } if (error) return error; m_da.proto().send_exec_ok(); return ngs::Success(); } namespace { ngs::Error_code create_collection_impl(xpl::Sql_data_context &da, const std::string &schema, const std::string &name) { xpl::Query_string_builder qb; qb.put("CREATE TABLE "); if (!schema.empty()) qb.quote_identifier(schema).dot(); qb.quote_identifier(name) .put(" (doc JSON," "_id VARCHAR(32) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(doc, '$._id'))) STORED PRIMARY KEY" ") CHARSET utf8mb4 ENGINE=InnoDB;"); xpl::Sql_data_context::Result_info info; const ngs::PFS_string &tmp(qb.get()); log_debug("CreateCollection: %s", tmp.c_str()); return da.execute_sql_no_result(tmp.c_str(), tmp.length(), info); } } // namespace /* Stmt: create_collection * Required arguments: * - name: string - name of created collection * - schema: string - name of collection's schema */ ngs::Error_code xpl::Admin_command_handler::create_collection(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_create_collection>(); std::string schema; std::string collection; ngs::Error_code error = args.string_arg("schema", schema).string_arg("name", collection).end(); if (error) return error; if (schema.empty()) return ngs::Error_code(ER_X_BAD_SCHEMA, "Invalid schema"); if (collection.empty()) return ngs::Error_code(ER_X_BAD_TABLE, "Invalid collection name"); error = create_collection_impl(m_da, schema, collection); if (error) return error; m_da.proto().send_exec_ok(); return ngs::Success(); } namespace { /* * valid input examples: * DECIMAL * DECIMAL UNSIGNED * DECIMAL(10) * DECIMAL(10) UNSIGNED * DECIMAL(10,5) * DECIMAL(10,5) UNSIGNED */ bool parse_type(const std::string &s, std::string &r_type, int &r_arg, int &r_arg2, bool &r_uns) { std::string::const_iterator c = s.begin(); for (; c != s.end() && isalpha(*c); ++c) r_type.push_back(toupper(*c)); if (c != s.end()) { int consumed; if (sscanf(s.c_str() + (c - s.begin()), "(%i,%i)%n", &r_arg, &r_arg2, &consumed) == 2) c += consumed; else if (sscanf(s.c_str() + (c - s.begin()), "(%i)%n", &r_arg, &consumed) == 1) c += consumed; // skip potential spaces while (c != s.end() && isspace(*c)) c++; std::string ident; for (; c != s.end() && isalpha(*c); ++c) ident.push_back(toupper(*c)); r_uns = false; if (ident == "UNSIGNED") r_uns = true; else { if (!ident.empty()) return false; } if (c != s.end()) return false; } return true; } std::string get_type_prefix(const std::string &prefix, int type_arg, int type_arg2, bool is_unsigned, bool required) { std::stringstream result; std::string traits; // type result << "ix_" << prefix; if (type_arg > 0) result << type_arg; if (type_arg2 > 0) result << "_" << type_arg2; // additional traits (unsigned, required, ...) if (is_unsigned) traits += "u"; if (required) traits += "r"; if (!traits.empty()) result << "_" << traits; result << "_"; return result.str(); } typedef std::list<std::vector<std::string> > String_fields_values; ngs::Error_code query_string_columns(xpl::Sql_data_context &da, const ngs::PFS_string &sql, std::vector<unsigned> &field_idxs, String_fields_values &ret_values) { xpl::Buffering_command_delegate::Resultset r_rows; std::vector<xpl::Command_delegate::Field_type> r_types; xpl::Sql_data_context::Result_info r_info; ngs::Error_code err = da.execute_sql_and_collect_results(sql.data(), sql.length(), r_types, r_rows, r_info); if (err) return err; ret_values.clear(); size_t fields_number = field_idxs.size(); xpl::Buffering_command_delegate::Resultset::iterator it = r_rows.begin(); for (; it != r_rows.end(); ++it) { ret_values.push_back(std::vector<std::string>(fields_number)); for (size_t i = 0; i < field_idxs.size(); ++i) { unsigned field_idx = field_idxs[i]; xpl::Buffering_command_delegate::Row_data *row_data = &(*it); if ((!row_data) || (row_data->fields.size() <= field_idx)) { log_error("query_string_columns failed: invalid row data"); return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement"); } xpl::Buffering_command_delegate::Field_value *field = row_data->fields[field_idx]; if (!field) { log_error("query_string_columns failed: missing row data"); return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement"); } if (MYSQL_TYPE_VARCHAR != r_types[field_idx].type && MYSQL_TYPE_STRING != r_types[field_idx].type && MYSQL_TYPE_TINY_BLOB != r_types[field_idx].type && MYSQL_TYPE_MEDIUM_BLOB != r_types[field_idx].type && MYSQL_TYPE_LONG_BLOB != r_types[field_idx].type && MYSQL_TYPE_BLOB != r_types[field_idx].type && MYSQL_TYPE_VAR_STRING != r_types[field_idx].type) { log_error("query_string_columns failed: invalid field type"); return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement"); } ret_values.back()[i] = *field->value.v_string; } } return ngs::Success(); } bool name_is(const std::vector<std::string> &field, const std::string &name) { return field[0] == name; } ngs::Error_code remove_nonvirtual_column_names(const std::string &schema_name, const std::string &table_name, String_fields_values &ret_column_names, xpl::Sql_data_context &da) { xpl::Query_string_builder qb; const unsigned FIELD_COLMN_IDX = 0; const unsigned EXTRA_COLMN_IDX = 5; if (ret_column_names.size() == 0) return ngs::Success(); qb.put("SHOW COLUMNS FROM ") .quote_identifier(schema_name).dot().quote_identifier(table_name) .put(" WHERE Field IN ("); String_fields_values::const_iterator it_columns = ret_column_names.begin(); for (;;) { qb.quote_string((*it_columns)[0]); if (++it_columns != ret_column_names.end()) qb.put(","); else break; } qb.put(")"); std::vector<unsigned> fields_ids(2); fields_ids[0] = FIELD_COLMN_IDX; fields_ids[1] = EXTRA_COLMN_IDX; String_fields_values column_descs; ngs::Error_code error = query_string_columns(da, qb.get(), fields_ids, column_descs); if (error) return error; String_fields_values::const_iterator it_field = column_descs.begin(); for (; it_field != column_descs.end(); ++it_field) { std::string column_name = (*it_field)[0]; std::string column_desc = (*it_field)[1]; if (!(column_desc.find("VIRTUAL GENERATED") != std::string::npos)) ret_column_names.remove_if(ngs::bind(name_is, ngs::placeholders::_1, column_name)); } return ngs::Success(); } ngs::Error_code index_on_virtual_column_supported(const std::string &schema_name, const std::string &table_name, xpl::Sql_data_context &da, bool &r_supports) { const unsigned CREATE_COLMN_IDX = 1; xpl::Query_string_builder qb; std::vector<unsigned> fields_ids(1); fields_ids[0] = CREATE_COLMN_IDX; String_fields_values create_stmts; qb.put("SHOW CREATE TABLE ").quote_identifier(schema_name).dot().quote_identifier(table_name); ngs::Error_code error = query_string_columns(da, qb.get(), fields_ids, create_stmts); if (error) return error; // if query didn't fail it should return 1 row if (create_stmts.size() != 1) { const unsigned int num_of_rows = static_cast<unsigned int>(create_stmts.size()); log_error("index_on_virtual_column_supported() failed: wrong number of rows: %u", num_of_rows); return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement"); } String_fields_values::const_iterator it_create_stmt = create_stmts.begin(); std::string create_stmt = (*it_create_stmt)[0]; size_t pos = create_stmt.find("ENGINE="); if (pos == std::string::npos) { log_error("index_on_virtual_column_supported() failed: no engine info: %s", create_stmt.c_str()); return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement"); } std::string engine; std::string::const_iterator ci = create_stmt.begin() + pos + strlen("ENGINE="); for (; ci != create_stmt.end() && isalpha(*ci); ++ci) engine.push_back(*ci); // currently only InnoDB supports VIRTUAL GENERATED columns r_supports = (engine == "InnoDB"); return ngs::Success(); } bool table_column_exists(const std::string &schema_name, const std::string &table_name, const std::string &column_name, xpl::Sql_data_context &da, bool &r_exists) { xpl::Query_string_builder qb; xpl::Buffering_command_delegate::Resultset r_rows; std::vector<xpl::Command_delegate::Field_type> r_types; xpl::Sql_data_context::Result_info r_info; qb.put("SHOW COLUMNS FROM ") .quote_identifier(schema_name).dot().quote_identifier(table_name) .put(" WHERE Field = ").quote_string(column_name); ngs::Error_code err = da.execute_sql_and_collect_results(qb.get().data(), qb.get().length(), r_types, r_rows, r_info); if (err) return false; r_exists = r_rows.size() > 0; return true; } std::string hash_column_name(const std::string &name) { std::string hash; hash.resize(2*SHA1_HASH_SIZE + 2); // just an arbitrary hash ::make_scrambled_password(&hash[0], name.c_str()); hash.resize(2*SHA1_HASH_SIZE + 1); // strip the \0 return hash.substr(1); // skip the 1st char } } // namespace /* Stmt: create_collection_index * Required arguments: * - name: string - name of index * - collection: string - name of indexed collection * - schema: string - name of collection's schema * - unique: bool - whether the index should be a unique index * - constraint: object, list - detailed information for the generated column * - member: string - path to document member for which the index will be created * - required: bool - whether the generated column will be created as NOT NULL * - type: string - data type of the created index * * VARCHAR and CHAR are now indexable because: * - varchar column needs to be created with a length, which would limit documents to have * that field smaller than that * - if we use left() to truncate the value of the column, then the index won't be usable unless * queries also specify left(), which is not desired. */ ngs::Error_code xpl::Admin_command_handler::create_collection_index(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_create_collection_index>(); Query_string_builder qb; bool required = false; typedef Index_field_traits _T; static std::map<std::string, _T> valid_types; static struct Valid_type_init { Valid_type_init() { // binary unsigned unqote prefix_len column_prefix valid_types["TINYINT"] = _T(false, true, false, false, "it"); valid_types["SMALLINT"] = _T(false, true, false, false, "is"); valid_types["MEDIUMINT"] = _T(false, true, false, false, "im"); valid_types["INT"] = _T(false, true, false, false, "i"); valid_types["INTEGER"] = _T(false, true, false, false, "i"); valid_types["BIGINT"] = _T(false, true, false, false, "ib"); valid_types["REAL"] = _T(false, true, false, false, "fr"); valid_types["FLOAT"] = _T(false, true, false, false, "f"); valid_types["DOUBLE"] = _T(false, true, false, false, "fd"); valid_types["DECIMAL"] = _T(false, true, false, false, "xd"); valid_types["NUMERIC"] = _T(false, true, false, false, "xn"); valid_types["DATE"] = _T(false, false, true, false, "d"); valid_types["TIME"] = _T(false, false, true, false, "dt"); valid_types["TIMESTAMP"] = _T(false, false, true, false, "ds"); valid_types["DATETIME"] = _T(false, false, true, false, "dd"); valid_types["YEAR"] = _T(false, false, true, false, "dy"); valid_types["BIT"] = _T(false, false, true, true, "t"); valid_types["BLOB"] = _T(true, false, true, true, "bt"); valid_types["TEXT"] = _T(true, false, true, true, "t"); } } _type_init; std::string schema; std::string collection; std::string index_name; bool unique = false; std::vector<Command_arguments*> constraints; ngs::Error_code error = args.string_arg("schema", schema) .string_arg("collection", collection) .string_arg("name", index_name) .bool_arg("unique", unique) .object_list("constraint", constraints) .error(); if (error) return error; std::vector<std::string> col_field_path; std::vector<std::string> col_raw_type; std::vector<bool> col_required; bool column_exists = false; typedef std::vector<Command_arguments*>::iterator It; for (It i = constraints.begin(); i != constraints.end(); ++i) { std::string f, t; bool r = false; error = (*i)->docpath_arg("member", f) .string_arg("type", t) .bool_arg("required", r) .error(); if (error) return error; if (f.empty()) return ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Argument value '%s' for document member is invalid", f.c_str()); col_field_path.push_back(f); col_raw_type.push_back(t); col_required.push_back(r); required = required || r; } error = args.end(); if (error) return error; if (schema.empty()) return ngs::Error(ER_X_BAD_SCHEMA, "Invalid schema '%s'", schema.c_str()); if (collection.empty()) return ngs::Error(ER_X_BAD_TABLE, "Invalid collection name '%s'", collection.c_str()); if (index_name.empty()) return ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Argument value '%s' for index name is invalid", index_name.c_str()); // check if the table's engine supports index on the virtual column bool virtual_supported = false; error = index_on_virtual_column_supported(schema, collection, m_da, virtual_supported); if (error) { if (error.error == ER_INTERNAL_ERROR) return error; else // if it is not internal then the reason is bad schema or table name return ngs::Error(ER_X_BAD_TABLE, "Invalid collection name: %s.%s", schema.c_str(), collection.c_str()); } std::string column_type = virtual_supported ? "VIRTUAL" : "STORED"; std::vector<std::pair<std::string, std::string> > columns; // NOTE: This could be done more efficiently with ALGORIHM=INPLACE but: // - currently server does not support adding virtual columns to the table inplace combined with // other ALTER TABLE statement (adding index in this case) // - attempt to split adding the index and adding the virtual columns into 2 separate statements // leads to the server crash ("Bug 21640846 ASSERTION FAILURE WHEN CREATING VIRTUAL COLUMN.") // generate DDL qb.put("ALTER TABLE ").quote_identifier(schema).dot().quote_identifier(collection); for (size_t c = col_field_path.size(), i = 0; i < c; i++) { std::string column_name; std::string type_name; int type_arg = -1, type_arg2 = -1; bool is_unsigned = false; // validate the type if (!col_raw_type[i].empty()) { if (!parse_type(col_raw_type[i], type_name, type_arg, type_arg2, is_unsigned) || (valid_types.find(type_name) == valid_types.end()) || (is_unsigned && !valid_types[type_name].unsigned_allowed)) return ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid or unsupported type specification '%s'", col_raw_type[i].c_str()); } else { type_name = "TEXT"; type_arg = 64; } std::string required = col_required[i] ? "NOT NULL" : ""; column_name = '$' + get_type_prefix(valid_types[type_name].v_col_prefix, type_arg, type_arg2, is_unsigned, !required.empty()) + hash_column_name(col_field_path[i].substr(2)); // if column with given name already exists just skip adding it and use it for the index if (!table_column_exists(schema, collection, column_name, m_da, column_exists)) return ngs::Error(ER_X_BAD_TABLE, "Invalid collection name: %s.%s", schema.c_str(), collection.c_str()); std::stringstream column_index_size; if (type_arg > 0) { column_index_size << "(" << type_arg; if (type_arg2 > 0) column_index_size << ", " << type_arg2; column_index_size << ")"; } if (!column_exists) { std::string extract_begin, extract_end; if (valid_types[type_name].unquote) { extract_begin = "JSON_UNQUOTE("; extract_end = ")"; } qb.put(" ADD COLUMN ").quote_identifier(column_name).put(" ").put(type_name); if (type_name != "TEXT") qb.put(column_index_size.str()); if (is_unsigned) qb.put(" UNSIGNED"); qb.put(" GENERATED ALWAYS AS (").put(extract_begin).put("JSON_EXTRACT(doc, ") .quote_string(col_field_path[i]).put(")").put(extract_end).put(") ") .put(column_type).put(" ").put(required).put(","); } columns.push_back(std::make_pair(column_name, valid_types[type_name].is_binary ? column_index_size.str() : "")); } qb.put(unique ? " ADD UNIQUE INDEX " : " ADD INDEX ") .quote_identifier(index_name).put(" ("); std::vector<std::pair<std::string, std::string> >::const_iterator it = columns.begin(); for (; it != columns.end(); ++it) { if (it != columns.begin()) qb.put(","); qb.quote_identifier(it->first).put(it->second); } qb.put(")"); Sql_data_context::Result_info info; const ngs::PFS_string &tmp(qb.get()); log_debug("CreateCollectionIndex: %s", tmp.c_str()); error = m_da.execute_sql_no_result(tmp.data(), tmp.length(), info); if (error) { // if we're creating a NOT NULL generated index/column and get a NULL error, it's // because one of the existing documents had a NULL / unset value if (error.error == ER_BAD_NULL_ERROR && required) return ngs::Error_code(ER_X_DOC_REQUIRED_FIELD_MISSING, "Collection contains document missing required field"); return error; } m_da.proto().send_exec_ok(); return ngs::Success(); } /* Stmt: drop_collection * Required arguments: * - name: string - name of dropped collection * - schema: string - name of collection's schema */ ngs::Error_code xpl::Admin_command_handler::drop_collection(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_drop_collection>(); Query_string_builder qb; std::string schema; std::string collection; ngs::Error_code error = args.string_arg("schema", schema).string_arg("name", collection).end(); if (error) return error; if (schema.empty()) return ngs::Error_code(ER_X_BAD_SCHEMA, "Invalid schema"); if (collection.empty()) return ngs::Error_code(ER_X_BAD_TABLE, "Invalid collection name"); qb.put("DROP TABLE ").quote_identifier(schema).dot().quote_identifier(collection); const ngs::PFS_string &tmp(qb.get()); log_debug("DropCollection: %s", tmp.c_str()); Sql_data_context::Result_info info; error = m_da.execute_sql_no_result(tmp.data(), tmp.length(), info); if (error) return error; m_da.proto().send_exec_ok(); return ngs::Success(); } namespace { ngs::Error_code get_index_virtual_column_names(const std::string &schema_name, const std::string &table_name, const std::string &index_name, xpl::Sql_data_context &da, String_fields_values &ret_column_names) { const unsigned INDEX_NAME_COLUMN_IDX = 4; xpl::Query_string_builder qb; /* get list of all index column names */ qb.put("SHOW INDEX FROM ") .quote_identifier(schema_name).dot().quote_identifier(table_name) .put(" WHERE Key_name = ").quote_string(index_name); std::vector<unsigned> fields_ids(1); fields_ids[0] = INDEX_NAME_COLUMN_IDX; ngs::Error_code error = query_string_columns(da, qb.get(), fields_ids, ret_column_names); if (error) return error; /* remove from the list columns that shouldn't be dropped */ /* don't drop non-virtual columns */ error = remove_nonvirtual_column_names(schema_name, table_name, ret_column_names, da); if (error) return error; xpl::Buffering_command_delegate::Resultset r_rows; std::vector<xpl::Command_delegate::Field_type> r_types; xpl::Sql_data_context::Result_info r_info; String_fields_values::iterator it = ret_column_names.begin(); while (it != ret_column_names.end()) { /* don't drop '_id' column */ if ((*it)[0] == "_id") { ret_column_names.erase(it++); continue; } /* don't drop columns used by other index(es) */ qb.clear(); qb.put("SHOW INDEX FROM ") .quote_identifier(schema_name).dot().quote_identifier(table_name) .put(" WHERE Key_name <> ").quote_string(index_name) .put(" AND Column_name = ").quote_string((*it)[0]); da.execute_sql_and_collect_results(qb.get().data(), qb.get().length(), r_types, r_rows, r_info); if (r_rows.size() > 0) { ret_column_names.erase(it++); continue; } ++it; } return ngs::Success(); } } // namespace /* Stmt: drop_collection_index * Required arguments: * - name: string - name of dropped index * - collection: string - name of collection with dropped index * - schema: string - name of collection's schema */ ngs::Error_code xpl::Admin_command_handler::drop_collection_index(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_drop_collection_index>(); Query_string_builder qb; std::string schema; std::string collection; std::string name; ngs::Error_code error = args .string_arg("schema", schema) .string_arg("collection", collection) .string_arg("name", name).end(); if (error) return error; if (schema.empty()) return ngs::Error_code(ER_X_BAD_SCHEMA, "Invalid schema"); if (collection.empty()) return ngs::Error_code(ER_X_BAD_TABLE, "Invalid collection name"); if (name.empty()) return ngs::Error_code(ER_X_MISSING_ARGUMENT, "Invalid index name"); String_fields_values column_names; // collect the index columns (if any) to be dropped error = get_index_virtual_column_names(schema, collection, name, m_da, column_names); if (error) { if (error.error == ER_INTERNAL_ERROR) return error; else // if it is not internal then the reason is bad schema or table name return ngs::Error(ER_X_BAD_TABLE, "Invalid collection name: %s.%s", schema.c_str(), collection.c_str()); } // drop the index qb.put("ALTER TABLE ").quote_identifier(schema).dot().quote_identifier(collection) .put(" DROP INDEX ").quote_identifier(name); // drop the index's virtual columns String_fields_values::const_iterator it = column_names.begin(); for (; it != column_names.end(); ++it) { qb.put(", DROP COLUMN ").quote_identifier((*it)[0]); } const ngs::PFS_string &tmp(qb.get()); log_debug("DropCollectionIndex: %s", tmp.c_str()); Sql_data_context::Result_info info; error = m_da.execute_sql_no_result(tmp.data(), tmp.length(), info); if (error) return error; m_da.proto().send_exec_ok(); return ngs::Success(); } namespace { static const char* const fixed_notice_names[] = { "account_expired", "generated_insert_id", "rows_affected", "produced_message" }; static const char* const *fixed_notice_names_end = &fixed_notice_names[0] + sizeof(fixed_notice_names) / sizeof(fixed_notice_names[0]); inline bool is_fixed_notice_name(const std::string ¬ice) { return std::find(fixed_notice_names, fixed_notice_names_end, notice) != fixed_notice_names_end; } inline void add_notice_row(xpl::Sql_data_context &da, const std::string ¬ice, longlong status) { da.proto().start_row(); da.proto().row_builder().add_string_field(notice.c_str(), notice.length(), NULL); da.proto().row_builder().add_longlong_field(status, 0); da.proto().send_row(); } } // namespace /* Stmt: enable_notices * Required arguments: * - notice: string, list - name (or names) of enabled notice */ ngs::Error_code xpl::Admin_command_handler::enable_notices(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_enable_notices>(); std::vector<std::string> notices; ngs::Error_code error = args.string_list("notice", notices).end(); if (error) return error; bool enable_warnings = false; for (std::vector<std::string>::const_iterator i = notices.begin(); i != notices.end(); ++i) { if (*i == "warnings") enable_warnings = true; else if (!is_fixed_notice_name(*i)) return ngs::Error(ER_X_BAD_NOTICE, "Invalid notice name %s", i->c_str()); } if (enable_warnings) m_options.set_send_warnings(true); m_da.proto().send_exec_ok(); return ngs::Success(); } /* Stmt: disable_notices * Required arguments: * - notice: string, list - name (or names) of enabled notice */ ngs::Error_code xpl::Admin_command_handler::disable_notices(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_disable_notices>(); std::vector<std::string> notices; ngs::Error_code error = args.string_list("notice", notices).end(); if (error) return error; bool disable_warnings = false; for (std::vector<std::string>::const_iterator i = notices.begin(); i != notices.end(); ++i) { if (*i == "warnings") disable_warnings = true; else if (is_fixed_notice_name(*i)) return ngs::Error(ER_X_CANNOT_DISABLE_NOTICE, "Cannot disable notice %s", i->c_str()); else return ngs::Error(ER_X_BAD_NOTICE, "Invalid notice name %s", i->c_str()); } if (disable_warnings) m_options.set_send_warnings(false); m_da.proto().send_exec_ok(); return ngs::Success(); } /* Stmt: list_notices * No arguments required */ ngs::Error_code xpl::Admin_command_handler::list_notices(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_list_notices>(); ngs::Error_code error = args.end(); if (error) return error; // notice | enabled // <name> | <1/0> m_da.proto().send_column_metadata("", "", "", "", "notice", "", 0, Mysqlx::Resultset::ColumnMetaData::BYTES, 0, 0, 0); m_da.proto().send_column_metadata("", "", "", "", "enabled", "", 0, Mysqlx::Resultset::ColumnMetaData::SINT, 0, 0, 0); add_notice_row(m_da, "warnings", m_options.get_send_warnings() ? 1 : 0); for (const char* const *notice = fixed_notice_names; notice < fixed_notice_names_end; ++notice) add_notice_row(m_da, *notice, 1); m_da.proto().send_result_fetch_done(); m_da.proto().send_exec_ok(); return ngs::Success(); } namespace { ngs::Error_code is_schema_selected_and_exists(xpl::Sql_data_context &da, const std::string &schema) { xpl::Query_string_builder qb; qb.put("SHOW TABLES"); if (!schema.empty()) qb.put(" FROM ").quote_identifier(schema); xpl::Sql_data_context::Result_info info; return da.execute_sql_no_result(qb.get().data(), qb.get().length(), info); } template<typename T> T get_system_variable(xpl::Sql_data_context &da, const std::string &variable) { xpl::Sql_data_result result(da); try { result.query(("SELECT @@" + variable).c_str()); if (result.size() != 1) { log_error("Unable to retrieve system variable '%s'", variable.c_str()); return T(); } T value = T(); result.get(value); return value; } catch (const ngs::Error_code &) { log_error("Unable to retrieve system variable '%s'", variable.c_str()); return T(); } } const char *const COUNT_DOC = "COUNT(CASE WHEN (column_name = 'doc' " "AND data_type = 'json') THEN 1 ELSE NULL END)"; const char *const COUNT_ID = "COUNT(CASE WHEN (column_name = '_id' " "AND generation_expression = " "'json_unquote(json_extract(`doc`,''$._id''))') THEN 1 ELSE NULL END)"; const char *const COUNT_GEN = "COUNT(CASE WHEN (column_name != '_id' " "AND generation_expression RLIKE " "'^(json_unquote[[.(.]])?json_extract[[.(.]]`doc`," "''[[.$.]]([[...]][^[:space:][...]]+)+''[[.).]]{1,2}$') THEN 1 ELSE NULL " "END)"; } // namespace /* Stmt: list_objects * Required arguments: * - schema: string, optional - name of listed object's schema * - pattern: string, optional - a filter to use for matching object names to be returned */ ngs::Error_code xpl::Admin_command_handler::list_objects(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_list_objects>(); static const bool is_table_names_case_sensitive = get_system_variable<long>(m_da, "lower_case_table_names") == 0l; static const char *const BINARY_OPERATOR = is_table_names_case_sensitive && get_system_variable<long>(m_da, "lower_case_file_system") == 0l ? "BINARY " : ""; std::string schema, pattern; ngs::Error_code error = args .string_arg("schema", schema, true) .string_arg("pattern", pattern, true).end(); if (error) return error; if (!is_table_names_case_sensitive) schema = to_lower(schema); error = is_schema_selected_and_exists(m_da, schema); if (error) return error; Query_string_builder qb; qb.put("SELECT ") .put(BINARY_OPERATOR) .put("T.table_name AS name, " "IF(ANY_VALUE(T.table_type) LIKE '%VIEW', " "IF(COUNT(*)=1 AND ") .put(COUNT_DOC) .put("=1, 'COLLECTION_VIEW', 'VIEW'), IF(COUNT(*)-2 = ") .put(COUNT_GEN) .put(" AND ") .put(COUNT_DOC) .put("=1 AND ") .put(COUNT_ID) .put("=1, 'COLLECTION', 'TABLE')) AS type " "FROM information_schema.tables AS T " "LEFT JOIN information_schema.columns AS C ON (") .put(BINARY_OPERATOR) .put("T.table_schema = C.table_schema AND ") .put(BINARY_OPERATOR) .put("T.table_name = C.table_name) " "WHERE T.table_schema = "); if (schema.empty()) qb.put("schema()"); else qb.quote_string(schema); if (!pattern.empty()) qb.put(" AND T.table_name LIKE ").quote_string(pattern); qb.put(" GROUP BY name ORDER BY name"); Sql_data_context::Result_info info; error = m_da.execute_sql_and_stream_results(qb.get().data(), qb.get().length(), false, info); if (error) return error; m_da.proto().send_exec_ok(); return ngs::Success(); } namespace { bool is_collection(xpl::Sql_data_context &da, const std::string &schema, const std::string &name) { xpl::Query_string_builder qb; qb.put("SELECT COUNT(*) AS cnt,") .put(COUNT_DOC).put(" AS doc,").put(COUNT_ID).put(" AS id,").put(COUNT_GEN).put(" AS gen " "FROM information_schema.columns " "WHERE table_name = ").quote_string(name).put(" AND table_schema = "); if (schema.empty()) qb.put("schema()"); else qb.quote_string(schema); xpl::Sql_data_result result(da); try { result.query(qb.get()); if (result.size() != 1) { log_debug("Unable to recognize '%s' as a collection; query result size: %lu", std::string(schema.empty() ? name : schema + "." + name).c_str(), static_cast<unsigned long>(result.size())); return false; } long int cnt = 0, doc = 0, id = 0, gen = 0; result.get(cnt).get(doc).get(id).get(gen); return doc == 1 && id == 1 && (cnt == gen + doc + id); } #if defined(XPLUGIN_LOG_DEBUG) && !defined(XPLUGIN_DISABLE_LOG) catch (const ngs::Error_code &e) #else catch (const ngs::Error_code &) #endif { log_debug("Unable to recognize '%s' as a collection; exception message: '%s'", std::string(schema.empty() ? name : schema + "." + name).c_str(), e.message.c_str()); return false; } } } // namespace /* Stmt: ensure_collection * Required arguments: * - name: string - name of created collection * - schema: string, optional - name of collection's schema */ ngs::Error_code xpl::Admin_command_handler::ensure_collection(Command_arguments &args) { m_session.update_status<&Common_status_variables::m_stmt_ensure_collection>(); std::string schema; std::string collection; ngs::Error_code error = args.string_arg("schema", schema, true).string_arg("name", collection).end(); if (error) return error; if (collection.empty()) return ngs::Error_code(ER_X_BAD_TABLE, "Invalid collection name"); error = create_collection_impl(m_da, schema, collection); if (error) { if (error.error != ER_TABLE_EXISTS_ERROR) return error; if (!is_collection(m_da, schema, collection)) return ngs::Error(ER_X_INVALID_COLLECTION, "Table '%s' exists but is not a collection", (schema.empty() ? collection : schema + '.' + collection).c_str()); } m_da.proto().send_exec_ok(); return ngs::Success(); } const char* const xpl::Admin_command_handler::Command_arguments::PLACEHOLDER = "?"; xpl::Admin_command_arguments_list::Admin_command_arguments_list(const List &args) : m_args(args), m_current(m_args.begin()), m_args_consumed(0) {} xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::string_arg(const char *name, std::string &ret_value, bool optional) { if (check_scalar_arg(name, Mysqlx::Datatypes::Scalar::V_STRING, "string", optional)) { const std::string &value = m_current->scalar().v_string().value(); if (memchr(value.data(), 0, value.length())) { m_error = ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid value for argument '%s'", name); return *this; } ret_value = value; ++m_current; } return *this; } xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::string_list(const char *name, std::vector<std::string> &ret_value, bool optional) { std::string value; do { string_arg(name, value, optional); ret_value.push_back(value); value.clear(); } while (!is_end()); return *this; } xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::sint_arg(const char *name, int64_t &ret_value, bool optional) { if (check_scalar_arg(name, Mysqlx::Datatypes::Scalar::V_SINT, "signed int", optional)) { if (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_UINT) ret_value = (int64_t)m_current->scalar().v_unsigned_int(); else if (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_SINT) ret_value = m_current->scalar().v_signed_int(); ++m_current; } return *this; } xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::uint_arg(const char *name, uint64_t &ret_value, bool optional) { if (check_scalar_arg(name, Mysqlx::Datatypes::Scalar::V_UINT, "unsigned int", optional)) { if (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_UINT) ret_value = m_current->scalar().v_unsigned_int(); else if (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_SINT) ret_value = (uint64_t)m_current->scalar().v_signed_int(); ++m_current; } return *this; } xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::bool_arg(const char *name, bool &ret_value, bool optional) { if (check_scalar_arg(name, Mysqlx::Datatypes::Scalar::V_BOOL, "bool", optional)) { ret_value = m_current->scalar().v_bool(); ++m_current; } return *this; } xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::docpath_arg(const char *name, std::string &ret_value, bool optional) { m_args_consumed++; if (!m_error) { if (m_current == m_args.end()) m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Too few arguments"); else { if (m_current->type() == Mysqlx::Datatypes::Any::SCALAR && m_current->has_scalar() && (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_STRING && m_current->scalar().has_v_string())) { ret_value = m_current->scalar().v_string().value(); // We could perform some extra validation on the document path here, but // since the path will be quoted and escaped when used, it would be redundant. // Plus, the best way to have the exact same syntax as the server // is to let the server do it. if (ret_value.empty() || ret_value.size() < 2) m_error = ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid document path value for argument %s", name); } else arg_type_mismatch(name, m_args_consumed, "document path string"); } ++m_current; } return *this; } xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::object_list(const char *name, std::vector<Command_arguments*> &ret_value, bool optional, unsigned expected_members_count) { List::difference_type left = m_args.end() - m_current; if (left % expected_members_count > 0) { m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Too few values for argument '%s'", name); return *this; } for (unsigned i = 0; i < left / expected_members_count; ++i) ret_value.push_back(this); return *this; } bool xpl::Admin_command_arguments_list::is_end() const { return !(m_error.error == 0 && m_args.size() > m_args_consumed); } const ngs::Error_code &xpl::Admin_command_arguments_list::end() { if (m_error.error == ER_X_CMD_NUM_ARGUMENTS || (m_error.error == 0 && m_args.size() > m_args_consumed)) { m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Invalid number of arguments, expected %i but got %i", m_args_consumed, m_args.size()); } return m_error; } void xpl::Admin_command_arguments_list::arg_type_mismatch(const char *argname, int argpos, const char *type) { m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE, "Invalid type for argument '%s' at #%i (should be %s)", argname, argpos, type); } bool xpl::Admin_command_arguments_list::check_scalar_arg(const char *argname, Mysqlx::Datatypes::Scalar::Type type, const char *type_name, bool optional) { m_args_consumed++; if (!m_error) { if (m_current == m_args.end()) { if (!optional) m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Insufficient number of arguments"); } else { if (m_current->type() == Mysqlx::Datatypes::Any::SCALAR && m_current->has_scalar()) { if (m_current->scalar().type() == type) { //TODO: add charset check for strings? // return true only if value to be consumed is available return true; } else if (type == Mysqlx::Datatypes::Scalar::V_SINT && m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_UINT && m_current->scalar().v_unsigned_int() < (uint64_t)std::numeric_limits<int64_t>::max()) { return true; } else if (type == Mysqlx::Datatypes::Scalar::V_UINT && m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_SINT && m_current->scalar().v_signed_int() >= 0) { return true; } else if (optional && m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_NULL) ; else arg_type_mismatch(argname, m_args_consumed, type_name); } else arg_type_mismatch(argname, m_args_consumed, type_name); ++m_current; } } return false; } namespace { typedef ::Mysqlx::Datatypes::Object_ObjectField Object_field; struct Object_field_key_is_equal { Object_field_key_is_equal(const char *p) : m_pattern(p) {} bool operator() (const Object_field &fld) const { return fld.has_key() && fld.key() == m_pattern; } private: const char *m_pattern; }; template<typename T> class General_argument_validator { public: General_argument_validator(const char*, ngs::Error_code&) {} void operator() (const T &input, T &output) { output = input; } }; template<typename T, typename V = General_argument_validator<T> > class Argument_type_handler { public: Argument_type_handler(const char *name, T &value, ngs::Error_code &error) : m_validator(name, error), m_value(&value), m_error(error), m_name(name) {} Argument_type_handler(const char *name, ngs::Error_code &error) : m_validator(name, error), m_value(NULL), m_error(error), m_name(name) {} void assign(T &value) { m_value = &value; } void operator() (const T &value) { m_validator(value, *m_value); } void operator() () { m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE, "Invalid type of value for argument '%s'", m_name); } template<typename O> void operator()(const O &value) { this->operator()(); } private: V m_validator; T *m_value; ngs::Error_code &m_error; const char *m_name; }; class String_argument_validator { public: String_argument_validator(const char *name, ngs::Error_code &error) : m_name(name), m_error(error) {} void operator() (const std::string &input, std::string &output) { if (memchr(input.data(), 0, input.length())) { m_error = ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid value for argument '%s'", m_name); return; } output = input; } protected: const char *m_name; ngs::Error_code &m_error; }; class Docpath_argument_validator : String_argument_validator { public: Docpath_argument_validator(const char *name, ngs::Error_code &error) : String_argument_validator(name, error) {} void operator() (const std::string &input, std::string &output) { static const xpl::Regex re("^[[.dollar-sign.]]([[.period.]][^[:space:][.period.]]+)+$"); std::string value; String_argument_validator::operator ()(input, value); if (m_error) return; if (re.match(value.c_str())) output = value; else m_error = ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid value for argument '%s', expected path to document member", m_name); } }; } // namespace xpl::Admin_command_arguments_object::Admin_command_arguments_object(const List &args) : m_args_empty(args.size() == 0), m_is_object(args.size() == 1 && args.Get(0).has_obj()), m_object(m_is_object ? args.Get(0).obj() : Object::default_instance()), m_args_consumed(0) {} xpl::Admin_command_arguments_object::Admin_command_arguments_object(const Object &obj) : m_args_empty(true), m_is_object(true), m_object(obj), m_args_consumed(0) {} void xpl::Admin_command_arguments_object::expected_value_error(const char *name) { m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Invalid number of arguments, expected value for '%s'", name); } template<typename H> void xpl::Admin_command_arguments_object::get_scalar_arg(const char *name, bool optional, H &handler) { const Object::ObjectField *field = get_object_field(name, optional); if (!field) return; get_scalar_value(field->value(), handler); } const xpl::Admin_command_arguments_object::Object::ObjectField *xpl::Admin_command_arguments_object::get_object_field(const char *name, bool optional) { if (m_error) return NULL; ++m_args_consumed; if (!m_is_object) { if (!optional) expected_value_error(name); return NULL; } const Object_field_list &fld = m_object.fld(); Object_field_list::const_iterator i = std::find_if(fld.begin(), fld.end(), Object_field_key_is_equal(name)); if (i == fld.end()) { if (!optional) expected_value_error(name); return NULL; } return &(*i); } template<typename H> void xpl::Admin_command_arguments_object::get_scalar_value(const Any &value, H &handler) { try { ngs::Getter_any::put_scalar_value_to_functor(value, handler); } catch (const ngs::Error_code &e) { m_error = e; } } xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::string_arg(const char *name, std::string &ret_value, bool optional) { Argument_type_handler<std::string, String_argument_validator> handler(name, ret_value, m_error); get_scalar_arg(name, optional, handler); return *this; } xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::string_list(const char *name, std::vector<std::string> &ret_value, bool optional) { const Object::ObjectField *field = get_object_field(name, optional); if (!field) return *this; if (!field->value().has_type()) { expected_value_error(name); return *this; } std::vector<std::string> values; Argument_type_handler<std::string, String_argument_validator> handler(name, m_error); switch (field->value().type()) { case ::Mysqlx::Datatypes::Any_Type_ARRAY: for (int i = 0; i < field->value().array().value_size(); ++i) { handler.assign(*values.insert(values.end(), "")); get_scalar_value(field->value().array().value(i), handler); } break; case ::Mysqlx::Datatypes::Any_Type_SCALAR: handler.assign(*values.insert(values.end(), "")); get_scalar_value(field->value(), handler); break; default: m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE, "Invalid type of argument '%s', expected list of arguments", name); } if (!m_error) ret_value = values; return *this; } xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::sint_arg(const char *name, int64_t &ret_value, bool optional) { Argument_type_handler<google::protobuf::int64> handler(name, ret_value, m_error); get_scalar_arg(name, optional, handler); return *this; } xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::uint_arg(const char *name, uint64_t &ret_value, bool optional) { Argument_type_handler<google::protobuf::uint64> handler(name, ret_value, m_error); get_scalar_arg(name, optional, handler); return *this; } xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::bool_arg(const char *name, bool &ret_value, bool optional) { Argument_type_handler<bool> handler(name, ret_value, m_error); get_scalar_arg(name, optional, handler); return *this; } xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::docpath_arg(const char *name, std::string &ret_value, bool optional) { Argument_type_handler<std::string, Docpath_argument_validator> handler(name, ret_value, m_error); get_scalar_arg(name, optional, handler); return *this; } xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::object_list(const char *name, std::vector<Command_arguments*> &ret_value, bool optional, unsigned) { const Object::ObjectField *field = get_object_field(name, optional); if (!field) return *this; if (!field->value().has_type()) { expected_value_error(name); return *this; } std::vector<Command_arguments*> values; switch (field->value().type()) { case ::Mysqlx::Datatypes::Any_Type_ARRAY: for (int i = 0; i < field->value().array().value_size(); ++i) { const Any &any = field->value().array().value(i); if (!any.has_type() || any.type() != ::Mysqlx::Datatypes::Any_Type_OBJECT) { m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE, "Invalid type of argument '%s', expected list of objects", name); break; } values.push_back(add_sub_object(any.obj())); } break; case ::Mysqlx::Datatypes::Any_Type_OBJECT: values.push_back(add_sub_object(field->value().obj())); break; default: m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE, "Invalid type of argument '%s', expected list of objects", name); } if (!m_error) ret_value = values; return *this; } xpl::Admin_command_arguments_object *xpl::Admin_command_arguments_object::add_sub_object(const Object &object) { Admin_command_arguments_object *obj = new Admin_command_arguments_object(object); m_sub_objects.push_back(ngs::shared_ptr<Admin_command_arguments_object>(obj)); return obj; } const ngs::Error_code &xpl::Admin_command_arguments_object::end() { if (m_error) return m_error; if (m_is_object) { if (m_object.fld().size() > m_args_consumed) m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Invalid number of arguments, expected %i but got %i", m_args_consumed, m_object.fld().size()); } else { if (!m_args_empty) m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE, "Invalid type of arguments, expected object of arguments"); } return m_error; } bool xpl::Admin_command_arguments_object::is_end() const { return !(m_error.error == 0 && m_is_object && m_object.fld().size() > m_args_consumed); }