diff --git a/test/src/unit-cppapi-enumerations.cc b/test/src/unit-cppapi-enumerations.cc index 63e3d7d7e3e..95e9027422a 100644 --- a/test/src/unit-cppapi-enumerations.cc +++ b/test/src/unit-cppapi-enumerations.cc @@ -33,6 +33,7 @@ #include #include +#include "test/support/src/array_schema_helpers.h" #include "test/support/src/vfs_helpers.h" #include "tiledb/api/c_api/array/array_api_internal.h" #include "tiledb/api/c_api/array_schema/array_schema_api_internal.h" @@ -313,6 +314,96 @@ TEST_CASE_METHOD( REQUIRE(enmr_name2.has_value() == false); } +TEST_CASE_METHOD( + CPPEnumerationFx, + "CPP: Enumerations From Disk - ArraySchema::get_enumeration_from_name", + "[enumeration][array-schema-get-enumeration-from-name][rest]") { + create_array(); + + std::optional expect_enumeration; + { + auto array = tiledb::Array(ctx_, uri_, TILEDB_READ); + expect_enumeration = + ArrayExperimental::get_enumeration(ctx_, array, enmr_name); + } + + SECTION("default schema load retrieves enumeration on request only") { + auto schema = Array::load_schema(ctx_, uri_); + + CHECK(!schema.ptr()->array_schema()->is_enumeration_loaded(enmr_name)); + + auto actual_enumeration = + ArraySchemaExperimental::get_enumeration_from_name( + ctx_, schema, enmr_name); + CHECK(schema.ptr()->array_schema()->is_enumeration_loaded(enmr_name)); + CHECK(test::is_equivalent_enumeration( + *expect_enumeration, actual_enumeration)); + } + + SECTION("schema load with rest config retrieves enumeration eagerly") { + Config config; + config["rest.load_enumerations_on_array_open"] = "true"; + + auto schema = Array::load_schema_with_config(ctx_, config, uri_); + CHECK(schema.ptr()->array_schema()->is_enumeration_loaded(enmr_name)); + + // requesting it should do no I/O (we did it already), + // unclear how to check that + auto actual_enumeration = + ArraySchemaExperimental::get_enumeration_from_name( + ctx_, schema, enmr_name); + CHECK(schema.ptr()->array_schema()->is_enumeration_loaded(enmr_name)); + CHECK(test::is_equivalent_enumeration( + *expect_enumeration, actual_enumeration)); + } +} + +TEST_CASE_METHOD( + CPPEnumerationFx, + "CPP: Enumerations From Disk - " + "ArraySchema::get_enumeration_from_attribute_name", + "[enumeration][array-schema-get-enumeration-from-attribute-name][rest]") { + create_array(); + + const std::string attr_name = "attr1"; + + std::optional expect_enumeration; + { + auto array = tiledb::Array(ctx_, uri_, TILEDB_READ); + expect_enumeration = + ArrayExperimental::get_enumeration(ctx_, array, enmr_name); + } + + SECTION("default schema load retrieves enumeration on request only") { + auto schema = Array::load_schema(ctx_, uri_); + + CHECK(!schema.ptr()->array_schema()->is_enumeration_loaded(enmr_name)); + + auto actual_enumeration = + ArraySchemaExperimental::get_enumeration_from_attribute_name( + ctx_, schema, attr_name); + CHECK(schema.ptr()->array_schema()->is_enumeration_loaded(enmr_name)); + CHECK(test::is_equivalent_enumeration( + *expect_enumeration, actual_enumeration)); + } + + SECTION("schema load with rest config retrieves enumeration eagerly") { + Config config; + config["rest.load_enumerations_on_array_open"] = "true"; + + auto schema = Array::load_schema_with_config(ctx_, config, uri_); + CHECK(schema.ptr()->array_schema()->is_enumeration_loaded(enmr_name)); + + // requesting it should do no I/O (we did it already), + // unclear how to check that + auto actual_enumeration = + ArraySchemaExperimental::get_enumeration_from_attribute_name( + ctx_, schema, attr_name); + CHECK(schema.ptr()->array_schema()->is_enumeration_loaded(enmr_name)); + CHECK(test::is_equivalent_enumeration( + *expect_enumeration, actual_enumeration)); + } +} TEST_CASE_METHOD( CPPEnumerationFx, "CPP: Array::load_all_enumerations", diff --git a/test/support/CMakeLists.txt b/test/support/CMakeLists.txt index 8ffc4028043..f6d11f1ac4c 100644 --- a/test/support/CMakeLists.txt +++ b/test/support/CMakeLists.txt @@ -36,6 +36,7 @@ list(APPEND TILEDB_CORE_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/tiledb/sm/c_api") # Gather the test source files set(TILEDB_TEST_SUPPORT_SOURCES + src/array_schema_helpers.cc src/ast_helpers.h src/ast_helpers.cc src/helpers.h diff --git a/test/support/src/array_schema_helpers.cc b/test/support/src/array_schema_helpers.cc new file mode 100644 index 00000000000..f9aa9855bc0 --- /dev/null +++ b/test/support/src/array_schema_helpers.cc @@ -0,0 +1,54 @@ +/** + * @file array_schema_helpers.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2017-2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file defines some array schema test suite helper functions. + */ + +#include "test/support/src/array_schema_helpers.h" +#include "tiledb/api/c_api/enumeration/enumeration_api_internal.h" +#include "tiledb/sm/array_schema/enumeration.h" +#include "tiledb/sm/cpp_api/tiledb" + +using namespace tiledb; + +namespace tiledb::test { + +bool is_equivalent_enumeration( + const Enumeration& left, const Enumeration& right) { + return left.name() == right.name() && left.type() == right.type() && + left.cell_val_num() == right.cell_val_num() && + left.ordered() == right.ordered() && + std::equal( + left.ptr()->data().begin(), + left.ptr()->data().end(), + right.ptr()->data().begin(), + right.ptr()->data().end()); +} + +} // namespace tiledb::test diff --git a/test/support/src/array_schema_helpers.h b/test/support/src/array_schema_helpers.h new file mode 100644 index 00000000000..578c5aefe9b --- /dev/null +++ b/test/support/src/array_schema_helpers.h @@ -0,0 +1,50 @@ +/** + * @file array_schema_helpers.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2017-2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file declares some array schema test suite helper functions. + */ + +#ifndef TILEDB_TEST_ARRAY_SCHEMA_HELPERS_H +#define TILEDB_TEST_ARRAY_SCHEMA_HELPERS_H + +#include "tiledb/sm/cpp_api/tiledb" +#include "tiledb/sm/cpp_api/tiledb_experimental" + +namespace tiledb::test { + +/** + * @return if two enumerations `left` and `right` are equivalent, + * i.e. have the same name, datatype, variants, etc + */ +bool is_equivalent_enumeration( + const tiledb::Enumeration& left, const tiledb::Enumeration& right); + +} // namespace tiledb::test + +#endif diff --git a/tiledb/api/c_api/array/test/CMakeLists.txt b/tiledb/api/c_api/array/test/CMakeLists.txt index f846cc1e97e..5496cc0c1fd 100644 --- a/tiledb/api/c_api/array/test/CMakeLists.txt +++ b/tiledb/api/c_api/array/test/CMakeLists.txt @@ -31,7 +31,6 @@ commence(unit_test capi_array) this_target_object_libraries( capi_array_stub capi_array_schema_stub - capi_attribute_stub capi_domain_stub ) this_target_link_libraries(tiledb_test_support_lib) diff --git a/tiledb/api/c_api/array_schema/CMakeLists.txt b/tiledb/api/c_api/array_schema/CMakeLists.txt index 4beb4b49d9f..187639ee0ce 100644 --- a/tiledb/api/c_api/array_schema/CMakeLists.txt +++ b/tiledb/api/c_api/array_schema/CMakeLists.txt @@ -36,6 +36,8 @@ commence(object_library capi_array_schema_stub) this_target_sources(${SOURCES}) this_target_link_libraries(export) this_target_object_libraries(array_schema) + this_target_object_libraries(array) + this_target_object_libraries(capi_attribute_stub) this_target_object_libraries(capi_context_stub) conclude(object_library) diff --git a/tiledb/api/c_api/array_schema/array_schema_api.cc b/tiledb/api/c_api/array_schema/array_schema_api.cc index 36887033bf5..dfaf4d093f4 100644 --- a/tiledb/api/c_api/array_schema/array_schema_api.cc +++ b/tiledb/api/c_api/array_schema/array_schema_api.cc @@ -34,6 +34,7 @@ #include "array_schema_api_experimental.h" #include "array_schema_api_internal.h" +#include "tiledb/api/c_api/attribute/attribute_api_external_experimental.h" #include "tiledb/api/c_api/attribute/attribute_api_internal.h" #include "tiledb/api/c_api/context/context_api_internal.h" #include "tiledb/api/c_api/current_domain/current_domain_api_external_experimental.h" @@ -181,6 +182,54 @@ capi_return_t tiledb_array_schema_timestamp_range( return TILEDB_OK; } +capi_return_t tiledb_array_schema_get_enumeration_from_name( + tiledb_ctx_t* ctx, + tiledb_array_schema_t* array_schema, + const char* enumeration_name, + tiledb_enumeration_t** enumeration) { + ensure_array_schema_is_valid(array_schema); + ensure_output_pointer_is_valid(enumeration); + + if (enumeration_name == nullptr) { + throw CAPIException("'enumeration_name' must not be null"); + } + + array_schema->load_enumeration(ctx, enumeration_name); + + auto ptr = array_schema->get_enumeration(enumeration_name); + *enumeration = tiledb_enumeration_handle_t::make_handle(ptr); + + return TILEDB_OK; +} + +capi_return_t tiledb_array_schema_get_enumeration_from_attribute_name( + tiledb_ctx_t* ctx, + tiledb_array_schema_t* array_schema, + const char* attribute_name, + tiledb_enumeration_t** enumeration) { + ensure_array_schema_is_valid(array_schema); + ensure_output_pointer_is_valid(enumeration); + + tiledb_attribute_t* attribute; + capi_return_t getattr = tiledb_array_schema_get_attribute_from_name( + ctx, array_schema, attribute_name, &attribute); + if (tiledb_status(getattr) != TILEDB_OK) { + return getattr; + } + + tiledb_string_t* enumeration_name_inner; + capi_return_t getenmr = tiledb_attribute_get_enumeration_name( + ctx, attribute, &enumeration_name_inner); + if (tiledb_status(getenmr) != TILEDB_OK) { + return getenmr; + } + + std::string enumeration_name(enumeration_name_inner->view()); + return api_entry_with_context< + tiledb::api::tiledb_array_schema_get_enumeration_from_name>( + ctx, array_schema, enumeration_name.c_str(), enumeration); +} + capi_return_t tiledb_array_schema_add_enumeration( tiledb_array_schema_t* array_schema, tiledb_enumeration_t* enumeration) { ensure_array_schema_is_valid(array_schema); @@ -365,6 +414,10 @@ capi_return_t tiledb_array_schema_get_attribute_from_name( ensure_array_schema_is_valid(array_schema); ensure_output_pointer_is_valid(attr); + if (name == nullptr) { + throw CAPIException("'attribute_name' must not be null"); + } + uint32_t attribute_num = array_schema->attribute_num(); if (attribute_num == 0) { *attr = nullptr; @@ -540,6 +593,28 @@ CAPI_INTERFACE( ctx, array_schema, lo, hi); } +CAPI_INTERFACE( + array_schema_get_enumeration_from_name, + tiledb_ctx_t* ctx, + tiledb_array_schema_t* array_schema, + const char* enumeration_name, + tiledb_enumeration_t** enumeration) { + return api_entry_with_context< + tiledb::api::tiledb_array_schema_get_enumeration_from_name>( + ctx, array_schema, enumeration_name, enumeration); +} + +CAPI_INTERFACE( + array_schema_get_enumeration_from_attribute_name, + tiledb_ctx_t* ctx, + tiledb_array_schema_t* array_schema, + const char* attribute_name, + tiledb_enumeration_t** enumeration) { + return api_entry_with_context< + tiledb::api::tiledb_array_schema_get_enumeration_from_attribute_name>( + ctx, array_schema, attribute_name, enumeration); +} + CAPI_INTERFACE( array_schema_add_enumeration, tiledb_ctx_t* ctx, diff --git a/tiledb/api/c_api/array_schema/array_schema_api_experimental.h b/tiledb/api/c_api/array_schema/array_schema_api_experimental.h index ab050882366..ca2cd467c4e 100644 --- a/tiledb/api/c_api/array_schema/array_schema_api_experimental.h +++ b/tiledb/api/c_api/array_schema/array_schema_api_experimental.h @@ -67,6 +67,61 @@ TILEDB_EXPORT capi_return_t tiledb_array_schema_timestamp_range( uint64_t* lo, uint64_t* hi) TILEDB_NOEXCEPT; +/** + * Retrieves an enumeration from an array schema using the enumeration name, + * + * **Example:** + * + * The following retrieves the enumeration named "states" in the schema. + * + * @code{.c} + * tiledb_enumeration_t* enmr; + * tiledb_array_schema_get_enumeration_from_name(ctx, + * array_schema, "states", &enmr); + * tiledb_enumeration_free(&enmr); + * @endcode + * + * @param[in] ctx The TileDB context. + * @param[in] array_schema The array schema. + * @param[in] name The name of the enumeration to retrieve. + * @param[out] enmr The enumeration object to retrieve. + * @return `TILEDB_OK` for success and `TILEDB_ERR` for error. + */ +TILEDB_EXPORT capi_return_t tiledb_array_schema_get_enumeration_from_name( + tiledb_ctx_t* ctx, + tiledb_array_schema_t* array_schema, + const char* enumeration_name, + tiledb_enumeration_t** enumeration) TILEDB_NOEXCEPT; + +/** + * Retrieves an enumeration from an array schema from the attribute with the + * given name. + * + * **Example:** + * + * The following retrieves the enumeration for the attribute named "states" in + * the schema. + * + * @code{.c} + * tiledb_enumeration_t* enmr; + * tiledb_array_schema_get_enumeration_from_attribute_name(ctx, + * array_schema, "states", &enmr); + * tiledb_enumeration_free(&enmr); + * @endcode + * + * @param[in] ctx The TileDB context. + * @param[in] array_schema The array schema. + * @param[in] name The name of the attribute whose enumeration to retrieve. + * @param[out] enmr The enumeration object to retrieve. + * @return `TILEDB_OK` for success and `TILEDB_ERR` for error. + */ +TILEDB_EXPORT capi_return_t +tiledb_array_schema_get_enumeration_from_attribute_name( + tiledb_ctx_t* ctx, + tiledb_array_schema_t* array_schema, + const char* attribute_name, + tiledb_enumeration_t** enumeration) TILEDB_NOEXCEPT; + /** * Adds an enumeration to an array schema. * diff --git a/tiledb/api/c_api/array_schema/array_schema_api_internal.h b/tiledb/api/c_api/array_schema/array_schema_api_internal.h index c8daaa13106..364c05262d0 100644 --- a/tiledb/api/c_api/array_schema/array_schema_api_internal.h +++ b/tiledb/api/c_api/array_schema/array_schema_api_internal.h @@ -35,8 +35,10 @@ #include "array_schema_api_external.h" +#include "tiledb/api/c_api/context/context_api_internal.h" #include "tiledb/api/c_api_support/handle/handle.h" #include "tiledb/common/common.h" +#include "tiledb/sm/array/array.h" #include "tiledb/sm/array_schema/array_schema.h" #include "tiledb/sm/enums/array_type.h" #include "tiledb/sm/enums/layout.h" @@ -90,6 +92,16 @@ struct tiledb_array_schema_handle_t dim_id, name, label_order, label_type, check_name); } + void load_enumeration(tiledb_ctx_t* ctx, const char* enumeration_name) { + load_enumeration_into_schema( + ctx->context(), enumeration_name, *array_schema_); + } + + shared_ptr get_enumeration( + const char* name) const { + return array_schema_->get_enumeration(name); + } + void add_enumeration(shared_ptr enmr) { return array_schema_->add_enumeration(enmr); } diff --git a/tiledb/api/c_api/array_schema/test/CMakeLists.txt b/tiledb/api/c_api/array_schema/test/CMakeLists.txt index a61b624205b..f39b09d180d 100644 --- a/tiledb/api/c_api/array_schema/test/CMakeLists.txt +++ b/tiledb/api/c_api/array_schema/test/CMakeLists.txt @@ -30,7 +30,6 @@ commence(unit_test capi_array_schema) this_target_sources(unit_capi_array_schema.cc) this_target_object_libraries( capi_array_schema_stub - capi_attribute_stub capi_current_domain capi_domain_stub ) diff --git a/tiledb/api/c_api/array_schema/test/unit_capi_array_schema.cc b/tiledb/api/c_api/array_schema/test/unit_capi_array_schema.cc index 8e86cc4c5e6..98a388072bd 100644 --- a/tiledb/api/c_api/array_schema/test/unit_capi_array_schema.cc +++ b/tiledb/api/c_api/array_schema/test/unit_capi_array_schema.cc @@ -32,6 +32,7 @@ #include #include "../../../c_api_test_support/testsupport_capi_array_schema.h" #include "../../../c_api_test_support/testsupport_capi_context.h" +#include "../../attribute/attribute_api_external_experimental.h" #include "../array_schema_api_experimental.h" #include "../array_schema_api_external.h" #include "../array_schema_api_internal.h" @@ -361,6 +362,148 @@ TEST_CASE( CHECK(enumeration == nullptr); } +TEST_CASE( + "C API: tiledb_array_schema_get_enumeration_from_name argument validation", + "[capi][array_schema]") { + capi_return_t rc; + ordinary_array_schema x{}; + tiledb_enumeration_t* enumeration; + SECTION("null context") { + rc = tiledb_array_schema_get_enumeration_from_name( + nullptr, x.schema, "primes", &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_INVALID_CONTEXT); + } + SECTION("null schema") { + rc = tiledb_array_schema_get_enumeration_from_name( + x.ctx(), nullptr, "primes", &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_ERR); + } + SECTION("null name") { + rc = tiledb_array_schema_get_enumeration_from_name( + x.ctx(), x.schema, nullptr, &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_ERR); + } + SECTION("null enumeration") { + rc = tiledb_array_schema_get_enumeration_from_name( + x.ctx(), x.schema, "primes", nullptr); + REQUIRE(tiledb_status(rc) == TILEDB_ERR); + } + SECTION("success") { + int32_t values[5] = {2, 3, 5, 7, 11}; + rc = tiledb_enumeration_alloc( + x.ctx(), + "primes", + TILEDB_UINT32, + 1, + 0, + values, + sizeof(uint32_t) * 5, + nullptr, + 0, + &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + rc = tiledb_array_schema_add_enumeration(x.ctx(), x.schema, enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + REQUIRE_NOTHROW(tiledb_enumeration_free(&enumeration)); + CHECK(enumeration == nullptr); + + rc = tiledb_array_schema_get_enumeration_from_name( + x.ctx(), x.schema, "primes", &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + REQUIRE(enumeration != nullptr); + + tiledb_string_t* tiledb_name(nullptr); + rc = tiledb_enumeration_get_name(x.ctx(), enumeration, &tiledb_name); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + REQUIRE(tiledb_name != nullptr); + + const char* name; + size_t length; + rc = tiledb_string_view(tiledb_name, &name, &length); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + CHECK(std::string(name, length) == "primes"); + } +} + +TEST_CASE( + "C API: tiledb_array_schema_get_enumeration_from_attribute_name argument " + "validation", + "[capi][array_schema]") { + capi_return_t rc; + ordinary_array_schema_with_attr x{}; + tiledb_enumeration_t* enumeration; + SECTION("null context") { + rc = tiledb_array_schema_get_enumeration_from_attribute_name( + nullptr, x.schema, "a", &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_INVALID_CONTEXT); + } + SECTION("null schema") { + rc = tiledb_array_schema_get_enumeration_from_attribute_name( + x.ctx(), nullptr, "a", &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_ERR); + } + SECTION("null name") { + rc = tiledb_array_schema_get_enumeration_from_attribute_name( + x.ctx(), x.schema, nullptr, &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_ERR); + } + SECTION("null enumeration") { + rc = tiledb_array_schema_get_enumeration_from_attribute_name( + x.ctx(), x.schema, "a", nullptr); + REQUIRE(tiledb_status(rc) == TILEDB_ERR); + } + SECTION("invalid attribute") { + rc = tiledb_array_schema_get_enumeration_from_attribute_name( + x.ctx(), x.schema, "foobar", nullptr); + REQUIRE(tiledb_status(rc) == TILEDB_ERR); + } + SECTION("success") { + // create and add enumeration to schema + int32_t values[5] = {2, 3, 5, 7, 11}; + rc = tiledb_enumeration_alloc( + x.ctx(), + "primes", + TILEDB_UINT32, + 1, + 0, + values, + sizeof(uint32_t) * 5, + nullptr, + 0, + &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + rc = tiledb_array_schema_add_enumeration(x.ctx(), x.schema, enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + REQUIRE_NOTHROW(tiledb_enumeration_free(&enumeration)); + CHECK(enumeration == nullptr); + + // add enumeration to the attribute + tiledb_attribute_t* attribute; + rc = tiledb_array_schema_get_attribute_from_name( + x.ctx(), x.schema, "a", &attribute); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + rc = tiledb_attribute_set_enumeration_name(x.ctx(), attribute, "primes"); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + + // then retrieve the enumeration using attribute name + rc = tiledb_array_schema_get_enumeration_from_attribute_name( + x.ctx(), x.schema, "a", &enumeration); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + REQUIRE(enumeration != nullptr); + + tiledb_string_t* tiledb_name(nullptr); + rc = tiledb_enumeration_get_name(x.ctx(), enumeration, &tiledb_name); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + REQUIRE(tiledb_name != nullptr); + + const char* name; + size_t length; + rc = tiledb_string_view(tiledb_name, &name, &length); + REQUIRE(tiledb_status(rc) == TILEDB_OK); + CHECK(std::string(name, length) == "primes"); + } +} + TEST_CASE( "C API: tiledb_array_schema_set_coords_filter_list argument validation", "[capi][array_schema]") { @@ -759,6 +902,11 @@ TEST_CASE( x.ctx(), nullptr, "a", &attr); REQUIRE(tiledb_status(rc) == TILEDB_ERR); } + SECTION("null name") { + rc = tiledb_array_schema_get_attribute_from_name( + x.ctx(), x.schema, nullptr, &attr); + REQUIRE(tiledb_status(rc) == TILEDB_ERR); + } SECTION("invalid name") { rc = tiledb_array_schema_get_attribute_from_name( x.ctx(), x.schema, "b", &attr); diff --git a/tiledb/sm/array/array.cc b/tiledb/sm/array/array.cc index f7a708fd2de..3126a0f1f3c 100644 --- a/tiledb/sm/array/array.cc +++ b/tiledb/sm/array/array.cc @@ -56,6 +56,7 @@ #include "tiledb/sm/object/object_mutex.h" #include "tiledb/sm/query/update_value.h" #include "tiledb/sm/rest/rest_client.h" +#include "tiledb/sm/storage_manager/context.h" #include "tiledb/sm/tile/generic_tile_io.h" #include @@ -827,7 +828,8 @@ Array::get_enumerations_all_schemas() { array_uri_, array_dir_timestamp_start_, array_dir_timestamp_end_, - this, + config_, + array_schema_latest(), {}, memory_tracker_); @@ -908,7 +910,8 @@ std::vector> Array::get_enumerations( array_uri_, array_dir_timestamp_start_, array_dir_timestamp_end_, - this, + config_, + array_schema_latest(), names_to_load, memory_tracker_)[array_schema_latest().name()]; } else { @@ -2071,4 +2074,59 @@ void ensure_supported_schema_version_for_read(format_version_t version) { } } +// NB: this is used to implement `tiledb_array_schema_get_enumeration_*` +// but is defined here instead of array_schema to avoid a circular dependency +// (array_directory depends on array_schema). +void load_enumeration_into_schema( + Context& ctx, const std::string& enmr_name, ArraySchema& array_schema) { + if (array_schema.is_enumeration_loaded(enmr_name)) { + return; + } + + auto tracker = ctx.resources().ephemeral_memory_tracker(); + + if (array_schema.array_uri().is_tiledb()) { + auto rest_client = ctx.resources().rest_client(); + if (rest_client == nullptr) { + throw ArrayException( + "Error loading enumerations; Remote array schema with no REST " + "client."); + } + + auto ret = rest_client->post_enumerations_from_rest( + array_schema.array_uri(), + array_schema.timestamp_start(), + array_schema.timestamp_end(), + ctx.resources().config(), + array_schema, + {enmr_name}, + tracker); + + // response is a map {schema: [enumerations]} + // we should be the only schema, and expect only one enumeration + for (auto enumeration : ret[array_schema.name()]) { + array_schema.store_enumeration(enumeration); + } + } else { + auto& path = array_schema.get_enumeration_path_name(enmr_name); + + // Create key + tiledb::sm::EncryptionKey key; + throw_if_not_ok( + key.set_key(tiledb::sm::EncryptionType::NO_ENCRYPTION, nullptr, 0)); + + // Load URIs from the array directory + tiledb::sm::ArrayDirectory array_dir( + ctx.resources(), + array_schema.array_uri(), + 0, + UINT64_MAX, + tiledb::sm::ArrayDirectoryMode::SCHEMA_ONLY); + + auto enumeration = array_dir.load_enumeration(path, key, tracker); + + array_schema.store_enumeration(enumeration); + } +} + } // namespace tiledb::sm diff --git a/tiledb/sm/array/array.h b/tiledb/sm/array/array.h index babc3009a43..2861cbd5435 100644 --- a/tiledb/sm/array/array.h +++ b/tiledb/sm/array/array.h @@ -52,6 +52,7 @@ namespace tiledb::sm { class ArraySchema; class ArraySchemaEvolution; +class Context; class FragmentMetadata; class MemoryTracker; enum class QueryType : uint8_t; @@ -1214,6 +1215,17 @@ class Array { void set_array_closed(); }; +/** + * Loads an enumeration into a schema. + * Used to implement `tiledb_array_schema_get_enumeration*` APIs. + * + * @param ctx + * @param enmr_name the requested enumeration + * @param schema the target schema + */ +void load_enumeration_into_schema( + Context& ctx, const std::string& enmr_name, ArraySchema& array_schema); + } // namespace tiledb::sm #endif // TILEDB_ARRAY_H diff --git a/tiledb/sm/array/array_directory.h b/tiledb/sm/array/array_directory.h index 3016109a8b8..03c13646ec5 100644 --- a/tiledb/sm/array/array_directory.h +++ b/tiledb/sm/array/array_directory.h @@ -399,6 +399,18 @@ class ArrayDirectory { const EncryptionKey& encryption_key, shared_ptr memory_tracker) const; + /** + * Load an enumeration from the given path. + * + * @param enumeration_path The enumeration path to load. + * @param encryption_key The encryption key to use. + * @return shared_ptr The loaded enumeration. + */ + shared_ptr load_enumeration( + const std::string& enumeration_path, + const EncryptionKey& encryption_key, + shared_ptr memory_tracker) const; + /** Returns the array URI. */ const URI& uri() const; @@ -819,18 +831,6 @@ class ArrayDirectory { * @return True if supported, false otherwise */ bool consolidation_with_timestamps_supported(const URI& uri) const; - - /** - * Load an enumeration from the given path. - * - * @param enumeration_path The enumeration path to load. - * @param encryption_key The encryption key to use. - * @return shared_ptr The loaded enumeration. - */ - shared_ptr load_enumeration( - const std::string& enumeration_path, - const EncryptionKey& encryption_key, - shared_ptr memory_tracker) const; }; } // namespace tiledb::sm diff --git a/tiledb/sm/array_schema/array_schema.cc b/tiledb/sm/array_schema/array_schema.cc index 8413112b055..34046da55f8 100644 --- a/tiledb/sm/array_schema/array_schema.cc +++ b/tiledb/sm/array_schema/array_schema.cc @@ -1529,6 +1529,10 @@ uint64_t ArraySchema::timestamp_start() const { return timestamp_range_.first; } +uint64_t ArraySchema::timestamp_end() const { + return timestamp_range_.second; +} + const URI& ArraySchema::uri() const { return uri_; } diff --git a/tiledb/sm/array_schema/array_schema.h b/tiledb/sm/array_schema/array_schema.h index 8ca8daae80d..4dec74ee410 100644 --- a/tiledb/sm/array_schema/array_schema.h +++ b/tiledb/sm/array_schema/array_schema.h @@ -568,9 +568,12 @@ class ArraySchema { /** Returns the timestamp range. */ std::pair timestamp_range() const; - /** Returns the the first timestamp. */ + /** Returns the first timestamp. */ uint64_t timestamp_start() const; + /** Returns the end timestamp */ + uint64_t timestamp_end() const; + /** Returns the array schema uri. */ const URI& uri() const; diff --git a/tiledb/sm/array_schema/array_schema_operations.cc b/tiledb/sm/array_schema/array_schema_operations.cc index d17388b48d6..bb8bab73547 100644 --- a/tiledb/sm/array_schema/array_schema_operations.cc +++ b/tiledb/sm/array_schema/array_schema_operations.cc @@ -238,12 +238,35 @@ shared_ptr load_array_schema( throw std::runtime_error("Failed to load array schema; Invalid array URI"); } + // Load enumerations if config option is set. + const bool incl_enums = config.get( + "rest.load_enumerations_on_array_open", Config::must_find); + if (uri.is_tiledb()) { auto& rest_client = ctx.rest_client(); auto&& [st, array_schema_response] = rest_client.get_array_schema_from_rest(uri); throw_if_not_ok(st); - return std::move(array_schema_response).value(); + auto array_schema = std::move(array_schema_response).value(); + + if (incl_enums) { + auto tracker = ctx.resources().ephemeral_memory_tracker(); + // Pass an empty list of enumeration names. REST will use timestamps to + // load all enumerations on all schemas for the array within that range. + auto ret = rest_client.post_enumerations_from_rest( + uri, + array_schema->timestamp_start(), + array_schema->timestamp_end(), + config, + *array_schema, + {}, + tracker); + + for (auto& enmr : ret[array_schema->name()]) { + array_schema->store_enumeration(enmr); + } + } + return array_schema; } else { // Create key tiledb::sm::EncryptionKey key; @@ -264,9 +287,6 @@ shared_ptr load_array_schema( auto&& array_schema_latest = array_dir->load_array_schema_latest(key, tracker); - // Load enumerations if config option is set. - bool incl_enums = config.get( - "rest.load_enumerations_on_array_open", Config::must_find); if (incl_enums) { std::vector enmr_paths_to_load; auto enmr_names = array_schema_latest->get_enumeration_names(); diff --git a/tiledb/sm/cpp_api/array.h b/tiledb/sm/cpp_api/array.h index 6ef99ad961c..1b21340d9a5 100644 --- a/tiledb/sm/cpp_api/array.h +++ b/tiledb/sm/cpp_api/array.h @@ -717,6 +717,32 @@ class Array { return ArraySchema(ctx, schema); } + /** + * Loads the array schema from an array. + * Options to load additional features are read from the optionally-provided + * `config`. See `tiledb_array_schema_load_with_config`. + * + * **Example:** + * @code{.cpp} + * tiledb::Config config; + * config["rest.load_enumerations_on_array_open"] = "true"; + * auto schema = tiledb::Array::load_schema_with_config(ctx, config, + * "s3://bucket-name/array-name"); + * @endcode + * + * @param ctx The TileDB context. + * @param config The request for additional features. + * @param uri The array URI. + * @return The loaded ArraySchema object. + */ + static ArraySchema load_schema_with_config( + const Context& ctx, const Config& config, const std::string& uri) { + tiledb_array_schema_t* schema; + ctx.handle_error(tiledb_array_schema_load_with_config( + ctx.ptr().get(), config.ptr().get(), uri.c_str(), &schema)); + return ArraySchema(ctx, schema); + } + /** * Gets the encryption type the given array was created with. * diff --git a/tiledb/sm/cpp_api/array_schema_experimental.h b/tiledb/sm/cpp_api/array_schema_experimental.h index 8da27be3c94..a1ef94bd7fb 100644 --- a/tiledb/sm/cpp_api/array_schema_experimental.h +++ b/tiledb/sm/cpp_api/array_schema_experimental.h @@ -189,6 +189,52 @@ class ArraySchemaExperimental { ctx.ptr().get(), array_schema.ptr().get(), current_domain.ptr().get())); } + /** + * Retrieve an enumeration from the array schema using the enumeration name. + * + * @param ctx TileDB context. + * @param array_schema Array schema. + * @param enumeration_name The name of the enumeration to retrieve. + * + * @return the loaded enumeration. + */ + static Enumeration get_enumeration_from_name( + const Context& ctx, + const ArraySchema& array_schema, + const std::string& enumeration_name) { + tiledb_enumeration_t* enumeration; + ctx.handle_error(tiledb_array_schema_get_enumeration_from_name( + ctx.ptr().get(), + array_schema.ptr().get(), + enumeration_name.c_str(), + &enumeration)); + return Enumeration(ctx, enumeration); + } + + /** + * Retrieve an enumeration from the array schema from the attribute + * with the given name. + * + * @param ctx TileDB context. + * @param array_schema Array schema. + * @param attribute_name The name of the attribute whose enumeration to + * retrieve. + * + * @return the loaded enumeration. + */ + static Enumeration get_enumeration_from_attribute_name( + const Context& ctx, + const ArraySchema& array_schema, + const std::string& attribute_name) { + tiledb_enumeration_t* enumeration; + ctx.handle_error(tiledb_array_schema_get_enumeration_from_attribute_name( + ctx.ptr().get(), + array_schema.ptr().get(), + attribute_name.c_str(), + &enumeration)); + return Enumeration(ctx, enumeration); + } + /** * Add an enumeration to the array schema. * diff --git a/tiledb/sm/rest/rest_client.h b/tiledb/sm/rest/rest_client.h index cc456e258fe..3a823f0804f 100644 --- a/tiledb/sm/rest/rest_client.h +++ b/tiledb/sm/rest/rest_client.h @@ -383,7 +383,8 @@ class RestClient { const URI&, uint64_t, uint64_t, - Array*, + const Config&, + const ArraySchema&, const std::vector&, shared_ptr) { throw RestClientDisabledException(); diff --git a/tiledb/sm/rest/rest_client_remote.cc b/tiledb/sm/rest/rest_client_remote.cc index 87926f01efe..fb97579ba5f 100644 --- a/tiledb/sm/rest/rest_client_remote.cc +++ b/tiledb/sm/rest/rest_client_remote.cc @@ -220,10 +220,13 @@ RestClientRemote::get_array_schema_from_rest(const URI& uri) { // Ensure data has a null delimiter for cap'n proto if using JSON RETURN_NOT_OK_TUPLE( ensure_json_null_delimited_string(&returned_data), nullopt); - return { - Status::Ok(), - serialization::array_schema_deserialize( - serialization_type_, returned_data, memory_tracker_)}; + + auto array_schema = serialization::array_schema_deserialize( + serialization_type_, returned_data, memory_tracker_); + + array_schema->set_array_uri(uri); + + return {Status::Ok(), array_schema}; } std::tuple< @@ -571,14 +574,10 @@ RestClientRemote::post_enumerations_from_rest( const URI& uri, uint64_t timestamp_start, uint64_t timestamp_end, - Array* array, + const Config& config, + const ArraySchema& array_schema, const std::vector& enumeration_names, shared_ptr memory_tracker) { - if (array == nullptr) { - throw RestClientException( - "Error getting enumerations from REST; array is null."); - } - if (!memory_tracker) { memory_tracker = memory_tracker_; } @@ -586,7 +585,7 @@ RestClientRemote::post_enumerations_from_rest( BufferList serialized{memory_tracker_}; auto& buff = serialized.emplace_buffer(); serialization::serialize_load_enumerations_request( - array->config(), enumeration_names, serialization_type_, buff); + config, enumeration_names, serialization_type_, buff); // Init curl and form the URL Curl curlc(logger_); @@ -617,7 +616,7 @@ RestClientRemote::post_enumerations_from_rest( // Ensure data has a null delimiter for cap'n proto if using JSON throw_if_not_ok(ensure_json_null_delimited_string(&returned_data)); return serialization::deserialize_load_enumerations_response( - *array, serialization_type_, returned_data, memory_tracker); + array_schema, serialization_type_, returned_data, memory_tracker); } void RestClientRemote::post_query_plan_from_rest( diff --git a/tiledb/sm/rest/rest_client_remote.h b/tiledb/sm/rest/rest_client_remote.h index bc2f52c312a..3d823d97328 100644 --- a/tiledb/sm/rest/rest_client_remote.h +++ b/tiledb/sm/rest/rest_client_remote.h @@ -279,7 +279,8 @@ class RestClientRemote : public RestClient { * @param uri Array URI. * @param timestamp_start Inclusive starting timestamp at which to open array. * @param timestamp_end Inclusive ending timestamp at which to open array. - * @param array Array to fetch metadata for. + * @param config Config options + * @param array_schema Array schema to fetch enumerations for. * @param enumeration_names The names of the enumerations to get. */ std::unordered_map>> @@ -287,7 +288,8 @@ class RestClientRemote : public RestClient { const URI& uri, uint64_t timestamp_start, uint64_t timestamp_end, - Array* array, + const Config& config, + const ArraySchema& array_schema, const std::vector& enumeration_names, shared_ptr) override; diff --git a/tiledb/sm/serialization/enumeration.cc b/tiledb/sm/serialization/enumeration.cc index da5ea73c239..c21251ab40a 100644 --- a/tiledb/sm/serialization/enumeration.cc +++ b/tiledb/sm/serialization/enumeration.cc @@ -191,7 +191,7 @@ void load_enumerations_response_to_capnp( std::unordered_map>> load_enumerations_response_from_capnp( const capnp::LoadEnumerationsResponse::Reader& reader, - const Array& array, + const ArraySchema& array_schema, shared_ptr memory_tracker) { std::unordered_map>> ret; @@ -204,7 +204,7 @@ load_enumerations_response_from_capnp( } // The name of the latest array schema will not be serialized in the // response if we are only loading enumerations from the latest schema. - return {{array.array_schema_latest().name(), loaded_enmrs}}; + return {{array_schema.name(), loaded_enmrs}}; } else if (reader.hasAllEnumerations()) { auto all_enmrs_reader = reader.getAllEnumerations(); for (auto enmr_entry_reader : all_enmrs_reader.getEntries()) { @@ -345,7 +345,7 @@ void serialize_load_enumerations_response( std::unordered_map>> deserialize_load_enumerations_response( - const Array& array, + const ArraySchema& array_schema, SerializationType serialize_type, span response, shared_ptr memory_tracker) { @@ -359,7 +359,7 @@ deserialize_load_enumerations_response( json.decode(kj::StringPtr(response.data(), response.size()), builder); capnp::LoadEnumerationsResponse::Reader reader = builder.asReader(); return load_enumerations_response_from_capnp( - reader, array, memory_tracker); + reader, array_schema, memory_tracker); } case SerializationType::CAPNP: { const auto mBytes = reinterpret_cast(response.data()); @@ -369,7 +369,7 @@ deserialize_load_enumerations_response( capnp::LoadEnumerationsResponse::Reader reader = array_reader.getRoot(); return load_enumerations_response_from_capnp( - reader, array, memory_tracker); + reader, array_schema, memory_tracker); } default: { throw EnumerationSerializationException( diff --git a/tiledb/sm/serialization/enumeration.h b/tiledb/sm/serialization/enumeration.h index ed53f9d4e4e..210d91f9d23 100644 --- a/tiledb/sm/serialization/enumeration.h +++ b/tiledb/sm/serialization/enumeration.h @@ -92,7 +92,7 @@ void serialize_load_enumerations_response( std::unordered_map>> deserialize_load_enumerations_response( - const Array& array, + const ArraySchema& array_schema, SerializationType serialization_type, span response, shared_ptr memory_tracker);