blob: 1002a08f2fb722e24b36584976adac92610f6932 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Shared helpers for materialising multimodal BLOB descriptor columns."""
def fetch_blob_bodies(
file_io, data, blob_cols, parallelism, map_blob_cols=(), array_blob_cols=()):
"""Fetch scalar, MAP and ARRAY BLOB payload bytes.
``data`` is a ``dict`` mapping each BLOB column name to row-aligned cells.
A cell may be serialized ``BlobDescriptor`` bytes, inline payload bytes,
``None``, an ARRAY of such values, or a MAP represented by key-value pairs.
Returned values preserve row and element order and are grouped per column.
"""
from pypaimon.table.row.blob import (
BlobDescriptorSerde,
BlobViewStruct,
)
ranges = []
inline = {}
targets = []
bodies = {col: [] for col in blob_cols}
scalar_offsets = {}
map_blob_cols = set(map_blob_cols)
array_blob_cols = set(array_blob_cols)
def queue_blob_fetch(value):
index = len(ranges)
if value is None:
ranges.append(None)
else:
raw = bytes(value)
if BlobViewStruct.is_blob_view_struct(raw):
raise ValueError(
"read_blobs does not support unresolved blob-view columns; "
"read such a column on its own, or enable blob-view resolution.")
if BlobDescriptorSerde.is_descriptor(raw):
descriptor = BlobDescriptorSerde.deserialize(raw)
ranges.append(
(descriptor.uri, descriptor.offset, descriptor.length)
)
else:
ranges.append(None)
inline[index] = raw
return index
for col in blob_cols:
is_map = col in map_blob_cols
if not is_map and col not in array_blob_cols:
start = len(ranges)
for value in data[col]:
queue_blob_fetch(value)
scalar_offsets[col] = (start, len(ranges))
continue
for value in data[col]:
if value is None:
bodies[col].append(None)
continue
entries = _map_entries(value) if is_map else enumerate(value)
row_index = len(bodies[col])
row = []
bodies[col].append(row)
for key, item in entries:
entry_index = len(row)
row.append((key, None) if is_map else None)
range_index = queue_blob_fetch(item)
targets.append((col, row_index, entry_index, range_index))
fetched = (
file_io.read_ranges_coalesced(ranges, parallelism)
if ranges
else []
)
for index, raw in inline.items():
fetched[index] = raw
for col, (start, end) in scalar_offsets.items():
bodies[col] = fetched[start:end]
for col, row_index, entry_index, index in targets:
value = fetched[index]
if col in map_blob_cols:
key = bodies[col][row_index][entry_index][0]
value = (key, value)
bodies[col][row_index][entry_index] = value
return bodies
def _map_entries(value):
if isinstance(value, dict):
return list(value.items())
if isinstance(value, (list, tuple)) and all(
isinstance(entry, (list, tuple)) and len(entry) == 2
for entry in value):
return value
return None