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 along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23  */
24 
25 #include "update_statement_builder.h"
26 #include "ngs_common/protocol_protobuf.h"
27 #include "xpl_error.h"
28 
29 
30 using ::Mysqlx::Crud::UpdateOperation;
31 
32 
build(const Update & msg) const33 void xpl::Update_statement_builder::build(const Update &msg) const
34 {
35   m_builder.put("UPDATE ");
36   add_collection(msg.collection());
37   add_operation(msg.operation(), is_table_data_model(msg));
38   add_filter(msg.criteria());
39   add_order(msg.order());
40   add_limit(msg.limit(), true);
41 }
42 
43 
add_operation(const Operation_list & operation,const bool is_relational) const44 void xpl::Update_statement_builder::add_operation(const Operation_list &operation,
45                                                   const bool is_relational) const
46 {
47   if (operation.size() == 0)
48     throw ngs::Error_code(ER_X_BAD_UPDATE_DATA, "Invalid update expression list");
49 
50   m_builder.put(" SET ");
51   if (is_relational)
52     add_table_operation(operation);
53   else
54     add_document_operation(operation);
55 }
56 
57 
add_document_operation_item(const Operation_item & item,int & opeartion_id) const58 void xpl::Update_statement_builder::add_document_operation_item(const Operation_item &item, int &opeartion_id) const
59 {
60   if (opeartion_id != item.operation())
61     m_builder.put(")");
62   opeartion_id = item.operation();
63 
64   if (item.source().has_schema_name() ||
65       item.source().has_table_name() ||
66       item.source().has_name())
67      throw ngs::Error_code(ER_X_BAD_COLUMN_TO_UPDATE, "Invalid column name to update");
68 
69   if (item.operation() != UpdateOperation::ITEM_MERGE)
70   {
71     if (item.source().document_path_size() == 0 ||
72         (item.source().document_path(0).type() != ::Mysqlx::Expr::DocumentPathItem::MEMBER &&
73          item.source().document_path(0).type() != ::Mysqlx::Expr::DocumentPathItem::MEMBER_ASTERISK))
74       throw ngs::Error_code(ER_X_BAD_MEMBER_TO_UPDATE, "Invalid document member location");
75 
76     if (item.source().document_path_size() == 1 &&
77         item.source().document_path(0).type() == ::Mysqlx::Expr::DocumentPathItem::MEMBER)
78     {
79       if (item.source().document_path(0).value() == "_id")
80         throw ngs::Error(ER_X_BAD_MEMBER_TO_UPDATE, "Forbidden update operation on '$._id' member");
81     }
82     m_builder.put(",").put_expr(item.source().document_path());
83   }
84 
85   switch (item.operation())
86   {
87   case UpdateOperation::ITEM_REMOVE:
88     if (item.has_value())
89       throw ngs::Error(ER_X_BAD_UPDATE_DATA, "Unexpected value argument for ITEM_REMOVE operation");
90     break;
91 
92   case UpdateOperation::ITEM_MERGE:
93   {
94     Query_string_builder value;
95     m_builder.m_gen.clone(value).feed(item.value());
96     m_builder.put(",IF(JSON_TYPE(").put(value)
97        .put(")='OBJECT',JSON_REMOVE(").put(value)
98        .put(",'$._id'),'_ERROR_')");
99     break;
100   }
101 
102   default:
103     m_builder.put(",").put_expr(item.value());
104   }
105 }
106 
107 
add_document_operation(const Operation_list & operation) const108 void xpl::Update_statement_builder::add_document_operation(const Operation_list &operation) const
109 {
110   int prev = -1;
111   m_builder.put("doc=");
112 
113   for (Operation_list::const_reverse_iterator o = operation.rbegin();
114        o != operation.rend(); ++o)
115   {
116     if (prev == o->operation())
117       continue;
118 
119     switch (o->operation())
120     {
121     case UpdateOperation::ITEM_REMOVE:
122       m_builder.put("JSON_REMOVE(");
123       break;
124 
125     case UpdateOperation::ITEM_SET:
126       m_builder.put("JSON_SET(");
127       break;
128 
129     case UpdateOperation::ITEM_REPLACE:
130       m_builder.put("JSON_REPLACE(");
131       break;
132 
133     case UpdateOperation::ITEM_MERGE:
134       m_builder.put("JSON_MERGE(");
135       break;
136 
137     case UpdateOperation::ARRAY_INSERT:
138       m_builder.put("JSON_ARRAY_INSERT(");
139       break;
140 
141     case UpdateOperation::ARRAY_APPEND:
142       m_builder.put("JSON_ARRAY_APPEND(");
143       break;
144 
145     default:
146       throw ngs::Error_code(ER_X_BAD_TYPE_OF_UPDATE, "Invalid type of update operation for document");
147     }
148     prev = o->operation();
149   }
150   m_builder.put("doc")
151      .put_each(operation.begin(), operation.end(),
152                ngs::bind(&Update_statement_builder::add_document_operation_item,
153                          this, ngs::placeholders::_1,
154                          static_cast<int>(operation.begin()->operation())))
155      .put(")");
156 }
157 
158 namespace
159 {
160 typedef ::Mysqlx::Crud::UpdateOperation Operation_item;
161 
162 struct Is_not_equal
163 {
Is_not_equal__anon03014d040111::Is_not_equal164   Is_not_equal(const Operation_item &item)
165   : m_item(item)
166   {}
167 
operator ()__anon03014d040111::Is_not_equal168   bool operator() (const Operation_item &item) const
169   {
170     return
171         item.source().name() != m_item.source().name() ||
172         item.operation() != m_item.operation();
173   }
174 
175   const Operation_item &m_item;
176 };
177 
178 } // namespace
179 
180 
add_table_operation(const Operation_list & operation) const181 void xpl::Update_statement_builder::add_table_operation(const Operation_list &operation) const
182 {
183   Operation_iterator
184     b = operation.begin(),
185     e = std::find_if(b, operation.end(), Is_not_equal(*b));
186   add_table_operation_items(b, e);
187   while (e != operation.end())
188   {
189     b = e;
190     e = std::find_if(b, operation.end(), Is_not_equal(*b));
191     m_builder.put(",");
192     add_table_operation_items(b, e);
193   }
194 }
195 
196 
add_table_operation_items(Operation_iterator begin,Operation_iterator end) const197 void xpl::Update_statement_builder::add_table_operation_items(Operation_iterator begin,
198                                                               Operation_iterator end) const
199 {
200   if (begin->source().has_schema_name() ||
201       begin->source().has_table_name() ||
202       begin->source().name().empty())
203     throw ngs::Error_code(ER_X_BAD_COLUMN_TO_UPDATE, "Invalid column name to update");
204 
205   switch (begin->operation())
206   {
207   case UpdateOperation::SET:
208     if (begin->source().document_path_size() != 0)
209       throw ngs::Error_code(ER_X_BAD_COLUMN_TO_UPDATE, "Invalid column name to update");
210     m_builder.put_list(begin, end,
211                        ngs::bind(&Update_statement_builder::add_field_with_value, this, ngs::placeholders::_1));
212     break;
213 
214   case UpdateOperation::ITEM_REMOVE:
215     m_builder.put_identifier(begin->source().name())
216       .put("=JSON_REMOVE(")
217       .put_identifier(begin->source().name())
218       .put_each(begin, end, ngs::bind(&Update_statement_builder::add_member, this, ngs::placeholders::_1))
219       .put(")");
220     break;
221 
222   case UpdateOperation::ITEM_SET:
223     m_builder.put_identifier(begin->source().name())
224       .put("=JSON_SET(")
225       .put_identifier(begin->source().name())
226       .put_each(begin, end, ngs::bind(&Update_statement_builder::add_member_with_value, this, ngs::placeholders::_1))
227       .put(")");
228     break;
229 
230   case UpdateOperation::ITEM_REPLACE:
231     m_builder.put_identifier(begin->source().name())
232       .put("=JSON_REPLACE(")
233       .put_identifier(begin->source().name())
234       .put_each(begin, end, ngs::bind(&Update_statement_builder::add_member_with_value, this, ngs::placeholders::_1))
235       .put(")");
236     break;
237 
238   case UpdateOperation::ITEM_MERGE:
239     m_builder.put_identifier(begin->source().name())
240       .put("=JSON_MERGE(")
241       .put_identifier(begin->source().name())
242       .put_each(begin, end, ngs::bind(&Update_statement_builder::add_value, this, ngs::placeholders::_1))
243       .put(")");
244     break;
245 
246   case UpdateOperation::ARRAY_INSERT:
247     m_builder.put_identifier(begin->source().name())
248       .put("=JSON_ARRAY_INSERT(")
249       .put_identifier(begin->source().name())
250       .put_each(begin, end, ngs::bind(&Update_statement_builder::add_member_with_value, this, ngs::placeholders::_1))
251       .put(")");
252     break;
253 
254   case UpdateOperation::ARRAY_APPEND:
255     m_builder.put_identifier(begin->source().name())
256       .put("=JSON_ARRAY_APPEND(")
257       .put_identifier(begin->source().name())
258       .put_each(begin, end, ngs::bind(&Update_statement_builder::add_member_with_value, this, ngs::placeholders::_1))
259       .put(")");
260     break;
261 
262   default:
263     throw ngs::Error_code(ER_X_BAD_TYPE_OF_UPDATE,
264                           "Invalid type of update operation for table");
265   }
266 }
267 
268 
add_member(const Operation_item & item) const269 void xpl::Update_statement_builder::add_member(const Operation_item &item) const
270 {
271   if (item.source().document_path_size() == 0)
272     throw ngs::Error_code(ER_X_BAD_MEMBER_TO_UPDATE, "Invalid member location");
273   m_builder.put(",").put_expr(item.source().document_path());
274 }
275 
276 
add_value(const Operation_item & item) const277 void xpl::Update_statement_builder::add_value(const Operation_item &item) const
278 {
279   m_builder.put(",").put_expr(item.value());
280 }
281 
282 
add_member_with_value(const Operation_item & item) const283 void xpl::Update_statement_builder::add_member_with_value(const Operation_item &item) const
284 {
285   add_member(item);
286   add_value(item);
287 }
288 
289 
add_field_with_value(const Operation_item & item) const290 void xpl::Update_statement_builder::add_field_with_value(const Operation_item &item) const
291 {
292   m_builder.put_expr(item.source()).put("=").put_expr(item.value());
293 }
294