1 /**
2  * @file unit-sparse-unordered-with-dups-reader.cc
3  *
4  * @section LICENSE
5  *
6  * The MIT License
7  *
8  * @copyright Copyright (c) 2017-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 for the sparse unordered with duplicates reader.
31  */
32 
33 #include "test/src/helpers.h"
34 #include "tiledb/sm/c_api/tiledb.h"
35 
36 #ifdef _WIN32
37 #include "tiledb/sm/filesystem/win.h"
38 #else
39 #include "tiledb/sm/filesystem/posix.h"
40 #endif
41 
42 #include <catch.hpp>
43 
44 using namespace tiledb::sm;
45 using namespace tiledb::test;
46 
47 /* ********************************* */
48 /*         STRUCT DEFINITION         */
49 /* ********************************* */
50 
51 struct CSparseUnorderedWithDupsFx {
52   tiledb_ctx_t* ctx_ = nullptr;
53   tiledb_vfs_t* vfs_ = nullptr;
54   std::string temp_dir_;
55   std::string array_name_;
56   const char* ARRAY_NAME = "test_sparse_unordered_with_dups";
57   tiledb_array_t* array_ = nullptr;
58   std::string total_budget_;
59   std::string ratio_tile_ranges_;
60   std::string ratio_array_data_;
61   std::string ratio_result_tiles_;
62   std::string ratio_coords_;
63   std::string ratio_rcs_;
64   std::string ratio_query_condition_;
65 
66   void create_default_array_1d();
67   void write_1d_fragment(
68       int* coords, uint64_t* coords_size, int* data, uint64_t* data_size);
69   int32_t read(
70       bool set_subarray,
71       bool set_qc,
72       int* coords,
73       uint64_t* coords_size,
74       int* data,
75       uint64_t* data_size,
76       tiledb_query_t** query = nullptr,
77       tiledb_array_t** array_ret = nullptr);
78   void reset_config();
79   void update_config();
80 
81   CSparseUnorderedWithDupsFx();
82   ~CSparseUnorderedWithDupsFx();
83 };
84 
CSparseUnorderedWithDupsFx()85 CSparseUnorderedWithDupsFx::CSparseUnorderedWithDupsFx() {
86   reset_config();
87 
88   // Create temporary directory based on the supported filesystem.
89 #ifdef _WIN32
90   temp_dir_ = tiledb::sm::Win::current_dir() + "\\tiledb_test\\";
91 #else
92   temp_dir_ = "file://" + tiledb::sm::Posix::current_dir() + "/tiledb_test/";
93 #endif
94   create_dir(temp_dir_, ctx_, vfs_);
95   array_name_ = temp_dir_ + ARRAY_NAME;
96 }
97 
~CSparseUnorderedWithDupsFx()98 CSparseUnorderedWithDupsFx::~CSparseUnorderedWithDupsFx() {
99   tiledb_array_free(&array_);
100   remove_dir(temp_dir_, ctx_, vfs_);
101   tiledb_ctx_free(&ctx_);
102   tiledb_vfs_free(&vfs_);
103 }
104 
reset_config()105 void CSparseUnorderedWithDupsFx::reset_config() {
106   total_budget_ = "1048576";
107   ratio_tile_ranges_ = "0.1";
108   ratio_array_data_ = "0.1";
109   ratio_result_tiles_ = "0.05";
110   ratio_coords_ = "0.5";
111   ratio_rcs_ = "0.05";
112   ratio_query_condition_ = "0.25";
113   update_config();
114 }
115 
update_config()116 void CSparseUnorderedWithDupsFx::update_config() {
117   if (ctx_ != nullptr)
118     tiledb_ctx_free(&ctx_);
119 
120   if (vfs_ != nullptr)
121     tiledb_vfs_free(&vfs_);
122 
123   tiledb_config_t* config;
124   tiledb_error_t* error = nullptr;
125   REQUIRE(tiledb_config_alloc(&config, &error) == TILEDB_OK);
126   REQUIRE(error == nullptr);
127 
128   REQUIRE(
129       tiledb_config_set(
130           config,
131           "sm.query.sparse_unordered_with_dups.reader",
132           "refactored",
133           &error) == TILEDB_OK);
134   REQUIRE(error == nullptr);
135 
136   REQUIRE(
137       tiledb_config_set(
138           config, "sm.mem.total_budget", total_budget_.c_str(), &error) ==
139       TILEDB_OK);
140   REQUIRE(error == nullptr);
141 
142   REQUIRE(
143       tiledb_config_set(
144           config,
145           "sm.mem.reader.sparse_unordered_with_dups.ratio_tile_ranges",
146           ratio_tile_ranges_.c_str(),
147           &error) == TILEDB_OK);
148   REQUIRE(error == nullptr);
149 
150   REQUIRE(
151       tiledb_config_set(
152           config,
153           "sm.mem.reader.sparse_unordered_with_dups.ratio_array_data",
154           ratio_array_data_.c_str(),
155           &error) == TILEDB_OK);
156   REQUIRE(error == nullptr);
157 
158   REQUIRE(
159       tiledb_config_set(
160           config,
161           "sm.mem.reader.sparse_unordered_with_dups.ratio_result_tiles",
162           ratio_result_tiles_.c_str(),
163           &error) == TILEDB_OK);
164   REQUIRE(error == nullptr);
165 
166   REQUIRE(
167       tiledb_config_set(
168           config,
169           "sm.mem.reader.sparse_unordered_with_dups.ratio_coords",
170           ratio_coords_.c_str(),
171           &error) == TILEDB_OK);
172   REQUIRE(error == nullptr);
173 
174   REQUIRE(
175       tiledb_config_set(
176           config,
177           "sm.mem.reader.sparse_unordered_with_dups.ratio_rcs",
178           ratio_rcs_.c_str(),
179           &error) == TILEDB_OK);
180   REQUIRE(error == nullptr);
181 
182   REQUIRE(
183       tiledb_config_set(
184           config,
185           "sm.mem.reader.sparse_unordered_with_dups.ratio_query_condition",
186           ratio_query_condition_.c_str(),
187           &error) == TILEDB_OK);
188   REQUIRE(error == nullptr);
189 
190   REQUIRE(tiledb_ctx_alloc(config, &ctx_) == TILEDB_OK);
191   REQUIRE(error == nullptr);
192   REQUIRE(tiledb_vfs_alloc(ctx_, config, &vfs_) == TILEDB_OK);
193   tiledb_config_free(&config);
194 }
195 
create_default_array_1d()196 void CSparseUnorderedWithDupsFx::create_default_array_1d() {
197   int domain[] = {1, 10};
198   int tile_extent = 2;
199   create_array(
200       ctx_,
201       array_name_,
202       TILEDB_SPARSE,
203       {"d"},
204       {TILEDB_INT32},
205       {domain},
206       {&tile_extent},
207       {"a"},
208       {TILEDB_INT32},
209       {1},
210       {tiledb::test::Compressor(TILEDB_FILTER_NONE, -1)},
211       TILEDB_ROW_MAJOR,
212       TILEDB_ROW_MAJOR,
213       2,
214       true);  // allows dups.
215 }
216 
write_1d_fragment(int * coords,uint64_t * coords_size,int * data,uint64_t * data_size)217 void CSparseUnorderedWithDupsFx::write_1d_fragment(
218     int* coords, uint64_t* coords_size, int* data, uint64_t* data_size) {
219   // Open array for writing.
220   tiledb_array_t* array;
221   auto rc = tiledb_array_alloc(ctx_, array_name_.c_str(), &array);
222   REQUIRE(rc == TILEDB_OK);
223   rc = tiledb_array_open(ctx_, array, TILEDB_WRITE);
224   REQUIRE(rc == TILEDB_OK);
225 
226   // Create the query.
227   tiledb_query_t* query;
228   rc = tiledb_query_alloc(ctx_, array, TILEDB_WRITE, &query);
229   REQUIRE(rc == TILEDB_OK);
230   rc = tiledb_query_set_layout(ctx_, query, TILEDB_UNORDERED);
231   REQUIRE(rc == TILEDB_OK);
232   rc = tiledb_query_set_data_buffer(ctx_, query, "a", data, data_size);
233   REQUIRE(rc == TILEDB_OK);
234   rc = tiledb_query_set_data_buffer(ctx_, query, "d", coords, coords_size);
235   REQUIRE(rc == TILEDB_OK);
236 
237   // Submit query.
238   rc = tiledb_query_submit(ctx_, query);
239   REQUIRE(rc == TILEDB_OK);
240 
241   // Close array.
242   rc = tiledb_array_close(ctx_, array);
243   REQUIRE(rc == TILEDB_OK);
244 
245   // Clean up.
246   tiledb_array_free(&array);
247   tiledb_query_free(&query);
248 }
249 
read(bool set_subarray,bool set_qc,int * coords,uint64_t * coords_size,int * data,uint64_t * data_size,tiledb_query_t ** query_ret,tiledb_array_t ** array_ret)250 int32_t CSparseUnorderedWithDupsFx::read(
251     bool set_subarray,
252     bool set_qc,
253     int* coords,
254     uint64_t* coords_size,
255     int* data,
256     uint64_t* data_size,
257     tiledb_query_t** query_ret,
258     tiledb_array_t** array_ret) {
259   // Open array for reading.
260   tiledb_array_t* array;
261   auto rc = tiledb_array_alloc(ctx_, array_name_.c_str(), &array);
262   CHECK(rc == TILEDB_OK);
263   rc = tiledb_array_open(ctx_, array, TILEDB_READ);
264   CHECK(rc == TILEDB_OK);
265 
266   // Create query.
267   tiledb_query_t* query;
268   rc = tiledb_query_alloc(ctx_, array, TILEDB_READ, &query);
269   CHECK(rc == TILEDB_OK);
270 
271   if (set_subarray) {
272     // Set subarray.
273     int subarray[] = {1, 10};
274     rc = tiledb_query_set_subarray(ctx_, query, subarray);
275     CHECK(rc == TILEDB_OK);
276   }
277 
278   if (set_qc) {
279     tiledb_query_condition_t* query_condition = nullptr;
280     rc = tiledb_query_condition_alloc(ctx_, &query_condition);
281     CHECK(rc == TILEDB_OK);
282     int32_t val = 10;
283     rc = tiledb_query_condition_init(
284         ctx_, query_condition, "a", &val, sizeof(int32_t), TILEDB_LT);
285     CHECK(rc == TILEDB_OK);
286 
287     rc = tiledb_query_set_condition(ctx_, query, query_condition);
288     CHECK(rc == TILEDB_OK);
289 
290     tiledb_query_condition_free(&query_condition);
291   }
292 
293   rc = tiledb_query_set_layout(ctx_, query, TILEDB_UNORDERED);
294   CHECK(rc == TILEDB_OK);
295   rc = tiledb_query_set_data_buffer(ctx_, query, "a", data, data_size);
296   CHECK(rc == TILEDB_OK);
297   rc = tiledb_query_set_data_buffer(
298       ctx_, query, TILEDB_COORDS, coords, coords_size);
299   CHECK(rc == TILEDB_OK);
300 
301   // Submit query.
302   auto ret = tiledb_query_submit(ctx_, query);
303 
304   if (query_ret == nullptr || array_ret == nullptr) {
305     // Clean up.
306     rc = tiledb_array_close(ctx_, array);
307     CHECK(rc == TILEDB_OK);
308     tiledb_array_free(&array);
309     tiledb_query_free(&query);
310   } else {
311     *query_ret = query;
312     *array_ret = array;
313   }
314 
315   return ret;
316 }
317 
318 /* ********************************* */
319 /*                TESTS              */
320 /* ********************************* */
321 
322 TEST_CASE_METHOD(
323     CSparseUnorderedWithDupsFx,
324     "Sparse unordered with dups reader: Tile ranges budget exceeded",
325     "[sparse-unordered-with-dups][tile-ranges][budget-exceeded]") {
326   // Create default array.
327   reset_config();
328   create_default_array_1d();
329 
330   // Write a fragment.
331   int coords[] = {1, 2, 3, 4, 5};
332   uint64_t coords_size = sizeof(coords);
333   int data[] = {1, 2, 3, 4, 5};
334   uint64_t data_size = sizeof(data);
335   write_1d_fragment(coords, &coords_size, data, &data_size);
336 
337   // We should have one tile range (size 16) which will be bigger than budget
338   // (10).
339   total_budget_ = "1000";
340   ratio_tile_ranges_ = "0.01";
341   update_config();
342 
343   // Try to read.
344   int coords_r[5];
345   int data_r[5];
346   uint64_t coords_r_size = sizeof(coords_r);
347   uint64_t data_r_size = sizeof(data_r);
348   auto rc = read(true, false, coords_r, &coords_r_size, data_r, &data_r_size);
349   CHECK(rc == TILEDB_ERR);
350 
351   // Check we hit the correct error.
352   tiledb_error_t* error = NULL;
353   rc = tiledb_ctx_get_last_error(ctx_, &error);
354   CHECK(rc == TILEDB_OK);
355 
356   const char* msg;
357   rc = tiledb_error_message(error, &msg);
358   CHECK(rc == TILEDB_OK);
359 
360   std::string error_str(msg);
361   CHECK(
362       error_str.find("Exceeded memory budget for result tile ranges") !=
363       std::string::npos);
364 }
365 
366 TEST_CASE_METHOD(
367     CSparseUnorderedWithDupsFx,
368     "Sparse unordered with dups reader: tile offsets budget exceeded",
369     "[sparse-unordered-with-dups][tile-offsets][budget-exceeded]") {
370   // Create default array.
371   reset_config();
372   create_default_array_1d();
373 
374   // Write a fragment.
375   int coords[] = {1, 2, 3, 4, 5};
376   uint64_t coords_size = sizeof(coords);
377   int data[] = {1, 2, 3, 4, 5};
378   uint64_t data_size = sizeof(data);
379   write_1d_fragment(coords, &coords_size, data, &data_size);
380 
381   // We should have 3 tiles (tile offset size 24) which will be bigger than
382   // budget (10).
383   total_budget_ = "1000";
384   ratio_array_data_ = "0.01";
385   update_config();
386 
387   // Try to read.
388   int coords_r[5];
389   int data_r[5];
390   uint64_t coords_r_size = sizeof(coords_r);
391   uint64_t data_r_size = sizeof(data_r);
392   auto rc = read(true, false, coords_r, &coords_r_size, data_r, &data_r_size);
393   CHECK(rc == TILEDB_ERR);
394 
395   // Check we hit the correct error.
396   tiledb_error_t* error = NULL;
397   rc = tiledb_ctx_get_last_error(ctx_, &error);
398   CHECK(rc == TILEDB_OK);
399 
400   const char* msg;
401   rc = tiledb_error_message(error, &msg);
402   CHECK(rc == TILEDB_OK);
403 
404   std::string error_str(msg);
405   CHECK(error_str.find("Cannot load tile offsets") != std::string::npos);
406 }
407 
408 TEST_CASE_METHOD(
409     CSparseUnorderedWithDupsFx,
410     "Sparse unordered with dups reader: result tiles budget forcing one tile "
411     "at a time",
412     "[sparse-unordered-with-dups][small-rt-budget]") {
413   // Create default array.
414   reset_config();
415   create_default_array_1d();
416 
417   bool use_subarray = false;
418   SECTION("- No subarray") {
419     use_subarray = false;
420   }
421   SECTION("- Subarray") {
422     use_subarray = true;
423   }
424 
425   // Write a fragment.
426   int coords[] = {1, 2, 3, 4, 5};
427   uint64_t coords_size = sizeof(coords);
428   int data[] = {1, 2, 3, 4, 5};
429   uint64_t data_size = sizeof(data);
430   write_1d_fragment(coords, &coords_size, data, &data_size);
431 
432   // Two result tile (~880) will be bigger than the budget (500).
433   total_budget_ = "10000";
434   ratio_result_tiles_ = "0.05";
435   update_config();
436 
437   tiledb_array_t* array = nullptr;
438   tiledb_query_t* query = nullptr;
439 
440   // Try to read.
441   int coords_r[5];
442   int data_r[5];
443   uint64_t coords_r_size = sizeof(coords_r);
444   uint64_t data_r_size = sizeof(data_r);
445   auto rc = read(
446       use_subarray,
447       false,
448       coords_r,
449       &coords_r_size,
450       data_r,
451       &data_r_size,
452       &query,
453       &array);
454   CHECK(rc == TILEDB_OK);
455 
456   // Check incomplete query status.
457   tiledb_query_status_t status;
458   tiledb_query_get_status(ctx_, query, &status);
459   CHECK(status == TILEDB_INCOMPLETE);
460 
461   // Should only read one tile (2 values).
462   CHECK(8 == data_r_size);
463   CHECK(8 == coords_r_size);
464 
465   int coords_c[] = {1, 2};
466   int data_c[] = {1, 2};
467   CHECK(!std::memcmp(coords_c, coords_r, coords_r_size));
468   CHECK(!std::memcmp(data_c, data_r, data_r_size));
469 
470   // Read again.
471   rc = tiledb_query_submit(ctx_, query);
472   CHECK(rc == TILEDB_OK);
473 
474   // Check incomplete query status.
475   tiledb_query_get_status(ctx_, query, &status);
476   CHECK(status == TILEDB_INCOMPLETE);
477 
478   // Should only read one more tile (2 values).
479   CHECK(8 == data_r_size);
480   CHECK(8 == coords_r_size);
481 
482   int coords_c_2[] = {3, 4};
483   int data_c_2[] = {3, 4};
484   CHECK(!std::memcmp(coords_c_2, coords_r, coords_r_size));
485   CHECK(!std::memcmp(data_c_2, data_r, data_r_size));
486 
487   // Read again.
488   rc = tiledb_query_submit(ctx_, query);
489   CHECK(rc == TILEDB_OK);
490 
491   // Check complete query status.
492   tiledb_query_get_status(ctx_, query, &status);
493   CHECK(status == TILEDB_COMPLETED);
494 
495   // Should read last tile (1 value).
496   CHECK(4 == data_r_size);
497   CHECK(4 == coords_r_size);
498 
499   int coords_c_3[] = {5};
500   int data_c_3[] = {5};
501   CHECK(!std::memcmp(coords_c_3, coords_r, coords_r_size));
502   CHECK(!std::memcmp(data_c_3, data_r, data_r_size));
503 
504   // Clean up.
505   rc = tiledb_array_close(ctx_, array);
506   CHECK(rc == TILEDB_OK);
507   tiledb_array_free(&array);
508   tiledb_query_free(&query);
509 }
510 
511 TEST_CASE_METHOD(
512     CSparseUnorderedWithDupsFx,
513     "Sparse unordered with dups reader: result tiles budget too small",
514     "[sparse-unordered-with-dups][rt-budget][too-small]") {
515   // Create default array.
516   reset_config();
517   create_default_array_1d();
518 
519   bool use_subarray = false;
520   SECTION("- No subarray") {
521     use_subarray = false;
522   }
523   SECTION("- Subarray") {
524     use_subarray = true;
525   }
526 
527   // Write a fragment.
528   int coords[] = {1, 2, 3, 4, 5};
529   uint64_t coords_size = sizeof(coords);
530   int data[] = {1, 2, 3, 4, 5};
531   uint64_t data_size = sizeof(data);
532   write_1d_fragment(coords, &coords_size, data, &data_size);
533 
534   // One result tile will be bigger than the budget (10).
535   total_budget_ = "10000";
536   ratio_result_tiles_ = "0.001";
537   update_config();
538 
539   // Try to read.
540   int coords_r[5];
541   int data_r[5];
542   uint64_t coords_r_size = sizeof(coords_r);
543   uint64_t data_r_size = sizeof(data_r);
544   auto rc =
545       read(use_subarray, false, coords_r, &coords_r_size, data_r, &data_r_size);
546   CHECK(rc == TILEDB_ERR);
547 
548   // Check we hit the correct error.
549   tiledb_error_t* error = NULL;
550   rc = tiledb_ctx_get_last_error(ctx_, &error);
551   CHECK(rc == TILEDB_OK);
552 
553   const char* msg;
554   rc = tiledb_error_message(error, &msg);
555   CHECK(rc == TILEDB_OK);
556 
557   std::string error_str(msg);
558   CHECK(error_str.find("Cannot load a single tile") != std::string::npos);
559 }
560 
561 TEST_CASE_METHOD(
562     CSparseUnorderedWithDupsFx,
563     "Sparse unordered with dups reader: coords budget forcing one tile at a "
564     "time",
565     "[sparse-unordered-with-dups][small-coords-budget]") {
566   // Create default array.
567   reset_config();
568   create_default_array_1d();
569 
570   bool use_subarray = false;
571   SECTION("- No subarray") {
572     use_subarray = false;
573   }
574   SECTION("- Subarray") {
575     use_subarray = true;
576   }
577 
578   // Write a fragment.
579   int coords[] = {1, 2, 3, 4, 5};
580   uint64_t coords_size = sizeof(coords);
581   int data[] = {1, 2, 3, 4, 5};
582   uint64_t data_size = sizeof(data);
583   write_1d_fragment(coords, &coords_size, data, &data_size);
584 
585   // Two result tile (2 * 8) will be bigger than the budget (10).
586   total_budget_ = "10000";
587   ratio_coords_ = "0.001";
588   update_config();
589 
590   tiledb_array_t* array = nullptr;
591   tiledb_query_t* query = nullptr;
592 
593   // Try to read.
594   int coords_r[5];
595   int data_r[5];
596   uint64_t coords_r_size = sizeof(coords_r);
597   uint64_t data_r_size = sizeof(data_r);
598   auto rc = read(
599       use_subarray,
600       false,
601       coords_r,
602       &coords_r_size,
603       data_r,
604       &data_r_size,
605       &query,
606       &array);
607   CHECK(rc == TILEDB_OK);
608 
609   // Check incomplete query status.
610   tiledb_query_status_t status;
611   tiledb_query_get_status(ctx_, query, &status);
612   CHECK(status == TILEDB_INCOMPLETE);
613 
614   // Should only read one tile (2 values).
615   CHECK(8 == data_r_size);
616   CHECK(8 == coords_r_size);
617 
618   int coords_c[] = {1, 2};
619   int data_c[] = {1, 2};
620   CHECK(!std::memcmp(coords_c, coords_r, coords_r_size));
621   CHECK(!std::memcmp(data_c, data_r, data_r_size));
622 
623   // Read again.
624   rc = tiledb_query_submit(ctx_, query);
625   CHECK(rc == TILEDB_OK);
626 
627   // Check incomplete query status.
628   tiledb_query_get_status(ctx_, query, &status);
629   CHECK(status == TILEDB_INCOMPLETE);
630 
631   // Should only read one more tile (2 values).
632   CHECK(8 == data_r_size);
633   CHECK(8 == coords_r_size);
634 
635   int coords_c_2[] = {3, 4};
636   int data_c_2[] = {3, 4};
637   CHECK(!std::memcmp(coords_c_2, coords_r, coords_r_size));
638   CHECK(!std::memcmp(data_c_2, data_r, data_r_size));
639 
640   // Read again.
641   rc = tiledb_query_submit(ctx_, query);
642   CHECK(rc == TILEDB_OK);
643 
644   // Check complete query status.
645   tiledb_query_get_status(ctx_, query, &status);
646   CHECK(status == TILEDB_COMPLETED);
647 
648   // Should read last tile (1 value).
649   CHECK(4 == data_r_size);
650   CHECK(4 == coords_r_size);
651 
652   int coords_c_3[] = {5};
653   int data_c_3[] = {5};
654   CHECK(!std::memcmp(coords_c_3, coords_r, coords_r_size));
655   CHECK(!std::memcmp(data_c_3, data_r, data_r_size));
656 
657   // Clean up.
658   rc = tiledb_array_close(ctx_, array);
659   CHECK(rc == TILEDB_OK);
660   tiledb_array_free(&array);
661   tiledb_query_free(&query);
662 }
663 
664 TEST_CASE_METHOD(
665     CSparseUnorderedWithDupsFx,
666     "Sparse unordered with dups reader: coords budget too small",
667     "[sparse-unordered-with-dups][coords-budget][too-small]") {
668   // Create default array.
669   reset_config();
670   create_default_array_1d();
671 
672   bool use_subarray = false;
673   SECTION("- No subarray") {
674     use_subarray = false;
675   }
676   SECTION("- Subarray") {
677     use_subarray = true;
678   }
679 
680   // Write a fragment.
681   int coords[] = {1, 2, 3, 4, 5};
682   uint64_t coords_size = sizeof(coords);
683   int data[] = {1, 2, 3, 4, 5};
684   uint64_t data_size = sizeof(data);
685   write_1d_fragment(coords, &coords_size, data, &data_size);
686 
687   // One result tile (8) will be bigger than the budget (5).
688   total_budget_ = "10000";
689   ratio_coords_ = "0.0005";
690   update_config();
691 
692   // Try to read.
693   int coords_r[5];
694   int data_r[5];
695   uint64_t coords_r_size = sizeof(coords_r);
696   uint64_t data_r_size = sizeof(data_r);
697   auto rc =
698       read(use_subarray, false, coords_r, &coords_r_size, data_r, &data_r_size);
699   CHECK(rc == TILEDB_ERR);
700 
701   // Check we hit the correct error.
702   tiledb_error_t* error = NULL;
703   rc = tiledb_ctx_get_last_error(ctx_, &error);
704   CHECK(rc == TILEDB_OK);
705 
706   const char* msg;
707   rc = tiledb_error_message(error, &msg);
708   CHECK(rc == TILEDB_OK);
709 
710   std::string error_str(msg);
711   CHECK(error_str.find("Cannot load a single tile") != std::string::npos);
712 }
713 
714 TEST_CASE_METHOD(
715     CSparseUnorderedWithDupsFx,
716     "Sparse unordered with dups reader: rcs budget forcing one tile at a time",
717     "[sparse-unordered-with-dups][small-rcs-budget]") {
718   // Create default array.
719   reset_config();
720   create_default_array_1d();
721 
722   bool use_subarray = false;
723   SECTION("- No subarray") {
724     use_subarray = false;
725   }
726   SECTION("- Subarray") {
727     use_subarray = true;
728   }
729 
730   // Write a fragment.
731   int coords[] = {1, 2, 3, 4, 5};
732   uint64_t coords_size = sizeof(coords);
733   int data[] = {1, 2, 3, 4, 5};
734   uint64_t data_size = sizeof(data);
735   write_1d_fragment(coords, &coords_size, data, &data_size);
736 
737   // One cell slab (24) will be bigger than the budget (10).
738   total_budget_ = "10000";
739   ratio_rcs_ = "0.001";
740   update_config();
741 
742   tiledb_array_t* array = nullptr;
743   tiledb_query_t* query = nullptr;
744 
745   // Try to read.
746   int coords_r[5];
747   int data_r[5];
748   uint64_t coords_r_size = sizeof(coords_r);
749   uint64_t data_r_size = sizeof(data_r);
750   auto rc = read(
751       use_subarray,
752       false,
753       coords_r,
754       &coords_r_size,
755       data_r,
756       &data_r_size,
757       &query,
758       &array);
759   CHECK(rc == TILEDB_OK);
760 
761   // Check incomplete query status.
762   tiledb_query_status_t status;
763   tiledb_query_get_status(ctx_, query, &status);
764   CHECK(status == TILEDB_INCOMPLETE);
765 
766   // Should only read one tile (2 values).
767   CHECK(8 == data_r_size);
768   CHECK(8 == coords_r_size);
769 
770   int coords_c[] = {1, 2};
771   int data_c[] = {1, 2};
772   CHECK(!std::memcmp(coords_c, coords_r, coords_r_size));
773   CHECK(!std::memcmp(data_c, data_r, data_r_size));
774 
775   // Read again.
776   rc = tiledb_query_submit(ctx_, query);
777   CHECK(rc == TILEDB_OK);
778 
779   // Check incomplete query status.
780   tiledb_query_get_status(ctx_, query, &status);
781   CHECK(status == TILEDB_INCOMPLETE);
782 
783   // Should only read one more tile (2 values).
784   CHECK(8 == data_r_size);
785   CHECK(8 == coords_r_size);
786 
787   int coords_c_2[] = {3, 4};
788   int data_c_2[] = {3, 4};
789   CHECK(!std::memcmp(coords_c_2, coords_r, coords_r_size));
790   CHECK(!std::memcmp(data_c_2, data_r, data_r_size));
791 
792   // Read again.
793   rc = tiledb_query_submit(ctx_, query);
794   CHECK(rc == TILEDB_OK);
795 
796   // Check complete query status.
797   tiledb_query_get_status(ctx_, query, &status);
798   CHECK(status == TILEDB_COMPLETED);
799 
800   // Should read last tile (1 value).
801   CHECK(4 == data_r_size);
802   CHECK(4 == coords_r_size);
803 
804   int coords_c_3[] = {5};
805   int data_c_3[] = {5};
806   CHECK(!std::memcmp(coords_c_3, coords_r, coords_r_size));
807   CHECK(!std::memcmp(data_c_3, data_r, data_r_size));
808 
809   // Clean up.
810   rc = tiledb_array_close(ctx_, array);
811   CHECK(rc == TILEDB_OK);
812   tiledb_array_free(&array);
813   tiledb_query_free(&query);
814 }
815 
816 TEST_CASE_METHOD(
817     CSparseUnorderedWithDupsFx,
818     "Sparse unordered with dups reader: qc budget forcing one tile at a time",
819     "[sparse-unordered-with-dups][small-qc-budget]") {
820   // Create default array.
821   reset_config();
822   create_default_array_1d();
823 
824   bool use_subarray = false;
825   SECTION("- No subarray") {
826     use_subarray = false;
827   }
828   SECTION("- Subarray") {
829     use_subarray = true;
830   }
831 
832   // Write a fragment.
833   int coords[] = {1, 2, 3, 4, 5};
834   uint64_t coords_size = sizeof(coords);
835   int data[] = {1, 2, 3, 4, 5};
836   uint64_t data_size = sizeof(data);
837   write_1d_fragment(coords, &coords_size, data, &data_size);
838 
839   // Two qc tile (16) will be bigger than the budget (10).
840   total_budget_ = "10000";
841   ratio_query_condition_ = "0.001";
842   update_config();
843 
844   tiledb_array_t* array = nullptr;
845   tiledb_query_t* query = nullptr;
846 
847   // Try to read.
848   int coords_r[5];
849   int data_r[5];
850   uint64_t coords_r_size = sizeof(coords_r);
851   uint64_t data_r_size = sizeof(data_r);
852   auto rc = read(
853       use_subarray,
854       true,
855       coords_r,
856       &coords_r_size,
857       data_r,
858       &data_r_size,
859       &query,
860       &array);
861   CHECK(rc == TILEDB_OK);
862 
863   // Check incomplete query status.
864   tiledb_query_status_t status;
865   tiledb_query_get_status(ctx_, query, &status);
866   CHECK(status == TILEDB_INCOMPLETE);
867 
868   // Should only read one tile (2 values).
869   CHECK(8 == data_r_size);
870   CHECK(8 == coords_r_size);
871 
872   int coords_c[] = {1, 2};
873   int data_c[] = {1, 2};
874   CHECK(!std::memcmp(coords_c, coords_r, coords_r_size));
875   CHECK(!std::memcmp(data_c, data_r, data_r_size));
876 
877   // Read again.
878   rc = tiledb_query_submit(ctx_, query);
879   CHECK(rc == TILEDB_OK);
880 
881   // Check incomplete query status.
882   tiledb_query_get_status(ctx_, query, &status);
883   CHECK(status == TILEDB_INCOMPLETE);
884 
885   // Should only read one more tile (2 values).
886   CHECK(8 == data_r_size);
887   CHECK(8 == coords_r_size);
888 
889   int coords_c_2[] = {3, 4};
890   int data_c_2[] = {3, 4};
891   CHECK(!std::memcmp(coords_c_2, coords_r, coords_r_size));
892   CHECK(!std::memcmp(data_c_2, data_r, data_r_size));
893 
894   // Read again.
895   rc = tiledb_query_submit(ctx_, query);
896   CHECK(rc == TILEDB_OK);
897 
898   // Check complete query status.
899   tiledb_query_get_status(ctx_, query, &status);
900   CHECK(status == TILEDB_COMPLETED);
901 
902   // Should read last tile (1 value).
903   CHECK(4 == data_r_size);
904   CHECK(4 == coords_r_size);
905 
906   int coords_c_3[] = {5};
907   int data_c_3[] = {5};
908   CHECK(!std::memcmp(coords_c_3, coords_r, coords_r_size));
909   CHECK(!std::memcmp(data_c_3, data_r, data_r_size));
910 
911   // Clean up.
912   rc = tiledb_array_close(ctx_, array);
913   CHECK(rc == TILEDB_OK);
914   tiledb_array_free(&array);
915   tiledb_query_free(&query);
916 }
917 
918 TEST_CASE_METHOD(
919     CSparseUnorderedWithDupsFx,
920     "Sparse unordered with dups reader: qc budget too small",
921     "[sparse-unordered-with-dups][qc-budget][too-small]") {
922   // Create default array.
923   reset_config();
924   create_default_array_1d();
925 
926   bool use_subarray = false;
927   SECTION("- No subarray") {
928     use_subarray = false;
929   }
930   SECTION("- Subarray") {
931     use_subarray = true;
932   }
933 
934   // Write a fragment.
935   int coords[] = {1, 2, 3, 4, 5};
936   uint64_t coords_size = sizeof(coords);
937   int data[] = {1, 2, 3, 4, 5};
938   uint64_t data_size = sizeof(data);
939   write_1d_fragment(coords, &coords_size, data, &data_size);
940 
941   // One qc tile (8) will be bigger than the budget (5).
942   total_budget_ = "10000";
943   ratio_query_condition_ = "0.0005";
944   update_config();
945 
946   // Try to read.
947   int coords_r[5];
948   int data_r[5];
949   uint64_t coords_r_size = sizeof(coords_r);
950   uint64_t data_r_size = sizeof(data_r);
951   auto rc =
952       read(use_subarray, true, coords_r, &coords_r_size, data_r, &data_r_size);
953   CHECK(rc == TILEDB_ERR);
954 
955   // Check we hit the correct error.
956   tiledb_error_t* error = NULL;
957   rc = tiledb_ctx_get_last_error(ctx_, &error);
958   CHECK(rc == TILEDB_OK);
959 
960   const char* msg;
961   rc = tiledb_error_message(error, &msg);
962   CHECK(rc == TILEDB_OK);
963 
964   std::string error_str(msg);
965   CHECK(error_str.find("Cannot load a single tile") != std::string::npos);
966 }