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