3030 * Tests end to end enumerations.
3131 */
3232
33+ #include " test/support/src/array_schema_helpers.h"
3334#include " test/support/src/vfs_helpers.h"
3435#include " test/support/tdb_catch.h"
36+ #include " tiledb/api/c_api/array/array_api_internal.h"
37+ #include " tiledb/api/c_api/array_schema/array_schema_api_internal.h"
3538#include " tiledb/api/c_api/enumeration/enumeration_api_internal.h"
3639#include " tiledb/sm/array_schema/array_schema.h"
40+ #include " tiledb/sm/array_schema/array_schema_evolution.h"
3741#include " tiledb/sm/c_api/tiledb_struct_def.h"
3842#include " tiledb/sm/cpp_api/tiledb"
3943#include " tiledb/sm/cpp_api/tiledb_experimental"
@@ -46,6 +50,7 @@ struct RESTEnumerationFx {
4650 void create_array (const std::string& array_name);
4751
4852 tiledb::test::VFSTestSetup vfs_test_setup_;
53+ shared_ptr<sm::MemoryTracker> memory_tracker_;
4954 std::string uri_;
5055 Context ctx_;
5156};
@@ -145,8 +150,233 @@ TEST_CASE_METHOD(
145150 new_array.close ();
146151}
147152
153+ TEST_CASE_METHOD (
154+ RESTEnumerationFx,
155+ " Load Enumerations - All Schemas" ,
156+ " [enumeration][array][load-all-enumerations][all-schemas][rest]" ) {
157+ uri_ = vfs_test_setup_.array_uri (" load_enmrs_all_schemas" );
158+ auto config = vfs_test_setup_.ctx ().config ();
159+ bool load_enmrs = GENERATE (true , false );
160+ config[" rest.load_enumerations_on_array_open" ] =
161+ load_enmrs ? " true" : " false" ;
162+ vfs_test_setup_.update_config (config.ptr ().get ());
163+ ctx_ = vfs_test_setup_.ctx ();
164+
165+ create_array (uri_);
166+ Array opened_array (ctx_, uri_, TILEDB_READ);
167+ auto array = opened_array.ptr ()->array ();
168+ auto schema = array->array_schema_latest_ptr ();
169+ REQUIRE (schema->is_enumeration_loaded (" my_enum" ) == load_enmrs);
170+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == load_enmrs);
171+
172+ // If not using array open v3 just test that the correct exception is thrown
173+ if (!array->use_refactored_array_open ()) {
174+ CHECK_THROWS_WITH (
175+ array->load_all_enumerations (true ),
176+ Catch::Matchers::ContainsSubstring (
177+ " The array must be opened using "
178+ " `rest.use_refactored_array_open=true`" ));
179+ return ;
180+ }
181+ // If enumerations were loaded on array open this will not submit an
182+ // additional request.
183+ auto actual_enmrs = array->get_enumerations_all_schemas ();
184+ if (!load_enmrs) {
185+ REQUIRE (schema->is_enumeration_loaded (" my_enum" ) == true );
186+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == true );
187+ }
188+
189+ // Fetch enumerations created in the initial array schema for validation.
190+ auto enmr1 = array->get_enumeration (" my_enum" );
191+ auto enmr2 = array->get_enumeration (" fruit" );
192+ decltype (actual_enmrs) expected_enmrs{{schema->name (), {enmr1, enmr2}}};
193+ auto validate_enmrs = [&]() {
194+ for (const auto & [schema_name, enmrs] : expected_enmrs) {
195+ REQUIRE (actual_enmrs.contains (schema_name));
196+ REQUIRE (enmrs.size () == actual_enmrs[schema_name].size ());
197+ CHECK_THAT (
198+ enmrs,
199+ Catch::Matchers::UnorderedRangeEquals (
200+ actual_enmrs[schema_name], [](const auto & a, const auto & b) {
201+ return tiledb::test::is_equivalent_enumeration (*a, *b);
202+ }));
203+ }
204+ };
205+ validate_enmrs ();
206+
207+ // Evolve once to add an enumeration.
208+ sm::URI uri (uri_);
209+ auto ase = make_shared<sm::ArraySchemaEvolution>(HERE (), memory_tracker_);
210+ std::vector<std::string> var_values{" one" , " two" , " three" };
211+ auto var_enmr = Enumeration::create (ctx_, " ase_var_enmr" , var_values);
212+ ase->add_enumeration (var_enmr.ptr ()->enumeration ());
213+ auto attr4 =
214+ make_shared<sm::Attribute>(HERE (), " attr4" , sm::Datatype::UINT16);
215+ attr4->set_enumeration_name (" ase_var_enmr" );
216+ CHECK_NOTHROW (ase->evolve_schema (schema));
217+ // Apply evolution to the array and reopen.
218+ CHECK_NOTHROW (sm::Array::evolve_array_schema (
219+ ctx_.ptr ()->resources (), uri, ase.get (), array->get_encryption_key ()));
220+ CHECK (array->reopen ().ok ());
221+ schema = array->array_schema_latest_ptr ();
222+ std::string schema_name_2 = schema->name ();
223+ REQUIRE (schema->is_enumeration_loaded (" my_enum" ) == load_enmrs);
224+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == load_enmrs);
225+ REQUIRE (schema->is_enumeration_loaded (" ase_var_enmr" ) == load_enmrs);
226+
227+ expected_enmrs[schema_name_2] = {enmr1, enmr2, var_enmr.ptr ()->enumeration ()};
228+ actual_enmrs = array->get_enumerations_all_schemas ();
229+ if (!load_enmrs) {
230+ REQUIRE (schema->is_enumeration_loaded (" my_enum" ) == true );
231+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == true );
232+ REQUIRE (schema->is_enumeration_loaded (" ase_var_enmr" ) == true );
233+ }
234+ validate_enmrs ();
235+
236+ // Evolve a second time to drop an enumeration.
237+ ase = make_shared<sm::ArraySchemaEvolution>(HERE (), memory_tracker_);
238+ ase->drop_enumeration (" my_enum" );
239+ ase->drop_attribute (" attr1" );
240+ CHECK_NOTHROW (ase->evolve_schema (schema));
241+ // Apply evolution to the array and reopen.
242+ CHECK_NOTHROW (sm::Array::evolve_array_schema (
243+ ctx_.ptr ()->resources (), uri, ase.get (), array->get_encryption_key ()));
244+ CHECK (array->reopen ().ok ());
245+ schema = array->array_schema_latest_ptr ();
246+ std::string schema_name_3 = schema->name ();
247+ REQUIRE_THROWS_WITH (
248+ schema->is_enumeration_loaded (" my_enum" ),
249+ Catch::Matchers::ContainsSubstring (" unknown enumeration" ));
250+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == load_enmrs);
251+ REQUIRE (schema->is_enumeration_loaded (" ase_var_enmr" ) == load_enmrs);
252+
253+ expected_enmrs[schema_name_3] = {enmr2, var_enmr.ptr ()->enumeration ()};
254+ actual_enmrs = array->get_enumerations_all_schemas ();
255+ if (!load_enmrs) {
256+ REQUIRE_THROWS_WITH (
257+ schema->is_enumeration_loaded (" my_enum" ),
258+ Catch::Matchers::ContainsSubstring (" unknown enumeration" ));
259+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == true );
260+ REQUIRE (schema->is_enumeration_loaded (" ase_var_enmr" ) == true );
261+ }
262+
263+ validate_enmrs ();
264+ }
265+
266+ TEST_CASE_METHOD (
267+ RESTEnumerationFx,
268+ " Load Enumerations - All Schemas partial load" ,
269+ " [enumeration][array][load-all-enumerations][all-schemas][rest]" ) {
270+ uri_ = vfs_test_setup_.array_uri (" load_enmrs_all_schemas" );
271+
272+ create_array (uri_);
273+ Array opened_array (ctx_, uri_, TILEDB_READ);
274+ auto array = opened_array.ptr ()->array ();
275+ auto schema = array->array_schema_latest_ptr ();
276+ REQUIRE (schema->is_enumeration_loaded (" my_enum" ) == false );
277+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == false );
278+ // Fetch one enumeration, intentionally leaving the other unloaded.
279+ auto enmr1 = array->get_enumeration (" my_enum" );
280+ REQUIRE (schema->is_enumeration_loaded (" my_enum" ) == true );
281+
282+ // If not using array open v3 just test that the correct exception is thrown
283+ if (!array->use_refactored_array_open ()) {
284+ CHECK_THROWS_WITH (
285+ array->load_all_enumerations (true ),
286+ Catch::Matchers::ContainsSubstring (
287+ " The array must be opened using "
288+ " `rest.use_refactored_array_open=true`" ));
289+ return ;
290+ }
291+
292+ // Load all enumerations.
293+ auto actual_enmrs = array->get_enumerations_all_schemas ();
294+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == true );
295+ auto enmr2 = array->get_enumeration (" fruit" );
296+
297+ decltype (actual_enmrs) expected_enmrs{{schema->name (), {enmr1, enmr2}}};
298+ auto validate_enmrs = [&]() {
299+ for (const auto & [schema_name, enmrs] : expected_enmrs) {
300+ REQUIRE (actual_enmrs.contains (schema_name));
301+ REQUIRE (enmrs.size () == actual_enmrs[schema_name].size ());
302+ CHECK_THAT (
303+ enmrs,
304+ Catch::Matchers::UnorderedRangeEquals (
305+ actual_enmrs[schema_name], [](const auto & a, const auto & b) {
306+ return tiledb::test::is_equivalent_enumeration (*a, *b);
307+ }));
308+ }
309+ };
310+ validate_enmrs ();
311+
312+ // Evolve once to add an enumeration.
313+ sm::URI uri (uri_);
314+ auto ase = make_shared<sm::ArraySchemaEvolution>(HERE (), memory_tracker_);
315+ std::vector<std::string> var_values{" one" , " two" , " three" };
316+ auto var_enmr = Enumeration::create (ctx_, " ase_var_enmr" , var_values);
317+ ase->add_enumeration (var_enmr.ptr ()->enumeration ());
318+ auto attr4 =
319+ make_shared<sm::Attribute>(HERE (), " attr4" , sm::Datatype::UINT16);
320+ attr4->set_enumeration_name (" ase_var_enmr" );
321+ CHECK_NOTHROW (ase->evolve_schema (schema));
322+ // Apply evolution to the array and reopen.
323+ CHECK_NOTHROW (sm::Array::evolve_array_schema (
324+ ctx_.ptr ()->resources (), uri, ase.get (), array->get_encryption_key ()));
325+ CHECK (array->reopen ().ok ());
326+ schema = array->array_schema_latest_ptr ();
327+ std::string schema_name_2 = schema->name ();
328+ REQUIRE (schema->is_enumeration_loaded (" my_enum" ) == false );
329+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == false );
330+ REQUIRE (schema->is_enumeration_loaded (" ase_var_enmr" ) == false );
331+
332+ SECTION (" Partial load a single evolved enumeration" ) {
333+ // Load all enumerations except the enumeration we added with evolution
334+ // above.
335+ array->get_enumeration (" my_enum" );
336+ REQUIRE (schema->is_enumeration_loaded (" my_enum" ) == true );
337+ array->get_enumeration (" fruit" );
338+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == true );
339+ // Load the remaining `ase_var_enmr` enumeration.
340+ actual_enmrs = array->get_enumerations_all_schemas ();
341+ expected_enmrs[schema_name_2] = {
342+ enmr1, enmr2, var_enmr.ptr ()->enumeration ()};
343+ validate_enmrs ();
344+ }
345+
346+ SECTION (" Partial load multiple enumerations" ) {
347+ // Load all enumerations except the enumeration we added with evolution
348+ // above.
349+ array->get_enumeration (" fruit" );
350+ REQUIRE (schema->is_enumeration_loaded (" fruit" ) == true );
351+ // Load the remaining `my_enum` and `ase_var_enmr` enumerations.
352+ }
353+
354+ actual_enmrs = array->get_enumerations_all_schemas ();
355+ expected_enmrs[schema_name_2] = {enmr1, enmr2, var_enmr.ptr ()->enumeration ()};
356+ validate_enmrs ();
357+
358+ SECTION (" Drop all enumerations and validate earlier schemas" ) {
359+ ase = make_shared<sm::ArraySchemaEvolution>(HERE (), memory_tracker_);
360+ ase->drop_enumeration (" my_enum" );
361+ ase->drop_attribute (" attr1" );
362+ ase->drop_enumeration (" fruit" );
363+ ase->drop_attribute (" attr3" );
364+ ase->drop_enumeration (" ase_var_enmr" );
365+ CHECK_NOTHROW (ase->evolve_schema (schema));
366+ CHECK_NOTHROW (sm::Array::evolve_array_schema (
367+ ctx_.ptr ()->resources (), uri, ase.get (), array->get_encryption_key ()));
368+ CHECK (array->reopen ().ok ());
369+ schema = array->array_schema_latest_ptr ();
370+ std::string schema_name_3 = schema->name ();
371+ actual_enmrs = array->get_enumerations_all_schemas ();
372+ expected_enmrs[schema_name_3] = {};
373+ validate_enmrs ();
374+ }
375+ }
376+
148377RESTEnumerationFx::RESTEnumerationFx ()
149- : ctx_(vfs_test_setup_.ctx()){};
378+ : memory_tracker_(tiledb::test::create_test_memory_tracker())
379+ , ctx_(vfs_test_setup_.ctx()){};
150380
151381void RESTEnumerationFx::create_array (const std::string& array_name) {
152382 // Create a simple array for testing. This ends up with just five elements in
0 commit comments