blob: 897dc412fd3b94cd050485c93fb63133d210f401 [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 Any, Dict, List, Mapping, Optional, Sequence
import pyarrow as pa
from pypaimon.common.predicate import Predicate
from pypaimon.table.row.internal_row import InternalRow
from pypaimon.write.row_utils import require_columns
def _authorize_update(table):
"""Require the same whole-table grant as plan_all for fast paths."""
from pypaimon.catalog.catalog_exception import TableNoPermissionException
from pypaimon.read.auth_support import authorize
query_auth = table.catalog_environment.table_query_auth(table.options, table.identifier)
if authorize(table, query_auth, None) is not None:
raise TableNoPermissionException(table.identifier)
def predicate_fields(predicate: Predicate) -> set:
if predicate.field is not None:
return {predicate.field}
fields = set()
for child in predicate.literals or []:
fields.update(predicate_fields(child))
return fields
def _validate_predicate_update(
self, assignments: Mapping[str, Any], read_columns: Optional[Sequence[str]],
has_callable: bool, has_array: bool):
if not self.table.options.data_evolution_enabled():
raise ValueError("update_by_predicate requires 'data-evolution.enabled' = 'true'.")
if not self.table.options.row_tracking_enabled():
raise ValueError("update_by_predicate requires 'row-tracking.enabled' = 'true'.")
if not assignments:
raise ValueError('assignments must not be empty.')
if read_columns and (not has_callable):
raise ValueError('read_columns requires a callable assignment.')
if has_callable:
if has_array:
raise ValueError('Callable assignments cannot be combined with Arrow array assignments.')
if not read_columns:
raise ValueError('Callable assignments require read_columns.')
for col in read_columns:
if col not in self.table.field_names:
raise ValueError(f'Read column {col} is not in table schema.')
partition_keys = set(self.table.partition_keys)
for col in assignments:
if col not in self.table.field_names:
raise ValueError(f'Column {col} is not in table schema.')
if col in partition_keys:
raise ValueError(f"update_by_predicate does not support updating partition column '{col}'.")
def _validate_inputs(self, data: pa.Table, upsert_keys: List[str], update_cols: Optional[List[str]]):
"""Validate inputs before processing."""
if not self.table.options.data_evolution_enabled():
raise ValueError("upsert_by_arrow_with_key requires 'data-evolution.enabled' = 'true'.")
if not self.table.options.row_tracking_enabled():
raise ValueError("upsert_by_arrow_with_key requires 'row-tracking.enabled' = 'true'.")
if not upsert_keys:
raise ValueError('upsert_keys must not be empty.')
for key in upsert_keys:
if key not in self.table.field_names:
raise ValueError(f"upsert_key '{key}' is not in table schema fields: {self.table.field_names}")
if key not in data.column_names:
raise ValueError(f"upsert_key '{key}' is not in input data columns: {data.column_names}")
partition_keys = self.table.partition_keys
if partition_keys:
missing_in_data = [pk for pk in partition_keys if pk not in data.column_names]
if missing_in_data:
raise ValueError(
'For partitioned tables, input data must contain all partition key '
f'columns. Missing: {missing_in_data}')
if update_cols is not None:
for col in update_cols:
if col not in self.table.field_names:
raise ValueError(
f"Column '{col}' in update_cols is not in table schema fields: {self.table.field_names}")
if data.num_rows == 0:
raise ValueError('Input data is empty.')
def _validate_row_inputs(self, values_by_name: Dict[str, Any],
upsert_keys: List[str], update_cols: Optional[List[str]]):
if not self.table.options.data_evolution_enabled():
raise ValueError("upsert_by_key requires 'data-evolution.enabled' = 'true'.")
if not self.table.options.row_tracking_enabled():
raise ValueError("upsert_by_key requires 'row-tracking.enabled' = 'true'.")
if not upsert_keys:
raise ValueError('upsert_keys must not be empty.')
for key in upsert_keys:
if key not in self.table.field_names:
raise ValueError(f"upsert_key '{key}' is not in table schema fields: {self.table.field_names}")
unknown_fields = [field_name for field_name in values_by_name if field_name not in self.table.field_names]
if unknown_fields:
raise ValueError(
f'upsert_by_key got row field(s) {unknown_fields} '
f'that are not in table schema fields: {self.table.field_names}')
require_columns(values_by_name, upsert_keys, 'upsert_by_key')
require_columns(values_by_name, self.table.partition_keys, 'upsert_by_key')
if update_cols is not None:
for col in update_cols:
if col not in self.table.field_names:
raise ValueError(
f"Column '{col}' in update_cols is not in table schema fields: {self.table.field_names}")
def _normalize_rows(rows) -> List:
if isinstance(rows, InternalRow):
return [rows]
return list(rows)