2020// / \file arrow_c_data_util_test.cc
2121// / Verifies ProjectBatch behavior across registered implementations.
2222
23+ #include < array>
2324#include < cstdint>
2425#include < memory>
2526#include < string>
@@ -76,15 +77,15 @@ ProjectionContext::ProjectBatchFunction ArrowComputeFunction() {
7677std::shared_ptr<::arrow::RecordBatch> RunProjectBatch (
7778 const ::arrow::RecordBatch& batch, const std::vector<int32_t >& alive_indices,
7879 const Schema& required_schema, const Schema& projected_schema,
79- ProjectionContext::ProjectBatchFunction project_batch_function) {
80+ ProjectionContext::ProjectBatchFunction project_batch_function, bool validate_batch ) {
8081 ArrowSchema c_schema;
8182 ArrowArray c_array;
8283 EXPECT_TRUE (::arrow::ExportRecordBatch (batch, &c_array, &c_schema).ok ());
8384 ArrowSchemaGuard schema_guard (&c_schema);
8485 ArrowArrayGuard array_guard (&c_array);
8586
86- auto projection =
87- ProjectionContext::Make (required_schema, projected_schema, project_batch_function);
87+ auto projection = ProjectionContext::Make (required_schema, projected_schema,
88+ project_batch_function, validate_batch );
8889 EXPECT_THAT (projection, IsOk ());
8990
9091 auto result = ProjectBatch (&c_array, alive_indices, projection.value ());
@@ -103,22 +104,15 @@ void ExpectProjectBatch(const ::arrow::RecordBatch& batch,
103104 const Schema& required_schema, const Schema& projected_schema,
104105 std::string_view expected_json) {
105106 auto expected = MakeBatch (projected_schema, expected_json);
106- auto nanoarrow =
107- RunProjectBatch (batch, alive_indices, required_schema, projected_schema, nullptr );
108- auto arrow_compute = RunProjectBatch (batch, alive_indices, required_schema,
109- projected_schema, ArrowComputeFunction ());
110-
111- EXPECT_TRUE (nanoarrow->Equals (*expected)) << " nanoarrow:\n "
112- << nanoarrow->ToString () << " expected:\n "
113- << expected->ToString ();
114- EXPECT_TRUE (arrow_compute->Equals (*expected))
115- << " arrow_compute:\n "
116- << arrow_compute->ToString () << " expected:\n "
117- << expected->ToString ();
118- EXPECT_TRUE (nanoarrow->Equals (*arrow_compute))
119- << " nanoarrow:\n "
120- << nanoarrow->ToString () << " arrow_compute:\n "
121- << arrow_compute->ToString ();
107+ for (bool validate_batch : {true , false }) {
108+ auto nanoarrow = RunProjectBatch (batch, alive_indices, required_schema,
109+ projected_schema, nullptr , validate_batch);
110+ auto arrow_compute =
111+ RunProjectBatch (batch, alive_indices, required_schema, projected_schema,
112+ ArrowComputeFunction (), validate_batch);
113+ EXPECT_TRUE (nanoarrow->Equals (*expected)) << nanoarrow->ToString ();
114+ EXPECT_TRUE (arrow_compute->Equals (*expected)) << arrow_compute->ToString ();
115+ }
122116}
123117
124118std::shared_ptr<Schema> MakeFullSchema () {
@@ -231,6 +225,87 @@ TEST(ProjectBatchTest, ProjectionRejectsNestedPruning) {
231225 EXPECT_THAT (projection, IsError (ErrorKind::kInvalidArgument ));
232226}
233227
228+ TEST (ProjectBatchTest, ValidateBatchControlsNonMonotonicStringOffsets) {
229+ for (bool validate_batch : {true , false }) {
230+ for (auto function :
231+ {ProjectionContext::ProjectBatchFunction{nullptr }, ArrowComputeFunction ()}) {
232+ auto schema = std::make_shared<Schema>(
233+ std::vector<SchemaField>{SchemaField::MakeRequired (1 , " value" , string ())});
234+
235+ // DEFAULT accepts the first/last offsets; FULL rejects the intermediate decrease.
236+ ArrowArray root;
237+ ASSERT_EQ (ArrowArrayInitFromType (&root, NANOARROW_TYPE_STRUCT ), NANOARROW_OK );
238+ ASSERT_EQ (ArrowArrayAllocateChildren (&root, 1 ), NANOARROW_OK );
239+
240+ ArrowArray* value_array = root.children [0 ];
241+ ASSERT_EQ (ArrowArrayInitFromType (value_array, NANOARROW_TYPE_STRING ), NANOARROW_OK );
242+ const std::array<int32_t , 3 > raw_offsets = {0 , 5 , 2 };
243+ ASSERT_EQ (ArrowBufferAppend (ArrowArrayBuffer (value_array, 1 ), raw_offsets.data (),
244+ raw_offsets.size () * sizeof (int32_t )),
245+ NANOARROW_OK );
246+ ASSERT_EQ (ArrowBufferAppend (ArrowArrayBuffer (value_array, 2 ), " ab" , 2 ),
247+ NANOARROW_OK );
248+ value_array->length = 2 ;
249+ value_array->null_count = 0 ;
250+ ASSERT_EQ (ArrowArrayFinishBuildingDefault (value_array, nullptr ), NANOARROW_OK );
251+
252+ root.length = 2 ;
253+ root.null_count = 0 ;
254+ ASSERT_EQ (ArrowArrayFinishBuildingDefault (&root, nullptr ), NANOARROW_OK );
255+
256+ auto projection =
257+ ProjectionContext::Make (*schema, *schema, function, validate_batch);
258+ ASSERT_THAT (projection, IsOk ());
259+
260+ // Empty selection skips the content check without reading corrupt data.
261+ auto result = ProjectBatch (&root, /* row_indices=*/ {}, projection.value ());
262+ if (validate_batch) {
263+ EXPECT_THAT (result, IsError (ErrorKind::kInvalidArrowData ));
264+ EXPECT_THAT (result, HasErrorMessage (" element size >= 0" ));
265+ } else {
266+ EXPECT_THAT (result, IsOk ());
267+ }
268+ if (result.has_value ()) {
269+ ArrowArrayGuard guard (&result.value ());
270+ EXPECT_EQ (result->length , 0 );
271+ }
272+ EXPECT_EQ (root.release , nullptr );
273+ }
274+ }
275+ }
276+
277+ TEST (ProjectBatchTest, SkippedContentValidationStillRejectsInvalidStructure) {
278+ auto schema = MakeFullSchema ();
279+ auto batch = MakeBatch (*schema, R"( [[1,"a",1.0]])" );
280+ for (auto function :
281+ {ProjectionContext::ProjectBatchFunction{nullptr }, ArrowComputeFunction ()}) {
282+ ArrowArray array;
283+ ASSERT_TRUE (::arrow::ExportRecordBatch (*batch, &array).ok ());
284+ array.length = -1 ;
285+ ICEBERG_UNWRAP_OR_FAIL (
286+ auto projection,
287+ ProjectionContext::Make (*schema, *schema, function, /* validate_batch=*/ false ));
288+ auto result = ProjectBatch (&array, {}, projection);
289+ EXPECT_THAT (result, IsError (ErrorKind::kInvalidArrowData ));
290+ EXPECT_THAT (result, HasErrorMessage (" length" ));
291+ EXPECT_EQ (array.release , nullptr );
292+ }
293+ }
294+
295+ TEST (ProjectBatchTest, MovePreservesValidateBatch) {
296+ auto schema = MakeFullSchema ();
297+ ICEBERG_UNWRAP_OR_FAIL (
298+ auto without_validation,
299+ ProjectionContext::Make (*schema, *schema, nullptr , /* validate_batch=*/ false ));
300+ auto moved = std::move (without_validation);
301+ EXPECT_FALSE (moved.validate_batch ());
302+ ICEBERG_UNWRAP_OR_FAIL (auto validation,
303+ ProjectionContext::Make (*schema, *schema, nullptr ));
304+ EXPECT_TRUE (validation.validate_batch ());
305+ validation = std::move (moved);
306+ EXPECT_FALSE (validation.validate_batch ());
307+ }
308+
234309TEST (WriteProjectionTest, IdenticalSchemasTransferBatchWithoutConversion) {
235310 auto input_schema = MakeFullSchema ();
236311 auto write_schema = MakeFullSchema ();
0 commit comments