From a7fd8d6dd74bb4fa1ae25a6f995da93812f92c20 Mon Sep 17 00:00:00 2001 From: joe maley Date: Tue, 10 Nov 2020 16:47:53 -0500 Subject: [PATCH] Nullable Attribute Support (#1895) Attributes can be defined as nullable. Nullable attributes require a "validity vector" buffer for both read and write queries, similar to how var-sized attributes require an additional "offsets" buffer. Both fixed and var-sized attributes may be nullable. Using the C API, attributes must be set nullable before adding them to the schema, e.g.: ``` tiledb_attribute_t* attr; tiledb_attribute_alloc(ctx, "my_attr", TILEDB_INT32, &attr); tiledb_attribute_set_nullable(ctx, attr, 1 /* nullable */); tiledb_array_schema_t* array_schema; tiledb_array_schema_alloc(ctx_, TILEDB_DENSE, &array_schema); tiledb_array_schema_add_attribute(ctx_, array_schema, attr); ``` Write queries require a validity vector (bytemap) for nullable attributes. In the below example, values "200" and "300" are null. These values may or may not be written to the disk. TileDB may treat them as garbage. ``` int32_t buffer = {100, 200, 300, 400}; uint64_t buffer_size = sizeof(buffer); uint8_t buffer_validity = {1, 0, 0, 1}; uint64_t buffer_validity_size = sizeof(buffer_validity); tiledb_query_set_buffer_nullable( ctx, query, "my_attr", buffer, buffer_size, buffer_validity, buffer_validity_size); ``` Overview: - Format version bumped from 6 to 7. - Validity vector buffers are written to their own tile, similar to how offset buffers are written to their own tile, separate from the value tile. - Currently, the "validity vector" is a bytemap in all usage (APIs, in-memory, and on-disk). In the future, we could like to store the validity vector as a bitmap in-memory and on-disk, but allowing the user to use an API that uses either a bitmap or bytemap. - A new, internal `ValidityVector` class has been introduced to store the validity vector in-memory. This may seem extraneous because it wraps a simple buffer, but this will change in the future when we support bitmaps. - Similar to the existing "sm.memory_budget" and "sm.memory_budget_var" config parameters, there is now a "sm.memory_budget_validity" for budgeting the validity vector buffers. - Similar to offset tiles, validity tiles have their own compressor that is independent of the user-defined attribute filter. I have tentatively chosen RLE compression. - C/C++ APIs has been added. - The `QueryBuffer` class has been moved from `misc/query_buffer.h` to `query/query_buffer.h` because it now depends on `query/validity_vector`, which is outside of the `misc` directory. - Many of the internal classes are now nullable-aware (`Reader`, `Writer`, `Query`, `FilterPipeline`, `Subarray`, `SubarrayPartitioner`). Co-authored-by: Joe Maley --- HISTORY.md | 5 +- doc/source/c-api.rst | 12 + format_spec/FORMAT_SPEC.md | 4 +- format_spec/array_schema.md | 5 +- format_spec/fragment.md | 6 +- test/CMakeLists.txt | 13 +- test/src/unit-ReadCellSlabIter.cc | 42 +- test/src/unit-SubarrayPartitioner-dense.cc | 8 +- test/src/unit-SubarrayPartitioner-error.cc | 10 +- test/src/unit-SubarrayPartitioner-sparse.cc | 22 +- test/src/unit-ValidityVector.cc | 103 +++ test/src/unit-capi-array_schema.cc | 5 +- test/src/unit-capi-consolidation.cc | 2 +- test/src/unit-capi-fill_values.cc | 25 +- test/src/unit-capi-nullable.cc | 847 +++++++++++++++++++ test/src/unit-cppapi-fill_values.cc | 27 +- test/src/unit-cppapi-nullable.cc | 563 +++++++++++++ tiledb/common/status.cc | 3 + tiledb/common/status.h | 6 + tiledb/sm/array_schema/array_schema.cc | 34 +- tiledb/sm/array_schema/array_schema.h | 14 +- tiledb/sm/array_schema/attribute.cc | 35 +- tiledb/sm/array_schema/attribute.h | 26 +- tiledb/sm/array_schema/dimension.h | 1 + tiledb/sm/array_schema/domain.h | 1 + tiledb/sm/c_api/tiledb.cc | 144 ++++ tiledb/sm/c_api/tiledb.h | 226 ++++- tiledb/sm/config/config.h | 2 +- tiledb/sm/cpp_api/attribute.h | 19 + tiledb/sm/cpp_api/query.h | 542 +++++++++++- tiledb/sm/fragment/fragment_metadata.cc | 322 ++++++- tiledb/sm/fragment/fragment_metadata.h | 127 ++- tiledb/sm/misc/constants.cc | 14 +- tiledb/sm/misc/constants.h | 16 +- tiledb/sm/misc/types.h | 59 -- tiledb/sm/misc/utils.cc | 12 +- tiledb/sm/query/query.cc | 206 ++++- tiledb/sm/query/query.h | 162 ++++ tiledb/sm/query/query_buffer.h | 209 +++++ tiledb/sm/query/reader.cc | 599 +++++++++++-- tiledb/sm/query/reader.h | 173 +++- tiledb/sm/query/result_tile.cc | 113 ++- tiledb/sm/query/result_tile.h | 38 +- tiledb/sm/query/validity_vector.h | 166 ++++ tiledb/sm/query/writer.cc | 891 ++++++++++++++++---- tiledb/sm/query/writer.h | 185 +++- tiledb/sm/serialization/query.cc | 22 +- tiledb/sm/stats/stats.cc | 29 +- tiledb/sm/stats/stats.h | 4 + tiledb/sm/subarray/subarray.cc | 310 ++++++- tiledb/sm/subarray/subarray.h | 55 ++ tiledb/sm/subarray/subarray_partitioner.cc | 278 +++++- tiledb/sm/subarray/subarray_partitioner.h | 49 +- 53 files changed, 6291 insertions(+), 500 deletions(-) create mode 100644 test/src/unit-ValidityVector.cc create mode 100644 test/src/unit-capi-nullable.cc create mode 100644 test/src/unit-cppapi-nullable.cc create mode 100644 tiledb/sm/query/query_buffer.h create mode 100644 tiledb/sm/query/validity_vector.h diff --git a/HISTORY.md b/HISTORY.md index b831a295d09..b41605160d6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,9 +10,10 @@ ## New features +* Support for nullable attributes. [#1895](https://github.com/TileDB-Inc/TileDB/pull/1895) +* Support for Hilbert order sorting for sparse arrays. [#1880](https://github.com/TileDB-Inc/TileDB/pull/1880) * Support for AWS S3 "AssumeRole" temporary credentials [#1882](https://github.com/TileDB-Inc/TileDB/pull/1882) -* Added support for Hilbert order sorting for sparse arrays. [#1880](https://github.com/TileDB-Inc/TileDB/pull/1880) -* Added experimental support for an in-memory backend used with bootstrap option "--enable-memfs" [#1873](https://github.com/TileDB-Inc/TileDB/pull/1873) +* Experimental support for an in-memory backend used with bootstrap option "--enable-memfs" [#1873](https://github.com/TileDB-Inc/TileDB/pull/1873) ## Improvements diff --git a/doc/source/c-api.rst b/doc/source/c-api.rst index b67ad107520..43f06ed110d 100644 --- a/doc/source/c-api.rst +++ b/doc/source/c-api.rst @@ -319,6 +319,8 @@ Attribute :project: TileDB-C .. doxygenfunction:: tiledb_attribute_free :project: TileDB-C +.. doxygenfunction:: tiledb_attribute_set_nullable + :project: TileDB-C .. doxygenfunction:: tiledb_attribute_set_filter_list :project: TileDB-C .. doxygenfunction:: tiledb_attribute_set_cell_val_num @@ -327,6 +329,8 @@ Attribute :project: TileDB-C .. doxygenfunction:: tiledb_attribute_get_type :project: TileDB-C +.. doxygenfunction:: tiledb_attribute_get_nullable + :project: TileDB-C .. doxygenfunction:: tiledb_attribute_get_filter_list :project: TileDB-C .. doxygenfunction:: tiledb_attribute_get_cell_val_num @@ -396,10 +400,18 @@ Query :project: TileDB-C .. doxygenfunction:: tiledb_query_set_buffer_var :project: TileDB-C +.. doxygenfunction:: tiledb_query_set_buffer_nullable + :project: TileDB-C +.. doxygenfunction:: tiledb_query_set_buffer_var_nullable + :project: TileDB-C .. doxygenfunction:: tiledb_query_get_buffer :project: TileDB-C .. doxygenfunction:: tiledb_query_get_buffer_var :project: TileDB-C +.. doxygenfunction:: tiledb_query_get_buffer_nullable + :project: TileDB-C +.. doxygenfunction:: tiledb_query_get_buffer_var_nullable + :project: TileDB-C .. doxygenfunction:: tiledb_query_set_layout :project: TileDB-C .. doxygenfunction:: tiledb_query_free diff --git a/format_spec/FORMAT_SPEC.md b/format_spec/FORMAT_SPEC.md index 09fc37b12ff..85fb159a8ca 100644 --- a/format_spec/FORMAT_SPEC.md +++ b/format_spec/FORMAT_SPEC.md @@ -2,7 +2,7 @@ :information_source: **Notes:** -- The current TileDB format version number is **5** (`uint32_t`). +- The current TileDB format version number is **7** (`uint32_t`). - All data written by TileDB and referenced in this document is **little-endian**. ## Table of Contents @@ -15,4 +15,4 @@ * [Tile](./tile.md) * [Generic Tile](./generic_tile.md) * **Group** - * [File hierarchy](./group_file_hierarchy.md) \ No newline at end of file + * [File hierarchy](./group_file_hierarchy.md) diff --git a/format_spec/array_schema.md b/format_spec/array_schema.md index 0ff5d65782a..87804443ddf 100644 --- a/format_spec/array_schema.md +++ b/format_spec/array_schema.md @@ -1,4 +1,4 @@ -# Array Schema File +#Array Schema File The array schema file has name `__array_schema.tdb` and is located here: @@ -23,6 +23,7 @@ The array schema file consists of a single [generic tile](./generic_tile.md), wi | Capacity | `uint64_t` | For sparse fragments, the data tile capacity | | Coords filters | [Filter Pipeline](./filter_pipeline.md) | The filter pipeline used as default for coordinate tiles | | Offsets filters | [Filter Pipeline](./filter_pipeline.md) | The filter pipeline used for cell var-len offset tiles | +| Validity filters | [Filter Pipeline](./filter_pipeline.md) | The filter pipeline used for cell validity tiles | | Domain | [Domain](#domain) | The array domain | | Num attributes | `uint32_t` | Number of attributes in the array | | Attribute 1 | [Attribute](#attribute) | First attribute | @@ -69,4 +70,4 @@ The attribute has internal format: | Filters | [Filter Pipeline](./filter_pipeline.md) | The filter pipeline used on attribute value tiles | | Fill value size | `uint64_t` | The size in bytes of the fill value | | Fill value | `uint8_t[]` | The fill value | - +| Nullable | `bool` | Whether or not the attribute can be null | diff --git a/format_spec/fragment.md b/format_spec/fragment.md index dead2662448..32014041af4 100644 --- a/format_spec/fragment.md +++ b/format_spec/fragment.md @@ -27,7 +27,7 @@ my_array # array folder There can be any number of fragments in an array. The fragment folder contains: * A single [fragment metadata file](#fragment-metadata-file) named `__fragment_metadata.tdb`. -* Any number of [data files](#data-file). For each fixed-sized attribute `a1` (or dimension `d1`), there is a single data file `a1.tdb` (`d1.tdb`) containing the values along this attribute (dimension). For every var-sized attribute `a2` (or dimensions `d2`), there are two data files; `a2_var.tdb` (`d2_var.tdb`) containing the var-sized values of the attribute (dimension) and `a2.tdb` (`d2.tdb`) containing the starting offsets of each value in `a2_var.tdb` (`d2_var.rdb`). +* Any number of [data files](#data-file). For each fixed-sized attribute `a1` (or dimension `d1`), there is a single data file `a1.tdb` (`d1.tdb`) containing the values along this attribute (dimension). For every var-sized attribute `a2` (or dimensions `d2`), there are two data files; `a2_var.tdb` (`d2_var.tdb`) containing the var-sized values of the attribute (dimension) and `a2.tdb` (`d2.tdb`) containing the starting offsets of each value in `a2_var.tdb` (`d2_var.rdb`). Both fixed-sized and var-sized attributes can be nullable. A nullable attribute, `a3`, will have an additional file `a3_validity.tdb` that contains its validity vector. ## Fragment Metadata File @@ -127,6 +127,7 @@ The footer is a simple blob \(i.e., _not a generic tile_\) with the following in | Last tile cell num | `uint64_t` | For sparse arrays, the number of cells in the last tile in the fragment | | File sizes | `uint64_t[]` | The size in bytes of each attribute/dimension file in the fragment. For var-length attributes/dimensions, this is the size of the offsets file. | | File var sizes | `uint64_t[]` | The size in bytes of each var-length attribute/dimension file in the fragment. | +| File validity sizes | `uint64_t[]` | The size in bytes of each attribute/dimension validity vector file in the fragment. | | R-Tree offset | `uint64_t` | The offset to the generic tile storing the R-Tree in the metadata file. | | Tile offset for attribute/dimension 1 | `uint64_t` | The offset to the generic tile storing the tile offsets for attribute/dimension 1. | | … | … | … | @@ -137,6 +138,9 @@ The footer is a simple blob \(i.e., _not a generic tile_\) with the following in | Tile var sizes offset for attribute/dimension 1 | `uint64_t` | The offset to the generic tile storing the variable tile sizes for attribute/dimension 1. | | … | … | … | | Tile var sizes offset for attribute/dimension N | `uint64_t` | The offset to the generic tile storing the variable tile sizes for attribute/dimension N. | +| Tile validity offset for attribute/dimension 1 | `uint64_t` | The offset to the generic tile storing the tile validity offsets for attribute/dimension 1. | +| … | … | … | +| Tile validity offset for attribute/dimension N | `uint64_t` | The offset to the generic tile storing the tile validity offsets for attribute/dimension N | | Footer length | `uint64_t` | Sum of bytes of the above fields. Only present when there is at least one var-sized dimension. | ## Data File diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2398fdf65eb..7d616372a7a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -93,9 +93,9 @@ set(TILEDB_TEST_SOURCES src/unit-buffer.cc src/unit-bufferlist.cc src/unit-capi-any.cc + src/unit-capi-array.cc src/unit-capi-array_schema.cc src/unit-capi-async.cc - src/unit-capi-array.cc src/unit-capi-buffer.cc src/unit-capi-config.cc src/unit-capi-consolidation.cc @@ -103,15 +103,15 @@ set(TILEDB_TEST_SOURCES src/unit-capi-dense_array_2.cc src/unit-capi-dense_neg.cc src/unit-capi-dense_vector.cc - src/unit-duplicates.cc src/unit-capi-empty-var-length.cc src/unit-capi-enum_values.cc src/unit-capi-error.cc src/unit-capi-fill_values.cc src/unit-capi-filter.cc - src/unit-capi-incomplete.cc src/unit-capi-incomplete-2.cc + src/unit-capi-incomplete.cc src/unit-capi-metadata.cc + src/unit-capi-nullable.cc src/unit-capi-object_mgmt.cc src/unit-capi-query.cc src/unit-capi-query_2.cc @@ -120,12 +120,13 @@ set(TILEDB_TEST_SOURCES src/unit-capi-sparse_neg.cc src/unit-capi-sparse_neg_2.cc src/unit-capi-sparse_real.cc - src/unit-capi-string_dims.cc src/unit-capi-sparse_real_2.cc src/unit-capi-string.cc + src/unit-capi-string_dims.cc src/unit-capi-uri.cc src/unit-capi-version.cc src/unit-capi-vfs.cc + src/unit-duplicates.cc src/unit-CellSlabIter.cc src/unit-compression-dd.cc src/unit-compression-rle.cc @@ -156,6 +157,7 @@ set(TILEDB_TEST_SOURCES src/unit-uri.cc src/unit-utils.cc src/unit-uuid.cc + src/unit-ValidityVector.cc src/unit-vfs.cc src/unit-win-filesystem.cc src/unit.cc @@ -166,13 +168,14 @@ if (TILEDB_CPP_API) src/unit-cppapi-array.cc src/unit-cppapi-checksum.cc src/unit-cppapi-config.cc - src/unit-cppapi-consolidation.cc src/unit-cppapi-consolidation-sparse.cc + src/unit-cppapi-consolidation.cc src/unit-cppapi-datetimes.cc src/unit-cppapi-fill_values.cc src/unit-cppapi-filter.cc src/unit-cppapi-hilbert.cc src/unit-cppapi-metadata.cc + src/unit-cppapi-nullable.cc src/unit-cppapi-query.cc src/unit-cppapi-schema.cc src/unit-cppapi-subarray.cc diff --git a/test/src/unit-ReadCellSlabIter.cc b/test/src/unit-ReadCellSlabIter.cc index 80378b99f9b..9f4da806061 100644 --- a/test/src/unit-ReadCellSlabIter.cc +++ b/test/src/unit-ReadCellSlabIter.cc @@ -445,9 +445,9 @@ TEST_CASE_METHOD( .ok()); Tile tile_2_0( Datatype::UINT64, sizeof(uint64_t), 0, &chunked_buffer_2_0, false); - auto tile_pair = result_tile_2_0.tile_pair("d"); - REQUIRE(tile_pair != nullptr); - tile_pair->first = tile_2_0; + auto tile_tuple = result_tile_2_0.tile_tuple("d"); + REQUIRE(tile_tuple != nullptr); + std::get<0>(*tile_tuple) = tile_2_0; std::vector vec_3_0 = {1000, 1000, 8, 9}; Buffer buff_3_0(&vec_3_0[0], vec_3_0.size() * sizeof(uint64_t)); @@ -457,9 +457,9 @@ TEST_CASE_METHOD( .ok()); Tile tile_3_0( Datatype::UINT64, sizeof(uint64_t), 0, &chunked_buffer_3_0, false); - tile_pair = result_tile_3_0.tile_pair("d"); - REQUIRE(tile_pair != nullptr); - tile_pair->first = tile_3_0; + tile_tuple = result_tile_3_0.tile_tuple("d"); + REQUIRE(tile_tuple != nullptr); + std::get<0>(*tile_tuple) = tile_3_0; std::vector vec_3_1 = {1000, 12, 19, 1000}; Buffer buff_3_1(&vec_3_1[0], vec_3_1.size() * sizeof(uint64_t)); @@ -469,9 +469,9 @@ TEST_CASE_METHOD( .ok()); Tile tile_3_1( Datatype::UINT64, sizeof(uint64_t), 0, &chunked_buffer_3_1, false); - tile_pair = result_tile_3_1.tile_pair("d"); - REQUIRE(tile_pair != nullptr); - tile_pair->first = tile_3_1; + tile_tuple = result_tile_3_1.tile_tuple("d"); + REQUIRE(tile_tuple != nullptr); + std::get<0>(*tile_tuple) = tile_3_1; result_coords.emplace_back(&result_tile_2_0, 1); result_coords.emplace_back(&result_tile_2_0, 3); @@ -1271,9 +1271,9 @@ TEST_CASE_METHOD( .ok()); Tile tile_3_0_d1( Datatype::UINT64, sizeof(uint64_t), 0, &chunked_buffer_3_0_d1, false); - auto tile_pair = result_tile_3_0.tile_pair("d1"); - REQUIRE(tile_pair != nullptr); - tile_pair->first = tile_3_0_d1; + auto tile_tuple = result_tile_3_0.tile_tuple("d1"); + REQUIRE(tile_tuple != nullptr); + std::get<0>(*tile_tuple) = tile_3_0_d1; std::vector vec_3_0_d2 = {1000, 3, 1000, 1000}; Buffer buff_3_0_d2(&vec_3_0_d2[0], vec_3_0_d2.size() * sizeof(uint64_t)); @@ -1283,9 +1283,9 @@ TEST_CASE_METHOD( .ok()); Tile tile_3_0_d2( Datatype::UINT64, sizeof(uint64_t), 0, &chunked_buffer_3_0_d2, false); - tile_pair = result_tile_3_0.tile_pair("d2"); - REQUIRE(tile_pair != nullptr); - tile_pair->first = tile_3_0_d2; + tile_tuple = result_tile_3_0.tile_tuple("d2"); + REQUIRE(tile_tuple != nullptr); + std::get<0>(*tile_tuple) = tile_3_0_d2; std::vector vec_3_1_d1 = {5, 1000, 5, 1000}; Buffer buff_3_1_d1(&vec_3_1_d1[0], vec_3_1_d1.size() * sizeof(uint64_t)); @@ -1295,9 +1295,9 @@ TEST_CASE_METHOD( .ok()); Tile tile_3_1_d1( Datatype::UINT64, sizeof(uint64_t), 0, &chunked_buffer_3_1_d1, false); - tile_pair = result_tile_3_1.tile_pair("d1"); - REQUIRE(tile_pair != nullptr); - tile_pair->first = tile_3_1_d1; + tile_tuple = result_tile_3_1.tile_tuple("d1"); + REQUIRE(tile_tuple != nullptr); + std::get<0>(*tile_tuple) = tile_3_1_d1; std::vector vec_3_1_d2 = {5, 1000, 6, 1000}; Buffer buff_3_1_d2(&vec_3_1_d2[0], vec_3_1_d2.size() * sizeof(uint64_t)); @@ -1307,9 +1307,9 @@ TEST_CASE_METHOD( .ok()); Tile tile_3_1_d2( Datatype::UINT64, sizeof(uint64_t), 0, &chunked_buffer_3_1_d2, false); - tile_pair = result_tile_3_1.tile_pair("d2"); - REQUIRE(tile_pair != nullptr); - tile_pair->first = tile_3_1_d2; + tile_tuple = result_tile_3_1.tile_tuple("d2"); + REQUIRE(tile_tuple != nullptr); + std::get<0>(*tile_tuple) = tile_3_1_d2; result_coords.emplace_back(&result_tile_3_0, 1); result_coords.emplace_back(&result_tile_3_1, 0); diff --git a/test/src/unit-SubarrayPartitioner-dense.cc b/test/src/unit-SubarrayPartitioner-dense.cc index c3f86feb9a4..c3d37ed2055 100644 --- a/test/src/unit-SubarrayPartitioner-dense.cc +++ b/test/src/unit-SubarrayPartitioner-dense.cc @@ -268,7 +268,7 @@ void SubarrayPartitionerDenseFx::test_subarray_partitioner( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner subarray_partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); auto st = subarray_partitioner.set_result_budget(attr.c_str(), budget); CHECK(st.ok()); @@ -289,7 +289,7 @@ void SubarrayPartitionerDenseFx::test_subarray_partitioner( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner subarray_partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); // Note: this is necessary, otherwise the subarray partitioner does // not check if the memory budget is exceeded for attributes whose @@ -301,7 +301,7 @@ void SubarrayPartitionerDenseFx::test_subarray_partitioner( st = subarray_partitioner.set_result_budget("b", 1000000, 1000000); CHECK(st.ok()); - st = subarray_partitioner.set_memory_budget(budget, budget_var); + st = subarray_partitioner.set_memory_budget(budget, budget_var, 0); CHECK(st.ok()); check_partitions(subarray_partitioner, partitions, unsplittable); @@ -563,7 +563,7 @@ TEST_CASE_METHOD( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner subarray_partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); auto st = subarray_partitioner.set_result_budget("a", 100 * sizeof(int)); CHECK(st.ok()); st = subarray_partitioner.set_result_budget("b", 1, 1); diff --git a/test/src/unit-SubarrayPartitioner-error.cc b/test/src/unit-SubarrayPartitioner-error.cc index f6ec90d29cf..4af21f77eb0 100644 --- a/test/src/unit-SubarrayPartitioner-error.cc +++ b/test/src/unit-SubarrayPartitioner-error.cc @@ -144,7 +144,7 @@ TEST_CASE_METHOD( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner subarray_partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); uint64_t budget, budget_off, budget_val; auto st = subarray_partitioner.get_result_budget("a", &budget); @@ -207,16 +207,16 @@ TEST_CASE_METHOD( CHECK(st.ok()); CHECK(budget == 1000); - uint64_t memory_budget, memory_budget_var; + uint64_t memory_budget, memory_budget_var, memory_budget_validity; st = subarray_partitioner.get_memory_budget( - &memory_budget, &memory_budget_var); + &memory_budget, &memory_budget_var, &memory_budget_validity); CHECK(st.ok()); CHECK(memory_budget == memory_budget_); CHECK(memory_budget_var == memory_budget_var_); - st = subarray_partitioner.set_memory_budget(16, 16); + st = subarray_partitioner.set_memory_budget(16, 16, 0); CHECK(st.ok()); st = subarray_partitioner.get_memory_budget( - &memory_budget, &memory_budget_var); + &memory_budget, &memory_budget_var, &memory_budget_validity); CHECK(st.ok()); CHECK(memory_budget == 16); CHECK(memory_budget_var == 16); diff --git a/test/src/unit-SubarrayPartitioner-sparse.cc b/test/src/unit-SubarrayPartitioner-sparse.cc index 00e8aa4a715..222863fbf87 100644 --- a/test/src/unit-SubarrayPartitioner-sparse.cc +++ b/test/src/unit-SubarrayPartitioner-sparse.cc @@ -359,7 +359,7 @@ void SubarrayPartitionerSparseFx::test_subarray_partitioner( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner subarray_partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); auto st = subarray_partitioner.set_result_budget(attr.c_str(), budget); CHECK(st.ok()); @@ -382,7 +382,7 @@ void SubarrayPartitionerSparseFx::test_subarray_partitioner( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner subarray_partitioner( - subarray, memory_budget, memory_budget_var, &tp); + subarray, memory_budget, memory_budget_var, 0, &tp); auto st = subarray_partitioner.set_result_budget(attr.c_str(), result_budget); CHECK(st.ok()); @@ -403,7 +403,7 @@ void SubarrayPartitionerSparseFx::test_subarray_partitioner( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner subarray_partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); // Note: this is necessary, otherwise the subarray partitioner does // not check if the memory budget is exceeded for attributes whose @@ -415,7 +415,7 @@ void SubarrayPartitionerSparseFx::test_subarray_partitioner( st = subarray_partitioner.set_result_budget("b", 1000000, 1000000); CHECK(st.ok()); - st = subarray_partitioner.set_memory_budget(budget, budget_var); + st = subarray_partitioner.set_memory_budget(budget, budget_var, 0); CHECK(st.ok()); check_partitions(subarray_partitioner, partitions, unsplittable); @@ -677,7 +677,7 @@ TEST_CASE_METHOD( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner subarray_partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); auto st = subarray_partitioner.set_result_budget("a", 100); CHECK(st.ok()); st = subarray_partitioner.set_result_budget("b", 1, 1); @@ -2271,7 +2271,7 @@ TEST_CASE_METHOD( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); auto st = partitioner.set_result_budget("d", 10); CHECK(!st.ok()); uint64_t budget = 0; @@ -2298,7 +2298,7 @@ TEST_CASE_METHOD( r.set_str_range("a", "bb"); subarray_full.add_range(0, r); SubarrayPartitioner partitioner_full( - subarray_full, memory_budget_, memory_budget_var_, &tp); + subarray_full, memory_budget_, memory_budget_var_, 0, &tp); st = partitioner_full.set_result_budget("d", 16, 4); CHECK(st.ok()); CHECK(partitioner_full.get_result_budget("d", &budget_off, &budget_val).ok()); @@ -2318,7 +2318,7 @@ TEST_CASE_METHOD( r.set_str_range("a", "bb"); subarray_split.add_range(0, r); SubarrayPartitioner partitioner_split( - subarray_split, memory_budget_, memory_budget_var_, &tp); + subarray_split, memory_budget_, memory_budget_var_, 0, &tp); st = partitioner_split.set_result_budget("d", 10, 4); CHECK(st.ok()); CHECK( @@ -2348,7 +2348,7 @@ TEST_CASE_METHOD( r.set_str_range("bb", "cc"); subarray_no_split.add_range(0, r); SubarrayPartitioner partitioner_no_split( - subarray_no_split, memory_budget_, memory_budget_var_, &tp); + subarray_no_split, memory_budget_, memory_budget_var_, 0, &tp); st = partitioner_no_split.set_result_budget("d", 16, 10); CHECK(st.ok()); CHECK(partitioner_no_split.get_result_budget("d", &budget_off, &budget_val) @@ -2370,7 +2370,7 @@ TEST_CASE_METHOD( r.set_str_range("bb", "cc"); subarray_split_2.add_range(0, r); SubarrayPartitioner partitioner_split_2( - subarray_split_2, memory_budget_, memory_budget_var_, &tp); + subarray_split_2, memory_budget_, memory_budget_var_, 0, &tp); st = partitioner_split_2.set_result_budget("d", 8, 10); CHECK(st.ok()); CHECK(partitioner_split_2.get_result_budget("d", &budget_off, &budget_val) @@ -2500,7 +2500,7 @@ TEST_CASE_METHOD( ThreadPool tp; CHECK(tp.init(4).ok()); SubarrayPartitioner partitioner( - subarray, memory_budget_, memory_budget_var_, &tp); + subarray, memory_budget_, memory_budget_var_, 0, &tp); auto st = partitioner.set_result_budget("d", 10, 4); CHECK(st.ok()); CHECK(partitioner.get_result_budget("d", &budget_off, &budget_val).ok()); diff --git a/test/src/unit-ValidityVector.cc b/test/src/unit-ValidityVector.cc new file mode 100644 index 00000000000..e06c2c80709 --- /dev/null +++ b/test/src/unit-ValidityVector.cc @@ -0,0 +1,103 @@ +/** + * @file unit-ValidityVector.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2020 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 + * + * Tests the `ValidityVector` class. + */ + +#include "tiledb/sm/query/validity_vector.h" + +#include +#include + +using namespace std; +using namespace tiledb::sm; + +TEST_CASE( + "ValidityVector: Test default constructor", + "[ValidityVector][default_constructor]") { + ValidityVector validity_vector; + REQUIRE(validity_vector.bytemap() == nullptr); + REQUIRE(validity_vector.bytemap_size() == nullptr); + REQUIRE(validity_vector.buffer() == nullptr); + REQUIRE(validity_vector.buffer_size() == nullptr); +} + +TEST_CASE( + "ValidityVector: Test move constructor", + "[ValidityVector][move_constructor]") { + uint8_t bytemap[10]; + uint64_t bytemap_size = sizeof(bytemap); + for (uint64_t i = 0; i < bytemap_size; ++i) + bytemap[i] = i % 2; + + ValidityVector validity_vector1; + validity_vector1.init_bytemap(bytemap, &bytemap_size); + + ValidityVector validity_vector2(std::move(validity_vector1)); + + REQUIRE(validity_vector2.bytemap() == bytemap); + REQUIRE(validity_vector2.bytemap_size() == &bytemap_size); + REQUIRE(validity_vector2.buffer() == bytemap); + REQUIRE(validity_vector2.buffer_size() == &bytemap_size); +} + +TEST_CASE( + "ValidityVector: Test move-assignment", + "[ValidityVector][move_assignment]") { + uint8_t bytemap[10]; + uint64_t bytemap_size = sizeof(bytemap); + for (uint64_t i = 0; i < bytemap_size; ++i) + bytemap[i] = i % 2; + + ValidityVector validity_vector1; + validity_vector1.init_bytemap(bytemap, &bytemap_size); + + ValidityVector validity_vector2 = std::move(validity_vector1); + + REQUIRE(validity_vector2.bytemap() == bytemap); + REQUIRE(validity_vector2.bytemap_size() == &bytemap_size); + REQUIRE(validity_vector2.buffer() == bytemap); + REQUIRE(validity_vector2.buffer_size() == &bytemap_size); +} + +TEST_CASE( + "ValidityVector: Test init_bytemap", "[ValidityVector][init_bytemap]") { + uint8_t bytemap[10]; + uint64_t bytemap_size = sizeof(bytemap); + for (uint64_t i = 0; i < bytemap_size; ++i) + bytemap[i] = i % 2; + + ValidityVector validity_vector; + validity_vector.init_bytemap(bytemap, &bytemap_size); + + REQUIRE(validity_vector.bytemap() == bytemap); + REQUIRE(validity_vector.bytemap_size() == &bytemap_size); + REQUIRE(validity_vector.buffer() == bytemap); + REQUIRE(validity_vector.buffer_size() == &bytemap_size); +} diff --git a/test/src/unit-capi-array_schema.cc b/test/src/unit-capi-array_schema.cc index 8a6b81f6b8a..8cec196cdac 100644 --- a/test/src/unit-capi-array_schema.cc +++ b/test/src/unit-capi-array_schema.cc @@ -979,7 +979,8 @@ void ArraySchemaFx::load_and_check_array_schema(const std::string& path) { "false\n" "- Coordinates filters: 1\n" + " > ZSTD: COMPRESSION_LEVEL=-1\n" + "- Offsets filters: 1\n" + - " > ZSTD: COMPRESSION_LEVEL=-1\n\n" + "### Dimension ###\n" + + " > ZSTD: COMPRESSION_LEVEL=-1\n" + "- Validity filters: 1\n" + + " > RLE: COMPRESSION_LEVEL=-1\n\n" + "### Dimension ###\n" + "- Name: " + DIM1_NAME + "\n" + "- Type: INT64\n" + "- Cell val num: 1\n" + "- Domain: " + DIM1_DOMAIN_STR + "\n" + "- Tile extent: " + DIM1_TILE_EXTENT_STR + "\n" + "- Filters: 0\n\n" + @@ -990,7 +991,7 @@ void ArraySchemaFx::load_and_check_array_schema(const std::string& path) { " > BZIP2: COMPRESSION_LEVEL=5\n" + " > BitWidthReduction: BIT_WIDTH_MAX_WINDOW=1000\n\n" + "### Attribute ###\n" + "- Name: " + ATTR_NAME + "\n" + - "- Type: " + ATTR_TYPE_STR + "\n" + + "- Type: " + ATTR_TYPE_STR + "\n" + "- Nullable: false\n" + "- Cell val num: " + CELL_VAL_NUM_STR + "\n" + "- Filters: 2\n" + " > BZIP2: COMPRESSION_LEVEL=5\n" + " > BitWidthReduction: BIT_WIDTH_MAX_WINDOW=1000\n" + diff --git a/test/src/unit-capi-consolidation.cc b/test/src/unit-capi-consolidation.cc index ba9afeeb4f8..a4e816cfaf4 100644 --- a/test/src/unit-capi-consolidation.cc +++ b/test/src/unit-capi-consolidation.cc @@ -3968,7 +3968,7 @@ TEST_CASE_METHOD( REQUIRE(rc == TILEDB_OK); REQUIRE(error == nullptr); rc = tiledb_config_set( - config, "sm.consolidation.step_size_ratio", "0.7", &error); + config, "sm.consolidation.step_size_ratio", "0.75", &error); REQUIRE(rc == TILEDB_OK); REQUIRE(error == nullptr); diff --git a/test/src/unit-capi-fill_values.cc b/test/src/unit-capi-fill_values.cc index 137e0380ebf..16fccdbdfe4 100644 --- a/test/src/unit-capi-fill_values.cc +++ b/test/src/unit-capi-fill_values.cc @@ -93,8 +93,9 @@ TEST_CASE( // Check dump std::string dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: 1\n" + - "- Filters: 0\n" + "- Fill value: -2147483648\n"; + "- Type: INT32\n" + "- Nullable: false\n" + + "- Cell val num: 1\n" + "- Filters: 0\n" + + "- Fill value: -2147483648\n"; check_dump(ctx, a, dump); // Correct setter @@ -109,8 +110,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: 1\n" + "- Filters: 0\n" + - "- Fill value: 5\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: 1\n" + + "- Filters: 0\n" + "- Fill value: 5\n"; check_dump(ctx, a, dump); // Setting the cell val num, also sets the fill value to a new default @@ -124,8 +125,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: 2\n" + "- Filters: 0\n" + - "- Fill value: -2147483648, -2147483648\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: 2\n" + + "- Filters: 0\n" + "- Fill value: -2147483648, -2147483648\n"; check_dump(ctx, a, dump); // Set a fill value that is comprised of two integers @@ -142,8 +143,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: 2\n" + "- Filters: 0\n" + - "- Fill value: 1, 2\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: 2\n" + + "- Filters: 0\n" + "- Fill value: 1, 2\n"; check_dump(ctx, a, dump); // Make the attribute var-sized @@ -152,8 +153,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: var\n" + "- Filters: 0\n" + - "- Fill value: -2147483648\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: var\n" + + "- Filters: 0\n" + "- Fill value: -2147483648\n"; check_dump(ctx, a, dump); // Get the default var-sized fill value @@ -177,8 +178,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: var\n" + "- Filters: 0\n" + - "- Fill value: 1, 2, 3\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: var\n" + + "- Filters: 0\n" + "- Fill value: 1, 2, 3\n"; check_dump(ctx, a, dump); // Clean up diff --git a/test/src/unit-capi-nullable.cc b/test/src/unit-capi-nullable.cc new file mode 100644 index 00000000000..65ceba47779 --- /dev/null +++ b/test/src/unit-capi-nullable.cc @@ -0,0 +1,847 @@ +/** + * @file unit-capi-nullable.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2020 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 + * + * Tests arrays with nullable attributes. + */ + +#include "catch.hpp" +#include "test/src/helpers.h" +#ifdef _WIN32 +#include "tiledb/sm/filesystem/win.h" +#else +#include "tiledb/sm/filesystem/posix.h" +#endif +#include "tiledb/sm/c_api/tiledb.h" +#include "tiledb/sm/misc/utils.h" + +#include +#include + +using namespace std; +using namespace tiledb::sm; +using namespace tiledb::test; + +struct test_dim_t { + test_dim_t( + const string& name, + const tiledb_datatype_t type, + const void* const domain, + const uint64_t tile_extent) + : name_(name) + , type_(type) + , domain_(domain) + , tile_extent_(tile_extent) { + } + + string name_; + tiledb_datatype_t type_; + const void* domain_; + uint64_t tile_extent_; +}; + +struct test_attr_t { + test_attr_t( + const string& name, + const tiledb_datatype_t type, + const uint32_t cell_val_num, + const bool nullable) + : name_(name) + , type_(type) + , cell_val_num_(cell_val_num) + , nullable_(nullable) { + } + + string name_; + tiledb_datatype_t type_; + uint32_t cell_val_num_; + bool nullable_; +}; + +struct test_query_buffer_t { + test_query_buffer_t( + const string& name, + void* const buffer, + uint64_t* const buffer_size, + void* const buffer_var, + uint64_t* const buffer_var_size, + uint8_t* const buffer_validity, + uint64_t* const buffer_validity_size) + : name_(name) + , buffer_(buffer) + , buffer_size_(buffer_size) + , buffer_var_(buffer_var) + , buffer_var_size_(buffer_var_size) + , buffer_validity_(buffer_validity) + , buffer_validity_size_(buffer_validity_size) { + } + + string name_; + void* buffer_; + uint64_t* buffer_size_; + void* buffer_var_; + uint64_t* buffer_var_size_; + uint8_t* buffer_validity_; + uint64_t* buffer_validity_size_; +}; + +class NullableArrayFx { + public: +#ifdef _WIN32 + const string FILE_URI_PREFIX = ""; + const string FILE_TEMP_DIR = + tiledb::sm::Win::current_dir() + "\\tiledb_test\\"; +#else + const string FILE_URI_PREFIX = "file://"; + const string FILE_TEMP_DIR = + tiledb::sm::Posix::current_dir() + "/tiledb_test/"; +#endif + + /** Constructor. */ + NullableArrayFx(); + + /** Destructor. */ + ~NullableArrayFx(); + + /** + * Creates, writes, and reads nullable attributes. + * + * @param test_attrs The nullable attributes to test. + * @param array_type The type of the array (dense/sparse). + * @param cell_order The cell order of the array. + * @param tile_order The tile order of the array. + * @param write_order The write layout. + */ + void do_2d_nullable_test( + const vector& test_attrs, + tiledb_array_type_t array_type, + tiledb_layout_t cell_order, + tiledb_layout_t tile_order, + tiledb_layout_t write_order); + + private: + /** The C-API context object. */ + tiledb_ctx_t* ctx_; + + /** The C-API VFS object. */ + tiledb_vfs_t* vfs_; + + /** + * Creates a directory using `vfs_`. + * + * @param path The directory path. + */ + void create_dir(const string& path); + + /** + * Removes a directory using `vfs_`. + * + * @param path The directory path. + */ + void remove_dir(const string& path); + + /** + * Creates a TileDB array. + * + * @param array_name The name of the array. + * @param array_type The type of the array (dense/sparse). + * @param test_dims The dimensions in the array. + * @param test_attrs The attributes in the array. + * @param cell_order The cell order of the array. + * @param tile_order The tile order of the array. + */ + void create_array( + const string& array_name, + tiledb_array_type_t array_type, + const vector& test_dims, + const vector& test_attrs, + tiledb_layout_t cell_order, + tiledb_layout_t tile_order); + + /** + * Creates and executes a single write query. + * + * @param array_name The name of the array. + * @param test_query_buffers The query buffers to write. + * @param layout The write layout. + */ + void write( + const string& array_name, + const vector& test_query_buffers, + tiledb_layout_t layout); + + /** + * Creates and executes a single read query. + * + * @param array_name The name of the array. + * @param test_query_buffers The query buffers to read. + * @param subarray The subarray to read. + */ + void read( + const string& array_name, + const vector& test_query_buffers, + const void* subarray); +}; + +NullableArrayFx::NullableArrayFx() { + // Create a config. + tiledb_config_t* config = nullptr; + tiledb_error_t* error = nullptr; + REQUIRE(tiledb_config_alloc(&config, &error) == TILEDB_OK); + REQUIRE(error == nullptr); + + // Create the context. + REQUIRE(tiledb_ctx_alloc(config, &ctx_) == TILEDB_OK); + REQUIRE(error == nullptr); + + // Create the VFS. + REQUIRE(tiledb_vfs_alloc(ctx_, config, &vfs_) == TILEDB_OK); + + tiledb_config_free(&config); +} + +NullableArrayFx::~NullableArrayFx() { + remove_dir(FILE_TEMP_DIR); +} + +void NullableArrayFx::create_dir(const string& path) { + REQUIRE(tiledb_vfs_create_dir(ctx_, vfs_, path.c_str()) == TILEDB_OK); +} + +void NullableArrayFx::remove_dir(const string& path) { + int is_dir = 0; + REQUIRE(tiledb_vfs_is_dir(ctx_, vfs_, path.c_str(), &is_dir) == TILEDB_OK); + if (is_dir) + REQUIRE(tiledb_vfs_remove_dir(ctx_, vfs_, path.c_str()) == TILEDB_OK); +} + +void NullableArrayFx::create_array( + const string& array_name, + const tiledb_array_type_t array_type, + const vector& test_dims, + const vector& test_attrs, + const tiledb_layout_t cell_order, + const tiledb_layout_t tile_order) { + remove_dir(FILE_TEMP_DIR); + create_dir(FILE_TEMP_DIR); + + // Create the dimensions. + vector dims; + dims.reserve(test_dims.size()); + for (const auto& test_dim : test_dims) { + tiledb_dimension_t* dim; + const int rc = tiledb_dimension_alloc( + ctx_, + test_dim.name_.c_str(), + test_dim.type_, + test_dim.domain_, + &test_dim.tile_extent_, + &dim); + REQUIRE(rc == TILEDB_OK); + + dims.emplace_back(dim); + } + + // Create the domain. + tiledb_domain_t* domain; + int rc = tiledb_domain_alloc(ctx_, &domain); + REQUIRE(rc == TILEDB_OK); + for (const auto& dim : dims) { + rc = tiledb_domain_add_dimension(ctx_, domain, dim); + REQUIRE(rc == TILEDB_OK); + } + + // Create attributes + vector attrs; + attrs.reserve(test_attrs.size()); + for (const auto& test_attr : test_attrs) { + tiledb_attribute_t* attr; + rc = tiledb_attribute_alloc( + ctx_, test_attr.name_.c_str(), test_attr.type_, &attr); + REQUIRE(rc == TILEDB_OK); + + rc = tiledb_attribute_set_cell_val_num(ctx_, attr, test_attr.cell_val_num_); + REQUIRE(rc == TILEDB_OK); + + if (test_attr.nullable_) { + rc = tiledb_attribute_set_nullable(ctx_, attr, 1); + REQUIRE(rc == TILEDB_OK); + } + + attrs.emplace_back(attr); + } + + // Create array schema + tiledb_array_schema_t* array_schema; + rc = tiledb_array_schema_alloc(ctx_, array_type, &array_schema); + REQUIRE(rc == TILEDB_OK); + rc = tiledb_array_schema_set_cell_order(ctx_, array_schema, cell_order); + REQUIRE(rc == TILEDB_OK); + rc = tiledb_array_schema_set_tile_order(ctx_, array_schema, tile_order); + REQUIRE(rc == TILEDB_OK); + rc = tiledb_array_schema_set_domain(ctx_, array_schema, domain); + REQUIRE(rc == TILEDB_OK); + for (const auto& attr : attrs) { + rc = tiledb_array_schema_add_attribute(ctx_, array_schema, attr); + REQUIRE(rc == TILEDB_OK); + } + + // Check array schema + rc = tiledb_array_schema_check(ctx_, array_schema); + REQUIRE(rc == TILEDB_OK); + + // Create array + rc = tiledb_array_create( + ctx_, (FILE_TEMP_DIR + array_name).c_str(), array_schema); + REQUIRE(rc == TILEDB_OK); + + // Free attributes. + for (auto& attr : attrs) { + tiledb_attribute_free(&attr); + } + + // Free dimensions. + for (auto& dim : dims) { + tiledb_dimension_free(&dim); + } + + // Free the domain. + tiledb_domain_free(&domain); + + // Free the array schema. + tiledb_array_schema_free(&array_schema); +} + +void NullableArrayFx::write( + const string& array_name, + const vector& test_query_buffers, + const tiledb_layout_t layout) { + // Open the array for writing. + tiledb_array_t* array; + int rc = + tiledb_array_alloc(ctx_, (FILE_TEMP_DIR + array_name).c_str(), &array); + REQUIRE(rc == TILEDB_OK); + rc = tiledb_array_open(ctx_, array, TILEDB_WRITE); + REQUIRE(rc == TILEDB_OK); + + // Create the write query. + tiledb_query_t* query; + rc = tiledb_query_alloc(ctx_, array, TILEDB_WRITE, &query); + REQUIRE(rc == TILEDB_OK); + + // Set the query layout. + rc = tiledb_query_set_layout(ctx_, query, layout); + REQUIRE(rc == TILEDB_OK); + + // Set the query buffers. + for (const auto& test_query_buffer : test_query_buffers) { + if (test_query_buffer.buffer_validity_size_ == nullptr) { + if (test_query_buffer.buffer_var_ == nullptr) { + rc = tiledb_query_set_buffer( + ctx_, + query, + test_query_buffer.name_.c_str(), + test_query_buffer.buffer_, + test_query_buffer.buffer_size_); + REQUIRE(rc == TILEDB_OK); + } else { + rc = tiledb_query_set_buffer_var( + ctx_, + query, + test_query_buffer.name_.c_str(), + static_cast(test_query_buffer.buffer_), + test_query_buffer.buffer_size_, + test_query_buffer.buffer_var_, + test_query_buffer.buffer_var_size_); + REQUIRE(rc == TILEDB_OK); + } + } else { + if (test_query_buffer.buffer_var_ == nullptr) { + rc = tiledb_query_set_buffer_nullable( + ctx_, + query, + test_query_buffer.name_.c_str(), + test_query_buffer.buffer_, + test_query_buffer.buffer_size_, + test_query_buffer.buffer_validity_, + test_query_buffer.buffer_validity_size_); + REQUIRE(rc == TILEDB_OK); + } else { + rc = tiledb_query_set_buffer_var_nullable( + ctx_, + query, + test_query_buffer.name_.c_str(), + static_cast(test_query_buffer.buffer_), + test_query_buffer.buffer_size_, + test_query_buffer.buffer_var_, + test_query_buffer.buffer_var_size_, + test_query_buffer.buffer_validity_, + test_query_buffer.buffer_validity_size_); + REQUIRE(rc == TILEDB_OK); + } + } + } + + // Submit the query. + rc = tiledb_query_submit(ctx_, query); + REQUIRE(rc == TILEDB_OK); + + // Finalize the query, a no-op for non-global writes. + rc = tiledb_query_finalize(ctx_, query); + REQUIRE(rc == TILEDB_OK); + + // Clean up + rc = tiledb_array_close(ctx_, array); + REQUIRE(rc == TILEDB_OK); + tiledb_array_free(&array); + tiledb_query_free(&query); +} + +void NullableArrayFx::read( + const string& array_name, + const vector& test_query_buffers, + const void* const subarray) { + // Open the array for reading. + tiledb_array_t* array; + int rc = + tiledb_array_alloc(ctx_, (FILE_TEMP_DIR + array_name).c_str(), &array); + REQUIRE(rc == TILEDB_OK); + rc = tiledb_array_open(ctx_, array, TILEDB_READ); + REQUIRE(rc == TILEDB_OK); + + // Create the read query. + tiledb_query_t* query; + rc = tiledb_query_alloc(ctx_, array, TILEDB_READ, &query); + REQUIRE(rc == TILEDB_OK); + + // Set the query buffers. + for (size_t i = 0; i < test_query_buffers.size(); ++i) { + const test_query_buffer_t& test_query_buffer = test_query_buffers[i]; + if (test_query_buffer.buffer_validity_size_ == nullptr) { + if (test_query_buffer.buffer_var_ == nullptr) { + rc = tiledb_query_set_buffer( + ctx_, + query, + test_query_buffer.name_.c_str(), + test_query_buffer.buffer_, + test_query_buffer.buffer_size_); + REQUIRE(rc == TILEDB_OK); + } else { + rc = tiledb_query_set_buffer_var( + ctx_, + query, + test_query_buffer.name_.c_str(), + static_cast(test_query_buffer.buffer_), + test_query_buffer.buffer_size_, + test_query_buffer.buffer_var_, + test_query_buffer.buffer_var_size_); + REQUIRE(rc == TILEDB_OK); + } + } else { + if (test_query_buffer.buffer_var_ == nullptr) { + rc = tiledb_query_set_buffer_nullable( + ctx_, + query, + test_query_buffer.name_.c_str(), + test_query_buffer.buffer_, + test_query_buffer.buffer_size_, + test_query_buffer.buffer_validity_, + test_query_buffer.buffer_validity_size_); + REQUIRE(rc == TILEDB_OK); + } else { + rc = tiledb_query_set_buffer_var_nullable( + ctx_, + query, + test_query_buffer.name_.c_str(), + static_cast(test_query_buffer.buffer_), + test_query_buffer.buffer_size_, + test_query_buffer.buffer_var_, + test_query_buffer.buffer_var_size_, + test_query_buffer.buffer_validity_, + test_query_buffer.buffer_validity_size_); + REQUIRE(rc == TILEDB_OK); + } + } + } + + // Set the subarray to read. + rc = tiledb_query_set_subarray(ctx_, query, subarray); + REQUIRE(rc == TILEDB_OK); + + // Submit the query. + rc = tiledb_query_submit(ctx_, query); + REQUIRE(rc == TILEDB_OK); + + // Finalize the query, a no-op for non-global writes. + rc = tiledb_query_finalize(ctx_, query); + REQUIRE(rc == TILEDB_OK); + + // Clean up + rc = tiledb_array_close(ctx_, array); + REQUIRE(rc == TILEDB_OK); + tiledb_array_free(&array); + tiledb_query_free(&query); +} + +void NullableArrayFx::do_2d_nullable_test( + const vector& test_attrs, + const tiledb_array_type_t array_type, + const tiledb_layout_t cell_order, + const tiledb_layout_t tile_order, + const tiledb_layout_t write_order) { + const string array_name = "2d_nullable_array"; + + // Skip row-major and col-major writes for sparse arrays. + if (array_type == TILEDB_SPARSE && + (write_order == TILEDB_ROW_MAJOR || write_order == TILEDB_COL_MAJOR)) { + return; + } + + // Define the dimensions. + vector test_dims; + const uint64_t d1_domain[] = {1, 4}; + const uint64_t d1_tile_extent = 2; + test_dims.emplace_back("d1", TILEDB_UINT64, d1_domain, d1_tile_extent); + const uint64_t d2_domain[] = {1, 4}; + const uint64_t d2_tile_extent = 2; + test_dims.emplace_back("d2", TILEDB_UINT64, d2_domain, d2_tile_extent); + + // Create the array. + create_array( + array_name, array_type, test_dims, test_attrs, cell_order, tile_order); + + // Define the write query buffers for "a1". + vector write_query_buffers; + int a1_write_buffer[16]; + for (int i = 0; i < 16; ++i) + a1_write_buffer[i] = i; + uint64_t a1_write_buffer_size = sizeof(a1_write_buffer); + uint8_t a1_write_buffer_validity[16]; + for (int i = 0; i < 16; ++i) + a1_write_buffer_validity[i] = rand() % 2; + uint64_t a1_write_buffer_validity_size = sizeof(a1_write_buffer_validity); + write_query_buffers.emplace_back( + "a1", + a1_write_buffer, + &a1_write_buffer_size, + nullptr, + nullptr, + a1_write_buffer_validity, + &a1_write_buffer_validity_size); + + // Define the write query buffers for "a2". + int a2_write_buffer[16]; + for (int i = 0; i < 16; ++i) + a2_write_buffer[i] = a1_write_buffer[16 - 1 - i]; + uint64_t a2_write_buffer_size = sizeof(a2_write_buffer); + uint8_t a2_write_buffer_validity[16]; + for (uint64_t i = 0; i < 16; ++i) + a2_write_buffer_validity[i] = a1_write_buffer_validity[16 - 1 - i]; + uint64_t a2_write_buffer_validity_size = sizeof(a2_write_buffer_validity); + if (test_attrs.size() >= 2) { + write_query_buffers.emplace_back( + "a2", + a2_write_buffer, + &a2_write_buffer_size, + nullptr, + nullptr, + a2_write_buffer_validity, + &a2_write_buffer_validity_size); + } + + // Define the write query buffers for "a3". + uint64_t a3_write_buffer[16]; + for (uint64_t i = 0; i < 16; ++i) + a3_write_buffer[i] = i * sizeof(int) * 2; + uint64_t a3_write_buffer_size = sizeof(a3_write_buffer); + int a3_write_buffer_var[32]; + for (int i = 0; i < 32; ++i) + a3_write_buffer_var[i] = i; + uint64_t a3_write_buffer_var_size = sizeof(a3_write_buffer_var); + uint8_t a3_write_buffer_validity[32]; + for (int i = 0; i < 32; ++i) + a3_write_buffer_validity[i] = rand() % 2; + uint64_t a3_write_buffer_validity_size = sizeof(a3_write_buffer_validity); + if (test_attrs.size() >= 3) { + write_query_buffers.emplace_back( + "a3", + a3_write_buffer, + &a3_write_buffer_size, + a3_write_buffer_var, + &a3_write_buffer_var_size, + a3_write_buffer_validity, + &a3_write_buffer_validity_size); + } + + // Define dimension query buffers for either sparse arrays or dense arrays + // with an unordered write order. + uint64_t d1_write_buffer[16]; + uint64_t d2_write_buffer[16]; + uint64_t d1_write_buffer_size; + uint64_t d2_write_buffer_size; + if (array_type == TILEDB_SPARSE || write_order == TILEDB_UNORDERED) { + vector d1_write_vec; + vector d2_write_vec; + + // Coordinates for sparse arrays written in global order have unique + // ordering when either/both cell and tile ordering is col-major. + if (array_type == TILEDB_SPARSE && write_order == TILEDB_GLOBAL_ORDER && + (cell_order == TILEDB_COL_MAJOR || tile_order == TILEDB_COL_MAJOR)) { + if (cell_order == TILEDB_ROW_MAJOR && tile_order == TILEDB_COL_MAJOR) { + d1_write_vec = {1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 2, 2, 3, 3, 4, 4}; + d2_write_vec = {1, 2, 1, 2, 1, 2, 1, 2, 3, 4, 3, 4, 3, 4, 3, 4}; + } else if ( + cell_order == TILEDB_COL_MAJOR && tile_order == TILEDB_ROW_MAJOR) { + d1_write_vec = {1, 2, 1, 2, 1, 2, 1, 2, 3, 4, 3, 4, 3, 4, 3, 4}; + d2_write_vec = {1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 2, 2, 3, 3, 4, 4}; + } else { + REQUIRE(cell_order == TILEDB_COL_MAJOR); + REQUIRE(tile_order == TILEDB_COL_MAJOR); + d1_write_vec = {1, 2, 1, 2, 3, 4, 3, 4, 1, 2, 1, 2, 3, 4, 3, 4}; + d2_write_vec = {1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4}; + } + } else { + d1_write_vec = {1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4}; + d2_write_vec = {1, 2, 1, 2, 3, 4, 3, 4, 1, 2, 1, 2, 3, 4, 3, 4}; + } + + REQUIRE(d1_write_vec.size() == 16); + for (size_t i = 0; i < 16; ++i) + d1_write_buffer[i] = d1_write_vec[i]; + d1_write_buffer_size = sizeof(d1_write_buffer); + write_query_buffers.emplace_back( + "d1", + d1_write_buffer, + &d1_write_buffer_size, + nullptr, + nullptr, + nullptr, + nullptr); + + REQUIRE(d2_write_vec.size() == 16); + for (size_t i = 0; i < 16; ++i) + d2_write_buffer[i] = d2_write_vec[i]; + d2_write_buffer_size = sizeof(d2_write_buffer); + write_query_buffers.emplace_back( + "d2", + d2_write_buffer, + &d2_write_buffer_size, + nullptr, + nullptr, + nullptr, + nullptr); + } + + // Execute the write query. + write(array_name, write_query_buffers, write_order); + + // Define the read query buffers for "a1". + vector read_query_buffers; + int a1_read_buffer[16] = {0}; + uint64_t a1_read_buffer_size = sizeof(a1_read_buffer); + uint8_t a1_read_buffer_validity[16] = {0}; + uint64_t a1_read_buffer_validity_size = sizeof(a1_read_buffer_validity); + read_query_buffers.emplace_back( + "a1", + a1_read_buffer, + &a1_read_buffer_size, + nullptr, + nullptr, + a1_read_buffer_validity, + &a1_read_buffer_validity_size); + + // Define the read query buffers for "a2". + int a2_read_buffer[16] = {0}; + uint64_t a2_read_buffer_size = sizeof(a2_read_buffer); + uint8_t a2_read_buffer_validity[16] = {0}; + uint64_t a2_read_buffer_validity_size = sizeof(a2_read_buffer_validity); + if (test_attrs.size() >= 2) { + read_query_buffers.emplace_back( + "a2", + a2_read_buffer, + &a2_read_buffer_size, + nullptr, + nullptr, + a2_read_buffer_validity, + &a2_read_buffer_validity_size); + } + + // Define the read query buffers for "a3". + uint64_t a3_read_buffer[16] = {0}; + uint64_t a3_read_buffer_size = sizeof(a3_read_buffer); + int a3_read_buffer_var[32] = {0}; + uint64_t a3_read_buffer_var_size = sizeof(a3_read_buffer); + uint8_t a3_read_buffer_validity[32] = {0}; + uint64_t a3_read_buffer_validity_size = sizeof(a3_read_buffer_validity); + if (test_attrs.size() >= 3) { + read_query_buffers.emplace_back( + "a3", + a3_read_buffer, + &a3_read_buffer_size, + a3_read_buffer_var, + &a3_read_buffer_var_size, + a3_read_buffer_validity, + &a3_read_buffer_validity_size); + } + + // Execute a read query over the entire domain. + const uint64_t subarray_full[] = {1, 4, 1, 4}; + read(array_name, read_query_buffers, subarray_full); + + // Each value in `a1_read_buffer` corresponds to its index in + // the original `a1_write_buffer`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + REQUIRE(a1_read_buffer_size == a1_write_buffer_size); + REQUIRE(a1_read_buffer_validity_size == a1_write_buffer_validity_size); + uint8_t expected_a1_read_buffer_validity[16]; + for (int i = 0; i < 16; ++i) { + const uint64_t idx = a1_read_buffer[i]; + expected_a1_read_buffer_validity[i] = a1_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a1_read_buffer_validity, + expected_a1_read_buffer_validity, + a1_read_buffer_validity_size)); + + // Each value in `a2_read_buffer` corresponds to its reversed index in + // the original `a2_write_buffer`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + if (test_attrs.size() >= 2) { + REQUIRE(a2_read_buffer_size == a2_write_buffer_size); + REQUIRE(a2_read_buffer_validity_size == a2_write_buffer_validity_size); + uint8_t expected_a2_read_buffer_validity[16]; + for (int i = 0; i < 16; ++i) { + const uint64_t idx = a2_read_buffer[16 - 1 - i]; + expected_a2_read_buffer_validity[i] = a2_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a2_read_buffer_validity, + expected_a2_read_buffer_validity, + a2_read_buffer_validity_size)); + } + + // Each value in `a3_read_buffer_var` corresponds to its index in + // the original `a3_write_buffer_var`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + if (test_attrs.size() >= 3) { + REQUIRE(a3_read_buffer_size == a3_write_buffer_size); + REQUIRE(a3_read_buffer_var_size == a3_write_buffer_var_size); + REQUIRE(a3_read_buffer_validity_size == a3_write_buffer_validity_size); + uint8_t expected_a3_read_buffer_validity[32]; + for (int i = 0; i < 32; ++i) { + const uint64_t idx = a3_read_buffer_var[i]; + expected_a3_read_buffer_validity[i] = a3_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a3_read_buffer_validity, + expected_a3_read_buffer_validity, + a3_read_buffer_validity_size)); + } + + // Execute a read query over a partial domain. + const uint64_t subarray_partial[] = {2, 3, 2, 3}; + read(array_name, read_query_buffers, subarray_partial); + + // Each value in `a1_read_buffer` corresponds to its index in + // the original `a1_write_buffer`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + REQUIRE(a1_read_buffer_size == a1_write_buffer_size / 4); + REQUIRE(a1_read_buffer_validity_size == a1_write_buffer_validity_size / 4); + for (int i = 0; i < 4; ++i) { + const uint64_t idx = a1_read_buffer[i]; + expected_a1_read_buffer_validity[i] = a1_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a1_read_buffer_validity, + expected_a1_read_buffer_validity, + a1_read_buffer_validity_size)); + + // Each value in `a2_read_buffer` corresponds to its reversed index in + // the original `a2_write_buffer`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + if (test_attrs.size() >= 2) { + REQUIRE(a2_read_buffer_size == a2_write_buffer_size / 4); + REQUIRE(a2_read_buffer_validity_size == a2_write_buffer_validity_size / 4); + uint8_t expected_a2_read_buffer_validity[4]; + for (int i = 0; i < 4; ++i) { + const uint64_t idx = a2_read_buffer[4 - 1 - i]; + expected_a2_read_buffer_validity[i] = a2_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a2_read_buffer_validity, + expected_a2_read_buffer_validity, + a2_read_buffer_validity_size)); + } + + // Each value in `a3_read_buffer_var` corresponds to its index in + // the original `a3_write_buffer_var`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + if (test_attrs.size() >= 3) { + REQUIRE(a3_read_buffer_size == a3_write_buffer_size / 4); + REQUIRE(a3_read_buffer_var_size == a3_write_buffer_var_size / 4); + REQUIRE(a3_read_buffer_validity_size == a3_write_buffer_validity_size / 4); + uint8_t expected_a3_read_buffer_validity[32]; + for (int i = 0; i < 8; ++i) { + const uint64_t idx = a3_read_buffer_var[i]; + expected_a3_read_buffer_validity[i] = a3_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a3_read_buffer_validity, + expected_a3_read_buffer_validity, + a3_read_buffer_validity_size)); + } +} + +TEST_CASE_METHOD( + NullableArrayFx, + "C API: Test 2D array with nullable attributes", + "[capi][2d][nullable]") { + vector attrs; + attrs.emplace_back("a1", TILEDB_INT32, 1, true); + attrs.emplace_back("a2", TILEDB_INT32, 1, true); + attrs.emplace_back("a3", TILEDB_INT32, TILEDB_VAR_NUM, true); + + for (auto attr_iter = attrs.begin(); attr_iter != attrs.end(); ++attr_iter) { + vector test_attrs(attrs.begin(), attr_iter + 1); + for (const tiledb_array_type_t array_type : {TILEDB_DENSE, TILEDB_SPARSE}) { + for (const tiledb_layout_t cell_order : + {TILEDB_ROW_MAJOR, TILEDB_COL_MAJOR}) { + for (const tiledb_layout_t tile_order : + {TILEDB_ROW_MAJOR, TILEDB_COL_MAJOR}) { + for (const tiledb_layout_t write_order : {TILEDB_ROW_MAJOR, + TILEDB_COL_MAJOR, + TILEDB_UNORDERED, + TILEDB_GLOBAL_ORDER}) { + do_2d_nullable_test( + test_attrs, array_type, cell_order, tile_order, write_order); + } + } + } + } + } +} diff --git a/test/src/unit-cppapi-fill_values.cc b/test/src/unit-cppapi-fill_values.cc index 7d8d0c56c8d..a625bd0fb25 100644 --- a/test/src/unit-cppapi-fill_values.cc +++ b/test/src/unit-cppapi-fill_values.cc @@ -247,8 +247,9 @@ TEST_CASE( // Check dump std::string dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: 1\n" + - "- Filters: 0\n" + "- Fill value: -2147483648\n"; + "- Type: INT32\n" + "- Nullable: false\n" + + "- Cell val num: 1\n" + "- Filters: 0\n" + + "- Fill value: -2147483648\n"; check_dump(a, dump); // Correct setter @@ -261,8 +262,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: 1\n" + "- Filters: 0\n" + - "- Fill value: 5\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: 1\n" + + "- Filters: 0\n" + "- Fill value: 5\n"; check_dump(a, dump); // Setting the cell val num, also sets the fill value to a new default @@ -274,8 +275,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: 2\n" + "- Filters: 0\n" + - "- Fill value: -2147483648, -2147483648\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: 2\n" + + "- Filters: 0\n" + "- Fill value: -2147483648, -2147483648\n"; check_dump(a, dump); // Set a fill value that is comprised of two integers @@ -290,8 +291,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: 2\n" + "- Filters: 0\n" + - "- Fill value: 1, 2\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: 2\n" + + "- Filters: 0\n" + "- Fill value: 1, 2\n"; check_dump(a, dump); // Make the attribute var-sized @@ -299,8 +300,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: var\n" + "- Filters: 0\n" + - "- Fill value: -2147483648\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: var\n" + + "- Filters: 0\n" + "- Fill value: -2147483648\n"; check_dump(a, dump); // Get the default var-sized fill value @@ -321,8 +322,8 @@ TEST_CASE( // Check dump dump = std::string("### Attribute ###\n") + "- Name: a\n" + - "- Type: INT32\n" + "- Cell val num: var\n" + "- Filters: 0\n" + - "- Fill value: 1, 2, 3\n"; + "- Type: INT32\n" + "- Nullable: false\n" + "- Cell val num: var\n" + + "- Filters: 0\n" + "- Fill value: 1, 2, 3\n"; check_dump(a, dump); } @@ -504,4 +505,4 @@ TEST_CASE( } CHECK_NOTHROW(vfs.remove_dir(array_name)); -} \ No newline at end of file +} diff --git a/test/src/unit-cppapi-nullable.cc b/test/src/unit-cppapi-nullable.cc new file mode 100644 index 00000000000..8ca65feddd5 --- /dev/null +++ b/test/src/unit-cppapi-nullable.cc @@ -0,0 +1,563 @@ +/** + * @file unit-cppapi-nullable.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2020 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 + * + * Tests arrays with nullable attributes. + */ + +#include "catch.hpp" +#include "test/src/helpers.h" +#include "tiledb/sm/cpp_api/tiledb" +#include "tiledb/sm/misc/utils.h" + +#include +#include + +using namespace std; +using namespace tiledb; +using namespace tiledb::test; + +template +struct test_dim_t { + test_dim_t( + const string& name, const array& domain, const uint64_t tile_extent) + : name_(name) + , domain_(domain) + , tile_extent_(tile_extent) { + } + + string name_; + array domain_; + uint64_t tile_extent_; +}; + +template +struct test_attr_t { + test_attr_t(const string& name, const bool var, const bool nullable) + : name_(name) + , var_(var) + , nullable_(nullable) { + } + + string name_; + bool var_; + bool nullable_; +}; + +template +struct test_query_buffer_t { + test_query_buffer_t(const string& name, vector* const data) + : name_(name) + , offsets_(nullptr) + , data_(data) + , validity_bytemap_(nullptr) { + } + + test_query_buffer_t( + const string& name, + vector* const data, + vector* const validity_bytemap) + : name_(name) + , offsets_(nullptr) + , data_(data) + , validity_bytemap_(std::move(validity_bytemap)) { + } + + test_query_buffer_t( + const string& name, + vector* const offsets, + vector* const data, + vector* const validity_bytemap) + : name_(name) + , offsets_(offsets) + , data_(data) + , validity_bytemap_(validity_bytemap) { + } + + string name_; + vector* offsets_; + vector* data_; + vector* validity_bytemap_; +}; + +class NullableArrayCppFx { + public: + /** Constructor. */ + NullableArrayCppFx(); + + /** Destructor. */ + ~NullableArrayCppFx(); + + /** + * Creates, writes, and reads nullable attributes. + * + * @param test_attrs The nullable attributes to test. + * @param array_type The type of the array (dense/sparse). + * @param cell_order The cell order of the array. + * @param tile_order The tile order of the array. + * @param write_order The write layout. + */ + template + void do_2d_nullable_test( + const vector>& test_attrs, + tiledb_array_type_t array_type, + tiledb_layout_t cell_order, + tiledb_layout_t tile_order, + tiledb_layout_t write_order); + + private: + /** The C++ API context object. */ + Context ctx_; + + /** The C++ API VFS object. */ + VFS vfs_; + + /** + * Removes a directory using `vfs_`. + * + * @param path The directory path. + */ + void remove_dir(const string& path); + + /** + * Creates a TileDB array. + * + * @param array_name The name of the array. + * @param array_type The type of the array (dense/sparse). + * @param test_dims The dimensions in the array. + * @param test_attrs The attributes in the array. + * @param cell_order The cell order of the array. + * @param tile_order The tile order of the array. + */ + template + void create_array( + const string& array_name, + tiledb_array_type_t array_type, + const vector>& test_dims, + const vector>& test_attrs, + tiledb_layout_t cell_order, + tiledb_layout_t tile_order); + + /** + * Creates and executes a single write query. + * + * @param array_name The name of the array. + * @param test_query_buffers The query buffers to write. + * @param layout The write layout. + */ + template + void write( + const string& array_name, + const vector>& test_query_buffers, + tiledb_layout_t layout); + + /** + * Creates and executes a single read query. + * + * @param array_name The name of the array. + * @param test_query_buffers The query buffers to read. + * @param subarray The subarray to read. + */ + template + void read( + const string& array_name, + const vector>& test_query_buffers, + const vector& subarray); +}; + +NullableArrayCppFx::NullableArrayCppFx() + : vfs_(ctx_) { +} + +NullableArrayCppFx::~NullableArrayCppFx() { +} + +void NullableArrayCppFx::remove_dir(const string& path) { + if (vfs_.is_dir(path)) + vfs_.remove_dir(path); +} + +template +void NullableArrayCppFx::create_array( + const string& array_name, + const tiledb_array_type_t array_type, + const vector>& test_dims, + const vector>& test_attrs, + const tiledb_layout_t cell_order, + const tiledb_layout_t tile_order) { + remove_dir(array_name); + + // Create the domain. + Domain domain(ctx_); + + // Create the dimensions. + for (const auto& test_dim : test_dims) { + domain.add_dimension(Dimension::create( + ctx_, test_dim.name_, test_dim.domain_, test_dim.tile_extent_)); + } + + // Create the array schema. + ArraySchema schema(ctx_, array_type); + schema.set_domain(domain); + schema.set_cell_order(cell_order); + schema.set_tile_order(tile_order); + + // Create the attributes. + for (const auto& test_attr : test_attrs) { + Attribute attr = + test_attr.var_ ? + Attribute::create>(ctx_, test_attr.name_) : + Attribute::create(ctx_, test_attr.name_); + attr.set_nullable(test_attr.nullable_); + schema.add_attribute(attr); + } + + // Check the array schema. + schema.check(); + + // Create the array. + Array::create(array_name, schema); +} + +template +void NullableArrayCppFx::write( + const string& array_name, + const vector>& test_query_buffers, + const tiledb_layout_t layout) { + // Open the array for writing. + Array array(ctx_, array_name, TILEDB_WRITE); + REQUIRE(array.is_open()); + + // Create the write query. + Query query(ctx_, array, TILEDB_WRITE); + + // Set the query layout. + query.set_layout(layout); + + // Set the query buffers. + for (const auto& test_query_buffer : test_query_buffers) { + if (!test_query_buffer.validity_bytemap_) { + if (!test_query_buffer.offsets_) { + query.set_buffer(test_query_buffer.name_, *test_query_buffer.data_); + } else { + query.set_buffer( + test_query_buffer.name_, + *test_query_buffer.offsets_, + *test_query_buffer.data_); + } + } else { + if (!test_query_buffer.offsets_) { + query.set_buffer_nullable( + test_query_buffer.name_, + *test_query_buffer.data_, + *test_query_buffer.validity_bytemap_); + } else { + query.set_buffer_nullable( + test_query_buffer.name_, + *test_query_buffer.offsets_, + *test_query_buffer.data_, + *test_query_buffer.validity_bytemap_); + } + } + } + + // Submit the query. + REQUIRE(query.submit() == Query::Status::COMPLETE); + + // Finalize the query, a no-op for non-global writes. + query.finalize(); + + // Clean up + array.close(); +} + +template +void NullableArrayCppFx::read( + const string& array_name, + const vector>& test_query_buffers, + const vector& subarray) { + // Open the array for reading. + Array array(ctx_, array_name, TILEDB_READ); + REQUIRE(array.is_open()); + + // Create the read query. + Query query(ctx_, array, TILEDB_READ); + + // Set the query buffers. + for (const auto& test_query_buffer : test_query_buffers) { + if (!test_query_buffer.validity_bytemap_) { + if (!test_query_buffer.offsets_) { + query.set_buffer(test_query_buffer.name_, *test_query_buffer.data_); + } else { + query.set_buffer( + test_query_buffer.name_, + *test_query_buffer.offsets_, + *test_query_buffer.data_); + } + } else { + if (!test_query_buffer.offsets_) { + query.set_buffer_nullable( + test_query_buffer.name_, + *test_query_buffer.data_, + *test_query_buffer.validity_bytemap_); + } else { + query.set_buffer_nullable( + test_query_buffer.name_, + *test_query_buffer.offsets_, + *test_query_buffer.data_, + *test_query_buffer.validity_bytemap_); + } + } + } + + // Set the subarray to read. + query.set_subarray(subarray); + + // Submit the query. + REQUIRE(query.submit() == Query::Status::COMPLETE); + + // Finalize the query, a no-op for non-global reads. + query.finalize(); + + // Clean up + array.close(); +} + +template +void NullableArrayCppFx::do_2d_nullable_test( + const vector>& test_attrs, + const tiledb_array_type_t array_type, + const tiledb_layout_t cell_order, + const tiledb_layout_t tile_order, + const tiledb_layout_t write_order) { + const string array_name = "cpp_2d_nullable_array"; + + // Skip row-major and col-major writes for sparse arrays. + if (array_type == TILEDB_SPARSE && + (write_order == TILEDB_ROW_MAJOR || write_order == TILEDB_COL_MAJOR)) { + return; + } + + // Define the dimensions. + vector> test_dims; + const array d1_domain = {1, 4}; + const uint64_t d1_tile_extent = 2; + test_dims.emplace_back("d1", d1_domain, d1_tile_extent); + const array d2_domain = {1, 4}; + const uint64_t d2_tile_extent = 2; + test_dims.emplace_back("d2", d2_domain, d2_tile_extent); + + // Create the array. + create_array( + array_name, array_type, test_dims, test_attrs, cell_order, tile_order); + + // Define the write query buffers for "a1". + vector> write_query_buffers; + vector a1_write_buffer; + for (int i = 0; i < 16; ++i) + a1_write_buffer.emplace_back(i); + vector a1_write_buffer_validity; + for (int i = 0; i < 16; ++i) + a1_write_buffer_validity.emplace_back(rand() % 2); + write_query_buffers.emplace_back( + "a1", &a1_write_buffer, &a1_write_buffer_validity); + + // Define the write query buffers for "a2". + vector a2_write_buffer(a1_write_buffer.size()); + for (size_t i = 0; i < a1_write_buffer.size(); ++i) + a2_write_buffer[i] = a1_write_buffer[a1_write_buffer.size() - 1 - i]; + vector a2_write_buffer_validity(a1_write_buffer_validity.size()); + for (size_t i = 0; i < a1_write_buffer_validity.size(); ++i) + a2_write_buffer_validity[i] = + a1_write_buffer_validity[a1_write_buffer_validity.size() - 1 - i]; + if (test_attrs.size() >= 2) { + write_query_buffers.emplace_back( + "a2", &a2_write_buffer, &a2_write_buffer_validity); + } + + // Define the write query buffers for "a3". + vector a3_write_buffer; + for (uint64_t i = 0; i < 16; ++i) + a3_write_buffer.emplace_back(i * sizeof(int) * 2); + vector a3_write_buffer_var; + for (int i = 0; i < 32; ++i) + a3_write_buffer_var.emplace_back(i); + vector a3_write_buffer_validity; + for (int i = 0; i < 32; ++i) + a3_write_buffer_validity.emplace_back(rand() % 2); + if (test_attrs.size() >= 3) { + write_query_buffers.emplace_back( + "a3", + &a3_write_buffer, + &a3_write_buffer_var, + &a3_write_buffer_validity); + } + + // Define dimension query buffers for either sparse arrays or dense arrays + // with an unordered write order. + vector d1_write_buffer; + vector d2_write_buffer; + if (array_type == TILEDB_SPARSE || write_order == TILEDB_UNORDERED) { + // Coordinates for sparse arrays written in global order have unique + // ordering when either/both cell and tile ordering is col-major. + if (array_type == TILEDB_SPARSE && write_order == TILEDB_GLOBAL_ORDER && + (cell_order == TILEDB_COL_MAJOR || tile_order == TILEDB_COL_MAJOR)) { + if (cell_order == TILEDB_ROW_MAJOR && tile_order == TILEDB_COL_MAJOR) { + d1_write_buffer = {1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 2, 2, 3, 3, 4, 4}; + d2_write_buffer = {1, 2, 1, 2, 1, 2, 1, 2, 3, 4, 3, 4, 3, 4, 3, 4}; + } else if ( + cell_order == TILEDB_COL_MAJOR && tile_order == TILEDB_ROW_MAJOR) { + d1_write_buffer = {1, 2, 1, 2, 1, 2, 1, 2, 3, 4, 3, 4, 3, 4, 3, 4}; + d2_write_buffer = {1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 2, 2, 3, 3, 4, 4}; + } else { + REQUIRE(cell_order == TILEDB_COL_MAJOR); + REQUIRE(tile_order == TILEDB_COL_MAJOR); + d1_write_buffer = {1, 2, 1, 2, 3, 4, 3, 4, 1, 2, 1, 2, 3, 4, 3, 4}; + d2_write_buffer = {1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4}; + } + } else { + d1_write_buffer = {1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4}; + d2_write_buffer = {1, 2, 1, 2, 3, 4, 3, 4, 1, 2, 1, 2, 3, 4, 3, 4}; + } + + write_query_buffers.emplace_back("d1", &d1_write_buffer); + write_query_buffers.emplace_back("d2", &d2_write_buffer); + } + + // Execute the write query. + write(array_name, write_query_buffers, write_order); + + // Define the read query buffers for "a1". + vector> read_query_buffers; + vector a1_read_buffer(16); + vector a1_read_buffer_validity(16); + read_query_buffers.emplace_back( + "a1", &a1_read_buffer, &a1_read_buffer_validity); + + // Define the read query buffers for "a2". + vector a2_read_buffer(16); + vector a2_read_buffer_validity(16); + if (test_attrs.size() >= 2) { + read_query_buffers.emplace_back( + "a2", &a2_read_buffer, &a2_read_buffer_validity); + } + + // Define the read query buffers for "a3". + vector a3_read_buffer(16); + vector a3_read_buffer_var(32); + vector a3_read_buffer_validity(32); + if (test_attrs.size() >= 3) { + read_query_buffers.emplace_back( + "a3", &a3_read_buffer, &a3_read_buffer_var, &a3_read_buffer_validity); + } + + // Execute a read query over the entire domain. + const vector subarray_full = {1, 4, 1, 4}; + read(array_name, read_query_buffers, subarray_full); + + // Each value in `a1_read_buffer` corresponds to its index in + // the original `a1_write_buffer`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + REQUIRE(a1_read_buffer.size() == a1_write_buffer.size()); + REQUIRE(a1_read_buffer_validity.size() == a1_write_buffer_validity.size()); + vector expected_a1_read_buffer_validity(16); + for (int i = 0; i < 16; ++i) { + const uint64_t idx = a1_read_buffer[i]; + expected_a1_read_buffer_validity[i] = a1_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a1_read_buffer_validity.data(), + expected_a1_read_buffer_validity.data(), + a1_read_buffer_validity.size())); + + // Each value in `a2_read_buffer` corresponds to its reversed index in + // the original `a2_write_buffer`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + if (test_attrs.size() >= 2) { + REQUIRE(a2_read_buffer.size() == a2_write_buffer.size()); + REQUIRE(a2_read_buffer_validity.size() == a2_write_buffer_validity.size()); + vector expected_a2_read_buffer_validity(16); + for (int i = 0; i < 16; ++i) { + const uint64_t idx = a2_read_buffer[16 - 1 - i]; + expected_a2_read_buffer_validity[i] = a2_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a2_read_buffer_validity.data(), + expected_a2_read_buffer_validity.data(), + a2_read_buffer_validity.size())); + } + + // Each value in `a3_read_buffer_var` corresponds to its index in + // the original `a3_write_buffer_var`. Check that the ordering of + // the validity buffer matches the ordering in the value buffer. + if (test_attrs.size() >= 3) { + REQUIRE(a3_read_buffer.size() == a3_write_buffer.size()); + REQUIRE(a3_read_buffer_var.size() == a3_write_buffer_var.size()); + REQUIRE(a3_read_buffer_validity.size() == a3_write_buffer_validity.size()); + vector expected_a3_read_buffer_validity(32); + for (int i = 0; i < 32; ++i) { + const uint64_t idx = a3_read_buffer_var[i]; + expected_a3_read_buffer_validity[i] = a3_write_buffer_validity[idx]; + } + REQUIRE(!memcmp( + a3_read_buffer_validity.data(), + expected_a3_read_buffer_validity.data(), + a3_read_buffer_validity.size())); + } + + remove_dir(array_name); +} + +TEST_CASE_METHOD( + NullableArrayCppFx, + "C++ API: Test 2D array with nullable attributes", + "[cppapi][2d][nullable]") { + vector> attrs; + attrs.emplace_back("a1", false /* var */, true /* nullable */); + attrs.emplace_back("a2", false /* var */, true /* nullable */); + attrs.emplace_back("a3", true /* var */, true /* nullable */); + + for (auto attr_iter = attrs.begin(); attr_iter != attrs.end(); ++attr_iter) { + vector> test_attrs(attrs.begin(), attr_iter + 1); + for (const tiledb_array_type_t array_type : {TILEDB_DENSE, TILEDB_SPARSE}) { + for (const tiledb_layout_t cell_order : + {TILEDB_ROW_MAJOR, TILEDB_COL_MAJOR}) { + for (const tiledb_layout_t tile_order : + {TILEDB_ROW_MAJOR, TILEDB_COL_MAJOR}) { + for (const tiledb_layout_t write_order : {TILEDB_ROW_MAJOR, + TILEDB_COL_MAJOR, + TILEDB_UNORDERED, + TILEDB_GLOBAL_ORDER}) { + do_2d_nullable_test( + test_attrs, array_type, cell_order, tile_order, write_order); + } + } + } + } + } +} \ No newline at end of file diff --git a/tiledb/common/status.cc b/tiledb/common/status.cc index b8253cc87b0..e1ebd6701f2 100644 --- a/tiledb/common/status.cc +++ b/tiledb/common/status.cc @@ -135,6 +135,9 @@ std::string Status::code_to_string() const { case StatusCode::Query: type = "[TileDB::Query] Error"; break; + case StatusCode::ValidityVector: + type = "[TileDB::ValidityVector] Error"; + break; case StatusCode::VFS: type = "[TileDB::VFS] Error"; break; diff --git a/tiledb/common/status.h b/tiledb/common/status.h index 2731fb46408..5bc2ceaf561 100644 --- a/tiledb/common/status.h +++ b/tiledb/common/status.h @@ -91,6 +91,7 @@ enum class StatusCode : char { ChunkedBuffer, Buffer, Query, + ValidityVector, VFS, ConstBuffer, Dimension, @@ -234,6 +235,11 @@ class Status { return Status(StatusCode::Query, msg, -1); } + /** Return a ValidityVectorError error class Status with a given message **/ + static Status ValidityVectorError(const std::string& msg) { + return Status(StatusCode::ValidityVector, msg, -1); + } + /** Return a VFSError error class Status with a given message **/ static Status VFSError(const std::string& msg) { return Status(StatusCode::VFS, msg, -1); diff --git a/tiledb/sm/array_schema/array_schema.cc b/tiledb/sm/array_schema/array_schema.cc index 3ea0d89d628..90b28c80718 100644 --- a/tiledb/sm/array_schema/array_schema.cc +++ b/tiledb/sm/array_schema/array_schema.cc @@ -73,12 +73,15 @@ ArraySchema::ArraySchema(ArrayType array_type) tile_order_ = Layout::ROW_MAJOR; version_ = constants::format_version; - // Set up default filter pipelines for coords and offsets + // Set up default filter pipelines for coords, offsets, and validity values. coords_filters_.add_filter(CompressionFilter( constants::coords_compression, constants::coords_compression_level)); cell_var_offsets_filters_.add_filter(CompressionFilter( constants::cell_var_offsets_compression, constants::cell_var_offsets_compression_level)); + cell_validity_filters_.add_filter(CompressionFilter( + constants::cell_validity_compression, + constants::cell_validity_compression_level)); } ArraySchema::ArraySchema(const ArraySchema* array_schema) { @@ -90,6 +93,7 @@ ArraySchema::ArraySchema(const ArraySchema* array_schema) { capacity_ = array_schema->capacity_; cell_order_ = array_schema->cell_order_; cell_var_offsets_filters_ = array_schema->cell_var_offsets_filters_; + cell_validity_filters_ = array_schema->cell_validity_filters_; coords_filters_ = array_schema->coords_filters_; tile_order_ = array_schema->tile_order_; version_ = array_schema->version_; @@ -197,6 +201,10 @@ const FilterPipeline& ArraySchema::cell_var_offsets_filters() const { return cell_var_offsets_filters_; } +const FilterPipeline& ArraySchema::cell_validity_filters() const { + return cell_validity_filters_; +} + Status ArraySchema::check() const { if (domain_ == nullptr) return LOG_STATUS( @@ -302,6 +310,9 @@ void ArraySchema::dump(FILE* out) const { "\n- Offsets filters: %u", (unsigned)cell_var_offsets_filters_.size()); cell_var_offsets_filters_.dump(out); + fprintf( + out, "\n- Validity filters: %u", (unsigned)cell_validity_filters_.size()); + cell_validity_filters_.dump(out); fprintf(out, "\n"); if (domain_ != nullptr) @@ -335,6 +346,13 @@ bool ArraySchema::is_dim(const std::string& name) const { return this->dimension(name) != nullptr; } +bool ArraySchema::is_nullable(const std::string& name) const { + const Attribute* const attr = this->attribute(name); + if (attr == nullptr) + return false; + return attr->nullable(); +} + // ===== FORMAT ===== // version (uint32_t) // allow_dups (bool) @@ -344,6 +362,7 @@ bool ArraySchema::is_dim(const std::string& name) const { // capacity (uint64_t) // coords_filters (see FilterPipeline::serialize) // cell_var_offsets_filters (see FilterPipeline::serialize) +// cell_validity_filters (see FilterPipeline::serialize) // domain // attribute_num (uint32_t) // attribute #1 @@ -378,6 +397,9 @@ Status ArraySchema::serialize(Buffer* buff) const { // Write offsets filters RETURN_NOT_OK(cell_var_offsets_filters_.serialize(buff)); + // Write validity filters + RETURN_NOT_OK(cell_validity_filters_.serialize(buff)); + // Write domain RETURN_NOT_OK(domain_->serialize(buff, version)); @@ -483,6 +505,10 @@ Status ArraySchema::deserialize(ConstBuffer* buff) { // Load offsets filters RETURN_NOT_OK(cell_var_offsets_filters_.deserialize(buff)); + // Load validity filters + if (version_ >= 7) + RETURN_NOT_OK(cell_validity_filters_.deserialize(buff)); + // Load domain domain_ = new Domain(); RETURN_NOT_OK(domain_->deserialize(buff, version_)); @@ -564,6 +590,12 @@ Status ArraySchema::set_cell_order(Layout cell_order) { return Status::Ok(); } +Status ArraySchema::set_cell_validity_filter_pipeline( + const FilterPipeline* pipeline) { + cell_validity_filters_ = *pipeline; + return Status::Ok(); +} + Status ArraySchema::set_domain(Domain* domain) { if (domain == nullptr) return LOG_STATUS( diff --git a/tiledb/sm/array_schema/array_schema.h b/tiledb/sm/array_schema/array_schema.h index 7fd4e17409e..bae643cc058 100644 --- a/tiledb/sm/array_schema/array_schema.h +++ b/tiledb/sm/array_schema/array_schema.h @@ -128,9 +128,12 @@ class ArraySchema { /** Returns the number of values per cell of the input attribute/dimension. */ unsigned int cell_val_num(const std::string& name) const; - /** Return the fileter pipeline used for offsets of variable-sized cells. */ + /** Return the filter pipeline used for offsets of variable-sized cells. */ const FilterPipeline& cell_var_offsets_filters() const; + /** Return the filter pipeline used for validity cells. */ + const FilterPipeline& cell_validity_filters() const; + /** * Checks the correctness of the array schema. * @@ -190,6 +193,9 @@ class ArraySchema { /** Returns true if the input name is a dimension. */ bool is_dim(const std::string& name) const; + /** Returns true if the input name is nullable. */ + bool is_nullable(const std::string& name) const; + /** * Serializes the array schema object into a buffer. * @@ -257,6 +263,9 @@ class ArraySchema { /** Sets the filter pipeline for the variable cell offsets. */ Status set_cell_var_offsets_filter_pipeline(const FilterPipeline* pipeline); + /** Sets the filter pipeline for the validity cell offsets. */ + Status set_cell_validity_filter_pipeline(const FilterPipeline* pipeline); + /** Sets the filter pipeline for the coordinates. */ Status set_coords_filter_pipeline(const FilterPipeline* pipeline); @@ -318,6 +327,9 @@ class ArraySchema { /** The filter pipeline run on offset tiles for var-length attributes. */ FilterPipeline cell_var_offsets_filters_; + /** The filter pipeline run on validity tiles for nullable attributes. */ + FilterPipeline cell_validity_filters_; + /** The filter pipeline run on coordinate tiles. */ FilterPipeline coords_filters_; diff --git a/tiledb/sm/array_schema/attribute.cc b/tiledb/sm/array_schema/attribute.cc index cd8f881d896..f7e51d1d770 100644 --- a/tiledb/sm/array_schema/attribute.cc +++ b/tiledb/sm/array_schema/attribute.cc @@ -53,12 +53,14 @@ namespace sm { /* ********************************* */ Attribute::Attribute() - : Attribute("", Datatype::CHAR) { + : Attribute("", Datatype::CHAR, false) { } -Attribute::Attribute(const std::string& name, Datatype type) { +Attribute::Attribute( + const std::string& name, const Datatype type, const bool nullable) { name_ = name; type_ = type; + nullable_ = nullable; cell_val_num_ = (type == Datatype::ANY) ? constants::var_num : 1; set_default_fill_value(); } @@ -68,6 +70,7 @@ Attribute::Attribute(const Attribute* attr) { name_ = attr->name(); type_ = attr->type(); cell_val_num_ = attr->cell_val_num(); + nullable_ = attr->nullable(); filters_ = attr->filters_; fill_value_ = attr->fill_value_; } @@ -89,7 +92,7 @@ unsigned int Attribute::cell_val_num() const { return cell_val_num_; } -Status Attribute::deserialize(ConstBuffer* buff, uint32_t version) { +Status Attribute::deserialize(ConstBuffer* buff, const uint32_t version) { // Load attribute name uint32_t attribute_name_size; RETURN_NOT_OK(buff->read(&attribute_name_size, sizeof(uint32_t))); @@ -119,6 +122,10 @@ Status Attribute::deserialize(ConstBuffer* buff, uint32_t version) { set_default_fill_value(); } + // Load nullable flag + if (version >= 7) + RETURN_NOT_OK(buff->read(&nullable_, sizeof(bool))); + return Status::Ok(); } @@ -129,6 +136,7 @@ void Attribute::dump(FILE* out) const { fprintf(out, "### Attribute ###\n"); fprintf(out, "- Name: %s\n", name_.c_str()); fprintf(out, "- Type: %s\n", datatype_str(type_).c_str()); + fprintf(out, "- Nullable: %s\n", (nullable_ ? "true" : "false")); if (!var_size()) fprintf(out, "- Cell val num: %u\n", cell_val_num_); else @@ -154,7 +162,8 @@ const std::string& Attribute::name() const { // type (uint8_t) // cell_val_num (uint32_t) // filter_pipeline (see FilterPipeline::serialize) -Status Attribute::serialize(Buffer* buff, uint32_t version) { +// nullable (bool) +Status Attribute::serialize(Buffer* buff, const uint32_t version) { // Write attribute name auto attribute_name_size = (uint32_t)name_.size(); RETURN_NOT_OK(buff->write(&attribute_name_size, sizeof(uint32_t))); @@ -178,6 +187,10 @@ Status Attribute::serialize(Buffer* buff, uint32_t version) { RETURN_NOT_OK(buff->write(&fill_value_[0], fill_value_.size())); } + // Write nullable + if (version >= 7) + RETURN_NOT_OK(buff->write(&nullable_, sizeof(bool))); + return Status::Ok(); } @@ -193,6 +206,16 @@ Status Attribute::set_cell_val_num(unsigned int cell_val_num) { return Status::Ok(); } +Status Attribute::set_nullable(const bool nullable) { + nullable_ = nullable; + return Status::Ok(); +} + +Status Attribute::get_nullable(bool* const nullable) { + *nullable = nullable_; + return Status::Ok(); +} + Status Attribute::set_filter_pipeline(const FilterPipeline* pipeline) { if (pipeline == nullptr) return LOG_STATUS(Status::AttributeError( @@ -264,6 +287,10 @@ bool Attribute::var_size() const { return cell_val_num_ == constants::var_num; } +bool Attribute::nullable() const { + return nullable_; +} + /* ********************************* */ /* PRIVATE METHODS */ /* ********************************* */ diff --git a/tiledb/sm/array_schema/attribute.h b/tiledb/sm/array_schema/attribute.h index b8258a59201..462c4b9af01 100644 --- a/tiledb/sm/array_schema/attribute.h +++ b/tiledb/sm/array_schema/attribute.h @@ -67,7 +67,7 @@ class Attribute { * @note The default number of values per cell is 1 for all datatypes except * `ANY`, which is always variable-sized. */ - Attribute(const std::string& name, Datatype type); + Attribute(const std::string& name, Datatype type, bool nullable = false); /** * Constructor. It clones the input attribute. @@ -138,6 +138,21 @@ class Attribute { */ Status set_cell_val_num(unsigned int cell_val_num); + /** + * Sets the nullability for this attribute. + * + * @return Status + */ + Status set_nullable(bool nullable); + + /** + * Gets the nullability for this attribute. + * + * @param nullable Mutates to true or false. + * @return Status + */ + Status get_nullable(bool* nullable); + /** Sets the filter pipeline for this attribute. */ Status set_filter_pipeline(const FilterPipeline* pipeline); @@ -168,6 +183,12 @@ class Attribute { */ bool var_size() const; + /** + * Returns *true* if this is a nullable attribute, and *false* + * otherwise. + */ + bool nullable() const; + private: /* ********************************* */ /* PRIVATE ATTRIBUTES */ @@ -176,6 +197,9 @@ class Attribute { /** The attribute number of values per cell. */ unsigned cell_val_num_; + /** True if this attribute may be null. */ + bool nullable_; + /** The attribute filter pipeline. */ FilterPipeline filters_; diff --git a/tiledb/sm/array_schema/dimension.h b/tiledb/sm/array_schema/dimension.h index 5f526b2c9a7..af94044513e 100644 --- a/tiledb/sm/array_schema/dimension.h +++ b/tiledb/sm/array_schema/dimension.h @@ -43,6 +43,7 @@ #include "tiledb/common/status.h" #include "tiledb/sm/misc/types.h" #include "tiledb/sm/misc/utils.h" +#include "tiledb/sm/query/query_buffer.h" #include "tiledb/sm/query/result_coords.h" #include "tiledb/sm/tile/tile.h" diff --git a/tiledb/sm/array_schema/domain.h b/tiledb/sm/array_schema/domain.h index 86d2e9b5d74..7d0438c17b6 100644 --- a/tiledb/sm/array_schema/domain.h +++ b/tiledb/sm/array_schema/domain.h @@ -36,6 +36,7 @@ #include "tiledb/common/status.h" #include "tiledb/sm/misc/macros.h" #include "tiledb/sm/misc/types.h" +#include "tiledb/sm/query/query_buffer.h" #include "tiledb/sm/query/result_coords.h" #include diff --git a/tiledb/sm/c_api/tiledb.cc b/tiledb/sm/c_api/tiledb.cc index 008db8366f3..124a293698e 100644 --- a/tiledb/sm/c_api/tiledb.cc +++ b/tiledb/sm/c_api/tiledb.cc @@ -1472,6 +1472,18 @@ void tiledb_attribute_free(tiledb_attribute_t** attr) { } } +int32_t tiledb_attribute_set_nullable( + tiledb_ctx_t* ctx, tiledb_attribute_t* attr, uint8_t nullable) { + if (sanity_check(ctx) == TILEDB_ERR || sanity_check(ctx, attr) == TILEDB_ERR) + return TILEDB_ERR; + + if (SAVE_ERROR_CATCH( + ctx, attr->attr_->set_nullable(static_cast(nullable)))) + return TILEDB_ERR; + + return TILEDB_OK; +} + int32_t tiledb_attribute_set_filter_list( tiledb_ctx_t* ctx, tiledb_attribute_t* attr, @@ -1517,6 +1529,18 @@ int32_t tiledb_attribute_get_type( return TILEDB_OK; } +int32_t tiledb_attribute_get_nullable( + tiledb_ctx_t* ctx, tiledb_attribute_t* attr, uint8_t* nullable) { + if (sanity_check(ctx) == TILEDB_ERR || sanity_check(ctx, attr) == TILEDB_ERR) + return TILEDB_ERR; + + if (SAVE_ERROR_CATCH( + ctx, attr->attr_->get_nullable(reinterpret_cast(nullable)))) + return TILEDB_ERR; + + return TILEDB_OK; +} + int32_t tiledb_attribute_get_filter_list( tiledb_ctx_t* ctx, tiledb_attribute_t* attr, @@ -2618,6 +2642,70 @@ int32_t tiledb_query_set_buffer_var( return TILEDB_OK; } +int32_t tiledb_query_set_buffer_nullable( + tiledb_ctx_t* ctx, + tiledb_query_t* query, + const char* name, + void* buffer, + uint64_t* buffer_size, + uint8_t* buffer_validity_bytemap, + uint64_t* buffer_validity_bytemap_size) { + // Sanity check + if (sanity_check(ctx) == TILEDB_ERR || sanity_check(ctx, query) == TILEDB_ERR) + return TILEDB_ERR; + + // Set attribute buffer + if (SAVE_ERROR_CATCH( + ctx, + query->query_->set_buffer_vbytemap( + name, + buffer, + buffer_size, + buffer_validity_bytemap, + buffer_validity_bytemap_size))) + return TILEDB_ERR; + + return TILEDB_OK; +} + +int32_t tiledb_query_set_buffer_var_nullable( + tiledb_ctx_t* ctx, + tiledb_query_t* query, + const char* name, + uint64_t* buffer_off, + uint64_t* buffer_off_size, + void* buffer_val, + uint64_t* buffer_val_size, + uint8_t* buffer_validity_bytemap, + uint64_t* buffer_validity_bytemap_size) { + // Sanity check + if (sanity_check(ctx) == TILEDB_ERR || sanity_check(ctx, query) == TILEDB_ERR) + return TILEDB_ERR; + + // On writes, check the provided offsets for validity. + if (query->query_->type() == tiledb::sm::QueryType::WRITE && + save_error( + ctx, + tiledb::sm::Query::check_var_attr_offsets( + buffer_off, buffer_off_size, buffer_val_size))) + return TILEDB_ERR; + + // Set attribute buffers + if (SAVE_ERROR_CATCH( + ctx, + query->query_->set_buffer_vbytemap( + name, + buffer_off, + buffer_off_size, + buffer_val, + buffer_val_size, + buffer_validity_bytemap, + buffer_validity_bytemap_size))) + return TILEDB_ERR; + + return TILEDB_OK; +} + int32_t tiledb_query_get_buffer( tiledb_ctx_t* ctx, tiledb_query_t* query, @@ -2658,6 +2746,62 @@ int32_t tiledb_query_get_buffer_var( return TILEDB_OK; } +int32_t tiledb_query_get_buffer_nullable( + tiledb_ctx_t* ctx, + tiledb_query_t* query, + const char* name, + void** buffer, + uint64_t** buffer_size, + uint8_t** buffer_validity_bytemap, + uint64_t** buffer_validity_bytemap_size) { + // Sanity check + if (sanity_check(ctx) == TILEDB_ERR || sanity_check(ctx, query) == TILEDB_ERR) + return TILEDB_ERR; + + // Set attribute buffer + if (SAVE_ERROR_CATCH( + ctx, + query->query_->get_buffer_vbytemap( + name, + buffer, + buffer_size, + buffer_validity_bytemap, + buffer_validity_bytemap_size))) + return TILEDB_ERR; + + return TILEDB_OK; +} + +int32_t tiledb_query_get_buffer_var_nullable( + tiledb_ctx_t* ctx, + tiledb_query_t* query, + const char* name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + uint8_t** buffer_validity_bytemap, + uint64_t** buffer_validity_bytemap_size) { + // Sanity check + if (sanity_check(ctx) == TILEDB_ERR || sanity_check(ctx, query) == TILEDB_ERR) + return TILEDB_ERR; + + // Get attribute buffers + if (SAVE_ERROR_CATCH( + ctx, + query->query_->get_buffer_vbytemap( + name, + buffer_off, + buffer_off_size, + buffer_val, + buffer_val_size, + buffer_validity_bytemap, + buffer_validity_bytemap_size))) + return TILEDB_ERR; + + return TILEDB_OK; +} + int32_t tiledb_query_set_layout( tiledb_ctx_t* ctx, tiledb_query_t* query, tiledb_layout_t layout) { // Sanity check diff --git a/tiledb/sm/c_api/tiledb.h b/tiledb/sm/c_api/tiledb.h index 395c46921c8..aee5fa627c5 100644 --- a/tiledb/sm/c_api/tiledb.h +++ b/tiledb/sm/c_api/tiledb.h @@ -1911,6 +1911,23 @@ TILEDB_EXPORT int32_t tiledb_attribute_alloc( */ TILEDB_EXPORT void tiledb_attribute_free(tiledb_attribute_t** attr); +/** + * Sets the nullability of an attribute. + * + * **Example:** + * + * @code{.c} + * tiledb_attribute_set_nullable(ctx, attr, 1); + * @endcode + * + * @param ctx The TileDB context. + * @param attr The target attribute. + * @param nullable Non-zero if the attribute is nullable. + * @return `TILEDB_OK` for success and `TILEDB_ERR` for error. + */ +TILEDB_EXPORT int32_t tiledb_attribute_set_nullable( + tiledb_ctx_t* ctx, tiledb_attribute_t* attr, uint8_t nullable); + /** * Sets the filter list for an attribute. * @@ -1995,6 +2012,25 @@ TILEDB_EXPORT int32_t tiledb_attribute_get_name( TILEDB_EXPORT int32_t tiledb_attribute_get_type( tiledb_ctx_t* ctx, const tiledb_attribute_t* attr, tiledb_datatype_t* type); +/** + * Sets the nullability of an attribute. + * + * **Example:** + * + * @code{.c} + * uint8_t nullable; + * tiledb_attribute_get_nullable(ctx, attr, &nullable); + * @endcode + * + * @param ctx The TileDB context. + * @param attr The target attribute. + * @param nullable Output argument, non-zero for nullable and zero + * for non-nullable. + * @return `TILEDB_OK` for success and `TILEDB_ERR` for error. + */ +TILEDB_EXPORT int32_t tiledb_attribute_get_nullable( + tiledb_ctx_t* ctx, tiledb_attribute_t* attr, uint8_t* nullable); + /** * Retrieves the filter list for an attribute. * @@ -3269,7 +3305,109 @@ TILEDB_EXPORT int32_t tiledb_query_set_buffer_var( uint64_t* buffer_val_size); /** - * Gets the buffer of a fixed-sized attribute from a query. If the + * Sets the buffer for a fixed-sized, nullable attribute to a query, which will + * either hold the values to be written (if it is a write query), or will hold + * the results from a read query. The validity buffer is a byte map, where each + * non-zero byte represents a valid (i.e. "non-null") attribute value. + * + * **Example:** + * + * @code{.c} + * int32_t a1[100]; + * uint64_t a1_size = sizeof(a1); + * uint8_t a1_validity[100]; + * uint64_t a1_validity_size = sizeof(a1_validity); + * tiledb_query_set_buffer_nullable( + * ctx, query, "a1", a1, &a1_size, a1_validity, &a1_validity_size); + * @endcode + * + * @param ctx The TileDB context. + * @param query The TileDB query. + * @param name The attribute/dimension to set the buffer for. Note that + * zipped coordinates have special name `TILEDB_COORDS`. + * @param buffer The buffer that either have the input data to be written, + * or will hold the data to be read. + * @param buffer_size In the case of writes, this is the size of `buffer` + * in bytes. In the case of reads, this initially contains the allocated + * size of `buffer`, but after the termination of the query + * it will contain the size of the useful (read) data in `buffer`. + * @param buffer_validity_bytemap The validity byte map that has exactly + * one value for each value in `buffer`. + * @param buffer_validity_bytemap_size In the case of writes, this is the + * size of `buffer_validity_bytemap` in bytes. In the case of reads, + * this initially contains the allocated size of `buffer_validity_bytemap`, + * but after the termination of the query it will contain the size of the + * useful (read) data in `buffer_validity_bytemap`. + * @return `TILEDB_OK` for success and `TILEDB_ERR` for error. + */ +TILEDB_EXPORT int32_t tiledb_query_set_buffer_nullable( + tiledb_ctx_t* ctx, + tiledb_query_t* query, + const char* name, + void* buffer, + uint64_t* buffer_size, + uint8_t* buffer_validity_bytemap, + uint64_t* buffer_validity_bytemap_size); + +/** + * Sets the buffer for a var-sized, nullable attribute to a query, which will + * either hold the values to be written (if it is a write query), or will hold + * the results from a read query. + * + * **Example:** + * + * @code{.c} + * uint64_t a2_off[10]; + * uint64_t a2_off_size = sizeof(a2_off); + * char a2_val[100]; + * uint64_t a2_val_size = sizeof(a2_val); + * uint8_t a2_validity[100]; + * uint64_t a2_validity_size = sizeof(a2_validity); + * tiledb_query_set_buffer_var( + * ctx, query, "a2", a2_off, &a2_off_size, a2_val, &a2_val_size, + * a2_validity, &a2_validity_size); + * @endcode + * + * @param ctx The TileDB context. + * @param query The TileDB query. + * @param name The attribute/dimension to set the buffer for. + * @param buffer_off The buffer that either have the input data to be written, + * or will hold the data to be read. This buffer holds the starting offsets + * of each cell value in `buffer_val`. + * @param buffer_off_size In the case of writes, it is the size of `buffer_off` + * in bytes. In the case of reads, this initially contains the allocated + * size of `buffer_off`, but after the *end of the query* + * (`tiledb_query_submit`) it will contain the size of the useful (read) + * data in `buffer_off`. + * @param buffer_val The buffer that either have the input data to be written, + * or will hold the data to be read. This buffer holds the actual var-sized + * cell values. + * @param buffer_val_size In the case of writes, it is the size of `buffer_val` + * in bytes. In the case of reads, this initially contains the allocated + * size of `buffer_val`, but after the termination of the function + * it will contain the size of the useful (read) data in `buffer_val`. + * @param buffer_validity_bytemap The validity byte map that has exactly + * one value for each value in `buffer`. + * @param buffer_validity_bytemap_size In the case of writes, this is the + * size of `buffer_validity_bytemap` in bytes. In the case of reads, + * this initially contains the allocated size of `buffer_validity_bytemap`, + * but after the termination of the query it will contain the size of the + * useful (read) data in `buffer_validity_bytemap`. + * @return `TILEDB_OK` for success and `TILEDB_ERR` for error. + */ +TILEDB_EXPORT int32_t tiledb_query_set_buffer_var_nullable( + tiledb_ctx_t* ctx, + tiledb_query_t* query, + const char* name, + uint64_t* buffer_off, + uint64_t* buffer_off_size, + void* buffer_val, + uint64_t* buffer_val_size, + uint8_t* buffer_validity_bytemap, + uint64_t* buffer_validity_bytemap_size); + +/** + * Gets the buffer of a fixed-sized attribute/dimension from a query. If the * buffer has not been set, then `buffer` is set to `nullptr`. * * **Example:** @@ -3335,6 +3473,92 @@ TILEDB_EXPORT int32_t tiledb_query_get_buffer_var( void** buffer_val, uint64_t** buffer_val_size); +/** + * Gets the buffer of a fixed-sized, nullable attribute from a query. If the + * buffer has not been set, then `buffer` and `buffer_validity_bytemap` are + * set to `nullptr`. + * + * **Example:** + * + * @code{.c} + * int* a1; + * uint64_t* a1_size; + * uint8_t* a1_validity; + * uint64_t* a1_validity_size; + * tiledb_query_get_buffer( + * ctx, query, "a1", &a1, &a1_size, &a1_validity, &a1_validity_size); + * @endcode + * + * @param ctx The TileDB context. + * @param query The TileDB query. + * @param name The attribute/dimension to get the buffer for. Note that the + * zipped coordinates have special name `TILEDB_COORDS`. + * @param buffer The buffer to retrieve. + * @param buffer_size A pointer to the size of the buffer. Note that this is + * a double pointer and returns the original variable address from + * `set_buffer`. + * @param buffer_validity_bytemap The validity bytemap buffer to retrieve. + * @param buffer_validity_bytemap_size A pointer to the size of the validity + * bytemap buffer. Note that this is a double pointer and returns the + * origina variable address from `set_buffer_nullable`. + * @return `TILEDB_OK` for success and `TILEDB_ERR` for error. + */ +TILEDB_EXPORT int32_t tiledb_query_get_buffer_nullable( + tiledb_ctx_t* ctx, + tiledb_query_t* query, + const char* name, + void** buffer, + uint64_t** buffer_size, + uint8_t** buffer_validity_bytemap, + uint64_t** buffer_validity_bytemap_size); + +/** + * Gets the values and offsets buffers for a var-sized, nullable attribute + * to a query. If the buffers have not been set, then `buffer_off`, + * `buffer_val`, and `buffer_validity_bytemap` are set to `nullptr`. + * + * **Example:** + * + * @code{.c} + * uint64_t* a2_off; + * uint64_t* a2_off_size; + * char* a2_val; + * uint64_t* a2_val_size; + * uint8_t* a2_validity; + * uint64_t* a2_validity_size; + * tiledb_query_get_buffer_var( + * ctx, query, "a2", &a2_off, &a2_off_size, &a2_val, &a2_val_size, + * &a2_validity, &a2_validity_size); + * @endcode + * + * @param ctx The TileDB context. + * @param query The TileDB query. + * @param name The attribute/dimension to set the buffer for. + * @param buffer_off The offsets buffer to be retrieved. + * @param buffer_off_size A pointer to the size of the offsets buffer. Note that + * this is a `uint_64**` pointer and returns the original variable address + * from `set_buffer`. + * @param buffer_val The values buffer to be retrieved. + * @param buffer_val_size A pointer to the size of the values buffer. Note that + * this is a `uint_64**` pointer and returns the original variable address + * from `set_buffer`. + * @param buffer_validity_bytemap The validity bytemap buffer to retrieve. + * @param buffer_validity_bytemap_size A pointer to the size of the validity + * bytemap buffer. Note that this is a double pointer and returns the + * origina variable address from `set_buffer_var_nullable`. + * @return `TILEDB_OK` for success and `TILEDB_ERR` for error. + */ +TILEDB_EXPORT int32_t tiledb_query_get_buffer_var_nullable( + tiledb_ctx_t* ctx, + tiledb_query_t* query, + const char* name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + uint8_t** buffer_validity_bytemap, + uint64_t** buffer_validity_bytemap_size); + /** * Sets the layout of the cells to be written or read. * diff --git a/tiledb/sm/config/config.h b/tiledb/sm/config/config.h index 62f65a2853f..0c218a8bf47 100644 --- a/tiledb/sm/config/config.h +++ b/tiledb/sm/config/config.h @@ -113,7 +113,7 @@ class Config { * tuning parameter for use on workloads where partitioning time is * quicker than sorting result coordinates. This budget is used as * a target and may be adjusted if it is too small. Additionally, it - * is used for both fixed and var-sized budgets. + * is used for fixed, var-sized, and validity vector budgets. */ static const std::string SM_SUB_PARTITIONER_MEMORY_BUDGET; diff --git a/tiledb/sm/cpp_api/attribute.h b/tiledb/sm/cpp_api/attribute.h index c075a53bb41..f50370b4a3c 100644 --- a/tiledb/sm/cpp_api/attribute.h +++ b/tiledb/sm/cpp_api/attribute.h @@ -337,6 +337,25 @@ class Attribute { return *this; } + /** + * Sets the nullability of an attribute. + * + * **Example:** + * @code{.cpp} + * auto a1 = Attribute::create(...); + * a1.set_nullable(true); + * @endcode + * + * @param nullable Whether the attribute is nullable. + * @return Reference to this Attribute + */ + Attribute& set_nullable(bool nullable) { + auto& ctx = ctx_.get(); + ctx.handle_error(tiledb_attribute_set_nullable( + ctx.ptr().get(), attr_.get(), static_cast(nullable))); + return *this; + } + /** Returns the C TileDB attribute object pointer. */ std::shared_ptr ptr() const { return attr_; diff --git a/tiledb/sm/cpp_api/query.h b/tiledb/sm/cpp_api/query.h index 1835cb4ba67..f5de4e40d9a 100644 --- a/tiledb/sm/cpp_api/query.h +++ b/tiledb/sm/cpp_api/query.h @@ -120,7 +120,7 @@ class Query { * // Open the array for writing * tiledb::Context ctx; * tiledb::Array array(ctx, "my_array", TILEDB_WRITE); - * Query query(ctx, array, TILEDB_WRITE); + * tiledb::Query query(ctx, array, TILEDB_WRITE); * @endcode * * @param ctx TileDB context @@ -387,7 +387,7 @@ class Query { std::pair>(); // Query hasn't been submitted for (const auto& b_it : buff_sizes_) { auto attr_name = b_it.first; - auto size_pair = b_it.second; + auto size_tuple = b_it.second; auto var = ((attr_name != "__coords") && ((schema_.has_attribute(attr_name) && @@ -396,15 +396,93 @@ class Query { schema_.domain().dimension(attr_name).cell_val_num() == TILEDB_VAR_NUM))); auto element_size = element_sizes_.find(attr_name)->second; - elements[attr_name] = (var) ? std::pair( - size_pair.first / sizeof(uint64_t), - size_pair.second / element_size) : - std::pair( - 0, size_pair.second / element_size); + elements[attr_name] = var ? + std::pair( + std::get<0>(size_tuple) / sizeof(uint64_t), + std::get<1>(size_tuple) / element_size) : + std::pair( + 0, std::get<1>(size_tuple) / element_size); } return elements; } + /** + * Returns the number of elements in the result buffers from a read query. + * This is a map from the attribute name to a tuple of values. + * + * The first is number of elements (offsets) for var size attributes, and the + * second is number of elements in the data buffer. For fixed sized attributes + * (and coordinates), the first is always 0. The third element is the size of + * the validity bytemap buffer. + * + * + * For variable sized attributes: the first value is the + * number of cells read, i.e. the number of offsets read for the attribute. + * The second value is the total number of elements in the data buffer. For + * example, a read query on a variable-length `float` attribute that reads + * three cells would return 3 for the first number in the pair. If the total + * amount of `floats` read across the three cells was 10, then the second + * number in the pair would be 10. + * + * For fixed-length attributes, the first value is always 0. The second value + * is the total number of elements in the data buffer. For example, a read + * query on a single `float` attribute that reads three cells would return 3 + * for the second value. A read query on a `float` attribute with cell_val_num + * 2 that reads three cells would return 3 * 2 = 6 for the second value. + * + * If the query has not been submitted, an empty map is returned. + * + * **Example:** + * @code{.cpp} + * // Submit a read query. + * query.submit(); + * auto result_el = query.result_buffer_elements_nullable(); + * + * // For fixed-sized attributes, the second tuple element is the number of + * // elements that were read for the attribute across all cells. Note: number + * // of elements and not number of bytes. + * auto num_a1_elements = std::get<1>(result_el["a1"]); + * + * // In variable attributes, e.g. std::string type, need two buffers, + * // one for offsets and one for cell data ("elements"). + * auto num_a2_offsets = std::get<0>(result_el["a2"]); + * auto num_a2_elements = std::get<1>(result_el["a2"]); + * + * // For both fixed-size and variable-sized attributes, the third tuple + * // element is the number of elements in the validity bytemap. + * auto num_a1_validity_values = std::get<2>(result_el["a1"]); + * auto num_a2_validity_values = std::get<2>(result_el["a2"]); + * @endcode + */ + std::unordered_map> + result_buffer_elements_nullable() const { + std::unordered_map> + elements; + if (buff_sizes_.empty()) + return std::unordered_map< + std::string, + std::tuple>(); // Query hasn't been + // submitted + for (const auto& b_it : buff_sizes_) { + auto attr_name = b_it.first; + auto size_tuple = b_it.second; + auto var = schema_.has_attribute(attr_name) && + schema_.attribute(attr_name).cell_val_num() == TILEDB_VAR_NUM; + auto element_size = element_sizes_.find(attr_name)->second; + elements[attr_name] = var ? + std::tuple( + std::get<0>(size_tuple) / sizeof(uint64_t), + std::get<1>(size_tuple) / element_size, + std::get<2>(size_tuple) / sizeof(uint8_t)) : + std::tuple( + 0, + std::get<1>(size_tuple) / element_size, + std::get<2>(size_tuple) / sizeof(uint8_t)); + } + + return elements; + } + /** * Adds a 1D range along a subarray dimension, in the form * (start, end, stride). The datatype of the range @@ -1093,6 +1171,248 @@ class Query { sizeof(char)); } + /** + * Sets a buffer for a fixed-sized, nullable attribute. + * + * **Example:** + * @code{.cpp} + * tiledb::Context ctx; + * tiledb::Array array(ctx, array_name, TILEDB_WRITE); + * int data_a1[] = {0, 1, 2, 3}; + * uint8_t validity_bytemap[] = {1, 1, 0, 1}; + * Query query(ctx, array); + * query.set_buffer("a1", data_a1, 4, validity_bytemap, 4); + * @endcode + * + * @note set_buffer(std::string, std::vector) is preferred as it is safer. + * + * @tparam T Attribute value type + * @param name Attribute name + * @param data Buffer array pointer with elements of the + * attribute type. + * @param data_nelements Number of array elements in `data` + * @param validity_bytemap The validity bytemap buffer. + * @param validity_bytemap_nelements The number of values within + * `validity_bytemap_nelements` + **/ + template + Query& set_buffer_nullable( + const std::string& name, + T* data, + uint64_t data_nelements, + uint8_t* validity_bytemap, + uint64_t validity_bytemap_nelements) { + // Checks + auto is_attr = schema_.has_attribute(name); + if (!is_attr) + throw TileDBError( + std::string("Cannot set buffer; Attribute '") + name + + "' does not exist"); + else + impl::type_check(schema_.attribute(name).type()); + + return set_buffer_nullable( + name, + data, + data_nelements, + sizeof(T), + validity_bytemap, + validity_bytemap_nelements); + } + + /** + * Sets a buffer for a fixed-sized, nullable attribute. + * + * **Example:** + * @code{.cpp} + * tiledb::Context ctx; + * tiledb::Array array(ctx, array_name, TILEDB_WRITE); + * std::vector data_a1 = {0, 1, 2, 3}; + * std::vector validity_bytemap = {1, 1, 0, 1}; + * Query query(ctx, array); + * query.set_buffer("a1", data_a1, validity_bytemap); + * @endcode + * + * @tparam T Attribute value type + * @param name Attribute name + * @param buf Buffer vector with elements of the attribute/dimension type. + * @param validity_bytemap Buffer vector with elements of the attribute + * validity values. + **/ + template + Query& set_buffer_nullable( + const std::string& name, + std::vector& buf, + std::vector& validity_bytemap) { + return set_buffer_nullable( + name, + buf.data(), + buf.size(), + sizeof(T), + validity_bytemap.data(), + validity_bytemap.size()); + } + + /** + * Sets a buffer for a fixed-sized, nullable attribute. + * + * @note This unsafe version does not perform type checking; the given buffer + * is assumed to be the correct type, and the size of an element in the given + * buffer is assumed to be the size of the datatype of the attribute. + * + * @param nam Attribute name + * @param data Buffer array pointer with elements of the attribute type. + * @param data_nelements Number of array elements in buffer + * @param validity_bytemap The validity bytemap buffer. + * @param validity_bytemap_nelements The number of values within + * `validity_bytemap_nelements` + **/ + Query& set_buffer_nullable( + const std::string& name, + void* data, + uint64_t data_nelements, + uint8_t* validity_bytemap, + uint64_t validity_bytemap_nelements) { + // Checks + auto is_attr = schema_.has_attribute(name); + if (!is_attr) + throw TileDBError( + std::string("Cannot set buffer; Attribute '") + name + + "' does not exist"); + + // Compute element size (in bytes). + size_t element_size = tiledb_datatype_size(schema_.attribute(name).type()); + + return set_buffer_nullable( + name, + data, + data_nelements, + element_size, + validity_bytemap, + validity_bytemap_nelements); + } + + /** + * Sets a buffer for a variable-sized, nullable attribute. + * + * @note This unsafe version does not perform type checking; the given buffer + * is assumed to be the correct type, and the size of an element in the given + * buffer is assumed to be the size of the datatype of the attribute. + * + * @param name Attribute name + * @param offsets Offsets array pointer where a new element begins in the data + * buffer. + * @param offsets_nelements Number of elements in offsets buffer. + * @param data Buffer array pointer with elements of the attribute type. + * @param data_nelements Number of array elements in data buffer. + * @param validity_bytemap The validity bytemap buffer. + * @param validity_bytemap_nelements The number of values within + * `validity_bytemap_nelements` + **/ + Query& set_buffer_nullable( + const std::string& name, + uint64_t* offsets, + uint64_t offset_nelements, + void* data, + uint64_t data_nelements, + uint8_t* validity_bytemap, + uint64_t validity_bytemap_nelements) { + // Checks + auto is_attr = schema_.has_attribute(name); + if (!is_attr) + throw TileDBError( + std::string("Cannot set buffer; Attribute '") + name + + "' does not exist"); + + // Compute element size (in bytes). + auto type = schema_.attribute(name).type(); + size_t element_size = tiledb_datatype_size(type); + + return set_buffer_nullable( + name, + offsets, + offset_nelements, + data, + data_nelements, + element_size, + validity_bytemap, + validity_bytemap_nelements); + } + + /** + * Sets a buffer for a variable-sized, nullable attribute. + * + * **Example:** + * @code{.cpp} + * tiledb::Context ctx; + * tiledb::Array array(ctx, array_name, TILEDB_WRITE); + * std::vector data_a1 = {0, 1, 2, 3}; + * std::vector offsets_a1 = {0, 8}; + * std::vector validity_bytemap = {1, 1, 0, 1}; + * Query query(ctx, array); + * query.set_buffer("a1", offsets_a1, data_a1, validity_bytemap); + * @endcode + * + * @tparam T Attribute value type + * @param name Attribute name + * @param offsets Offsets where a new element begins in the data buffer. + * @param data Buffer vector with elements of the attribute type. + * For variable sized attributes, the buffer should be flattened. E.x. + * an attribute of type std::string should have a buffer Vec type of + * std::string, where the values of each cell are concatenated. + * @param validity_bytemap Buffer vector with elements of the attribute + * validity values. + **/ + template + Query& set_buffer_nullable( + const std::string& name, + std::vector& offsets, + std::vector& data, + std::vector& validity_bytemap) { + // Checks + auto is_attr = schema_.has_attribute(name); + if (!is_attr) + throw TileDBError( + std::string("Cannot set buffer; Attribute '") + name + + "' does not exist"); + else + impl::type_check(schema_.attribute(name).type()); + + return set_buffer_nullable( + name, + offsets.data(), + offsets.size(), + data.data(), + data.size(), + sizeof(T), + validity_bytemap.data(), + validity_bytemap.size()); + } + + /** + * Sets a buffer for a variable-sized, nullable attribute. + * + * @tparam T Attribute value type + * @param attr Attribute name + * @param buf Pair of offset, data, validity bytemap buffers + **/ + template + Query& set_buffer_nullable( + const std::string& name, + std::tuple, std::vector, std::vector>& + buf) { + // Checks + auto is_attr = schema_.has_attribute(name); + if (!is_attr) + throw TileDBError( + std::string("Cannot set buffer; Attribute '") + name + + "' does not exist"); + impl::type_check(schema_.attribute(name).type()); + + return set_buffer_nullable( + name, std::get<0>(buf), std::get<1>(buf), std::get<2>(buf)); + } + /** * Gets a buffer for a fixed-sized attribute/dimension. * @@ -1172,6 +1492,107 @@ class Query { return *this; } + /** + * Gets a buffer for a fixed-sized, nullable attribute. + * + * @param name Attribute name + * @param data Buffer array pointer with elements of the attribute type. + * @param data_nelements Number of array elements. + * @param data_element_size Size of array elements (in bytes). + * @param validity_bytemap Buffer array pointer with elements of the + * attribute validity values. + * @param validity_bytemap_nelements Number of validity bytemap elements. + **/ + Query& get_buffer_nullable( + const std::string& name, + void** data, + uint64_t* data_nelements, + uint64_t* data_element_size, + uint8_t** validity_bytemap, + uint64_t* validity_bytemap_nelements) { + auto ctx = ctx_.get(); + uint64_t* data_nbytes = nullptr; + uint64_t* validity_bytemap_nbytes = nullptr; + auto elem_size_iter = element_sizes_.find(name); + if (elem_size_iter == element_sizes_.end()) { + throw TileDBError( + "[TileDB::C++API] Error: No buffer set for attribute '" + name + + "'!"); + } + + ctx.handle_error(tiledb_query_get_buffer_nullable( + ctx.ptr().get(), + query_.get(), + name.c_str(), + data, + &data_nbytes, + validity_bytemap, + &validity_bytemap_nbytes)); + + assert(*data_nbytes % elem_size_iter->second == 0); + + *data_nelements = *data_nbytes / elem_size_iter->second; + *data_element_size = elem_size_iter->second; + *validity_bytemap_nelements = *validity_bytemap_nbytes / sizeof(uint8_t); + + return *this; + } + + /** + * Gets a buffer for a var-sized, nullable attribute. + * + * @param name Attribute name + * @param offsets Offsets array pointer with elements of uint64_t type. + * @param offsets_nelements Number of array elements. + * @param data Buffer array pointer with elements of the attribute type. + * @param data_nelements Number of array elements. + * @param element_size Size of array elements (in bytes). + * @param validity_bytemap Buffer array pointer with elements of the + * attribute validity values. + * @param validity_bytemap_nelements Number of validity bytemap elements. + **/ + Query& get_buffer_nullable( + const std::string& name, + uint64_t** offsets, + uint64_t* offsets_nelements, + void** data, + uint64_t* data_nelements, + uint64_t* element_size, + uint8_t** validity_bytemap, + uint64_t* validity_bytemap_nelements) { + auto ctx = ctx_.get(); + uint64_t* offsets_nbytes = nullptr; + uint64_t* data_nbytes = nullptr; + uint64_t* validity_bytemap_nbytes = nullptr; + auto elem_size_iter = element_sizes_.find(name); + if (elem_size_iter == element_sizes_.end()) { + throw TileDBError( + "[TileDB::C++API] Error: No buffer set for attribute '" + name + + "'!"); + } + + ctx.handle_error(tiledb_query_get_buffer_var_nullable( + ctx.ptr().get(), + query_.get(), + name.c_str(), + offsets, + &offsets_nbytes, + data, + &data_nbytes, + validity_bytemap, + &validity_bytemap_nbytes)); + + assert(*data_nbytes % elem_size_iter->second == 0); + assert(*offsets_nbytes % sizeof(uint64_t) == 0); + + *data_nelements = (*data_nbytes) / elem_size_iter->second; + *offsets_nelements = (*offsets_nbytes) / sizeof(uint64_t); + *element_size = elem_size_iter->second; + *validity_bytemap_nelements = *validity_bytemap_nbytes / sizeof(uint8_t); + + return *this; + } + /* ********************************* */ /* STATIC FUNCTIONS */ /* ********************************* */ @@ -1212,13 +1633,15 @@ class Query { /** * The buffer sizes that were set along with the buffers to the TileDB - * query. It is a map from the attribute name to a pair of sizes. - * For var-sized attributes, the first element of the pair is the + * query. It is a map from the attribute name to a tuple of sizes. + * For var-sized attributes, the first element of the tuple is the * offsets size and the second is the var-sized values size. For * fixed-sized attributes, the first is always 0, and the second is - * the values size. All sizes are in bytes. + * the values size. All sizes are in bytes. If the attribute is nullable, + * the third element in the tuple is the size of the validity buffer. */ - std::unordered_map> buff_sizes_; + std::unordered_map> + buff_sizes_; /** * Stores the size of a single element for the buffer set for a given @@ -1263,14 +1686,14 @@ class Query { size_t element_size) { auto ctx = ctx_.get(); size_t size = nelements * element_size; - buff_sizes_[attr] = std::pair(0, size); + buff_sizes_[attr] = std::tuple(0, size, 0); element_sizes_[attr] = element_size; ctx.handle_error(tiledb_query_set_buffer( ctx.ptr().get(), query_.get(), attr.c_str(), buff, - &(buff_sizes_[attr].second))); + &std::get<1>(buff_sizes_[attr]))); return *this; } @@ -1285,7 +1708,7 @@ class Query { * @param data Buffer array pointer with elements of the attribute type. * For variable sized attributes, the buffer should be flattened. * @param data_nelements Number of array elements in data buffer. - * @param element_size Size of data array elements (in bytes). + * @param data_element_size Size of data array elements (in bytes). **/ Query& set_buffer( const std::string& attr, @@ -1293,20 +1716,99 @@ class Query { uint64_t offset_nelements, void* data, uint64_t data_nelements, - size_t element_size) { + size_t data_element_size) { auto ctx = ctx_.get(); - auto data_size = data_nelements * element_size; + auto data_size = data_nelements * data_element_size; auto offset_size = offset_nelements * sizeof(uint64_t); - element_sizes_[attr] = element_size; - buff_sizes_[attr] = std::pair(offset_size, data_size); + element_sizes_[attr] = data_element_size; + buff_sizes_[attr] = + std::tuple(offset_size, data_size, 0); ctx.handle_error(tiledb_query_set_buffer_var( ctx.ptr().get(), query_.get(), attr.c_str(), offsets, - &(buff_sizes_[attr].first), + &std::get<0>(buff_sizes_[attr]), + data, + &std::get<1>(buff_sizes_[attr]))); + return *this; + } + + /** + * Sets a buffer for a nullable, fixed-sized attribute. + * + * @param attr Attribute name + * @param data Buffer array pointer with elements of the attribute type. + * @param data_nelements Number of array elements. + * @param data_element_size Size of array elements (in bytes). + * @param validity_bytemap Buffer array pointer with validity bytemap values. + * @param validity_bytemap_nelements Number of validity bytemap elements. + **/ + Query& set_buffer_nullable( + const std::string& attr, + void* data, + uint64_t data_nelements, + size_t data_element_size, + uint8_t* validity_bytemap, + uint64_t validity_bytemap_nelements) { + auto ctx = ctx_.get(); + size_t data_size = data_nelements * data_element_size; + size_t validity_size = validity_bytemap_nelements * sizeof(uint8_t); + buff_sizes_[attr] = + std::tuple(0, data_size, validity_size); + element_sizes_[attr] = data_element_size; + ctx.handle_error(tiledb_query_set_buffer_nullable( + ctx.ptr().get(), + query_.get(), + attr.c_str(), + data, + &std::get<1>(buff_sizes_[attr]), + validity_bytemap, + &std::get<2>(buff_sizes_[attr]))); + return *this; + } + + /** + * Sets a buffer for a nullable, variable-sized attribute. + * + * @tparam T Attribute value type + * @param attr Attribute name + * @param offsets Offsets array pointer where a new element begins in the data + * buffer. + * @param offsets_nelements Number of elements in offsets buffer. + * @param data Buffer array pointer with elements of the attribute type. + * For variable sized attributes, the buffer should be flattened. + * @param data_nelements Number of array elements in data buffer. + * @param data_element_size Size of data array elements (in bytes). + * @param validity_bytemap Buffer array pointer with validity bytemap values. + * @param validity_bytemap_nelements Number of validity bytemap elements. + **/ + Query& set_buffer_nullable( + const std::string& attr, + uint64_t* offsets, + uint64_t offset_nelements, + void* data, + uint64_t data_nelements, + size_t data_element_size, + uint8_t* validity_bytemap, + uint64_t validity_bytemap_nelements) { + auto ctx = ctx_.get(); + auto data_size = data_nelements * data_element_size; + auto offset_size = offset_nelements * sizeof(uint64_t); + size_t validity_size = validity_bytemap_nelements * sizeof(uint8_t); + element_sizes_[attr] = data_element_size; + buff_sizes_[attr] = std::tuple( + offset_size, data_size, validity_size); + ctx.handle_error(tiledb_query_set_buffer_var_nullable( + ctx.ptr().get(), + query_.get(), + attr.c_str(), + offsets, + &std::get<0>(buff_sizes_[attr]), data, - &(buff_sizes_[attr].second))); + &std::get<1>(buff_sizes_[attr]), + validity_bytemap, + &std::get<2>(buff_sizes_[attr]))); return *this; } }; diff --git a/tiledb/sm/fragment/fragment_metadata.cc b/tiledb/sm/fragment/fragment_metadata.cc index 35ed75bd76c..2f8f1f8c1b0 100644 --- a/tiledb/sm/fragment/fragment_metadata.cc +++ b/tiledb/sm/fragment/fragment_metadata.cc @@ -139,6 +139,17 @@ void FragmentMetadata::set_tile_var_size( tile_var_sizes_[idx][tid] = size; } +void FragmentMetadata::set_tile_validity_offset( + const std::string& name, uint64_t tid, uint64_t step) { + auto it = idx_map_.find(name); + assert(it != idx_map_.end()); + auto idx = it->second; + tid += tile_index_base_; + assert(tid < tile_validity_offsets_[idx].size()); + tile_validity_offsets_[idx][tid] = file_validity_sizes_[idx]; + file_validity_sizes_[idx] += step; +} + uint64_t FragmentMetadata::cell_num(uint64_t tile_pos) const { if (dense_) return array_schema_->domain()->cell_num_per_tile(); @@ -328,6 +339,8 @@ Status FragmentMetadata::fragment_size(uint64_t* size) const { *size += file_size; for (const auto& file_var_size : file_var_sizes_) *size += file_var_size; + for (const auto& file_validity_size : file_validity_sizes_) + *size += file_validity_size; // Add fragment metadata file size assert(meta_file_size_ != 0); // The file size should be loaded @@ -396,6 +409,12 @@ Status FragmentMetadata::init(const NDRange& non_empty_domain) { // Initialize variable tile sizes tile_var_sizes_.resize(num); + // Initialize validity tile offsets + tile_validity_offsets_.resize(num); + file_validity_sizes_.resize(num); + for (unsigned int i = 0; i < num; ++i) + file_validity_sizes_[i] = 0; + return Status::Ok(); } @@ -478,6 +497,17 @@ Status FragmentMetadata::store(const EncryptionKey& encryption_key) { offset += nbytes; } + // Store validity tile offsets + if (version_ >= 7) { + gt_offsets_.tile_validity_offsets_.resize(num); + for (unsigned int i = 0; i < num; ++i) { + gt_offsets_.tile_validity_offsets_[i] = offset; + RETURN_NOT_OK_ELSE( + store_tile_validity_offsets(i, encryption_key, &nbytes), clean_up()); + offset += nbytes; + } + } + // Store footer RETURN_NOT_OK_ELSE(store_footer(encryption_key), clean_up()); @@ -499,6 +529,7 @@ Status FragmentMetadata::set_num_tiles(uint64_t num_tiles) { tile_offsets_[i].resize(num_tiles, 0); tile_var_offsets_[i].resize(num_tiles, 0); tile_var_sizes_[i].resize(num_tiles, 0); + tile_validity_offsets_[i].resize(num_tiles, 0); } if (!dense_) { @@ -532,6 +563,10 @@ URI FragmentMetadata::var_uri(const std::string& name) const { return fragment_uri_.join_path(name + "_var" + constants::file_suffix); } +URI FragmentMetadata::validity_uri(const std::string& name) const { + return fragment_uri_.join_path(name + "_validity" + constants::file_suffix); +} + Status FragmentMetadata::load_tile_offsets( const EncryptionKey& encryption_key, std::vector&& names) { // Sort 'names' in ascending order of their index. The @@ -559,6 +594,12 @@ Status FragmentMetadata::load_tile_offsets( RETURN_NOT_OK(load_tile_var_offsets(encryption_key, idx_map_[name])); } + // Load all of the var offsets. + for (const auto& name : names) { + if (array_schema_->is_nullable(name)) + RETURN_NOT_OK(load_tile_validity_offsets(encryption_key, idx_map_[name])); + } + return Status::Ok(); } @@ -588,6 +629,19 @@ Status FragmentMetadata::file_var_offset( return Status::Ok(); } +Status FragmentMetadata::file_validity_offset( + const EncryptionKey& encryption_key, + const std::string& name, + uint64_t tile_idx, + uint64_t* offset) { + auto it = idx_map_.find(name); + assert(it != idx_map_.end()); + auto idx = it->second; + RETURN_NOT_OK(load_tile_validity_offsets(encryption_key, idx)); + *offset = tile_validity_offsets_[idx][tile_idx]; + return Status::Ok(); +} + const NDRange& FragmentMetadata::mbr(uint64_t tile_idx) const { return rtree_.leaf(tile_idx); } @@ -625,6 +679,7 @@ Status FragmentMetadata::persisted_tile_var_size( assert(it != idx_map_.end()); auto idx = it->second; RETURN_NOT_OK(load_tile_var_offsets(encryption_key, idx)); + auto tile_num = this->tile_num(); *tile_size = (tile_idx != tile_num - 1) ? @@ -635,6 +690,27 @@ Status FragmentMetadata::persisted_tile_var_size( return Status::Ok(); } +Status FragmentMetadata::persisted_tile_validity_size( + const EncryptionKey& encryption_key, + const std::string& name, + uint64_t tile_idx, + uint64_t* tile_size) { + auto it = idx_map_.find(name); + assert(it != idx_map_.end()); + auto idx = it->second; + RETURN_NOT_OK(load_tile_validity_offsets(encryption_key, idx)); + + auto tile_num = this->tile_num(); + + *tile_size = + (tile_idx != tile_num - 1) ? + tile_validity_offsets_[idx][tile_idx + 1] - + tile_validity_offsets_[idx][tile_idx] : + file_validity_sizes_[idx] - tile_validity_offsets_[idx][tile_idx]; + + return Status::Ok(); +} + uint64_t FragmentMetadata::tile_size( const std::string& name, uint64_t tile_idx) const { auto var_size = array_schema_->var_size(name); @@ -679,6 +755,7 @@ Status FragmentMetadata::write_footer(Buffer* buff) const { RETURN_NOT_OK(write_last_tile_cell_num(buff)); RETURN_NOT_OK(write_file_sizes(buff)); RETURN_NOT_OK(write_file_var_sizes(buff)); + RETURN_NOT_OK(write_file_validity_sizes(buff)); RETURN_NOT_OK(write_generic_tile_offsets(buff)); return Status::Ok(); @@ -724,7 +801,13 @@ Status FragmentMetadata::load_tile_var_sizes( Status FragmentMetadata::get_footer_size( uint32_t version, uint64_t* size) const { - *size = (version < 3) ? footer_size_v3_v4() : footer_size_v5_or_higher(); + if (version < 3) { + *size = footer_size_v3_v4(); + } else if (version < 4) { + *size = footer_size_v5_v6(); + } else { + *size = footer_size_v7_or_higher(); + } return Status::Ok(); } @@ -778,7 +861,7 @@ uint64_t FragmentMetadata::footer_size_v3_v4() const { return size; } -uint64_t FragmentMetadata::footer_size_v5_or_higher() const { +uint64_t FragmentMetadata::footer_size_v5_v6() const { auto dim_num = array_schema_->dim_num(); auto num = array_schema_->attribute_num() + dim_num + 1; uint64_t domain_size = 0; @@ -818,6 +901,48 @@ uint64_t FragmentMetadata::footer_size_v5_or_higher() const { return size; } +uint64_t FragmentMetadata::footer_size_v7_or_higher() const { + auto dim_num = array_schema_->dim_num(); + auto num = array_schema_->attribute_num() + dim_num + 1; + uint64_t domain_size = 0; + + if (non_empty_domain_.empty()) { + // For var-sized dimensions, this function would be called only upon + // writing the footer to storage, in which case the non-empty domain + // would not be empty. For reading the footer from storage, the footer + // size is explicitly stored to and retrieved from storage, so this + // function is not called then. + assert(array_schema_->domain()->all_dims_fixed()); + for (unsigned d = 0; d < dim_num; ++d) + domain_size += 2 * array_schema_->domain()->dimension(d)->coord_size(); + } else { + for (unsigned d = 0; d < dim_num; ++d) { + domain_size += non_empty_domain_[d].size(); + if (array_schema_->dimension(d)->var_size()) + domain_size += 2 * sizeof(uint64_t); // Two more sizes get serialized + } + } + + // Get footer size + uint64_t size = 0; + size += sizeof(uint32_t); // version + size += sizeof(char); // dense + size += sizeof(char); // null non-empty domain + size += domain_size; // non-empty domain + size += sizeof(uint64_t); // sparse tile num + size += sizeof(uint64_t); // last tile cell num + size += num * sizeof(uint64_t); // file sizes + size += num * sizeof(uint64_t); // file var sizes + size += num * sizeof(uint64_t); // file validity sizes + size += sizeof(uint64_t); // R-Tree offset + size += num * sizeof(uint64_t); // tile offsets + size += num * sizeof(uint64_t); // tile var offsets + size += num * sizeof(uint64_t); // tile var sizes + size += num * sizeof(uint64_t); // tile validity sizes + + return size; +} + template std::vector FragmentMetadata::compute_overlapping_tile_ids( const T* subarray) const { @@ -1035,6 +1160,31 @@ Status FragmentMetadata::load_tile_var_sizes( return Status::Ok(); } +Status FragmentMetadata::load_tile_validity_offsets( + const EncryptionKey& encryption_key, unsigned idx) { + if (version_ <= 6) + return Status::Ok(); + + std::lock_guard lock(mtx_); + + if (loaded_metadata_.tile_validity_offsets_[idx]) + return Status::Ok(); + + Buffer buff; + RETURN_NOT_OK(read_generic_tile_from_file( + encryption_key, gt_offsets_.tile_validity_offsets_[idx], &buff)); + + STATS_ADD_COUNTER( + stats::Stats::CounterType::READ_TILE_VALIDITY_OFFSETS_SIZE, buff.size()); + + ConstBuffer cbuff(&buff); + RETURN_NOT_OK(load_tile_validity_offsets(idx, &cbuff)); + + loaded_metadata_.tile_validity_offsets_[idx] = true; + + return Status::Ok(); +} + // ===== FORMAT ===== // bounding_coords_num (uint64_t) // bounding_coords_#1 (void*) bounding_coords_#2 (void*) ... @@ -1141,6 +1291,23 @@ Status FragmentMetadata::load_file_var_sizes_v5_or_higher(ConstBuffer* buff) { return Status::Ok(); } +Status FragmentMetadata::load_file_validity_sizes( + ConstBuffer* buff, uint32_t version) { + if (version <= 6) + return Status::Ok(); + + auto num = array_schema_->attribute_num() + array_schema_->dim_num() + 1; + file_validity_sizes_.resize(num); + Status st = buff->read(&file_validity_sizes_[0], num * sizeof(uint64_t)); + + if (!st.ok()) { + return LOG_STATUS(Status::FragmentMetadataError( + "Cannot load fragment metadata; Reading tile offsets failed")); + } + + return Status::Ok(); +} + // ===== FORMAT ===== // last_tile_cell_num (uint64_t) Status FragmentMetadata::load_last_tile_cell_num(ConstBuffer* buff) { @@ -1510,6 +1677,37 @@ Status FragmentMetadata::load_tile_var_sizes(unsigned idx, ConstBuffer* buff) { return Status::Ok(); } +Status FragmentMetadata::load_tile_validity_offsets( + unsigned idx, ConstBuffer* buff) { + Status st; + uint64_t tile_validity_offsets_num = 0; + + // Get number of tile offsets + st = buff->read(&tile_validity_offsets_num, sizeof(uint64_t)); + if (!st.ok()) { + return LOG_STATUS( + Status::FragmentMetadataError("Cannot load fragment metadata; Reading " + "number of validity tile offsets " + "failed")); + } + + // Get tile offsets + if (tile_validity_offsets_num != 0) { + tile_validity_offsets_[idx].resize(tile_validity_offsets_num); + st = buff->read( + &tile_validity_offsets_[idx][0], + tile_validity_offsets_num * sizeof(uint64_t)); + + if (!st.ok()) { + return LOG_STATUS(Status::FragmentMetadataError( + "Cannot load fragment metadata; Reading validity tile offsets " + "failed")); + } + } + + return Status::Ok(); +} + Status FragmentMetadata::load_version(ConstBuffer* buff) { RETURN_NOT_OK(buff->read(&version_, sizeof(uint32_t))); return Status::Ok(); @@ -1529,8 +1727,10 @@ Status FragmentMetadata::load_generic_tile_offsets( ConstBuffer* buff, uint32_t version) { if (version == 3 || version == 4) return load_generic_tile_offsets_v3_v4(buff); - else if (version > 4) - return load_generic_tile_offsets_v5_or_higher(buff); + else if (version >= 5 && version < 7) + return load_generic_tile_offsets_v5_v6(buff); + else if (version >= 7) + return load_generic_tile_offsets_v7_or_higher(buff); assert(false); return Status::Ok(); @@ -1564,7 +1764,35 @@ Status FragmentMetadata::load_generic_tile_offsets_v3_v4(ConstBuffer* buff) { return Status::Ok(); } -Status FragmentMetadata::load_generic_tile_offsets_v5_or_higher( +Status FragmentMetadata::load_generic_tile_offsets_v5_v6(ConstBuffer* buff) { + // Load R-Tree offset + RETURN_NOT_OK(buff->read(>_offsets_.rtree_, sizeof(uint64_t))); + + // Load offsets for tile offsets + auto num = array_schema_->attribute_num() + array_schema_->dim_num() + 1; + gt_offsets_.tile_offsets_.resize(num); + for (unsigned i = 0; i < num; ++i) { + RETURN_NOT_OK(buff->read(>_offsets_.tile_offsets_[i], sizeof(uint64_t))); + } + + // Load offsets for tile var offsets + gt_offsets_.tile_var_offsets_.resize(num); + for (unsigned i = 0; i < num; ++i) { + RETURN_NOT_OK( + buff->read(>_offsets_.tile_var_offsets_[i], sizeof(uint64_t))); + } + + // Load offsets for tile var sizes + gt_offsets_.tile_var_sizes_.resize(num); + for (unsigned i = 0; i < num; ++i) { + RETURN_NOT_OK( + buff->read(>_offsets_.tile_var_sizes_[i], sizeof(uint64_t))); + } + + return Status::Ok(); +} + +Status FragmentMetadata::load_generic_tile_offsets_v7_or_higher( ConstBuffer* buff) { // Load R-Tree offset RETURN_NOT_OK(buff->read(>_offsets_.rtree_, sizeof(uint64_t))); @@ -1590,6 +1818,15 @@ Status FragmentMetadata::load_generic_tile_offsets_v5_or_higher( buff->read(>_offsets_.tile_var_sizes_[i], sizeof(uint64_t))); } + // Load offsets for tile validity offsets + if (version_ >= 7) { + gt_offsets_.tile_validity_offsets_.resize(num); + for (unsigned i = 0; i < num; ++i) { + RETURN_NOT_OK( + buff->read(>_offsets_.tile_validity_offsets_[i], sizeof(uint64_t))); + } + } + return Status::Ok(); } @@ -1625,6 +1862,7 @@ Status FragmentMetadata::load_v1_v2(const EncryptionKey& encryption_key) { RETURN_NOT_OK(load_last_tile_cell_num(&cbuff)); RETURN_NOT_OK(load_file_sizes(&cbuff, version_)); RETURN_NOT_OK(load_file_var_sizes(&cbuff, version_)); + RETURN_NOT_OK(load_file_validity_sizes(&cbuff, version_)); return Status::Ok(); } @@ -1668,6 +1906,7 @@ Status FragmentMetadata::load_footer( RETURN_NOT_OK(load_last_tile_cell_num(cbuff.get())); RETURN_NOT_OK(load_file_sizes(cbuff.get(), version)); RETURN_NOT_OK(load_file_var_sizes(cbuff.get(), version)); + RETURN_NOT_OK(load_file_validity_sizes(cbuff.get(), version)); unsigned num = array_schema_->attribute_num() + 1; num += (version >= 5) ? array_schema_->dim_num() : 0; @@ -1675,10 +1914,12 @@ Status FragmentMetadata::load_footer( tile_offsets_.resize(num); tile_var_offsets_.resize(num); tile_var_sizes_.resize(num); + tile_validity_offsets_.resize(num); loaded_metadata_.tile_offsets_.resize(num, false); loaded_metadata_.tile_var_offsets_.resize(num, false); loaded_metadata_.tile_var_sizes_.resize(num, false); + loaded_metadata_.tile_validity_offsets_.resize(num, false); RETURN_NOT_OK(load_generic_tile_offsets(cbuff.get(), version)); @@ -1717,6 +1958,24 @@ Status FragmentMetadata::write_file_var_sizes(Buffer* buff) const { return Status::Ok(); } +// ===== FORMAT ===== +// file_validity_sizes#0 (uint64_t) +// ... +// file_validity_sizes#{attribute_num+dim_num} (uint64_t) +Status FragmentMetadata::write_file_validity_sizes(Buffer* buff) const { + if (version_ <= 6) + return Status::Ok(); + + auto num = array_schema_->attribute_num() + array_schema_->dim_num() + 1; + Status st = buff->write(&file_validity_sizes_[0], num * sizeof(uint64_t)); + if (!st.ok()) { + return LOG_STATUS(Status::FragmentMetadataError( + "Cannot serialize fragment metadata; Writing file sizes failed")); + } + + return Status::Ok(); +} + // ===== FORMAT ===== // rtree_offset(uint64_t) // tile_offsets_offset_0(uint64_t) @@ -1766,6 +2025,18 @@ Status FragmentMetadata::write_generic_tile_offsets(Buffer* buff) const { } } + // Write tile validity offsets + if (version_ >= 7) { + for (unsigned i = 0; i < num; ++i) { + st = + buff->write(>_offsets_.tile_validity_offsets_[i], sizeof(uint64_t)); + if (!st.ok()) { + return LOG_STATUS(Status::FragmentMetadataError( + "Cannot serialize fragment metadata; Writing tile offsets failed")); + } + } + } + return Status::Ok(); } @@ -2044,6 +2315,47 @@ Status FragmentMetadata::write_tile_var_sizes(unsigned idx, Buffer* buff) { return Status::Ok(); } +Status FragmentMetadata::store_tile_validity_offsets( + unsigned idx, const EncryptionKey& encryption_key, uint64_t* nbytes) { + Buffer buff; + RETURN_NOT_OK(write_tile_validity_offsets(idx, &buff)); + RETURN_NOT_OK( + write_generic_tile_to_file(encryption_key, std::move(buff), nbytes)); + + STATS_ADD_COUNTER( + stats::Stats::CounterType::WRITE_TILE_VALIDITY_OFFSETS_SIZE, *nbytes); + + return Status::Ok(); +} + +Status FragmentMetadata::write_tile_validity_offsets( + unsigned idx, Buffer* buff) { + Status st; + + // Write number of tile offsets + uint64_t tile_validity_offsets_num = tile_validity_offsets_[idx].size(); + st = buff->write(&tile_validity_offsets_num, sizeof(uint64_t)); + if (!st.ok()) { + return LOG_STATUS( + Status::FragmentMetadataError("Cannot serialize fragment metadata; " + "Writing number of validity tile offsets " + "failed")); + } + + // Write tile offsets + if (tile_validity_offsets_num != 0) { + st = buff->write( + &tile_validity_offsets_[idx][0], + tile_validity_offsets_num * sizeof(uint64_t)); + if (!st.ok()) { + return LOG_STATUS(Status::FragmentMetadataError( + "Cannot serialize fragment metadata; Writing tile offsets failed")); + } + } + + return Status::Ok(); +} + Status FragmentMetadata::write_version(Buffer* buff) const { RETURN_NOT_OK(buff->write(&version_, sizeof(uint32_t))); return Status::Ok(); diff --git a/tiledb/sm/fragment/fragment_metadata.h b/tiledb/sm/fragment/fragment_metadata.h index 49c896347d6..db3aa8da8f7 100644 --- a/tiledb/sm/fragment/fragment_metadata.h +++ b/tiledb/sm/fragment/fragment_metadata.h @@ -311,6 +311,18 @@ class FragmentMetadata { */ void set_tile_var_size(const std::string& name, uint64_t tid, uint64_t size); + /** + * Sets a validity tile offset for the input attribute. + * + * @param name The attribute for which the offset is set. + * @param tid The index of the tile for which the offset is set. + * @param step This is essentially the step by which the previous + * offset will be expanded. It is practically the last tile size. + * @return void + */ + void set_tile_validity_offset( + const std::string& name, uint64_t tid, uint64_t step); + /** Returns the tile index base value. */ uint64_t tile_index_base() const; @@ -323,6 +335,9 @@ class FragmentMetadata { /** Returns the URI of the input variable-sized attribute/dimension. */ URI var_uri(const std::string& name) const; + /** Returns the validity URI of the input nullable attribute. */ + URI validity_uri(const std::string& name) const; + /** * Retrieves the starting offset of the input tile of the input attribute * or dimension in the file. If the attribute/dimension is var-sized, it @@ -356,6 +371,22 @@ class FragmentMetadata { uint64_t tile_idx, uint64_t* offset); + /** + * Retrieves the starting offset of the input validity tile of the + * input attribute in the file. + * + * @param encryption_key The key the array got opened with. + * @param name The input attribute. + * @param tile_idx The index of the tile in the metadata. + * @param offset The file offset to be retrieved. + * @return Status + */ + Status file_validity_offset( + const EncryptionKey& encryption_key, + const std::string& name, + uint64_t tile_idx, + uint64_t* offset); + /** * Retrieves the size of the fragment metadata footer * (which contains the generic tile offsets) along with its size. @@ -403,6 +434,22 @@ class FragmentMetadata { uint64_t tile_idx, uint64_t* tile_size); + /** + * Retrieves the size of the validity tile when it is persisted (e.g. the size + * of the compressed tile on disk) for a given attribute. + * + * @param encryption_key The key the array got opened with. + * @param name The input attribute. + * @param tile_idx The index of the tile in the metadata. + * @param tile_size The tile size to be retrieved. + * @return Status + */ + Status persisted_tile_validity_size( + const EncryptionKey& encryption_key, + const std::string& name, + uint64_t tile_idx, + uint64_t* tile_size); + /** * Returns the (uncompressed) tile size for a given attribute or dimension * and tile index. If the attribute/dimension is var-sized, this will return @@ -465,6 +512,16 @@ class FragmentMetadata { Status load_tile_offsets( const EncryptionKey& encryption_key, std::vector&& names); + /** + * Loads validity tile offsets for the attribute names. + * + * @param encryption_key The key the array got opened with. + * @param names The attribute names. + * @return Status + */ + Status load_tile_validity_offsets( + const EncryptionKey& encryption_key, std::vector&& names); + private: /* ********************************* */ /* TYPE DEFINITIONS */ @@ -480,6 +537,7 @@ class FragmentMetadata { std::vector tile_offsets_; std::vector tile_var_offsets_; std::vector tile_var_sizes_; + std::vector tile_validity_offsets_; }; /** Keeps track of which metadata is loaded. */ @@ -489,6 +547,7 @@ class FragmentMetadata { std::vector tile_offsets_; std::vector tile_var_offsets_; std::vector tile_var_sizes_; + std::vector tile_validity_offsets_; }; /* ********************************* */ @@ -528,6 +587,9 @@ class FragmentMetadata { /** Stores the size of each variable attribute file. */ std::vector file_var_sizes_; + /** Stores the size of each validity attribute file. */ + std::vector file_validity_sizes_; + /** The uri of the fragment the metadata belongs to. */ URI fragment_uri_; @@ -576,6 +638,12 @@ class FragmentMetadata { */ std::vector> tile_var_sizes_; + /** + * The validity tile offsets in their corresponding attribute files. + * Meaningful only when there is compression. + */ + std::vector> tile_validity_offsets_; + /** The format version of this metadata. */ uint32_t version_; @@ -607,9 +675,17 @@ class FragmentMetadata { * Returns the size of the fragment metadata footer * (which contains the generic tile offsets) along with its size. * - * Applicable to format version 5 or higher. + * Applicable to format versions 5 and 6. + */ + uint64_t footer_size_v5_v6() const; + + /** + * Returns the size of the fragment metadata footer + * (which contains the generic tile offsets) along with its size. + * + * Applicable to format version 7 or higher. */ - uint64_t footer_size_v5_or_higher() const; + uint64_t footer_size_v7_or_higher() const; /** * Returns the ids (positions) of the tiles overlapping `subarray`. @@ -654,6 +730,13 @@ class FragmentMetadata { * */ Status load_tile_var_sizes(const EncryptionKey& encryption_key, unsigned idx); + /** + * Loads the validity tile offsets for the input attribute idx + * from storage. + */ + Status load_tile_validity_offsets( + const EncryptionKey& encryption_key, unsigned idx); + /** Loads the generic tile offsets from the buffer. */ Status load_generic_tile_offsets(ConstBuffer* buff, uint32_t version); @@ -665,9 +748,15 @@ class FragmentMetadata { /** * Loads the generic tile offsets from the buffer. Applicable to - * versions 5 or higher. + * versions 5 and 6. */ - Status load_generic_tile_offsets_v5_or_higher(ConstBuffer* buff); + Status load_generic_tile_offsets_v5_v6(ConstBuffer* buff); + + /** + * Loads the generic tile offsets from the buffer. Applicable to + * versions 7 or higher. + */ + Status load_generic_tile_offsets_v7_or_higher(ConstBuffer* buff); /** * Loads the bounding coordinates from the fragment metadata buffer. @@ -710,6 +799,9 @@ class FragmentMetadata { */ Status load_file_var_sizes_v5_or_higher(ConstBuffer* buff); + /** Loads the sizes of each attribute validity file from the buffer. */ + Status load_file_validity_sizes(ConstBuffer* buff, uint32_t version); + /** * Loads the cell number of the last tile from the fragment metadata buffer. * @@ -780,6 +872,12 @@ class FragmentMetadata { */ Status load_tile_var_sizes(unsigned idx, ConstBuffer* buff); + /** + * Loads the validity tile offsets for the input attribute from the + * input buffer. + */ + Status load_tile_validity_offsets(unsigned idx, ConstBuffer* buff); + /** Loads the format version from the buffer. */ Status load_version(ConstBuffer* buff); @@ -820,6 +918,9 @@ class FragmentMetadata { /** Writes the sizes of each variable attribute file to the buffer. */ Status write_file_var_sizes(Buffer* buff) const; + /** Writes the sizes of each validitiy attribute file to the buffer. */ + Status write_file_validity_sizes(Buffer* buff) const; + /** Writes the generic tile offsets to the buffer. */ Status write_generic_tile_offsets(Buffer* buff) const; @@ -899,6 +1000,24 @@ class FragmentMetadata { */ Status write_tile_var_sizes(unsigned idx, Buffer* buff); + /** + * Writes the validity tile offsets of the input attribute to storage. + * + * @param idx The index of the attribute. + * @param encryption_key The encryption key. + * @param nbytes The total number of bytes written for the validity tile + * offsets. + * @return Status + */ + Status store_tile_validity_offsets( + unsigned idx, const EncryptionKey& encryption_key, uint64_t* nbytes); + + /** + * Writes the validity tile offsets of the input attribute idx to the + * input buffer. + */ + Status write_tile_validity_offsets(unsigned idx, Buffer* buff); + /** Writes the format version to the buffer. */ Status write_version(Buffer* buff) const; diff --git a/tiledb/sm/misc/constants.cc b/tiledb/sm/misc/constants.cc index 98b4d9b7ad9..497aceeb2ec 100644 --- a/tiledb/sm/misc/constants.cc +++ b/tiledb/sm/misc/constants.cc @@ -86,6 +86,12 @@ const uint64_t cell_var_offset_size = sizeof(uint64_t); /** The type of a variable cell offset. */ const Datatype cell_var_offset_type = Datatype::UINT64; +/** The size of a validity cell. */ +const uint64_t cell_validity_size = sizeof(uint8_t); + +/** The type of a validity cell. */ +const Datatype cell_validity_type = Datatype::UINT8; + /** A special value indicating variable size. */ const uint64_t var_size = std::numeric_limits::max(); @@ -95,6 +101,12 @@ Compressor cell_var_offsets_compression = Compressor::ZSTD; /** The default compression level for the offsets of variable-sized cells. */ int cell_var_offsets_compression_level = -1; +/** The default compressor for the validity value cells. */ +Compressor cell_validity_compression = Compressor::RLE; + +/** The default compression level for the validity value cells. */ +int cell_validity_compression_level = -1; + /** Special name reserved for the coordinates attribute. */ const std::string coords = "__coords"; @@ -455,7 +467,7 @@ const int32_t library_version[3] = { TILEDB_VERSION_MAJOR, TILEDB_VERSION_MINOR, TILEDB_VERSION_PATCH}; /** The TileDB serialization format version number. */ -const uint32_t format_version = 6; +const uint32_t format_version = 7; /** The maximum size of a tile chunk (unit of compression) in bytes. */ const uint64_t max_tile_chunk_size = 64 * 1024; diff --git a/tiledb/sm/misc/constants.h b/tiledb/sm/misc/constants.h index 64d075c999e..55fd504be7c 100644 --- a/tiledb/sm/misc/constants.h +++ b/tiledb/sm/misc/constants.h @@ -71,12 +71,18 @@ extern const std::string array_metadata_folder_name; /** The default tile capacity. */ extern const uint64_t capacity; -/** The size of a variable cell offset. */ +/** The size of a variable offset cell. */ extern const uint64_t cell_var_offset_size; -/** The type of a variable cell offset. */ +/** The type of a variable offset cell. */ extern const Datatype cell_var_offset_type; +/** The size of a validity cell. */ +extern const uint64_t cell_validity_size; + +/** The type of a validity cell. */ +extern const Datatype cell_validity_type; + /** A special value indicating varibale size. */ extern const uint64_t var_size; @@ -86,6 +92,12 @@ extern Compressor cell_var_offsets_compression; /** The default compression level for the offsets of variable-sized cells. */ extern int cell_var_offsets_compression_level; +/** The default compressor for the validity value cells. */ +extern Compressor cell_validity_compression; + +/** The default compression level for the validity value cells. */ +extern int cell_validity_compression_level; + /** The default compressor for the coordinates. */ extern Compressor coords_compression; diff --git a/tiledb/sm/misc/types.h b/tiledb/sm/misc/types.h index b3f33adb3f4..83dfe463373 100644 --- a/tiledb/sm/misc/types.h +++ b/tiledb/sm/misc/types.h @@ -241,65 +241,6 @@ typedef std::vector ByteVecValue; /** A byte vector. */ typedef std::vector ByteVec; -/** Contains the buffer(s) and buffer size(s) for some attribute/dimension. */ -struct QueryBuffer { - /** - * The attribute/dimension buffer. In case the attribute/dimension is - * var-sized, this is the offsets buffer. - */ - void* buffer_; - /** - * For a var-sized attribute/dimension, this is the data buffer. It is - * `nullptr` for fixed-sized attributes/dimensions. - */ - void* buffer_var_; - /** - * The size (in bytes) of `buffer_`. Note that this size may be altered by - * a read query to reflect the useful data written in the buffer. - */ - uint64_t* buffer_size_; - /** - * The size (in bytes) of `buffer_var_`. Note that this size may be altered - * by a read query to reflect the useful data written in the buffer. - */ - uint64_t* buffer_var_size_; - /** - * This is the original size (in bytes) of `buffer_` (before - * potentially altered by the query). - */ - uint64_t original_buffer_size_; - /** - * This is the original size (in bytes) of `buffer_var_` (before - * potentially altered by the query). - */ - uint64_t original_buffer_var_size_; - - /** Constructor. */ - QueryBuffer() { - buffer_ = nullptr; - buffer_var_ = nullptr; - buffer_size_ = nullptr; - buffer_var_size_ = nullptr; - original_buffer_size_ = 0; - original_buffer_var_size_ = 0; - } - - /** Constructor. */ - QueryBuffer( - void* buffer, - void* buffer_var, - uint64_t* buffer_size, - uint64_t* buffer_var_size) - : buffer_(buffer) - , buffer_var_(buffer_var) - , buffer_size_(buffer_size) - , buffer_var_size_(buffer_var_size) { - original_buffer_size_ = *buffer_size; - original_buffer_var_size_ = - (buffer_var_size_ != nullptr) ? *buffer_var_size : 0; - } -}; - } // namespace sm } // namespace tiledb diff --git a/tiledb/sm/misc/utils.cc b/tiledb/sm/misc/utils.cc index d57e4cdaaca..2558ce269ac 100644 --- a/tiledb/sm/misc/utils.cc +++ b/tiledb/sm/misc/utils.cc @@ -268,10 +268,18 @@ Status get_timestamp_range( } Status get_fragment_name_version(const std::string& name, uint32_t* version) { - // First check if it is in version 3, which has 5 '_' in the name + // First check if it is version 3 or greater, which has 5 '_' + // characters in the name. size_t n = std::count(name.begin(), name.end(), '_'); if (n == 5) { - *version = 3; + // Fetch the fragment version from the fragment name. If the fragment + // version is greater than or equal to 7, we have a footer version of 4. + // Otherwise, it is version 3. + const int frag_version = std::stoi(name.substr(name.find_last_of('_') + 1)); + if (frag_version >= 7) + *version = 4; + else + *version = 3; return Status::Ok(); } diff --git a/tiledb/sm/query/query.cc b/tiledb/sm/query/query.cc index 5d72552d6d1..faac3e2ed9a 100644 --- a/tiledb/sm/query/query.cc +++ b/tiledb/sm/query/query.cc @@ -408,6 +408,101 @@ Status Query::get_buffer( name, buffer_off, buffer_off_size, buffer_val, buffer_val_size); } +Status Query::get_buffer_vbytemap( + const char* name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + uint8_t** buffer_validity_bytemap, + uint64_t** buffer_validity_bytemap_size) const { + const ValidityVector* vv; + RETURN_NOT_OK(get_buffer( + name, buffer_off, buffer_off_size, buffer_val, buffer_val_size, &vv)); + + *buffer_validity_bytemap = vv->bytemap(); + *buffer_validity_bytemap_size = vv->bytemap_size(); + + return Status::Ok(); +} + +Status Query::get_buffer_vbytemap( + const char* name, + void** buffer, + uint64_t** buffer_size, + uint8_t** buffer_validity_bytemap, + uint64_t** buffer_validity_bytemap_size) const { + const ValidityVector* vv; + RETURN_NOT_OK(get_buffer(name, buffer, buffer_size, &vv)); + + *buffer_validity_bytemap = vv->bytemap(); + *buffer_validity_bytemap_size = vv->bytemap_size(); + + return Status::Ok(); +} + +Status Query::get_buffer( + const char* name, + void** buffer, + uint64_t** buffer_size, + const ValidityVector** validity_vector) const { + // Check nullable attribute + auto array_schema = this->array_schema(); + if (array_schema->attribute(name) == nullptr) + return LOG_STATUS(Status::QueryError( + std::string("Cannot get buffer; Invalid attribute name '") + name + + "'")); + if (array_schema->var_size(name)) + return LOG_STATUS(Status::QueryError( + std::string("Cannot get buffer; '") + name + "' is var-sized")); + if (!array_schema->is_nullable(name)) + return LOG_STATUS(Status::QueryError( + std::string("Cannot get buffer; '") + name + "' is non-nullable")); + + if (type_ == QueryType::WRITE) + return writer_.get_buffer_nullable( + name, buffer, buffer_size, validity_vector); + return reader_.get_buffer_nullable( + name, buffer, buffer_size, validity_vector); +} + +Status Query::get_buffer( + const char* name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + const ValidityVector** validity_vector) const { + // Check attribute + auto array_schema = this->array_schema(); + if (array_schema->attribute(name) == nullptr) + return LOG_STATUS(Status::QueryError( + std::string("Cannot get buffer; Invalid attribute name '") + name + + "'")); + if (!array_schema->var_size(name)) + return LOG_STATUS(Status::QueryError( + std::string("Cannot get buffer; '") + name + "' is fixed-sized")); + if (!array_schema->is_nullable(name)) + return LOG_STATUS(Status::QueryError( + std::string("Cannot get buffer; '") + name + "' is non-nullable")); + + if (type_ == QueryType::WRITE) + return writer_.get_buffer_nullable( + name, + buffer_off, + buffer_off_size, + buffer_val, + buffer_val_size, + validity_vector); + return reader_.get_buffer_nullable( + name, + buffer_off, + buffer_off_size, + buffer_val, + buffer_val_size, + validity_vector); +} + Status Query::get_attr_serialization_state( const std::string& attribute, SerializationState::AttrState** state) { *state = &serialization_state_.attribute_states[attribute]; @@ -576,11 +671,7 @@ Status Query::disable_check_global_order() { return Status::Ok(); } -Status Query::set_buffer( - const std::string& name, - void* buffer, - uint64_t* buffer_size, - bool check_null_buffers) { +Status Query::check_set_fixed_buffer(const std::string& name) { if (name == constants::coords && !array_->array_schema()->domain()->all_dims_same_type()) return LOG_STATUS(Status::QueryError( @@ -593,6 +684,16 @@ Status Query::set_buffer( "Cannot set buffer; Setting a buffer for zipped coordinates is not " "applicable to domains with variable-sized dimensions")); + return Status::Ok(); +} + +Status Query::set_buffer( + const std::string& name, + void* const buffer, + uint64_t* const buffer_size, + const bool check_null_buffers) { + RETURN_NOT_OK(check_set_fixed_buffer(name)); + if (type_ == QueryType::WRITE) return writer_.set_buffer(name, buffer, buffer_size); return reader_.set_buffer(name, buffer, buffer_size, check_null_buffers); @@ -600,11 +701,11 @@ Status Query::set_buffer( Status Query::set_buffer( const std::string& name, - uint64_t* buffer_off, - uint64_t* buffer_off_size, - void* buffer_val, - uint64_t* buffer_val_size, - bool check_null_buffers) { + uint64_t* const buffer_off, + uint64_t* const buffer_off_size, + void* const buffer_val, + uint64_t* const buffer_val_size, + const bool check_null_buffers) { if (type_ == QueryType::WRITE) return writer_.set_buffer( name, buffer_off, buffer_off_size, buffer_val, buffer_val_size); @@ -617,6 +718,91 @@ Status Query::set_buffer( check_null_buffers); } +Status Query::set_buffer_vbytemap( + const std::string& name, + void* const buffer, + uint64_t* const buffer_size, + uint8_t* const buffer_validity_bytemap, + uint64_t* const buffer_validity_bytemap_size, + const bool check_null_buffers) { + // Convert the bytemap into a ValidityVector. + ValidityVector vv; + RETURN_NOT_OK( + vv.init_bytemap(buffer_validity_bytemap, buffer_validity_bytemap_size)); + + return set_buffer( + name, buffer, buffer_size, std::move(vv), check_null_buffers); +} + +Status Query::set_buffer_vbytemap( + const std::string& name, + uint64_t* const buffer_off, + uint64_t* const buffer_off_size, + void* const buffer_val, + uint64_t* const buffer_val_size, + uint8_t* const buffer_validity_bytemap, + uint64_t* const buffer_validity_bytemap_size, + const bool check_null_buffers) { + // Convert the bytemap into a ValidityVector. + ValidityVector vv; + RETURN_NOT_OK( + vv.init_bytemap(buffer_validity_bytemap, buffer_validity_bytemap_size)); + + return set_buffer( + name, + buffer_off, + buffer_off_size, + buffer_val, + buffer_val_size, + std::move(vv), + check_null_buffers); +} + +Status Query::set_buffer( + const std::string& name, + void* const buffer, + uint64_t* const buffer_size, + ValidityVector&& validity_vector, + const bool check_null_buffers) { + RETURN_NOT_OK(check_set_fixed_buffer(name)); + + if (type_ == QueryType::WRITE) + return writer_.set_buffer( + name, buffer, buffer_size, std::move(validity_vector)); + return reader_.set_buffer( + name, + buffer, + buffer_size, + std::move(validity_vector), + check_null_buffers); +} + +Status Query::set_buffer( + const std::string& name, + uint64_t* const buffer_off, + uint64_t* const buffer_off_size, + void* const buffer_val, + uint64_t* const buffer_val_size, + ValidityVector&& validity_vector, + const bool check_null_buffers) { + if (type_ == QueryType::WRITE) + return writer_.set_buffer( + name, + buffer_off, + buffer_off_size, + buffer_val, + buffer_val_size, + std::move(validity_vector)); + return reader_.set_buffer( + name, + buffer_off, + buffer_off_size, + buffer_val, + buffer_val_size, + std::move(validity_vector), + check_null_buffers); +} + Status Query::set_est_result_size( std::unordered_map& est_result_size, std::unordered_map& max_mem_size) { diff --git a/tiledb/sm/query/query.h b/tiledb/sm/query/query.h index 80532c5598d..214dd59b61e 100644 --- a/tiledb/sm/query/query.h +++ b/tiledb/sm/query/query.h @@ -45,6 +45,7 @@ #include "tiledb/sm/array_schema/domain.h" #include "tiledb/sm/misc/utils.h" #include "tiledb/sm/query/reader.h" +#include "tiledb/sm/query/validity_vector.h" #include "tiledb/sm/query/writer.h" using namespace tiledb::common; @@ -286,6 +287,47 @@ class Query { void** buffer_val, uint64_t** buffer_val_size) const; + /** + * Retrieves the buffer and validity bytemap of a fixed-sized, nullable + * attribute. + * + * @param name The buffer attribute name. An empty string means + * the special default attribute. + * @param buffer The buffer to be retrieved. + * @param buffer_size A pointer to the buffer size to be retrieved. + * @param buffer The buffer to be retrieved. + * @param buffer_size A pointer to the buffer size to be retrieved. + * @return Status + */ + Status get_buffer_vbytemap( + const char* name, + void** buffer, + uint64_t** buffer_size, + uint8_t** buffer_validity_bytemap, + uint64_t** buffer_validity_bytemap_size) const; + + /** + * Retrieves the offsets, values, and validity bytemap buffers of + * a var-sized, nullable attribute. + * + * @param name The attribute name. An empty string means + * the special default attribute. + * @param buffer_off The offsets buffer to be retrieved. + * @param buffer_off_size A pointer to the offsets buffer size to be + * retrieved. + * @param buffer_val The values buffer to be retrieved. + * @param buffer_val_size A pointer to the values buffer size to be retrieved. + * @return Status + */ + Status get_buffer_vbytemap( + const char* name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + uint8_t** buffer_validity_bytemap, + uint64_t** buffer_validity_bytemap_size) const; + /** * Returns the serialization state associated with the given attribute. * @@ -398,6 +440,78 @@ class Query { uint64_t* buffer_val_size, bool check_null_buffers = true); + /** + * Sets the buffer for a fixed-sized, nullable attribute with a validity + * bytemap. + * + * @param name The attribute to set the buffer for. + * @param buffer The buffer that either have the input data to be written, + * or will hold the data to be read. + * @param buffer_size In the case of writes, this is the size of `buffer` + * in bytes. In the case of reads, this initially contains the allocated + * size of `buffer`, but after the termination of the query + * it will contain the size of the useful (read) data in `buffer`. + * @param buffer_validity_bytemap The buffer that either have the validity + * bytemap associated with the input data to be written, or will hold the + * validity bytemap to be read. + * @param buffer_validity_bytemap_size In the case of writes, this is the size + * of `buffer_validity_bytemap` in bytes. In the case of reads, this initially + * contains the allocated size of `buffer_validity_bytemap`, but after the + * termination of the query it will contain the size of the useful (read) + * data in `buffer_validity_bytemap`. + * @param check_null_buffers If true (default), null buffers are not allowed. + * @return Status + */ + Status set_buffer_vbytemap( + const std::string& name, + void* buffer, + uint64_t* buffer_size, + uint8_t* buffer_validity_bytemap, + uint64_t* buffer_validity_bytemap_size, + bool check_null_buffers = true); + + /** + * Sets the buffer for a var-sized, nullable attribute with a validity + * bytemap. + * + * @param name The attribute to set the buffer for. + * @param buffer_off The buffer that either have the input data to be written, + * or will hold the data to be read. This buffer holds the starting + * offsets of each cell value in `buffer_val`. + * @param buffer_off_size In the case of writes, it is the size of + * `buffer_off` in bytes. In the case of reads, this initially contains + * the allocated size of `buffer_off`, but after the termination of the + * function it will contain the size of the useful (read) data in + * `buffer_off`. + * @param buffer_val The buffer that either have the input data to be written, + * or will hold the data to be read. This buffer holds the actual + * var-sized cell values. + * @param buffer_val_size In the case of writes, it is the size of + * `buffer_val` in bytes. In the case of reads, this initially contains + * the allocated size of `buffer_val`, but after the termination of the + * query it will contain the size of the useful (read) data in + * `buffer_val`. + * @param buffer_validity_bytemap The buffer that either have the validity + * bytemap associated with the input data to be written, or will hold the + * validity bytemap to be read. + * @param buffer_validity_bytemap_size In the case of writes, this is the size + * of `buffer_validity_bytemap` in bytes. In the case of reads, this initially + * contains the allocated size of `buffer_validity_bytemap`, but after the + * termination of the query it will contain the size of the useful (read) + * data in `buffer_validity_bytemap`. + * @param check_null_buffers If true (default), null buffers are not allowed. + * @return Status + */ + Status set_buffer_vbytemap( + const std::string& name, + uint64_t* buffer_off, + uint64_t* buffer_off_size, + void* buffer_val, + uint64_t* buffer_val_size, + uint8_t* buffer_validity_bytemap, + uint64_t* buffer_validity_bytemap_size, + bool check_null_buffers = true); + /** * Used by serialization to set the estimated result size * @@ -507,6 +621,54 @@ class Query { /* ********************************* */ /* PRIVATE METHODS */ /* ********************************* */ + + Status check_set_fixed_buffer(const std::string& name); + + /** + * Internal routine for setting fixed-sized, nullable attribute buffers with + * a ValidityVector. + */ + Status set_buffer( + const std::string& name, + void* buffer, + uint64_t* buffer_size, + ValidityVector&& validity_vector, + bool check_null_buffers = true); + + /** + * Internal routine for setting var-sized, nullable attribute buffers with + * a ValidityVector. + */ + Status set_buffer( + const std::string& name, + uint64_t* buffer_off, + uint64_t* buffer_off_size, + void* buffer_val, + uint64_t* buffer_val_size, + ValidityVector&& validity_vector, + bool check_null_buffers = true); + + /** + * Internal routine for getting fixed-sized, nullable attribute buffers with + * a ValidityVector. + */ + Status get_buffer( + const char* name, + void** buffer, + uint64_t** buffer_size, + const ValidityVector** validity_vector) const; + + /** + * Internal routine for getting fixed-sized, nullable attribute buffers with + * a ValidityVector. + */ + Status get_buffer( + const char* name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + const ValidityVector** validity_vector) const; }; } // namespace sm diff --git a/tiledb/sm/query/query_buffer.h b/tiledb/sm/query/query_buffer.h new file mode 100644 index 00000000000..753f2e35773 --- /dev/null +++ b/tiledb/sm/query/query_buffer.h @@ -0,0 +1,209 @@ +/** + * @file query_buffer.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2020 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 class QueryBuffer. + */ + +#ifndef TILEDB_QUERY_BUFFER_H +#define TILEDB_QUERY_BUFFER_H + +#include "tiledb/common/logger.h" +#include "tiledb/common/status.h" +#include "tiledb/sm/misc/macros.h" +#include "tiledb/sm/query/validity_vector.h" + +using namespace tiledb::common; + +namespace tiledb { +namespace sm { + +/** Contains the buffer(s) and buffer size(s) for some attribute/dimension. */ +class QueryBuffer { + public: + /* ********************************* */ + /* CONSTRUCTORS & DESTRUCTORS */ + /* ********************************* */ + + /** Default constructor. */ + QueryBuffer() + : buffer_(nullptr) + , buffer_var_(nullptr) + , buffer_size_(nullptr) + , buffer_var_size_(nullptr) + , original_buffer_size_(0) + , original_buffer_var_size_(0) + , original_validity_vector_size_(0) { + } + + /** Value Constructor. */ + QueryBuffer( + void* const buffer, + void* const buffer_var, + uint64_t* const buffer_size, + uint64_t* const buffer_var_size) + : buffer_(buffer) + , buffer_var_(buffer_var) + , buffer_size_(buffer_size) + , buffer_var_size_(buffer_var_size) { + original_buffer_size_ = *buffer_size; + original_buffer_var_size_ = + (buffer_var_size_ != nullptr) ? *buffer_var_size_ : 0; + original_validity_vector_size_ = 0; + } + + /** Value Constructor. */ + QueryBuffer( + void* const buffer, + void* const buffer_var, + uint64_t* const buffer_size, + uint64_t* const buffer_var_size, + ValidityVector&& validity_vector) + : buffer_(buffer) + , buffer_var_(buffer_var) + , buffer_size_(buffer_size) + , buffer_var_size_(buffer_var_size) + , validity_vector_(std::move(validity_vector)) { + original_buffer_size_ = *buffer_size; + original_buffer_var_size_ = + (buffer_var_size_ != nullptr) ? *buffer_var_size_ : 0; + original_validity_vector_size_ = + (validity_vector_.buffer_size() != nullptr) ? + *validity_vector_.buffer_size() : + 0; + } + + /** Destructor. */ + ~QueryBuffer() = default; + + /** Copy constructor. */ + QueryBuffer(const QueryBuffer& rhs) + : buffer_(rhs.buffer_) + , buffer_var_(rhs.buffer_var_) + , buffer_size_(rhs.buffer_size_) + , buffer_var_size_(rhs.buffer_var_size_) + , original_buffer_size_(rhs.original_buffer_size_) + , original_buffer_var_size_(rhs.original_buffer_var_size_) + , original_validity_vector_size_(rhs.original_validity_vector_size_) + , validity_vector_(rhs.validity_vector_) { + } + + /** Move constructor. */ + QueryBuffer(QueryBuffer&& rhs) + : buffer_(rhs.buffer_) + , buffer_var_(rhs.buffer_var_) + , buffer_size_(rhs.buffer_size_) + , buffer_var_size_(rhs.buffer_var_size_) + , original_buffer_size_(rhs.original_buffer_size_) + , original_buffer_var_size_(rhs.original_buffer_var_size_) + , original_validity_vector_size_(rhs.original_validity_vector_size_) + , validity_vector_(std::move(rhs.validity_vector_)) { + } + + /* ********************************* */ + /* OPERATORS */ + /* ********************************* */ + + /** Move-assignment Operator. */ + QueryBuffer& operator=(QueryBuffer&& rhs) { + if (&rhs == this) + return *this; + + std::swap(buffer_, rhs.buffer_); + std::swap(buffer_var_, rhs.buffer_var_); + std::swap(buffer_size_, rhs.buffer_size_); + std::swap(buffer_var_size_, rhs.buffer_var_size_); + std::swap(original_buffer_size_, rhs.original_buffer_size_); + std::swap(original_buffer_var_size_, rhs.original_buffer_var_size_); + std::swap( + original_validity_vector_size_, rhs.original_validity_vector_size_); + validity_vector_ = std::move(rhs.validity_vector_); + + return *this; + } + + DISABLE_COPY_ASSIGN(QueryBuffer); + + /* ********************************* */ + /* PUBLIC ATTRIBUTES */ + /* ********************************* */ + + /** + * The attribute/dimension buffer. In case the attribute/dimension is + * var-sized, this is the offsets buffer. + */ + void* buffer_; + + /** + * For a var-sized attribute/dimension, this is the data buffer. It is + * `nullptr` for fixed-sized attributes/dimensions. + */ + void* buffer_var_; + + /** + * The size (in bytes) of `buffer_`. Note that this size may be altered by + * a read query to reflect the useful data written in the buffer. + */ + uint64_t* buffer_size_; + + /** + * The size (in bytes) of `buffer_var_`. Note that this size may be altered + * by a read query to reflect the useful data written in the buffer. + */ + uint64_t* buffer_var_size_; + + /** + * This is the original size (in bytes) of `buffer_` (before + * potentially altered by the query). + */ + uint64_t original_buffer_size_; + + /** + * This is the original size (in bytes) of `buffer_var_` (before + * potentially altered by the query). + */ + uint64_t original_buffer_var_size_; + + /** + * This is the original size (in bytes) of `validity_vector_.buffer()` (before + * potentially altered by the query). + */ + uint64_t original_validity_vector_size_; + + /** + * The validity vector, which wraps a uint8_t* bytemap buffer and a + * uint64_t* bytemap buffer size. These will be null for non-nullable + * attributes. + */ + ValidityVector validity_vector_; +}; + +} // namespace sm +} // namespace tiledb + +#endif // TILEDB_QUERY_BUFFER_H diff --git a/tiledb/sm/query/reader.cc b/tiledb/sm/query/reader.cc index 4a906dd7c46..c4e62e72c31 100644 --- a/tiledb/sm/query/reader.cc +++ b/tiledb/sm/query/reader.cc @@ -208,6 +208,51 @@ Status Reader::get_buffer( *buffer_val = it->second.buffer_var_; *buffer_val_size = it->second.buffer_var_size_; } + + return Status::Ok(); +} + +Status Reader::get_buffer_nullable( + const std::string& name, + void** buffer, + uint64_t** buffer_size, + const ValidityVector** valdity_vector) const { + auto it = buffers_.find(name); + if (it == buffers_.end()) { + *buffer = nullptr; + *buffer_size = nullptr; + *valdity_vector = nullptr; + } else { + *buffer = it->second.buffer_; + *buffer_size = it->second.buffer_size_; + *valdity_vector = &it->second.validity_vector_; + } + + return Status::Ok(); +} + +Status Reader::get_buffer_nullable( + const std::string& name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + const ValidityVector** valdity_vector) const { + auto it = buffers_.find(name); + if (it == buffers_.end()) { + *buffer_off = nullptr; + *buffer_off_size = nullptr; + *buffer_val = nullptr; + *buffer_val_size = nullptr; + *valdity_vector = nullptr; + } else { + *buffer_off = (uint64_t*)it->second.buffer_; + *buffer_off_size = it->second.buffer_size_; + *buffer_val = it->second.buffer_var_; + *buffer_val_size = it->second.buffer_var_size_; + *valdity_vector = &it->second.validity_vector_; + } + return Status::Ok(); } @@ -232,13 +277,7 @@ Status Reader::init(const Layout& layout) { // Check subarray RETURN_NOT_OK(check_subarray()); - // Get configuration parameters - const char *memory_budget, *memory_budget_var; - auto config = storage_manager_->config(); - RETURN_NOT_OK(config.get("sm.memory_budget", &memory_budget)); - RETURN_NOT_OK(config.get("sm.memory_budget_var", &memory_budget_var)); - RETURN_NOT_OK(utils::parse::convert(memory_budget, &memory_budget_)); - RETURN_NOT_OK(utils::parse::convert(memory_budget_var, &memory_budget_var_)); + // Initialize the read state RETURN_NOT_OK(init_read_state()); return Status::Ok(); @@ -346,9 +385,9 @@ void Reader::set_array_schema(const ArraySchema* array_schema) { Status Reader::set_buffer( const std::string& name, - void* buffer, - uint64_t* buffer_size, - bool check_null_buffers) { + void* const buffer, + uint64_t* const buffer_size, + const bool check_null_buffers) { // Check buffer if (check_null_buffers && (buffer == nullptr || buffer_size == nullptr)) return LOG_STATUS(Status::ReaderError( @@ -360,8 +399,8 @@ Status Reader::set_buffer( Status::ReaderError("Cannot set buffer; Array schema not set")); // For easy reference - bool is_dim = array_schema_->is_dim(name); - bool is_attr = array_schema_->is_attr(name); + const bool is_dim = array_schema_->is_dim(name); + const bool is_attr = array_schema_->is_attr(name); // Check that attribute/dimension exists if (name != constants::coords && !is_dim && !is_attr) @@ -369,8 +408,15 @@ Status Reader::set_buffer( std::string("Cannot set buffer; Invalid attribute/dimension '") + name + "'")); + // Must not be nullable + if (array_schema_->is_nullable(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute/dimension '") + name + + "' is nullable")); + // Check that attribute/dimension is fixed-sized - bool var_size = (name != constants::coords && array_schema_->var_size(name)); + const bool var_size = + (name != constants::coords && array_schema_->var_size(name)); if (var_size) return LOG_STATUS(Status::ReaderError( std::string("Cannot set buffer; Input attribute/dimension '") + name + @@ -384,7 +430,7 @@ Status Reader::set_buffer( "a zipped coordinate buffer in the same query"))); // Error if setting a new attribute/dimension after initialization - bool exists = buffers_.find(name) != buffers_.end(); + const bool exists = buffers_.find(name) != buffers_.end(); if (read_state_.initialized_ && !exists) return LOG_STATUS(Status::ReaderError( std::string("Cannot set buffer for new attribute/dimension '") + name + @@ -398,11 +444,11 @@ Status Reader::set_buffer( Status Reader::set_buffer( const std::string& name, - uint64_t* buffer_off, - uint64_t* buffer_off_size, - void* buffer_val, - uint64_t* buffer_val_size, - bool check_null_buffers) { + uint64_t* const buffer_off, + uint64_t* const buffer_off_size, + void* const buffer_val, + uint64_t* const buffer_val_size, + const bool check_null_buffers) { // Check buffer if (check_null_buffers && (buffer_off == nullptr || buffer_off_size == nullptr || @@ -416,8 +462,8 @@ Status Reader::set_buffer( Status::ReaderError("Cannot set buffer; Array schema not set")); // For easy reference - bool is_dim = array_schema_->is_dim(name); - bool is_attr = array_schema_->is_attr(name); + const bool is_dim = array_schema_->is_dim(name); + const bool is_attr = array_schema_->is_attr(name); // Check that attribute/dimension exists if (!is_dim && !is_attr) @@ -425,6 +471,12 @@ Status Reader::set_buffer( std::string("Cannot set buffer; Invalid attribute/dimension '") + name + "'")); + // Must not be nullable + if (array_schema_->is_nullable(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute/dimension '") + name + + "' is nullable")); + // Check that attribute/dimension is var-sized if (!array_schema_->var_size(name)) return LOG_STATUS(Status::ReaderError( @@ -432,7 +484,7 @@ Status Reader::set_buffer( "' is fixed-sized")); // Error if setting a new attribute/dimension after initialization - bool exists = buffers_.find(name) != buffers_.end(); + const bool exists = buffers_.find(name) != buffers_.end(); if (read_state_.initialized_ && !exists) return LOG_STATUS(Status::ReaderError( std::string("Cannot set buffer for new attribute/dimension '") + name + @@ -445,6 +497,110 @@ Status Reader::set_buffer( return Status::Ok(); } +Status Reader::set_buffer( + const std::string& name, + void* const buffer, + uint64_t* const buffer_size, + ValidityVector&& validity_vector, + const bool check_null_buffers) { + // Check buffer + if (check_null_buffers && (buffer == nullptr || buffer_size == nullptr)) + return LOG_STATUS(Status::ReaderError( + "Cannot set buffer; Buffer or buffer size is null")); + + // Array schema must exist + if (array_schema_ == nullptr) + return LOG_STATUS( + Status::ReaderError("Cannot set buffer; Array schema not set")); + + // Must be an attribute + if (!array_schema_->is_attr(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Buffer name '") + name + + "' is not an attribute")); + + // Must be fixed-size + if (array_schema_->var_size(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute '") + name + + "' is var-sized")); + + // Must be nullable + if (!array_schema_->is_nullable(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute '") + name + + "' is not nullable")); + + // Error if setting a new attribute/dimension after initialization + const bool exists = buffers_.find(name) != buffers_.end(); + if (read_state_.initialized_ && !exists) + return LOG_STATUS(Status::ReaderError( + std::string("Cannot set buffer for new attribute '") + name + + "' after initialization")); + + // Set attribute buffer + buffers_[name] = QueryBuffer( + buffer, nullptr, buffer_size, nullptr, std::move(validity_vector)); + + return Status::Ok(); +} + +Status Reader::set_buffer( + const std::string& name, + uint64_t* const buffer_off, + uint64_t* const buffer_off_size, + void* const buffer_val, + uint64_t* const buffer_val_size, + ValidityVector&& validity_vector, + const bool check_null_buffers) { + // Check buffer + if (check_null_buffers && + (buffer_off == nullptr || buffer_off_size == nullptr || + buffer_val == nullptr || buffer_val_size == nullptr)) + return LOG_STATUS(Status::ReaderError( + "Cannot set buffer; Buffer or buffer size is null")); + + // Array schema must exist + if (array_schema_ == nullptr) + return LOG_STATUS( + Status::ReaderError("Cannot set buffer; Array schema not set")); + + // Must be an attribute + if (!array_schema_->is_attr(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Buffer name '") + name + + "' is not an attribute")); + + // Must be fixed-size + if (!array_schema_->var_size(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute '") + name + + "' is fixed-sized")); + + // Must be nullable + if (!array_schema_->is_nullable(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute '") + name + + "' is not nullable")); + + // Error if setting a new attribute after initialization + const bool exists = buffers_.find(name) != buffers_.end(); + if (read_state_.initialized_ && !exists) + return LOG_STATUS(Status::ReaderError( + std::string("Cannot set buffer for new attribute '") + name + + "' after initialization")); + + // Set attribute/dimension buffer + buffers_[name] = QueryBuffer( + buffer_off, + buffer_val, + buffer_off_size, + buffer_val_size, + std::move(validity_vector)); + + return Status::Ok(); +} + void Reader::set_fragment_metadata( const std::vector& fragment_metadata) { fragment_metadata_ = fragment_metadata; @@ -984,6 +1140,12 @@ Status Reader::copy_fixed_cells( // Update buffer offsets *(buffers_[name].buffer_size_) = buffer_offset; + if (array_schema_->is_nullable(name)) { + const uint64_t attr_datatype_size = + datatype_size(array_schema_->type(name)); + *(buffers_[name].validity_vector_.buffer_size()) = + (buffer_offset / attr_datatype_size) * constants::cell_validity_size; + } return Status::Ok(); @@ -1016,8 +1178,10 @@ Status Reader::copy_partitioned_fixed_cells( assert(result_cell_slabs); // For easy reference. + auto nullable = array_schema_->is_nullable(*name); auto it = buffers_.find(*name); auto buffer = (unsigned char*)it->second.buffer_; + auto buffer_validity = (unsigned char*)it->second.validity_vector_.buffer(); auto cell_size = array_schema_->cell_size(*name); ByteVecValue fill_value; if (array_schema_->is_attr(*name)) @@ -1040,17 +1204,35 @@ Status Reader::copy_partitioned_fixed_cells( auto fill_num = bytes_to_copy / fill_value_size; for (uint64_t j = 0; j < fill_num; ++j) { std::memcpy(buffer + offset, fill_value.data(), fill_value_size); + if (nullable) { + const uint64_t attr_datatype_size = + datatype_size(array_schema_->type(*name)); + std::memset( + buffer_validity + + (offset / attr_datatype_size * constants::cell_validity_size), + 0, + fill_value_size / attr_datatype_size * + constants::cell_validity_size); + } offset += fill_value_size; } } else { // Non-empty range if (stride == UINT64_MAX) { - RETURN_NOT_OK( - cs.tile_->read(*name, buffer + offset, cs.start_, cs.length_)); + if (!nullable) + RETURN_NOT_OK( + cs.tile_->read(*name, buffer, offset, cs.start_, cs.length_)); + else + RETURN_NOT_OK(cs.tile_->read_nullable( + *name, buffer, offset, cs.start_, cs.length_, buffer_validity)); } else { auto cell_offset = offset; auto start = cs.start_; for (uint64_t j = 0; j < cs.length_; ++j) { - RETURN_NOT_OK(cs.tile_->read(*name, buffer + cell_offset, start, 1)); + if (!nullable) + RETURN_NOT_OK(cs.tile_->read(*name, buffer, cell_offset, start, 1)); + else + RETURN_NOT_OK(cs.tile_->read_nullable( + *name, buffer, cell_offset, start, 1, buffer_validity)); cell_offset += cell_size; start += stride; } @@ -1083,7 +1265,7 @@ Status Reader::copy_var_cells( populate_cvc_ctx_cache(result_cell_slabs, ctx_cache); // Compute the destinations of offsets and var-len data in the buffers. - uint64_t total_offset_size, total_var_size; + uint64_t total_offset_size, total_var_size, total_validity_size; std::unique_ptr> offset_offsets_per_cs = ctx_cache->get_offset_offsets_per_cs(); std::unique_ptr> var_offsets_per_cs = @@ -1095,14 +1277,17 @@ Status Reader::copy_var_cells( offset_offsets_per_cs.get(), var_offsets_per_cs.get(), &total_offset_size, - &total_var_size)); + &total_var_size, + &total_validity_size)); auto it = buffers_.find(name); auto buffer_size = it->second.buffer_size_; auto buffer_var_size = it->second.buffer_var_size_; + auto buffer_validity_size = it->second.validity_vector_.buffer_size(); // Check for overflow and return early (without copying) in that case. - if (total_offset_size > *buffer_size || total_var_size > *buffer_var_size) { + if (total_offset_size > *buffer_size || total_var_size > *buffer_var_size || + (buffer_validity_size && total_validity_size > *buffer_validity_size)) { read_state_.overflowed_ = true; return Status::Ok(); } @@ -1131,6 +1316,8 @@ Status Reader::copy_var_cells( // Update buffer offsets *(buffers_[name].buffer_size_) = total_offset_size; *(buffers_[name].buffer_var_size_) = total_var_size; + if (array_schema_->is_nullable(name)) + *(buffers_[name].validity_vector_.buffer_size()) = total_validity_size; return Status::Ok(); @@ -1159,18 +1346,22 @@ Status Reader::compute_var_cell_destinations( std::vector* offset_offsets_per_cs, std::vector* var_offsets_per_cs, uint64_t* total_offset_size, - uint64_t* total_var_size) const { + uint64_t* total_var_size, + uint64_t* total_validity_size) const { // For easy reference + auto nullable = array_schema_->is_nullable(name); auto num_cs = result_cell_slabs.size(); auto offset_size = constants::cell_var_offset_size; ByteVecValue fill_value; if (array_schema_->is_attr(name)) fill_value = array_schema_->attribute(name)->fill_value(); auto fill_value_size = (uint64_t)fill_value.size(); + auto attr_datatype_size = datatype_size(array_schema_->type(name)); // Compute the destinations for all result cell slabs *total_offset_size = 0; *total_var_size = 0; + *total_validity_size = 0; size_t total_cs_length = 0; for (uint64_t cs_idx = 0; cs_idx < num_cs; cs_idx++) { const auto& cs = result_cell_slabs[cs_idx]; @@ -1180,9 +1371,9 @@ Status Reader::compute_var_cell_destinations( uint64_t tile_cell_num = 0; uint64_t tile_var_size = 0; if (cs.tile_ != nullptr) { - const auto tile_pair = cs.tile_->tile_pair(name); - const auto& tile = tile_pair->first; - const auto& tile_var = tile_pair->second; + const auto tile_tuple = cs.tile_->tile_tuple(name); + const auto& tile = std::get<0>(*tile_tuple); + const auto& tile_var = std::get<1>(*tile_tuple); // Get the internal buffer to the offset values. ChunkedBuffer* const chunked_buffer = tile.chunked_buffer(); @@ -1219,6 +1410,9 @@ Status Reader::compute_var_cell_destinations( (*var_offsets_per_cs)[total_cs_length + dest_vec_idx] = *total_var_size; *total_offset_size += offset_size; *total_var_size += cell_var_size; + if (nullable) + *total_validity_size += + cell_var_size / attr_datatype_size * constants::cell_validity_size; } total_cs_length += cs.length_; @@ -1239,13 +1433,16 @@ Status Reader::copy_partitioned_var_cells( assert(result_cell_slabs); auto it = buffers_.find(*name); + auto nullable = array_schema_->is_nullable(*name); auto buffer = (unsigned char*)it->second.buffer_; auto buffer_var = (unsigned char*)it->second.buffer_var_; + auto buffer_validity = (unsigned char*)it->second.validity_vector_.buffer(); uint64_t offset_size = constants::cell_var_offset_size; ByteVecValue fill_value; if (array_schema_->is_attr(*name)) fill_value = array_schema_->attribute(*name)->fill_value(); auto fill_value_size = (uint64_t)fill_value.size(); + auto attr_datatype_size = datatype_size(array_schema_->type(*name)); // Fetch the starting array offset into both `offset_offsets_per_cs` // and `var_offsets_per_cs`. @@ -1265,11 +1462,14 @@ Status Reader::copy_partitioned_var_cells( // Get tile information, if the range is nonempty. uint64_t* tile_offsets = nullptr; Tile* tile_var = nullptr; + Tile* tile_validity = nullptr; uint64_t tile_cell_num = 0; if (cs.tile_ != nullptr) { - const auto tile_pair = cs.tile_->tile_pair(*name); - Tile* const tile = &tile_pair->first; - tile_var = &tile_pair->second; + const auto tile_tuple = cs.tile_->tile_tuple(*name); + Tile* const tile = &std::get<0>(*tile_tuple); + tile_var = &std::get<1>(*tile_tuple); + tile_validity = &std::get<2>(*tile_tuple); + // Get the internal buffer to the offset values. ChunkedBuffer* const chunked_buffer = tile->chunked_buffer(); @@ -1292,6 +1492,7 @@ Status Reader::copy_partitioned_var_cells( // offset_offsets[dest_vec_idx]; auto var_offset = (*var_offsets_per_cs)[arr_offset + dest_vec_idx]; auto var_dest = buffer_var + var_offset; + auto validity_dest = buffer_validity + (var_offset / attr_datatype_size); // Copy offset std::memcpy(offset_dest, &var_offset, offset_size); @@ -1299,6 +1500,12 @@ Status Reader::copy_partitioned_var_cells( // Copy variable-sized value if (cs.tile_ == nullptr) { std::memcpy(var_dest, fill_value.data(), fill_value_size); + if (nullable) + std::memset( + validity_dest, + 0, + fill_value_size / attr_datatype_size * + constants::cell_validity_size); } else { const uint64_t cell_var_size = (cell_idx != tile_cell_num - 1) ? @@ -1306,7 +1513,14 @@ Status Reader::copy_partitioned_var_cells( tile_var->size() - (tile_offsets[cell_idx] - tile_offsets[0]); const uint64_t tile_var_offset = tile_offsets[cell_idx] - tile_offsets[0]; + RETURN_NOT_OK(tile_var->read(var_dest, cell_var_size, tile_var_offset)); + + if (nullable) + RETURN_NOT_OK(tile_validity->read( + validity_dest, + cell_var_size / attr_datatype_size, + tile_var_offset / attr_datatype_size)); } } @@ -1421,10 +1635,10 @@ Status Reader::compute_result_cell_slabs_row_col( if (result_cell_slab.tile_ != nullptr) { auto frag_idx = result_cell_slab.tile_->frag_idx(); auto tile_idx = result_cell_slab.tile_->tile_idx(); - auto frag_tile_pair = std::pair(frag_idx, tile_idx); - auto it = frag_tile_set->find(frag_tile_pair); + auto frag_tile_tuple = std::pair(frag_idx, tile_idx); + auto it = frag_tile_set->find(frag_tile_tuple); if (it == frag_tile_set->end()) { - frag_tile_set->insert(frag_tile_pair); + frag_tile_set->insert(frag_tile_tuple); result_tiles->push_back(result_cell_slab.tile_); } } @@ -1473,8 +1687,8 @@ Status Reader::compute_result_coords( STATS_START_TIMER(stats::Stats::TimerType::READ_COMPUTE_RESULT_COORDS) // Get overlapping tile indexes - typedef std::pair FragTilePair; - std::map result_tile_map; + typedef std::pair FragTileTuple; + std::map result_tile_map; std::vector single_fragment; RETURN_CANCEL_OR_ERROR(compute_sparse_result_tiles( @@ -1546,6 +1760,7 @@ Status Reader::compute_result_coords( // will be corrected in a retry. uint64_t sub_partitioner_memory_budget = cfg_sub_memory_budget; uint64_t sub_partitioner_memory_budget_var = cfg_sub_memory_budget; + uint64_t sub_partitioner_memory_budget_validity = cfg_sub_memory_budget; // Create a sub-partitioner to partition the current subarray in // `read_state_.partitioner_`. This allows us to compute the range @@ -1557,7 +1772,8 @@ Status Reader::compute_result_coords( SubarrayPartitioner sub_partitioner( partitioner->current(), sub_partitioner_memory_budget, - sub_partitioner_memory_budget, + sub_partitioner_memory_budget_var, + sub_partitioner_memory_budget_validity, storage_manager_->compute_tp()); // Set the individual attribute budgets in the sub-partitioner @@ -1566,13 +1782,28 @@ Status Reader::compute_result_coords( const std::string& attr_name = kv.first; const SubarrayPartitioner::ResultBudget& result_budget = kv.second; if (!array_schema_->var_size(attr_name)) { - RETURN_NOT_OK(sub_partitioner.set_result_budget( - attr_name.c_str(), result_budget.size_fixed_)); + if (!array_schema_->is_nullable(attr_name)) { + RETURN_NOT_OK(sub_partitioner.set_result_budget( + attr_name.c_str(), result_budget.size_fixed_)); + } else { + RETURN_NOT_OK(sub_partitioner.set_result_budget_nullable( + attr_name.c_str(), + result_budget.size_fixed_, + result_budget.size_validity_)); + } } else { - RETURN_NOT_OK(sub_partitioner.set_result_budget( - attr_name.c_str(), - result_budget.size_fixed_, - result_budget.size_var_)); + if (!array_schema_->is_nullable(attr_name)) { + RETURN_NOT_OK(sub_partitioner.set_result_budget( + attr_name.c_str(), + result_budget.size_fixed_, + result_budget.size_var_)); + } else { + RETURN_NOT_OK(sub_partitioner.set_result_budget_nullable( + attr_name.c_str(), + result_budget.size_fixed_, + result_budget.size_var_, + result_budget.size_validity_)); + } } } @@ -1587,17 +1818,25 @@ Status Reader::compute_result_coords( while (read_state_.unsplittable_) { uint64_t partitioner_memory_budget; uint64_t partitioner_memory_budget_var; + uint64_t partitioner_memory_budget_validity; RETURN_NOT_OK(partitioner->get_memory_budget( - &partitioner_memory_budget, &partitioner_memory_budget_var)); + &partitioner_memory_budget, + &partitioner_memory_budget_var, + &partitioner_memory_budget_validity)); sub_partitioner_memory_budget = std::min( partitioner_memory_budget, sub_partitioner_memory_budget * 2); sub_partitioner_memory_budget_var = std::min( partitioner_memory_budget_var, sub_partitioner_memory_budget_var * 2); + sub_partitioner_memory_budget_validity = std::min( + partitioner_memory_budget_validity, + sub_partitioner_memory_budget_validity * 2); RETURN_NOT_OK(sub_partitioner.set_memory_budget( - sub_partitioner_memory_budget, sub_partitioner_memory_budget)); + sub_partitioner_memory_budget, + sub_partitioner_memory_budget_var, + sub_partitioner_memory_budget_validity)); RETURN_NOT_OK(sub_partitioner.next(&read_state_.unsplittable_)); } @@ -1950,6 +2189,7 @@ Status Reader::unfilter_tiles( STATS_START_TIMER(stat_type); auto var_size = array_schema_->var_size(name); + auto nullable = array_schema_->is_nullable(name); auto num_tiles = static_cast(result_tiles.size()); auto encryption_key = array_->encryption_key(); @@ -1965,12 +2205,12 @@ Status Reader::unfilter_tiles( if (name != constants::coords || (name == constants::coords && format_version < 5) || (array_schema_->is_dim(name) && format_version >= 5)) { - auto tile_pair = tile->tile_pair(name); + auto tile_tuple = tile->tile_tuple(name); // Skip non-existent attributes/dimensions (e.g. coords in the // dense case). - if (tile_pair == nullptr || - tile_pair->first.filtered_buffer()->size() == 0) + if (tile_tuple == nullptr || + std::get<0>(*tile_tuple).filtered_buffer()->size() == 0) return Status::Ok(); // Get information about the tile in its fragment. @@ -1980,8 +2220,9 @@ Status Reader::unfilter_tiles( RETURN_NOT_OK(fragment->file_offset( *encryption_key, name, tile_idx, &tile_attr_offset)); - auto& t = tile_pair->first; - auto& t_var = tile_pair->second; + auto& t = std::get<0>(*tile_tuple); + auto& t_var = std::get<1>(*tile_tuple); + auto& t_validity = std::get<2>(*tile_tuple); // If we're performing selective unfiltering, lookup the result // cell slab ranges associated with this tile. If we do not have @@ -2018,13 +2259,35 @@ Status Reader::unfilter_tiles( t_var.filtered_buffer())); } + // Cache 't_validity'. + if (nullable && t_validity.filtered()) { + auto tile_attr_validity_uri = fragment->validity_uri(name); + uint64_t tile_attr_validity_offset; + RETURN_NOT_OK(fragment->file_validity_offset( + *encryption_key, name, tile_idx, &tile_attr_validity_offset)); + + // Store the filtered buffer in the tile cache. + RETURN_NOT_OK(storage_manager_->write_to_cache( + tile_attr_validity_uri, + tile_attr_validity_offset, + t_validity.filtered_buffer())); + } + // Unfilter 't' for fixed-sized tiles, otherwise unfilter both 't' and // 't_var' for var-sized tiles. if (!var_size) { - RETURN_NOT_OK(unfilter_tile(name, &t, result_cell_slab_ranges)); + if (!nullable) + RETURN_NOT_OK(unfilter_tile(name, &t, result_cell_slab_ranges)); + else + RETURN_NOT_OK(unfilter_tile_nullable( + name, &t, &t_validity, result_cell_slab_ranges)); } else { - RETURN_NOT_OK( - unfilter_tile(name, &t, &t_var, result_cell_slab_ranges)); + if (!nullable) + RETURN_NOT_OK( + unfilter_tile(name, &t, &t_var, result_cell_slab_ranges)); + else + RETURN_NOT_OK(unfilter_tile_nullable( + name, &t, &t_var, &t_validity, result_cell_slab_ranges)); } } @@ -2102,6 +2365,93 @@ Status Reader::unfilter_tile( return Status::Ok(); } +Status Reader::unfilter_tile_nullable( + const std::string& name, + Tile* tile, + Tile* tile_validity, + const std::vector>* result_cell_slab_ranges) + const { + FilterPipeline filters = array_schema_->filters(name); + FilterPipeline validity_filters = array_schema_->cell_validity_filters(); + + // Append an encryption unfilter when necessary. + RETURN_NOT_OK(FilterPipeline::append_encryption_filter( + &filters, array_->get_encryption_key())); + RETURN_NOT_OK(FilterPipeline::append_encryption_filter( + &validity_filters, array_->get_encryption_key())); + + // Skip selective unfiltering on coordinate tiles. + if (name == constants::coords || tile->stores_coords()) { + result_cell_slab_ranges = nullptr; + } + + // Reverse the tile filters. + RETURN_NOT_OK(filters.run_reverse( + tile, + storage_manager_->compute_tp(), + storage_manager_->config(), + result_cell_slab_ranges)); + + // Reverse the validity tile filters, without + // selective decompression. + RETURN_NOT_OK(validity_filters.run_reverse( + tile_validity, + storage_manager_->compute_tp(), + storage_manager_->config(), + nullptr)); + + return Status::Ok(); +} + +Status Reader::unfilter_tile_nullable( + const std::string& name, + Tile* tile, + Tile* tile_var, + Tile* tile_validity, + const std::vector>* result_cell_slab_ranges) + const { + FilterPipeline offset_filters = array_schema_->cell_var_offsets_filters(); + FilterPipeline filters = array_schema_->filters(name); + FilterPipeline validity_filters = array_schema_->cell_validity_filters(); + + // Append an encryption unfilter when necessary. + RETURN_NOT_OK(FilterPipeline::append_encryption_filter( + &offset_filters, array_->get_encryption_key())); + RETURN_NOT_OK(FilterPipeline::append_encryption_filter( + &filters, array_->get_encryption_key())); + RETURN_NOT_OK(FilterPipeline::append_encryption_filter( + &validity_filters, array_->get_encryption_key())); + + // Skip selective unfiltering on coordinate tiles. + if (name == constants::coords || tile->stores_coords()) { + result_cell_slab_ranges = nullptr; + } + + // Reverse the tile filters, but do not use selective + // unfiltering for offset tiles. + RETURN_NOT_OK(offset_filters.run_reverse( + tile, + storage_manager_->compute_tp(), + storage_manager_->config(), + nullptr)); + RETURN_NOT_OK(filters.run_reverse( + tile, + tile_var, + storage_manager_->compute_tp(), + storage_manager_->config(), + result_cell_slab_ranges)); + + // Reverse the validity tile filters, without + // selective decompression. + RETURN_NOT_OK(validity_filters.run_reverse( + tile_validity, + storage_manager_->compute_tp(), + storage_manager_->config(), + nullptr)); + + return Status::Ok(); +} + Status Reader::get_all_result_coords( ResultTile* tile, std::vector* result_coords) const { auto coords_num = tile->cell_num(); @@ -2150,11 +2500,17 @@ Status Reader::init_read_state() { config.get("sm.memory_budget_var", &memory_budget_var, &found)); assert(found); + // Consider the validity memory budget to be identical to `sm.memory_budget` + // because the validity vector is currently a bytemap. When converted to a + // bitmap, this can be budgeted as `sm.memory_budget` / 8 + uint64_t memory_budget_validity = memory_budget; + // Create read state read_state_.partitioner_ = SubarrayPartitioner( subarray_, memory_budget, memory_budget_var, + memory_budget_validity, storage_manager_->compute_tp()); read_state_.overflowed_ = false; read_state_.unsplittable_ = false; @@ -2164,12 +2520,26 @@ Status Reader::init_read_state() { auto attr_name = a.first; auto buffer_size = a.second.buffer_size_; auto buffer_var_size = a.second.buffer_var_size_; + auto buffer_validity_size = a.second.validity_vector_.buffer_size(); if (!array_schema_->var_size(attr_name)) { - RETURN_NOT_OK(read_state_.partitioner_.set_result_budget( - attr_name.c_str(), *buffer_size)); + if (!array_schema_->is_nullable(attr_name)) { + RETURN_NOT_OK(read_state_.partitioner_.set_result_budget( + attr_name.c_str(), *buffer_size)); + } else { + RETURN_NOT_OK(read_state_.partitioner_.set_result_budget_nullable( + attr_name.c_str(), *buffer_size, *buffer_validity_size)); + } } else { - RETURN_NOT_OK(read_state_.partitioner_.set_result_budget( - attr_name.c_str(), *buffer_size, *buffer_var_size)); + if (!array_schema_->is_nullable(attr_name)) { + RETURN_NOT_OK(read_state_.partitioner_.set_result_budget( + attr_name.c_str(), *buffer_size, *buffer_var_size)); + } else { + RETURN_NOT_OK(read_state_.partitioner_.set_result_budget_nullable( + attr_name.c_str(), + *buffer_size, + *buffer_var_size, + *buffer_validity_size)); + } } } @@ -2215,6 +2585,53 @@ Status Reader::init_tile( return Status::Ok(); } +Status Reader::init_tile_nullable( + uint32_t format_version, + const std::string& name, + Tile* tile, + Tile* tile_validity) const { + // For easy reference + auto cell_size = array_schema_->cell_size(name); + auto type = array_schema_->type(name); + auto is_coords = (name == constants::coords); + auto dim_num = (is_coords) ? array_schema_->dim_num() : 0; + + // Initialize + RETURN_NOT_OK(tile->init_filtered(format_version, type, cell_size, dim_num)); + RETURN_NOT_OK(tile_validity->init_filtered( + format_version, + constants::cell_validity_type, + constants::cell_validity_size, + 0)); + + return Status::Ok(); +} + +Status Reader::init_tile_nullable( + uint32_t format_version, + const std::string& name, + Tile* tile, + Tile* tile_var, + Tile* tile_validity) const { + // For easy reference + auto type = array_schema_->type(name); + + // Initialize + RETURN_NOT_OK(tile->init_filtered( + format_version, + constants::cell_var_offset_type, + constants::cell_var_offset_size, + 0)); + RETURN_NOT_OK( + tile_var->init_filtered(format_version, type, datatype_size(type), 0)); + RETURN_NOT_OK(tile_validity->init_filtered( + format_version, + constants::cell_validity_type, + constants::cell_validity_size, + 0)); + return Status::Ok(); +} + Status Reader::load_tile_offsets(const std::vector& names) { const auto encryption_key = array_->encryption_key(); @@ -2318,6 +2735,7 @@ Status Reader::read_tiles( // For each tile, read from its fragment. const bool var_size = array_schema_->var_size(name); + const bool nullable = array_schema_->is_nullable(name); const auto encryption_key = array_->encryption_key(); // Gather the unique fragments indexes for which there are tiles @@ -2362,14 +2780,22 @@ Status Reader::read_tiles( tile->init_attr_tile(name); } - ResultTile::TilePair* const tile_pair = tile->tile_pair(name); - assert(tile_pair != nullptr); - Tile* const t = &tile_pair->first; - Tile* const t_var = &tile_pair->second; + ResultTile::TileTuple* const tile_tuple = tile->tile_tuple(name); + assert(tile_tuple != nullptr); + Tile* const t = &std::get<0>(*tile_tuple); + Tile* const t_var = &std::get<1>(*tile_tuple); + Tile* const t_validity = &std::get<2>(*tile_tuple); if (!var_size) { - RETURN_NOT_OK(init_tile(format_version, name, t)); + if (nullable) + RETURN_NOT_OK(init_tile_nullable(format_version, name, t, t_validity)); + else + RETURN_NOT_OK(init_tile(format_version, name, t)); } else { - RETURN_NOT_OK(init_tile(format_version, name, t, t_var)); + if (nullable) + RETURN_NOT_OK( + init_tile_nullable(format_version, name, t, t_var, t_validity)); + else + RETURN_NOT_OK(init_tile(format_version, name, t, t_var)); } // Get information about the tile in its fragment @@ -2390,6 +2816,7 @@ Status Reader::read_tiles( t->filtered_buffer(), tile_persisted_size, &cache_hit)); + if (!cache_hit) { // Add the region of the fragment to be read. RETURN_NOT_OK(t->filtered_buffer()->realloc(tile_persisted_size)); @@ -2428,6 +2855,36 @@ Status Reader::read_tiles( tile_var_persisted_size); } } + + if (nullable) { + auto tile_validity_attr_uri = fragment->validity_uri(name); + uint64_t tile_attr_validity_offset; + RETURN_NOT_OK(fragment->file_validity_offset( + *encryption_key, name, tile_idx, &tile_attr_validity_offset)); + uint64_t tile_validity_persisted_size; + RETURN_NOT_OK(fragment->persisted_tile_validity_size( + *encryption_key, name, tile_idx, &tile_validity_persisted_size)); + + Buffer cached_valdity_buffer; + RETURN_NOT_OK(storage_manager_->read_from_cache( + tile_validity_attr_uri, + tile_attr_validity_offset, + t_validity->filtered_buffer(), + tile_validity_persisted_size, + &cache_hit)); + + if (!cache_hit) { + // Add the region of the fragment to be read. + RETURN_NOT_OK(t_validity->filtered_buffer()->realloc( + tile_validity_persisted_size)); + t_validity->filtered_buffer()->set_size(tile_validity_persisted_size); + t_validity->filtered_buffer()->reset_offset(); + all_regions[tile_validity_attr_uri].emplace_back( + tile_attr_validity_offset, + t_validity->filtered_buffer()->data(), + tile_validity_persisted_size); + } + } } // We're done accessing elements within `result_tiles`. @@ -2455,6 +2912,9 @@ void Reader::reset_buffer_sizes() { *(it.second.buffer_size_) = it.second.original_buffer_size_; if (it.second.buffer_var_size_ != nullptr) *(it.second.buffer_var_size_) = it.second.original_buffer_var_size_; + if (it.second.validity_vector_.buffer_size() != nullptr) + *(it.second.validity_vector_.buffer_size()) = + it.second.original_validity_vector_size_; } } @@ -2697,6 +3157,8 @@ void Reader::zero_out_buffer_sizes() { *(buffer.second.buffer_size_) = 0; if (buffer.second.buffer_var_size_ != nullptr) *(buffer.second.buffer_var_size_) = 0; + if (buffer.second.validity_vector_.buffer_size() != nullptr) + *(buffer.second.validity_vector_.buffer_size()) = 0; } } @@ -2735,6 +3197,9 @@ void Reader::get_dim_attr_stats() const { } else { STATS_ADD_COUNTER(stats::Stats::CounterType::READ_ATTR_FIXED_NUM, 1); } + if (array_schema_->is_nullable(name)) { + STATS_ADD_COUNTER(stats::Stats::CounterType::READ_ATTR_NULLABLE_NUM, 1); + } } else { if (var_size) { STATS_ADD_COUNTER(stats::Stats::CounterType::READ_DIM_VAR_NUM, 1); diff --git a/tiledb/sm/query/reader.h b/tiledb/sm/query/reader.h index b6e892051c2..9164bf8b34e 100644 --- a/tiledb/sm/query/reader.h +++ b/tiledb/sm/query/reader.h @@ -44,9 +44,11 @@ #include "tiledb/sm/array_schema/tile_domain.h" #include "tiledb/sm/misc/types.h" #include "tiledb/sm/misc/uri.h" +#include "tiledb/sm/query/query_buffer.h" #include "tiledb/sm/query/result_cell_slab.h" #include "tiledb/sm/query/result_coords.h" #include "tiledb/sm/query/result_space_tile.h" +#include "tiledb/sm/query/validity_vector.h" #include "tiledb/sm/query/write_cell_slab_iter.h" #include "tiledb/sm/subarray/subarray_partitioner.h" @@ -252,6 +254,42 @@ class Reader { void** buffer_val, uint64_t** buffer_val_size) const; + /** + * Retrieves the buffer of a fixed-sized, nullable attribute. + * + * @param name The attribute name. + * @param buffer The buffer to be retrieved. + * @param buffer_size A pointer to the buffer size to be retrieved. + * @param ValidityVector The validity vector to be retrieved. + * @return Status + */ + Status get_buffer_nullable( + const std::string& name, + void** buffer, + uint64_t** buffer_size, + const ValidityVector** validity_vector) const; + + /** + * Retrieves the offsets, values, and validity buffers of a var-sized, + * nullable attribute. + * + * @param name The attribute/dimension name. + * @param buffer_off The offsets buffer to be retrieved. + * @param buffer_off_size A pointer to the offsets buffer size to be + * retrieved. + * @param buffer_val The values buffer to be retrieved. + * @param buffer_val_size A pointer to the values buffer size to be retrieved. + * @param ValidityVector The validity vector to be retrieved. + * @return Status + */ + Status get_buffer_nullable( + const std::string& name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + const ValidityVector** validity_vector) const; + /** Returns the first fragment uri. */ URI first_fragment_uri() const; @@ -330,6 +368,57 @@ class Reader { uint64_t* buffer_val_size, bool check_null_buffers = true); + /** + * Sets the buffer for a fixed-sized, nullable attribute. + * + * @param name The attribute to set the buffer for. + * @param buffer The buffer that will hold the data to be read. + * @param buffer_size This initially contains the allocated + * size of `buffer`, but after the termination of the function + * it will contain the size of the useful (read) data in `buffer`. + * @param check_null_buffers If true (default), null buffers are not allowed. + * @param validity_vector The validity vector associated with values in + * `buffer`. + * @return Status + */ + Status set_buffer( + const std::string& name, + void* buffer, + uint64_t* buffer_size, + ValidityVector&& validity_vector, + bool check_null_buffers = true); + + /** + * Sets the buffer for a var-sized, nullable attribute. + * + * @param name The name to set the buffer for. + * @param buffer_off The buffer that will hold the data to be read. + * This buffer holds the starting offsets of each cell value in + * `buffer_val`. + * @param buffer_off_size This initially contains + * the allocated size of `buffer_off`, but after the termination of the + * function it will contain the size of the useful (read) data in + * `buffer_off`. + * @param buffer_val The buffer that will hold the data to be read. + * This buffer holds the actual var-sized cell values. + * @param buffer_val_size This initially contains + * the allocated size of `buffer_val`, but after the termination of the + * function it will contain the size of the useful (read) data in + * `buffer_val`. + * @param validity_vector The validity vector associated with values in + * `buffer_val`. + * @param check_null_buffers If true (default), null buffers are not allowed. + * @return Status + */ + Status set_buffer( + const std::string& name, + uint64_t* buffer_off, + uint64_t* buffer_off_size, + void* buffer_val, + uint64_t* buffer_val_size, + ValidityVector&& validity_vector, + bool check_null_buffers = true); + /** Sets the fragment metadata. */ void set_fragment_metadata( const std::vector& fragment_metadata); @@ -824,15 +913,6 @@ class Reader { /** The query subarray (initially the whole domain by default). */ Subarray subarray_; - /** - * The memory budget for the fixed-sized attributes and the offsets - * of the var-sized attributes. - */ - uint64_t memory_budget_; - - /** The memory budget for the var-sized attributes. */ - uint64_t memory_budget_var_; - /** Protects result tiles. */ mutable std::mutex result_tiles_mutex_; @@ -1092,7 +1172,8 @@ class Reader { std::vector* offset_offsets_per_cs, std::vector* var_offsets_per_cs, uint64_t* total_offset_size, - uint64_t* total_var_size) const; + uint64_t* total_var_size, + uint64_t* total_validity_size) const; /** * Copies the cells for the input **var-sized** attribute/dimension and result @@ -1337,6 +1418,46 @@ class Reader { const std::vector>* result_cell_slab_ranges) const; + /** + * Runs the input fixed-sized tile for the input nullable attribute + * through the filter pipeline. The tile buffer is modified to contain the + * output of the pipeline. + * + * @param name The attribute/dimension the tile belong to. + * @param tile The tile to be unfiltered. + * @param tile_validity The validity tile to be unfiltered. + * @param result_cell_slab_ranges Result cell slab ranges sorted in ascending + * order. + * @return Status + */ + Status unfilter_tile_nullable( + const std::string& name, + Tile* tile, + Tile* tile_validity, + const std::vector>* result_cell_slab_ranges) + const; + + /** + * Runs the input var-sized tile for the input nullable attribute through + * the filter pipeline. The tile buffer is modified to contain the output of + * the pipeline. + * + * @param name The attribute/dimension the tile belong to. + * @param tile The offsets tile to be unfiltered. + * @param tile_var The value tile to be unfiltered. + * @param tile_validity The validity tile to be unfiltered. + * @param result_cell_slab_ranges Result cell slab ranges sorted in ascending + * order. + * @return Status + */ + Status unfilter_tile_nullable( + const std::string& name, + Tile* tile, + Tile* tile_var, + Tile* tile_validity, + const std::vector>* result_cell_slab_ranges) + const; + /** * Gets all the result coordinates of the input tile into `result_coords`. * @@ -1385,6 +1506,38 @@ class Reader { Tile* tile, Tile* tile_var) const; + /** + * Initializes a fixed-sized tile. + * + * @param format_version The format version of the tile. + * @param name The attribute/dimension the tile belongs to. + * @param tile The tile to be initialized. + * @param tile_validity The validity tile to be initialized. + * @return Status + */ + Status init_tile_nullable( + uint32_t format_version, + const std::string& name, + Tile* tile, + Tile* tile_validity) const; + + /** + * Initializes a var-sized tile. + * + * @param format_version The format version of the tile. + * @param name The attribute/dimension the tile belongs to. + * @param tile The offsets tile to be initialized. + * @param tile_var The var-sized data tile to be initialized. + * @param tile_validity The validity tile to be initialized. + * @return Status + */ + Status init_tile_nullable( + uint32_t format_version, + const std::string& name, + Tile* tile, + Tile* tile_var, + Tile* tile_validity) const; + /** * Loads tile offsets for each attribute/dimension name into * their associated element in `fragment_metadata_`. diff --git a/tiledb/sm/query/result_tile.cc b/tiledb/sm/query/result_tile.cc index 1dc5f8fc15c..733764a2e0e 100644 --- a/tiledb/sm/query/result_tile.cc +++ b/tiledb/sm/query/result_tile.cc @@ -72,14 +72,14 @@ bool ResultTile::operator==(const ResultTile& rt) const { } uint64_t ResultTile::cell_num() const { - if (!coord_tiles_[0].second.first.empty()) - return coord_tiles_[0].second.first.cell_num(); + if (!std::get<0>(coord_tiles_[0].second).empty()) + return std::get<0>(coord_tiles_[0].second).cell_num(); - if (!coords_tile_.first.empty()) - return coords_tile_.first.cell_num(); + if (!std::get<0>(coords_tile_).empty()) + return std::get<0>(coords_tile_).cell_num(); if (!attr_tiles_.empty()) - return attr_tiles_.begin()->second.first.cell_num(); + return std::get<0>(attr_tiles_.begin()->second).cell_num(); return 0; } @@ -91,14 +91,14 @@ const Domain* ResultTile::domain() const { void ResultTile::erase_tile(const std::string& name) { // Handle zipped coordinates tiles if (name == constants::coords) { - coords_tile_ = TilePair(Tile(), Tile()); + coords_tile_ = TileTuple(Tile(), Tile(), Tile()); return; } // Handle dimension tile for (auto& ct : coord_tiles_) { if (ct.first == name) { - ct.second = TilePair(Tile(), Tile()); + ct.second = TileTuple(Tile(), Tile(), Tile()); return; } } @@ -114,19 +114,19 @@ void ResultTile::init_attr_tile(const std::string& name) { // Handle attributes if (attr_tiles_.find(name) == attr_tiles_.end()) - attr_tiles_.emplace(name, TilePair(Tile(), Tile())); + attr_tiles_.emplace(name, TileTuple(Tile(), Tile(), Tile())); } void ResultTile::init_coord_tile(const std::string& name, unsigned dim_idx) { - coord_tiles_[dim_idx] = - std::pair(name, TilePair(Tile(), Tile())); + coord_tiles_[dim_idx] = std::pair( + name, TileTuple(Tile(), Tile(), Tile())); // When at least one unzipped coordinate has been initialized, we will // use the unzipped `coord()` implementation. coord_func_ = &ResultTile::unzipped_coord; } -ResultTile::TilePair* ResultTile::tile_pair(const std::string& name) { +ResultTile::TileTuple* ResultTile::tile_tuple(const std::string& name) { // Handle zipped coordinates tile if (name == constants::coords) return &coords_tile_; @@ -146,7 +146,7 @@ ResultTile::TilePair* ResultTile::tile_pair(const std::string& name) { } const void* ResultTile::unzipped_coord(uint64_t pos, unsigned dim_idx) const { - const auto& coord_tile = coord_tiles_[dim_idx].second.first; + const auto& coord_tile = std::get<0>(coord_tiles_[dim_idx].second); const uint64_t offset = pos * coord_tile.cell_size(); ChunkedBuffer* const chunked_buffer = coord_tile.chunked_buffer(); assert( @@ -158,10 +158,11 @@ const void* ResultTile::unzipped_coord(uint64_t pos, unsigned dim_idx) const { } const void* ResultTile::zipped_coord(uint64_t pos, unsigned dim_idx) const { - auto coords_size = coords_tile_.first.cell_size(); - auto coord_size = coords_size / coords_tile_.first.dim_num(); + auto coords_size = std::get<0>(coords_tile_).cell_size(); + auto coord_size = coords_size / std::get<0>(coords_tile_).dim_num(); const uint64_t offset = pos * coords_size + dim_idx * coord_size; - ChunkedBuffer* const chunked_buffer = coords_tile_.first.chunked_buffer(); + ChunkedBuffer* const chunked_buffer = + std::get<0>(coords_tile_).chunked_buffer(); assert( chunked_buffer->buffer_addressing() == ChunkedBuffer::BufferAddressing::CONTIGUOUS); @@ -171,8 +172,8 @@ const void* ResultTile::zipped_coord(uint64_t pos, unsigned dim_idx) const { } std::string ResultTile::coord_string(uint64_t pos, unsigned dim_idx) const { - const auto& coord_tile_off = coord_tiles_[dim_idx].second.first; - const auto& coord_tile_val = coord_tiles_[dim_idx].second.second; + const auto& coord_tile_off = std::get<0>(coord_tiles_[dim_idx].second); + const auto& coord_tile_val = std::get<1>(coord_tiles_[dim_idx].second); assert(!coord_tile_off.empty()); assert(!coord_tile_val.empty()); auto cell_num = coord_tile_off.cell_num(); @@ -204,12 +205,13 @@ std::string ResultTile::coord_string(uint64_t pos, unsigned dim_idx) const { uint64_t ResultTile::coord_size(unsigned dim_idx) const { // Handle zipped coordinate tiles - if (!coords_tile_.first.empty()) - return coords_tile_.first.cell_size() / coords_tile_.first.dim_num(); + if (!std::get<0>(coords_tile_).empty()) + return std::get<0>(coords_tile_).cell_size() / + std::get<0>(coords_tile_).dim_num(); // Handle separate coordinate tiles assert(dim_idx < coord_tiles_.size()); - return coord_tiles_[dim_idx].second.first.cell_size(); + return std::get<0>(coord_tiles_[dim_idx].second).cell_size(); } bool ResultTile::same_coords( @@ -237,32 +239,39 @@ uint64_t ResultTile::tile_idx() const { } Status ResultTile::read( - const std::string& name, void* buffer, uint64_t pos, uint64_t len) { + const std::string& name, + void* buffer, + uint64_t buffer_offset, + uint64_t pos, + uint64_t len) { + buffer = static_cast(buffer) + buffer_offset; + bool is_dim = false; + RETURN_NOT_OK(domain_->has_dimension(name, &is_dim)); + // Typical case // If asking for an attribute, or split dim buffers with split coordinates // or coordinates have been fetched as zipped - RETURN_NOT_OK(domain_->has_dimension(name, &is_dim)); if ((!is_dim && name != constants::coords) || (is_dim && !coord_tiles_[0].first.empty()) || - (name == constants::coords && !coords_tile_.first.empty())) { - const auto& tile = this->tile_pair(name)->first; + (name == constants::coords && !std::get<0>(coords_tile_).empty())) { + const auto& tile = std::get<0>(*this->tile_tuple(name)); auto cell_size = tile.cell_size(); auto nbytes = len * cell_size; auto offset = pos * cell_size; return tile.read(buffer, nbytes, offset); } else if ( name == constants::coords && !coord_tiles_[0].first.empty() && - coords_tile_.first.empty()) { + std::get<0>(coords_tile_).empty()) { // Special case where zipped coordinates are requested, but the // result tile stores separate coordinates auto dim_num = coord_tiles_.size(); - assert(coords_tile_.first.empty()); + assert(std::get<0>(coords_tile_).empty()); auto buff = static_cast(buffer); auto buff_offset = 0; for (uint64_t c = 0; c < len; ++c) { for (unsigned d = 0; d < dim_num; ++d) { - auto coord_tile = coord_tiles_[d].second.first; + auto coord_tile = std::get<0>(coord_tiles_[d].second); auto cell_size = coord_tile.cell_size(); auto tile_offset = (pos + c) * cell_size; RETURN_NOT_OK( @@ -273,7 +282,7 @@ Status ResultTile::read( } else { // Last case which is zipped coordinates but split buffers // This is only for backwards compatibility of pre format 5 (v2.0) arrays - assert(!coords_tile_.first.empty()); + assert(!std::get<0>(coords_tile_).empty()); assert(name != constants::coords); int dim_offset = 0; for (uint32_t i = 0; i < domain_->dim_num(); ++i) { @@ -283,12 +292,12 @@ Status ResultTile::read( } } auto buff = static_cast(buffer); - auto cell_size = coords_tile_.first.cell_size(); + auto cell_size = std::get<0>(coords_tile_).cell_size(); auto dim_size = cell_size / domain_->dim_num(); uint64_t offset = pos * cell_size + dim_size * dim_offset; for (uint64_t c = 0; c < len; ++c) { - RETURN_NOT_OK( - coords_tile_.first.read(buff + (c * dim_size), dim_size, offset)); + RETURN_NOT_OK(std::get<0>(coords_tile_) + .read(buff + (c * dim_size), dim_size, offset)); offset += cell_size; } }; @@ -296,16 +305,45 @@ Status ResultTile::read( return Status::Ok(); } +Status ResultTile::read_nullable( + const std::string& name, + void* buffer, + uint64_t buffer_offset, + uint64_t pos, + uint64_t len, + void* buffer_validity) { + const auto& tile = std::get<0>(*this->tile_tuple(name)); + const auto& tile_validity = std::get<2>(*this->tile_tuple(name)); + + auto cell_size = tile.cell_size(); + auto validity_cell_size = tile_validity.cell_size(); + + buffer = static_cast(buffer) + buffer_offset; + buffer_validity = static_cast(buffer_validity) + + (buffer_offset / cell_size * validity_cell_size); + + auto nbytes = len * cell_size; + auto offset = pos * cell_size; + auto validity_nbytes = len * validity_cell_size; + auto validity_offset = pos * validity_cell_size; + + RETURN_NOT_OK(tile.read(buffer, nbytes, offset)); + RETURN_NOT_OK( + tile_validity.read(buffer_validity, validity_nbytes, validity_offset)); + + return Status::Ok(); +} + bool ResultTile::stores_zipped_coords() const { - return !coords_tile_.first.empty(); + return !std::get<0>(coords_tile_).empty(); } const Tile& ResultTile::zipped_coords_tile() const { assert(stores_zipped_coords()); - return coords_tile_.first; + return std::get<0>(coords_tile_); } -const ResultTile::TilePair& ResultTile::coord_tile(unsigned dim_idx) const { +const ResultTile::TileTuple& ResultTile::coord_tile(unsigned dim_idx) const { assert(!stores_zipped_coords()); assert(!coord_tiles_.empty()); return coord_tiles_[dim_idx].second; @@ -348,7 +386,7 @@ void ResultTile::compute_results_dense( // Handle separate coordinate tiles if (!stores_zipped_coords) { - const auto& coord_tile = result_tile->coord_tile(dim_idx).first; + const auto& coord_tile = std::get<0>(result_tile->coord_tile(dim_idx)); ChunkedBuffer* const chunked_buffer = coord_tile.chunked_buffer(); assert( @@ -371,7 +409,8 @@ void ResultTile::compute_results_dense( if (meta->dense()) { overwritten = true; for (unsigned d = 0; d < dim_num; ++d) { - const auto& coord_tile = result_tile->coord_tile(dim_idx).first; + const auto& coord_tile = + std::get<0>(result_tile->coord_tile(dim_idx)); ChunkedBuffer* const chunked_buffer = coord_tile.chunked_buffer(); assert( @@ -460,7 +499,7 @@ void ResultTile::compute_results_sparse( // Handle separate coordinate tiles if (!stores_zipped_coords) { - const auto& coord_tile = result_tile->coord_tile(dim_idx).first; + const auto& coord_tile = std::get<0>(result_tile->coord_tile(dim_idx)); ChunkedBuffer* const chunked_buffer = coord_tile.chunked_buffer(); assert( chunked_buffer->buffer_addressing() == diff --git a/tiledb/sm/query/result_tile.h b/tiledb/sm/query/result_tile.h index be694e5b3a2..2a4f58d9d31 100644 --- a/tiledb/sm/query/result_tile.h +++ b/tiledb/sm/query/result_tile.h @@ -59,11 +59,12 @@ class FragmentMetadata; class ResultTile { public: /** - * For each fixed-sized attributes, the second tile in the pair is ignored. - * For var-sized attributes, the first is the offsets tile and the second is - * the var-sized values tile. + * For each fixed-sized attributes, the second tile in the tuple is ignored. + * For var-sized attributes, the first tile is the offsets tile and the second + * tile is the var-sized values tile. If the attribute is nullable, the third + * tile contains the validity vector. */ - typedef std::pair TilePair; + typedef std::tuple TileTuple; /* ********************************* */ /* CONSTRUCTORS & DESTRUCTORS */ @@ -113,7 +114,7 @@ class ResultTile { const Tile& zipped_coords_tile() const; /** Returns the coordinate tile for the input dimension. */ - const TilePair& coord_tile(unsigned dim_idx) const; + const TileTuple& coord_tile(unsigned dim_idx) const; /** Returns the stored domain. */ const Domain* domain() const; @@ -128,7 +129,7 @@ class ResultTile { void init_coord_tile(const std::string& name, unsigned dim_idx); /** Returns the tile pair for the input attribute or dimension. */ - TilePair* tile_pair(const std::string& name); + TileTuple* tile_tuple(const std::string& name); /** * Returns a constant pointer to the coordinate at position `pos` for @@ -167,7 +168,24 @@ class ResultTile { * the coordinates at position `pos`. */ Status read( - const std::string& name, void* buffer, uint64_t pos, uint64_t len); + const std::string& name, + void* buffer, + uint64_t buffer_offset, + uint64_t pos, + uint64_t len); + + /** + * Reads `len` coordinates the from the tile, starting at the beginning of + * the coordinates at position `pos`. The associated validity values are + * stored in the `buffer_validity`. + */ + Status read_nullable( + const std::string& name, + void* buffer, + uint64_t buffer_offset, + uint64_t pos, + uint64_t len, + void* buffer_validity); /** * Applicable only to sparse tiles of dense arrays. @@ -248,16 +266,16 @@ class ResultTile { uint64_t tile_idx_ = UINT64_MAX; /** Maps attribute names to tiles. */ - std::unordered_map attr_tiles_; + std::unordered_map attr_tiles_; /** The zipped coordinates tile. */ - TilePair coords_tile_; + TileTuple coords_tile_; /** * The separate coordinate tiles along with their names, sorted on the * dimension order. */ - std::vector> coord_tiles_; + std::vector> coord_tiles_; /** * Stores the appropriate templated compute_results_dense() function based for diff --git a/tiledb/sm/query/validity_vector.h b/tiledb/sm/query/validity_vector.h new file mode 100644 index 00000000000..f79c5941519 --- /dev/null +++ b/tiledb/sm/query/validity_vector.h @@ -0,0 +1,166 @@ +/** + * @file validity_vector.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2020 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 class ValidityVector. + */ + +#ifndef TILEDB_VALIDITY_VECTOR_H +#define TILEDB_VALIDITY_VECTOR_H + +#include + +#include "tiledb/common/logger.h" +#include "tiledb/common/status.h" +#include "tiledb/sm/misc/macros.h" + +using namespace tiledb::common; + +namespace tiledb { +namespace sm { + +class ValidityVector { + public: + /* ********************************* */ + /* CONSTRUCTORS & DESTRUCTORS */ + /* ********************************* */ + + /** Default constructor. */ + ValidityVector() + : buffer_(nullptr) + , buffer_size_(0) { + } + + /** Copy constructor. */ + ValidityVector(const ValidityVector& rhs) + : buffer_(rhs.buffer_) + , buffer_size_(rhs.buffer_size_) { + } + + /** Move constructor. */ + ValidityVector(ValidityVector&& rhs) { + std::swap(buffer_, rhs.buffer_); + std::swap(buffer_size_, rhs.buffer_size_); + } + + /** Destructor. */ + ~ValidityVector() = default; + + /* ********************************* */ + /* OPERATORS */ + /* ********************************* */ + + /** Move-assignment operator. */ + ValidityVector& operator=(ValidityVector&& rhs) { + if (&rhs == this) + return *this; + + std::swap(buffer_, rhs.buffer_); + std::swap(buffer_size_, rhs.buffer_size_); + + return *this; + } + + DISABLE_COPY_ASSIGN(ValidityVector); + + /* ********************************* */ + /* API */ + /* ********************************* */ + + /** + * Initializes the validity vector with a bytemap. This does + * not take ownership of the bytemap. Each non-zero byte represents + * a valid attribute value. + * + * @param bytemap The byte map. + * @param bytemap_size The byte size of `bytemap`. + * @return Status + */ + Status init_bytemap(uint8_t* const bytemap, uint64_t* bytemap_size) { + if (buffer_ != nullptr) + return Status::ValidityVectorError( + "ValidityVector instance already initialized"); + + buffer_ = bytemap; + buffer_size_ = bytemap_size; + + return Status::Ok(); + } + + /** Returns the bytemap that this instance was initialized with. */ + uint8_t* bytemap() const { + return buffer_; + } + + /** + * Returns the size of the bytemap that this instance was initialized + * with. + */ + uint64_t* bytemap_size() const { + return buffer_size_; + } + + /** + * Returns the internal buffer. This is currently a byte map, but + * will change to a bitmap in the future. + * + * @return a pointer to the internal buffer. + */ + uint8_t* buffer() const { + return buffer_; + } + + /** + * Returns the size of the internal buffer. + * + * @return a pointer to the internal buffer size. + */ + uint64_t* buffer_size() const { + return buffer_size_; + } + + private: + /* ********************************* */ + /* PRIVATE ATTRIBUTES */ + /* ********************************* */ + + /** + * Contains a byte-map, where each non-zero byte represents + * a valid (non-null) attribute value and a zero byte represents + * a null (non-valid) attribute value. + */ + uint8_t* buffer_; + + /** The size of `buffer_size_`. */ + uint64_t* buffer_size_; +}; + +} // namespace sm +} // namespace tiledb + +#endif // TILEDB_VALIDITY_VECTOR_H diff --git a/tiledb/sm/query/writer.cc b/tiledb/sm/query/writer.cc index 8f961ab9162..c9c0efa0f15 100644 --- a/tiledb/sm/query/writer.cc +++ b/tiledb/sm/query/writer.cc @@ -219,6 +219,56 @@ Status Writer::get_buffer( return Status::Ok(); } +Status Writer::get_buffer_nullable( + const std::string& name, + void** buffer, + uint64_t** buffer_size, + const ValidityVector** validity_vector) const { + // Attribute or dimension + auto it = buffers_.find(name); + if (it != buffers_.end()) { + *buffer = it->second.buffer_; + *buffer_size = it->second.buffer_size_; + *validity_vector = &it->second.validity_vector_; + return Status::Ok(); + } + + // Named buffer does not exist + *buffer = nullptr; + *buffer_size = nullptr; + *validity_vector = nullptr; + + return Status::Ok(); +} + +Status Writer::get_buffer_nullable( + const std::string& name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + const ValidityVector** validity_vector) const { + // Attribute or dimension + auto it = buffers_.find(name); + if (it != buffers_.end()) { + *buffer_off = (uint64_t*)it->second.buffer_; + *buffer_off_size = it->second.buffer_size_; + *buffer_val = it->second.buffer_var_; + *buffer_val_size = it->second.buffer_var_size_; + *validity_vector = &it->second.validity_vector_; + return Status::Ok(); + } + + // Named buffer does not exist + *buffer_off = nullptr; + *buffer_off_size = nullptr; + *buffer_val = nullptr; + *buffer_val_size = nullptr; + *validity_vector = nullptr; + + return Status::Ok(); +} + bool Writer::get_check_coord_dups() const { return check_coord_dups_; } @@ -301,7 +351,7 @@ void Writer::set_array_schema(const ArraySchema* array_schema) { } Status Writer::set_buffer( - const std::string& name, void* buffer, uint64_t* buffer_size) { + const std::string& name, void* const buffer, uint64_t* const buffer_size) { // Check buffer if (buffer == nullptr || buffer_size == nullptr) return LOG_STATUS(Status::WriterError( @@ -317,8 +367,8 @@ Status Writer::set_buffer( return set_coords_buffer(buffer, buffer_size); // For easy reference - bool is_dim = array_schema_->is_dim(name); - bool is_attr = array_schema_->is_attr(name); + const bool is_dim = array_schema_->is_dim(name); + const bool is_attr = array_schema_->is_attr(name); // Neither a dimension nor an attribute if (!is_dim && !is_attr) @@ -326,9 +376,14 @@ Status Writer::set_buffer( std::string("Cannot set buffer; Invalid buffer name '") + name + "' (it should be an attribute or dimension)")); + // Must not be nullable + if (array_schema_->is_nullable(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute/dimension '") + name + + "' is nullable")); + // Error if it is var-sized - bool var_size = (array_schema_->var_size(name)); - if (var_size) + if (array_schema_->var_size(name)) return LOG_STATUS(Status::WriterError( std::string("Cannot set buffer; Input attribute/dimension '") + name + "' is var-sized")); @@ -369,10 +424,10 @@ Status Writer::set_buffer( Status Writer::set_buffer( const std::string& name, - uint64_t* buffer_off, - uint64_t* buffer_off_size, - void* buffer_val, - uint64_t* buffer_val_size) { + uint64_t* const buffer_off, + uint64_t* const buffer_off_size, + void* const buffer_val, + uint64_t* const buffer_val_size) { // Check buffer if (buffer_off == nullptr || buffer_off_size == nullptr || buffer_val == nullptr || buffer_val_size == nullptr) @@ -385,8 +440,8 @@ Status Writer::set_buffer( Status::WriterError("Cannot set buffer; Array schema not set")); // For easy reference - bool is_dim = array_schema_->is_dim(name); - bool is_attr = array_schema_->is_attr(name); + const bool is_dim = array_schema_->is_dim(name); + const bool is_attr = array_schema_->is_attr(name); // Neither a dimension nor an attribute if (!is_dim && !is_attr) @@ -394,9 +449,14 @@ Status Writer::set_buffer( std::string("Cannot set buffer; Invalid buffer name '") + name + "' (it should be an attribute or dimension)")); + // Must not be nullable + if (array_schema_->is_nullable(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute/dimension '") + name + + "' is nullable")); + // Error if it is fixed-sized - bool var_size = (array_schema_->var_size(name)); - if (!var_size) + if (!array_schema_->var_size(name)) return LOG_STATUS(Status::WriterError( std::string("Cannot set buffer; Input attribute/dimension '") + name + "' is fixed-sized")); @@ -430,6 +490,107 @@ Status Writer::set_buffer( return Status::Ok(); } +Status Writer::set_buffer( + const std::string& name, + void* const buffer, + uint64_t* const buffer_size, + ValidityVector&& validity_vector) { + // Check buffer + if (buffer == nullptr || buffer_size == nullptr) + return LOG_STATUS(Status::WriterError( + "Cannot set buffer; Buffer or buffer size is null")); + + // Array schema must exist + if (array_schema_ == nullptr) + return LOG_STATUS( + Status::WriterError("Cannot set buffer; Array schema not set")); + + // Must be an attribute + if (!array_schema_->is_attr(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Buffer name '") + name + + "' is not an attribute")); + + // Must be fixed-size + if (array_schema_->var_size(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute '") + name + + "' is var-sized")); + + // Must be nullable + if (!array_schema_->is_nullable(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute '") + name + + "' is not nullable")); + + // Error if setting a new attribute after initialization + const bool exists = buffers_.find(name) != buffers_.end(); + if (initialized_ && !exists) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer for new attribute '") + name + + "' after initialization")); + + // Set attribute/dimension buffer + buffers_[name] = QueryBuffer( + buffer, nullptr, buffer_size, nullptr, std::move(validity_vector)); + + return Status::Ok(); +} + +Status Writer::set_buffer( + const std::string& name, + uint64_t* const buffer_off, + uint64_t* const buffer_off_size, + void* const buffer_val, + uint64_t* const buffer_val_size, + ValidityVector&& validity_vector) { + // Check buffer + if (buffer_off == nullptr || buffer_off_size == nullptr || + buffer_val == nullptr || buffer_val_size == nullptr) + return LOG_STATUS(Status::WriterError( + "Cannot set buffer; Buffer or buffer size is null")); + + // Array schema must exist + if (array_schema_ == nullptr) + return LOG_STATUS( + Status::WriterError("Cannot set buffer; Array schema not set")); + + // Must be an attribute + if (!array_schema_->is_attr(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Buffer name '") + name + + "' is not an attribute")); + + // Must be var-size + if (!array_schema_->var_size(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute '") + name + + "' is fixed-sized")); + + // Must be nullable + if (!array_schema_->is_nullable(name)) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer; Input attribute '") + name + + "' is not nullable")); + + // Error if setting a new attribute after initialization + const bool exists = buffers_.find(name) != buffers_.end(); + if (initialized_ && !exists) + return LOG_STATUS(Status::WriterError( + std::string("Cannot set buffer for new attribute '") + name + + "' after initialization")); + + // Set attribute/dimension buffer + buffers_[name] = QueryBuffer( + buffer_off, + buffer_val, + buffer_off_size, + buffer_val_size, + std::move(validity_vector)); + + return Status::Ok(); +} + void Writer::set_fragment_uri(const URI& fragment_uri) { fragment_uri_ = fragment_uri; } @@ -555,8 +716,8 @@ Status Writer::check_buffer_sizes() const { uint64_t expected_cell_num = 0; for (const auto& it : buffers_) { const auto& attr = it.first; - bool is_var = array_schema_->var_size(attr); - auto buffer_size = *it.second.buffer_size_; + const bool is_var = array_schema_->var_size(attr); + const uint64_t buffer_size = *it.second.buffer_size_; if (is_var) { expected_cell_num = buffer_size / constants::cell_var_offset_size; } else { @@ -570,7 +731,34 @@ Status Writer::check_buffer_sizes() const { ss << " (" << expected_cell_num << " != " << cell_num << ")"; return LOG_STATUS(Status::WriterError(ss.str())); } + + if (array_schema_->is_nullable(attr)) { + uint64_t attr_datatype_size = datatype_size(array_schema_->type(attr)); + uint64_t expected_validity_cell_num; + if (is_var) { + const uint64_t buffer_var_size = *it.second.buffer_var_size_; + expected_validity_cell_num = buffer_var_size / attr_datatype_size; + } else { + expected_validity_cell_num = buffer_size / attr_datatype_size; + } + + const uint64_t buffer_validity_size = + *it.second.validity_vector_.buffer_size(); + const uint64_t cell_validity_num = + buffer_validity_size / constants::cell_validity_size; + + if (expected_validity_cell_num != cell_validity_num) { + std::stringstream ss; + ss << "Buffer sizes check failed; Invalid number of validity cells " + "given for "; + ss << "attribute '" << attr << "'"; + ss << " (" << expected_validity_cell_num << " != " << cell_validity_num + << ")"; + return LOG_STATUS(Status::WriterError(ss.str())); + } + } } + return Status::Ok(); } @@ -933,6 +1121,8 @@ Status Writer::close_files(FragmentMetadata* meta) const { RETURN_NOT_OK(storage_manager_->close_file(meta->uri(name))); if (array_schema_->var_size(name)) RETURN_NOT_OK(storage_manager_->close_file(meta->var_uri(name))); + if (array_schema_->is_nullable(name)) + RETURN_NOT_OK(storage_manager_->close_file(meta->validity_uri(name))); } return Status::Ok(); @@ -1132,13 +1322,14 @@ Status Writer::compute_coords_metadata( // Compute number of tiles. Assumes all attributes and // and dimensions have the same number of tiles auto it = tiles.begin(); - auto tile_num = array_schema_->var_size(it->first) ? it->second.size() / 2 : - it->second.size(); + const uint64_t t = 1 + (array_schema_->var_size(it->first) ? 1 : 0) + + (array_schema_->is_nullable(it->first) ? 1 : 0); + auto tile_num = it->second.size() / t; auto dim_num = array_schema_->dim_num(); // Compute MBRs auto statuses = parallel_for( - storage_manager_->compute_tp(), 0, tile_num, [&](uint64_t t) { + storage_manager_->compute_tp(), 0, tile_num, [&](uint64_t i) { NDRange mbr(dim_num); std::vector data(dim_num); for (unsigned d = 0; d < dim_num; ++d) { @@ -1147,13 +1338,13 @@ Status Writer::compute_coords_metadata( auto tiles_it = tiles.find(dim_name); assert(tiles_it != tiles.end()); if (!dim->var_size()) - dim->compute_mbr(tiles_it->second[t], &mbr[d]); + dim->compute_mbr(tiles_it->second[i], &mbr[d]); else dim->compute_mbr_var( - tiles_it->second[2 * t], tiles_it->second[2 * t + 1], &mbr[d]); + tiles_it->second[2 * i], tiles_it->second[2 * i + 1], &mbr[d]); } - meta->set_mbr(t, mbr); + meta->set_mbr(i, mbr); return Status::Ok(); }); @@ -1278,14 +1469,23 @@ Status Writer::filter_tiles( Status Writer::filter_tiles( const std::string& name, std::vector* tiles) const { - bool var_size = array_schema_->var_size(name); + const bool var_size = array_schema_->var_size(name); + const bool nullable = array_schema_->is_nullable(name); + // Filter all tiles auto tile_num = tiles->size(); for (size_t i = 0; i < tile_num; ++i) { - RETURN_NOT_OK(filter_tile(name, &(*tiles)[i], var_size)); + RETURN_NOT_OK(filter_tile(name, &(*tiles)[i], var_size, false)); + if (var_size) { ++i; - RETURN_NOT_OK(filter_tile(name, &(*tiles)[i], false)); + RETURN_NOT_OK(filter_tile(name, &(*tiles)[i], false, false)); + } + + if (nullable) { + ++i; + + RETURN_NOT_OK(filter_tile(name, &(*tiles)[i], false, true)); } } @@ -1293,13 +1493,23 @@ Status Writer::filter_tiles( } Status Writer::filter_tile( - const std::string& name, Tile* tile, bool offsets) const { + const std::string& name, + Tile* const tile, + const bool offsets, + const bool nullable) const { const auto orig_size = tile->chunked_buffer()->size(); // Get a copy of the appropriate filter pipeline. - FilterPipeline filters = - (offsets ? array_schema_->cell_var_offsets_filters() : - array_schema_->filters(name)); + FilterPipeline filters; + if (offsets) { + assert(!nullable); + filters = array_schema_->cell_var_offsets_filters(); + } else if (nullable) { + filters = array_schema_->cell_validity_filters(); + ; + } else { + filters = array_schema_->filters(name); + } // Append an encryption filter when necessary. RETURN_NOT_OK(FilterPipeline::append_encryption_filter( @@ -1475,7 +1685,7 @@ Status Writer::global_write_handle_last_tile() { Status Writer::filter_last_tiles( std::unordered_map>* tiles) const { // Initialize attribute and coordinate tiles - for (auto it : buffers_) + for (const auto& it : buffers_) (*tiles)[it.first] = std::vector(); // Prepare the tiles first @@ -1486,8 +1696,11 @@ Status Writer::filter_last_tiles( std::advance(buff_it, i); const auto& name = &(buff_it->first); - auto& last_tile = global_write_state_->last_tiles_[*name].first; - auto& last_tile_var = global_write_state_->last_tiles_[*name].second; + auto& last_tile = std::get<0>(global_write_state_->last_tiles_[*name]); + auto& last_tile_var = + std::get<1>(global_write_state_->last_tiles_[*name]); + auto& last_tile_validity = + std::get<2>(global_write_state_->last_tiles_[*name]); if (!last_tile.empty()) { std::vector& tiles_ref = (*tiles)[*name]; @@ -1496,6 +1709,8 @@ Status Writer::filter_last_tiles( tiles_ref.push_back(last_tile.clone(false)); if (!last_tile_var.empty()) tiles_ref.push_back(last_tile_var.clone(false)); + if (!last_tile_validity.empty()) + tiles_ref.push_back(last_tile_validity.clone(false)); } return Status::Ok(); }); @@ -1524,7 +1739,7 @@ bool Writer::all_last_tiles_empty() const { // See if any last attribute/coordinate tiles are nonempty for (const auto& it : buffers_) { const auto& name = it.first; - auto& last_tile = global_write_state_->last_tiles_[name].first; + auto& last_tile = std::get<0>(global_write_state_->last_tiles_[name]); if (!last_tile.empty()) return false; } @@ -1549,18 +1764,33 @@ Status Writer::init_global_write_state() { for (const auto& it : buffers_) { // Initialize last tiles const auto& name = it.first; - auto last_tile_pair = std::pair>( - name, std::pair(Tile(), Tile())); - auto it_ret = global_write_state_->last_tiles_.emplace(last_tile_pair); + auto last_tile_tuple = std::pair>( + name, std::tuple(Tile(), Tile(), Tile())); + auto it_ret = global_write_state_->last_tiles_.emplace(last_tile_tuple); if (!array_schema_->var_size(name)) { - auto& last_tile = it_ret.first->second.first; - RETURN_NOT_OK_ELSE(init_tile(name, &last_tile), clean_up(uri)); + auto& last_tile = std::get<0>(it_ret.first->second); + if (!array_schema_->is_nullable(name)) { + RETURN_NOT_OK_ELSE(init_tile(name, &last_tile), clean_up(uri)); + } else { + auto& last_tile_validity = std::get<2>(it_ret.first->second); + RETURN_NOT_OK_ELSE( + init_tile_nullable(name, &last_tile, &last_tile_validity), + clean_up(uri)); + } } else { - auto& last_tile = it_ret.first->second.first; - auto& last_tile_var = it_ret.first->second.second; - RETURN_NOT_OK_ELSE( - init_tile(name, &last_tile, &last_tile_var), clean_up(uri)); + auto& last_tile = std::get<0>(it_ret.first->second); + auto& last_tile_var = std::get<1>(it_ret.first->second); + if (!array_schema_->is_nullable(name)) { + RETURN_NOT_OK_ELSE( + init_tile(name, &last_tile, &last_tile_var), clean_up(uri)); + } else { + auto& last_tile_validity = std::get<2>(it_ret.first->second); + RETURN_NOT_OK_ELSE( + init_tile_nullable( + name, &last_tile, &last_tile_var, &last_tile_validity), + clean_up(uri)); + } } // Initialize cells written @@ -1607,6 +1837,60 @@ Status Writer::init_tile( return Status::Ok(); } +Status Writer::init_tile_nullable( + const std::string& name, Tile* tile, Tile* tile_validity) const { + // For easy reference + auto cell_size = array_schema_->cell_size(name); + auto type = array_schema_->type(name); + auto domain = array_schema_->domain(); + auto capacity = array_schema_->capacity(); + auto cell_num_per_tile = has_coords_ ? capacity : domain->cell_num_per_tile(); + auto tile_size = cell_num_per_tile * cell_size; + + // Initialize + RETURN_NOT_OK(tile->init_unfiltered( + constants::format_version, type, tile_size, cell_size, 0)); + RETURN_NOT_OK(tile_validity->init_unfiltered( + constants::format_version, + constants::cell_validity_type, + tile_size, + constants::cell_validity_size, + 0)); + + return Status::Ok(); +} + +Status Writer::init_tile_nullable( + const std::string& name, + Tile* tile, + Tile* tile_var, + Tile* tile_validity) const { + // For easy reference + auto type = array_schema_->type(name); + auto domain = array_schema_->domain(); + auto capacity = array_schema_->capacity(); + auto cell_num_per_tile = has_coords_ ? capacity : domain->cell_num_per_tile(); + auto tile_size = cell_num_per_tile * constants::cell_var_offset_size; + + // Initialize + RETURN_NOT_OK(tile->init_unfiltered( + constants::format_version, + constants::cell_var_offset_type, + tile_size, + constants::cell_var_offset_size, + 0)); + RETURN_NOT_OK(tile_var->init_unfiltered( + constants::format_version, type, tile_size, datatype_size(type), 0)); + RETURN_NOT_OK(tile_validity->init_unfiltered( + constants::format_version, + constants::cell_validity_type, + tile_size, + constants::cell_validity_size, + 0)); + + return Status::Ok(); +} + template Status Writer::init_tile_dense_cell_range_iters( std::vector>* iters) const { @@ -1673,14 +1957,25 @@ Status Writer::init_tiles( uint64_t tile_num, std::vector* tiles) const { // Initialize tiles - bool var_size = array_schema_->var_size(name); - auto tiles_len = (var_size) ? 2 * tile_num : tile_num; + const bool var_size = array_schema_->var_size(name); + const bool nullable = array_schema_->is_nullable(name); + const size_t t = + 1 + static_cast(var_size) + static_cast(nullable); + const size_t tiles_len = t * tile_num; tiles->resize(tiles_len); - for (size_t i = 0; i < tiles_len; i += (1 + var_size)) { + for (size_t i = 0; i < tiles_len; i += t) { if (!var_size) { - RETURN_NOT_OK(init_tile(name, &((*tiles)[i]))); + if (nullable) + RETURN_NOT_OK( + init_tile_nullable(name, &((*tiles)[i]), &((*tiles)[i + 1]))); + else + RETURN_NOT_OK(init_tile(name, &((*tiles)[i]))); } else { - RETURN_NOT_OK(init_tile(name, &((*tiles)[i]), &((*tiles)[i + 1]))); + if (nullable) + RETURN_NOT_OK(init_tile_nullable( + name, &((*tiles)[i]), &((*tiles)[i + 1]), &((*tiles)[i + 2]))); + else + RETURN_NOT_OK(init_tile(name, &((*tiles)[i]), &((*tiles)[i + 1]))); } } @@ -1834,22 +2129,6 @@ Status Writer::prepare_and_filter_attr_tiles( const auto& attr = buff_it->first; auto& tiles = (*attr_tiles)[attr]; RETURN_CANCEL_OR_ERROR(prepare_tiles(attr, write_cell_ranges, &tiles)); - - /* - if (i == 0) { - // Gather stats - const uint64_t tile_num = write_cell_ranges.size(); - uint64_t cell_num = 0; - auto var_size = array_schema_->var_size(attr); - for (size_t t = 0; t < tile_num; ++t) - cell_num += var_size ? tiles[2 * t].cell_num() : - tiles[t].cell_num(); - STATS_ADD_COUNTER(stats::Stats::CounterType::WRITE_CELL_NUM, - cell_num); - STATS_ADD_COUNTER(stats::Stats::CounterType::WRITE_TILE_NUM, - tile_num); - } - */ RETURN_CANCEL_OR_ERROR(filter_tiles(attr, &tiles)); return Status::Ok(); }); @@ -1906,8 +2185,10 @@ Status Writer::prepare_full_tiles_fixed( const std::set& coord_dups, std::vector* tiles) const { // For easy reference + auto nullable = array_schema_->is_nullable(name); auto it = buffers_.find(name); auto buffer = (unsigned char*)it->second.buffer_; + auto buffer_validity = (unsigned char*)it->second.validity_vector_.buffer(); auto buffer_size = it->second.buffer_size_; auto cell_size = array_schema_->cell_size(name); auto capacity = array_schema_->capacity(); @@ -1920,13 +2201,20 @@ Status Writer::prepare_full_tiles_fixed( return Status::Ok(); // First fill the last tile - auto& last_tile = global_write_state_->last_tiles_[name].first; + auto& last_tile = std::get<0>(global_write_state_->last_tiles_[name]); + auto& last_tile_validity = + std::get<2>(global_write_state_->last_tiles_[name]); uint64_t cell_idx = 0; if (!last_tile.empty()) { if (coord_dups.empty()) { do { RETURN_NOT_OK( last_tile.write(buffer + cell_idx * cell_size, cell_size)); + if (nullable) { + RETURN_NOT_OK(last_tile_validity.write( + buffer_validity + cell_idx * constants::cell_validity_size, + constants::cell_validity_size)); + } ++cell_idx; } while (!last_tile.full() && cell_idx != cell_num); } else { @@ -1934,6 +2222,11 @@ Status Writer::prepare_full_tiles_fixed( if (coord_dups.find(cell_idx) == coord_dups.end()) { RETURN_NOT_OK( last_tile.write(buffer + cell_idx * cell_size, cell_size)); + if (nullable) { + RETURN_NOT_OK(last_tile_validity.write( + buffer_validity + cell_idx * constants::cell_validity_size, + constants::cell_validity_size)); + } } ++cell_idx; } while (!last_tile.full() && cell_idx != cell_num); @@ -1947,26 +2240,46 @@ Status Writer::prepare_full_tiles_fixed( (full_tile_num - last_tile.full()) * cell_num_per_tile; if (full_tile_num > 0) { - tiles->resize(full_tile_num); - for (auto& tile : (*tiles)) - RETURN_NOT_OK(init_tile(name, &tile)); + const uint64_t t = 1 + (nullable ? 1 : 0); + tiles->resize(t * full_tile_num); + + for (uint64_t i = 0; i < tiles->size(); i += t) + if (!nullable) + RETURN_NOT_OK(init_tile(name, &((*tiles)[i]))); + else + RETURN_NOT_OK( + init_tile_nullable(name, &((*tiles)[i]), &((*tiles)[i + 1]))); // Handle last tile (it must be either full or empty) if (last_tile.full()) { (*tiles)[0] = last_tile; last_tile.reset(); + if (nullable) { + (*tiles)[1] = last_tile_validity; + last_tile_validity.reset(); + } } else { assert(last_tile.empty()); + if (nullable) { + assert(last_tile_validity.empty()); + } } // Write all remaining cells one by one if (coord_dups.empty()) { for (uint64_t tile_idx = 0, i = 0; i < cell_num_to_write;) { if ((*tiles)[tile_idx].full()) - ++tile_idx; + tile_idx += t; RETURN_NOT_OK((*tiles)[tile_idx].write( buffer + cell_idx * cell_size, cell_size * cell_num_per_tile)); + + if (nullable) { + RETURN_NOT_OK((*tiles)[tile_idx + 1].write( + buffer_validity + cell_idx * constants::cell_validity_size, + constants::cell_validity_size * cell_num_per_tile)); + } + cell_idx += cell_num_per_tile; i += cell_num_per_tile; } @@ -1975,10 +2288,16 @@ Status Writer::prepare_full_tiles_fixed( ++cell_idx, ++i) { if (coord_dups.find(cell_idx) == coord_dups.end()) { if ((*tiles)[tile_idx].full()) - ++tile_idx; + tile_idx += t; RETURN_NOT_OK((*tiles)[tile_idx].write( buffer + cell_idx * cell_size, cell_size)); + + if (nullable) { + RETURN_NOT_OK((*tiles)[tile_idx + 1].write( + buffer_validity + cell_idx * constants::cell_validity_size, + constants::cell_validity_size)); + } } } } @@ -1989,12 +2308,22 @@ Status Writer::prepare_full_tiles_fixed( if (coord_dups.empty()) { for (; cell_idx < cell_num; ++cell_idx) { RETURN_NOT_OK(last_tile.write(buffer + cell_idx * cell_size, cell_size)); + if (nullable) { + RETURN_NOT_OK(last_tile_validity.write( + buffer_validity + cell_idx * constants::cell_validity_size, + constants::cell_validity_size)); + } } } else { for (; cell_idx < cell_num; ++cell_idx) { if (coord_dups.find(cell_idx) == coord_dups.end()) RETURN_NOT_OK( last_tile.write(buffer + cell_idx * cell_size, cell_size)); + if (nullable) { + RETURN_NOT_OK(last_tile_validity.write( + buffer_validity + cell_idx * constants::cell_validity_size, + constants::cell_validity_size)); + } } } @@ -2009,14 +2338,17 @@ Status Writer::prepare_full_tiles_var( std::vector* tiles) const { // For easy reference auto it = buffers_.find(name); + auto nullable = array_schema_->is_nullable(name); auto buffer = (uint64_t*)it->second.buffer_; auto buffer_var = (unsigned char*)it->second.buffer_var_; + auto buffer_validity = (unsigned char*)it->second.validity_vector_.buffer(); auto buffer_size = it->second.buffer_size_; auto buffer_var_size = it->second.buffer_var_size_; auto capacity = array_schema_->capacity(); auto cell_num = *buffer_size / constants::cell_var_offset_size; auto domain = array_schema_->domain(); auto cell_num_per_tile = has_coords_ ? capacity : domain->cell_num_per_tile(); + auto attr_datatype_size = datatype_size(array_schema_->type(name)); uint64_t offset, var_size; // Do nothing if there are no cells to write @@ -2024,40 +2356,59 @@ Status Writer::prepare_full_tiles_var( return Status::Ok(); // First fill the last tile - auto& last_tile_pair = global_write_state_->last_tiles_[name]; - auto& last_tile = last_tile_pair.first; - auto& last_tile_var = last_tile_pair.second; + auto& last_tile_tuple = global_write_state_->last_tiles_[name]; + auto& last_tile = std::get<0>(last_tile_tuple); + auto& last_tile_var = std::get<1>(last_tile_tuple); + auto& last_tile_validity = std::get<2>(last_tile_tuple); + + (void)last_tile_validity; + (void)nullable; + (void)buffer_validity; uint64_t cell_idx = 0; if (!last_tile.empty()) { if (coord_dups.empty()) { do { - // Write offset + // Write offset. offset = last_tile_var.size(); RETURN_NOT_OK(last_tile.write(&offset, sizeof(offset))); - // Write var-sized value + // Write var-sized value(s). var_size = (cell_idx == cell_num - 1) ? *buffer_var_size - buffer[cell_idx] : buffer[cell_idx + 1] - buffer[cell_idx]; RETURN_NOT_OK( - last_tile_var.write(&buffer_var[buffer[cell_idx]], var_size)); + last_tile_var.write(buffer_var + buffer[cell_idx], var_size)); + + // Write validity value(s). + if (nullable) + RETURN_NOT_OK(last_tile_validity.write( + buffer_validity + (buffer[cell_idx] / attr_datatype_size * + constants::cell_validity_size), + var_size / attr_datatype_size * constants::cell_validity_size)); ++cell_idx; } while (!last_tile.full() && cell_idx != cell_num); } else { do { if (coord_dups.find(cell_idx) == coord_dups.end()) { - // Write offset + // Write offset. offset = last_tile_var.size(); RETURN_NOT_OK(last_tile.write(&offset, sizeof(offset))); - // Write var-sized value + // Write var-sized value(s). var_size = (cell_idx == cell_num - 1) ? *buffer_var_size - buffer[cell_idx] : buffer[cell_idx + 1] - buffer[cell_idx]; RETURN_NOT_OK( - last_tile_var.write(&buffer_var[buffer[cell_idx]], var_size)); + last_tile_var.write(buffer_var + buffer[cell_idx], var_size)); + + // Write validity value(s). + if (nullable) + RETURN_NOT_OK(last_tile_validity.write( + buffer_validity + (buffer[cell_idx] / attr_datatype_size * + constants::cell_validity_size), + var_size / attr_datatype_size * constants::cell_validity_size)); } ++cell_idx; @@ -2072,10 +2423,15 @@ Status Writer::prepare_full_tiles_var( (full_tile_num - last_tile.full()) * cell_num_per_tile; if (full_tile_num > 0) { - tiles->resize(2 * full_tile_num); + const uint64_t t = 2 + (nullable ? 1 : 0); + tiles->resize(t * full_tile_num); auto tiles_len = tiles->size(); - for (uint64_t i = 0; i < tiles_len; i += 2) - RETURN_NOT_OK(init_tile(name, &((*tiles)[i]), &((*tiles)[i + 1]))); + for (uint64_t i = 0; i < tiles_len; i += t) + if (!nullable) + RETURN_NOT_OK(init_tile(name, &((*tiles)[i]), &((*tiles)[i + 1]))); + else + RETURN_NOT_OK(init_tile_nullable( + name, &((*tiles)[i]), &((*tiles)[i + 1]), &((*tiles)[i + 2]))); // Handle last tile (it must be either full or empty) if (last_tile.full()) { @@ -2083,9 +2439,15 @@ Status Writer::prepare_full_tiles_var( last_tile.reset(); (*tiles)[1] = last_tile_var; last_tile_var.reset(); + if (nullable) { + (*tiles)[2] = last_tile_validity; + last_tile_validity.reset(); + } } else { assert(last_tile.empty()); assert(last_tile_var.empty()); + if (nullable) + assert(last_tile_validity.empty()); } // Write all remaining cells one by one @@ -2093,36 +2455,50 @@ Status Writer::prepare_full_tiles_var( for (uint64_t tile_idx = 0, i = 0; i < cell_num_to_write; ++cell_idx, ++i) { if ((*tiles)[tile_idx].full()) - tile_idx += 2; + tile_idx += t; - // Write offset + // Write offset. offset = (*tiles)[tile_idx + 1].size(); RETURN_NOT_OK((*tiles)[tile_idx].write(&offset, sizeof(offset))); - // Write var-sized value + // Write var-sized value(s). var_size = (cell_idx == cell_num - 1) ? *buffer_var_size - buffer[cell_idx] : buffer[cell_idx + 1] - buffer[cell_idx]; RETURN_NOT_OK((*tiles)[tile_idx + 1].write( - &buffer_var[buffer[cell_idx]], var_size)); + buffer_var + buffer[cell_idx], var_size)); + + // Write validity value(s). + if (nullable) + RETURN_NOT_OK((*tiles)[tile_idx + 2].write( + buffer_validity + (buffer[cell_idx] / attr_datatype_size * + constants::cell_validity_size), + var_size / attr_datatype_size * constants::cell_validity_size)); } } else { for (uint64_t tile_idx = 0, i = 0; i < cell_num_to_write; ++cell_idx, ++i) { if (coord_dups.find(cell_idx) == coord_dups.end()) { if ((*tiles)[tile_idx].full()) - tile_idx += 2; + tile_idx += t; - // Write offset + // Write offset. offset = (*tiles)[tile_idx + 1].size(); RETURN_NOT_OK((*tiles)[tile_idx].write(&offset, sizeof(offset))); - // Write var-sized value + // Write var-sized value(s). var_size = (cell_idx == cell_num - 1) ? *buffer_var_size - buffer[cell_idx] : buffer[cell_idx + 1] - buffer[cell_idx]; RETURN_NOT_OK((*tiles)[tile_idx + 1].write( - &buffer_var[buffer[cell_idx]], var_size)); + buffer_var + buffer[cell_idx], var_size)); + + // Write validity value(s). + if (nullable) + RETURN_NOT_OK((*tiles)[tile_idx + 2].write( + buffer_validity + (buffer[cell_idx] / attr_datatype_size * + constants::cell_validity_size), + var_size / attr_datatype_size * constants::cell_validity_size)); } } } @@ -2132,30 +2508,44 @@ Status Writer::prepare_full_tiles_var( assert(cell_num - cell_idx < cell_num_per_tile - last_tile.cell_num()); if (coord_dups.empty()) { for (; cell_idx < cell_num; ++cell_idx) { - // Write offset + // Write offset. offset = last_tile_var.size(); RETURN_NOT_OK(last_tile.write(&offset, sizeof(offset))); - // Write var-sized value + // Write var-sized value(s). var_size = (cell_idx == cell_num - 1) ? *buffer_var_size - buffer[cell_idx] : buffer[cell_idx + 1] - buffer[cell_idx]; RETURN_NOT_OK( - last_tile_var.write(&buffer_var[buffer[cell_idx]], var_size)); + last_tile_var.write(buffer_var + buffer[cell_idx], var_size)); + + // Write validity value(s). + if (nullable) + RETURN_NOT_OK(last_tile_validity.write( + buffer_validity + (buffer[cell_idx] / attr_datatype_size * + constants::cell_validity_size), + var_size / attr_datatype_size * constants::cell_validity_size)); } } else { for (; cell_idx < cell_num; ++cell_idx) { if (coord_dups.find(cell_idx) == coord_dups.end()) { - // Write offset + // Write offset. offset = last_tile_var.size(); RETURN_NOT_OK(last_tile.write(&offset, sizeof(offset))); - // Write var-sized value + // Write var-sized value(s). var_size = (cell_idx == cell_num - 1) ? *buffer_var_size - buffer[cell_idx] : buffer[cell_idx + 1] - buffer[cell_idx]; RETURN_NOT_OK( - last_tile_var.write(&buffer_var[buffer[cell_idx]], var_size)); + last_tile_var.write(buffer_var + buffer[cell_idx], var_size)); + + // Write validity value(s). + if (nullable) + RETURN_NOT_OK(last_tile_validity.write( + buffer_validity + (buffer[cell_idx] / attr_datatype_size * + constants::cell_validity_size), + var_size / attr_datatype_size * constants::cell_validity_size)); } } } @@ -2175,13 +2565,17 @@ Status Writer::prepare_tiles( return Status::Ok(); // For easy reference + auto nullable = array_schema_->is_nullable(attribute); auto var_size = array_schema_->var_size(attribute); auto it = buffers_.find(attribute); auto buffer = (uint64_t*)it->second.buffer_; auto buffer_var = (uint64_t*)it->second.buffer_var_; + auto buffer_validity = (uint64_t*)it->second.validity_vector_.buffer(); auto buffer_size = it->second.buffer_size_; auto buffer_var_size = it->second.buffer_var_size_; + auto buffer_validity_size = it->second.validity_vector_.buffer_size(); auto cell_val_num = array_schema_->cell_val_num(attribute); + auto attr_datatype_size = datatype_size(array_schema_->type(attribute)); // Initialize tiles and buffer RETURN_NOT_OK(init_tiles(attribute, tile_num, tiles)); @@ -2189,47 +2583,102 @@ Status Writer::prepare_tiles( auto buff_var = (!var_size) ? nullptr : std::make_shared(buffer_var, *buffer_var_size); + auto buff_validity = + (!nullable) ? + nullptr : + std::make_shared(buffer_validity, *buffer_validity_size); // Populate each tile with the write cell ranges - uint64_t end_pos = array_schema_->domain()->cell_num_per_tile() - 1; - for (size_t i = 0, t = 0; i < tile_num; ++i, t += (var_size) ? 2 : 1) { + const uint64_t end_pos = array_schema_->domain()->cell_num_per_tile() - 1; + const uint64_t t_inc = 1 + (var_size ? 1 : 0) + (nullable ? 1 : 0); + for (size_t i = 0, t = 0; i < tile_num; ++i, t += t_inc) { uint64_t pos = 0; for (const auto& wcr : write_cell_ranges[i]) { // Write empty range if (wcr.pos_ > pos) { - if (var_size) - RETURN_NOT_OK(write_empty_cell_range_to_tile_var( - wcr.pos_ - pos, &(*tiles)[t], &(*tiles)[t + 1])); - else - RETURN_NOT_OK(write_empty_cell_range_to_tile( - (wcr.pos_ - pos) * cell_val_num, &(*tiles)[t])); + if (var_size) { + if (nullable) + RETURN_NOT_OK(write_empty_cell_range_to_tile_var_nullable( + wcr.pos_ - pos, + &(*tiles)[t], + &(*tiles)[t + 1], + &(*tiles)[t + 2])); + else + RETURN_NOT_OK(write_empty_cell_range_to_tile_var( + wcr.pos_ - pos, &(*tiles)[t], &(*tiles)[t + 1])); + } else { + if (nullable) + RETURN_NOT_OK(write_empty_cell_range_to_tile_nullable( + (wcr.pos_ - pos) * cell_val_num, + &(*tiles)[t], + &(*tiles)[t + 1])); + else + RETURN_NOT_OK(write_empty_cell_range_to_tile( + (wcr.pos_ - pos) * cell_val_num, &(*tiles)[t])); + } pos = wcr.pos_; } // Write (non-empty) range - if (var_size) - RETURN_NOT_OK(write_cell_range_to_tile_var( - buff.get(), - buff_var.get(), - wcr.start_, - wcr.end_, - &(*tiles)[t], - &(*tiles)[t + 1])); - else - RETURN_NOT_OK(write_cell_range_to_tile( - buff.get(), wcr.start_, wcr.end_, &(*tiles)[t])); + if (var_size) { + if (nullable) { + RETURN_NOT_OK(write_cell_range_to_tile_var_nullable( + buff.get(), + buff_var.get(), + buff_validity.get(), + wcr.start_, + wcr.end_, + attr_datatype_size, + &(*tiles)[t], + &(*tiles)[t + 1], + &(*tiles)[t + 2])); + } else + RETURN_NOT_OK(write_cell_range_to_tile_var( + buff.get(), + buff_var.get(), + wcr.start_, + wcr.end_, + &(*tiles)[t], + &(*tiles)[t + 1])); + } else { + if (nullable) + RETURN_NOT_OK(write_cell_range_to_tile_nullable( + buff.get(), + buff_validity.get(), + wcr.start_, + wcr.end_, + &(*tiles)[t], + &(*tiles)[t + 1])); + else + RETURN_NOT_OK(write_cell_range_to_tile( + buff.get(), wcr.start_, wcr.end_, &(*tiles)[t])); + } pos += wcr.end_ - wcr.start_ + 1; } // Write empty range if (pos <= end_pos) { - if (var_size) - RETURN_NOT_OK(write_empty_cell_range_to_tile_var( - end_pos - pos + 1, &(*tiles)[t], &(*tiles)[t + 1])); - else - RETURN_NOT_OK(write_empty_cell_range_to_tile( - (end_pos - pos + 1) * cell_val_num, &(*tiles)[t])); + if (var_size) { + if (nullable) { + RETURN_NOT_OK(write_empty_cell_range_to_tile_var_nullable( + end_pos - pos + 1, + &(*tiles)[t], + &(*tiles)[t + 1], + &(*tiles)[t + 2])); + } else + RETURN_NOT_OK(write_empty_cell_range_to_tile_var( + end_pos - pos + 1, &(*tiles)[t], &(*tiles)[t + 1])); + } else { + if (nullable) + RETURN_NOT_OK(write_empty_cell_range_to_tile_nullable( + (end_pos - pos + 1) * cell_val_num, + &(*tiles)[t], + &(*tiles)[t + 1])); + else + RETURN_NOT_OK(write_empty_cell_range_to_tile( + (end_pos - pos + 1) * cell_val_num, &(*tiles)[t])); + } } } @@ -2288,7 +2737,10 @@ Status Writer::prepare_tiles_fixed( return Status::Ok(); // For easy reference + auto nullable = array_schema_->is_nullable(name); auto buffer = (unsigned char*)buffers_.find(name)->second.buffer_; + auto buffer_validity = + (unsigned char*)buffers_.find(name)->second.validity_vector_.buffer(); auto cell_size = array_schema_->cell_size(name); auto cell_num = (uint64_t)cell_pos.size(); auto capacity = array_schema_->capacity(); @@ -2296,9 +2748,15 @@ Status Writer::prepare_tiles_fixed( auto tile_num = utils::math::ceil(cell_num - dups_num, capacity); // Initialize tiles - tiles->resize(tile_num); - for (auto& tile : (*tiles)) - RETURN_NOT_OK(init_tile(name, &tile)); + const uint64_t t = 1 + (nullable ? 1 : 0); + tiles->resize(t * tile_num); + for (uint64_t i = 0; i < tiles->size(); i += t) { + if (!nullable) + RETURN_NOT_OK(init_tile(name, &((*tiles)[i]))); + else + RETURN_NOT_OK( + init_tile_nullable(name, &((*tiles)[i]), &((*tiles)[i + 1]))); + } // Write all cells one by one if (dups_num == 0) { @@ -2308,6 +2766,10 @@ Status Writer::prepare_tiles_fixed( RETURN_NOT_OK((*tiles)[tile_idx].write( buffer + cell_pos[i] * cell_size, cell_size)); + if (nullable) + RETURN_NOT_OK((*tiles)[tile_idx + 1].write( + buffer_validity + cell_pos[i] * constants::cell_validity_size, + constants::cell_validity_size)); } } else { for (uint64_t i = 0, tile_idx = 0; i < cell_num; ++i) { @@ -2319,6 +2781,10 @@ Status Writer::prepare_tiles_fixed( RETURN_NOT_OK((*tiles)[tile_idx].write( buffer + cell_pos[i] * cell_size, cell_size)); + if (nullable) + RETURN_NOT_OK((*tiles)[tile_idx + 1].write( + buffer_validity + cell_pos[i] * constants::cell_validity_size, + constants::cell_validity_size)); } } @@ -2332,38 +2798,55 @@ Status Writer::prepare_tiles_var( std::vector* tiles) const { // For easy reference auto it = buffers_.find(name); + auto nullable = array_schema_->is_nullable(name); auto buffer = (uint64_t*)it->second.buffer_; auto buffer_var = (unsigned char*)it->second.buffer_var_; + auto buffer_validity = (unsigned char*)it->second.validity_vector_.buffer(); auto buffer_var_size = it->second.buffer_var_size_; auto cell_num = (uint64_t)cell_pos.size(); auto capacity = array_schema_->capacity(); auto dups_num = coord_dups.size(); auto tile_num = utils::math::ceil(cell_num - dups_num, capacity); + auto attr_datatype_size = datatype_size(array_schema_->type(name)); uint64_t offset; uint64_t var_size; // Initialize tiles - tiles->resize(2 * tile_num); + const uint64_t t = 2 + (nullable ? 1 : 0); + tiles->resize(t * tile_num); auto tiles_len = tiles->size(); - for (uint64_t i = 0; i < tiles_len; i += 2) - RETURN_NOT_OK(init_tile(name, &((*tiles)[i]), &((*tiles)[i + 1]))); + for (uint64_t i = 0; i < tiles_len; i += t) { + if (!nullable) + RETURN_NOT_OK(init_tile(name, &((*tiles)[i]), &((*tiles)[i + 1]))); + else + RETURN_NOT_OK(init_tile_nullable( + name, &((*tiles)[i]), &((*tiles)[i + 1]), &((*tiles)[i + 2]))); + } // Write all cells one by one if (dups_num == 0) { for (uint64_t i = 0, tile_idx = 0; i < cell_num; ++i) { if ((*tiles)[tile_idx].full()) - tile_idx += 2; + tile_idx += t; - // Write offset + // Write offset. offset = (*tiles)[tile_idx + 1].size(); RETURN_NOT_OK((*tiles)[tile_idx].write(&offset, sizeof(offset))); - // Write var-sized value + // Write var-sized value(s). var_size = (cell_pos[i] == cell_num - 1) ? *buffer_var_size - buffer[cell_pos[i]] : buffer[cell_pos[i] + 1] - buffer[cell_pos[i]]; RETURN_NOT_OK((*tiles)[tile_idx + 1].write( - &buffer_var[buffer[cell_pos[i]]], var_size)); + buffer_var + buffer[cell_pos[i]], var_size)); + + // Write validity value(s). + if (nullable) { + RETURN_NOT_OK((*tiles)[tile_idx + 2].write( + buffer_validity + (buffer[cell_pos[i]] / attr_datatype_size * + constants::cell_validity_size), + var_size / attr_datatype_size * constants::cell_validity_size)); + } } } else { for (uint64_t i = 0, tile_idx = 0; i < cell_num; ++i) { @@ -2371,18 +2854,26 @@ Status Writer::prepare_tiles_var( continue; if ((*tiles)[tile_idx].full()) - tile_idx += 2; + tile_idx += t; - // Write offset + // Write offset. offset = (*tiles)[tile_idx + 1].size(); RETURN_NOT_OK((*tiles)[tile_idx].write(&offset, sizeof(offset))); - // Write var-sized value + // Write var-sized value(s). var_size = (cell_pos[i] == cell_num - 1) ? *buffer_var_size - buffer[cell_pos[i]] : buffer[cell_pos[i] + 1] - buffer[cell_pos[i]]; RETURN_NOT_OK((*tiles)[tile_idx + 1].write( - &buffer_var[buffer[cell_pos[i]]], var_size)); + buffer_var + buffer[cell_pos[i]], var_size)); + + // Write validity value(s). + if (nullable) { + RETURN_NOT_OK((*tiles)[tile_idx + 2].write( + buffer_validity + (buffer[cell_pos[i]] / attr_datatype_size * + constants::cell_validity_size), + var_size / attr_datatype_size * constants::cell_validity_size)); + } } } @@ -2465,7 +2956,7 @@ Status Writer::split_coords_buffer() { if (buff.buffer_ == nullptr) RETURN_NOT_OK(Status::WriterError( "Cannot split coordinate buffers; memory allocation failed")); - buffers_[dim_name] = buff; + buffers_[dim_name] = std::move(buff); } // Split coordinates @@ -2521,8 +3012,10 @@ Status Writer::unordered_write() { // Set the number of tiles in the metadata auto it = tiles.begin(); - auto tile_num = array_schema_->var_size(it->first) ? it->second.size() / 2 : - it->second.size(); + const uint64_t tile_num_divisor = + 1 + (array_schema_->var_size(it->first) ? 1 : 0) + + (array_schema_->is_nullable(it->first) ? 1 : 0); + auto tile_num = it->second.size() / tile_num_divisor; frag_meta->set_num_tiles(tile_num); STATS_ADD_COUNTER(stats::Stats::CounterType::WRITE_TILE_NUM, tile_num); @@ -2566,6 +3059,24 @@ Status Writer::write_empty_cell_range_to_tile(uint64_t num, Tile* tile) const { return Status::Ok(); } +Status Writer::write_empty_cell_range_to_tile_nullable( + uint64_t num, Tile* tile, Tile* tile_validity) const { + auto type = tile->type(); + auto fill_size = datatype_size(type); + auto fill_value = constants::fill_value(type); + assert(fill_value != nullptr); + + for (uint64_t i = 0; i < num; ++i) { + RETURN_NOT_OK(tile->write(fill_value, fill_size)); + + // Write validity empty value + uint8_t empty_validity_value = 0; + RETURN_NOT_OK(tile_validity->write(&empty_validity_value, sizeof(uint8_t))); + } + + return Status::Ok(); +} + Status Writer::write_empty_cell_range_to_tile_var( uint64_t num, Tile* tile, Tile* tile_var) const { auto type = tile_var->type(); @@ -2585,11 +3096,53 @@ Status Writer::write_empty_cell_range_to_tile_var( return Status::Ok(); } +Status Writer::write_empty_cell_range_to_tile_var_nullable( + uint64_t num, Tile* tile, Tile* tile_var, Tile* tile_validity) const { + auto type = tile_var->type(); + auto fill_size = datatype_size(type); + auto fill_value = constants::fill_value(type); + assert(fill_value != nullptr); + + for (uint64_t i = 0; i < num; ++i) { + // Write next offset + const uint64_t next_offset = tile_var->size(); + RETURN_NOT_OK(tile->write(&next_offset, sizeof(uint64_t))); + + // Write variable-sized empty value + RETURN_NOT_OK(tile_var->write(fill_value, fill_size)); + + // Write validity empty value + uint8_t empty_validity_value = 0; + RETURN_NOT_OK(tile_validity->write(&empty_validity_value, sizeof(uint8_t))); + } + + return Status::Ok(); +} + Status Writer::write_cell_range_to_tile( ConstBuffer* buff, uint64_t start, uint64_t end, Tile* tile) const { - auto cell_size = tile->cell_size(); - buff->set_offset(start * cell_size); - return tile->write(buff, (end - start + 1) * cell_size); + auto fixed_cell_size = tile->cell_size(); + buff->set_offset(start * fixed_cell_size); + return tile->write(buff, (end - start + 1) * fixed_cell_size); +} + +Status Writer::write_cell_range_to_tile_nullable( + ConstBuffer* buff, + ConstBuffer* buff_validity, + uint64_t start, + uint64_t end, + Tile* tile, + Tile* tile_validity) const { + auto fixed_cell_size = tile->cell_size(); + buff->set_offset(start * fixed_cell_size); + RETURN_NOT_OK(tile->write(buff, (end - start + 1) * fixed_cell_size)); + + auto validity_cell_size = constants::cell_validity_size; + buff_validity->set_offset(start * validity_cell_size); + RETURN_NOT_OK(tile_validity->write( + buff_validity, (end - start + 1) * validity_cell_size)); + + return Status::Ok(); } Status Writer::write_cell_range_to_tile_var( @@ -2620,6 +3173,42 @@ Status Writer::write_cell_range_to_tile_var( return Status::Ok(); } +Status Writer::write_cell_range_to_tile_var_nullable( + ConstBuffer* buff, + ConstBuffer* buff_var, + ConstBuffer* buff_validity, + uint64_t start, + uint64_t end, + uint64_t attr_datatype_size, + Tile* tile, + Tile* tile_var, + Tile* tile_validity) const { + auto buff_cell_num = buff->size() / sizeof(uint64_t); + + for (auto i = start; i <= end; ++i) { + // Write next offset. + uint64_t next_offset = tile_var->size(); + RETURN_NOT_OK(tile->write(&next_offset, sizeof(uint64_t))); + + // Write variable-sized value(s). + auto last_cell = (i == buff_cell_num - 1); + auto start_offset = buff->value(i * sizeof(uint64_t)); + auto end_offset = last_cell ? + buff_var->size() : + buff->value((i + 1) * sizeof(uint64_t)); + auto cell_var_size = end_offset - start_offset; + buff_var->set_offset(start_offset); + RETURN_NOT_OK(tile_var->write(buff_var, cell_var_size)); + + // Write the validity value(s). + buff_validity->set_offset(start_offset / attr_datatype_size); + RETURN_NOT_OK(tile_validity->write( + buff_validity, cell_var_size / attr_datatype_size)); + } + + return Status::Ok(); +} + Status Writer::write_all_tiles( FragmentMetadata* frag_meta, std::unordered_map>* const tiles) const { @@ -2654,9 +3243,11 @@ Status Writer::write_tiles( return Status::Ok(); // For easy reference - bool var_size = array_schema_->var_size(name); + const bool var_size = array_schema_->var_size(name); + const bool nullable = array_schema_->is_nullable(name); const auto& uri = frag_meta->uri(name); const auto& var_uri = var_size ? frag_meta->var_uri(name) : URI(""); + const auto& validity_uri = nullable ? frag_meta->validity_uri(name) : URI(""); // Write tiles auto tile_num = tiles->size(); @@ -2674,6 +3265,16 @@ Status Writer::write_tiles( name, tile_id, tile->filtered_buffer()->size()); frag_meta->set_tile_var_size(name, tile_id, tile->pre_filtered_size()); } + + if (nullable) { + ++i; + + tile = &(*tiles)[i]; + RETURN_NOT_OK( + storage_manager_->write(validity_uri, tile->filtered_buffer())); + frag_meta->set_tile_validity_offset( + name, tile_id, tile->filtered_buffer()->size()); + } } // Close files, except in the case of global order @@ -2681,6 +3282,9 @@ Status Writer::write_tiles( RETURN_NOT_OK(storage_manager_->close_file(frag_meta->uri(name))); if (var_size) RETURN_NOT_OK(storage_manager_->close_file(frag_meta->var_uri(name))); + if (nullable) + RETURN_NOT_OK( + storage_manager_->close_file(frag_meta->validity_uri(name))); } return Status::Ok(); @@ -2694,8 +3298,7 @@ std::string Writer::coords_to_str(uint64_t i) const { for (unsigned d = 0; d < dim_num; ++d) { auto dim = array_schema_->dimension(d); const auto& dim_name = dim->name(); - auto buff = buffers_.find(dim_name)->second; - ss << dim->coord_to_str(buff, i); + ss << dim->coord_to_str(buffers_.find(dim_name)->second, i); if (d < dim_num - 1) ss << ", "; } @@ -2734,6 +3337,10 @@ void Writer::get_dim_attr_stats() const { } else { STATS_ADD_COUNTER(stats::Stats::CounterType::WRITE_ATTR_FIXED_NUM, 1); } + if (array_schema_->is_nullable(name)) { + STATS_ADD_COUNTER( + stats::Stats::CounterType::WRITE_ATTR_NULLABLE_NUM, 1); + } } else { STATS_ADD_COUNTER(stats::Stats::CounterType::WRITE_DIM_NUM, 1); if (var_size) { diff --git a/tiledb/sm/query/writer.h b/tiledb/sm/query/writer.h index e13bd10e8d6..3abe579af52 100644 --- a/tiledb/sm/query/writer.h +++ b/tiledb/sm/query/writer.h @@ -40,6 +40,8 @@ #include "tiledb/common/status.h" #include "tiledb/sm/fragment/written_fragment_info.h" #include "tiledb/sm/misc/types.h" +#include "tiledb/sm/query/query_buffer.h" +#include "tiledb/sm/query/validity_vector.h" #include "tiledb/sm/query/write_cell_slab_iter.h" #include "tiledb/sm/subarray/subarray.h" #include "tiledb/sm/tile/tile.h" @@ -70,9 +72,10 @@ class Writer { * Stores the last tile of each attribute/dimension for each write * operation. For fixed-sized attributes/dimensions, the second tile is * ignored. For var-sized attributes/dimensions, the first tile is the - * offsets tile, whereas the second tile is the values tile. + * offsets tile, whereas the second tile is the values tile. In both cases, + * the third tile stores a validity tile for nullable attributes. */ - std::unordered_map> last_tiles_; + std::unordered_map> last_tiles_; /** * Stores the number of cells written for each attribute/dimension across @@ -195,6 +198,42 @@ class Writer { void** buffer_val, uint64_t** buffer_val_size) const; + /** + * Retrieves the buffer of a fixed-sized, nullable attribute. + * + * @param name The buffer name. + * @param buffer The buffer to be retrieved. + * @param buffer_size A pointer to the buffer size to be retrieved. + * @param validity_vector A pointer to the validity vector to be retrieved. + * @return Status + */ + Status get_buffer_nullable( + const std::string& name, + void** buffer, + uint64_t** buffer_size, + const ValidityVector** validity_vector) const; + + /** + * Retrieves the offsets and values buffers of a var-sized, + * nullable attribute. + * + * @param name The buffer attribute/dimension. + * @param buffer_off The offsets buffer to be retrieved. + * @param buffer_off_size A pointer to the offsets buffer size to be + * retrieved. + * @param buffer_val The values buffer to be retrieved. + * @param buffer_val_size A pointer to the values buffer size to be retrieved. + * @param validity_vector A pointer to the validity vector to be retrieved. + * @return Status + */ + Status get_buffer_nullable( + const std::string& name, + uint64_t** buffer_off, + uint64_t** buffer_off_size, + void** buffer_val, + uint64_t** buffer_val_size, + const ValidityVector** validity_vector) const; + /** Returns current setting of check_coord_dups_ */ bool get_check_coord_dups() const; @@ -250,6 +289,45 @@ class Writer { void* buffer_val, uint64_t* buffer_val_size); + /** + * Sets the buffer for a fixed-sized, nullable attribute. + * + * @param name The attribute to set the buffer for. + * @param buffer The buffer that has the input data to be written. + * @param buffer_size The size of `buffer` in bytes. + * @param validity_vector The validity vector associated with values in + * `buffer`. + * @return Status + */ + Status set_buffer( + const std::string& name, + void* buffer, + uint64_t* buffer_size, + ValidityVector&& validity_vector); + + /** + * Sets the buffer for a var-sized, nullable attribute. + * + * @param name The attribute to set the buffer for. + * @param buffer_off The buffer that has the input data to be written, + * This buffer holds the starting offsets of each cell value in + * `buffer_val`. + * @param buffer_off_size The size of `buffer_off` in bytes. + * @param buffer_val The buffer that has the input data to be written. + * This buffer holds the actual var-sized cell values. + * @param buffer_val_size The size of `buffer_val` in bytes. + * @param validity_vector The validity vector associated with values in + * `buffer_val`. + * @return Status + */ + Status set_buffer( + const std::string& name, + uint64_t* buffer_off, + uint64_t* buffer_off_size, + void* buffer_val, + uint64_t* buffer_val_size, + ValidityVector&& validity_vector); + /** Sets current setting of check_coord_dups_ */ void set_check_coord_dups(bool b); @@ -570,9 +648,11 @@ class Writer { * @param tile The tile to be filtered. * @param offsets True if the tile to be filtered contains offsets for a * var-sized attribute/dimension. + * @param offsets True if the tile to be filtered contains validity values. * @return Status */ - Status filter_tile(const std::string& name, Tile* tile, bool offsets) const; + Status filter_tile( + const std::string& name, Tile* tile, bool offsets, bool nullable) const; /** Finalizes the global write state. */ Status finalize_global_write_state(); @@ -614,6 +694,32 @@ class Writer { */ Status init_tile(const std::string& name, Tile* tile, Tile* tile_var) const; + /** + * Initializes a fixed-sized, nullable tile. + * + * @param name The attribute the tile belongs to. + * @param tile The tile to be initialized. + * @param tile_validity The validity tile to be initialized. + * @return Status + */ + Status init_tile_nullable( + const std::string& name, Tile* tile, Tile* tile_validity) const; + + /** + * Initializes a var-sized, nullable tile. + * + * @param name The attribute the tile belongs to. + * @param tile The offsets tile to be initialized. + * @param tile_var The var-sized data tile to be initialized. + * @param tile_validity The validity tile to be initialized. + * @return Status + */ + Status init_tile_nullable( + const std::string& name, + Tile* tile, + Tile* tile_var, + Tile* tile_validity) const; + /** * Initializes dense cell range iterators for the subarray to be writte, * one per overlapping tile. @@ -896,6 +1002,18 @@ class Writer { */ Status write_empty_cell_range_to_tile(uint64_t num, Tile* tile) const; + /** + * Writes an empty cell range to the input tile. + * Applicable to **fixed-sized** attributes. + * + * @param num Number of empty values to write. + * @param tile The tile to write to. + * @param tile_validity The tile with the validity cells to write to. + * @return Status + */ + Status write_empty_cell_range_to_tile_nullable( + uint64_t num, Tile* tile, Tile* tile_validity) const; + /** * Writes an empty cell range to the input tile. * Applicable to **variable-sized** attributes. @@ -908,6 +1026,19 @@ class Writer { Status write_empty_cell_range_to_tile_var( uint64_t num, Tile* tile, Tile* tile_var) const; + /** + * Writes an empty cell range to the input tile. + * Applicable to **variable-sized** attributes. + * + * @param num Number of empty values to write. + * @param tile The tile offsets to write to. + * @param tile_var The tile with the var-sized cells to write to. + * @param tile_validity The tile with the validity cells to write to. + * @return Status + */ + Status write_empty_cell_range_to_tile_var_nullable( + uint64_t num, Tile* tile, Tile* tile_var, Tile* tile_validity) const; + /** * Writes the input cell range to the input tile, for a particular * buffer. Applicable to **fixed-sized** attributes. @@ -921,6 +1052,27 @@ class Writer { Status write_cell_range_to_tile( ConstBuffer* buff, uint64_t start, uint64_t end, Tile* tile) const; + /** + * Writes the input cell range to the input tile, for a particular + * buffer. Applicable to **fixed-sized** attributes. + * + * @param buff The write buffer where the cells will be copied from. + * @param buff_validity The write buffer where the validity cell values will + * be copied from. + * @param start The start element in the write buffer. + * @param end The end element in the write buffer. + * @param tile The tile to write to. + * @param tile_validity The validity tile to be initialized. + * @return Status + */ + Status write_cell_range_to_tile_nullable( + ConstBuffer* buff, + ConstBuffer* buff_validity, + uint64_t start, + uint64_t end, + Tile* tile, + Tile* tile_validity) const; + /** * Writes the input cell range to the input tile, for a particular * buffer. Applicable to **variable-sized** attributes. @@ -941,6 +1093,33 @@ class Writer { Tile* tile, Tile* tile_var) const; + /** + * Writes the input cell range to the input tile, for a particular + * buffer. Applicable to **variable-sized**, nullable attributes. + * + * @param buff The write buffer where the cell offsets will be copied from. + * @param buff_var The write buffer where the cell values will be copied from. + * @param buff_validity The write buffer where the validity cell values will + * be copied from. + * @param start The start element in the write buffer. + * @param end The end element in the write buffer. + * @param attr_datatype_size The size of each attribute value in `buff_var`. + * @param tile The tile offsets to write to. + * @param tile_var The tile with the var-sized cells to write to. + * @param tile_validity The validity tile to be initialized. + * @return Status + */ + Status write_cell_range_to_tile_var_nullable( + ConstBuffer* buff, + ConstBuffer* buff_var, + ConstBuffer* buff_validity, + uint64_t start, + uint64_t end, + uint64_t attr_datatype_size, + Tile* tile, + Tile* tile_var, + Tile* tile_validity) const; + /** * Writes all the input tiles to storage. * diff --git a/tiledb/sm/serialization/query.cc b/tiledb/sm/serialization/query.cc index de52775ed42..30005a7b516 100644 --- a/tiledb/sm/serialization/query.cc +++ b/tiledb/sm/serialization/query.cc @@ -225,8 +225,9 @@ Status subarray_partitioner_to_capnp( } // Overall mem budget - uint64_t mem_budget, mem_budget_var; - RETURN_NOT_OK(partitioner.get_memory_budget(&mem_budget, &mem_budget_var)); + uint64_t mem_budget, mem_budget_var, mem_budget_validity; + RETURN_NOT_OK(partitioner.get_memory_budget( + &mem_budget, &mem_budget_var, &mem_budget_validity)); builder->setMemoryBudget(mem_budget); builder->setMemoryBudgetVar(mem_budget_var); @@ -245,6 +246,7 @@ Status subarray_partitioner_from_capnp( uint64_t memory_budget_var = 0; RETURN_NOT_OK(tiledb::sm::utils::parse::convert( Config::SM_MEMORY_BUDGET_VAR, &memory_budget_var)); + uint64_t memory_budget_validity = 0; // Get subarray layout first Layout layout = Layout::ROW_MAJOR; @@ -255,7 +257,11 @@ Status subarray_partitioner_from_capnp( Subarray subarray(array, layout, false); RETURN_NOT_OK(subarray_from_capnp(reader.getSubarray(), &subarray)); *partitioner = SubarrayPartitioner( - subarray, memory_budget, memory_budget_var, compute_tp); + subarray, + memory_budget, + memory_budget_var, + memory_budget_validity, + compute_tp); // Per-attr mem budgets if (reader.hasBudget()) { @@ -314,7 +320,7 @@ Status subarray_partitioner_from_capnp( // Overall mem budget RETURN_NOT_OK(partitioner->set_memory_budget( - reader.getMemoryBudget(), reader.getMemoryBudgetVar())); + reader.getMemoryBudget(), reader.getMemoryBudgetVar(), 0)); return Status::Ok(); } @@ -1043,8 +1049,8 @@ Status query_est_result_size_reader_from_capnp( auto result_size = it.getValue(); est_result_sizes_map.emplace( name, - Subarray::ResultSize{result_size.getSizeFixed(), - result_size.getSizeVar()}); + Subarray::ResultSize{ + result_size.getSizeFixed(), result_size.getSizeVar(), 0}); } std::unordered_map max_memory_sizes_map; @@ -1053,8 +1059,8 @@ Status query_est_result_size_reader_from_capnp( auto memory_size = it.getValue(); max_memory_sizes_map.emplace( name, - Subarray::MemorySize{memory_size.getSizeFixed(), - memory_size.getSizeVar()}); + Subarray::MemorySize{ + memory_size.getSizeFixed(), memory_size.getSizeVar(), 0}); } return query->set_est_result_size(est_result_sizes_map, max_memory_sizes_map); diff --git a/tiledb/sm/stats/stats.cc b/tiledb/sm/stats/stats.cc index e18e8db9a33..6177104a6d9 100644 --- a/tiledb/sm/stats/stats.cc +++ b/tiledb/sm/stats/stats.cc @@ -216,6 +216,8 @@ std::string Stats::dump_write() const { counter_stats_.find(CounterType::WRITE_ATTR_FIXED_NUM)->second; auto write_attr_var_num = counter_stats_.find(CounterType::WRITE_ATTR_VAR_NUM)->second; + auto write_attr_nullable_num = + counter_stats_.find(CounterType::WRITE_ATTR_NULLABLE_NUM)->second; auto write_dim_num = counter_stats_.find(CounterType::WRITE_DIM_NUM)->second; auto write_dim_fixed_num = counter_stats_.find(CounterType::WRITE_DIM_FIXED_NUM)->second; @@ -234,6 +236,9 @@ std::string Stats::dump_write() const { counter_stats_.find(CounterType::WRITE_TILE_VAR_OFFSETS_SIZE)->second; auto tile_var_sizes_size = counter_stats_.find(CounterType::WRITE_TILE_VAR_SIZES_SIZE)->second; + auto tile_validity_offsets_size = + counter_stats_.find(CounterType::WRITE_TILE_VALIDITY_OFFSETS_SIZE) + ->second; auto frag_meta_footer_size = counter_stats_.find(CounterType::WRITE_FRAG_META_FOOTER_SIZE)->second; auto array_schema_size = @@ -286,7 +291,8 @@ std::string Stats::dump_write() const { auto total_meta_size = array_schema_size + frag_meta_footer_size + rtree_size + tile_offsets_size + - tile_var_offsets_size + tile_var_sizes_size; + tile_var_offsets_size + tile_var_sizes_size + + tile_validity_offsets_size; write_bytes(&ss, "- Total metadata written: ", total_meta_size); write_bytes(&ss, " * Array schema: ", array_schema_size); write_bytes(&ss, " * Fragment metadata footer: ", frag_meta_footer_size); @@ -294,6 +300,7 @@ std::string Stats::dump_write() const { write_bytes(&ss, " * Fixed-sized tile offsets: ", tile_offsets_size); write_bytes(&ss, " * Var-sized tile offsets: ", tile_var_offsets_size); write_bytes(&ss, " * Var-sized tile sizes: ", tile_var_sizes_size); + write_bytes(&ss, " * Validity tile sizes: ", tile_validity_offsets_size); ss << "\n"; if (write_array_meta_size != 0) { @@ -312,6 +319,9 @@ std::string Stats::dump_write() const { auto var_tiles = 2 * write_tile_num * (write_attr_var_num + write_dim_var_num); write(&ss, " * Number of var-sized physical tiles written: ", var_tiles); + auto validity_tiles = write_tile_num * write_attr_nullable_num; + write( + &ss, " * Number of validity physical tiles written: ", validity_tiles); ss << "\n"; write(&ss, "- Write time: ", write_time); @@ -435,6 +445,8 @@ std::string Stats::dump_read() const { counter_stats_.find(CounterType::READ_TILE_VAR_OFFSETS_SIZE)->second; auto tile_var_sizes_size = counter_stats_.find(CounterType::READ_TILE_VAR_SIZES_SIZE)->second; + auto tile_validity_offsets_size = + counter_stats_.find(CounterType::READ_TILE_VALIDITY_OFFSETS_SIZE)->second; auto read_array_meta_size = counter_stats_.find(CounterType::READ_ARRAY_META_SIZE)->second; auto read_num = counter_stats_.find(CounterType::READ_NUM)->second; @@ -445,6 +457,8 @@ std::string Stats::dump_read() const { counter_stats_.find(CounterType::READ_ATTR_FIXED_NUM)->second; auto read_attr_var_num = counter_stats_.find(CounterType::READ_ATTR_VAR_NUM)->second; + auto read_attr_nullable_num = + counter_stats_.find(CounterType::READ_ATTR_NULLABLE_NUM)->second; auto read_dim_fixed_num = counter_stats_.find(CounterType::READ_DIM_FIXED_NUM)->second; auto read_dim_var_num = @@ -468,6 +482,8 @@ std::string Stats::dump_read() const { (read_dim_fixed_num + read_dim_zipped_num + read_attr_fixed_num); auto read_var_phys_tiles_num = 2 * read_overlap_tile_num * (read_dim_var_num + read_attr_var_num); + auto read_validity_phys_tiles_num = + read_overlap_tile_num * read_attr_nullable_num; std::stringstream ss; if (read_num != 0) { @@ -497,7 +513,8 @@ std::string Stats::dump_read() const { write( &ss, "- Number of physical tiles read: ", - read_fixed_phys_tiles_num + read_var_phys_tiles_num); + read_fixed_phys_tiles_num + read_var_phys_tiles_num + + read_validity_phys_tiles_num); write( &ss, " * Number of physical fixed-sized tiles read: ", @@ -506,6 +523,10 @@ std::string Stats::dump_read() const { &ss, " * Number of physical var-sized tiles read: ", read_var_phys_tiles_num); + write( + &ss, + " * Number of physical validity tiles read: ", + read_validity_phys_tiles_num); write(&ss, "- Number of cells read: ", read_cell_num); write(&ss, "- Number of result cells: ", read_result_num); write_ratio( @@ -562,7 +583,8 @@ std::string Stats::dump_read() const { auto total_meta_size = array_schema_size + consolidated_frag_meta_size + frag_meta_size + rtree_size + tile_offsets_size + - tile_var_offsets_size + tile_var_sizes_size; + tile_var_offsets_size + tile_var_sizes_size + + tile_validity_offsets_size; write_bytes(&ss, "- Total metadata read: ", total_meta_size); write_bytes(&ss, " * Array schema: ", array_schema_size); write_bytes( @@ -574,6 +596,7 @@ std::string Stats::dump_read() const { write_bytes(&ss, " * Fixed-sized tile offsets: ", tile_offsets_size); write_bytes(&ss, " * Var-sized tile offsets: ", tile_var_offsets_size); write_bytes(&ss, " * Var-sized tile sizes: ", tile_var_sizes_size); + write_bytes(&ss, " * Validity tile offsets: ", tile_validity_offsets_size); ss << "\n"; if (read_load_array_meta != 0) { diff --git a/tiledb/sm/stats/stats.h b/tiledb/sm/stats/stats.h index b526191fe0f..a1753e8d2c5 100644 --- a/tiledb/sm/stats/stats.h +++ b/tiledb/sm/stats/stats.h @@ -150,12 +150,14 @@ class Stats { READ_TILE_OFFSETS_SIZE, READ_TILE_VAR_OFFSETS_SIZE, READ_TILE_VAR_SIZES_SIZE, + READ_TILE_VALIDITY_OFFSETS_SIZE, READ_ARRAY_META_SIZE, READ_NUM, READ_BYTE_NUM, READ_UNFILTERED_BYTE_NUM, READ_ATTR_FIXED_NUM, READ_ATTR_VAR_NUM, + READ_ATTR_NULLABLE_NUM, READ_DIM_FIXED_NUM, READ_DIM_VAR_NUM, READ_DIM_ZIPPED_NUM, @@ -168,6 +170,7 @@ class Stats { WRITE_ATTR_NUM, WRITE_ATTR_FIXED_NUM, WRITE_ATTR_VAR_NUM, + WRITE_ATTR_NULLABLE_NUM, WRITE_DIM_NUM, WRITE_DIM_FIXED_NUM, WRITE_DIM_VAR_NUM, @@ -178,6 +181,7 @@ class Stats { WRITE_TILE_OFFSETS_SIZE, WRITE_TILE_VAR_OFFSETS_SIZE, WRITE_TILE_VAR_SIZES_SIZE, + WRITE_TILE_VALIDITY_OFFSETS_SIZE, WRITE_FRAG_META_FOOTER_SIZE, WRITE_ARRAY_SCHEMA_SIZE, WRITE_TILE_NUM, diff --git a/tiledb/sm/subarray/subarray.cc b/tiledb/sm/subarray/subarray.cc index 8401462e1f1..1a887e9a086 100644 --- a/tiledb/sm/subarray/subarray.cc +++ b/tiledb/sm/subarray/subarray.cc @@ -530,6 +530,12 @@ Status Subarray::get_est_result_size( Status::SubarrayError("Cannot get estimated result size; " "Attribute/Dimension must be fixed-sized")); + // Check if attribute/dimension is nullable + if (array_schema->is_nullable(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute/Dimension must not be nullable")); + // Compute tile overlap for each fragment RETURN_NOT_OK(compute_est_result_size(compute_tp)); *size = static_cast(ceil(est_result_size_[name].size_fixed_)); @@ -576,6 +582,12 @@ Status Subarray::get_est_result_size( Status::SubarrayError("Cannot get estimated result size; " "Attribute/Dimension must be var-sized")); + // Check if attribute/dimension is nullable + if (array_schema->is_nullable(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute/Dimension must not be nullable")); + // Compute tile overlap for each fragment RETURN_NOT_OK(compute_est_result_size(compute_tp)); *size_off = static_cast(ceil(est_result_size_[name].size_fixed_)); @@ -599,6 +611,130 @@ Status Subarray::get_est_result_size( return Status::Ok(); } +Status Subarray::get_est_result_size_nullable( + const char* name, + uint64_t* size, + uint64_t* size_validity, + ThreadPool* const compute_tp) { + // Check attribute/dimension name + if (name == nullptr) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute name cannot be null")); + + // Check size pointer + if (size == nullptr || size_validity == nullptr) + return LOG_STATUS(Status::SubarrayError( + "Cannot get estimated result size; Input sizes cannot be null")); + + // Check if name is attribute + const auto array_schema = array_->array_schema(); + const bool is_attr = array_schema->is_attr(name); + + // Check if attribute exists + if (!is_attr) + return LOG_STATUS(Status::SubarrayError( + std::string("Cannot get estimated result size; Attribute '") + name + + "' does not exist")); + + // Check if the attribute is fixed-sized + if (array_schema->var_size(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute must be fixed-sized")); + + // Check if attribute is nullable + if (!array_schema->is_nullable(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute must be nullable")); + + // Compute tile overlap for each fragment + RETURN_NOT_OK(compute_est_result_size(compute_tp)); + *size = static_cast(ceil(est_result_size_[name].size_fixed_)); + *size_validity = + static_cast(ceil(est_result_size_[name].size_validity_)); + + // If the size is non-zero, ensure it is large enough to + // contain at least one cell. + const auto cell_size = array_schema->cell_size(name); + if (*size > 0 && *size < cell_size) { + *size = cell_size; + *size_validity = 1; + } + + return Status::Ok(); +} + +Status Subarray::get_est_result_size_nullable( + const char* name, + uint64_t* size_off, + uint64_t* size_val, + uint64_t* size_validity, + ThreadPool* const compute_tp) { + // Check attribute/dimension name + if (name == nullptr) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute name cannot be null")); + + // Check size pointer + if (size_off == nullptr || size_val == nullptr || size_validity == nullptr) + return LOG_STATUS(Status::SubarrayError( + "Cannot get estimated result size; Input sizes cannot be null")); + + // Check if name is attribute + const auto array_schema = array_->array_schema(); + const bool is_attr = array_schema->is_attr(name); + + // Check if attribute exists + if (!is_attr) + return LOG_STATUS(Status::SubarrayError( + std::string("Cannot get estimated result size; Attribute '") + name + + "' does not exist")); + + // Check if the attribute is var-sized + if (!array_schema->var_size(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute must be var-sized")); + + // Check if attribute is nullable + if (!array_schema->is_nullable(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute must be nullable")); + + // Compute tile overlap for each fragment + RETURN_NOT_OK(compute_est_result_size(compute_tp)); + *size_off = static_cast(ceil(est_result_size_[name].size_fixed_)); + *size_val = static_cast(ceil(est_result_size_[name].size_var_)); + *size_validity = + static_cast(ceil(est_result_size_[name].size_validity_)); + + // If the value size is non-zero, ensure both it and the offset and + // validity sizes are large enough to contain at least one cell. Otherwise, + // ensure the offset and validity sizes are also zero. + if (*size_val > 0) { + const uint64_t off_cell_size = constants::cell_var_offset_size; + if (*size_off < off_cell_size) + *size_off = off_cell_size; + + const uint64_t val_cell_size = datatype_size(array_schema->type(name)); + if (*size_val < val_cell_size) + *size_val = val_cell_size; + + const uint64_t validity_cell_size = constants::cell_validity_size; + if (*size_validity < validity_cell_size) + *size_validity = validity_cell_size; + } else { + *size_off = 0; + *size_validity = 0; + } + + return Status::Ok(); +} + Status Subarray::get_max_memory_size( const char* name, uint64_t* size, ThreadPool* const compute_tp) { // Check attribute/dimension name @@ -609,7 +745,7 @@ Status Subarray::get_max_memory_size( // Check size pointer if (size == nullptr) return LOG_STATUS(Status::SubarrayError( - "Cannot get max memory size; Inpute size cannot be null")); + "Cannot get max memory size; Input size cannot be null")); // Check if name is attribute or dimension auto array_schema = array_->array_schema(); @@ -627,6 +763,12 @@ Status Subarray::get_max_memory_size( return LOG_STATUS(Status::SubarrayError( "Cannot get max memory size; Attribute/Dimension must be fixed-sized")); + // Check if attribute/dimension is nullable + if (array_schema->is_nullable(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute/Dimension must not be nullable")); + // Compute tile overlap for each fragment compute_est_result_size(compute_tp); *size = max_mem_size_[name].size_fixed_; @@ -665,10 +807,106 @@ Status Subarray::get_max_memory_size( return LOG_STATUS(Status::SubarrayError( "Cannot get max memory size; Attribute/Dimension must be var-sized")); + // Check if attribute/dimension is nullable + if (array_schema->is_nullable(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute/Dimension must not be nullable")); + + // Compute tile overlap for each fragment + compute_est_result_size(compute_tp); + *size_off = max_mem_size_[name].size_fixed_; + *size_val = max_mem_size_[name].size_var_; + + return Status::Ok(); +} + +Status Subarray::get_max_memory_size_nullable( + const char* name, + uint64_t* size, + uint64_t* size_validity, + ThreadPool* const compute_tp) { + // Check attribute name + if (name == nullptr) + return LOG_STATUS(Status::SubarrayError( + "Cannot get max memory size; Attribute cannot be null")); + + // Check size pointer + if (size == nullptr || size_validity == nullptr) + return LOG_STATUS(Status::SubarrayError( + "Cannot get max memory size; Input sizes cannot be null")); + + // Check if name is attribute + auto array_schema = array_->array_schema(); + bool is_attr = array_schema->is_attr(name); + + // Check if attribute exists + if (!is_attr) + return LOG_STATUS(Status::SubarrayError( + std::string("Cannot get max memory size; Attribute '") + name + + "' does not exist")); + + // Check if the attribute is fixed-sized + if (array_schema->var_size(name)) + return LOG_STATUS(Status::SubarrayError( + "Cannot get max memory size; Attribute must be fixed-sized")); + + // Check if attribute is nullable + if (!array_schema->is_nullable(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute must be nullable")); + + // Compute tile overlap for each fragment + compute_est_result_size(compute_tp); + *size = max_mem_size_[name].size_fixed_; + *size_validity = max_mem_size_[name].size_validity_; + + return Status::Ok(); +} + +Status Subarray::get_max_memory_size_nullable( + const char* name, + uint64_t* size_off, + uint64_t* size_val, + uint64_t* size_validity, + ThreadPool* const compute_tp) { + // Check attribute/dimension name + if (name == nullptr) + return LOG_STATUS(Status::SubarrayError( + "Cannot get max memory size; Attribute/Dimension cannot be null")); + + // Check size pointer + if (size_off == nullptr || size_val == nullptr || size_validity == nullptr) + return LOG_STATUS(Status::SubarrayError( + "Cannot get max memory size; Input sizes cannot be null")); + + // Check if name is attribute or dimension + auto array_schema = array_->array_schema(); + bool is_attr = array_schema->is_attr(name); + + // Check if attribute exists + if (!is_attr) + return LOG_STATUS(Status::SubarrayError( + std::string("Cannot get max memory size; Attribute '") + name + + "' does not exist")); + + // Check if the attribute is var-sized + if (!array_schema->var_size(name)) + return LOG_STATUS(Status::SubarrayError( + "Cannot get max memory size; Attribute/Dimension must be var-sized")); + + // Check if attribute is nullable + if (!array_schema->is_nullable(name)) + return LOG_STATUS( + Status::SubarrayError("Cannot get estimated result size; " + "Attribute must be nullable")); + // Compute tile overlap for each fragment compute_est_result_size(compute_tp); *size_off = max_mem_size_[name].size_fixed_; *size_val = max_mem_size_[name].size_var_; + *size_validity = max_mem_size_[name].size_validity_; return Status::Ok(); } @@ -945,11 +1183,15 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( for (size_t r = 0; r < range_num; ++r) (*result_sizes)[r].reserve(names.size()); - // Create vector of var sizes + // Create vector of var and validity flags std::vector var_sizes; + std::vector nullable; var_sizes.reserve(names.size()); - for (const auto& name : names) + nullable.reserve(names.size()); + for (const auto& name : names) { var_sizes.push_back(array_schema->var_size(name)); + nullable.push_back(array_schema->is_nullable(name)); + } auto all_dims_same_type = array_schema->domain()->all_dims_same_type(); auto all_dims_fixed = array_schema->domain()->all_dims_fixed(); @@ -969,6 +1211,7 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( fragment_metadata, names, var_sizes, + nullable, r, r_coords, &(*result_sizes)[r - range_start], @@ -995,6 +1238,7 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( // Global order - noop } } + return Status::Ok(); }); for (auto st : statuses) @@ -1003,7 +1247,7 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( // Compute the mem sizes vector mem_sizes->resize(range_num); for (auto& ms : *mem_sizes) - ms.resize(names.size(), {0, 0}); + ms.resize(names.size(), {0, 0, 0}); std::unordered_set, utils::hash::pair_hash> all_frag_tiles; uint64_t tile_size, tile_var_size; @@ -1015,13 +1259,20 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( auto meta = fragment_metadata[ft.first]; for (size_t i = 0; i < names.size(); ++i) { tile_size = meta->tile_size(names[i], ft.second); + auto cell_size = array_schema->cell_size(names[i]); if (!var_sizes[i]) { mem_vec[i].size_fixed_ += tile_size; + if (nullable[i]) + mem_vec[i].size_validity_ += + tile_size / cell_size * constants::cell_validity_size; } else { RETURN_NOT_OK(meta->tile_var_size( *encryption_key, names[i], ft.second, &tile_var_size)); mem_vec[i].size_fixed_ += tile_size; mem_vec[i].size_var_ += tile_var_size; + if (nullable[i]) + mem_vec[i].size_validity_ += + tile_var_size / cell_size * constants::cell_validity_size; } } } @@ -1329,19 +1580,21 @@ Status Subarray::compute_est_result_size(ThreadPool* const compute_tp) { names, 0, range_num - 1, &result_sizes, &mem_sizes, compute_tp)); // Accummulate the individual estimated result sizes - std::vector est_vec(num, ResultSize{0.0, 0.0}); - std::vector mem_vec(num, MemorySize{0, 0}); + std::vector est_vec(num, ResultSize{0.0, 0.0, 0.0}); + std::vector mem_vec(num, MemorySize{0, 0, 0}); for (uint64_t r = 0; r < range_num; ++r) { for (size_t i = 0; i < result_sizes[r].size(); ++i) { est_vec[i].size_fixed_ += result_sizes[r][i].size_fixed_; est_vec[i].size_var_ += result_sizes[r][i].size_var_; + est_vec[i].size_validity_ += result_sizes[r][i].size_validity_; mem_vec[i].size_fixed_ += mem_sizes[r][i].size_fixed_; mem_vec[i].size_var_ += mem_sizes[r][i].size_var_; + mem_vec[i].size_validity_ += mem_sizes[r][i].size_validity_; } } // Calibrate for dense arrays - uint64_t min_size_fixed, min_size_var; + uint64_t min_size_fixed, min_size_var, min_size_validity; if (array_schema->dense()) { auto cell_num = this->cell_num(); for (unsigned i = 0; i < num; ++i) { @@ -1354,10 +1607,18 @@ Status Subarray::compute_est_result_size(ThreadPool* const compute_tp) { cell_num * array_schema->attribute(names[i])->fill_value().size(); } + if (array_schema->is_nullable(names[i])) { + min_size_validity = cell_num * constants::cell_validity_size; + } else { + min_size_validity = 0; + } + if (est_vec[i].size_fixed_ < min_size_fixed) est_vec[i].size_fixed_ = min_size_fixed; if (est_vec[i].size_var_ < min_size_var) est_vec[i].size_var_ = min_size_var; + if (est_vec[i].size_validity_ < min_size_validity) + est_vec[i].size_validity_ = min_size_validity; } } @@ -1366,6 +1627,7 @@ Status Subarray::compute_est_result_size(ThreadPool* const compute_tp) { for (auto& r : est_vec) { r.size_fixed_ *= constants::est_result_size_amplification; r.size_var_ *= constants::est_result_size_amplification; + r.size_validity_ *= constants::est_result_size_amplification; } } @@ -1395,11 +1657,12 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( const std::vector& fragment_meta, const std::vector& names, const std::vector& var_sizes, + const std::vector& nullable, uint64_t range_idx, const std::vector& range_coords, std::vector* result_sizes, std::set>* frag_tiles) { - result_sizes->resize(names.size(), {0.0, 0.0}); + result_sizes->resize(names.size(), {0.0, 0.0, 0.0}); // Compute estimated result auto fragment_num = (unsigned)relevant_fragments_.size(); @@ -1419,13 +1682,22 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( frag_tiles->insert(std::pair(f, tid)); tile_size = meta->tile_size(names[n], tid); + auto attr_datatype_size = datatype_size(array_schema->type(names[n])); if (!var_sizes[n]) { (*result_sizes)[n].size_fixed_ += tile_size; + if (nullable[n]) + (*result_sizes)[n].size_validity_ += + tile_size / attr_datatype_size * + constants::cell_validity_size; } else { (*result_sizes)[n].size_fixed_ += tile_size; RETURN_NOT_OK(meta->tile_var_size( *encryption_key, names[n], tid, &tile_var_size)); (*result_sizes)[n].size_var_ += tile_var_size; + if (nullable[n]) + (*result_sizes)[n].size_validity_ += + tile_var_size / attr_datatype_size * + constants::cell_validity_size; } } } @@ -1442,13 +1714,25 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( frag_tiles->insert(std::pair(f, tid)); tile_size = meta->tile_size(names[n], tid); + auto attr_datatype_size = datatype_size(array_schema->type(names[n])); if (!var_sizes[n]) { (*result_sizes)[n].size_fixed_ += tile_size * ratio; + if (nullable[n]) + (*result_sizes)[n].size_validity_ += + (tile_size / attr_datatype_size * + constants::cell_validity_size) * + ratio; + } else { (*result_sizes)[n].size_fixed_ += tile_size * ratio; RETURN_NOT_OK(meta->tile_var_size( *encryption_key, names[n], tid, &tile_var_size)); (*result_sizes)[n].size_var_ += tile_var_size * ratio; + if (nullable[n]) + (*result_sizes)[n].size_validity_ += + (tile_var_size / attr_datatype_size * + constants::cell_validity_size) * + ratio; } } } @@ -1466,7 +1750,9 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( cell_num, dim->domain_range(ranges_[d][range_coords[d]])); } - uint64_t max_size_fixed, max_size_var = UINT64_MAX; + uint64_t max_size_fixed = UINT64_MAX; + uint64_t max_size_var = UINT64_MAX; + uint64_t max_size_validity = UINT64_MAX; for (size_t n = 0; n < names.size(); ++n) { // Zipped coords applicable only in homogeneous domains if (names[n] == constants::coords && !all_dims_same_type) @@ -1478,11 +1764,17 @@ Status Subarray::compute_relevant_fragment_est_result_sizes( } else { max_size_fixed = utils::math::safe_mul(cell_num, array_schema->cell_size(names[n])); + if (nullable[n]) + max_size_validity = + utils::math::safe_mul(cell_num, constants::cell_validity_size); } + (*result_sizes)[n].size_fixed_ = std::min((*result_sizes)[n].size_fixed_, max_size_fixed); (*result_sizes)[n].size_var_ = std::min((*result_sizes)[n].size_var_, max_size_var); + (*result_sizes)[n].size_validity_ = std::min( + (*result_sizes)[n].size_validity_, max_size_validity); } } diff --git a/tiledb/sm/subarray/subarray.h b/tiledb/sm/subarray/subarray.h index f14e32c4d06..e355434772d 100644 --- a/tiledb/sm/subarray/subarray.h +++ b/tiledb/sm/subarray/subarray.h @@ -122,8 +122,12 @@ class Subarray { * attributes/dimensions. */ double size_fixed_; + /** Size of values for var-sized attributes/dimensions. */ double size_var_; + + /** Size of validity values for nullable attributes. */ + double size_validity_; }; /** @@ -137,11 +141,18 @@ class Subarray { * attributes/dimensions. */ uint64_t size_fixed_; + /** * Maximum size of overlapping tiles fetched into memory for * var-sized attributes/dimensions. */ uint64_t size_var_; + + /** + * Maximum size of overlapping validity tiles fetched into memory for + * nullable attributes. + */ + uint64_t size_validity_; }; /* ********************************* */ @@ -254,6 +265,7 @@ class Subarray { * @param names The name vector of the attributes/dimensions to focus on. * @param var_sizes A vector indicating which attribute/dimension is * var-sized. + * @param nullable A vector indicating which attribute is nullable. * @param range_idx The id of the subarray range to focus on. * @param range_coords The coordinates of the subarray range to focus on. * @param result_sizes The result sizes to be retrieved for all given names. @@ -269,6 +281,7 @@ class Subarray { const std::vector& fragment_meta, const std::vector& name, const std::vector& var_sizes, + const std::vector& nullable, uint64_t range_idx, const std::vector& range_coords, std::vector* result_sizes, @@ -378,6 +391,27 @@ class Subarray { uint64_t* size_val, ThreadPool* compute_tp); + /** + * Gets the estimated result size (in bytes) for the input fixed-sized, + * nullable attribute. + */ + Status get_est_result_size_nullable( + const char* name, + uint64_t* size, + uint64_t* size_validity, + ThreadPool* compute_tp); + + /** + * Gets the estimated result size (in bytes) for the input var-sized, + * nullable attribute. + */ + Status get_est_result_size_nullable( + const char* name, + uint64_t* size_off, + uint64_t* size_val, + uint64_t* size_validity, + ThreadPool* compute_tp); + /** returns whether the estimated result size has been computed or not */ bool est_result_size_computed(); @@ -398,6 +432,27 @@ class Subarray { uint64_t* size_val, ThreadPool* compute_tp); + /* + * Gets the maximum memory required to produce the result (in bytes) + * for the input fixed-sized, nullable attribute. + */ + Status get_max_memory_size_nullable( + const char* name, + uint64_t* size, + uint64_t* size_validity, + ThreadPool* compute_tp); + + /** + * Gets the maximum memory required to produce the result (in bytes) + * for the input var-sized, nullable attribute. + */ + Status get_max_memory_size_nullable( + const char* name, + uint64_t* size_off, + uint64_t* size_val, + uint64_t* size_validity, + ThreadPool* compute_tp); + /** Retrieves the query type of the subarray's array. */ Status get_query_type(QueryType* type) const; diff --git a/tiledb/sm/subarray/subarray_partitioner.cc b/tiledb/sm/subarray/subarray_partitioner.cc index 547b526c453..08485f7cc34 100644 --- a/tiledb/sm/subarray/subarray_partitioner.cc +++ b/tiledb/sm/subarray/subarray_partitioner.cc @@ -65,10 +65,12 @@ SubarrayPartitioner::SubarrayPartitioner( const Subarray& subarray, uint64_t memory_budget, uint64_t memory_budget_var, + uint64_t memory_budget_validity, ThreadPool* const compute_tp) : subarray_(subarray) , memory_budget_(memory_budget) , memory_budget_var_(memory_budget_var) + , memory_budget_validity_(memory_budget_validity) , compute_tp_(compute_tp) { subarray_.compute_tile_overlap(compute_tp_); state_.start_ = 0; @@ -156,6 +158,12 @@ Status SubarrayPartitioner::get_result_budget( std::string("Cannot get result budget; Input attribute/dimension '") + name + "' is var-sized")); + // Check if the attribute is nullable + if (array_schema->is_nullable(name)) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Input attribute/dimension '") + + name + "' is nullable")); + // Check if budget has been set auto b_it = budget_.find(name); if (b_it == budget_.end()) @@ -205,6 +213,12 @@ Status SubarrayPartitioner::get_result_budget( std::string("Cannot get result budget; Input attribute/dimension '") + name + "' is fixed-sized")); + // Check if the attribute is nullable + if (array_schema->is_nullable(name)) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Input attribute/dimension '") + + name + "' is nullable")); + // Check if budget has been set auto b_it = budget_.find(name); if (b_it == budget_.end()) @@ -220,15 +234,119 @@ Status SubarrayPartitioner::get_result_budget( return Status::Ok(); } +Status SubarrayPartitioner::get_result_budget_nullable( + const char* name, uint64_t* budget, uint64_t* budget_validity) const { + // Check attribute name + if (name == nullptr) + return LOG_STATUS(Status::SubarrayPartitionerError( + "Cannot get result budget; Attribute name cannot be null")); + + // Check budget pointers + if (budget == nullptr || budget_validity == nullptr) + return LOG_STATUS(Status::SubarrayPartitionerError( + "Cannot get result budget; Invalid budget input")); + + // For easy reference + auto array_schema = subarray_.array()->array_schema(); + bool is_attr = array_schema->is_attr(name); + + // Check if attribute exists + if (!is_attr) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Invalid attribute '") + name + + "'")); + + // Check if the attribute is fixed-sized + if (array_schema->var_size(name)) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Input attribute '") + name + + "' is var-sized")); + + // Check if the attribute is nullable + if (!array_schema->is_nullable(name)) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Input attribute '") + name + + "' is not nullable")); + + // Check if budget has been set + auto b_it = budget_.find(name); + if (b_it == budget_.end()) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Budget not set for " + "attribute '") + + name + "'")); + + // Get budgets + *budget = b_it->second.size_fixed_; + *budget_validity = b_it->second.size_validity_; + + return Status::Ok(); +} + +Status SubarrayPartitioner::get_result_budget_nullable( + const char* name, + uint64_t* budget_off, + uint64_t* budget_val, + uint64_t* budget_validity) const { + // Check attribute/dimension name + if (name == nullptr) + return LOG_STATUS(Status::SubarrayPartitionerError( + "Cannot get result budget; Attribute/Dimension name cannot be null")); + + // Check budget pointers + if (budget_off == nullptr || budget_val == nullptr || + budget_validity == nullptr) + return LOG_STATUS(Status::SubarrayPartitionerError( + "Cannot get result budget; Invalid budget input")); + + // For easy reference + auto array_schema = subarray_.array()->array_schema(); + bool is_attr = array_schema->is_attr(name); + + // Check if attribute exists + if (!is_attr) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Invalid attribute '") + name + + "'")); + + // Check if the attribute is var-sized + if (!array_schema->var_size(name)) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Input attribute '") + name + + "' is fixed-sized")); + + // Check if the attribute is nullable + if (!array_schema->is_nullable(name)) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Input attribute '") + name + + "' is not nullable")); + + // Check if budget has been set + auto b_it = budget_.find(name); + if (b_it == budget_.end()) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot get result budget; Budget not set for " + "attribute '") + + name + "'")); + + // Get budget + *budget_off = b_it->second.size_fixed_; + *budget_val = b_it->second.size_var_; + *budget_validity = b_it->second.size_validity_; + + return Status::Ok(); +} + const std::unordered_map* SubarrayPartitioner::get_result_budgets() const { return &budget_; } Status SubarrayPartitioner::get_memory_budget( - uint64_t* budget, uint64_t* budget_var) const { + uint64_t* budget, uint64_t* budget_var, uint64_t* budget_validity) const { *budget = memory_budget_; *budget_var = memory_budget_var_; + *budget_validity = memory_budget_validity_; return Status::Ok(); } @@ -303,7 +421,14 @@ Status SubarrayPartitioner::set_result_budget( std::string("Cannot set result budget; Input attribute/dimension '") + name + "' is var-sized")); - budget_[name] = ResultBudget{budget, 0}; + // Check if the attribute/dimension is nullable + bool nullable = array_schema->is_nullable(name); + if (nullable) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot set result budget; Input attribute/dimension '") + + name + "' is nullable")); + + budget_[name] = ResultBudget{budget, 0, 0}; return Status::Ok(); } @@ -337,15 +462,97 @@ Status SubarrayPartitioner::set_result_budget( std::string("Cannot set result budget; Input attribute/dimension '") + name + "' is fixed-sized")); - budget_[name] = ResultBudget{budget_off, budget_val}; + // Check if the attribute/dimension is nullable + bool nullable = array_schema->is_nullable(name); + if (nullable) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot set result budget; Input attribute/dimension '") + + name + "' is nullable")); + + budget_[name] = ResultBudget{budget_off, budget_val, 0}; + + return Status::Ok(); +} + +Status SubarrayPartitioner::set_result_budget_nullable( + const char* name, uint64_t budget, uint64_t budget_validity) { + // Check attribute/dimension name + if (name == nullptr) + return LOG_STATUS(Status::SubarrayPartitionerError( + "Cannot set result budget; Attribute name cannot be null")); + + // For easy reference + auto array_schema = subarray_.array()->array_schema(); + bool is_attr = array_schema->is_attr(name); + + // Check if attribute exists + if (!is_attr) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot set result budget; Invalid attribute '") + name + + "'")); + + // Check if the attribute is fixed-sized + bool var_size = array_schema->var_size(name); + if (var_size) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot set result budget; Input attribute '") + name + + "' is var-sized")); + + // Check if the attribute is nullable + bool nullable = array_schema->is_nullable(name); + if (!nullable) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot set result budget; Input attribute '") + name + + "' is not nullable")); + + budget_[name] = ResultBudget{budget, 0, budget_validity}; + + return Status::Ok(); +} + +Status SubarrayPartitioner::set_result_budget_nullable( + const char* name, + uint64_t budget_off, + uint64_t budget_val, + uint64_t budget_validity) { + // Check attribute/dimension name + if (name == nullptr) + return LOG_STATUS(Status::SubarrayPartitionerError( + "Cannot set result budget; Attribute name cannot be null")); + + // For easy reference + auto array_schema = subarray_.array()->array_schema(); + bool is_attr = array_schema->is_attr(name); + + // Check if attribute exists + if (!is_attr) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot set result budget; Invalid attribute '") + name + + "'")); + + // Check if the attribute is var-sized + if (!array_schema->var_size(name)) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot set result budget; Input attribute '") + name + + "' is fixed-sized")); + + // Check if the attribute is nullable + bool nullable = array_schema->is_nullable(name); + if (!nullable) + return LOG_STATUS(Status::SubarrayPartitionerError( + std::string("Cannot set result budget; Input attribute '") + name + + "' is not nullable")); + + budget_[name] = ResultBudget{budget_off, budget_val, budget_validity}; return Status::Ok(); } Status SubarrayPartitioner::set_memory_budget( - uint64_t budget, uint64_t budget_var) { + uint64_t budget, uint64_t budget_var, uint64_t budget_validity) { memory_budget_ = budget; memory_budget_var_ = budget_var; + memory_budget_validity_ = budget_validity; return Status::Ok(); } @@ -506,6 +713,7 @@ SubarrayPartitioner SubarrayPartitioner::clone() const { clone.state_ = state_; clone.memory_budget_ = memory_budget_; clone.memory_budget_var_ = memory_budget_var_; + clone.memory_budget_validity_ = memory_budget_validity_; clone.compute_tp_ = compute_tp_; return clone; @@ -521,8 +729,8 @@ Status SubarrayPartitioner::compute_current_start_end(bool* found) { std::vector budgets; names.reserve(budget_.size()); budgets.reserve(budget_.size()); - cur_sizes.resize(budget_.size(), Subarray::ResultSize({0.0, 0.0})); - mem_sizes.resize(budget_.size(), Subarray::MemorySize({0, 0})); + cur_sizes.resize(budget_.size(), Subarray::ResultSize({0.0, 0.0, 0.0})); + mem_sizes.resize(budget_.size(), Subarray::MemorySize({0, 0, 0})); for (const auto& budget_it : budget_) { names.emplace_back(budget_it.first); budgets.emplace_back(budget_it.second); @@ -549,12 +757,16 @@ Status SubarrayPartitioner::compute_current_start_end(bool* found) { const auto& budget = budgets[i]; cur_size.size_fixed_ += result_sizes[r][i].size_fixed_; cur_size.size_var_ += result_sizes[r][i].size_var_; + cur_size.size_validity_ += result_sizes[r][i].size_validity_; mem_size.size_fixed_ += memory_sizes[r][i].size_fixed_; mem_size.size_var_ += memory_sizes[r][i].size_var_; + mem_size.size_validity_ += memory_sizes[r][i].size_validity_; if (cur_size.size_fixed_ > budget.size_fixed_ || cur_size.size_var_ > budget.size_var_ || + cur_size.size_validity_ > budget.size_validity_ || mem_size.size_fixed_ > memory_budget_ || - mem_size.size_var_ > memory_budget_var_) { + mem_size.size_var_ > memory_budget_var_ || + mem_size.size_validity_ > memory_budget_validity_) { // Cannot find range that fits in the buffer if (current_.end_ == current_.start_) { *found = false; @@ -806,31 +1018,64 @@ bool SubarrayPartitioner::must_split(Subarray* partition) { auto array_schema = subarray_.array()->array_schema(); bool must_split = false; - uint64_t size_fixed, size_var, mem_size_fixed, mem_size_var; + uint64_t size_fixed; + uint64_t size_var; + uint64_t size_validity; + uint64_t mem_size_fixed; + uint64_t mem_size_var; + uint64_t mem_size_validity; for (const auto& b : budget_) { // Compute max sizes auto name = b.first; auto var_size = array_schema->var_size(name); + auto nullable = array_schema->is_nullable(name); // Compute est sizes size_fixed = 0; size_var = 0; + size_validity = 0; mem_size_fixed = 0; mem_size_var = 0; + mem_size_validity = 0; if (var_size) { - partition->get_est_result_size( - b.first.c_str(), &size_fixed, &size_var, compute_tp_); - partition->get_max_memory_size( - b.first.c_str(), &mem_size_fixed, &mem_size_var, compute_tp_); + if (!nullable) { + partition->get_est_result_size( + b.first.c_str(), &size_fixed, &size_var, compute_tp_); + partition->get_max_memory_size( + b.first.c_str(), &mem_size_fixed, &mem_size_var, compute_tp_); + } else { + partition->get_est_result_size_nullable( + b.first.c_str(), + &size_fixed, + &size_var, + &size_validity, + compute_tp_); + partition->get_max_memory_size_nullable( + b.first.c_str(), + &mem_size_fixed, + &mem_size_var, + &mem_size_validity, + compute_tp_); + } } else { - partition->get_est_result_size(b.first.c_str(), &size_fixed, compute_tp_); - partition->get_max_memory_size( - b.first.c_str(), &mem_size_fixed, compute_tp_); + if (!nullable) { + partition->get_est_result_size( + b.first.c_str(), &size_fixed, compute_tp_); + partition->get_max_memory_size( + b.first.c_str(), &mem_size_fixed, compute_tp_); + } else { + partition->get_est_result_size_nullable( + b.first.c_str(), &size_fixed, &size_validity, compute_tp_); + partition->get_max_memory_size_nullable( + b.first.c_str(), &mem_size_fixed, &mem_size_validity, compute_tp_); + } } // Check for budget overflow if (size_fixed > b.second.size_fixed_ || size_var > b.second.size_var_ || - mem_size_fixed > memory_budget_ || mem_size_var > memory_budget_var_) { + size_validity > b.second.size_validity_ || + mem_size_fixed > memory_budget_ || mem_size_var > memory_budget_var_ || + mem_size_validity > memory_budget_validity_) { must_split = true; break; } @@ -985,6 +1230,7 @@ void SubarrayPartitioner::swap(SubarrayPartitioner& partitioner) { std::swap(state_, partitioner.state_); std::swap(memory_budget_, partitioner.memory_budget_); std::swap(memory_budget_var_, partitioner.memory_budget_var_); + std::swap(memory_budget_validity_, partitioner.memory_budget_validity_); std::swap(compute_tp_, partitioner.compute_tp_); } diff --git a/tiledb/sm/subarray/subarray_partitioner.h b/tiledb/sm/subarray/subarray_partitioner.h index 03a8e1790bf..eb7c9bd5954 100644 --- a/tiledb/sm/subarray/subarray_partitioner.h +++ b/tiledb/sm/subarray/subarray_partitioner.h @@ -70,8 +70,12 @@ class SubarrayPartitioner { * attributes/dimensions. */ uint64_t size_fixed_; + /** Size of values for var-sized attributes/dimensions. */ uint64_t size_var_; + + /** Size of validity for nullable attributes. */ + uint64_t size_validity_; }; /** @@ -158,6 +162,7 @@ class SubarrayPartitioner { const Subarray& subarray, uint64_t memory_budget, uint64_t memory_budget_var, + uint64_t memory_budget_validity, ThreadPool* compute_tp); /** Destructor. */ @@ -207,6 +212,23 @@ class SubarrayPartitioner { Status get_result_budget( const char* name, uint64_t* budget_off, uint64_t* budget_val) const; + /** + * Gets result size budget (in bytes) for the input fixed-sized + * nullable attribute. + */ + Status get_result_budget_nullable( + const char* name, uint64_t* budget, uint64_t* budget_validity) const; + + /** + * Gets result size budget (in bytes) for the input var-sized + * nullable attribute. + */ + Status get_result_budget_nullable( + const char* name, + uint64_t* budget_off, + uint64_t* budget_val, + uint64_t* budget_validity) const; + /** * Returns a pointer to mapping containing all attribute/dimension result * budgets that have been set. @@ -222,7 +244,8 @@ class SubarrayPartitioner { * @param budget_var The budget for the var-sized attributes. * @return Status */ - Status get_memory_budget(uint64_t* budget, uint64_t* budget_var) const; + Status get_memory_budget( + uint64_t* budget, uint64_t* budget_var, uint64_t* budget_validity) const; /** * The partitioner iterates over the partitions of the subarray it is @@ -241,9 +264,11 @@ class SubarrayPartitioner { * @param budget The budget for the fixed-sized attributes and the * offsets of the var-sized attributes. * @param budget_var The budget for the var-sized attributes. + * @param budget_validity The budget for validity vectors. * @return Status */ - Status set_memory_budget(uint64_t budget, uint64_t budget_var); + Status set_memory_budget( + uint64_t budget, uint64_t budget_var, uint64_t budget_validity); /** * Sets result size budget (in bytes) for the input fixed-sized @@ -258,6 +283,23 @@ class SubarrayPartitioner { Status set_result_budget( const char* name, uint64_t budget_off, uint64_t budget_val); + /** + * Sets result size budget (in bytes) for the input fixed-sized, + * nullable attribute. + */ + Status set_result_budget_nullable( + const char* name, uint64_t budget, uint64_t budget_validity); + + /** + * Sets result size budget (in bytes) for the input var-sized + * nullable attribute. + */ + Status set_result_budget_nullable( + const char* name, + uint64_t budget_off, + uint64_t budget_val, + uint64_t budget_validity); + /** * Splits the current partition and updates the state, retrieving * a new current partition. This function is typically called @@ -304,6 +346,9 @@ class SubarrayPartitioner { /** The memory budget for the var-sized attributes. */ uint64_t memory_budget_var_; + /** The memory budget for the validity vectors. */ + uint64_t memory_budget_validity_; + /** The thread pool for compute-bound tasks. */ ThreadPool* compute_tp_;