This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new a6f1826af8 [Python] Accelerate shard column updates with Rust core 
(#10499)
a6f1826af8 is described below

commit a6f1826af86a5bd589ad1a923f91fc1acb979d11
Author: Jingsong Lee <[email protected]>
AuthorDate: Sun Oct 11 13:34:00 2026 +0800

    [Python] Accelerate shard column updates with Rust core (#10499)
---
 paimon-python/conftest.py                          |   5 +-
 .../pypaimon/tests/native_shard_update_test.py     | 339 +++++++++++++++++++++
 paimon-python/pypaimon/write/native_update.py      |  52 +++-
 paimon-python/pypaimon/write/table_update.py       |   8 +
 4 files changed, 401 insertions(+), 3 deletions(-)

diff --git a/paimon-python/conftest.py b/paimon-python/conftest.py
index 85c1f635a0..ef969ffb34 100644
--- a/paimon-python/conftest.py
+++ b/paimon-python/conftest.py
@@ -28,7 +28,7 @@ _native_plan_count = 0
 _native_read_count = 0
 _native_write_count = 0
 _native_commit_count = 0
-_native_update_counts = dict.fromkeys(('row_id', 'grouped', 'predicate', 
'upsert', 'incremental', 'merge'), 0)
+_native_update_counts = dict.fromkeys(('row_id', 'grouped', 'predicate', 
'upsert', 'incremental', 'merge', 'shard'), 0)
 _force_native_for_test = False
 _force_native_read_for_test = False
 _force_native_write_for_test = False
@@ -176,7 +176,7 @@ def pytest_configure(config):
         from pypaimon.write.native_merge_into import NativeTableMergeInto
         from pypaimon.write.native_update import (
             NativeBatchTableUpdate, NativePredicateTableUpdate,
-            NativeTableUpdateByRowId, NativeTableUpsert,
+            NativeTableUpdateByRowId, NativeTableUpsert, 
NativeShardTableUpdator,
         )
 
         def track_update(cls, method, kind):
@@ -196,6 +196,7 @@ def pytest_configure(config):
         track_update(NativeTableUpsert, 'upsert', 'upsert')
         track_update(NativeTableUpdateByRowId, 'update_columns', 'incremental')
         track_update(NativeTableMergeInto, 'prepare_commit', 'merge')
+        track_update(NativeShardTableUpdator, 'prepare_commit', 'shard')
 
 
 def pytest_collection_modifyitems(items):
diff --git a/paimon-python/pypaimon/tests/native_shard_update_test.py 
b/paimon-python/pypaimon/tests/native_shard_update_test.py
new file mode 100644
index 0000000000..57a699ba45
--- /dev/null
+++ b/paimon-python/pypaimon/tests/native_shard_update_test.py
@@ -0,0 +1,339 @@
+# 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.
+
+"""End-to-end coverage of the optional native batch row-ID update bridge."""
+
+import json
+from unittest.mock import patch
+
+import pyarrow as pa
+import pyarrow.compute as pc
+import pytest
+
+from pypaimon import Schema
+
+pytestmark = pytest.mark.native_plan
+
+
+def _table(catalog, rows=None, partitions=False):
+    schema = pa.schema([('p', pa.string()), ('id', pa.int32()), ('value', 
pa.int32())])
+    catalog.create_table('default.shards', Schema.from_pyarrow_schema(
+        schema, partition_keys=['p'] if partitions else [], options={
+            'data-evolution.enabled': 'true', 'row-tracking.enabled': 'true',
+            'deletion-vectors.enabled': 'true', 'write.native.enabled': 'true',
+        }), False)
+    table = catalog.get_table('default.shards')
+    if rows:
+        _append(table, rows, schema)
+    return table, schema
+
+
+def _append(table, rows, schema):
+    builder = table.new_batch_write_builder()
+    writer = builder.new_write()
+    try:
+        writer.write_arrow(pa.Table.from_pylist(rows, schema=schema))
+        builder.new_commit().commit(writer.prepare_commit())
+    finally:
+        writer.close()
+
+
+def _updater(table, index=0, count=1, projection=('id',), columns=('value',), 
stream=False):
+    builder = table.new_stream_write_builder() if stream else 
table.new_batch_write_builder()
+    update = builder.new_update()
+    if projection is not None:
+        update.with_read_projection(projection if isinstance(projection, dict) 
else list(projection))
+    update.with_update_type(list(columns))
+    with patch('pypaimon.write.table_update.ShardTableUpdator',
+               side_effect=AssertionError('Python shard mapping was used')):
+        return update.new_shard_updator(index, count)
+
+
+def _rows(table):
+    builder = table.new_read_builder()
+    return 
builder.new_read().to_arrow(builder.new_scan().plan().splits()).sort_by('id').to_pydict()
+
+
[email protected]('stream', [False, True])
+def 
test_native_shard_updates_use_the_captured_reader_order(native_rest_catalog, 
stream):
+    table, schema = _table(native_rest_catalog, [
+        dict(p='a', id=1, value=10), dict(p='a', id=2, value=20)])
+    updater = _updater(table, stream=stream)
+    _append(table, [dict(p='a', id=3, value=30)], schema)
+    reader = updater.arrow_reader()
+    for batch in reader:
+        assert batch.schema.names == ['id']
+        
updater.update_by_arrow_batch(pa.record_batch([pc.multiply(batch['id'], 100)], 
names=['value']))
+    messages = updater.prepare_commit()
+    assert all(message.check_from_snapshot == 1 for message in messages)
+    builder = table.new_stream_write_builder() if stream else 
table.new_batch_write_builder()
+    builder.new_commit().commit(messages, 27) if stream else 
builder.new_commit().commit(messages)
+    updater.close()
+    assert _rows(table) == dict(p=['a', 'a', 'a'], id=[1, 2, 3], value=[100, 
200, 30])
+
+
[email protected]('partitions', [False, True])
[email protected]('count', [1, 3, 7])
+def test_native_shards_are_balanced_by_file_group(native_rest_catalog, 
partitions, count):
+    table, schema = _table(native_rest_catalog, partitions=partitions)
+    for i in range(5):
+        _append(table, [dict(p=str(i % 2), id=i, value=i * 10)], schema)
+    snapshot_id = table.snapshot_manager().get_latest_snapshot().id
+    updaters = [_updater(table, i, count, projection=('_ROW_ID', 'id')) for i 
in range(count)]
+    sizes = []
+    for updater in updaters:
+        with updater.arrow_reader() as reader:
+            batches = list(reader)
+        sizes.append(sum(batch.num_rows for batch in batches))
+        if batches:
+            ids = pa.Table.from_batches(batches)['id']
+            # One input table may span physical files and partitions.
+            updater.update_by_arrow_batch(pa.record_batch([pc.multiply(ids, 
100).combine_chunks()], names=['value']))
+        messages = updater.prepare_commit()
+        assert all(message.check_from_snapshot == snapshot_id for message in 
messages)
+        for message in messages:
+            assert message.bucket == 0
+            assert all(file.first_row_id is not None and file.write_cols == 
['value'] for file in message.new_files)
+        table.new_batch_write_builder().new_commit().commit(messages)
+        updater.close()
+    base, remainder = divmod(5, count)
+    assert sizes == [base + (i < remainder) for i in range(count)]
+    assert _rows(table)['value'] == [0, 100, 200, 300, 400]
+    # Replacements and base files overlap, so they must still be one file 
group.
+    with _updater(table, 0, 7).arrow_reader() as reader:
+        assert sum(batch.num_rows for batch in reader) == 1
+
+
+def test_native_shard_deleted_rows_do_not_shift_updates(native_rest_catalog):
+    table, _ = _table(native_rest_catalog, [dict(p='a', id=i, value=i * 10) 
for i in range(1, 5)])
+    builder = table.new_batch_write_builder()
+    builder.new_commit().commit(builder.new_update().delete_by_row_id([1, 3]))
+    updater = _updater(table, projection=('id', '_ROW_ID'))
+    with updater.arrow_reader() as reader:
+        data = pa.Table.from_batches(list(reader))
+    assert data.to_pydict() == {'id': [1, 3], '_ROW_ID': [0, 2]}
+    updater.update_by_arrow_batch(pa.record_batch([pa.array([111, 333], 
type=pa.int32())], names=['value']))
+    messages = updater.prepare_commit()
+    assert all(file.row_count == 4 and file.first_row_id == 0 for m in 
messages for file in m.new_files)
+    table.new_batch_write_builder().new_commit().commit(messages)
+    assert _rows(table) == dict(p=['a', 'a'], id=[1, 3], value=[111, 333])
+
+
[email protected]('bad', ['wrong_column', 'wrong_type', 'excess_rows'])
+def 
test_native_shard_invalid_batch_does_not_consume_row_ids(native_rest_catalog, 
bad):
+    table, _ = _table(native_rest_catalog, [dict(p='a', id=i, value=i * 10) 
for i in range(1, 4)])
+    updater = _updater(table)
+    with updater.arrow_reader() as reader:
+        assert sum(batch.num_rows for batch in reader) == 3
+    if bad == 'wrong_column':
+        data = pa.record_batch([pa.array([99], type=pa.int32())], 
names=['missing'])
+    elif bad == 'wrong_type':
+        data = pa.record_batch([pa.array(['invalid'])], names=['value'])
+    else:
+        data = pa.record_batch([pa.array([99] * 4, type=pa.int32())], 
names=['value'])
+    with pytest.raises(ValueError):
+        updater.update_by_arrow_batch(data)
+    updater.update_by_arrow_batch(pa.record_batch([pa.array([100], 
type=pa.int32())], names=['value']))
+    with pytest.raises(ValueError, match='same number of rows'):
+        updater.prepare_commit()
+    assert table.snapshot_manager().get_latest_snapshot().id == 1
+    updater.update_by_arrow_batch(pa.record_batch([pa.array([200, 300], 
type=pa.int32())], names=['value']))
+    messages = updater.prepare_commit()
+    repeated = updater.prepare_commit()
+    assert [f.file_name for m in messages for f in m.new_files] == 
[f.file_name for m in repeated for f in m.new_files]
+    updater.close()
+    table.new_batch_write_builder().new_commit().commit(messages)
+    assert _rows(table)['value'] == [100, 200, 300]
+    with pytest.raises(ValueError):
+        updater.update_by_arrow_batch(data)
+
+
+def 
test_native_shard_closed_reader_cannot_prepare_an_incomplete_update(native_rest_catalog):
+    table, schema = _table(native_rest_catalog, [dict(p='a', id=1, value=10)])
+    _append(table, [dict(p='a', id=2, value=20)], schema)
+    updater = _updater(table)
+    reader = updater.arrow_reader()
+    batch = reader.read_next_batch()
+    updater.update_by_arrow_batch(pa.record_batch([pa.array([99] * 
batch.num_rows, type=pa.int32())], names=['value']))
+    reader.close()
+    with pytest.raises(ValueError, match='same number of rows'):
+        updater.prepare_commit()
+    with pytest.raises(ValueError, match='only be opened once'):
+        updater.arrow_reader()
+    updater.close()
+    assert _rows(table)['value'] == [10, 20]
+
+
+def 
test_native_shard_no_row_count_rolling_and_full_column_update(native_rest_catalog):
+    table, _ = _table(native_rest_catalog, [dict(p='a', id=i, value=i * 10) 
for i in range(5)])
+    table = table.copy({'target-file-row-num': '1'})
+    # All-column updates follow the same contract as a column subset.
+    updater = _updater(table, projection=('id',), columns=('p', 'id', 'value'))
+    with updater.arrow_reader() as reader:
+        batches = list(reader)
+    for batch in batches:
+        ids = batch['id'].to_pylist()
+        updater.update_by_arrow_batch(pa.record_batch([
+            pa.array([i + 10 for i in ids], type=pa.int32()),
+            pa.array(['new'] * len(ids)),
+            pa.array([i + 100 for i in ids], type=pa.int32()),
+        ], names=['id', 'p', 'value']))
+    messages = updater.prepare_commit()
+    assert len([file for m in messages for file in m.new_files]) == 1
+    table.new_batch_write_builder().new_commit().commit(messages)
+    assert _rows(table) == dict(p=['new'] * 5, id=list(range(10, 15)), 
value=list(range(100, 105)))
+
+
+def test_native_empty_shard_and_configuration_validation(native_rest_catalog):
+    table, _ = _table(native_rest_catalog)
+    updater = _updater(table, 2, 4)
+    with updater.arrow_reader() as reader:
+        assert list(reader) == []
+    assert updater.prepare_commit() == []
+    for index, count in [(-1, 1), (0, 0), (2, 2)]:
+        with pytest.raises(ValueError, match='Shard index'):
+            
table.new_batch_write_builder().new_update().new_shard_updator(index, count)
+    with pytest.raises(ValueError, match='not be empty'):
+        _updater(table, columns=())
+
+
+def test_native_shard_rejects_partition_column_updates(native_rest_catalog):
+    table, _ = _table(native_rest_catalog, partitions=True)
+    with pytest.raises(NotImplementedError, match='partition column'):
+        _updater(table, columns=('p',))
+
+
[email protected]('data_type, original, replacement', [
+    (pa.list_(pa.int32()), [[1, None], None], [[3, 4], []]),
+    (pa.map_(pa.string(), pa.int32()), [[('a', 1)], None], [[('b', None)], 
[]]),
+    (pa.struct([('a', pa.int32()), ('b', pa.string())]),
+     [dict(a=1, b='before'), None], [dict(a=None, b='after'), dict(a=2, 
b=None)]),
+])
+def 
test_native_shard_updates_existing_complex_column_types(native_rest_catalog, 
data_type, original, replacement):
+    schema = pa.schema([('id', pa.int32()), ('value', data_type)])
+    native_rest_catalog.create_table('default.complex_shards', 
Schema.from_pyarrow_schema(schema, options={
+        'row-tracking.enabled': 'true', 'data-evolution.enabled': 'true',
+        'write.native.enabled': 'true',
+    }), False)
+    table = native_rest_catalog.get_table('default.complex_shards')
+    builder = table.new_batch_write_builder()
+    writer = builder.new_write()
+    writer.write_arrow(pa.table({'id': [1, 2], 'value': original}, 
schema=schema))
+    builder.new_commit().commit(writer.prepare_commit())
+    writer.close()
+    updater = _updater(table)
+    with updater.arrow_reader() as reader:
+        assert sum(batch.num_rows for batch in reader) == 2
+    updater.update_by_arrow_batch(pa.record_batch([pa.array(replacement, 
type=data_type)], names=['value']))
+    
table.new_batch_write_builder().new_commit().commit(updater.prepare_commit())
+    assert _rows(table) == dict(id=[1, 2], value=replacement)
+
+
[email protected]('projection, expected_names', [(None, ['p', 'id', 
'value']), ((), [])])
+def 
test_native_shard_default_and_empty_projection_preserve_rows(native_rest_catalog,
 projection, expected_names):
+    table, _ = _table(native_rest_catalog, [dict(p='a', id=i, value=i * 10) 
for i in range(1, 4)])
+    updater = _updater(table, projection=projection)
+    with updater.arrow_reader() as reader:
+        count = 0
+        for batch in reader:
+            assert batch.schema.names == expected_names
+            count += batch.num_rows
+            updater.update_by_arrow_batch(pa.record_batch([
+                pa.array([99] * batch.num_rows, type=pa.int32())], 
names=['value']))
+    assert count == 3
+    
table.new_batch_write_builder().new_commit().commit(updater.prepare_commit())
+    assert _rows(table) == dict(p=['a'] * 3, id=[1, 2, 3], value=[99] * 3)
+
+
[email protected]('stream', [False, True])
[email protected]('projection, expected', [
+    (['payload.b'], {'payload_b': [3, None, 4]}),
+    (["attributes['key']"], {'attributes_key': [5, None, None]}),
+    ({'source': 'id'}, {'source': [1, 2, 3]}),
+])
+def test_native_shard_read_type_preserves_nested_map_and_alias_outputs(
+        native_rest_catalog, stream, projection, expected):
+    schema = pa.schema([
+        ('id', pa.int32()), ('value', pa.int32()),
+        ('payload', pa.struct([('a', pa.int32()), ('b', pa.int32())])),
+        ('attributes', pa.map_(pa.string(), pa.int32())),
+    ])
+    native_rest_catalog.create_table('default.projected_shards', 
Schema.from_pyarrow_schema(schema, options={
+        'row-tracking.enabled': 'true', 'data-evolution.enabled': 'true', 
'write.native.enabled': 'true',
+    }), False)
+    table = native_rest_catalog.get_table('default.projected_shards')
+    _append(table, [
+        dict(id=1, value=10, payload=dict(a=100, b=3), attributes=[('key', 5), 
('other', 9)]),
+        dict(id=2, value=20, payload=None, attributes=None),
+        dict(id=3, value=30, payload=dict(a=200, b=4), attributes=[]),
+    ], schema)
+    updater = _updater(table, projection=projection, stream=stream)
+    with updater.arrow_reader() as reader:
+        data = pa.Table.from_batches(list(reader), schema=reader.schema)
+    assert data.to_pydict() == expected
+    replacement = data.column(0).combine_chunks()
+    updater.update_by_arrow_batch(pa.record_batch([replacement], 
names=['value']))
+    
table.new_batch_write_builder().new_commit().commit(updater.prepare_commit())
+    result = _rows(table)
+    assert result['id'] == [1, 2, 3]
+    assert result['value'] == replacement.to_pylist()
+    assert result['payload'] == [dict(a=100, b=3), None, dict(a=200, b=4)]
+    assert result['attributes'] == [[('key', 5), ('other', 9)], None, []]
+
+
+def 
test_native_shard_rejects_malformed_read_type_before_starting(native_rest_catalog):
+    from pypaimon.write.native_commit import create_native_write_table
+    table, _ = _table(native_rest_catalog, [dict(p='a', id=1, value=10)])
+    native = create_native_write_table(table)
+    update = 
native.new_batch_write_builder().new_update().with_update_type(['value'])
+    for read_type in ('{', '{"type": "INT"}'):
+        with pytest.raises(ValueError, match='invalid Paimon read type'):
+            update.new_shard_updator(0, 1, read_type=read_type)
+    for fields in (
+            [{'id': 2147483642, 'name': '_ROW_ID', 'type': 'INT'}],
+            [{'id': 0, 'name': '_ROW_ID', 'type': 'BIGINT'}],
+            [{'id': 2147483642, 'name': '_ROW_ID', 'type': 'BIGINT'}] * 2):
+        with pytest.raises(ValueError, match='canonical BIGINT _ROW_ID'):
+            update.new_shard_updator(0, 1, read_type=json.dumps({'type': 
'ROW', 'fields': fields}))
+    assert _rows(table)['value'] == [10]
+
+
[email protected]('stream', [False, True])
+def 
test_native_shard_read_type_preserves_variant_extraction(native_rest_catalog, 
stream):
+    from pypaimon.data.generic_variant import GenericVariant
+    payloads = [dict(v=1.5), None, dict(v=3.0)]
+    variants = GenericVariant.to_arrow_array([
+        GenericVariant.from_python(value) if value is not None else None for 
value in payloads])
+    schema = pa.schema([('id', pa.int32()), ('payload', variants.type), 
('value', pa.float32())])
+    native_rest_catalog.create_table('default.variant_shards', 
Schema.from_pyarrow_schema(schema, options={
+        'row-tracking.enabled': 'true', 'data-evolution.enabled': 'true', 
'write.native.enabled': 'true',
+    }), False)
+    table = native_rest_catalog.get_table('default.variant_shards')
+    data = pa.table({'id': [1, 2, 3], 'payload': variants, 'value': [0.0] * 
3}, schema=schema)
+    _append(table, data.to_pylist(), schema)
+    updater = _updater(table, projection={
+        'source': 'id', 'picked': "try_variant_get(payload, '$.v', 'float')"}, 
stream=stream)
+    with updater.arrow_reader() as reader:
+        data = pa.Table.from_batches(list(reader), schema=reader.schema)
+    assert data.to_pydict() == dict(source=[1, 2, 3], picked=[1.5, None, 3.0])
+    assert data.schema.field('picked').type == pa.float32()
+    
updater.update_by_arrow_batch(pa.record_batch([data['picked'].combine_chunks()],
 names=['value']))
+    
table.new_batch_write_builder().new_commit().commit(updater.prepare_commit())
+    result = _rows(table)
+    assert result['value'] == [1.5, None, 3.0]
+    assert [GenericVariant.from_arrow_struct(value).to_python() if value is 
not None else None
+            for value in result['payload']] == payloads
diff --git a/paimon-python/pypaimon/write/native_update.py 
b/paimon-python/pypaimon/write/native_update.py
index f421a42e7d..a7623dbfeb 100644
--- a/paimon-python/pypaimon/write/native_update.py
+++ b/paimon-python/pypaimon/write/native_update.py
@@ -17,9 +17,11 @@
 
 """Optional native row-ID updates for batch data-evolution tables."""
 
+import json
+
 import pyarrow as pa
 
-from pypaimon.schema.data_types import MapType, PyarrowFieldParser, 
is_blob_file_field
+from pypaimon.schema.data_types import MapType, PyarrowFieldParser, RowType, 
is_blob_file_field
 from pypaimon.common.options.core_options import ChangelogProducer, CoreOptions
 from pypaimon.snapshot.snapshot import BATCH_COMMIT_IDENTIFIER
 from pypaimon.snapshot.time_travel_util import SCAN_KEYS
@@ -86,6 +88,54 @@ def create_native_update_by_row_id(table, commit_user, 
commit_identifier):
         table, commit_user, commit_identifier, writer)
 
 
+def create_native_shard_updator(table, commit_user, projection, columns, 
shard, shard_count, stream=False):
+    """Core owns whole-file sharding, the captured reader and positional 
updates."""
+    from pypaimon.read.native_plan import native_method_available
+    if not native_method_available('BatchTableUpdate', 'new_shard_updator'):
+        return None
+    if not _native_update_columns_supported(table, columns):
+        return None
+    native_table = _native_row_id_table(table)
+    if native_table is None:
+        return None
+    builder = 
(native_table.new_stream_write_builder().with_commit_user(commit_user) if 
stream else
+               
native_table.new_batch_write_builder()._with_commit_user(commit_user))
+    update = builder.new_update()
+    read_builder = table.new_read_builder()
+    if projection is not None:
+        read_builder.with_projection(projection)
+    if columns is not None:
+        update.with_update_type(columns)
+    read = read_builder.new_read()
+    writer = update.new_shard_updator(
+        shard, shard_count, read_type=json.dumps(RowType(True, 
read_builder.read_type()).to_dict()))
+    return NativeShardTableUpdator(table, read, writer)
+
+
+class NativeShardTableUpdator:
+    """Transport Arrow batches and commit messages for a core shard updater."""
+
+    def __init__(self, table, read, writer):
+        self.table = table
+        self.writer = writer
+        self.read = read
+
+    def arrow_reader(self):
+        from pypaimon.read.table_read import _ClosableArrowBatchReader
+        schema = self.read._output_arrow_schema()
+        batches = 
self.read._convert_native_batches(self.writer.arrow_reader(), schema)
+        return 
_ClosableArrowBatchReader(pa.ipc.RecordBatchReader.from_batches(schema, 
batches), batches)
+
+    def update_by_arrow_batch(self, data):
+        self.writer.update_by_arrow_batch(data)
+
+    def prepare_commit(self):
+        return from_native_commit_messages(self.table, 
self.writer.prepare_commit())
+
+    def close(self):
+        self.writer.close()
+
+
 def _supported_upsert_key_type(data_type):
     return any(check(data_type) for check in (
         pa.types.is_boolean, pa.types.is_integer, pa.types.is_string,
diff --git a/paimon-python/pypaimon/write/table_update.py 
b/paimon-python/pypaimon/write/table_update.py
index c3f8159fe5..6c9bb6b839 100644
--- a/paimon-python/pypaimon/write/table_update.py
+++ b/paimon-python/pypaimon/write/table_update.py
@@ -172,6 +172,14 @@ class TableUpdate:
             shard_num: Index of this shard/subtask.
             total_shard_count: Total number of shards/subtasks.
         """
+        if total_shard_count <= 0 or shard_num < 0 or shard_num >= 
total_shard_count:
+            raise ValueError('Shard index must be smaller than a positive 
shard count')
+        from pypaimon.write.native_update import create_native_shard_updator
+        native = create_native_shard_updator(
+            self.table, self.commit_user, self.projection, self.update_cols,
+            shard_num, total_shard_count, stream=isinstance(self, 
StreamTableUpdate))
+        if native is not None:
+            return native
         return ShardTableUpdator(
             self.table,
             self.projection,

Reply via email to