blob: dc128b30c89bee3b8a70e317188657a1b2295b8c [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.
"""Write pending primary-key events in arrival order, as Java does."""
import pyarrow as pa
from pypaimon.common.options.core_options import ChangelogProducer
from pypaimon.table.row.key_value import KeyValue
from pypaimon.execution.python.write.writer.key_value_data_writer import KeyValueDataWriter
class PostponeDataWriter(KeyValueDataWriter):
def __init__(self, **kwargs):
# Pending files never restore bucket sequences or produce a separate
# input changelog. Their own events become input to bucket assignment.
kwargs['max_seq_number'] = -1
kwargs['changelog_producer'] = ChangelogProducer.NONE
super().__init__(**kwargs)
self._retract_validated = False
self._failed = False
self._writing_stats = None
def write(self, data):
self._ensure_active()
try:
super().write(data)
except Exception:
self._failed = True
raise
def _ensure_active(self):
if self._failed:
raise RuntimeError('Postpone writer failed; create a new writer')
def _process_data(self, data):
enhanced = self._add_system_fields(data)
self._validate_retract(enhanced, enhanced.column('_VALUE_KIND').to_pylist())
return pa.Table.from_batches([enhanced])
def _validate_retract(self, data, kinds):
if self._retract_validated:
return
for index, kind in enumerate(kinds):
if kind not in (1, 3):
continue
row = tuple(column[index].as_py() for column in data.columns)
kv = KeyValue(len(self.trimmed_primary_keys), len(self.table.fields)).replace(row)
self._merge_function.reset()
self._merge_function.add(kv)
self._merge_function.get_result()
self._retract_validated = True
break
def _check_and_roll_if_needed(self):
if self._buffer.num_rows and (
self._buffer.num_rows >= self.target_file_row_num
or self._buffer.nbytes >= self.target_file_size):
self._flush_all()
def _flush_all(self):
data = self._buffer.materialize()
if data is not None and data.num_rows:
self._write_data_to_file(data)
self._buffer.reset()
def _write_data_to_file(self, data):
sequence = data.column('_SEQUENCE_NUMBER')
self._writing_stats = (sequence[0].as_py(), sequence[-1].as_py())
try:
super()._write_data_to_file(data)
finally:
self._writing_stats = None
def _create_data_file_meta(self, **kwargs):
assert self._writing_stats is not None
minimum, maximum = self._writing_stats
kwargs['min_sequence_number'] = minimum
kwargs['max_sequence_number'] = maximum
return super()._create_data_file_meta(**kwargs)
def prepare_commit(self):
self._ensure_active()
try:
return super().prepare_commit()
except Exception:
self._failed = True
self.abort()
raise
def close(self):
# Prepared messages own their files; discard only unprepared events.
self.abort()