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_;