Skip to content

Commit 493ab82

Browse files
authored
Update get_enumerations_all_schemas to store enumerations in the latest schema. (#5360)
This stores the loaded enumerations in the latest array schema after initializing all_array_schemas via `Array::get_enumerations_all_schemas` and adds two unit tests to cover some edge cases I encountered while improving testing. I also added a check to `get_enumerations_all_schemas` to potentially save a request if all enumerations are already loaded, so it could be called after array open with `rest_load_enumerations_on_array_open=true` and we will not spend extra time reloading enumerations. --- TYPE: NO_HISTORY DESC: Store enumerations loaded by `Array::get_enumerations_all_schemas` in the latest array schema.
1 parent aad089c commit 493ab82

File tree

11 files changed

+742
-214
lines changed

11 files changed

+742
-214
lines changed

test/src/unit-cppapi-enumerations.cc

Lines changed: 357 additions & 78 deletions
Large diffs are not rendered by default.

test/src/unit-enumerations.cc

Lines changed: 8 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,103 +1118,32 @@ TEST_CASE_METHOD(
11181118
REQUIRE(schema->is_enumeration_loaded("test_enmr") == true);
11191119
}
11201120

1121-
TEST_CASE_METHOD(
1122-
EnumerationFx,
1123-
"Array - Load All Enumerations - All Schemas",
1124-
"[enumeration][array][load-all-enumerations][all-schemas]") {
1125-
create_array();
1126-
auto array = get_array(QueryType::READ);
1127-
auto schema = array->array_schema_latest_ptr();
1128-
REQUIRE(schema->is_enumeration_loaded("test_enmr") == false);
1129-
std::string schema_name_1 = schema->name();
1130-
1131-
// If not using array open v3 just test that the correct exception is thrown
1132-
if (!array->use_refactored_array_open()) {
1133-
CHECK_THROWS_WITH(
1134-
array->load_all_enumerations(true),
1135-
Catch::Matchers::ContainsSubstring(
1136-
"The array must be opened using "
1137-
"`rest.use_refactored_array_open=true`"));
1138-
return;
1139-
}
1140-
1141-
// Evolve once to add an enumeration.
1142-
auto ase = make_shared<ArraySchemaEvolution>(HERE(), memory_tracker_);
1143-
std::vector<std::string> var_values{"one", "two", "three"};
1144-
auto var_enmr = create_enumeration(
1145-
var_values, false, Datatype::STRING_ASCII, "ase_var_enmr");
1146-
ase->add_enumeration(var_enmr);
1147-
auto attr4 = make_shared<Attribute>(HERE(), "attr4", Datatype::UINT16);
1148-
attr4->set_enumeration_name("ase_var_enmr");
1149-
CHECK_NOTHROW(ase->evolve_schema(schema));
1150-
// Apply evolution to the array and reopen.
1151-
CHECK_NOTHROW(Array::evolve_array_schema(
1152-
ctx_.resources(), uri_, ase.get(), array->get_encryption_key()));
1153-
CHECK(array->reopen().ok());
1154-
CHECK_NOTHROW(array->load_all_enumerations(true));
1155-
auto all_schemas = array->array_schemas_all();
1156-
schema = array->array_schema_latest_ptr();
1157-
std::string schema_name_2 = schema->name();
1158-
1159-
// Check all schemas.
1160-
CHECK(all_schemas[schema_name_1]->is_enumeration_loaded("test_enmr") == true);
1161-
CHECK(all_schemas[schema_name_2]->is_enumeration_loaded("test_enmr") == true);
1162-
CHECK(
1163-
all_schemas[schema_name_2]->is_enumeration_loaded("ase_var_enmr") ==
1164-
true);
1165-
1166-
// Evolve a second time to drop an enumeration.
1167-
ase = make_shared<ArraySchemaEvolution>(HERE(), memory_tracker_);
1168-
ase->drop_enumeration("test_enmr");
1169-
ase->drop_attribute("attr1");
1170-
CHECK_NOTHROW(ase->evolve_schema(schema));
1171-
// Apply evolution to the array and reopen.
1172-
CHECK_NOTHROW(Array::evolve_array_schema(
1173-
ctx_.resources(), uri_, ase.get(), array->get_encryption_key()));
1174-
CHECK(array->reopen().ok());
1175-
CHECK_NOTHROW(array->load_all_enumerations(true));
1176-
all_schemas = array->array_schemas_all();
1177-
schema = array->array_schema_latest_ptr();
1178-
std::string schema_name_3 = schema->name();
1179-
1180-
// Check all schemas.
1181-
CHECK(all_schemas[schema_name_1]->is_enumeration_loaded("test_enmr") == true);
1182-
CHECK(all_schemas[schema_name_2]->is_enumeration_loaded("test_enmr") == true);
1183-
CHECK(
1184-
all_schemas[schema_name_2]->is_enumeration_loaded("ase_var_enmr") ==
1185-
true);
1186-
CHECK_THROWS_WITH(
1187-
all_schemas[schema_name_3]->is_enumeration_loaded("test_enmr"),
1188-
Catch::Matchers::ContainsSubstring("No enumeration named"));
1189-
CHECK(
1190-
all_schemas[schema_name_3]->is_enumeration_loaded("ase_var_enmr") ==
1191-
true);
1192-
}
1193-
11941121
TEST_CASE_METHOD(
11951122
EnumerationFx,
11961123
"Array - Load All Enumerations - Repeated",
11971124
"[enumeration][array][load-all-enumerations][repeated]") {
11981125
create_array();
1126+
bool all_schemas = GENERATE(true, false);
11991127
auto array = get_array(QueryType::READ);
12001128
auto schema = array->array_schema_latest_ptr();
12011129

12021130
REQUIRE(schema->is_enumeration_loaded("test_enmr") == false);
12031131

1204-
CHECK_NOTHROW(array->load_all_enumerations());
1132+
CHECK_NOTHROW(array->load_all_enumerations(all_schemas));
12051133
REQUIRE(schema->is_enumeration_loaded("test_enmr") == true);
12061134

1207-
CHECK_NOTHROW(array->load_all_enumerations());
1135+
CHECK_NOTHROW(array->load_all_enumerations(all_schemas));
12081136
REQUIRE(schema->is_enumeration_loaded("test_enmr") == true);
12091137
}
12101138

12111139
TEST_CASE_METHOD(
12121140
EnumerationFx,
12131141
"Array - Load All Enumerations Error - Not Open",
12141142
"[enumeration][array][error][not-open]") {
1143+
bool all_schemas = GENERATE(true, false);
12151144
auto array = make_shared<Array>(HERE(), ctx_.resources(), uri_);
12161145
auto matcher = Catch::Matchers::ContainsSubstring("Array is not open");
1217-
REQUIRE_THROWS(array->load_all_enumerations());
1146+
REQUIRE_THROWS(array->load_all_enumerations(all_schemas));
12181147
}
12191148

12201149
/* ********************************* */
@@ -1704,8 +1633,9 @@ TEST_CASE_METHOD(
17041633
"ArraySchemaEvolution - Simple No Enumeration",
17051634
"[enumeration][array-schema-evolution][simple]") {
17061635
create_array();
1636+
bool all_schemas = GENERATE(true, false);
17071637
auto array = get_array(QueryType::READ);
1708-
array->load_all_enumerations();
1638+
array->load_all_enumerations(all_schemas);
17091639

17101640
auto orig_schema = array->array_schema_latest_ptr();
17111641
auto ase = make_shared<ArraySchemaEvolution>(HERE(), memory_tracker_);
@@ -2558,7 +2488,7 @@ TEST_CASE_METHOD(
25582488
REQUIRE(a1->serialize_enumerations() == (do_load == "true"));
25592489
REQUIRE(
25602490
a1->array_schema_latest_ptr()->get_loaded_enumeration_names().size() ==
2561-
0);
2491+
(do_load == "true" ? 2 : 0));
25622492

25632493
auto a2 = make_shared<Array>(HERE(), ctx.resources(), uri_);
25642494

test/src/unit-rest-enumerations.cc

Lines changed: 231 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@
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+
148377
RESTEnumerationFx::RESTEnumerationFx()
149-
: ctx_(vfs_test_setup_.ctx()){};
378+
: memory_tracker_(tiledb::test::create_test_memory_tracker())
379+
, ctx_(vfs_test_setup_.ctx()){};
150380

151381
void 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

Comments
 (0)