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 &notice)
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 &notice, 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