/Users/deen/code/yugabyte-db/src/yb/bfpg/bfpg-test.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | // This is a simple test to verify the correctness of builtin function library. |
14 | | |
15 | | #include <memory> |
16 | | #include <vector> |
17 | | |
18 | | #include "yb/bfpg/bfpg.h" |
19 | | |
20 | | #include "yb/common/ql_value.h" |
21 | | |
22 | | #include "yb/util/test_util.h" |
23 | | |
24 | | namespace yb { |
25 | | namespace bfpg { |
26 | | |
27 | | using std::shared_ptr; |
28 | | using std::make_shared; |
29 | | using std::to_string; |
30 | | using std::vector; |
31 | | //-------------------------------------------------------------------------------------------------- |
32 | | // BFTestValue is a data value to be used with builtin library for both phases - compilation and |
33 | | // execution. Note that the plan is to have two different data structures for two different. |
34 | | // - PGSQL treenode is used during compilation. |
35 | | // - QLValue is used during execution. |
36 | | class BFTestValue : public QLValue { |
37 | | public: |
38 | | typedef std::shared_ptr<BFTestValue> SharedPtr; |
39 | | typedef std::shared_ptr<const BFTestValue> SharedPtrConst; |
40 | | |
41 | 13 | BFTestValue() : QLValue() { |
42 | 13 | ql_type_id_ = DataType::UNKNOWN_DATA; |
43 | 13 | } |
44 | | |
45 | 69 | virtual DataType ql_type_id() const { |
46 | 69 | return ql_type_id_; |
47 | 69 | } |
48 | 49 | virtual void set_ql_type_id(DataType ql_type_id) { |
49 | 49 | ql_type_id_ = ql_type_id; |
50 | 49 | } |
51 | | |
52 | | private: |
53 | | DataType ql_type_id_; |
54 | | }; |
55 | | |
56 | | //-------------------------------------------------------------------------------------------------- |
57 | | // Construct BFApiTest classes from the builtin template. This test is kept to be generic for all |
58 | | // applications, so it defines its own type. For specific application, appropriate datatypes should |
59 | | // be used in its tests. |
60 | | using BFCompileApiTest = BFCompileApi<BFTestValue, BFTestValue>; |
61 | | using BFExecApiTest = BFExecImmediateApi<BFTestValue, BFTestValue>; |
62 | | |
63 | | //-------------------------------------------------------------------------------------------------- |
64 | | // This is the driver for bfpg-test. |
65 | | class BfPgsqlTest : public YBTest { |
66 | | public: |
67 | | // Constructor and destructor. |
68 | 3 | BfPgsqlTest() { |
69 | 3 | } |
70 | | |
71 | 3 | virtual ~BfPgsqlTest() { |
72 | 3 | } |
73 | | |
74 | | //------------------------------------------------------------------------------------------------ |
75 | | // Test start and cleanup functions. |
76 | 3 | void SetUp() override { |
77 | 3 | YBTest::SetUp(); |
78 | 3 | } |
79 | | |
80 | 3 | void TearDown() override { |
81 | 3 | YBTest::TearDown(); |
82 | 3 | } |
83 | | |
84 | | // Convert the param values to datatypes that are specified by "bfdecl". |
85 | | Status ConvertParams(const BFDecl *bfdecl, |
86 | | const vector<BFTestValue::SharedPtr>& params, |
87 | 2 | vector<BFTestValue::SharedPtr> *converted_params) { |
88 | 2 | const auto pcount = params.size(); |
89 | 2 | converted_params->resize(pcount); |
90 | 2 | const std::vector<DataType>& ptypes = bfdecl->param_types(); |
91 | | |
92 | 2 | bool is_variadic = false; |
93 | 2 | vector<BFTestValue::SharedPtr> cast_params(2); |
94 | 6 | for (size_t pindex = 0; pindex < pcount; pindex++) { |
95 | 4 | if (is_variadic || ptypes[pindex] == DataType::TYPEARGS) { |
96 | | // No conversion is needed for the rest of the arguments. |
97 | 0 | is_variadic = true; |
98 | 0 | (*converted_params)[pindex] = params[pindex]; |
99 | |
|
100 | 4 | } else if (params[pindex]->ql_type_id() == ptypes[pindex]) { |
101 | 2 | (*converted_params)[pindex] = params[pindex]; |
102 | | |
103 | 2 | } else { |
104 | | // Casting is needed. |
105 | 2 | BFTestValue::SharedPtr converted_param = make_shared<BFTestValue>(); |
106 | 2 | converted_param->set_ql_type_id(ptypes[pindex]); |
107 | | |
108 | | // Converting params. |
109 | 2 | cast_params[0] = params[pindex]; |
110 | 2 | cast_params[1] = converted_param; |
111 | 2 | RETURN_NOT_OK(BFExecApiTest::ExecPgsqlFunc( |
112 | 2 | bfpg::kCastFuncName, cast_params, converted_param)); |
113 | | |
114 | | // Save converted value. |
115 | 2 | (*converted_params)[pindex] = converted_param; |
116 | 2 | } |
117 | 4 | } |
118 | | |
119 | 2 | return Status::OK(); |
120 | 2 | } |
121 | | }; |
122 | | |
123 | | //-------------------------------------------------------------------------------------------------- |
124 | | // The following test cases generally go through the following steps. |
125 | | // |
126 | | // - Use the BFTestValue functions to set the values and datatypes for parameter. |
127 | | // - Similarly, use the BFTestValue functions to set the datatypes for return result. |
128 | | // NOTE: When the return type is not set, builtin-library will set it. On the other hand, if the |
129 | | // return type is set, the builtin-library will check if the datatype is compatible with the |
130 | | // definition of the builtin function. |
131 | | // - Call FindPgsqlOpcode() to find the opcode. This step should be done during compilation. |
132 | | // - Call ExecPgsqlFunc() to run the builtin function. This step will be done during execution. |
133 | | |
134 | | // Test calls to functions with arguments whose datatypes are an exact matched to the signature. |
135 | 1 | TEST_F(BfPgsqlTest, TestExactMatchSignature) { |
136 | 1 | BFOpcode opcode; |
137 | 1 | const BFDecl *bfdecl; |
138 | | |
139 | 1 | BFTestValue::SharedPtr result = make_shared<BFTestValue>(); |
140 | 1 | BFTestValue::SharedPtr param0 = make_shared<BFTestValue>(); |
141 | 1 | BFTestValue::SharedPtr param1 = make_shared<BFTestValue>(); |
142 | | |
143 | | // Use raw pointer to test the API for raw pointers. |
144 | 1 | vector<BFTestValue::SharedPtr> params = { param0, param1 }; |
145 | | |
146 | | //------------------------------------------------------------------------------------------------ |
147 | | // Test cases of exact match calls for integer functions. |
148 | 1 | param0->set_ql_type_id(DataType::INT64); |
149 | 1 | param1->set_ql_type_id(DataType::INT64); |
150 | | |
151 | | // Test Case 1: Not specify the return type by setting it to NULL. |
152 | | // UNKNOWN = INT64 + INT64. |
153 | | // Set result type to be unknown and let builtin library resolve its type. |
154 | 1 | result->set_ql_type_id(DataType::UNKNOWN_DATA); |
155 | | |
156 | | // Initialize parameter values. |
157 | 1 | int int_val1 = 2133; |
158 | 1 | int int_val2 = 1234; |
159 | 1 | param0->set_int64_value(int_val1); |
160 | 1 | param1->set_int64_value(int_val2); |
161 | | |
162 | | // Find and execute the opcode. |
163 | 1 | ASSERT_OK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
164 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlOpcode(opcode, params, result)); |
165 | | |
166 | | // Write the result to an integer and check the result. |
167 | 1 | int expected_int_result = int_val1 + int_val2; |
168 | 1 | auto return_int_result = result->int64_value(); |
169 | 1 | ASSERT_EQ(return_int_result, expected_int_result); |
170 | | |
171 | | // Test Case 2: The return type is exact match |
172 | | // INT64 = INT64 + INT64. |
173 | | // Similar to test #1, but set result type to INT32 let builtin library does the type-checking. |
174 | 1 | result->set_ql_type_id(DataType::INT64); |
175 | 1 | int_val1 = 4133; |
176 | 1 | int_val2 = 7234; |
177 | 1 | param0->set_int64_value(int_val1); |
178 | 1 | param1->set_int64_value(int_val2); |
179 | 1 | ASSERT_OK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
180 | 1 | ASSERT_EQ(result->ql_type_id(), DataType::INT64); |
181 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlOpcode(opcode, params, result)); |
182 | | |
183 | 1 | expected_int_result = int_val1 + int_val2; |
184 | 1 | return_int_result = result->int64_value(); |
185 | 1 | ASSERT_EQ(return_int_result, expected_int_result); |
186 | | |
187 | | // Test Case 3: The return type is compatible to force a conversion for return value. |
188 | | // INT16 = INT64 + INT64. |
189 | | // Similar to test #1, but set result type to INT16. |
190 | 1 | BFTestValue::SharedPtr temp_result = make_shared<BFTestValue>(); |
191 | 1 | temp_result->set_ql_type_id(DataType::UNKNOWN_DATA); |
192 | | |
193 | 1 | int_val1 = 3133; |
194 | 1 | int_val2 = 9234; |
195 | 1 | param0->set_int64_value(int_val1); |
196 | 1 | param1->set_int64_value(int_val2); |
197 | 1 | ASSERT_OK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, temp_result)); |
198 | 1 | ASSERT_EQ(temp_result->ql_type_id(), DataType::INT64); |
199 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlOpcode(opcode, params, temp_result)); |
200 | | |
201 | | // Convert int64 value (temp_result) to int16 value (result). |
202 | 1 | result->set_ql_type_id(DataType::INT16); |
203 | 1 | vector<BFTestValue::SharedPtr> temp_params = { temp_result, result }; |
204 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlFunc(bfpg::kCastFuncName, temp_params, result)); |
205 | | |
206 | | // Check result. |
207 | 1 | expected_int_result = int_val1 + int_val2; |
208 | 1 | return_int_result = result->int16_value(); |
209 | 1 | ASSERT_EQ(return_int_result, expected_int_result); |
210 | | |
211 | | //------------------------------------------------------------------------------------------------ |
212 | | // Test exact match calls for double functions. |
213 | | // The steps in this test is the same as for integer. |
214 | | // Parameter type is now set to double. |
215 | 1 | param0->set_ql_type_id(DataType::DOUBLE); |
216 | 1 | param1->set_ql_type_id(DataType::DOUBLE); |
217 | | |
218 | | // - Case 1: Not specify the return type by setting it to NULL. |
219 | | // UNKNOWN = DOUBLE + DOUBLE. |
220 | 1 | result->set_ql_type_id(DataType::UNKNOWN_DATA); |
221 | | |
222 | 1 | double d_val1 = 777.7; |
223 | 1 | double d_val2 = 1111.7; |
224 | 1 | param0->set_double_value(d_val1); |
225 | 1 | param1->set_double_value(d_val2); |
226 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlFunc("+", params, result)); |
227 | 1 | ASSERT_EQ(result->ql_type_id(), DataType::DOUBLE); |
228 | | |
229 | | // Write the return value to an int so that we can run EQ check. |
230 | 1 | expected_int_result = d_val1 + d_val2; |
231 | 1 | return_int_result = result->double_value(); |
232 | 1 | ASSERT_EQ(return_int_result, expected_int_result); |
233 | | |
234 | | // - Case 2: Have exact match return type. |
235 | | // DOUBLE = DOUBLE + DOUBLE. |
236 | 1 | result->set_ql_type_id(DataType::DOUBLE); |
237 | | |
238 | 1 | d_val1 = 999.9; |
239 | 1 | d_val2 = 3333.3; |
240 | 1 | param0->set_double_value(d_val1); |
241 | 1 | param1->set_double_value(d_val2); |
242 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlFunc("+", params, result)); |
243 | | |
244 | 1 | expected_int_result = d_val1 + d_val2; |
245 | 1 | return_int_result = result->double_value(); |
246 | 1 | ASSERT_EQ(return_int_result, expected_int_result); |
247 | | |
248 | | // - Case 3: Have compatible return type. |
249 | | // FLOAT = DOUBLE + DOUBLE. |
250 | 1 | d_val1 = 888.9; |
251 | 1 | d_val2 = 8888.3; |
252 | 1 | param0->set_double_value(d_val1); |
253 | 1 | param1->set_double_value(d_val2); |
254 | | |
255 | | // Execute (double + double) and convert double(temp_result) to float(result). |
256 | 1 | result->set_ql_type_id(DataType::FLOAT); |
257 | 1 | temp_result->set_ql_type_id(DataType::DOUBLE); |
258 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlFunc("+", params, temp_result)); |
259 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlFunc("cast", temp_params, result)); |
260 | | |
261 | 1 | expected_int_result = d_val1 + d_val2; |
262 | 1 | return_int_result = result->float_value(); |
263 | 1 | ASSERT_EQ(return_int_result, expected_int_result); |
264 | | |
265 | | //------------------------------------------------------------------------------------------------ |
266 | | // Test exact match calls for string functions. |
267 | | // Test case: STRING = STRING + STRING |
268 | 1 | result->set_ql_type_id(DataType::STRING); |
269 | | |
270 | 1 | param0->set_ql_type_id(DataType::STRING); |
271 | 1 | param1->set_ql_type_id(DataType::STRING); |
272 | 1 | param0->set_string_value("First part of String. "); |
273 | 1 | param1->set_string_value("Second part of String."); |
274 | | |
275 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlFunc("+", params, result)); |
276 | 1 | ASSERT_EQ(result->string_value(), "First part of String. Second part of String."); |
277 | 1 | } |
278 | | |
279 | | // Test calls to functions with arguments whose datatypes are convertible but not an exact match to |
280 | | // the function signature. |
281 | 1 | TEST_F(BfPgsqlTest, TestCompatibleSignature) { |
282 | 1 | BFOpcode opcode; |
283 | 1 | const BFDecl *bfdecl; |
284 | | |
285 | 1 | BFTestValue::SharedPtr result = make_shared<BFTestValue>(); |
286 | 1 | BFTestValue::SharedPtr param0 = make_shared<BFTestValue>(); |
287 | 1 | BFTestValue::SharedPtr param1 = make_shared<BFTestValue>(); |
288 | | |
289 | | // Use shared pointer to test the API for shared_ptr. |
290 | 1 | vector<BFTestValue::SharedPtr> params = { param0, param1 }; |
291 | 1 | vector<BFTestValue::SharedPtr> converted_params; |
292 | | |
293 | | //------------------------------------------------------------------------------------------------ |
294 | | // Test case: Passing (STRING, INT16) to (STRING, DOUBLE) |
295 | | |
296 | | // Set result type to be unknown and let builtin library resolve its type. |
297 | 1 | result->set_ql_type_id(DataType::UNKNOWN_DATA); |
298 | | |
299 | | // Initialize parameter datatypes and values. |
300 | 1 | param0->set_ql_type_id(DataType::STRING); |
301 | 1 | param1->set_ql_type_id(DataType::INT16); |
302 | 1 | param0->set_string_value("The value is "); |
303 | 1 | param1->set_int16_value(100); |
304 | | |
305 | | // Find the opcode. |
306 | 1 | ASSERT_OK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
307 | 1 | ASSERT_OK(ConvertParams(bfdecl, params, &converted_params)); |
308 | | |
309 | | // Execute the opcode. |
310 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlOpcode(opcode, converted_params, result)); |
311 | | |
312 | | // Write the result to a string and check the result. |
313 | 1 | string expected_result = string("The value is ") + to_string(100.); |
314 | 1 | string return_result = result->string_value(); |
315 | 1 | ASSERT_EQ(return_result, expected_result); |
316 | | |
317 | | //------------------------------------------------------------------------------------------------ |
318 | | // Test case: Passing (INT64, STRING) to (DOUBLE, STRING) |
319 | | |
320 | | // Set result type to be unknown and let builtin library resolve its type. |
321 | 1 | result->set_ql_type_id(DataType::UNKNOWN_DATA); |
322 | | |
323 | | // Initialize parameter datatypes and values. |
324 | 1 | param0->set_ql_type_id(DataType::INT64); |
325 | 1 | param1->set_ql_type_id(DataType::STRING); |
326 | 1 | param0->set_int64_value(100); |
327 | 1 | param1->set_string_value(" is the value"); |
328 | | |
329 | | // Find the opcode. |
330 | 1 | ASSERT_OK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
331 | 1 | ASSERT_OK(ConvertParams(bfdecl, params, &converted_params)); |
332 | | |
333 | | // Execute the opcode. |
334 | 1 | ASSERT_OK(BFExecApiTest::ExecPgsqlOpcode(opcode, converted_params, result)); |
335 | | |
336 | | // Write the result to a string and check the result. |
337 | 1 | expected_result = to_string(100.) + string(" is the value"); |
338 | 1 | return_result = result->string_value(); |
339 | 1 | ASSERT_EQ(return_result, expected_result); |
340 | 1 | } |
341 | | |
342 | | // Test bad function calls. |
343 | 1 | TEST_F(BfPgsqlTest, TestErroneousFuncCalls) { |
344 | 1 | BFOpcode opcode; |
345 | 1 | const BFDecl *bfdecl; |
346 | | |
347 | 1 | BFTestValue::SharedPtr result = make_shared<BFTestValue>(); |
348 | 1 | vector<BFTestValue::SharedPtr> params; |
349 | | |
350 | | // Invalid function name. |
351 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("wrong_name", params, &opcode, &bfdecl, result)); |
352 | | |
353 | | //------------------------------------------------------------------------------------------------ |
354 | | // Test for invalid parameter count. |
355 | | // Passing 0 argument to '+', which takes exactly 2 arguments. |
356 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
357 | | |
358 | | // Passing 1 argument to '+', which takes exactly 2 arguments. |
359 | 1 | BFTestValue::SharedPtr param0 = make_shared<BFTestValue>(); |
360 | 1 | params.push_back(param0); |
361 | 1 | param0->set_ql_type_id(DataType::INT32); |
362 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
363 | | |
364 | 1 | BFTestValue::SharedPtr param1 = make_shared<BFTestValue>(); |
365 | 1 | param1->set_ql_type_id(DataType::INT32); |
366 | 1 | params.push_back(param1); |
367 | | |
368 | | // Passing 3 arguments to '+', which takes exactly 2 arguments. |
369 | 1 | BFTestValue::SharedPtr param2 = make_shared<BFTestValue>(); |
370 | 1 | param2->set_ql_type_id(DataType::INT32); |
371 | 1 | params.push_back(param2); |
372 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
373 | | |
374 | | //------------------------------------------------------------------------------------------------ |
375 | | // Test for invalid parameter types. |
376 | 1 | params.resize(2); |
377 | 1 | param0->set_ql_type_id(DataType::INT32); |
378 | 1 | param1->set_ql_type_id(DataType::BOOL); |
379 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
380 | | |
381 | 1 | param0->set_ql_type_id(DataType::BOOL); |
382 | 1 | param1->set_ql_type_id(DataType::INT32); |
383 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
384 | | |
385 | | //------------------------------------------------------------------------------------------------ |
386 | | // Test for invalid return type. |
387 | | // The builtin call will set the return-type after its evaluate the function calls. Howver, if |
388 | | // the return_type is set by application, the builtin-call will check if the given return type |
389 | | // is compatible. |
390 | 1 | param0->set_ql_type_id(DataType::INT32); |
391 | 1 | param1->set_ql_type_id(DataType::INT32); |
392 | | |
393 | 1 | result->set_ql_type_id(DataType::STRING); |
394 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
395 | | |
396 | 1 | result->set_ql_type_id(DataType::BOOL); |
397 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
398 | | |
399 | | //------------------------------------------------------------------------------------------------ |
400 | | // Test for ambiguous signature - Too many builtin match the signature of a function call. |
401 | | // The following can be matched with both signature INT(INT, INT) and DOUBLE(DOUBLE, DOUBLE). |
402 | 1 | param0->set_ql_type_id(DataType::INT8); |
403 | 1 | param1->set_ql_type_id(DataType::INT8); |
404 | 1 | ASSERT_NOK(BFCompileApiTest::FindPgsqlOpcode("+", params, &opcode, &bfdecl, result)); |
405 | 1 | } |
406 | | |
407 | | } // namespace bfpg |
408 | | } // namespace yb |