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 60646cccb5 [python] Enable Native MAP shared-shredding writes and 
updates (#10382)
60646cccb5 is described below

commit 60646cccb5645a3ffc441aad02742fbddcbd9a6f
Author: Jingsong Lee <[email protected]>
AuthorDate: Sun Oct 4 21:23:16 2026 +0800

    [python] Enable Native MAP shared-shredding writes and updates (#10382)
---
 paimon-python/README.md                            |   8 +
 .../tests/map_shared_shredding_write_test.py       |   5 +-
 .../tests/native_map_shredding_write_test.py       | 327 +++++++++++++++++++++
 paimon-python/pypaimon/write/native_update.py      |   4 +-
 paimon-python/pypaimon/write/native_write.py       |  14 -
 .../pypaimon/write/table_update_by_row_id.py       |  29 +-
 6 files changed, 357 insertions(+), 30 deletions(-)

diff --git a/paimon-python/README.md b/paimon-python/README.md
index 45151f5cab..42d38e0561 100644
--- a/paimon-python/README.md
+++ b/paimon-python/README.md
@@ -168,6 +168,14 @@ path select the Python writer before native data is 
written. If the runtime or t
 unavailable, write uses Python. Once Rust starts writing a batch, errors
 propagate without retrying that batch through Python.
 
+MAP columns configured with `fields.<name>.map.storage-layout=shared-shredding`
+also use native Parquet writes and data-evolution updates, including predicate
+updates and upserts. Rust applies Java's `plain`, `sequential` and `lru` column
+placement policies (`lru` by default), and adapts the physical column count
+between completed files. Files remain readable by the Python reader, including
+literal MAP-key projections. Shared-shredding requires `none`, `lz4` or `zstd`
+compression.
+
 Primary-key dynamic buckets (`bucket=-1`, with partition fields included in the
 primary key) also support native writes. HASH indexes are restored across 
writer
 restarts; `dynamic-bucket.max-buckets` bounds bucket growth and reuses existing
diff --git a/paimon-python/pypaimon/tests/map_shared_shredding_write_test.py 
b/paimon-python/pypaimon/tests/map_shared_shredding_write_test.py
index fe82a6d2ed..2ca511d09e 100644
--- a/paimon-python/pypaimon/tests/map_shared_shredding_write_test.py
+++ b/paimon-python/pypaimon/tests/map_shared_shredding_write_test.py
@@ -271,7 +271,7 @@ class MapSharedShreddingWriteTest(unittest.TestCase):
     def test_streams_physical_batches(self):
         from pypaimon.write.map_shared_shredding_writer import 
_MapFieldConverter
 
-        table = self._create_table('parquet', 256)
+        table = self._create_table('parquet', 256, {'write.native.enabled': 
'false'})
         data = pa.Table.from_pydict({
             'id': list(range(5000)), 'metrics': [[('a', 1)]] * 4999 + 
[[('late', 2)]],
         }, schema=self.arrow_schema)
@@ -332,7 +332,7 @@ class MapSharedShreddingWriteTest(unittest.TestCase):
         for count in (1000, 2500):
             layouts = []
             for by_row in (False, True):
-                table = self._create_table('parquet', 256)
+                table = self._create_table('parquet', 256, 
{'write.native.enabled': 'false'})
                 builder = table.new_batch_write_builder()
                 writer = builder.new_write()
                 if by_row:
@@ -372,6 +372,7 @@ class MapSharedShreddingWriteTest(unittest.TestCase):
                 "data-evolution.enabled": "true",
                 "row-tracking.enabled": "true",
                 "target-file-row-num": "2",
+                "write.native.enabled": "false",
             },
         )
         data = pa.Table.from_pydict({
diff --git a/paimon-python/pypaimon/tests/native_map_shredding_write_test.py 
b/paimon-python/pypaimon/tests/native_map_shredding_write_test.py
new file mode 100644
index 0000000000..b45d3c1311
--- /dev/null
+++ b/paimon-python/pypaimon/tests/native_map_shredding_write_test.py
@@ -0,0 +1,327 @@
+# 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.
+
+"""Native MAP shared-shredding writes must preserve Java's physical 
contract."""
+
+from contextlib import nullcontext
+from unittest.mock import patch
+
+import pyarrow as pa
+import pyarrow.parquet as pq
+import pytest
+
+from pypaimon import CatalogFactory, Schema
+from pypaimon.data.map_shared_shredding import is_shared_shredding, 
parse_shared_shredding_metadata
+from pypaimon.write.native_write import NativeTableWrite
+from pypaimon.table.row.generic_row import GenericRow
+
+pytestmark = pytest.mark.native_plan
+
+
+def _table(tmp_path, mode='append', policy='lru', extra=None, value_type=None):
+    catalog = CatalogFactory.create({'warehouse': str(tmp_path)})
+    catalog.create_database('default', True)
+    schema = pa.schema([
+        ('id', pa.int32()), ('pt', pa.string()),
+        ('metrics', pa.map_(pa.string(), value_type or pa.int64())),
+    ])
+    options = {
+        'file.format': 'parquet', 'file.compression': 'zstd',
+        'bucket': '1' if mode == 'pk' else '-1', 'write.native.enabled': 
'true',
+        'fields.metrics.map.storage-layout': 'shared-shredding',
+        'fields.metrics.map.shared-shredding.max-columns': '3',
+        'fields.metrics.map.shared-shredding.column-placement-policy': policy,
+    }
+    if mode == 'evolution':
+        options.update({'data-evolution.enabled': 'true', 
'row-tracking.enabled': 'true'})
+    options.update(extra or {})
+    catalog.create_table('default.maps', Schema.from_pyarrow_schema(
+        schema, partition_keys=['pt'], primary_keys=['pt', 'id'] if mode == 
'pk' else [],
+        options=options), False)
+    return catalog.get_table('default.maps'), schema
+
+
+def _write(table, schema, rows, stream=False):
+    builder = table.new_stream_write_builder() if stream else 
table.new_batch_write_builder()
+    writer, commit = builder.new_write(), builder.new_commit()
+    assert isinstance(writer, NativeTableWrite)
+    try:
+        with patch.object(writer, '_switch_to_python', 
side_effect=AssertionError('Python writer fallback')):
+            for row in rows:
+                writer.write_arrow(pa.Table.from_pylist([row], schema=schema))
+            messages = writer.prepare_commit(1) if stream else 
writer.prepare_commit()
+            if stream:
+                commit.commit(messages, 1)
+            else:
+                commit.commit(messages)
+        return messages
+    finally:
+        writer.close()
+        commit.close()
+
+
+def _read(table, native, projection=None):
+    table = table.copy({'read.native.enabled': str(native).lower(),
+                        'scan.native-plan.enabled': str(native).lower()})
+    builder = table.new_read_builder()
+    if projection:
+        builder.with_projection(projection)
+    reader = builder.new_read()
+    splits = builder.new_scan().plan().splits()
+    guard = patch.object(reader, '_create_split_read', 
side_effect=AssertionError('Python reader fallback'))
+    with guard if native else nullcontext():
+        return sorted(reader.to_arrow(splits).to_pylist(), key=lambda row: 
row['id'])
+
+
[email protected]('mode', ['append', 'pk', 'evolution'])
[email protected]('policy', ['plain', 'sequential', 'lru'])
[email protected]('stream', [False, True])
+def test_native_map_layout_and_cross_read(tmp_path, mode, policy, stream):
+    table, schema = _table(tmp_path, mode, policy)
+    rows = [
+        {'id': 1, 'pt': 'p', 'metrics': [('a', 1), ('b', 2), ('c', 3)]},
+        {'id': 2, 'pt': 'p', 'metrics': [('b', 20), ('a', None)]},
+        {'id': 3, 'pt': 'p', 'metrics': [('d', 4), ('e', 5), ('f', 6)]},
+        {'id': 4, 'pt': 'p', 'metrics': [('a', 7), ('d', 8), ('e', 9), ('f', 
10)]},
+        {'id': 5, 'pt': 'p', 'metrics': []},
+        {'id': 6, 'pt': 'p', 'metrics': None},
+    ]
+    messages = _write(table, schema, rows, stream)
+    files = [file for msg in messages for file in msg.new_files]
+    assert len(files) == 1
+    physical = pq.ParquetFile(files[0].file_path).read()
+    field = physical.schema.field('metrics')
+    assert is_shared_shredding(field)
+    assert parse_shared_shredding_metadata(field)[1] == 3
+    mappings = 
physical.column('metrics').combine_chunks().field('__field_mapping').to_pylist()
+    assert mappings[1] == ([1, 0, -1] if policy == 'plain' else [0, 1, -1])
+    assert mappings[2] == ([4, 5, 3] if policy == 'lru' else [3, 4, 5])
+    for row in rows:
+        if row['metrics'] is not None:
+            row['metrics'] = dict(row['metrics'])
+    for native in (False, True):
+        actual = _read(table, native)
+        for row in actual:
+            if row['metrics'] is not None:
+                row['metrics'] = dict(row['metrics'])
+        assert actual == rows
+        assert _read(table, native, ['id', "metrics['a']", "metrics['f']"]) == 
[
+            {'id': row['id'], 'metrics_a': (row['metrics'] or {}).get('a'),
+             'metrics_f': (row['metrics'] or {}).get('f')} for row in rows]
+
+
[email protected]('mode', ['append', 'evolution'])
+def test_native_map_adapts_width_after_completed_files(tmp_path, mode):
+    table, schema = _table(tmp_path, mode, extra={'target-file-row-num': '1'})
+    rows = [
+        {'id': 1, 'pt': 'p', 'metrics': [('a', 1)]},
+        {'id': 2, 'pt': 'p', 'metrics': [('b', 2), ('c', 3)]},
+        {'id': 3, 'pt': 'p', 'metrics': [('a', 4), ('c', 5), ('d', 6)]},
+        {'id': 4, 'pt': 'q', 'metrics': None},
+        {'id': 5, 'pt': 'q', 'metrics': []},
+    ]
+    messages = _write(table, schema, rows)
+    widths = {}
+    for message in messages:
+        for file in message.new_files:
+            physical = pq.ParquetFile(file.file_path).read()
+            widths[physical.column('id')[0].as_py()] = 
parse_shared_shredding_metadata(
+                physical.schema.field('metrics'))[1]
+    # The first file uses max-columns; later files use completed-file widths.
+    # Each partition owns an independent context; zero widths clamp to one.
+    assert widths == {1: 3, 2: 1, 3: 2, 4: 3, 5: 1}
+    assert _read(table, True) == rows
+
+
[email protected]('policy', ['plain', 'sequential', 'lru'])
[email protected]('codec', ['none', 'lz4', 'zstd'])
+def test_native_map_duplicate_keys_take_last_value(tmp_path, policy, codec):
+    table, schema = _table(tmp_path, policy=policy, extra={
+        'file.compression': codec, 
'fields.metrics.map.shared-shredding.max-columns': '1',
+    })
+    rows = [
+        {'id': 1, 'pt': 'p', 'metrics': [('a', 1), ('a', 2), ('b', 3), ('b', 
None)]},
+        {'id': 2, 'pt': 'p', 'metrics': [('b', 4), ('a', 5)]},
+    ]
+    _write(table, schema, rows)
+    for native in (False, True):
+        actual = _read(table, native)
+        assert [dict(row['metrics']) for row in actual] == [{'a': 2, 'b': 
None}, {'a': 5, 'b': 4}]
+        # Java retains the shared/overflow duplicate, both using the last 
value.
+        assert actual[0]['metrics'] == [('a', 2), ('a', 2), ('b', None)]
+        assert _read(table, native, ['id', "metrics['a']", "metrics['b']"]) == 
[
+            {'id': 1, 'metrics_a': 2, 'metrics_b': None},
+            {'id': 2, 'metrics_a': 5, 'metrics_b': 4},
+        ]
+
+
[email protected]('mode', ['append', 'pk', 'evolution'])
[email protected]('value_type,value', [
+    (pa.list_(pa.int32()), [1, None, 3]),
+    (pa.struct([('number', pa.int32()), ('label', pa.string())]), {'number': 
4, 'label': '中文'}),
+    (pa.map_(pa.string(), pa.int64()), [('nested', 7)]),
+])
+def test_native_map_nested_values_and_sliced_input(tmp_path, mode, value_type, 
value):
+    table, schema = _table(tmp_path, mode, value_type=value_type)
+    rows = [
+        {'id': 0, 'pt': 'p', 'metrics': [('discard', value)]},
+        {'id': 1, 'pt': 'p', 'metrics': [('a', value)]},
+        {'id': 2, 'pt': 'p', 'metrics': None},
+        {'id': 3, 'pt': 'p', 'metrics': []},
+        {'id': 4, 'pt': 'p', 'metrics': [('b', value), ('a', None)]},
+    ]
+    builder = table.new_batch_write_builder()
+    writer, commit = builder.new_write(), builder.new_commit()
+    assert isinstance(writer, NativeTableWrite)
+    try:
+        writer.write_arrow(pa.Table.from_pylist(rows, schema=schema).slice(1, 
3))
+        writer.write_row(GenericRow([rows[-1][name] for name in schema.names], 
table.fields))
+        assert writer._python_writer is None
+        commit.commit(writer.prepare_commit())
+    finally:
+        writer.close()
+        commit.close()
+    for native in (False, True):
+        actual = _read(table, native)
+        for row in actual + rows:
+            if row['metrics'] is not None:
+                row['metrics'] = dict(row['metrics'])
+        assert actual == rows[1:]
+
+
[email protected]('engine', ['deduplicate', 'partial-update', 
'aggregation'])
+def test_native_pk_map_merge_and_input_changelog(tmp_path, engine):
+    extra = {'merge-engine': engine, 'changelog-producer': 'input'}
+    if engine == 'aggregation':
+        extra['fields.metrics.aggregate-function'] = 'last_non_null_value'
+    table, schema = _table(tmp_path, 'pk', extra=extra)
+    _write(table, schema, [{'id': 1, 'pt': 'p', 'metrics': [('a', 1)]}])
+    messages = _write(table, schema, [
+        {'id': 1, 'pt': 'p', 'metrics': [('b', 2)]},
+        {'id': 1, 'pt': 'p', 'metrics': [('a', 3), ('c', None)]},
+    ])
+    for message in messages:
+        assert message.changelog_files
+        for file in message.new_files + message.changelog_files:
+            assert 
is_shared_shredding(pq.read_schema(file.file_path).field('metrics'))
+            # The two physical KV system columns must not shift value 
statistics.
+            assert len(file.value_stats.null_counts) == 
len(file.value_stats_cols or schema.names)
+            assert not any(name.startswith('_') for name in 
(file.value_stats_cols or []))
+    for native in (False, True):
+        assert _read(table, native) == [{'id': 1, 'pt': 'p', 'metrics': [('a', 
3), ('c', None)]}]
+
+
+def test_native_map_abort_removes_rolled_files(tmp_path):
+    table, schema = _table(tmp_path, extra={'target-file-row-num': '1'})
+    writer = table.new_batch_write_builder().new_write()
+    assert isinstance(writer, NativeTableWrite)
+    writer.write_arrow(pa.Table.from_pylist([{'id': 1, 'pt': 'p', 'metrics': 
[('a', 1)]}], schema=schema))
+    writer.abort()
+    assert not list(tmp_path.rglob('*.parquet'))
+
+
[email protected]('native_update', [False, True])
+def test_map_row_id_update_keeps_physical_layout(tmp_path, native_update):
+    from pypaimon.write.table_update_by_row_id import TableUpdateByRowId
+
+    table, schema = _table(tmp_path, 'evolution')
+    _write(table, schema, [
+        {'id': 1, 'pt': 'p', 'metrics': [('a', 1)]},
+        {'id': 2, 'pt': 'p', 'metrics': [('b', 2)]},
+    ])
+    row_ids = {row['id']: row['_ROW_ID'] for row in _read(table, True, ['id', 
'_ROW_ID'])}
+    update_schema = pa.schema([('_ROW_ID', pa.int64()), 
schema.field('metrics')])
+    update = pa.Table.from_pylist([
+        {'_ROW_ID': row_ids[2], 'metrics': [('new', 7), ('a', None)]},
+    ], schema=update_schema)
+    table = table.copy({'write.native.enabled': str(native_update).lower()})
+    builder = table.new_batch_write_builder()
+    guard = patch.object(TableUpdateByRowId, '_load_existing_files_info',
+                         side_effect=AssertionError('Python update fallback'))
+    with guard if native_update else nullcontext():
+        messages = 
builder.new_update().with_update_type(['metrics']).update_by_arrow_with_row_id(update)
+    for message in messages:
+        for file in message.new_files:
+            assert 
is_shared_shredding(pq.read_schema(file.file_path).field('metrics'))
+    commit = builder.new_commit()
+    try:
+        commit.commit(messages)
+    finally:
+        commit.close()
+    expected = [
+        {'id': 1, 'pt': 'p', 'metrics': [('a', 1)]},
+        {'id': 2, 'pt': 'p', 'metrics': [('a', None), ('new', 7)]},
+    ]
+    for native_read in (False, True):
+        assert _read(table, native_read) == expected
+
+
[email protected]('stream', [False, True])
[email protected]('operation', ['predicate_scalar', 
'predicate_callable', 'upsert'])
+def test_native_map_predicate_update_and_upsert(tmp_path, stream, operation):
+    from contextlib import ExitStack
+    from pypaimon.common.predicate_builder import PredicateBuilder
+    from pypaimon.write.table_update import TableUpdate
+    from pypaimon.write.table_update_by_row_id import TableUpdateByRowId
+    from pypaimon.write.table_upsert_by_key import TableUpsertByKey
+
+    table, schema = _table(tmp_path, 'evolution')
+    _write(table, schema, [
+        {'id': 1, 'pt': 'p', 'metrics': [('a', 1)]},
+        {'id': 2, 'pt': 'p', 'metrics': [('b', 2)]},
+    ])
+    builder = table.new_stream_write_builder() if stream else 
table.new_batch_write_builder()
+    update = builder.new_update().with_update_type(['metrics'])
+    replacement = [('new', 7), ('a', None)]
+    with ExitStack() as stack:
+        for cls, method in [(TableUpdateByRowId, '_load_existing_files_info'),
+                            (TableUpdate, '_build_predicate_update_table'),
+                            (TableUpsertByKey, '_upsert_partition')]:
+            stack.enter_context(patch.object(cls, method, 
side_effect=AssertionError('Python update fallback')))
+        if operation == 'upsert':
+            data = pa.Table.from_pylist([
+                {'id': 2, 'pt': 'p', 'metrics': replacement},
+                {'id': 3, 'pt': 'p', 'metrics': []},
+            ], schema=schema)
+            messages = (update.upsert_by_arrow_with_key(data, ['id'], 2) if 
stream
+                        else update.upsert_by_arrow_with_key(data, ['id']))
+        else:
+            predicate = PredicateBuilder(table.fields).equal('id', 2)
+            assignment = replacement
+            kwargs = {}
+            if operation == 'predicate_callable':
+                def assign_map(matched):
+                    assert matched.column_names == ['id', '_ROW_ID']
+                    assert matched['id'].to_pylist() == [2]
+                    return pa.array([replacement], 
type=schema.field('metrics').type)
+                assignment = assign_map
+                kwargs['read_columns'] = ['id']
+            messages = (update.update_by_predicate(predicate, {'metrics': 
assignment}, 2, **kwargs) if stream
+                        else update.update_by_predicate(predicate, {'metrics': 
assignment}, **kwargs))
+    for message in messages:
+        for file in message.new_files:
+            assert 
is_shared_shredding(pq.read_schema(file.file_path).field('metrics'))
+    commit = builder.new_commit()
+    try:
+        commit.commit(messages, 2) if stream else commit.commit(messages)
+    finally:
+        commit.close()
+    for native in (False, True):
+        actual = _read(table, native)
+        assert [(row['id'], dict(row['metrics'])) for row in actual] == (
+            [(1, {'a': 1}), (2, {'new': 7, 'a': None})]
+            + ([(3, {})] if operation == 'upsert' else []))
diff --git a/paimon-python/pypaimon/write/native_update.py 
b/paimon-python/pypaimon/write/native_update.py
index 3bd97ea32e..6c44aa00a6 100644
--- a/paimon-python/pypaimon/write/native_update.py
+++ b/paimon-python/pypaimon/write/native_update.py
@@ -27,7 +27,7 @@ from pypaimon.write.native_commit import (
     create_native_write_table, from_native_commit_messages,
 )
 from pypaimon.write.native_write import native_write_available, 
_native_partition_types_supported
-from pypaimon.write.table_update_by_row_id import TableUpdateByRowId, 
_RowIdUpdateFileWriter
+from pypaimon.write.table_update_by_row_id import TableUpdateByRowId, 
_supports_parquet_row_id_update
 from pypaimon.write.row_utils import value_for_arrow
 
 
@@ -37,7 +37,7 @@ def _native_row_id_table(table):
             or not table.options.native_write_enabled()
             or not table.options.data_evolution_enabled()
             or not table.options.row_tracking_enabled()
-            or not _RowIdUpdateFileWriter.supports_table(table)
+            or not _supports_parquet_row_id_update(table)
             or any(table.options.options.contains_key(key) for key in 
SCAN_KEYS)
             or not native_write_available()):
         return None
diff --git a/paimon-python/pypaimon/write/native_write.py 
b/paimon-python/pypaimon/write/native_write.py
index 64d0fd774f..a69cd3e1ed 100644
--- a/paimon-python/pypaimon/write/native_write.py
+++ b/paimon-python/pypaimon/write/native_write.py
@@ -48,18 +48,6 @@ def _native_partition_types_supported(schema, 
partition_keys):
         for data_type in (schema.field(name).type for name in partition_keys))
 
 
-def _native_map_layouts_supported(table, schema):
-    options = table.options.options.to_map()
-    for field in schema:
-        if table.options.map_storage_layout(field.name) != 'default':
-            return False
-        # Python rejects even an explicit default layout on a non-MAP field.
-        if (not pa.types.is_map(field.type)
-                and 'fields.{}.map.storage-layout'.format(field.name) in 
options):
-            return False
-    return True
-
-
 def create_native_write(table, commit_user, static_partition=None, 
stream=False,
                         *, fixed_bucket=False, bucket_plan=None):
     """Return a native writer if the table can use the filesystem write 
path."""
@@ -82,8 +70,6 @@ def create_native_write(table, commit_user, 
static_partition=None, stream=False,
                                                      MergeEngine.AGGREGATE))
             or table.options.changelog_file_format() not in (None, 'parquet')
             or table.options.file_format() != 'parquet'
-            # The native writer does not implement MAP shared-shredding 
layouts.
-            or not _native_map_layouts_supported(table, schema)
             # Rust cannot encode these partition keys yet.
             or not _native_partition_types_supported(schema, 
table.partition_keys)
             # Append dedicated files currently support top-level scalar Blob 
fields.
diff --git a/paimon-python/pypaimon/write/table_update_by_row_id.py 
b/paimon-python/pypaimon/write/table_update_by_row_id.py
index b1c65a1ed7..ad6b47334b 100644
--- a/paimon-python/pypaimon/write/table_update_by_row_id.py
+++ b/paimon-python/pypaimon/write/table_update_by_row_id.py
@@ -56,6 +56,20 @@ _ARROW_MAJOR = int(pa.__version__.split('.')[0])
 _DEFAULT_PARQUET_BLOCK_SIZE = 128 * 1024 * 1024
 
 
+def _supports_parquet_row_id_update(table):
+    """Shared format constraints for ordinary row-ID updates."""
+    options = table.options
+    return (not table.is_primary_key_table
+            and options.file_format(CoreOptions.FILE_FORMAT_PARQUET)
+            == CoreOptions.FILE_FORMAT_PARQUET
+            and not (options.variant_shredding_enabled()
+                     and options.variant_shredding_schema())
+            and not options.data_evolution_row_sidecar_enabled(False)
+            and not options.with_vector_format()
+            and options.changelog_producer() == ChangelogProducer.NONE
+            and not any(is_blob_file_field(f) for f in table.fields))
+
+
 class _RowIdUpdateFileWriter:
     """Write one plain-Parquet update file for a row-id file group."""
 
@@ -63,18 +77,9 @@ class _RowIdUpdateFileWriter:
 
     @staticmethod
     def supports_table(table):
-        options = table.options
-        return (not table.is_primary_key_table
-                and options.file_format(CoreOptions.FILE_FORMAT_PARQUET)
-                == CoreOptions.FILE_FORMAT_PARQUET
-                and not (options.variant_shredding_enabled()
-                         and options.variant_shredding_schema())
-                and not options.data_evolution_row_sidecar_enabled(False)
-                and not options.with_vector_format()
-                and options.changelog_producer() == ChangelogProducer.NONE
-                and not any(options.map_storage_layout(f.name) == 
'shared-shredding'
-                            for f in table.fields)
-                and not any(is_blob_file_field(f) for f in table.fields))
+        return (_supports_parquet_row_id_update(table)
+                and not any(table.options.map_storage_layout(f.name) == 
'shared-shredding'
+                            for f in table.fields))
 
     def __init__(self, table, partition, column_names):
         if not self.supports_table(table):

Reply via email to