1 /**
2 * @file unit-QueryCondition.cc
3 *
4 * @section LICENSE
5 *
6 * The MIT License
7 *
8 * @copyright Copyright (c) 2021 TileDB, Inc.
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of this software and associated documentation files (the "Software"), to deal
12 * in the Software without restriction, including without limitation the rights
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 * copies of the Software, and to permit persons to whom the Software is
15 * furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 * THE SOFTWARE.
27 *
28 * @section DESCRIPTION
29 *
30 * Tests the `QueryCondition` class.
31 */
32
33 #include "tiledb/sm/array_schema/array_schema.h"
34 #include "tiledb/sm/array_schema/attribute.h"
35 #include "tiledb/sm/array_schema/dimension.h"
36 #include "tiledb/sm/array_schema/domain.h"
37 #include "tiledb/sm/enums/datatype.h"
38 #include "tiledb/sm/enums/query_condition_combination_op.h"
39 #include "tiledb/sm/enums/query_condition_op.h"
40 #include "tiledb/sm/query/query_condition.h"
41
42 #include <catch.hpp>
43 #include <iostream>
44
45 using namespace tiledb::sm;
46
47 TEST_CASE(
48 "QueryCondition: Test default constructor",
49 "[QueryCondition][default_constructor]") {
50 QueryCondition query_condition;
51 REQUIRE(query_condition.empty());
52 REQUIRE(query_condition.field_names().empty());
53
54 ArraySchema array_schema;
55 std::vector<ResultCellSlab> result_cell_slabs;
56 REQUIRE(query_condition.apply(&array_schema, &result_cell_slabs, 1).ok());
57 }
58
59 TEST_CASE("QueryCondition: Test init", "[QueryCondition][value_constructor]") {
60 std::string field_name = "foo";
61 int value = 5;
62
63 QueryCondition query_condition;
64 REQUIRE(query_condition
65 .init(
66 std::string(field_name),
67 &value,
68 sizeof(value),
69 QueryConditionOp::LT)
70 .ok());
71 REQUIRE(!query_condition.empty());
72 REQUIRE(!query_condition.field_names().empty());
73 REQUIRE(query_condition.field_names().count(field_name) == 1);
74 }
75
76 TEST_CASE(
77 "QueryCondition: Test copy constructor",
78 "[QueryCondition][copy_constructor]") {
79 std::string field_name = "foo";
80 int value = 5;
81
82 QueryCondition query_condition1;
83 REQUIRE(query_condition1
84 .init(
85 std::string(field_name),
86 &value,
87 sizeof(value),
88 QueryConditionOp::LT)
89 .ok());
90 QueryCondition query_condition2(query_condition1);
91 REQUIRE(!query_condition2.empty());
92 REQUIRE(!query_condition2.field_names().empty());
93 REQUIRE(query_condition2.field_names().count(field_name) == 1);
94 }
95
96 TEST_CASE(
97 "QueryCondition: Test move constructor",
98 "[QueryCondition][move_constructor]") {
99 std::string field_name = "foo";
100 int value = 5;
101
102 QueryCondition query_condition1;
103 REQUIRE(query_condition1
104 .init(
105 std::string(field_name),
106 &value,
107 sizeof(value),
108 QueryConditionOp::LT)
109 .ok());
110 QueryCondition query_condition2(std::move(query_condition1));
111 REQUIRE(!query_condition2.empty());
112 REQUIRE(!query_condition2.field_names().empty());
113 REQUIRE(query_condition2.field_names().count(field_name) == 1);
114 }
115
116 TEST_CASE(
117 "QueryCondition: Test assignment operator",
118 "[QueryCondition][assignment_operator]") {
119 std::string field_name = "foo";
120 int value = 5;
121
122 QueryCondition query_condition1;
123 REQUIRE(query_condition1
124 .init(
125 std::string(field_name),
126 &value,
127 sizeof(value),
128 QueryConditionOp::LT)
129 .ok());
130 QueryCondition query_condition2;
131 query_condition2 = query_condition1;
132 REQUIRE(!query_condition2.empty());
133 REQUIRE(!query_condition2.field_names().empty());
134 REQUIRE(query_condition2.field_names().count(field_name) == 1);
135 }
136
137 TEST_CASE(
138 "QueryCondition: Test move-assignment operator",
139 "[QueryCondition][move_assignment_operator]") {
140 std::string field_name = "foo";
141 int value = 5;
142
143 QueryCondition query_condition1;
144 REQUIRE(query_condition1
145 .init(
146 std::string(field_name),
147 &value,
148 sizeof(value),
149 QueryConditionOp::LT)
150 .ok());
151 QueryCondition query_condition2;
152 query_condition2 = std::move(query_condition1);
153 REQUIRE(!query_condition2.empty());
154 REQUIRE(!query_condition2.field_names().empty());
155 REQUIRE(query_condition2.field_names().count(field_name) == 1);
156 }
157
158 /**
159 * Tests a comparison operator on all cells in a tile.
160 *
161 * @param op The relational query condition operator.
162 * @param field_name The attribute name in the tile.
163 * @param cells The number of cells in the tile.
164 * @param result_tile The result tile.
165 * @param values The values written to the tile.
166 */
167 template <typename T>
168 void test_apply_cells(
169 const QueryConditionOp op,
170 const std::string& field_name,
171 const uint64_t cells,
172 ArraySchema* const array_schema,
173 ResultTile* const result_tile,
174 void* values);
175
176 /**
177 * C-string template-specialization for `test_apply_cells`.
178 */
179 template <>
test_apply_cells(const QueryConditionOp op,const std::string & field_name,const uint64_t cells,ArraySchema * const array_schema,ResultTile * const result_tile,void * values)180 void test_apply_cells<char*>(
181 const QueryConditionOp op,
182 const std::string& field_name,
183 const uint64_t cells,
184 ArraySchema* const array_schema,
185 ResultTile* const result_tile,
186 void* values) {
187 const char* const cmp_value = "ae";
188 QueryCondition query_condition;
189 REQUIRE(query_condition
190 .init(std::string(field_name), cmp_value, 2 * sizeof(char), op)
191 .ok());
192 // Run Check
193 REQUIRE(query_condition.check(array_schema).ok());
194
195 bool nullable = array_schema->attribute(field_name)->nullable();
196
197 // Build expected indexes of cells that meet the query condition
198 // criteria.
199 std::vector<uint64_t> expected_cell_idx_vec;
200 for (uint64_t i = 0; i < cells; ++i) {
201 if (nullable && (i % 2 == 0))
202 continue;
203
204 switch (op) {
205 case QueryConditionOp::LT:
206 if (std::string(&static_cast<char*>(values)[2 * i], 2) <
207 std::string(cmp_value, 2))
208 expected_cell_idx_vec.emplace_back(i);
209 break;
210 case QueryConditionOp::LE:
211 if (std::string(&static_cast<char*>(values)[2 * i], 2) <=
212 std::string(cmp_value, 2))
213 expected_cell_idx_vec.emplace_back(i);
214 break;
215 case QueryConditionOp::GT:
216 if (std::string(&static_cast<char*>(values)[2 * i], 2) >
217 std::string(cmp_value, 2))
218 expected_cell_idx_vec.emplace_back(i);
219 break;
220 case QueryConditionOp::GE:
221 if (std::string(&static_cast<char*>(values)[2 * i], 2) >=
222 std::string(cmp_value, 2))
223 expected_cell_idx_vec.emplace_back(i);
224 break;
225 case QueryConditionOp::EQ:
226 if (std::string(&static_cast<char*>(values)[2 * i], 2) ==
227 std::string(cmp_value, 2))
228 expected_cell_idx_vec.emplace_back(i);
229 break;
230 case QueryConditionOp::NE:
231 if (std::string(&static_cast<char*>(values)[2 * i], 2) !=
232 std::string(cmp_value, 2))
233 expected_cell_idx_vec.emplace_back(i);
234 break;
235 default:
236 REQUIRE(false);
237 }
238 }
239
240 // Apply the query condition.
241 ResultCellSlab result_cell_slab(result_tile, 0, cells);
242 std::vector<ResultCellSlab> result_cell_slabs;
243 result_cell_slabs.emplace_back(std::move(result_cell_slab));
244 REQUIRE(query_condition.apply(array_schema, &result_cell_slabs, 1).ok());
245
246 // Verify the result cell slabs contain the expected cells.
247 auto expected_iter = expected_cell_idx_vec.begin();
248 for (const auto& result_cell_slab : result_cell_slabs) {
249 for (uint64_t cell_idx = result_cell_slab.start_;
250 cell_idx < (result_cell_slab.start_ + result_cell_slab.length_);
251 ++cell_idx) {
252 REQUIRE(*expected_iter == cell_idx);
253 ++expected_iter;
254 }
255 }
256
257 if (nullable) {
258 if (op == QueryConditionOp::EQ || op == QueryConditionOp::NE) {
259 const uint64_t eq = op == QueryConditionOp::EQ ? 0 : 1;
260 QueryCondition query_condition_eq_null;
261 REQUIRE(
262 query_condition_eq_null.init(std::string(field_name), nullptr, 0, op)
263 .ok());
264 // Run Check
265 REQUIRE(query_condition_eq_null.check(array_schema).ok());
266
267 ResultCellSlab result_cell_slab_eq_null(result_tile, 0, cells);
268 std::vector<ResultCellSlab> result_cell_slabs_eq_null;
269 result_cell_slabs_eq_null.emplace_back(
270 std::move(result_cell_slab_eq_null));
271 REQUIRE(query_condition_eq_null
272 .apply(array_schema, &result_cell_slabs_eq_null, 1)
273 .ok());
274
275 REQUIRE(result_cell_slabs_eq_null.size() == (cells / 2));
276 for (const auto& result_cell_slab : result_cell_slabs_eq_null) {
277 REQUIRE((result_cell_slab.start_ % 2) == eq);
278 REQUIRE(result_cell_slab.length_ == 1);
279 }
280 }
281
282 return;
283 }
284
285 // Fetch the fill value.
286 const void* fill_value;
287 uint64_t fill_value_size;
288 array_schema->attribute(field_name)
289 ->get_fill_value(&fill_value, &fill_value_size);
290 REQUIRE(fill_value_size == 2 * sizeof(char));
291
292 // Build expected indexes of cells that meet the query condition
293 // criteria with the fill value;
294 std::vector<uint64_t> fill_expected_cell_idx_vec;
295 for (uint64_t i = 0; i < cells; ++i) {
296 switch (op) {
297 case QueryConditionOp::LT:
298 if (std::string(static_cast<const char*>(fill_value), 2) <
299 std::string(cmp_value, 2))
300 fill_expected_cell_idx_vec.emplace_back(i);
301 break;
302 case QueryConditionOp::LE:
303 if (std::string(static_cast<const char*>(fill_value), 2) <=
304 std::string(cmp_value, 2))
305 fill_expected_cell_idx_vec.emplace_back(i);
306 break;
307 case QueryConditionOp::GT:
308 if (std::string(static_cast<const char*>(fill_value), 2) >
309 std::string(cmp_value, 2))
310 fill_expected_cell_idx_vec.emplace_back(i);
311 break;
312 case QueryConditionOp::GE:
313 if (std::string(static_cast<const char*>(fill_value), 2) >=
314 std::string(cmp_value, 2))
315 fill_expected_cell_idx_vec.emplace_back(i);
316 break;
317 case QueryConditionOp::EQ:
318 if (std::string(static_cast<const char*>(fill_value), 2) ==
319 std::string(cmp_value, 2))
320 fill_expected_cell_idx_vec.emplace_back(i);
321 break;
322 case QueryConditionOp::NE:
323 if (std::string(static_cast<const char*>(fill_value), 2) !=
324 std::string(cmp_value, 2))
325 fill_expected_cell_idx_vec.emplace_back(i);
326 break;
327 default:
328 REQUIRE(false);
329 }
330 }
331
332 // Apply the query condition with an empty result tile, which will
333 // use the fill value.
334 ResultCellSlab fill_result_cell_slab(nullptr, 0, cells);
335 std::vector<ResultCellSlab> fill_result_cell_slabs;
336 fill_result_cell_slabs.emplace_back(std::move(fill_result_cell_slab));
337 REQUIRE(query_condition.apply(array_schema, &fill_result_cell_slabs, 1).ok());
338
339 // Verify the fill result cell slabs contain the expected cells.
340 auto fill_expected_iter = fill_expected_cell_idx_vec.begin();
341 for (const auto& fill_result_cell_slab : fill_result_cell_slabs) {
342 for (uint64_t cell_idx = fill_result_cell_slab.start_;
343 cell_idx <
344 (fill_result_cell_slab.start_ + fill_result_cell_slab.length_);
345 ++cell_idx) {
346 REQUIRE(*fill_expected_iter == cell_idx);
347 ++fill_expected_iter;
348 }
349 }
350 }
351
352 /**
353 * Non-specialized template type for `test_apply_cells`.
354 */
355 template <typename T>
test_apply_cells(const QueryConditionOp op,const std::string & field_name,const uint64_t cells,ArraySchema * const array_schema,ResultTile * const result_tile,void * values)356 void test_apply_cells(
357 const QueryConditionOp op,
358 const std::string& field_name,
359 const uint64_t cells,
360 ArraySchema* const array_schema,
361 ResultTile* const result_tile,
362 void* values) {
363 const T cmp_value = 5;
364 QueryCondition query_condition;
365 REQUIRE(
366 query_condition.init(std::string(field_name), &cmp_value, sizeof(T), op)
367 .ok());
368 // Run Check
369 REQUIRE(query_condition.check(array_schema).ok());
370
371 // Build expected indexes of cells that meet the query condition
372 // criteria.
373 std::vector<uint64_t> expected_cell_idx_vec;
374 for (uint64_t i = 0; i < cells; ++i) {
375 switch (op) {
376 case QueryConditionOp::LT:
377 if (static_cast<T*>(values)[i] < cmp_value)
378 expected_cell_idx_vec.emplace_back(i);
379 break;
380 case QueryConditionOp::LE:
381 if (static_cast<T*>(values)[i] <= cmp_value)
382 expected_cell_idx_vec.emplace_back(i);
383 break;
384 case QueryConditionOp::GT:
385 if (static_cast<T*>(values)[i] > cmp_value)
386 expected_cell_idx_vec.emplace_back(i);
387 break;
388 case QueryConditionOp::GE:
389 if (static_cast<T*>(values)[i] >= cmp_value)
390 expected_cell_idx_vec.emplace_back(i);
391 break;
392 case QueryConditionOp::EQ:
393 if (static_cast<T*>(values)[i] == cmp_value)
394 expected_cell_idx_vec.emplace_back(i);
395 break;
396 case QueryConditionOp::NE:
397 if (static_cast<T*>(values)[i] != cmp_value)
398 expected_cell_idx_vec.emplace_back(i);
399 break;
400 default:
401 REQUIRE(false);
402 }
403 }
404
405 // Apply the query condition.
406 ResultCellSlab result_cell_slab(result_tile, 0, cells);
407 std::vector<ResultCellSlab> result_cell_slabs;
408 result_cell_slabs.emplace_back(std::move(result_cell_slab));
409 REQUIRE(query_condition.apply(array_schema, &result_cell_slabs, 1).ok());
410
411 // Verify the result cell slabs contain the expected cells.
412 auto expected_iter = expected_cell_idx_vec.begin();
413 for (const auto& rcs : result_cell_slabs) {
414 for (uint64_t cell_idx = rcs.start_; cell_idx < (rcs.start_ + rcs.length_);
415 ++cell_idx) {
416 REQUIRE(*expected_iter == cell_idx);
417 ++expected_iter;
418 }
419 }
420
421 // Fetch the fill value.
422 const void* fill_value;
423 uint64_t fill_value_size;
424 array_schema->attribute(field_name)
425 ->get_fill_value(&fill_value, &fill_value_size);
426 REQUIRE(fill_value_size == sizeof(T));
427
428 // Build expected indexes of cells that meet the query condition
429 // criteria with the fill value;
430 std::vector<uint64_t> fill_expected_cell_idx_vec;
431 for (uint64_t i = 0; i < cells; ++i) {
432 switch (op) {
433 case QueryConditionOp::LT:
434 if (*static_cast<const T*>(fill_value) < cmp_value)
435 fill_expected_cell_idx_vec.emplace_back(i);
436 break;
437 case QueryConditionOp::LE:
438 if (*static_cast<const T*>(fill_value) <= cmp_value)
439 fill_expected_cell_idx_vec.emplace_back(i);
440 break;
441 case QueryConditionOp::GT:
442 if (*static_cast<const T*>(fill_value) > cmp_value)
443 fill_expected_cell_idx_vec.emplace_back(i);
444 break;
445 case QueryConditionOp::GE:
446 if (*static_cast<const T*>(fill_value) >= cmp_value)
447 fill_expected_cell_idx_vec.emplace_back(i);
448 break;
449 case QueryConditionOp::EQ:
450 if (*static_cast<const T*>(fill_value) == cmp_value)
451 fill_expected_cell_idx_vec.emplace_back(i);
452 break;
453 case QueryConditionOp::NE:
454 if (*static_cast<const T*>(fill_value) != cmp_value)
455 fill_expected_cell_idx_vec.emplace_back(i);
456 break;
457 default:
458 REQUIRE(false);
459 }
460 }
461
462 // Apply the query condition with an empty result tile, which will
463 // use the fill value.
464 ResultCellSlab fill_result_cell_slab(nullptr, 0, cells);
465 std::vector<ResultCellSlab> fill_result_cell_slabs;
466 fill_result_cell_slabs.emplace_back(std::move(fill_result_cell_slab));
467 REQUIRE(query_condition.apply(array_schema, &fill_result_cell_slabs, 1).ok());
468
469 // Verify the fill result cell slabs contain the expected cells.
470 auto fill_expected_iter = fill_expected_cell_idx_vec.begin();
471 for (const auto& fill_result_cell_slab : fill_result_cell_slabs) {
472 for (uint64_t cell_idx = fill_result_cell_slab.start_;
473 cell_idx <
474 (fill_result_cell_slab.start_ + fill_result_cell_slab.length_);
475 ++cell_idx) {
476 REQUIRE(*fill_expected_iter == cell_idx);
477 ++fill_expected_iter;
478 }
479 }
480 }
481
482 /**
483 * Tests each comparison operator on all cells in a tile.
484 *
485 * @param field_name The attribute name in the tile.
486 * @param cells The number of cells in the tile.
487 * @param result_tile The result tile.
488 * @param values The values written to the tile.
489 */
490 template <typename T>
test_apply_operators(const std::string & field_name,const uint64_t cells,ArraySchema * const array_schema,ResultTile * const result_tile,void * values)491 void test_apply_operators(
492 const std::string& field_name,
493 const uint64_t cells,
494 ArraySchema* const array_schema,
495 ResultTile* const result_tile,
496 void* values) {
497 test_apply_cells<T>(
498 QueryConditionOp::LT,
499 field_name,
500 cells,
501 array_schema,
502 result_tile,
503 values);
504 test_apply_cells<T>(
505 QueryConditionOp::LE,
506 field_name,
507 cells,
508 array_schema,
509 result_tile,
510 values);
511 test_apply_cells<T>(
512 QueryConditionOp::GT,
513 field_name,
514 cells,
515 array_schema,
516 result_tile,
517 values);
518 test_apply_cells<T>(
519 QueryConditionOp::GE,
520 field_name,
521 cells,
522 array_schema,
523 result_tile,
524 values);
525 test_apply_cells<T>(
526 QueryConditionOp::EQ,
527 field_name,
528 cells,
529 array_schema,
530 result_tile,
531 values);
532 test_apply_cells<T>(
533 QueryConditionOp::NE,
534 field_name,
535 cells,
536 array_schema,
537 result_tile,
538 values);
539 }
540
541 /**
542 * Populates a tile and tests query condition comparisons against
543 * each cell.
544 *
545 * @param field_name The attribute name in the tile.
546 * @param cells The number of cells in the tile.
547 * @param type The TILEDB data type of the attribute.
548 * @param array_schema The array schema.
549 * @param result_tile The result tile.
550 */
551 template <typename T>
552 void test_apply_tile(
553 const std::string& field_name,
554 uint64_t cells,
555 Datatype type,
556 ArraySchema* array_schema,
557 ResultTile* result_tile);
558
559 /**
560 * C-string template-specialization for `test_apply_tile`.
561 */
562 template <>
test_apply_tile(const std::string & field_name,const uint64_t cells,const Datatype type,ArraySchema * const array_schema,ResultTile * const result_tile)563 void test_apply_tile<char*>(
564 const std::string& field_name,
565 const uint64_t cells,
566 const Datatype type,
567 ArraySchema* const array_schema,
568 ResultTile* const result_tile) {
569 ResultTile::TileTuple* const tile_tuple = result_tile->tile_tuple(field_name);
570
571 bool var_size = array_schema->attribute(field_name)->var_size();
572 bool nullable = array_schema->attribute(field_name)->nullable();
573 Tile* const tile =
574 var_size ? &std::get<1>(*tile_tuple) : &std::get<0>(*tile_tuple);
575
576 REQUIRE(tile->init_unfiltered(
577 constants::format_version,
578 type,
579 2 * cells * sizeof(char),
580 2 * sizeof(char),
581 0)
582 .ok());
583
584 char* values = static_cast<char*>(malloc(sizeof(char) * 2 * cells));
585 for (uint64_t i = 0; i < cells; ++i) {
586 values[i * 2] = 'a';
587 values[(i * 2) + 1] = 'a' + static_cast<char>(i);
588 }
589 REQUIRE(tile->write(values, 2 * cells * sizeof(char)).ok());
590
591 if (var_size) {
592 Tile* const tile_offsets = &std::get<0>(*tile_tuple);
593 REQUIRE(tile_offsets
594 ->init_unfiltered(
595 constants::format_version,
596 constants::cell_var_offset_type,
597 10 * constants::cell_var_offset_size,
598 constants::cell_var_offset_size,
599 0)
600 .ok());
601
602 uint64_t* offsets =
603 static_cast<uint64_t*>(malloc(sizeof(uint64_t) * cells));
604 uint64_t offset = 0;
605 for (uint64_t i = 0; i < cells; ++i) {
606 offsets[i] = offset;
607 offset += 2;
608 }
609 REQUIRE(tile_offsets->write(offsets, cells * sizeof(uint64_t)).ok());
610 }
611
612 if (nullable) {
613 Tile* const tile_validity = &std::get<2>(*tile_tuple);
614 REQUIRE(tile_validity
615 ->init_unfiltered(
616 constants::format_version,
617 constants::cell_validity_type,
618 10 * constants::cell_validity_size,
619 constants::cell_validity_size,
620 0)
621 .ok());
622
623 uint8_t* validity = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * cells));
624 for (uint64_t i = 0; i < cells; ++i) {
625 validity[i] = i % 2;
626 }
627 REQUIRE(tile_validity->write(validity, cells * sizeof(uint8_t)).ok());
628 }
629
630 test_apply_operators<char*>(
631 field_name, cells, array_schema, result_tile, values);
632
633 free(values);
634 }
635
636 /**
637 * Non-specialized template type for `test_apply_tile`.
638 */
639 template <typename T>
test_apply_tile(const std::string & field_name,const uint64_t cells,const Datatype type,ArraySchema * const array_schema,ResultTile * const result_tile)640 void test_apply_tile(
641 const std::string& field_name,
642 const uint64_t cells,
643 const Datatype type,
644 ArraySchema* const array_schema,
645 ResultTile* const result_tile) {
646 ResultTile::TileTuple* const tile_tuple = result_tile->tile_tuple(field_name);
647 Tile* const tile = &std::get<0>(*tile_tuple);
648
649 REQUIRE(
650 tile->init_unfiltered(
651 constants::format_version, type, cells * sizeof(T), sizeof(T), 0)
652 .ok());
653 T* values = static_cast<T*>(malloc(sizeof(T) * cells));
654 for (uint64_t i = 0; i < cells; ++i) {
655 values[i] = static_cast<T>(i);
656 }
657 REQUIRE(tile->write(values, cells * sizeof(T)).ok());
658
659 test_apply_operators<T>(field_name, cells, array_schema, result_tile, values);
660
661 free(values);
662 }
663
664 /**
665 * Constructs a tile and tests query condition comparisons against
666 * each cell.
667 *
668 * @param type The TILEDB data type of the attribute.
669 * @param var_size Run the test with variable size attribute.
670 * @param nullable Run the test with nullable attribute.
671 */
672 template <typename T>
673 void test_apply(
674 const Datatype type, bool var_size = false, bool nullable = false);
675
676 /**
677 * C-string template-specialization for `test_apply`.
678 */
679 template <>
test_apply(const Datatype type,bool var_size,bool nullable)680 void test_apply<char*>(const Datatype type, bool var_size, bool nullable) {
681 REQUIRE(type == Datatype::STRING_ASCII);
682
683 const std::string field_name = "foo";
684 const uint64_t cells = 10;
685 const char* fill_value = "ac";
686
687 // Initialize the array schema.
688 ArraySchema array_schema;
689 Attribute attr(field_name, type);
690 REQUIRE(attr.set_nullable(nullable).ok());
691 REQUIRE(attr.set_cell_val_num(var_size ? constants::var_num : 2).ok());
692
693 if (!nullable) {
694 REQUIRE(attr.set_fill_value(fill_value, 2 * sizeof(char)).ok());
695 }
696
697 REQUIRE(array_schema.add_attribute(&attr).ok());
698 Domain domain;
699 Dimension dim("dim1", Datatype::UINT32);
700 uint32_t bounds[2] = {1, cells};
701 Range range(bounds, 2 * sizeof(uint32_t));
702 REQUIRE(dim.set_domain(range).ok());
703 REQUIRE(domain.add_dimension(&dim).ok());
704 REQUIRE(array_schema.set_domain(&domain).ok());
705
706 // Initialize the result tile.
707 ResultTile result_tile(0, 0, &domain);
708 result_tile.init_attr_tile(field_name);
709
710 test_apply_tile<char*>(field_name, cells, type, &array_schema, &result_tile);
711 }
712
713 /**
714 * Non-specialized template type for `test_apply`.
715 */
716 template <typename T>
test_apply(const Datatype type,bool var_size,bool nullable)717 void test_apply(const Datatype type, bool var_size, bool nullable) {
718 (void)var_size;
719 (void)nullable;
720 const std::string field_name = "foo";
721 const uint64_t cells = 10;
722 const T fill_value = 3;
723
724 // Initialize the array schema.
725 ArraySchema array_schema;
726 Attribute attr(field_name, type);
727 REQUIRE(attr.set_cell_val_num(1).ok());
728 REQUIRE(attr.set_fill_value(&fill_value, sizeof(T)).ok());
729 REQUIRE(array_schema.add_attribute(&attr).ok());
730 Domain domain;
731 Dimension dim("dim1", Datatype::UINT32);
732 uint32_t bounds[2] = {1, cells};
733 Range range(bounds, 2 * sizeof(uint32_t));
734 REQUIRE(dim.set_domain(range).ok());
735 REQUIRE(domain.add_dimension(&dim).ok());
736 REQUIRE(array_schema.set_domain(&domain).ok());
737
738 // Initialize the result tile.
739 ResultTile result_tile(0, 0, &domain);
740 result_tile.init_attr_tile(field_name);
741
742 test_apply_tile<T>(field_name, cells, type, &array_schema, &result_tile);
743 }
744
745 TEST_CASE("QueryCondition: Test apply", "[QueryCondition][apply]") {
746 test_apply<int8_t>(Datatype::INT8);
747 test_apply<uint8_t>(Datatype::UINT8);
748 test_apply<int16_t>(Datatype::INT16);
749 test_apply<uint16_t>(Datatype::UINT16);
750 test_apply<int32_t>(Datatype::INT32);
751 test_apply<uint32_t>(Datatype::UINT32);
752 test_apply<int64_t>(Datatype::INT64);
753 test_apply<uint64_t>(Datatype::UINT64);
754 test_apply<float>(Datatype::FLOAT32);
755 test_apply<double>(Datatype::FLOAT64);
756 test_apply<char>(Datatype::CHAR);
757 test_apply<int64_t>(Datatype::DATETIME_YEAR);
758 test_apply<int64_t>(Datatype::DATETIME_MONTH);
759 test_apply<int64_t>(Datatype::DATETIME_WEEK);
760 test_apply<int64_t>(Datatype::DATETIME_DAY);
761 test_apply<int64_t>(Datatype::DATETIME_HR);
762 test_apply<int64_t>(Datatype::DATETIME_MIN);
763 test_apply<int64_t>(Datatype::DATETIME_SEC);
764 test_apply<int64_t>(Datatype::DATETIME_MS);
765 test_apply<int64_t>(Datatype::DATETIME_US);
766 test_apply<int64_t>(Datatype::DATETIME_NS);
767 test_apply<int64_t>(Datatype::DATETIME_PS);
768 test_apply<int64_t>(Datatype::DATETIME_FS);
769 test_apply<int64_t>(Datatype::DATETIME_AS);
770 test_apply<char*>(Datatype::STRING_ASCII);
771 test_apply<char*>(Datatype::STRING_ASCII, true);
772 test_apply<char*>(Datatype::STRING_ASCII, false, true);
773 }
774
775 TEST_CASE(
776 "QueryCondition: Test combinations", "[QueryCondition][combinations]") {
777 const std::string field_name = "foo";
778 const uint64_t cells = 10;
779 const Datatype type = Datatype::UINT64;
780
781 // Initialize the array schema.
782 ArraySchema array_schema;
783 Attribute attr(field_name, type);
784 REQUIRE(array_schema.add_attribute(&attr).ok());
785 Domain domain;
786 Dimension dim("dim1", Datatype::UINT32);
787 uint32_t bounds[2] = {1, cells};
788 Range range(bounds, 2 * sizeof(uint32_t));
789 REQUIRE(dim.set_domain(range).ok());
790 REQUIRE(domain.add_dimension(&dim).ok());
791 REQUIRE(array_schema.set_domain(&domain).ok());
792
793 // Initialize the result tile.
794 ResultTile result_tile(0, 0, &domain);
795 result_tile.init_attr_tile(field_name);
796 ResultTile::TileTuple* const tile_tuple = result_tile.tile_tuple(field_name);
797 Tile* const tile = &std::get<0>(*tile_tuple);
798
799 // Initialize and populate the data tile.
800 REQUIRE(tile->init_unfiltered(
801 constants::format_version,
802 type,
803 cells * sizeof(uint64_t),
804 sizeof(uint64_t),
805 0)
806 .ok());
807 uint64_t* values = static_cast<uint64_t*>(malloc(sizeof(uint64_t) * cells));
808 for (uint64_t i = 0; i < cells; ++i) {
809 values[i] = i;
810 }
811 REQUIRE(tile->write(values, cells * sizeof(uint64_t)).ok());
812
813 // Build a combined query for `> 3 AND <= 6`.
814 uint64_t cmp_value_1 = 3;
815 QueryCondition query_condition_1;
816 REQUIRE(query_condition_1
817 .init(
818 std::string(field_name),
819 &cmp_value_1,
820 sizeof(uint64_t),
821 QueryConditionOp::GT)
822 .ok());
823 // Run Check
824 REQUIRE(query_condition_1.check(&array_schema).ok());
825 uint64_t cmp_value_2 = 6;
826 QueryCondition query_condition_2;
827 REQUIRE(query_condition_2
828 .init(
829 std::string(field_name),
830 &cmp_value_2,
831 sizeof(uint64_t),
832 QueryConditionOp::LE)
833 .ok());
834 // Run Check
835 REQUIRE(query_condition_2.check(&array_schema).ok());
836 QueryCondition query_condition_3;
837 REQUIRE(query_condition_1
838 .combine(
839 query_condition_2,
840 QueryConditionCombinationOp::AND,
841 &query_condition_3)
842 .ok());
843
844 ResultCellSlab result_cell_slab(&result_tile, 0, cells);
845 std::vector<ResultCellSlab> result_cell_slabs;
846 result_cell_slabs.emplace_back(std::move(result_cell_slab));
847
848 REQUIRE(query_condition_3.apply(&array_schema, &result_cell_slabs, 1).ok());
849
850 // Check that the cell slab now contains cell indexes 4, 5, and 6.
851 REQUIRE(result_cell_slabs.size() == 1);
852 REQUIRE(result_cell_slabs[0].start_ == 4);
853 REQUIRE(result_cell_slabs[0].length_ == 3);
854
855 free(values);
856 }
857
858 TEST_CASE(
859 "QueryCondition: Test empty string", "[QueryCondition][empty_string]") {
860 const std::string field_name = "foo";
861 const uint64_t cells = 10;
862 const char* fill_value = "ac";
863 const Datatype type = Datatype::STRING_ASCII;
864 bool var_size = true;
865 bool nullable = GENERATE(true, false);
866 QueryConditionOp op = GENERATE(QueryConditionOp::NE, QueryConditionOp::EQ);
867
868 // Initialize the array schema.
869 ArraySchema array_schema;
870 Attribute attr(field_name, type);
871 REQUIRE(attr.set_nullable(nullable).ok());
872 REQUIRE(attr.set_cell_val_num(var_size ? constants::var_num : 2).ok());
873
874 if (!nullable) {
875 REQUIRE(attr.set_fill_value(fill_value, 2 * sizeof(char)).ok());
876 }
877
878 REQUIRE(array_schema.add_attribute(&attr).ok());
879 Domain domain;
880 Dimension dim("dim1", Datatype::UINT32);
881 uint32_t bounds[2] = {1, cells};
882 Range range(bounds, 2 * sizeof(uint32_t));
883 REQUIRE(dim.set_domain(range).ok());
884 REQUIRE(domain.add_dimension(&dim).ok());
885 REQUIRE(array_schema.set_domain(&domain).ok());
886
887 // Initialize the result tile.
888 ResultTile result_tile(0, 0, &domain);
889 result_tile.init_attr_tile(field_name);
890
891 ResultTile::TileTuple* const tile_tuple = result_tile.tile_tuple(field_name);
892
893 var_size = array_schema.attribute(field_name)->var_size();
894 nullable = array_schema.attribute(field_name)->nullable();
895 Tile* const tile =
896 var_size ? &std::get<1>(*tile_tuple) : &std::get<0>(*tile_tuple);
897
898 REQUIRE(tile->init_unfiltered(
899 constants::format_version,
900 type,
901 2 * (cells - 2) * sizeof(char),
902 2 * sizeof(char),
903 0)
904 .ok());
905
906 char* values = static_cast<char*>(malloc(
907 sizeof(char) * 2 *
908 (cells - 2))); // static_cast<char*>(malloc(sizeof(char) * 2 * cells));
909
910 // Empty strings are at idx 8 and 9
911 for (uint64_t i = 0; i < (cells - 2); ++i) {
912 values[i * 2] = 'a';
913 values[(i * 2) + 1] = 'a' + static_cast<char>(i);
914 }
915
916 REQUIRE(tile->write(values, 2 * (cells - 2) * sizeof(char)).ok());
917
918 if (var_size) {
919 Tile* const tile_offsets = &std::get<0>(*tile_tuple);
920 REQUIRE(tile_offsets
921 ->init_unfiltered(
922 constants::format_version,
923 constants::cell_var_offset_type,
924 10 * constants::cell_var_offset_size,
925 constants::cell_var_offset_size,
926 0)
927 .ok());
928
929 uint64_t* offsets =
930 static_cast<uint64_t*>(malloc(sizeof(uint64_t) * cells));
931 uint64_t offset = 0;
932 for (uint64_t i = 0; i < cells - 2; ++i) {
933 offsets[i] = offset;
934 offset += 2;
935 }
936 offsets[cells - 2] = offset;
937 offsets[cells - 1] = offset;
938 REQUIRE(tile_offsets->write(offsets, cells * sizeof(uint64_t)).ok());
939
940 free(offsets);
941 }
942
943 if (nullable) {
944 Tile* const tile_validity = &std::get<2>(*tile_tuple);
945 REQUIRE(tile_validity
946 ->init_unfiltered(
947 constants::format_version,
948 constants::cell_validity_type,
949 10 * constants::cell_validity_size,
950 constants::cell_validity_size,
951 0)
952 .ok());
953
954 uint8_t* validity = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * cells));
955 for (uint64_t i = 0; i < cells; ++i) {
956 validity[i] = i % 2;
957 }
958 REQUIRE(tile_validity->write(validity, cells * sizeof(uint8_t)).ok());
959
960 free(validity);
961 }
962
963 // Empty string as condition value
964 const char* cmp_value = "";
965
966 QueryCondition query_condition;
967 REQUIRE(
968 query_condition.init(std::string(field_name), &cmp_value, 0, op).ok());
969
970 // Run Check
971 REQUIRE(query_condition.check(&array_schema).ok());
972
973 // Build expected indexes of cells that meet the query condition
974 // criteria.
975 std::vector<uint64_t> expected_cell_idx_vec;
976 for (uint64_t i = 0; i < cells; ++i) {
977 switch (op) {
978 case QueryConditionOp::EQ:
979 if ((nullable && (i % 2 == 0)) || (i >= 8)) {
980 expected_cell_idx_vec.emplace_back(i);
981 } else if (
982 std::string(&static_cast<char*>(values)[2 * i], 2) ==
983 std::string(cmp_value)) {
984 expected_cell_idx_vec.emplace_back(i);
985 }
986 break;
987 case QueryConditionOp::NE:
988 if ((nullable && (i % 2 == 0)) || (i >= 8)) {
989 continue;
990 } else if (
991 std::string(&static_cast<char*>(values)[2 * i], 2) !=
992 std::string(cmp_value)) {
993 expected_cell_idx_vec.emplace_back(i);
994 }
995 break;
996 default:
997 REQUIRE(false);
998 }
999 }
1000
1001 // Apply the query condition.
1002 ResultCellSlab result_cell_slab(&result_tile, 0, cells);
1003 std::vector<ResultCellSlab> result_cell_slabs;
1004 result_cell_slabs.emplace_back(std::move(result_cell_slab));
1005 REQUIRE(query_condition.apply(&array_schema, &result_cell_slabs, 1).ok());
1006
1007 // Verify the result cell slabs contain the expected cells.
1008 auto expected_iter = expected_cell_idx_vec.begin();
1009 for (const auto& result_cell_slab : result_cell_slabs) {
1010 for (uint64_t cell_idx = result_cell_slab.start_;
1011 cell_idx < (result_cell_slab.start_ + result_cell_slab.length_);
1012 ++cell_idx) {
1013 REQUIRE(*expected_iter == cell_idx);
1014 ++expected_iter;
1015 }
1016 }
1017
1018 free(values);
1019 }