blob: 9056f00dcb0f24c597031733e947cbe253a1c2ca [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.
from typing import List, Optional
import pyarrow as pa
from pyarrow import RecordBatch
from pypaimon.read.reader.iface.record_batch_reader import RecordBatchReader
from pypaimon.table.row.blob import Blob
class DeferredBlobResolveReader(RecordBatchReader):
"""Materialize projected BLOB payloads after row filtering.
This must remain the outermost BLOB materialization layer because adopted
metadata still identifies the materialized columns as logical BLOB fields.
"""
def __init__(self, inner: RecordBatchReader, file_io,
blob_field_names: List[str], blob_parallelism: int = 1):
self._inner = inner
self._file_io = file_io
self._blob_field_names = blob_field_names
self._blob_parallelism = max(1, blob_parallelism)
self._adopt_metadata(inner)
def read_arrow_batch(self) -> Optional[RecordBatch]:
batch = self._inner.read_arrow_batch()
if batch is None:
return None
columns = list(batch.columns)
fields = list(batch.schema)
changed = False
for field_name in self._blob_field_names:
column_index = batch.schema.get_field_index(field_name)
if column_index < 0:
continue
values = batch.column(column_index).to_pylist()
blobs = [Blob.from_bytes(value, self._file_io) for value in values]
if self._blob_parallelism > 1:
payloads = self._file_io.read_blobs_concurrent(
blobs, self._blob_parallelism)
else:
payloads = [blob.to_data() if blob else None for blob in blobs]
source_field = batch.schema.field(column_index)
columns[column_index] = pa.array(payloads, type=pa.large_binary())
fields[column_index] = pa.field(
field_name,
pa.large_binary(),
nullable=source_field.nullable,
metadata=source_field.metadata,
)
changed = True
if not changed:
return batch
return pa.RecordBatch.from_arrays(
columns,
schema=pa.schema(fields, metadata=batch.schema.metadata),
)
def close(self) -> None:
self._inner.close()