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 }