1 /*
2 * Copyright (c) 2015, 2021, Oracle and/or its affiliates.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License, version 2.0,
6 * as published by the Free Software Foundation.
7 *
8 * This program is also distributed with certain software (including
9 * but not limited to OpenSSL) that is licensed under separate terms,
10 * as designated in a particular file or component or in included license
11 * documentation. The authors of MySQL hereby grant you an additional
12 * permission to link the program and your derivative works with the
13 * separately licensed software that they have included with MySQL.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License, version 2.0, for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23 * 02110-1301 USA
24 */
25
26 #include "admin_cmd_handler.h"
27 #include "xpl_error.h"
28 #include "sql_data_context.h"
29 #include "query_string_builder.h"
30 #include "mysql/service_my_snprintf.h"
31 #include "ngs/protocol/row_builder.h"
32 #include "sql_data_result.h"
33 #include "ngs/mysqlx/getter_any.h"
34 #include "sha1.h"
35 #include "password.h"
36
37 #include "mysqlx_resultset.pb.h"
38 #include "mysqlx_datatypes.pb.h"
39 #include "mysqlx_sql.pb.h"
40
41 #include "xpl_regex.h"
42 #include "xpl_session.h"
43 #include "xpl_log.h"
44 #include "xpl_server.h"
45 #include <algorithm>
46
47
48 namespace
49 {
50
51 struct Index_field_traits
52 {
53 bool is_binary;
54 bool unsigned_allowed;
55 bool unquote;
56 bool prefix_len_allowed;
57 std::string v_col_prefix;
58
Index_field_traits__anoneb3f066a0111::Index_field_traits59 Index_field_traits(bool b, bool ua, bool u, bool pa, const std::string &pref)
60 : is_binary(b), unsigned_allowed(ua), unquote(u),
61 prefix_len_allowed(pa), v_col_prefix(pref)
62 {}
63
Index_field_traits__anoneb3f066a0111::Index_field_traits64 Index_field_traits()
65 : is_binary(false), unsigned_allowed(false), unquote(false),
66 prefix_len_allowed(false), v_col_prefix("")
67 {}
68 };
69
70
to_lower(std::string src)71 inline std::string to_lower(std::string src)
72 {
73 std::transform(src.begin(), src.end(), src.begin(), ::tolower);
74 return src;
75 }
76
77 } // namespace
78
79
80 const xpl::Admin_command_handler::Command_handler xpl::Admin_command_handler::m_command_handler;
81
82
Command_handler()83 xpl::Admin_command_handler::Command_handler::Command_handler()
84 {
85 (*this)["ping"] = &Admin_command_handler::ping;
86
87 (*this)["list_clients"] = &Admin_command_handler::list_clients;
88 (*this)["kill_client"] = &Admin_command_handler::kill_client;
89
90 (*this)["create_collection"] = &Admin_command_handler::create_collection;
91 (*this)["drop_collection"] = &Admin_command_handler::drop_collection;
92 (*this)["ensure_collection"] = &Admin_command_handler::ensure_collection;
93
94 (*this)["create_collection_index"] = &Admin_command_handler::create_collection_index;
95 (*this)["drop_collection_index"] = &Admin_command_handler::drop_collection_index;
96
97 (*this)["list_objects"] = &Admin_command_handler::list_objects;
98
99 (*this)["enable_notices"] = &Admin_command_handler::enable_notices;
100 (*this)["disable_notices"] = &Admin_command_handler::disable_notices;
101 (*this)["list_notices"] = &Admin_command_handler::list_notices;
102 }
103
104
execute(Admin_command_handler * admin,const std::string & namespace_,const std::string & command,Command_arguments & args) const105 ngs::Error_code xpl::Admin_command_handler::Command_handler::execute(Admin_command_handler *admin,
106 const std::string &namespace_,
107 const std::string &command,
108 Command_arguments &args) const
109 {
110 const_iterator iter = find(command);
111 if (iter == end())
112 return ngs::Error(ER_X_INVALID_ADMIN_COMMAND, "Invalid %s command %s", namespace_.c_str(), command.c_str());
113
114 try
115 {
116 return (admin->*(iter->second))(args);
117 }
118 catch (std::exception &e)
119 {
120 log_error("Error executing admin command %s: %s", command.c_str(), e.what());
121 return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement");
122 }
123 }
124
125
Admin_command_handler(Session & session)126 xpl::Admin_command_handler::Admin_command_handler(Session &session)
127 : m_session(session), m_da(session.data_context()), m_options(session.options())
128 {}
129
130
execute(const std::string & namespace_,const std::string & command,Command_arguments & args)131 ngs::Error_code xpl::Admin_command_handler::execute(const std::string &namespace_, const std::string &command,
132 Command_arguments &args)
133 {
134 if (m_da.password_expired())
135 return ngs::Error(ER_MUST_CHANGE_PASSWORD,
136 "You must reset your password using ALTER USER statement before executing this statement.");
137
138 if (command.empty())
139 {
140 log_error("Error executing empty admin command");
141 return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement");
142 }
143
144 return m_command_handler.execute(this, namespace_, to_lower(command), args);
145 }
146
147
148
149 /* Stmt: ping
150 * No arguments required
151 */
ping(Command_arguments & args)152 ngs::Error_code xpl::Admin_command_handler::ping(Command_arguments &args)
153 {
154 m_session.update_status<&Common_status_variables::m_stmt_ping>();
155
156 ngs::Error_code error = args.end();
157 if (error)
158 return error;
159
160 m_da.proto().send_exec_ok();
161 return ngs::Success();
162 }
163
164
165 namespace
166 {
167
168 struct Client_data_
169 {
170 uint64_t id;
171 std::string user;
172 std::string host;
173 uint64_t session;
174 bool has_session;
175
Client_data___anoneb3f066a0211::Client_data_176 Client_data_() : id(0), session(0), has_session(false) {}
177 };
178
179
get_client_data(std::vector<Client_data_> & clients_data,xpl::Session & requesting_session,xpl::Sql_data_context & da,ngs::Client_ptr & client)180 void get_client_data(std::vector<Client_data_> &clients_data, xpl::Session &requesting_session,
181 xpl::Sql_data_context &da, ngs::Client_ptr &client)
182 {
183 ngs::shared_ptr<xpl::Session> session(ngs::static_pointer_cast<xpl::Session>(client->session()));
184 Client_data_ c;
185
186 if (session)
187 {
188 const std::string user = session->is_ready() ? session->data_context().get_authenticated_user_name() : "";
189 if (requesting_session.can_see_user(user))
190 {
191 c.id = static_cast<long>(client->client_id_num());
192 c.host = client->client_hostname();
193 if (!user.empty())
194 {
195 c.user = user;
196 c.session = session->data_context().mysql_session_id();
197 c.has_session = true;
198 }
199
200 clients_data.push_back(c);
201 }
202 }
203 else if (da.has_authenticated_user_a_super_priv())
204 {
205 c.id = static_cast<long>(client->client_id_num());
206 c.host = client->client_hostname();
207
208 clients_data.push_back(c);
209 }
210 }
211
212 } // namespace
213
214
215 /* Stmt: list_clients
216 * No arguments required
217 */
list_clients(Command_arguments & args)218 ngs::Error_code xpl::Admin_command_handler::list_clients(Command_arguments &args)
219 {
220 m_session.update_status<&Common_status_variables::m_stmt_list_clients>();
221
222 ngs::Error_code error = args.end();
223 if (error)
224 return error;
225
226 std::vector<Client_data_> clients;
227 {
228 Server::Server_ref server(Server::get_instance());
229 if (server)
230 {
231 Mutex_lock lock((*server)->server().get_client_exit_mutex());
232 std::vector<ngs::Client_ptr> client_list;
233
234 (*server)->server().get_client_list().get_all_clients(client_list);
235
236 clients.reserve(client_list.size());
237
238 std::for_each(client_list.begin(), client_list.end(),
239 ngs::bind(get_client_data, ngs::ref(clients), ngs::ref(m_session), ngs::ref(m_da), ngs::placeholders::_1));
240 }
241 }
242
243 ngs::Protocol_encoder &proto(m_da.proto());
244
245 proto.send_column_metadata("", "", "", "", "client_id", "", 0, Mysqlx::Resultset::ColumnMetaData::UINT, 0, 0, 0);
246 proto.send_column_metadata("", "", "", "", "user", "", 0, Mysqlx::Resultset::ColumnMetaData::BYTES, 0, 0, 0);
247 proto.send_column_metadata("", "", "", "", "host", "", 0, Mysqlx::Resultset::ColumnMetaData::BYTES, 0, 0, 0);
248 proto.send_column_metadata("", "", "", "", "sql_session", "", 0, Mysqlx::Resultset::ColumnMetaData::UINT, 0, 0, 0);
249
250 for (std::vector<Client_data_>::const_iterator it = clients.begin(); it != clients.end(); ++it)
251 {
252 proto.start_row();
253 proto.row_builder().add_longlong_field(it->id, true);
254
255 if (it->user.empty())
256 proto.row_builder().add_null_field();
257 else
258 proto.row_builder().add_string_field(it->user.c_str(), it->user.length(), NULL);
259
260 if (it->host.empty())
261 proto.row_builder().add_null_field();
262 else
263 proto.row_builder().add_string_field(it->host.c_str(), it->host.length(), NULL);
264
265 if (!it->has_session)
266 proto.row_builder().add_null_field();
267 else
268 proto.row_builder().add_longlong_field(it->session, true);
269 proto.send_row();
270 }
271
272 proto.send_result_fetch_done();
273 proto.send_exec_ok();
274
275 return ngs::Success();
276 }
277
278
279 /* Stmt: kill_client
280 * Required arguments:
281 * - id: bigint - the client identification number
282 */
kill_client(Command_arguments & args)283 ngs::Error_code xpl::Admin_command_handler::kill_client(Command_arguments &args)
284 {
285 m_session.update_status<&Common_status_variables::m_stmt_kill_client>();
286
287 uint64_t cid = 0;
288
289 ngs::Error_code error = args.uint_arg("id", cid).end();
290 if (error)
291 return error;
292
293 {
294 xpl::Server::Server_ref server(Server::get_instance());
295 if (server)
296 error = (*server)->kill_client(cid, m_session);
297 }
298 if (error)
299 return error;
300
301 m_da.proto().send_exec_ok();
302
303 return ngs::Success();
304 }
305
306 namespace
307 {
308
create_collection_impl(xpl::Sql_data_context & da,const std::string & schema,const std::string & name)309 ngs::Error_code create_collection_impl(xpl::Sql_data_context &da, const std::string &schema, const std::string &name)
310 {
311 xpl::Query_string_builder qb;
312 qb.put("CREATE TABLE ");
313 if (!schema.empty())
314 qb.quote_identifier(schema).dot();
315 qb.quote_identifier(name)
316 .put(" (doc JSON,"
317 "_id VARCHAR(32) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(doc, '$._id'))) STORED PRIMARY KEY"
318 ") CHARSET utf8mb4 ENGINE=InnoDB;");
319
320 xpl::Sql_data_context::Result_info info;
321 const ngs::PFS_string &tmp(qb.get());
322 log_debug("CreateCollection: %s", tmp.c_str());
323 return da.execute_sql_no_result(tmp.c_str(), tmp.length(), info);
324 }
325
326 } // namespace
327
328
329 /* Stmt: create_collection
330 * Required arguments:
331 * - name: string - name of created collection
332 * - schema: string - name of collection's schema
333 */
create_collection(Command_arguments & args)334 ngs::Error_code xpl::Admin_command_handler::create_collection(Command_arguments &args)
335 {
336 m_session.update_status<&Common_status_variables::m_stmt_create_collection>();
337
338 std::string schema;
339 std::string collection;
340
341 ngs::Error_code error = args.string_arg("schema", schema).string_arg("name", collection).end();
342 if (error)
343 return error;
344
345 if (schema.empty())
346 return ngs::Error_code(ER_X_BAD_SCHEMA, "Invalid schema");
347 if (collection.empty())
348 return ngs::Error_code(ER_X_BAD_TABLE, "Invalid collection name");
349
350 error = create_collection_impl(m_da, schema, collection);
351 if (error)
352 return error;
353 m_da.proto().send_exec_ok();
354 return ngs::Success();
355 }
356
357
358 namespace
359 {
360 /*
361 * valid input examples:
362 * DECIMAL
363 * DECIMAL UNSIGNED
364 * DECIMAL(10)
365 * DECIMAL(10) UNSIGNED
366 * DECIMAL(10,5)
367 * DECIMAL(10,5) UNSIGNED
368 */
parse_type(const std::string & s,std::string & r_type,int & r_arg,int & r_arg2,bool & r_uns)369 bool parse_type(const std::string &s, std::string &r_type, int &r_arg, int &r_arg2, bool &r_uns)
370 {
371 std::string::const_iterator c = s.begin();
372 for (; c != s.end() && isalpha(*c); ++c)
373 r_type.push_back(toupper(*c));
374 if (c != s.end())
375 {
376 int consumed;
377 if (sscanf(s.c_str() + (c - s.begin()), "(%i,%i)%n", &r_arg, &r_arg2, &consumed) == 2)
378 c += consumed;
379 else if (sscanf(s.c_str() + (c - s.begin()), "(%i)%n", &r_arg, &consumed) == 1)
380 c += consumed;
381 // skip potential spaces
382 while (c != s.end() && isspace(*c))
383 c++;
384
385 std::string ident;
386 for (; c != s.end() && isalpha(*c); ++c)
387 ident.push_back(toupper(*c));
388
389 r_uns = false;
390 if (ident == "UNSIGNED")
391 r_uns = true;
392 else
393 {
394 if (!ident.empty())
395 return false;
396 }
397
398 if (c != s.end())
399 return false;
400 }
401 return true;
402 }
403
404
get_type_prefix(const std::string & prefix,int type_arg,int type_arg2,bool is_unsigned,bool required)405 std::string get_type_prefix(const std::string &prefix, int type_arg, int type_arg2, bool is_unsigned, bool required)
406 {
407 std::stringstream result;
408 std::string traits;
409
410 // type
411 result << "ix_" << prefix;
412 if (type_arg > 0)
413 result << type_arg;
414 if (type_arg2 > 0)
415 result << "_" << type_arg2;
416
417 // additional traits (unsigned, required, ...)
418 if (is_unsigned)
419 traits += "u";
420 if (required)
421 traits += "r";
422 if (!traits.empty())
423 result << "_" << traits;
424
425 result << "_";
426
427 return result.str();
428 }
429
430
431 typedef std::list<std::vector<std::string> > String_fields_values;
432
433
query_string_columns(xpl::Sql_data_context & da,const ngs::PFS_string & sql,std::vector<unsigned> & field_idxs,String_fields_values & ret_values)434 ngs::Error_code query_string_columns(xpl::Sql_data_context &da, const ngs::PFS_string &sql,
435 std::vector<unsigned> &field_idxs, String_fields_values &ret_values)
436 {
437 xpl::Buffering_command_delegate::Resultset r_rows;
438 std::vector<xpl::Command_delegate::Field_type> r_types;
439 xpl::Sql_data_context::Result_info r_info;
440
441 ngs::Error_code err = da.execute_sql_and_collect_results(sql.data(), sql.length(), r_types, r_rows, r_info);
442 if (err)
443 return err;
444
445 ret_values.clear();
446 size_t fields_number = field_idxs.size();
447 xpl::Buffering_command_delegate::Resultset::iterator it = r_rows.begin();
448 for (; it != r_rows.end(); ++it)
449 {
450 ret_values.push_back(std::vector<std::string>(fields_number));
451 for (size_t i = 0; i < field_idxs.size(); ++i)
452 {
453 unsigned field_idx = field_idxs[i];
454
455 xpl::Buffering_command_delegate::Row_data *row_data = &(*it);
456 if ((!row_data) || (row_data->fields.size() <= field_idx))
457 {
458 log_error("query_string_columns failed: invalid row data");
459 return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement");
460 }
461
462 xpl::Buffering_command_delegate::Field_value *field = row_data->fields[field_idx];
463 if (!field)
464 {
465 log_error("query_string_columns failed: missing row data");
466 return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement");
467 }
468
469 if (MYSQL_TYPE_VARCHAR != r_types[field_idx].type &&
470 MYSQL_TYPE_STRING != r_types[field_idx].type &&
471 MYSQL_TYPE_TINY_BLOB != r_types[field_idx].type &&
472 MYSQL_TYPE_MEDIUM_BLOB != r_types[field_idx].type &&
473 MYSQL_TYPE_LONG_BLOB != r_types[field_idx].type &&
474 MYSQL_TYPE_BLOB != r_types[field_idx].type &&
475 MYSQL_TYPE_VAR_STRING != r_types[field_idx].type)
476 {
477 log_error("query_string_columns failed: invalid field type");
478 return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement");
479 }
480
481 ret_values.back()[i] = *field->value.v_string;
482 }
483 }
484
485 return ngs::Success();
486 }
487
488
name_is(const std::vector<std::string> & field,const std::string & name)489 bool name_is(const std::vector<std::string> &field, const std::string &name)
490 {
491 return field[0] == name;
492 }
493
494
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)495 ngs::Error_code remove_nonvirtual_column_names(const std::string &schema_name, const std::string &table_name,
496 String_fields_values &ret_column_names, xpl::Sql_data_context &da)
497 {
498 xpl::Query_string_builder qb;
499 const unsigned FIELD_COLMN_IDX = 0;
500 const unsigned EXTRA_COLMN_IDX = 5;
501
502 if (ret_column_names.size() == 0)
503 return ngs::Success();
504
505 qb.put("SHOW COLUMNS FROM ")
506 .quote_identifier(schema_name).dot().quote_identifier(table_name)
507 .put(" WHERE Field IN (");
508 String_fields_values::const_iterator it_columns = ret_column_names.begin();
509 for (;;)
510 {
511 qb.quote_string((*it_columns)[0]);
512 if (++it_columns != ret_column_names.end())
513 qb.put(",");
514 else
515 break;
516 }
517 qb.put(")");
518
519 std::vector<unsigned> fields_ids(2);
520 fields_ids[0] = FIELD_COLMN_IDX;
521 fields_ids[1] = EXTRA_COLMN_IDX;
522 String_fields_values column_descs;
523
524 ngs::Error_code error = query_string_columns(da, qb.get(), fields_ids, column_descs);
525 if (error)
526 return error;
527
528 String_fields_values::const_iterator it_field = column_descs.begin();
529 for (; it_field != column_descs.end(); ++it_field)
530 {
531 std::string column_name = (*it_field)[0];
532 std::string column_desc = (*it_field)[1];
533 if (!(column_desc.find("VIRTUAL GENERATED") != std::string::npos))
534 ret_column_names.remove_if(ngs::bind(name_is, ngs::placeholders::_1, column_name));
535 }
536
537 return ngs::Success();
538 }
539
540
index_on_virtual_column_supported(const std::string & schema_name,const std::string & table_name,xpl::Sql_data_context & da,bool & r_supports)541 ngs::Error_code index_on_virtual_column_supported(const std::string &schema_name, const std::string &table_name,
542 xpl::Sql_data_context &da, bool &r_supports)
543 {
544 const unsigned CREATE_COLMN_IDX = 1;
545 xpl::Query_string_builder qb;
546 std::vector<unsigned> fields_ids(1);
547 fields_ids[0] = CREATE_COLMN_IDX;
548 String_fields_values create_stmts;
549
550 qb.put("SHOW CREATE TABLE ").quote_identifier(schema_name).dot().quote_identifier(table_name);
551 ngs::Error_code error = query_string_columns(da, qb.get(), fields_ids, create_stmts);
552 if (error)
553 return error;
554
555 // if query didn't fail it should return 1 row
556 if (create_stmts.size() != 1)
557 {
558 const unsigned int num_of_rows = static_cast<unsigned int>(create_stmts.size());
559 log_error("index_on_virtual_column_supported() failed: wrong number of rows: %u", num_of_rows);
560 return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement");
561 }
562
563 String_fields_values::const_iterator it_create_stmt = create_stmts.begin();
564 std::string create_stmt = (*it_create_stmt)[0];
565 size_t pos = create_stmt.find("ENGINE=");
566 if (pos == std::string::npos)
567 {
568 log_error("index_on_virtual_column_supported() failed: no engine info: %s", create_stmt.c_str());
569 return ngs::Error(ER_INTERNAL_ERROR, "Error executing statement");
570 }
571 std::string engine;
572 std::string::const_iterator ci = create_stmt.begin() + pos + strlen("ENGINE=");
573 for (; ci != create_stmt.end() && isalpha(*ci); ++ci)
574 engine.push_back(*ci);
575
576 // currently only InnoDB supports VIRTUAL GENERATED columns
577 r_supports = (engine == "InnoDB");
578
579 return ngs::Success();
580 }
581
582
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)583 bool table_column_exists(const std::string &schema_name, const std::string &table_name,
584 const std::string &column_name, xpl::Sql_data_context &da, bool &r_exists)
585 {
586 xpl::Query_string_builder qb;
587 xpl::Buffering_command_delegate::Resultset r_rows;
588 std::vector<xpl::Command_delegate::Field_type> r_types;
589 xpl::Sql_data_context::Result_info r_info;
590
591 qb.put("SHOW COLUMNS FROM ")
592 .quote_identifier(schema_name).dot().quote_identifier(table_name)
593 .put(" WHERE Field = ").quote_string(column_name);
594
595 ngs::Error_code err = da.execute_sql_and_collect_results(qb.get().data(), qb.get().length(), r_types, r_rows, r_info);
596 if (err)
597 return false;
598
599 r_exists = r_rows.size() > 0;
600 return true;
601 }
602
603
hash_column_name(const std::string & name)604 std::string hash_column_name(const std::string &name)
605 {
606 std::string hash;
607 hash.resize(2*SHA1_HASH_SIZE + 2);
608 // just an arbitrary hash
609 ::make_scrambled_password(&hash[0], name.c_str());
610 hash.resize(2*SHA1_HASH_SIZE + 1); // strip the \0
611 return hash.substr(1); // skip the 1st char
612 }
613
614 } // namespace
615
616
617 /* Stmt: create_collection_index
618 * Required arguments:
619 * - name: string - name of index
620 * - collection: string - name of indexed collection
621 * - schema: string - name of collection's schema
622 * - unique: bool - whether the index should be a unique index
623 * - constraint: object, list - detailed information for the generated column
624 * - member: string - path to document member for which the index will be created
625 * - required: bool - whether the generated column will be created as NOT NULL
626 * - type: string - data type of the created index
627 *
628 * VARCHAR and CHAR are now indexable because:
629 * - varchar column needs to be created with a length, which would limit documents to have
630 * that field smaller than that
631 * - if we use left() to truncate the value of the column, then the index won't be usable unless
632 * queries also specify left(), which is not desired.
633 */
create_collection_index(Command_arguments & args)634 ngs::Error_code xpl::Admin_command_handler::create_collection_index(Command_arguments &args)
635 {
636 m_session.update_status<&Common_status_variables::m_stmt_create_collection_index>();
637
638 Query_string_builder qb;
639 bool required = false;
640 typedef Index_field_traits _T;
641 static std::map<std::string, _T> valid_types;
642
643 static struct Valid_type_init
644 {
645 Valid_type_init()
646 {
647 // binary unsigned unqote prefix_len column_prefix
648 valid_types["TINYINT"] = _T(false, true, false, false, "it");
649 valid_types["SMALLINT"] = _T(false, true, false, false, "is");
650 valid_types["MEDIUMINT"] = _T(false, true, false, false, "im");
651 valid_types["INT"] = _T(false, true, false, false, "i");
652 valid_types["INTEGER"] = _T(false, true, false, false, "i");
653 valid_types["BIGINT"] = _T(false, true, false, false, "ib");
654 valid_types["REAL"] = _T(false, true, false, false, "fr");
655 valid_types["FLOAT"] = _T(false, true, false, false, "f");
656 valid_types["DOUBLE"] = _T(false, true, false, false, "fd");
657 valid_types["DECIMAL"] = _T(false, true, false, false, "xd");
658 valid_types["NUMERIC"] = _T(false, true, false, false, "xn");
659 valid_types["DATE"] = _T(false, false, true, false, "d");
660 valid_types["TIME"] = _T(false, false, true, false, "dt");
661 valid_types["TIMESTAMP"] = _T(false, false, true, false, "ds");
662 valid_types["DATETIME"] = _T(false, false, true, false, "dd");
663 valid_types["YEAR"] = _T(false, false, true, false, "dy");
664 valid_types["BIT"] = _T(false, false, true, true, "t");
665 valid_types["BLOB"] = _T(true, false, true, true, "bt");
666 valid_types["TEXT"] = _T(true, false, true, true, "t");
667 }
668 } _type_init;
669
670 std::string schema;
671 std::string collection;
672 std::string index_name;
673 bool unique = false;
674 std::vector<Command_arguments*> constraints;
675
676 ngs::Error_code error = args.string_arg("schema", schema)
677 .string_arg("collection", collection)
678 .string_arg("name", index_name)
679 .bool_arg("unique", unique)
680 .object_list("constraint", constraints)
681 .error();
682 if (error)
683 return error;
684
685 std::vector<std::string> col_field_path;
686 std::vector<std::string> col_raw_type;
687 std::vector<bool> col_required;
688 bool column_exists = false;
689 typedef std::vector<Command_arguments*>::iterator It;
690 for (It i = constraints.begin(); i != constraints.end(); ++i)
691 {
692 std::string f, t;
693 bool r = false;
694 error = (*i)->docpath_arg("member", f)
695 .string_arg("type", t)
696 .bool_arg("required", r)
697 .error();
698 if (error)
699 return error;
700 if (f.empty())
701 return ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Argument value '%s' for document member is invalid", f.c_str());
702 col_field_path.push_back(f);
703 col_raw_type.push_back(t);
704 col_required.push_back(r);
705 required = required || r;
706 }
707 error = args.end();
708 if (error)
709 return error;
710
711 if (schema.empty())
712 return ngs::Error(ER_X_BAD_SCHEMA, "Invalid schema '%s'", schema.c_str());
713 if (collection.empty())
714 return ngs::Error(ER_X_BAD_TABLE, "Invalid collection name '%s'", collection.c_str());
715 if (index_name.empty())
716 return ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Argument value '%s' for index name is invalid", index_name.c_str());
717
718 // check if the table's engine supports index on the virtual column
719 bool virtual_supported = false;
720 error = index_on_virtual_column_supported(schema, collection, m_da, virtual_supported);
721 if (error)
722 {
723 if (error.error == ER_INTERNAL_ERROR)
724 return error;
725 else
726 // if it is not internal then the reason is bad schema or table name
727 return ngs::Error(ER_X_BAD_TABLE, "Invalid collection name: %s.%s", schema.c_str(), collection.c_str());
728 }
729 std::string column_type = virtual_supported ? "VIRTUAL" : "STORED";
730
731 std::vector<std::pair<std::string, std::string> > columns;
732
733 // NOTE: This could be done more efficiently with ALGORIHM=INPLACE but:
734 // - currently server does not support adding virtual columns to the table inplace combined with
735 // other ALTER TABLE statement (adding index in this case)
736 // - attempt to split adding the index and adding the virtual columns into 2 separate statements
737 // leads to the server crash ("Bug 21640846 ASSERTION FAILURE WHEN CREATING VIRTUAL COLUMN.")
738
739 // generate DDL
740 qb.put("ALTER TABLE ").quote_identifier(schema).dot().quote_identifier(collection);
741 for (size_t c = col_field_path.size(), i = 0; i < c; i++)
742 {
743 std::string column_name;
744 std::string type_name;
745 int type_arg = -1, type_arg2 = -1;
746 bool is_unsigned = false;
747 // validate the type
748 if (!col_raw_type[i].empty())
749 {
750 if (!parse_type(col_raw_type[i], type_name, type_arg, type_arg2, is_unsigned)
751 || (valid_types.find(type_name) == valid_types.end())
752 || (is_unsigned && !valid_types[type_name].unsigned_allowed))
753 return ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid or unsupported type specification '%s'", col_raw_type[i].c_str());
754 }
755 else
756 {
757 type_name = "TEXT";
758 type_arg = 64;
759 }
760
761 std::string required = col_required[i] ? "NOT NULL" : "";
762
763 column_name = '$' + get_type_prefix(valid_types[type_name].v_col_prefix, type_arg, type_arg2, is_unsigned, !required.empty())
764 + hash_column_name(col_field_path[i].substr(2));
765
766 // if column with given name already exists just skip adding it and use it for the index
767 if (!table_column_exists(schema, collection, column_name, m_da, column_exists))
768 return ngs::Error(ER_X_BAD_TABLE, "Invalid collection name: %s.%s", schema.c_str(), collection.c_str());
769
770 std::stringstream column_index_size;
771 if (type_arg > 0)
772 {
773 column_index_size << "(" << type_arg;
774 if (type_arg2 > 0)
775 column_index_size << ", " << type_arg2;
776 column_index_size << ")";
777 }
778
779 if (!column_exists)
780 {
781 std::string extract_begin, extract_end;
782 if (valid_types[type_name].unquote)
783 {
784 extract_begin = "JSON_UNQUOTE(";
785 extract_end = ")";
786 }
787
788 qb.put(" ADD COLUMN ").quote_identifier(column_name).put(" ").put(type_name);
789
790 if (type_name != "TEXT")
791 qb.put(column_index_size.str());
792
793 if (is_unsigned)
794 qb.put(" UNSIGNED");
795
796 qb.put(" GENERATED ALWAYS AS (").put(extract_begin).put("JSON_EXTRACT(doc, ")
797 .quote_string(col_field_path[i]).put(")").put(extract_end).put(") ")
798 .put(column_type).put(" ").put(required).put(",");
799 }
800 columns.push_back(std::make_pair(column_name,
801 valid_types[type_name].is_binary ? column_index_size.str() : ""));
802 }
803
804 qb.put(unique ? " ADD UNIQUE INDEX " : " ADD INDEX ")
805 .quote_identifier(index_name).put(" (");
806
807 std::vector<std::pair<std::string, std::string> >::const_iterator it = columns.begin();
808 for (; it != columns.end(); ++it)
809 {
810 if (it != columns.begin())
811 qb.put(",");
812 qb.quote_identifier(it->first).put(it->second);
813 }
814 qb.put(")");
815
816 Sql_data_context::Result_info info;
817 const ngs::PFS_string &tmp(qb.get());
818 log_debug("CreateCollectionIndex: %s", tmp.c_str());
819 error = m_da.execute_sql_no_result(tmp.data(), tmp.length(), info);
820 if (error)
821 {
822 // if we're creating a NOT NULL generated index/column and get a NULL error, it's
823 // because one of the existing documents had a NULL / unset value
824 if (error.error == ER_BAD_NULL_ERROR && required)
825 return ngs::Error_code(ER_X_DOC_REQUIRED_FIELD_MISSING, "Collection contains document missing required field");
826 return error;
827 }
828 m_da.proto().send_exec_ok();
829 return ngs::Success();
830 }
831
832
833 /* Stmt: drop_collection
834 * Required arguments:
835 * - name: string - name of dropped collection
836 * - schema: string - name of collection's schema
837 */
drop_collection(Command_arguments & args)838 ngs::Error_code xpl::Admin_command_handler::drop_collection(Command_arguments &args)
839 {
840 m_session.update_status<&Common_status_variables::m_stmt_drop_collection>();
841
842 Query_string_builder qb;
843 std::string schema;
844 std::string collection;
845
846 ngs::Error_code error = args.string_arg("schema", schema).string_arg("name", collection).end();
847 if (error)
848 return error;
849
850 if (schema.empty())
851 return ngs::Error_code(ER_X_BAD_SCHEMA, "Invalid schema");
852 if (collection.empty())
853 return ngs::Error_code(ER_X_BAD_TABLE, "Invalid collection name");
854
855 qb.put("DROP TABLE ").quote_identifier(schema).dot().quote_identifier(collection);
856
857 const ngs::PFS_string &tmp(qb.get());
858 log_debug("DropCollection: %s", tmp.c_str());
859 Sql_data_context::Result_info info;
860 error = m_da.execute_sql_no_result(tmp.data(), tmp.length(), info);
861 if (error)
862 return error;
863 m_da.proto().send_exec_ok();
864
865 return ngs::Success();
866 }
867
868
869 namespace
870 {
871
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)872 ngs::Error_code get_index_virtual_column_names(const std::string &schema_name, const std::string &table_name, const std::string &index_name,
873 xpl::Sql_data_context &da, String_fields_values &ret_column_names)
874 {
875 const unsigned INDEX_NAME_COLUMN_IDX = 4;
876 xpl::Query_string_builder qb;
877
878 /* get list of all index column names */
879 qb.put("SHOW INDEX FROM ")
880 .quote_identifier(schema_name).dot().quote_identifier(table_name)
881 .put(" WHERE Key_name = ").quote_string(index_name);
882
883 std::vector<unsigned> fields_ids(1);
884 fields_ids[0] = INDEX_NAME_COLUMN_IDX;
885 ngs::Error_code error = query_string_columns(da, qb.get(), fields_ids, ret_column_names);
886 if (error)
887 return error;
888
889 /* remove from the list columns that shouldn't be dropped */
890
891 /* don't drop non-virtual columns */
892 error = remove_nonvirtual_column_names(schema_name, table_name, ret_column_names, da);
893 if (error)
894 return error;
895
896 xpl::Buffering_command_delegate::Resultset r_rows;
897 std::vector<xpl::Command_delegate::Field_type> r_types;
898 xpl::Sql_data_context::Result_info r_info;
899 String_fields_values::iterator it = ret_column_names.begin();
900 while (it != ret_column_names.end())
901 {
902 /* don't drop '_id' column */
903 if ((*it)[0] == "_id")
904 {
905 ret_column_names.erase(it++);
906 continue;
907 }
908
909 /* don't drop columns used by other index(es) */
910 qb.clear();
911 qb.put("SHOW INDEX FROM ")
912 .quote_identifier(schema_name).dot().quote_identifier(table_name)
913 .put(" WHERE Key_name <> ").quote_string(index_name)
914 .put(" AND Column_name = ").quote_string((*it)[0]);
915 da.execute_sql_and_collect_results(qb.get().data(), qb.get().length(), r_types, r_rows, r_info);
916 if (r_rows.size() > 0)
917 {
918 ret_column_names.erase(it++);
919 continue;
920 }
921 ++it;
922 }
923
924 return ngs::Success();
925 }
926
927 } // namespace
928
929
930 /* Stmt: drop_collection_index
931 * Required arguments:
932 * - name: string - name of dropped index
933 * - collection: string - name of collection with dropped index
934 * - schema: string - name of collection's schema
935 */
drop_collection_index(Command_arguments & args)936 ngs::Error_code xpl::Admin_command_handler::drop_collection_index(Command_arguments &args)
937 {
938 m_session.update_status<&Common_status_variables::m_stmt_drop_collection_index>();
939
940 Query_string_builder qb;
941 std::string schema;
942 std::string collection;
943 std::string name;
944
945 ngs::Error_code error = args
946 .string_arg("schema", schema)
947 .string_arg("collection", collection)
948 .string_arg("name", name).end();
949 if (error)
950 return error;
951
952 if (schema.empty())
953 return ngs::Error_code(ER_X_BAD_SCHEMA, "Invalid schema");
954 if (collection.empty())
955 return ngs::Error_code(ER_X_BAD_TABLE, "Invalid collection name");
956 if (name.empty())
957 return ngs::Error_code(ER_X_MISSING_ARGUMENT, "Invalid index name");
958
959 String_fields_values column_names;
960
961 // collect the index columns (if any) to be dropped
962 error = get_index_virtual_column_names(schema, collection, name, m_da, column_names);
963 if (error)
964 {
965 if (error.error == ER_INTERNAL_ERROR)
966 return error;
967 else
968 // if it is not internal then the reason is bad schema or table name
969 return ngs::Error(ER_X_BAD_TABLE, "Invalid collection name: %s.%s", schema.c_str(), collection.c_str());
970 }
971
972 // drop the index
973 qb.put("ALTER TABLE ").quote_identifier(schema).dot().quote_identifier(collection)
974 .put(" DROP INDEX ").quote_identifier(name);
975
976 // drop the index's virtual columns
977 String_fields_values::const_iterator it = column_names.begin();
978 for (; it != column_names.end(); ++it)
979 {
980 qb.put(", DROP COLUMN ").quote_identifier((*it)[0]);
981 }
982
983 const ngs::PFS_string &tmp(qb.get());
984 log_debug("DropCollectionIndex: %s", tmp.c_str());
985 Sql_data_context::Result_info info;
986 error = m_da.execute_sql_no_result(tmp.data(), tmp.length(), info);
987 if (error)
988 return error;
989
990 m_da.proto().send_exec_ok();
991 return ngs::Success();
992 }
993
994
995 namespace
996 {
997
998 static const char* const fixed_notice_names[] = {
999 "account_expired",
1000 "generated_insert_id",
1001 "rows_affected",
1002 "produced_message"
1003 };
1004 static const char* const *fixed_notice_names_end = &fixed_notice_names[0] + sizeof(fixed_notice_names) / sizeof(fixed_notice_names[0]);
1005
1006
is_fixed_notice_name(const std::string & notice)1007 inline bool is_fixed_notice_name(const std::string ¬ice)
1008 {
1009 return std::find(fixed_notice_names, fixed_notice_names_end, notice) != fixed_notice_names_end;
1010 }
1011
1012
add_notice_row(xpl::Sql_data_context & da,const std::string & notice,longlong status)1013 inline void add_notice_row(xpl::Sql_data_context &da, const std::string ¬ice, longlong status)
1014 {
1015 da.proto().start_row();
1016 da.proto().row_builder().add_string_field(notice.c_str(), notice.length(), NULL);
1017 da.proto().row_builder().add_longlong_field(status, 0);
1018 da.proto().send_row();
1019 }
1020
1021 } // namespace
1022
1023
1024 /* Stmt: enable_notices
1025 * Required arguments:
1026 * - notice: string, list - name (or names) of enabled notice
1027 */
enable_notices(Command_arguments & args)1028 ngs::Error_code xpl::Admin_command_handler::enable_notices(Command_arguments &args)
1029 {
1030 m_session.update_status<&Common_status_variables::m_stmt_enable_notices>();
1031
1032 std::vector<std::string> notices;
1033 ngs::Error_code error = args.string_list("notice", notices).end();
1034 if (error)
1035 return error;
1036
1037 bool enable_warnings = false;
1038 for (std::vector<std::string>::const_iterator i = notices.begin(); i != notices.end(); ++i)
1039 {
1040 if (*i == "warnings")
1041 enable_warnings = true;
1042 else if (!is_fixed_notice_name(*i))
1043 return ngs::Error(ER_X_BAD_NOTICE, "Invalid notice name %s", i->c_str());
1044 }
1045
1046 if (enable_warnings)
1047 m_options.set_send_warnings(true);
1048
1049 m_da.proto().send_exec_ok();
1050 return ngs::Success();
1051 }
1052
1053
1054 /* Stmt: disable_notices
1055 * Required arguments:
1056 * - notice: string, list - name (or names) of enabled notice
1057 */
disable_notices(Command_arguments & args)1058 ngs::Error_code xpl::Admin_command_handler::disable_notices(Command_arguments &args)
1059 {
1060 m_session.update_status<&Common_status_variables::m_stmt_disable_notices>();
1061
1062 std::vector<std::string> notices;
1063 ngs::Error_code error = args.string_list("notice", notices).end();
1064 if (error)
1065 return error;
1066
1067 bool disable_warnings = false;
1068 for (std::vector<std::string>::const_iterator i = notices.begin(); i != notices.end(); ++i)
1069 {
1070 if (*i == "warnings")
1071 disable_warnings = true;
1072 else if (is_fixed_notice_name(*i))
1073 return ngs::Error(ER_X_CANNOT_DISABLE_NOTICE, "Cannot disable notice %s", i->c_str());
1074 else
1075 return ngs::Error(ER_X_BAD_NOTICE, "Invalid notice name %s", i->c_str());
1076 }
1077
1078 if (disable_warnings)
1079 m_options.set_send_warnings(false);
1080
1081 m_da.proto().send_exec_ok();
1082 return ngs::Success();
1083 }
1084
1085
1086 /* Stmt: list_notices
1087 * No arguments required
1088 */
list_notices(Command_arguments & args)1089 ngs::Error_code xpl::Admin_command_handler::list_notices(Command_arguments &args)
1090 {
1091 m_session.update_status<&Common_status_variables::m_stmt_list_notices>();
1092
1093 ngs::Error_code error = args.end();
1094 if (error)
1095 return error;
1096
1097 // notice | enabled
1098 // <name> | <1/0>
1099
1100 m_da.proto().send_column_metadata("", "", "", "", "notice", "", 0, Mysqlx::Resultset::ColumnMetaData::BYTES, 0, 0, 0);
1101 m_da.proto().send_column_metadata("", "", "", "", "enabled", "", 0, Mysqlx::Resultset::ColumnMetaData::SINT, 0, 0, 0);
1102
1103 add_notice_row(m_da, "warnings", m_options.get_send_warnings() ? 1 : 0);
1104 for (const char* const *notice = fixed_notice_names; notice < fixed_notice_names_end; ++notice)
1105 add_notice_row(m_da, *notice, 1);
1106
1107 m_da.proto().send_result_fetch_done();
1108 m_da.proto().send_exec_ok();
1109 return ngs::Success();
1110 }
1111
1112
1113 namespace
1114 {
is_schema_selected_and_exists(xpl::Sql_data_context & da,const std::string & schema)1115 ngs::Error_code is_schema_selected_and_exists(xpl::Sql_data_context &da, const std::string &schema)
1116 {
1117 xpl::Query_string_builder qb;
1118 qb.put("SHOW TABLES");
1119 if (!schema.empty())
1120 qb.put(" FROM ").quote_identifier(schema);
1121
1122 xpl::Sql_data_context::Result_info info;
1123 return da.execute_sql_no_result(qb.get().data(), qb.get().length(), info);
1124 }
1125
1126 template<typename T>
get_system_variable(xpl::Sql_data_context & da,const std::string & variable)1127 T get_system_variable(xpl::Sql_data_context &da, const std::string &variable)
1128 {
1129 xpl::Sql_data_result result(da);
1130 try
1131 {
1132 result.query(("SELECT @@" + variable).c_str());
1133 if (result.size() != 1)
1134 {
1135 log_error("Unable to retrieve system variable '%s'", variable.c_str());
1136 return T();
1137 }
1138 T value = T();
1139 result.get(value);
1140 return value;
1141 }
1142 catch (const ngs::Error_code &)
1143 {
1144 log_error("Unable to retrieve system variable '%s'", variable.c_str());
1145 return T();
1146 }
1147 }
1148
1149 const char *const COUNT_DOC =
1150 "COUNT(CASE WHEN (column_name = 'doc' "
1151 "AND data_type = 'json') THEN 1 ELSE NULL END)";
1152 const char *const COUNT_ID =
1153 "COUNT(CASE WHEN (column_name = '_id' "
1154 "AND generation_expression = "
1155 "'json_unquote(json_extract(`doc`,''$._id''))') THEN 1 ELSE NULL END)";
1156 const char *const COUNT_GEN =
1157 "COUNT(CASE WHEN (column_name != '_id' "
1158 "AND generation_expression RLIKE "
1159 "'^(json_unquote[[.(.]])?json_extract[[.(.]]`doc`,"
1160 "''[[.$.]]([[...]][^[:space:][...]]+)+''[[.).]]{1,2}$') THEN 1 ELSE NULL "
1161 "END)";
1162 } // namespace
1163
1164 /* Stmt: list_objects
1165 * Required arguments:
1166 * - schema: string, optional - name of listed object's schema
1167 * - pattern: string, optional - a filter to use for matching object names to be returned
1168 */
list_objects(Command_arguments & args)1169 ngs::Error_code xpl::Admin_command_handler::list_objects(Command_arguments &args)
1170 {
1171 m_session.update_status<&Common_status_variables::m_stmt_list_objects>();
1172
1173 static const bool is_table_names_case_sensitive =
1174 get_system_variable<long>(m_da, "lower_case_table_names") == 0l;
1175
1176 static const char *const BINARY_OPERATOR =
1177 is_table_names_case_sensitive &&
1178 get_system_variable<long>(m_da, "lower_case_file_system") == 0l
1179 ? "BINARY "
1180 : "";
1181
1182 std::string schema, pattern;
1183 ngs::Error_code error = args
1184 .string_arg("schema", schema, true)
1185 .string_arg("pattern", pattern, true).end();
1186 if (error)
1187 return error;
1188
1189 if (!is_table_names_case_sensitive) schema = to_lower(schema);
1190
1191 error = is_schema_selected_and_exists(m_da, schema);
1192 if (error)
1193 return error;
1194
1195 Query_string_builder qb;
1196 qb.put("SELECT ")
1197 .put(BINARY_OPERATOR)
1198 .put("T.table_name AS name, "
1199 "IF(ANY_VALUE(T.table_type) LIKE '%VIEW', "
1200 "IF(COUNT(*)=1 AND ")
1201 .put(COUNT_DOC)
1202 .put("=1, 'COLLECTION_VIEW', 'VIEW'), IF(COUNT(*)-2 = ")
1203 .put(COUNT_GEN)
1204 .put(" AND ")
1205 .put(COUNT_DOC)
1206 .put("=1 AND ")
1207 .put(COUNT_ID)
1208 .put("=1, 'COLLECTION', 'TABLE')) AS type "
1209 "FROM information_schema.tables AS T "
1210 "LEFT JOIN information_schema.columns AS C ON (")
1211 .put(BINARY_OPERATOR)
1212 .put("T.table_schema = C.table_schema AND ")
1213 .put(BINARY_OPERATOR)
1214 .put("T.table_name = C.table_name) "
1215 "WHERE T.table_schema = ");
1216 if (schema.empty())
1217 qb.put("schema()");
1218 else
1219 qb.quote_string(schema);
1220 if (!pattern.empty())
1221 qb.put(" AND T.table_name LIKE ").quote_string(pattern);
1222 qb.put(" GROUP BY name ORDER BY name");
1223
1224 Sql_data_context::Result_info info;
1225 error = m_da.execute_sql_and_stream_results(qb.get().data(),
1226 qb.get().length(), false, info);
1227 if (error)
1228 return error;
1229
1230 m_da.proto().send_exec_ok();
1231 return ngs::Success();
1232 }
1233
1234
1235 namespace
1236 {
is_collection(xpl::Sql_data_context & da,const std::string & schema,const std::string & name)1237 bool is_collection(xpl::Sql_data_context &da, const std::string &schema, const std::string &name)
1238 {
1239 xpl::Query_string_builder qb;
1240 qb.put("SELECT COUNT(*) AS cnt,")
1241 .put(COUNT_DOC).put(" AS doc,").put(COUNT_ID).put(" AS id,").put(COUNT_GEN).put(" AS gen "
1242 "FROM information_schema.columns "
1243 "WHERE table_name = ").quote_string(name).put(" AND table_schema = ");
1244 if (schema.empty())
1245 qb.put("schema()");
1246 else
1247 qb.quote_string(schema);
1248
1249 xpl::Sql_data_result result(da);
1250 try
1251 {
1252 result.query(qb.get());
1253 if (result.size() != 1)
1254 {
1255 log_debug("Unable to recognize '%s' as a collection; query result size: %lu",
1256 std::string(schema.empty() ? name : schema + "." + name).c_str(),
1257 static_cast<unsigned long>(result.size()));
1258 return false;
1259 }
1260 long int cnt = 0, doc = 0, id = 0, gen = 0;
1261 result.get(cnt).get(doc).get(id).get(gen);
1262 return doc == 1 && id == 1 && (cnt == gen + doc + id);
1263 }
1264 #if defined(XPLUGIN_LOG_DEBUG) && !defined(XPLUGIN_DISABLE_LOG)
1265 catch (const ngs::Error_code &e)
1266 #else
1267 catch (const ngs::Error_code &)
1268 #endif
1269 {
1270 log_debug("Unable to recognize '%s' as a collection; exception message: '%s'",
1271 std::string(schema.empty() ? name : schema + "." + name).c_str(), e.message.c_str());
1272 return false;
1273 }
1274 }
1275
1276 } // namespace
1277
1278
1279 /* Stmt: ensure_collection
1280 * Required arguments:
1281 * - name: string - name of created collection
1282 * - schema: string, optional - name of collection's schema
1283 */
ensure_collection(Command_arguments & args)1284 ngs::Error_code xpl::Admin_command_handler::ensure_collection(Command_arguments &args)
1285 {
1286 m_session.update_status<&Common_status_variables::m_stmt_ensure_collection>();
1287 std::string schema;
1288 std::string collection;
1289
1290 ngs::Error_code error = args.string_arg("schema", schema, true).string_arg("name", collection).end();
1291 if (error)
1292 return error;
1293
1294 if (collection.empty())
1295 return ngs::Error_code(ER_X_BAD_TABLE, "Invalid collection name");
1296
1297 error = create_collection_impl(m_da, schema, collection);
1298 if (error)
1299 {
1300 if (error.error != ER_TABLE_EXISTS_ERROR)
1301 return error;
1302 if (!is_collection(m_da, schema, collection))
1303 return ngs::Error(ER_X_INVALID_COLLECTION,
1304 "Table '%s' exists but is not a collection",
1305 (schema.empty() ? collection : schema + '.' + collection).c_str());
1306 }
1307 m_da.proto().send_exec_ok();
1308 return ngs::Success();
1309 }
1310
1311
1312 const char* const xpl::Admin_command_handler::Command_arguments::PLACEHOLDER = "?";
1313
1314
Admin_command_arguments_list(const List & args)1315 xpl::Admin_command_arguments_list::Admin_command_arguments_list(const List &args)
1316 : m_args(args), m_current(m_args.begin()), m_args_consumed(0)
1317 {}
1318
1319
string_arg(const char * name,std::string & ret_value,bool optional)1320 xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::string_arg(const char *name, std::string &ret_value, bool optional)
1321 {
1322 if (check_scalar_arg(name, Mysqlx::Datatypes::Scalar::V_STRING, "string", optional))
1323 {
1324 const std::string &value = m_current->scalar().v_string().value();
1325 if (memchr(value.data(), 0, value.length()))
1326 {
1327 m_error = ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid value for argument '%s'", name);
1328 return *this;
1329 }
1330 ret_value = value;
1331 ++m_current;
1332 }
1333 return *this;
1334 }
1335
1336
string_list(const char * name,std::vector<std::string> & ret_value,bool optional)1337 xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::string_list(const char *name, std::vector<std::string> &ret_value, bool optional)
1338 {
1339 std::string value;
1340 do
1341 {
1342 string_arg(name, value, optional);
1343 ret_value.push_back(value);
1344 value.clear();
1345 }
1346 while (!is_end());
1347 return *this;
1348 }
1349
1350
sint_arg(const char * name,int64_t & ret_value,bool optional)1351 xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::sint_arg(const char *name, int64_t &ret_value, bool optional)
1352 {
1353 if (check_scalar_arg(name, Mysqlx::Datatypes::Scalar::V_SINT, "signed int", optional))
1354 {
1355 if (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_UINT)
1356 ret_value = (int64_t)m_current->scalar().v_unsigned_int();
1357 else if (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_SINT)
1358 ret_value = m_current->scalar().v_signed_int();
1359 ++m_current;
1360 }
1361 return *this;
1362 }
1363
1364
uint_arg(const char * name,uint64_t & ret_value,bool optional)1365 xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::uint_arg(const char *name, uint64_t &ret_value, bool optional)
1366 {
1367 if (check_scalar_arg(name, Mysqlx::Datatypes::Scalar::V_UINT, "unsigned int", optional))
1368 {
1369 if (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_UINT)
1370 ret_value = m_current->scalar().v_unsigned_int();
1371 else if (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_SINT)
1372 ret_value = (uint64_t)m_current->scalar().v_signed_int();
1373 ++m_current;
1374 }
1375 return *this;
1376 }
1377
1378
bool_arg(const char * name,bool & ret_value,bool optional)1379 xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::bool_arg(const char *name, bool &ret_value, bool optional)
1380 {
1381 if (check_scalar_arg(name, Mysqlx::Datatypes::Scalar::V_BOOL, "bool", optional))
1382 {
1383 ret_value = m_current->scalar().v_bool();
1384 ++m_current;
1385 }
1386 return *this;
1387 }
1388
1389
docpath_arg(const char * name,std::string & ret_value,bool optional)1390 xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::docpath_arg(const char *name, std::string &ret_value, bool optional)
1391 {
1392 m_args_consumed++;
1393 if (!m_error)
1394 {
1395 if (m_current == m_args.end())
1396 m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Too few arguments");
1397 else
1398 {
1399 if (m_current->type() == Mysqlx::Datatypes::Any::SCALAR && m_current->has_scalar() &&
1400 (m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_STRING && m_current->scalar().has_v_string()))
1401 {
1402 ret_value = m_current->scalar().v_string().value();
1403 // We could perform some extra validation on the document path here, but
1404 // since the path will be quoted and escaped when used, it would be redundant.
1405 // Plus, the best way to have the exact same syntax as the server
1406 // is to let the server do it.
1407 if (ret_value.empty() || ret_value.size() < 2)
1408 m_error = ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid document path value for argument %s", name);
1409 }
1410 else
1411 arg_type_mismatch(name, m_args_consumed, "document path string");
1412 }
1413 ++m_current;
1414 }
1415 return *this;
1416 }
1417
1418
object_list(const char * name,std::vector<Command_arguments * > & ret_value,bool optional,unsigned expected_members_count)1419 xpl::Admin_command_arguments_list &xpl::Admin_command_arguments_list::object_list(const char *name, std::vector<Command_arguments*> &ret_value,
1420 bool optional, unsigned expected_members_count)
1421 {
1422 List::difference_type left = m_args.end() - m_current;
1423 if (left % expected_members_count > 0)
1424 {
1425 m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Too few values for argument '%s'", name);
1426 return *this;
1427 }
1428 for (unsigned i = 0; i < left / expected_members_count; ++i)
1429 ret_value.push_back(this);
1430 return *this;
1431 }
1432
1433
is_end() const1434 bool xpl::Admin_command_arguments_list::is_end() const
1435 {
1436 return !(m_error.error == 0 && m_args.size() > m_args_consumed);
1437 }
1438
1439
end()1440 const ngs::Error_code &xpl::Admin_command_arguments_list::end()
1441 {
1442 if (m_error.error == ER_X_CMD_NUM_ARGUMENTS || (m_error.error == 0 && m_args.size() > m_args_consumed))
1443 {
1444 m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS,
1445 "Invalid number of arguments, expected %i but got %i", m_args_consumed, m_args.size());
1446 }
1447 return m_error;
1448 }
1449
1450
arg_type_mismatch(const char * argname,int argpos,const char * type)1451 void xpl::Admin_command_arguments_list::arg_type_mismatch(const char *argname, int argpos, const char *type)
1452 {
1453 m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE, "Invalid type for argument '%s' at #%i (should be %s)", argname, argpos, type);
1454 }
1455
1456
check_scalar_arg(const char * argname,Mysqlx::Datatypes::Scalar::Type type,const char * type_name,bool optional)1457 bool xpl::Admin_command_arguments_list::check_scalar_arg(const char *argname, Mysqlx::Datatypes::Scalar::Type type, const char *type_name, bool optional)
1458 {
1459 m_args_consumed++;
1460 if (!m_error)
1461 {
1462 if (m_current == m_args.end())
1463 {
1464 if (!optional)
1465 m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS, "Insufficient number of arguments");
1466 }
1467 else
1468 {
1469 if (m_current->type() == Mysqlx::Datatypes::Any::SCALAR && m_current->has_scalar())
1470 {
1471 if (m_current->scalar().type() == type)
1472 {
1473 //TODO: add charset check for strings?
1474 // return true only if value to be consumed is available
1475 return true;
1476 }
1477 else if (type == Mysqlx::Datatypes::Scalar::V_SINT &&
1478 m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_UINT &&
1479 m_current->scalar().v_unsigned_int() < (uint64_t)std::numeric_limits<int64_t>::max())
1480 {
1481 return true;
1482 }
1483 else if (type == Mysqlx::Datatypes::Scalar::V_UINT &&
1484 m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_SINT
1485 && m_current->scalar().v_signed_int() >= 0)
1486 {
1487 return true;
1488 }
1489 else if (optional && m_current->scalar().type() == Mysqlx::Datatypes::Scalar::V_NULL)
1490 ;
1491 else
1492 arg_type_mismatch(argname, m_args_consumed, type_name);
1493 }
1494 else
1495 arg_type_mismatch(argname, m_args_consumed, type_name);
1496 ++m_current;
1497 }
1498 }
1499 return false;
1500 }
1501
1502
1503
1504 namespace
1505 {
1506 typedef ::Mysqlx::Datatypes::Object_ObjectField Object_field;
1507
1508
1509 struct Object_field_key_is_equal
1510 {
Object_field_key_is_equal__anoneb3f066a0911::Object_field_key_is_equal1511 Object_field_key_is_equal(const char *p) : m_pattern(p) {}
operator ()__anoneb3f066a0911::Object_field_key_is_equal1512 bool operator() (const Object_field &fld) const
1513 {
1514 return fld.has_key() && fld.key() == m_pattern;
1515 }
1516 private:
1517 const char *m_pattern;
1518 };
1519
1520
1521 template<typename T>
1522 class General_argument_validator
1523 {
1524 public:
General_argument_validator(const char *,ngs::Error_code &)1525 General_argument_validator(const char*, ngs::Error_code&) {}
operator ()(const T & input,T & output)1526 void operator() (const T &input, T &output) { output = input; }
1527 };
1528
1529
1530 template<typename T, typename V = General_argument_validator<T> >
1531 class Argument_type_handler
1532 {
1533 public:
Argument_type_handler(const char * name,T & value,ngs::Error_code & error)1534 Argument_type_handler(const char *name, T &value, ngs::Error_code &error)
1535 : m_validator(name, error), m_value(&value), m_error(error), m_name(name)
1536 {}
1537
Argument_type_handler(const char * name,ngs::Error_code & error)1538 Argument_type_handler(const char *name, ngs::Error_code &error)
1539 : m_validator(name, error), m_value(NULL), m_error(error), m_name(name)
1540 {}
1541
assign(T & value)1542 void assign(T &value) { m_value = &value; }
operator ()(const T & value)1543 void operator() (const T &value) { m_validator(value, *m_value); }
operator ()()1544 void operator() () { m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE,
1545 "Invalid type of value for argument '%s'", m_name); }
operator ()(const O & value)1546 template<typename O> void operator()(const O &value) { this->operator()(); }
1547
1548 private:
1549 V m_validator;
1550 T *m_value;
1551 ngs::Error_code &m_error;
1552 const char *m_name;
1553 };
1554
1555
1556 class String_argument_validator
1557 {
1558 public:
String_argument_validator(const char * name,ngs::Error_code & error)1559 String_argument_validator(const char *name, ngs::Error_code &error)
1560 : m_name(name), m_error(error)
1561 {}
1562
operator ()(const std::string & input,std::string & output)1563 void operator() (const std::string &input, std::string &output)
1564 {
1565 if (memchr(input.data(), 0, input.length()))
1566 {
1567 m_error = ngs::Error(ER_X_CMD_ARGUMENT_VALUE, "Invalid value for argument '%s'", m_name);
1568 return;
1569 }
1570 output = input;
1571 }
1572
1573 protected:
1574 const char *m_name;
1575 ngs::Error_code &m_error;
1576 };
1577
1578
1579 class Docpath_argument_validator : String_argument_validator
1580 {
1581 public:
Docpath_argument_validator(const char * name,ngs::Error_code & error)1582 Docpath_argument_validator(const char *name, ngs::Error_code &error)
1583 : String_argument_validator(name, error)
1584 {}
1585
operator ()(const std::string & input,std::string & output)1586 void operator() (const std::string &input, std::string &output)
1587 {
1588 static const xpl::Regex re("^[[.dollar-sign.]]([[.period.]][^[:space:][.period.]]+)+$");
1589 std::string value;
1590 String_argument_validator::operator ()(input, value);
1591 if (m_error)
1592 return;
1593 if (re.match(value.c_str()))
1594 output = value;
1595 else
1596 m_error = ngs::Error(ER_X_CMD_ARGUMENT_VALUE,
1597 "Invalid value for argument '%s', expected path to document member", m_name);
1598 }
1599 };
1600
1601 } // namespace
1602
1603
Admin_command_arguments_object(const List & args)1604 xpl::Admin_command_arguments_object::Admin_command_arguments_object(const List &args)
1605 : m_args_empty(args.size() == 0),
1606 m_is_object(args.size() == 1 && args.Get(0).has_obj()),
1607 m_object(m_is_object ? args.Get(0).obj() : Object::default_instance()),
1608 m_args_consumed(0)
1609 {}
1610
1611
Admin_command_arguments_object(const Object & obj)1612 xpl::Admin_command_arguments_object::Admin_command_arguments_object(const Object &obj)
1613 : m_args_empty(true),
1614 m_is_object(true),
1615 m_object(obj),
1616 m_args_consumed(0)
1617 {}
1618
1619
expected_value_error(const char * name)1620 void xpl::Admin_command_arguments_object::expected_value_error(const char *name)
1621 {
1622 m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS,
1623 "Invalid number of arguments, expected value for '%s'", name);
1624 }
1625
1626
1627 template<typename H>
get_scalar_arg(const char * name,bool optional,H & handler)1628 void xpl::Admin_command_arguments_object::get_scalar_arg(const char *name, bool optional, H &handler)
1629 {
1630 const Object::ObjectField *field = get_object_field(name, optional);
1631 if (!field)
1632 return;
1633
1634 get_scalar_value(field->value(), handler);
1635 }
1636
1637
get_object_field(const char * name,bool optional)1638 const xpl::Admin_command_arguments_object::Object::ObjectField *xpl::Admin_command_arguments_object::get_object_field(const char *name, bool optional)
1639 {
1640 if (m_error)
1641 return NULL;
1642
1643 ++m_args_consumed;
1644
1645 if (!m_is_object)
1646 {
1647 if (!optional)
1648 expected_value_error(name);
1649 return NULL;
1650 }
1651
1652 const Object_field_list &fld = m_object.fld();
1653 Object_field_list::const_iterator i = std::find_if(fld.begin(), fld.end(), Object_field_key_is_equal(name));
1654 if (i == fld.end())
1655 {
1656 if (!optional)
1657 expected_value_error(name);
1658 return NULL;
1659 }
1660
1661 return &(*i);
1662 }
1663
1664
1665 template<typename H>
get_scalar_value(const Any & value,H & handler)1666 void xpl::Admin_command_arguments_object::get_scalar_value(const Any &value, H &handler)
1667 {
1668 try
1669 {
1670 ngs::Getter_any::put_scalar_value_to_functor(value, handler);
1671 }
1672 catch (const ngs::Error_code &e)
1673 {
1674 m_error = e;
1675 }
1676 }
1677
1678
string_arg(const char * name,std::string & ret_value,bool optional)1679 xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::string_arg(const char *name, std::string &ret_value, bool optional)
1680 {
1681 Argument_type_handler<std::string, String_argument_validator> handler(name, ret_value, m_error);
1682 get_scalar_arg(name, optional, handler);
1683 return *this;
1684 }
1685
1686
string_list(const char * name,std::vector<std::string> & ret_value,bool optional)1687 xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::string_list(const char *name, std::vector<std::string> &ret_value, bool optional)
1688 {
1689 const Object::ObjectField *field = get_object_field(name, optional);
1690 if (!field)
1691 return *this;
1692
1693 if (!field->value().has_type())
1694 {
1695 expected_value_error(name);
1696 return *this;
1697 }
1698
1699 std::vector<std::string> values;
1700 Argument_type_handler<std::string, String_argument_validator> handler(name, m_error);
1701
1702 switch (field->value().type())
1703 {
1704 case ::Mysqlx::Datatypes::Any_Type_ARRAY:
1705 for (int i = 0; i < field->value().array().value_size(); ++i)
1706 {
1707 handler.assign(*values.insert(values.end(), ""));
1708 get_scalar_value(field->value().array().value(i), handler);
1709 }
1710 break;
1711
1712 case ::Mysqlx::Datatypes::Any_Type_SCALAR:
1713 handler.assign(*values.insert(values.end(), ""));
1714 get_scalar_value(field->value(), handler);
1715 break;
1716
1717 default:
1718 m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE,
1719 "Invalid type of argument '%s', expected list of arguments", name);
1720 }
1721
1722 if (!m_error)
1723 ret_value = values;
1724
1725 return *this;
1726 }
1727
1728
sint_arg(const char * name,int64_t & ret_value,bool optional)1729 xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::sint_arg(const char *name, int64_t &ret_value, bool optional)
1730 {
1731 Argument_type_handler<google::protobuf::int64> handler(name, ret_value, m_error);
1732 get_scalar_arg(name, optional, handler);
1733 return *this;
1734 }
1735
1736
uint_arg(const char * name,uint64_t & ret_value,bool optional)1737 xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::uint_arg(const char *name, uint64_t &ret_value, bool optional)
1738 {
1739 Argument_type_handler<google::protobuf::uint64> handler(name, ret_value, m_error);
1740 get_scalar_arg(name, optional, handler);
1741 return *this;
1742 }
1743
1744
bool_arg(const char * name,bool & ret_value,bool optional)1745 xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::bool_arg(const char *name, bool &ret_value, bool optional)
1746 {
1747 Argument_type_handler<bool> handler(name, ret_value, m_error);
1748 get_scalar_arg(name, optional, handler);
1749 return *this;
1750 }
1751
1752
docpath_arg(const char * name,std::string & ret_value,bool optional)1753 xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::docpath_arg(const char *name, std::string &ret_value, bool optional)
1754 {
1755 Argument_type_handler<std::string, Docpath_argument_validator> handler(name, ret_value, m_error);
1756 get_scalar_arg(name, optional, handler);
1757 return *this;
1758 }
1759
1760
object_list(const char * name,std::vector<Command_arguments * > & ret_value,bool optional,unsigned)1761 xpl::Admin_command_arguments_object &xpl::Admin_command_arguments_object::object_list(const char *name, std::vector<Command_arguments*> &ret_value,
1762 bool optional, unsigned)
1763 {
1764 const Object::ObjectField *field = get_object_field(name, optional);
1765 if (!field)
1766 return *this;
1767
1768 if (!field->value().has_type())
1769 {
1770 expected_value_error(name);
1771 return *this;
1772 }
1773
1774 std::vector<Command_arguments*> values;
1775 switch (field->value().type())
1776 {
1777 case ::Mysqlx::Datatypes::Any_Type_ARRAY:
1778 for (int i = 0; i < field->value().array().value_size(); ++i)
1779 {
1780 const Any &any = field->value().array().value(i);
1781 if (!any.has_type() || any.type() != ::Mysqlx::Datatypes::Any_Type_OBJECT)
1782 {
1783 m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE,
1784 "Invalid type of argument '%s', expected list of objects", name);
1785 break;
1786 }
1787 values.push_back(add_sub_object(any.obj()));
1788 }
1789 break;
1790
1791 case ::Mysqlx::Datatypes::Any_Type_OBJECT:
1792 values.push_back(add_sub_object(field->value().obj()));
1793 break;
1794
1795 default:
1796 m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE,
1797 "Invalid type of argument '%s', expected list of objects", name);
1798 }
1799
1800 if (!m_error)
1801 ret_value = values;
1802
1803 return *this;
1804 }
1805
1806
add_sub_object(const Object & object)1807 xpl::Admin_command_arguments_object *xpl::Admin_command_arguments_object::add_sub_object(const Object &object)
1808 {
1809 Admin_command_arguments_object *obj = new Admin_command_arguments_object(object);
1810 m_sub_objects.push_back(ngs::shared_ptr<Admin_command_arguments_object>(obj));
1811 return obj;
1812 }
1813
1814
1815
1816
end()1817 const ngs::Error_code &xpl::Admin_command_arguments_object::end()
1818 {
1819 if (m_error)
1820 return m_error;
1821
1822 if (m_is_object)
1823 {
1824 if (m_object.fld().size() > m_args_consumed)
1825 m_error = ngs::Error(ER_X_CMD_NUM_ARGUMENTS,
1826 "Invalid number of arguments, expected %i but got %i",
1827 m_args_consumed, m_object.fld().size());
1828 }
1829 else
1830 {
1831 if (!m_args_empty)
1832 m_error = ngs::Error(ER_X_CMD_ARGUMENT_TYPE,
1833 "Invalid type of arguments, expected object of arguments");
1834 }
1835 return m_error;
1836 }
1837
1838
is_end() const1839 bool xpl::Admin_command_arguments_object::is_end() const
1840 {
1841 return !(m_error.error == 0 && m_is_object && m_object.fld().size() > m_args_consumed);
1842 }
1843
1844