Skip to content

Commit dad5b03

Browse files
Perform validity checks for ranges to add in an NDRectangle. (#5144)
[SC-49157](https://app.shortcut.com/tiledb-inc/story/49157/check-and-test-out-of-order-ranges-for-currentdomain) This PR adds validation that the ranges added in an `NDRectangle` are in the correct order. We add a `check_range_is_valid` function that accepts a range and a datatype, and call it on `NDRectangle::set_range` with the dimension's data type. The generic `check_range_is_valid` was also updated to handle `std::string_view`. --- TYPE: NO_HISTORY
1 parent df74fdb commit dad5b03

File tree

5 files changed

+103
-24
lines changed

5 files changed

+103
-24
lines changed

test/src/test-cppapi-ndrectangle.cc

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,9 @@
3939

4040
using namespace tiledb::test;
4141

42-
TEST_CASE_METHOD(
43-
TemporaryDirectoryFixture,
44-
"NDRectangle - Basic",
45-
"[cppapi][ArraySchema][NDRectangle]") {
42+
TEST_CASE("NDRectangle - Basic", "[cppapi][ArraySchema][NDRectangle]") {
4643
// Create the C++ context.
47-
tiledb::Context ctx_{ctx, false};
44+
tiledb::Context ctx_;
4845
tiledb::Domain domain(ctx_);
4946
auto d1 = tiledb::Dimension::create<int32_t>(ctx_, "x", {{0, 100}}, 10);
5047
auto d2 = tiledb::Dimension::create<int32_t>(ctx_, "y", {{0, 100}}, 10);
@@ -59,9 +56,6 @@ TEST_CASE_METHOD(
5956
int range_two[] = {30, 40};
6057
ndrect.set_range(1, range_two[0], range_two[1]);
6158

62-
// Check setting range in non-existent dim
63-
CHECK_THROWS(ndrect.set_range(2, range_two[0], range_two[1]));
64-
6559
// Get range
6660
auto range = ndrect.range<int>(0);
6761
CHECK(range[0] == 10);
@@ -77,3 +71,30 @@ TEST_CASE_METHOD(
7771
CHECK(range[0] == 30);
7872
CHECK(range[1] == 40);
7973
}
74+
75+
TEST_CASE("NDRectangle - Errors", "[cppapi][ArraySchema][NDRectangle]") {
76+
tiledb::Context ctx;
77+
78+
// Create a domain
79+
tiledb::Domain domain(ctx);
80+
auto d1 = tiledb::Dimension::create<int32_t>(ctx, "d1", {{1, 10}}, 5);
81+
auto d2 = tiledb::Dimension::create(
82+
ctx, "d2", TILEDB_STRING_ASCII, nullptr, nullptr);
83+
domain.add_dimension(d1);
84+
domain.add_dimension(d2);
85+
86+
// Create an NDRectangle
87+
tiledb::NDRectangle ndrect(ctx, domain);
88+
89+
// Set range with non-existent dimension
90+
CHECK_THROWS(ndrect.set_range(2, 1, 2));
91+
CHECK_THROWS(ndrect.set_range("d3", 1, 2));
92+
93+
// Set too small range type
94+
CHECK_THROWS(ndrect.set_range<uint8_t>(0, 1, 2));
95+
CHECK_THROWS(ndrect.set_range<uint8_t>("d1", 1, 2));
96+
97+
// Set range out of order
98+
CHECK_THROWS(ndrect.set_range(0, 2, 1));
99+
CHECK_THROWS(ndrect.set_range("d2", "bbb", "aaa"));
100+
}

tiledb/sm/array_schema/ndrectangle.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ void NDRectangle::set_range(const Range& r, uint32_t idx) {
121121
throw std::logic_error(
122122
"Trying to set a range for an index out of bounds is not possible.");
123123
}
124+
check_range_is_valid(r, domain_->dimension_ptr(idx)->type());
124125
range_data_[idx] = r;
125126
}
126127

tiledb/sm/cpp_api/ndrectangle.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,18 @@ class NDRectangle {
101101
* ndrect.set_range(0, start, end);
102102
* @endcode
103103
*
104-
* @tparam T The dimension datatype.
104+
* @tparam T The dimension datatype. Must be an integer or a floating point
105+
* number.
105106
* @param dim_name The name of the dimension to add the range to.
106107
* @param start The range start to add.
107108
* @param end The range end to add.
108109
* @return Reference to this NDRectangle.
109110
*/
110-
template <class T>
111+
template <
112+
class T,
113+
std::enable_if_t<
114+
std::is_integral_v<T> || std::is_floating_point_v<T>,
115+
bool> = true>
111116
NDRectangle& set_range(const std::string& dim_name, T start, T end) {
112117
auto& ctx = ctx_.get();
113118

@@ -138,13 +143,18 @@ class NDRectangle {
138143
* ndrect.set_range(0, start, end);
139144
* @endcode
140145
*
141-
* @tparam T The dimension datatype.
146+
* @tparam T The dimension datatype. Must be an integer or a floating point
147+
* number.
142148
* @param dim_idx The index of the dimension to add the range to.
143149
* @param start The range start to add.
144150
* @param end The range end to add.
145151
* @return Reference to this NDRectangle.
146152
*/
147-
template <class T>
153+
template <
154+
class T,
155+
std::enable_if_t<
156+
std::is_integral_v<T> || std::is_floating_point_v<T>,
157+
bool> = true>
148158
NDRectangle& set_range(uint32_t dim_idx, T start, T end) {
149159
auto& ctx = ctx_.get();
150160
// Create the tiledb_range_t struct and fill it

tiledb/type/range/range.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "tiledb/type/range/range.h"
3030
#include "tiledb/sm/enums/datatype.h"
3131
#include "tiledb/sm/misc/constants.h"
32+
#include "tiledb/type/apply_with_type.h"
3233

3334
using namespace tiledb::common;
3435

@@ -126,4 +127,21 @@ std::string range_str(const Range& range, const tiledb::sm::Datatype type) {
126127
return ss.str();
127128
}
128129

130+
void check_range_is_valid(const Range& range, const tiledb::sm::Datatype type) {
131+
if (range.empty())
132+
throw std::invalid_argument("Range is empty");
133+
134+
if (range.var_size()) {
135+
if (type != sm::Datatype::STRING_ASCII)
136+
throw std::invalid_argument(
137+
"Validating a variable range is only supported for type " +
138+
datatype_str(sm::Datatype::STRING_ASCII) + ".");
139+
check_range_is_valid<std::string_view>(range);
140+
return;
141+
}
142+
143+
apply_with_type(
144+
[&](const auto T) { check_range_is_valid<decltype(T)>(range); }, type);
145+
}
146+
129147
} // namespace tiledb::type

tiledb/type/range/range.h

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -453,22 +453,40 @@ Status check_range_is_subset(const Range& superset, const Range& range) {
453453
*/
454454
template <
455455
typename T,
456-
typename std::enable_if<std::is_arithmetic<T>::value>::type* = nullptr>
456+
typename std::enable_if_t<
457+
std::is_arithmetic_v<T> || std::is_same_v<T, std::string_view>>* =
458+
nullptr>
457459
void check_range_is_valid(const Range& range) {
458460
// Check has data.
459461
if (range.empty())
460462
throw std::invalid_argument("Range is empty");
461-
auto r = (const T*)range.data();
462-
// Check for NaN
463-
if constexpr (std::is_floating_point_v<T>) {
464-
if (std::isnan(r[0]) || std::isnan(r[1]))
465-
throw std::invalid_argument("Range contains NaN");
466-
}
467-
// Check range bounds
468-
if (r[0] > r[1])
469-
throw std::invalid_argument(
470-
"Lower range bound " + std::to_string(r[0]) +
471-
" cannot be larger than the higher bound " + std::to_string(r[1]));
463+
464+
// Compare string views
465+
if constexpr (std::is_same_v<T, std::string_view>) {
466+
auto start = range.start_str();
467+
auto end = range.end_str();
468+
// Check range bounds
469+
if (start > end)
470+
throw std::invalid_argument(
471+
"Lower range bound " + std::string(start) +
472+
" cannot be larger than the higher bound " + std::string(end));
473+
} else {
474+
if (range.size() != 2 * sizeof(T))
475+
throw std::invalid_argument(
476+
"Range size " + std::to_string(range.size()) +
477+
" does not match the expected size " + std::to_string(2 * sizeof(T)));
478+
auto r = (const T*)range.data();
479+
// Check for NaN
480+
if constexpr (std::is_floating_point_v<T>) {
481+
if (std::isnan(r[0]) || std::isnan(r[1]))
482+
throw std::invalid_argument("Range contains NaN");
483+
}
484+
// Check range bounds
485+
if (r[0] > r[1])
486+
throw std::invalid_argument(
487+
"Lower range bound " + std::to_string(r[0]) +
488+
" cannot be larger than the higher bound " + std::to_string(r[1]));
489+
}
472490
};
473491

474492
/**
@@ -495,6 +513,17 @@ void crop_range(const Range& bounds, Range& range) {
495513
*/
496514
std::string range_str(const Range& range, const tiledb::sm::Datatype type);
497515

516+
/**
517+
* Validates that the range's elements are in the correct order.
518+
*
519+
* @param range The range to validate.
520+
* @param type The datatype to view the range's elements as.
521+
*
522+
* @throws std::invalid_argument If the range's elements are not in the correct
523+
* order.
524+
*/
525+
void check_range_is_valid(const Range& range, const tiledb::sm::Datatype type);
526+
498527
} // namespace tiledb::type
499528

500529
#endif

0 commit comments

Comments
 (0)