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 "ngs_common/protocol_protobuf.h"
27 #include "xpl_error.h"
28 #include <algorithm>
29 
30 
build(const Find & msg) const31 void xpl::Find_statement_builder::build(const Find &msg) const
32 {
33   if (!is_table_data_model(msg) && msg.grouping_size() > 0)
34     add_document_statement_with_grouping(msg);
35   else
36     add_statement_common(msg);
37 }
38 
39 
add_statement_common(const Find & msg) const40 void xpl::Find_statement_builder::add_statement_common(const Find &msg) const
41 {
42   m_builder.put("SELECT ");
43   if (is_table_data_model(msg))
44     add_table_projection(msg.projection());
45   else
46     add_document_projection(msg.projection());
47   m_builder.put(" FROM ");
48   add_collection(msg.collection());
49   add_filter(msg.criteria());
50   add_grouping(msg.grouping());
51   add_grouping_criteria(msg.grouping_criteria());
52   add_order(msg.order());
53   add_limit(msg.limit(), false);
54 }
55 
56 
57 namespace
58 {
59 const char* const DERIVED_TABLE_NAME = "`_DERIVED_TABLE_`";
60 } // namespace
61 
62 
add_document_statement_with_grouping(const Find & msg) const63 void xpl::Find_statement_builder::add_document_statement_with_grouping(const Find &msg) const
64 {
65   if (msg.projection_size() == 0)
66     throw ngs::Error_code(ER_X_BAD_PROJECTION, "Invalid empty projection list for grouping");
67 
68   m_builder.put("SELECT ");
69   add_document_object(msg.projection(), &Find_statement_builder::add_document_primary_projection_item);
70   m_builder.put(" FROM (");
71   m_builder.put("SELECT ");
72   add_table_projection(msg.projection());
73   m_builder.put(" FROM ");
74   add_collection(msg.collection());
75   add_filter(msg.criteria());
76   add_grouping(msg.grouping());
77   add_grouping_criteria(msg.grouping_criteria());
78   add_order(msg.order());
79   add_limit(msg.limit(), false);
80   m_builder.put(") AS ").put(DERIVED_TABLE_NAME);
81 }
82 
83 
add_table_projection(const Projection_list & projection) const84 void xpl::Find_statement_builder::add_table_projection(const Projection_list &projection) const
85 {
86   if (projection.size() == 0)
87   {
88     m_builder.put("*");
89     return;
90   }
91   m_builder.put_list(projection, ngs::bind(&Find_statement_builder::add_table_projection_item, this, ngs::placeholders::_1));
92 }
93 
94 
add_table_projection_item(const Projection & item) const95 void xpl::Find_statement_builder::add_table_projection_item(const Projection &item) const
96 {
97   m_builder.put_expr(item.source());
98   add_alias(item);
99 }
100 
101 
add_document_projection(const Projection_list & projection) const102 void xpl::Find_statement_builder::add_document_projection(const Projection_list &projection) const
103 {
104   if (projection.size() == 0)
105   {
106     m_builder.put("doc");
107     return;
108   }
109 
110   if (projection.size() == 1 &&
111       !projection.Get(0).has_alias() &&
112       projection.Get(0).source().type() == Mysqlx::Expr::Expr::OBJECT)
113   {
114     m_builder.put_expr(projection.Get(0).source()).put(" AS doc");
115     return;
116   }
117 
118   add_document_object(projection, &Find_statement_builder::add_document_projection_item);
119 }
120 
121 
add_document_object(const Projection_list & projection,const Object_item_adder & adder) const122 void xpl::Find_statement_builder::add_document_object(const Projection_list &projection,
123                                                       const Object_item_adder &adder) const
124 {
125   m_builder.put("JSON_OBJECT(")
126       .put_list(projection, ngs::bind(adder, this, ngs::placeholders::_1))
127       .put(") AS doc");
128 }
129 
130 
add_document_projection_item(const Projection & item) const131 void xpl::Find_statement_builder::add_document_projection_item(const Projection &item) const
132 {
133   if (!item.has_alias())
134     throw ngs::Error(ER_X_PROJ_BAD_KEY_NAME,
135                      "Invalid projection target name");
136 
137   m_builder.put_quote(item.alias()).put(", ").put_expr(item.source());
138 }
139 
140 
add_document_primary_projection_item(const Projection & item) const141 void xpl::Find_statement_builder::add_document_primary_projection_item(const Projection &item) const
142 {
143   if (!item.has_alias())
144     throw ngs::Error(ER_X_PROJ_BAD_KEY_NAME,
145                      "Invalid projection target name");
146 
147   m_builder.put_quote(item.alias()).put(", ")
148       .put(DERIVED_TABLE_NAME).dot().put_identifier(item.alias());
149 }
150 
151 
add_grouping(const Grouping_list & group) const152 void xpl::Find_statement_builder::add_grouping(const Grouping_list &group) const
153 {
154   if (group.size() > 0)
155     m_builder.put(" GROUP BY ").put_list(group, &Generator::put_expr);
156 }
157 
158 
add_grouping_criteria(const Grouping_criteria & criteria) const159 void xpl::Find_statement_builder::add_grouping_criteria(const Grouping_criteria &criteria) const
160 {
161   if (criteria.IsInitialized())
162     m_builder.put(" HAVING ").put_expr(criteria);
163 }
164