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 "find_statement_builder.h"
26 #include "query_string_builder.h"
27 #include "mysqld_error.h"
28 #include "expr_generator.h"
29 #include "ngs_common/protocol_protobuf.h"
30 
31 #include <gtest/gtest.h>
32 
33 namespace xpl
34 {
35 namespace test
36 {
37 
38 class Find_statement_builder_impl: public Find_statement_builder
39 {
40 public:
Find_statement_builder_impl(Expression_generator & gen)41   Find_statement_builder_impl(Expression_generator &gen) : Find_statement_builder(gen) {}
42   using Find_statement_builder::add_table_projection;
43   using Find_statement_builder::add_document_projection;
44   using Find_statement_builder::add_grouping;
45   using Find_statement_builder::add_grouping_criteria;
46   using Find_statement_builder::Grouping_list;
47   using Find_statement_builder::Grouping_criteria;
48 };
49 
50 
51 class Find_statement_builder_test : public ::testing::Test
52 {
53 public:
Find_statement_builder_test()54   Find_statement_builder_test()
55   : args(*msg.mutable_args()),
56     expr_gen(query, args, schema, true),
57     builder(expr_gen)
58     {}
59   Find_statement_builder::Find msg;
60   Expression_generator::Args &args;
61   Query_string_builder query;
62   std::string schema;
63   Expression_generator expr_gen;
64   Find_statement_builder_impl builder;
65 
66   enum {DM_DOCUMENT = 0, DM_TABLE = 1};
67 
68   typedef ::google::protobuf::RepeatedPtrField< ::Mysqlx::Crud::Projection > Projection_list;
69 };
70 
71 
72 namespace
73 {
operator <<(::google::protobuf::Message & msg,const std::string & txt)74 void operator<< (::google::protobuf::Message &msg, const std::string& txt)
75 {
76   ASSERT_TRUE(::google::protobuf::TextFormat::ParseFromString(txt, &msg));
77 }
78 } // namespace
79 
80 
TEST_F(Find_statement_builder_test,add_projection_table_empty)81 TEST_F(Find_statement_builder_test, add_projection_table_empty)
82 {
83   Projection_list projection;
84   ASSERT_NO_THROW(builder.add_table_projection(projection));
85   EXPECT_EQ("*", query.get());
86 }
87 
88 
TEST_F(Find_statement_builder_test,add_document_projection_empty)89 TEST_F(Find_statement_builder_test, add_document_projection_empty)
90 {
91   Projection_list projection;
92   ASSERT_NO_THROW(builder.add_document_projection(projection));
93   EXPECT_EQ("doc", query.get());
94 }
95 
96 
TEST_F(Find_statement_builder_test,add_projection_table_one_member_item)97 TEST_F(Find_statement_builder_test, add_projection_table_one_member_item)
98 {
99   Projection_list projection;
100   *projection.Add() << "source { type: IDENT identifier "
101       "{ document_path { type: MEMBER value: \"alpha\" } } }";
102   ASSERT_NO_THROW(builder.add_table_projection(projection));
103   EXPECT_EQ("JSON_EXTRACT(doc,'$.alpha')", query.get());
104 }
105 
106 
TEST_F(Find_statement_builder_test,add_projection_table_one_item)107 TEST_F(Find_statement_builder_test, add_projection_table_one_item)
108 {
109   Projection_list projection;
110   *projection.Add() << "source { type: IDENT identifier "
111       "{ name: 'alpha' } }";
112   ASSERT_NO_THROW(builder.add_table_projection(projection));
113   EXPECT_EQ("`alpha`", query.get());
114 }
115 
116 
TEST_F(Find_statement_builder_test,add_projection_table_two_items)117 TEST_F(Find_statement_builder_test, add_projection_table_two_items)
118 {
119   Projection_list projection;
120   *projection.Add() << "source { type: IDENT identifier "
121       "{ name: 'alpha' } }";
122   *projection.Add() <<  "source { type: IDENT identifier "
123       "{ name: 'beta' } }";
124   ASSERT_NO_THROW(builder.add_table_projection(projection));
125   EXPECT_EQ("`alpha`,`beta`", query.get());
126 }
127 
128 
TEST_F(Find_statement_builder_test,add_projection_table_two_items_placeholder)129 TEST_F(Find_statement_builder_test, add_projection_table_two_items_placeholder)
130 {
131   *args.Add() << "type: V_DOUBLE v_double: 2.2";
132 
133   Projection_list projection;
134   *projection.Add() << "source { type: IDENT identifier "
135       "{ name: 'alpha' } }";
136   *projection.Add() <<  "source { type: PLACEHOLDER position: 0 }";
137   ASSERT_NO_THROW(builder.add_table_projection(projection));
138   EXPECT_EQ("`alpha`,2.2", query.get());
139 }
140 
141 
TEST_F(Find_statement_builder_test,add_projection_table_one_item_with_alias)142 TEST_F(Find_statement_builder_test, add_projection_table_one_item_with_alias)
143 {
144   Projection_list projection;
145   *projection.Add() << "source { type: IDENT identifier "
146       "{ name: 'alpha' } } alias: 'beta'";
147   ASSERT_NO_THROW(builder.add_table_projection(projection));
148   EXPECT_EQ("`alpha` AS `beta`", query.get());
149 }
150 
151 
TEST_F(Find_statement_builder_test,add_projection_document_one_item_no_alias)152 TEST_F(Find_statement_builder_test, add_projection_document_one_item_no_alias)
153 {
154   Projection_list projection;
155   *projection.Add() << "source { type: IDENT identifier "
156       "{ name: 'alpha' } }";
157   EXPECT_THROW(builder.add_document_projection(projection), ngs::Error_code);
158 }
159 
160 
TEST_F(Find_statement_builder_test,add_projection_document_one_member_item)161 TEST_F(Find_statement_builder_test, add_projection_document_one_member_item)
162 {
163   Projection_list projection;
164   *projection.Add() << "source { type: IDENT identifier "
165       "{ document_path { type: MEMBER value: \"alpha\" } } }"
166       "alias: \"beta\"";
167   ASSERT_NO_THROW(builder.add_document_projection(projection));
168   EXPECT_EQ("JSON_OBJECT('beta', JSON_EXTRACT(doc,'$.alpha')) AS doc",
169             query.get());
170 }
171 
172 
TEST_F(Find_statement_builder_test,add_projection_document_two_member_items)173 TEST_F(Find_statement_builder_test, add_projection_document_two_member_items)
174 {
175   Projection_list projection;
176   *projection.Add() << "source { type: IDENT identifier "
177       "{ document_path { type: MEMBER value: \"alpha\" } } }"
178       "alias: \"beta\"";
179   *projection.Add() << "source { type: IDENT identifier "
180       "{ document_path { type: MEMBER value: \"first\" } } }"
181       "alias: \"second\"";
182   ASSERT_NO_THROW(builder.add_document_projection(projection));
183   EXPECT_EQ("JSON_OBJECT('beta', JSON_EXTRACT(doc,'$.alpha'),"
184       "'second', JSON_EXTRACT(doc,'$.first')) AS doc",
185       query.get());
186 }
187 
188 
TEST_F(Find_statement_builder_test,add_projection_document_two_member_items_placeholder)189 TEST_F(Find_statement_builder_test, add_projection_document_two_member_items_placeholder)
190 {
191   *args.Add() << "type: V_DOUBLE v_double: 2.2";
192   Projection_list projection;
193   *projection.Add() << "source { type: IDENT identifier "
194       "{ document_path { type: MEMBER value: \"alpha\" } } }"
195       "alias: \"beta\"";
196   *projection.Add() << "source {type: PLACEHOLDER position: 0}"
197       "alias: \"second\"";
198   ASSERT_NO_THROW(builder.add_document_projection(projection));
199   EXPECT_EQ("JSON_OBJECT('beta', JSON_EXTRACT(doc,'$.alpha'),"
200       "'second', 2.2) AS doc",
201       query.get());
202 }
203 
204 
TEST_F(Find_statement_builder_test,add_gruping_empty)205 TEST_F(Find_statement_builder_test, add_gruping_empty)
206 {
207   Find_statement_builder_impl::Grouping_list group;
208   ASSERT_NO_THROW(builder.add_grouping(group));
209   EXPECT_EQ("", query.get());
210 }
211 
212 
TEST_F(Find_statement_builder_test,add_gruping_one_item)213 TEST_F(Find_statement_builder_test, add_gruping_one_item)
214 {
215   Find_statement_builder_impl::Grouping_list group;
216   *group.Add() << "type: IDENT identifier { name: 'alpha' }";
217   ASSERT_NO_THROW(builder.add_grouping(group));
218   EXPECT_EQ(" GROUP BY `alpha`", query.get());
219 }
220 
221 
TEST_F(Find_statement_builder_test,add_gruping_two_items)222 TEST_F(Find_statement_builder_test, add_gruping_two_items)
223 {
224   Find_statement_builder_impl::Grouping_list group;
225   *group.Add() << "type: IDENT identifier { name: 'alpha' }";
226   *group.Add() << "type: IDENT identifier { name: 'beta' }";
227   ASSERT_NO_THROW(builder.add_grouping(group));
228   EXPECT_EQ(" GROUP BY `alpha`,`beta`", query.get());
229 }
230 
231 
TEST_F(Find_statement_builder_test,add_gruping_two_items_placeholder)232 TEST_F(Find_statement_builder_test, add_gruping_two_items_placeholder)
233 {
234   *args.Add() << "type: V_SINT v_signed_int: 2";
235 
236   Find_statement_builder_impl::Grouping_list group;
237   *group.Add() << "type: IDENT identifier { name: 'alpha' }";
238   *group.Add() << "type: PLACEHOLDER position: 0";
239   ASSERT_NO_THROW(builder.add_grouping(group));
240   EXPECT_EQ(" GROUP BY `alpha`,2", query.get());
241 }
242 
243 
TEST_F(Find_statement_builder_test,add_gruping_criteria)244 TEST_F(Find_statement_builder_test, add_gruping_criteria)
245 {
246   Find_statement_builder_impl::Grouping_criteria criteria;
247   criteria << "type: OPERATOR operator {name: '>'"
248       "param {type: IDENT identifier {name: 'alpha'}}"
249       "param {type: LITERAL literal {type: V_DOUBLE v_double: 1.0}}}";
250   ASSERT_NO_THROW(builder.add_grouping_criteria(criteria));
251   EXPECT_EQ(" HAVING (`alpha` > 1)", query.get());
252 }
253 
254 
TEST_F(Find_statement_builder_test,add_gruping_criteria_placeholder)255 TEST_F(Find_statement_builder_test, add_gruping_criteria_placeholder)
256 {
257   *args.Add() << "type: V_DOUBLE v_double: 2.3";
258 
259   Find_statement_builder_impl::Grouping_criteria criteria;
260   criteria << "type: OPERATOR operator {name: '>'"
261       "param {type: IDENT identifier {name: 'alpha'}}"
262       "param {type: PLACEHOLDER position: 0}}";
263   ASSERT_NO_THROW(builder.add_grouping_criteria(criteria));
264   EXPECT_EQ(" HAVING (`alpha` > 2.3)", query.get());
265 }
266 
267 
TEST_F(Find_statement_builder_test,build_table)268 TEST_F(Find_statement_builder_test, build_table)
269 {
270   msg <<
271       "collection {name: 'xtable' schema: 'xschema'}"
272       "data_model: TABLE "
273       "projection {source {type: IDENT identifier {name: 'alpha'}}"
274       "            alias: 'zeta'} "
275       "criteria {type: OPERATOR "
276       "          operator {name: '>' "
277       "                    param {type: IDENT identifier {name: 'delta'}}"
278       "                    param {type: LITERAL literal "
279       "                                                 {type: V_DOUBLE"
280       "                                                  v_double: 1.0}}}}"
281       "order {expr {type: IDENT identifier {name: 'gamma'}}"
282       "       direction: DESC} "
283       "grouping {type: IDENT identifier {name: 'beta'}}"
284       "grouping_criteria {type: OPERATOR "
285       "          operator {name: '<' "
286       "                    param {type: IDENT identifier {name: 'lambda'}}"
287       "                    param {type: LITERAL literal"
288       "                                                {type: V_DOUBLE"
289       "                                             v_double: 2.0}}}}";
290   ASSERT_NO_THROW(builder.build(msg));
291   EXPECT_EQ(
292       "SELECT `alpha` AS `zeta` "
293       "FROM `xschema`.`xtable` "
294       "WHERE (`delta` > 1) "
295       "GROUP BY `beta` "
296       "HAVING (`lambda` < 2) "
297       "ORDER BY `gamma` DESC", query.get());
298 }
299 
300 
TEST_F(Find_statement_builder_test,build_document_no_grouping)301 TEST_F(Find_statement_builder_test, build_document_no_grouping)
302 {
303   msg <<
304       "collection {name: 'xtable' schema: 'xschema'}"
305       "data_model: DOCUMENT "
306       "projection {source {type: IDENT identifier {document_path {type: MEMBER "
307       "                                                             value: 'alpha'}}}"
308       "            alias: 'zeta'} "
309       "criteria {type: OPERATOR "
310       "          operator {name: '>' "
311       "                    param {type: IDENT identifier {document_path {type: MEMBER "
312       "                                                                  value: 'delta'}}}"
313       "                    param {type: LITERAL literal"
314       "                                                {type: V_DOUBLE"
315       "                                             v_double: 1.0}}}}"
316       "order {expr {type: IDENT identifier {document_path {type: MEMBER "
317       "                                                     value: 'gamma'}}}"
318       "       direction: DESC}";
319   ASSERT_NO_THROW(builder.build(msg));
320   EXPECT_EQ(
321       "SELECT JSON_OBJECT('zeta', JSON_EXTRACT(doc,'$.alpha')) AS doc "
322       "FROM `xschema`.`xtable` "
323       "WHERE (JSON_EXTRACT(doc,'$.delta') > 1) "
324       "ORDER BY JSON_EXTRACT(doc,'$.gamma') DESC",
325       query.get());
326 }
327 
328 
TEST_F(Find_statement_builder_test,build_document_with_grouping_and_criteria)329 TEST_F(Find_statement_builder_test, build_document_with_grouping_and_criteria)
330 {
331   msg <<
332       "collection {name: 'xtable' schema: 'xschema'}"
333       "data_model: DOCUMENT "
334       "projection {source {type: IDENT identifier {document_path {type: MEMBER "
335       "                                                             value: 'alpha'}}}"
336       "            alias: 'zeta'} "
337       "criteria {type: OPERATOR "
338       "          operator {name: '>' "
339       "                    param {type: IDENT identifier {document_path {type: MEMBER "
340       "                                                                  value: 'delta'}}}"
341       "                    param {type: LITERAL literal"
342       "                                                {type: V_DOUBLE"
343       "                                             v_double: 1.0}}}}"
344       "order {expr {type: IDENT identifier {document_path {type: MEMBER "
345       "                                                     value: 'beta'}}}"
346       "       direction: DESC} "
347       "grouping {type: IDENT identifier {document_path {type: MEMBER "
348       "                                                  value: 'alpha'}}}"
349       "grouping_criteria {type: OPERATOR "
350       "          operator {name: '<' "
351       "                    param {type: IDENT identifier {document_path {type: MEMBER "
352       "                                                                  value: 'lambda'}}}"
353       "                    param {type: LITERAL literal"
354       "                                                {type: V_DOUBLE"
355       "                                                v_double: 2.0}}}}";
356   ASSERT_NO_THROW(builder.build(msg));
357   EXPECT_STREQ(
358       "SELECT JSON_OBJECT('zeta', `_DERIVED_TABLE_`.`zeta`) AS doc FROM ("
359       "SELECT JSON_EXTRACT(doc,'$.alpha') AS `zeta` "
360       "FROM `xschema`.`xtable` "
361       "WHERE (JSON_EXTRACT(doc,'$.delta') > 1) "
362       "GROUP BY JSON_EXTRACT(doc,'$.alpha') "
363       "HAVING (JSON_EXTRACT(doc,'$.lambda') < 2) "
364       "ORDER BY JSON_EXTRACT(doc,'$.beta') DESC"
365       ") AS `_DERIVED_TABLE_`",
366       query.get().c_str());
367 }
368 
369 
TEST_F(Find_statement_builder_test,build_document_with_grouping)370 TEST_F(Find_statement_builder_test, build_document_with_grouping)
371 {
372   msg <<
373       "collection {name: 'xtable' schema: 'xschema'}"
374       "data_model: DOCUMENT "
375       "projection {source {type: IDENT identifier {document_path {type: MEMBER "
376       "                                                             value: 'alpha'}}}"
377       "            alias: 'zeta'} "
378       "projection {source {type: IDENT identifier {document_path {type: MEMBER "
379       "                                                             value: 'gama'}}}"
380       "            alias: 'ksi'} "
381       "grouping {type: IDENT identifier {document_path {type: MEMBER "
382       "                                                  value: 'alpha'}}}"
383       "grouping {type: IDENT identifier {document_path {type: MEMBER "
384       "                                                  value: 'gama'}}}";
385   ASSERT_NO_THROW(builder.build(msg));
386   EXPECT_EQ(
387       "SELECT JSON_OBJECT('zeta', `_DERIVED_TABLE_`.`zeta`,'ksi', `_DERIVED_TABLE_`.`ksi`) AS doc FROM ("
388       "SELECT JSON_EXTRACT(doc,'$.alpha') AS `zeta`,JSON_EXTRACT(doc,'$.gama') AS `ksi` "
389       "FROM `xschema`.`xtable` "
390       "GROUP BY JSON_EXTRACT(doc,'$.alpha'),JSON_EXTRACT(doc,'$.gama')"
391       ") AS `_DERIVED_TABLE_`",
392       query.get());
393 }
394 
395 
TEST_F(Find_statement_builder_test,build_document_with_grouping_no_projection)396 TEST_F(Find_statement_builder_test, build_document_with_grouping_no_projection)
397 {
398   msg <<
399       "collection {name: 'xtable' schema: 'xschema'}"
400       "data_model: DOCUMENT "
401       "grouping {type: IDENT identifier {document_path {type: MEMBER "
402       "                                                  value: 'beta'}}}";
403   EXPECT_THROW(builder.build(msg), ngs::Error_code);
404 }
405 
406 } // namespace test
407 } // namespace xpl
408