blob: 95e4434710993167b64b2f280744823f396c59ea [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 collections.abc import Mapping
from typing import Any, Dict, Iterable, List
import pyarrow as pa
from pypaimon.schema.data_types import DataField, PyarrowFieldParser, is_blob_file_field
from pypaimon.table.row.blob import Blob, BlobView
from pypaimon.table.row.internal_row import InternalRow
from pypaimon.table.row.vector import Vector
def validate_write_columns(write_cols):
"""Validate field selections before changing a live writer's configuration."""
names = list(write_cols)
if not names:
raise ValueError('write columns must not be empty')
seen = set()
for name in names:
if name in seen:
raise ValueError("Duplicate write column '%s'" % name)
seen.add(name)
return names
def row_to_named_values(
row: InternalRow, table_fields: List[DataField]) -> Dict[str, Any]:
if hasattr(row, 'fields') and getattr(row, 'fields') is not None:
row_fields = getattr(row, 'fields')
return {
field.name: row.get_field(i)
for i, field in enumerate(row_fields)
}
if len(row) != len(table_fields):
raise ValueError(
"Rows without field metadata must have the same arity as the "
f"table schema: {len(row)} != {len(table_fields)}."
)
return {
field.name: row.get_field(i)
for i, field in enumerate(table_fields)
}
def require_columns(
values_by_name: Dict[str, Any],
column_names: Iterable[str],
context: str) -> None:
missing = [
column_name
for column_name in column_names
if column_name not in values_by_name
]
if missing:
raise ValueError(
f"{context} requires row field(s) {missing}, "
f"but row only contains {list(values_by_name.keys())}."
)
def inline_blob_value(value: Any, descriptor: bool, view: bool) -> Any:
"""Serialize row objects to inline references without opening payloads."""
if descriptor and isinstance(value, Blob):
try:
return value.to_descriptor().serialize()
except Exception as error:
raise ValueError(
"blob-descriptor-field row values must be serialized "
"BlobDescriptor bytes or a Blob with a descriptor."
) from error
if view and isinstance(value, BlobView):
return value.view_struct.serialize()
return value
def normalize_inline_blob_row(
row: InternalRow,
values_by_name: Dict[str, Any],
field_dict: Dict[str, DataField],
column_names: List[str],
descriptor_fields: Iterable[str],
view_fields: Iterable[str]):
"""Normalize inline BLOB reference fields in a row and value map to bytes."""
descriptor_set = set(descriptor_fields)
view_set = set(view_fields)
inline_columns = [
name for name in column_names
if name in descriptor_set or name in view_set
]
if not inline_columns:
return row, values_by_name
values_by_name = dict(values_by_name)
for name in inline_columns:
values_by_name[name] = inline_blob_value(
values_by_name[name],
name in descriptor_set,
name in view_set,
)
from pypaimon.table.row.generic_row import GenericRow
row = GenericRow(
[values_by_name[name] for name in column_names],
[field_dict[name] for name in column_names],
row.get_row_kind(),
)
return row, values_by_name
def value_for_arrow(value: Any, field: DataField) -> Any:
if isinstance(value, Vector):
return value.to_list()
if isinstance(value, Blob) or (
is_blob_file_field(field)
and _contains_blob_value(value)
):
raise ValueError(
"Blob values cannot be converted to Arrow without materializing "
"the stream. Use a Row-aware blob write path."
)
return value
def _contains_blob_value(value: Any) -> bool:
if isinstance(value, Blob):
return True
if isinstance(value, Mapping):
return any(_contains_blob_value(item) for item in value.values())
if isinstance(value, (list, tuple)):
return any(_contains_blob_value(item) for item in value)
return False
def row_values_to_arrow_table(
values_by_name: Dict[str, Any],
fields: List[DataField],
column_names: List[str]) -> pa.Table:
field_by_name = {field.name: field for field in fields}
selected_fields = [field_by_name[name] for name in column_names]
schema = PyarrowFieldParser.from_paimon_schema(selected_fields)
data = {
name: [value_for_arrow(values_by_name[name], field_by_name[name])]
for name in column_names
}
return pa.Table.from_pydict(data, schema=schema)