/Users/deen/code/yugabyte-db/src/yb/bfpg/bfpg.cc
Line | Count | Source (jump to first uncovered line) |
1 | | //-------------------------------------------------------------------------------------------------- |
2 | | // Copyright (c) YugaByte, Inc. |
3 | | // |
4 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
5 | | // in compliance with the License. You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
10 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
11 | | // or implied. See the License for the specific language governing permissions and limitations |
12 | | // under the License. |
13 | | // |
14 | | //-------------------------------------------------------------------------------------------------- |
15 | | |
16 | | #include "yb/bfpg/bfpg.h" |
17 | | |
18 | | #include <functional> |
19 | | #include <unordered_map> |
20 | | |
21 | | #include "yb/common/ql_type.h" |
22 | | |
23 | | using std::function; |
24 | | using std::vector; |
25 | | using std::unordered_map; |
26 | | using strings::Substitute; |
27 | | |
28 | | namespace yb { |
29 | | namespace bfpg { |
30 | | |
31 | | //-------------------------------------------------------------------------------------------------- |
32 | 0 | bool IsAggregateOpcode(TSOpcode op) { |
33 | 0 | switch (op) { |
34 | 0 | case TSOpcode::kAvg: FALLTHROUGH_INTENDED; |
35 | 0 | case TSOpcode::kCount: FALLTHROUGH_INTENDED; |
36 | 0 | case TSOpcode::kMax: FALLTHROUGH_INTENDED; |
37 | 0 | case TSOpcode::kMin: FALLTHROUGH_INTENDED; |
38 | 0 | case TSOpcode::kSumInt8: FALLTHROUGH_INTENDED; |
39 | 0 | case TSOpcode::kSumInt16: FALLTHROUGH_INTENDED; |
40 | 0 | case TSOpcode::kSumInt32: FALLTHROUGH_INTENDED; |
41 | 0 | case TSOpcode::kSumInt64: FALLTHROUGH_INTENDED; |
42 | 0 | case TSOpcode::kSumFloat: FALLTHROUGH_INTENDED; |
43 | 0 | case TSOpcode::kSumDouble: |
44 | 0 | return true; |
45 | 0 | default: |
46 | 0 | return false; |
47 | 0 | } |
48 | 0 | } |
49 | | |
50 | | //-------------------------------------------------------------------------------------------------- |
51 | | // Check compatible type in function call. |
52 | 52 | inline bool IsCompatible(DataType left, DataType right) { |
53 | 52 | return QLType::IsPotentiallyConvertible(left, right); |
54 | 52 | } |
55 | | |
56 | | //-------------------------------------------------------------------------------------------------- |
57 | | // HasExactSignature() is a predicate to check if the datatypes of actual parameters (arguments) |
58 | | // and formal parameters (signature) are identical. |
59 | | // NOTES: |
60 | | // PTypePtr can be a (shared_ptr<MyClass>) or a raw pointer (MyClass*) |
61 | | |
62 | | static bool HasExactTypeSignature(const std::vector<DataType>& signature, |
63 | 197 | const std::vector<DataType>& actual_types) { |
64 | | // Check parameter count. |
65 | 197 | const auto formal_count = signature.size(); |
66 | 197 | const auto actual_count = actual_types.size(); |
67 | | |
68 | | // Check for exact match. |
69 | 197 | size_t index; |
70 | 238 | for (index = 0; index < formal_count; index++41 ) { |
71 | | // Check if the signature accept varargs which can be matched with the rest of arguments. |
72 | 227 | if (signature[index] == DataType::TYPEARGS) { |
73 | 0 | return true; |
74 | 0 | } |
75 | | |
76 | | // Return false if one of the following is true. |
77 | | // - The number of arguments is less than the formal count. |
78 | | // - The datatype of an argument is not an exact match of the signature type. |
79 | 227 | if (index >= actual_count || signature[index] != actual_types[index]222 ) { |
80 | 186 | return false; |
81 | 186 | } |
82 | 227 | } |
83 | | |
84 | | // Assert that the number of arguments is the same as the formal count. |
85 | 11 | if (index < actual_count) { |
86 | 0 | return false; |
87 | 0 | } |
88 | 11 | return true; |
89 | 11 | } |
90 | | |
91 | | //-------------------------------------------------------------------------------------------------- |
92 | | // HasSimilarSignature() is a predicate to check if the datatypes of actual parameters (arguments) |
93 | | // and formal parameters (signature) are similar. |
94 | | // |
95 | | // Similar is mainly used for integers vs floating point values. |
96 | | // INT8 is "similar" to INT64. |
97 | | // INT8 is NOT "similar" to DOUBLE. |
98 | | // FLOAT is "similar" to DOUBLE. |
99 | | // This rule is to help resolve the overloading functions between integers and float point data. |
100 | | // |
101 | | // NOTES: |
102 | | // PTypePtr and RTypePtr can be either a (shared_ptr<MyClass>) or a raw pointer (MyClass*) |
103 | | |
104 | | static bool HasSimilarTypeSignature(const std::vector<DataType>& signature, |
105 | 50 | const std::vector<DataType>& actual_types) { |
106 | 50 | const auto formal_count = signature.size(); |
107 | 50 | const auto actual_count = actual_types.size(); |
108 | | |
109 | | // Check for exact match. |
110 | 50 | size_t index; |
111 | 63 | for (index = 0; index < formal_count; index++13 ) { |
112 | | // Check if the signature accept varargs which can be matched with the rest of arguments. |
113 | 59 | if (signature[index] == DataType::TYPEARGS) { |
114 | 0 | return true; |
115 | 0 | } |
116 | | |
117 | | // Return false if one of the following is true. |
118 | | // - The number of arguments is less than the formal count. |
119 | | // - The datatype of an argument is not an similar match of the signature type. |
120 | 59 | if (index >= actual_count || !QLType::IsSimilar(signature[index], actual_types[index])53 ) { |
121 | 46 | return false; |
122 | 46 | } |
123 | 59 | } |
124 | | |
125 | | // Assert that the number of arguments is the same as the formal count. |
126 | 4 | if (index < actual_count) { |
127 | 1 | return false; |
128 | 1 | } |
129 | | |
130 | 3 | return true; |
131 | 4 | } |
132 | | |
133 | | //-------------------------------------------------------------------------------------------------- |
134 | | // HasCompatibleSignature() is a predicate to check if the arguments is convertible to the |
135 | | // signature. |
136 | | // |
137 | | // TODO(neil) Needs to allow passing double to int. |
138 | | // NOTES: |
139 | | // PTypePtr and RTypePtr can be either a (shared_ptr<MyClass>) or a raw pointer (MyClass*). |
140 | | |
141 | | static bool HasCompatibleTypeSignature(const std::vector<DataType>& signature, |
142 | 35 | const std::vector<DataType>& actual_types) { |
143 | | |
144 | 35 | const auto formal_count = signature.size(); |
145 | 35 | const auto actual_count = actual_types.size(); |
146 | | |
147 | | // Check for compatible match. |
148 | 35 | size_t index; |
149 | 53 | for (index = 0; index < formal_count; index++18 ) { |
150 | | // Check if the signature accept varargs which can be matched with the rest of arguments. |
151 | 49 | if (signature[index] == DataType::TYPEARGS) { |
152 | 0 | return true; |
153 | 0 | } |
154 | | |
155 | | // Return false if one of the following is true. |
156 | | // - The number of arguments is less than the formal count. |
157 | | // - The datatype of an argument is not a compatible match of the signature type. |
158 | 49 | if (index >= actual_count || !IsCompatible(signature[index], actual_types[index])41 ) { |
159 | 31 | return false; |
160 | 31 | } |
161 | 49 | } |
162 | | |
163 | | // Assert that the number of arguments is the same as the formal count. |
164 | 4 | if (index < actual_count) { |
165 | 2 | return false; |
166 | 2 | } |
167 | | |
168 | 2 | return true; |
169 | 4 | } |
170 | | |
171 | | //-------------------------------------------------------------------------------------------------- |
172 | | // Searches all overloading versions of a function and finds exactly one function specification |
173 | | // whose signature matches with the datatypes of the arguments. |
174 | | // NOTES: |
175 | | // "compare_signature" is a predicate to compare datatypes of formal and actual parameters. |
176 | | // PTypePtr and RTypePtr can be either a (shared_ptr<MyClass>) or a raw pointer (MyClass*) |
177 | | static Status FindMatch( |
178 | | const string& ql_name, |
179 | | function<bool(const std::vector<DataType>&, const std::vector<DataType>&)> compare_signature, |
180 | | BFOpcode max_opcode, |
181 | | const std::vector<DataType>& actual_types, |
182 | | BFOpcode *found_opcode, |
183 | | const BFDecl **found_decl, |
184 | 38 | DataType *return_type) { |
185 | | |
186 | | // Find a compatible operator, and raise error if there's more than one match. |
187 | 38 | const BFOperator *compatible_operator = nullptr; |
188 | 282 | while (true) { |
189 | 282 | const BFOperator *bf_operator = kBFOperators[static_cast<int>(max_opcode)].get(); |
190 | 282 | DCHECK(max_opcode == bf_operator->opcode()); |
191 | | |
192 | | // Check if each parameter has compatible type match. |
193 | 282 | if (compare_signature(bf_operator->param_types(), actual_types)) { |
194 | | // Found a compatible match. Make sure that it is the only match. |
195 | 16 | if (compatible_operator != nullptr) { |
196 | 0 | return STATUS(InvalidArgument, |
197 | 0 | Substitute("Found too many matches for builtin function '$0'", ql_name)); |
198 | 0 | } |
199 | 16 | compatible_operator = bf_operator; |
200 | 16 | } |
201 | | |
202 | | // Break the loop if we have processed all operators in the overloading chain. |
203 | 282 | if (max_opcode == bf_operator->overloaded_opcode()) { |
204 | 38 | break; |
205 | 38 | } |
206 | | |
207 | | // Jump to the next overloading opcode. |
208 | 244 | max_opcode = bf_operator->overloaded_opcode(); |
209 | 244 | } |
210 | | |
211 | | // Returns error if no match is found. |
212 | 38 | if (compatible_operator == nullptr) { |
213 | 22 | return STATUS(NotFound, |
214 | 22 | Substitute("Signature mismatch in call to builtin function '$0'", ql_name)); |
215 | 22 | } |
216 | | |
217 | | // Returns error if the return type is not compatible. |
218 | 16 | if (return_type != nullptr) { |
219 | 16 | if (QLType::IsUnknown(*return_type)) { |
220 | 5 | *return_type = compatible_operator->return_type(); |
221 | 11 | } else if (!IsCompatible(*return_type, compatible_operator->return_type())) { |
222 | 3 | return STATUS(InvalidArgument, |
223 | 3 | Substitute("Return-type mismatch in call to builtin function '$0'", ql_name)); |
224 | 3 | } |
225 | 16 | } |
226 | | |
227 | | // Raise error if the function execution was not yet implemented. |
228 | 13 | if (!compatible_operator->op_decl()->implemented()) { |
229 | 0 | return STATUS(NotSupported, |
230 | 0 | Substitute("Builtin function '$0' is not yet implemented", ql_name)); |
231 | 0 | } |
232 | | |
233 | 13 | *found_opcode = compatible_operator->opcode(); |
234 | 13 | *found_decl = compatible_operator->op_decl(); |
235 | 13 | return Status::OK(); |
236 | 13 | } |
237 | | |
238 | | //-------------------------------------------------------------------------------------------------- |
239 | | // Find the builtin opcode, declaration, and return type for a builtin call. |
240 | | // Inputs: Builtin function name and parameter types. |
241 | | // Outputs: opcode and bfdecl. |
242 | | // In/Out parameter: return_type |
243 | | // If return_type is given, check if it is compatible with the declaration. |
244 | | // If not, return_type is an output parameter whose value is the return type of the builtin. |
245 | | Status FindOpcodeByType(const string& ql_name, |
246 | | const std::vector<DataType>& actual_types, |
247 | | BFOpcode *opcode, |
248 | | const BFDecl **bfdecl, |
249 | 22 | DataType *return_type) { |
250 | 22 | auto entry = kBfPgsqlName2Opcode.find(ql_name); |
251 | 22 | if (entry == kBfPgsqlName2Opcode.end()) { |
252 | 1 | VLOG(3) << strings::Substitute("Function '$0' does not exist", ql_name)0 ; |
253 | 1 | return STATUS(NotFound, strings::Substitute("Function '$0' does not exist", ql_name)); |
254 | 1 | } |
255 | | |
256 | | // Seek the correct overload functions in the following order: |
257 | | // - Find the exact signature match. |
258 | | // Example: |
259 | | // . Overload #1: FuncX(int8_t i) would be used for the call FuncX(int8_t(7)). |
260 | | // . Overload #2: FuncX(int16_t i) would be used for the call FuncX(int16_t(7)). |
261 | | // |
262 | | // - For "cast" operator, if exact match is not found, return error. For all other operators, |
263 | | // continue to the next steps. |
264 | | // |
265 | | // - Find the similar signature match. |
266 | | // Example: |
267 | | // . Overload #2: FuncY(int64_t i) would be used for FuncY(int8_t(7)). |
268 | | // int64_t and int8_t are both integer values. |
269 | | // . Overload #1: FuncY(double d) would be used for FuncY(float(7)). |
270 | | // double & float are both floating values. |
271 | | // |
272 | | // - Find the compatible match. Signatures are of convertible datatypes. |
273 | 21 | Status s = FindMatch(ql_name, HasExactTypeSignature, entry->second, actual_types, |
274 | 21 | opcode, bfdecl, return_type); |
275 | 21 | VLOG(3) << "Seek exact match for function call " << ql_name << "(): " << s.ToString()0 ; |
276 | | |
277 | 21 | if (ql_name != kCastFuncName && s.IsNotFound()17 ) { |
278 | 10 | s = FindMatch(ql_name, HasSimilarTypeSignature, entry->second, actual_types, |
279 | 10 | opcode, bfdecl, return_type); |
280 | 10 | VLOG(3) << "Seek similar match for function call " << ql_name << "(): " << s.ToString()0 ; |
281 | | |
282 | 10 | if (s.IsNotFound()) { |
283 | 7 | s = FindMatch(ql_name, HasCompatibleTypeSignature, entry->second, actual_types, |
284 | 7 | opcode, bfdecl, return_type); |
285 | 7 | VLOG(3) << "Seek compatible match for function call " << ql_name << "(): " << s.ToString()0 ; |
286 | 7 | } |
287 | 10 | } |
288 | | |
289 | 21 | return s; |
290 | 22 | } |
291 | | |
292 | | } // namespace bfpg |
293 | | } // namespace yb |