@@ -535,6 +535,50 @@ class BlobTableInteTest : public testing::Test, public ::testing::WithParamInter
535535 });
536536 }
537537
538+ Result<std::shared_ptr<arrow::MapArray>> NormalizeMapBlobValues (
539+ const std::shared_ptr<arrow::MapArray>& map_array, bool blob_as_descriptor) const {
540+ const auto & values = checked_cast<const arrow::LargeBinaryArray&>(*map_array->items ());
541+ auto fs = std::make_shared<LocalFileSystem>();
542+ arrow::LargeBinaryBuilder builder;
543+ for (int64_t i = 0 ; i < values.length (); ++i) {
544+ if (values.IsNull (i)) {
545+ PAIMON_RETURN_NOT_OK_FROM_ARROW (builder.AppendNull ());
546+ continue ;
547+ }
548+ std::string_view stored = values.GetView (i);
549+ if (!blob_as_descriptor) {
550+ PAIMON_RETURN_NOT_OK_FROM_ARROW (builder.Append (stored));
551+ continue ;
552+ }
553+ PAIMON_ASSIGN_OR_RAISE (
554+ std::unique_ptr<Blob> blob,
555+ Blob::FromDescriptor (stored.data (), static_cast <int64_t >(stored.size ())));
556+ PAIMON_ASSIGN_OR_RAISE (PAIMON_UNIQUE_PTR <Bytes> data, blob->ToData (fs, pool_));
557+ PAIMON_RETURN_NOT_OK_FROM_ARROW (builder.Append (data->data (), data->size ()));
558+ }
559+ std::shared_ptr<arrow::Array> normalized_values;
560+ PAIMON_RETURN_NOT_OK_FROM_ARROW (builder.Finish (&normalized_values));
561+ return std::make_shared<arrow::MapArray>(map_array->type (), map_array->length (),
562+ map_array->value_offsets (), map_array->keys (),
563+ normalized_values, map_array->null_bitmap (),
564+ map_array->null_count (), map_array->offset ());
565+ }
566+
567+ void CheckMapBlobColumn (const std::shared_ptr<arrow::StructArray>& rows,
568+ const std::string& field_name, const std::string& expected_json,
569+ bool blob_as_descriptor) const {
570+ auto map_array =
571+ std::dynamic_pointer_cast<arrow::MapArray>(rows->GetFieldByName (field_name));
572+ ASSERT_TRUE (map_array) << field_name;
573+ ASSERT_OK_AND_ASSIGN (auto normalized,
574+ NormalizeMapBlobValues (map_array, blob_as_descriptor));
575+ auto expected = arrow::ipc::internal::json::ArrayFromJSON (map_array->type (), expected_json)
576+ .ValueOrDie ();
577+ ASSERT_TRUE (expected->Equals (normalized))
578+ << field_name << " expected: " << expected->ToString ()
579+ << " actual: " << normalized->ToString ();
580+ }
581+
538582 // / Verify DataFileMeta properties from a scan plan.
539583 // / Each vector element corresponds to one expected DataFileMeta (ordered by file index).
540584 static void VerifyDataFileMetas (
@@ -4334,4 +4378,111 @@ TEST_P(BlobTableInteTest, TestReadBlobDescriptorFieldFromJava) {
43344378 ASSERT_TRUE (resolved->Equals (expected_with_rk));
43354379}
43364380
4381+ TEST_P (BlobTableInteTest, TestReadMapBlobTableFromJava) {
4382+ if (GetParam () != " parquet" ) {
4383+ GTEST_SKIP () << " the Java fixture uses Parquet" ;
4384+ }
4385+ const std::string table_path = GetDataDir () + " /parquet/map_blob_java.db/map_blob_java" ;
4386+ const std::vector<std::string> read_fields = {" id" ,
4387+ " string_payloads" ,
4388+ " boolean_payloads" ,
4389+ " tinyint_payloads" ,
4390+ " smallint_payloads" ,
4391+ " int_payloads" ,
4392+ " bigint_payloads" ,
4393+ " date_payloads" ,
4394+ " binary_payloads" ,
4395+ " compact_decimal_payloads" ,
4396+ " large_decimal_payloads" };
4397+
4398+ for (int64_t snapshot_id : {1 , 3 }) {
4399+ ScanContextBuilder scan_builder (table_path);
4400+ scan_builder.AddOption (Options::SCAN_SNAPSHOT_ID , std::to_string (snapshot_id));
4401+ ASSERT_OK_AND_ASSIGN (auto scan_context, scan_builder.Finish ());
4402+ ASSERT_OK_AND_ASSIGN (auto table_scan, TableScan::Create (std::move (scan_context)));
4403+ ASSERT_OK_AND_ASSIGN (auto plan, table_scan->CreatePlan ());
4404+
4405+ size_t string_layer_count = 0 ;
4406+ for (const auto & split : plan->Splits ()) {
4407+ auto data_split = std::dynamic_pointer_cast<DataSplitImpl>(split);
4408+ ASSERT_TRUE (data_split);
4409+ for (const auto & file : data_split->DataFiles ()) {
4410+ if (file->write_cols ==
4411+ std::optional<std::vector<std::string>>({" string_payloads" })) {
4412+ ++string_layer_count;
4413+ }
4414+ }
4415+ }
4416+ ASSERT_EQ (snapshot_id == 1 ? 1 : 3 , string_layer_count);
4417+
4418+ for (bool blob_as_descriptor : {false , true }) {
4419+ std::map<std::string, std::string> read_options = {
4420+ {Options::BLOB_AS_DESCRIPTOR , blob_as_descriptor ? " true" : " false" }};
4421+ ASSERT_OK_AND_ASSIGN (auto result, ReadTable (table_path, read_fields, plan,
4422+ /* predicate=*/ nullptr , read_options));
4423+ ASSERT_TRUE (result);
4424+ auto combined = arrow::Concatenate (result->chunks ()).ValueOrDie ();
4425+ auto rows = std::dynamic_pointer_cast<arrow::StructArray>(combined);
4426+ ASSERT_TRUE (rows);
4427+ ASSERT_EQ (4 , rows->length ());
4428+ const auto & ids = checked_cast<const arrow::Int32Array&>(*rows->GetFieldByName (" id" ));
4429+ for (int64_t i = 0 ; i < ids.length (); ++i) {
4430+ ASSERT_EQ (i + 1 , ids.Value (i));
4431+ }
4432+
4433+ CheckMapBlobColumn (
4434+ rows, " string_payloads" ,
4435+ R"json( [[["", "string-empty"], ["alpha", "string-alpha"]], [], null, [["omega", "string-omega"]]])json" ,
4436+ blob_as_descriptor);
4437+ CheckMapBlobColumn (
4438+ rows, " boolean_payloads" ,
4439+ R"json( [[[false, "bool-false"], [true, "bool-true"]], null, null, null])json" ,
4440+ blob_as_descriptor);
4441+ CheckMapBlobColumn (
4442+ rows, " tinyint_payloads" ,
4443+ R"json( [[[-128, "tiny-min"], [-1, "tiny-negative"], [127, "tiny-max"]], null, null, null])json" ,
4444+ blob_as_descriptor);
4445+ CheckMapBlobColumn (
4446+ rows, " smallint_payloads" ,
4447+ R"json( [[[-32768, "small-min"], [-1, "small-negative"], [32767, "small-max"]], null, null, null])json" ,
4448+ blob_as_descriptor);
4449+ CheckMapBlobColumn (
4450+ rows, " int_payloads" ,
4451+ R"json( [[[-2147483648, "int-min"], [-1, "int-negative"], [2147483647, "int-max"]], null, null, null])json" ,
4452+ blob_as_descriptor);
4453+ CheckMapBlobColumn (
4454+ rows, " bigint_payloads" ,
4455+ R"json( [[[-9223372036854775808, "big-min"], [-1, "big-negative"], [9223372036854775807, "big-max"]], null, null, null])json" ,
4456+ blob_as_descriptor);
4457+ CheckMapBlobColumn (
4458+ rows, " date_payloads" ,
4459+ R"json( [[[-1, "date-negative"], [0, "date-epoch"]], null, null, null])json" ,
4460+ blob_as_descriptor);
4461+ CheckMapBlobColumn (
4462+ rows, " compact_decimal_payloads" ,
4463+ R"json( [[["-99999999.99", "compact-negative"], ["99999999.99", "compact-positive"]], null, null, null])json" ,
4464+ blob_as_descriptor);
4465+ CheckMapBlobColumn (
4466+ rows, " large_decimal_payloads" ,
4467+ R"json( [[["-999999999999999999.99", "large-negative"], ["999999999999999999.99", "large-positive"]], null, null, null])json" ,
4468+ blob_as_descriptor);
4469+
4470+ auto binary_map =
4471+ std::dynamic_pointer_cast<arrow::MapArray>(rows->GetFieldByName (" binary_payloads" ));
4472+ ASSERT_TRUE (binary_map);
4473+ ASSERT_OK_AND_ASSIGN (auto normalized_binary,
4474+ NormalizeMapBlobValues (binary_map, blob_as_descriptor));
4475+ const auto & binary_keys =
4476+ checked_cast<const arrow::BinaryArray&>(*normalized_binary->keys ());
4477+ const auto & binary_values =
4478+ checked_cast<const arrow::LargeBinaryArray&>(*normalized_binary->items ());
4479+ ASSERT_EQ (2 , normalized_binary->value_length (0 ));
4480+ ASSERT_EQ (" " , binary_keys.GetString (0 ));
4481+ ASSERT_EQ (std::string (" \0\xff\1\2 " , 4 ), binary_keys.GetString (1 ));
4482+ ASSERT_EQ (" binary-empty" , binary_values.GetString (0 ));
4483+ ASSERT_EQ (" binary-bytes" , binary_values.GetString (1 ));
4484+ }
4485+ }
4486+ }
4487+
43374488} // namespace paimon::test
0 commit comments