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 ad1e6c1878 [python] Remove legacy native read option forwarding 
(#10120)
ad1e6c1878 is described below

commit ad1e6c187817f23c2e20c15f6c59d5923fc5b6b0
Author: Jingsong Lee <[email protected]>
AuthorDate: Wed Sep 23 09:24:56 2026 +0800

    [python] Remove legacy native read option forwarding (#10120)
---
 paimon-python/README.md                            |   8 +-
 paimon-python/pypaimon/read/native_plan.py         |  64 ++------
 .../pypaimon/tests/native_plan_integration_test.py |   7 +-
 paimon-python/pypaimon/tests/native_plan_test.py   | 170 ++++++++++++---------
 4 files changed, 109 insertions(+), 140 deletions(-)

diff --git a/paimon-python/README.md b/paimon-python/README.md
index 7da6734823..3295d70df3 100644
--- a/paimon-python/README.md
+++ b/paimon-python/README.md
@@ -219,9 +219,11 @@ interrupts native reads that are still in flight. A 
missing reader capability,
 unsupported route, or native-reader construction failure falls back to Python;
 I/O and data errors raised after streaming starts surface to the caller.
 
-With Rust main's `Table.from_resolved_schema()` binding, filesystem and JDBC 
catalog
-tables preserve the Python table's resolved schema and complete effective
-options. Stale table objects, historical schemas, and `copy()` overrides or
+Native planning and reading require Rust's resolved-schema bindings:
+`Table.from_resolved_schema()` for filesystem, JDBC and path-based tables, and
+`Table.copy_with_resolved_schema()` for REST tables. The adapter passes the 
Python
+table's resolved schema and complete effective options without an option 
whitelist.
+Stale table objects, historical schemas, and `copy()` overrides or
 option removals no longer require catalog reloading or Python planning.
 Tables opened with `FileStoreTable.from_path(path, file_io_options=None)` use
 the same path with standard local, PyArrow or resolving FileIO. Storage options
diff --git a/paimon-python/pypaimon/read/native_plan.py 
b/paimon-python/pypaimon/read/native_plan.py
index 6eac710aaa..ca655dea07 100644
--- a/paimon-python/pypaimon/read/native_plan.py
+++ b/paimon-python/pypaimon/read/native_plan.py
@@ -27,7 +27,6 @@ from typing import List, Optional, Tuple
 from packaging.version import InvalidVersion, Version
 
 from pypaimon.common.options.config import CatalogOptions, OssOptions
-from pypaimon.common.options.core_options import CoreOptions
 from pypaimon.common.options.options_utils import OptionsUtils
 from pypaimon.common.predicate import Predicate
 from pypaimon.read.plan import Plan
@@ -167,45 +166,8 @@ def _catalog_context_options(table) -> dict:
     return normalized
 
 
-def _read_options(table) -> dict:
-    """Effective Rust read options, including FileStoreTable.copy overrides."""
-    options = {
-        CoreOptions.SOURCE_SPLIT_TARGET_SIZE.key(): str(
-            table.options.source_split_target_size()),
-        CoreOptions.SOURCE_SPLIT_OPEN_FILE_COST.key(): str(
-            table.options.source_split_open_file_cost()),
-        CoreOptions.DELETION_VECTORS_MERGE_ON_READ.key(): 
_option_value_to_string(
-            
table.options.options.get(CoreOptions.DELETION_VECTORS_MERGE_ON_READ)),
-    }
-    table_options = table.options.options
-    for option in (
-            CoreOptions.BLOB_AS_DESCRIPTOR,
-            CoreOptions.SCAN_VERSION,
-            CoreOptions.SCAN_SNAPSHOT_ID,
-            CoreOptions.SCAN_TAG_NAME,
-            CoreOptions.SCAN_TIMESTAMP_MILLIS,
-            CoreOptions.SCAN_WATERMARK,
-            CoreOptions.GLOBAL_INDEX_SEARCH_MODE,
-            CoreOptions.SCALAR_INDEX_SEARCH_MODE,
-            CoreOptions.VECTOR_INDEX_SEARCH_MODE,
-            CoreOptions.FULL_TEXT_INDEX_SEARCH_MODE):
-        if table_options.contains_key(option.key()):
-            options[option.key()] = _option_value_to_string(
-                table_options.to_map()[option.key()])
-
-    # Rust takes epoch millis but PyPaimon also accepts a timestamp string.
-    if table_options.contains_key(CoreOptions.SCAN_TIMESTAMP.key()):
-        from pypaimon.snapshot.time_travel_util import 
_parse_timestamp_to_millis
-        options[CoreOptions.SCAN_TIMESTAMP_MILLIS.key()] = str(
-            _parse_timestamp_to_millis(
-                table_options.get(CoreOptions.SCAN_TIMESTAMP)))
-    return options
-
-
 def _resolved_schema_file_io_options(table) -> Optional[dict]:
     """FileIO properties for tables whose metadata needs no catalog 
resolution."""
-    if not native_method_available('Table', 'from_resolved_schema'):
-        return None
     environment = table.catalog_environment
     loader = environment.catalog_loader
     if loader is None:
@@ -234,12 +196,10 @@ def _resolved_schema_file_io_options(table) -> 
Optional[dict]:
 
 
 def _resolved_schema_json(table) -> str:
+    """Preserve all effective table options as strings for Rust."""
     from pypaimon.common.json_util import JSON
     options = {str(key): _option_value_to_string(value)
                for key, value in table.table_schema.options.items() if value 
is not None}
-    options.update(_read_options(table))
-    # The timestamp string has already been converted to epoch millis.
-    options.pop(CoreOptions.SCAN_TIMESTAMP.key(), None)
     return JSON.to_json(table.table_schema.copy(new_options=options))
 
 
@@ -301,27 +261,21 @@ def _native_read_builder(table):
             database=table.identifier.get_database_name(),
             table=table.identifier.get_table_name(),
             branch=table.current_branch(), options=file_io_options)
-        builder = rt.new_read_builder()
     else:
         from pypaimon_rust.datafusion import PaimonCatalog
         catalog = PaimonCatalog(_catalog_options(table))
-        if native_method_available('Table', 'copy_with_resolved_schema'):
-            # REST may keep branch schemas in the catalog only. Load the base
-            # environment, then attach the schema/branch already resolved here.
-            rt = catalog.get_table((
-                table.identifier.get_database_name(), 
table.identifier.get_table_name()))
-            if rt.location() != table.table_path:
-                raise RuntimeError('Native catalog resolved a different table 
location')
-            rt = rt.copy_with_resolved_schema(_resolved_schema_json(table), 
branch=table.current_branch())
-            builder = rt.new_read_builder()
-        else:
-            rt = catalog.get_table(table.identifier.get_full_name())
-            builder = rt.new_read_builder(_read_options(table))
+        # REST may keep branch schemas in the catalog only. Load the base
+        # environment, then attach the schema/branch already resolved here.
+        rt = catalog.get_table((
+            table.identifier.get_database_name(), 
table.identifier.get_table_name()))
+        if rt.location() != table.table_path:
+            raise RuntimeError('Native catalog resolved a different table 
location')
+        rt = rt.copy_with_resolved_schema(_resolved_schema_json(table), 
branch=table.current_branch())
     if table.current_branch() != 'main':
         branch = getattr(rt, 'branch', None)
         if not callable(branch) or branch() != table.current_branch():
             raise RuntimeError("Native table did not resolve the requested 
branch")
-    return builder
+    return rt.new_read_builder()
 
 
 def _configure_native_read_builder(builder, predicate, limit, projection,
diff --git a/paimon-python/pypaimon/tests/native_plan_integration_test.py 
b/paimon-python/pypaimon/tests/native_plan_integration_test.py
index 646ea5eb68..aa22c9a7ba 100644
--- a/paimon-python/pypaimon/tests/native_plan_integration_test.py
+++ b/paimon-python/pypaimon/tests/native_plan_integration_test.py
@@ -1130,12 +1130,7 @@ class NativePlanIntegrationTest(unittest.TestCase):
 
     @unittest.skipUnless(native_reader_available(),
                          "pypaimon-rust native reader API not installed")
-    @patch('pypaimon.read.native_plan.native_method_available',
-           side_effect=lambda type_name, method: (
-               False if method in ('from_resolved_schema', 
'copy_with_resolved_schema')
-               else native_method_available(type_name, method)))
-    def test_native_read_dynamic_blob_as_descriptor(self, capabilities):
-        # Exercise the catalog path used by Rust versions without resolved 
schemas.
+    def test_native_read_dynamic_blob_as_descriptor(self):
         schema = pa.schema([('id', pa.int32()), ('payload', 
pa.large_binary())])
         self.cat.create_table(
             'default.native_dynamic_descriptor',
diff --git a/paimon-python/pypaimon/tests/native_plan_test.py 
b/paimon-python/pypaimon/tests/native_plan_test.py
index 124a64c4c2..285c0a40fa 100644
--- a/paimon-python/pypaimon/tests/native_plan_test.py
+++ b/paimon-python/pypaimon/tests/native_plan_test.py
@@ -33,8 +33,8 @@ from pypaimon.globalindex.global_index_result import 
GlobalIndexResult
 from pypaimon.globalindex.vector_search_result import ScoredGlobalIndexResult
 from pypaimon.read.native_plan import (
     _catalog_options,
+    _native_read_builder,
     _predicate_to_native,
-    _read_options,
     _resolved_schema_json,
     _restore_python_partition_paths,
     native_family_search_modes_available,
@@ -120,13 +120,11 @@ class NativePlanTest(unittest.TestCase):
                     False, AtomicType('STRING', False), AtomicType('INT'))))
             ])))
         ])
-        table = SimpleNamespace(table_schema=schema, 
options=CoreOptions(Options({})))
+        table = SimpleNamespace(table_schema=schema)
         self.assertEqual(json.loads(_resolved_schema_json(table)), {
             'version': 3, 'id': 7, 'highestFieldId': 3, 'timeMillis': 0,
             'partitionKeys': [], 'primaryKeys': [], 'comment': None,
-            'options': {'source.split.target-size': '134217728',
-                        'source.split.open-file-cost': '4194304',
-                        'deletion-vectors.merge-on-read': 'false'},
+            'options': {},
             'fields': [{'id': 0, 'name': 'attributes', 'type': {
                 'type': 'MAP NOT NULL', 'nullable': False, 'key': 'STRING NOT 
NULL',
                 'value': {'type': 'ROW', 'nullable': True, 'fields': [
@@ -160,24 +158,23 @@ class NativePlanTest(unittest.TestCase):
             pass
 
         table = Mock(file_io=LocalFileIO(), 
catalog_environment=CatalogEnvironment.empty())
-        with patch('pypaimon.read.native_plan.native_method_available', 
return_value=True):
-            self.assertEqual(_resolved_schema_file_io_options(table), {})
-            table.file_io = CustomIO()
+        self.assertEqual(_resolved_schema_file_io_options(table), {})
+        table.file_io = CustomIO()
+        self.assertIsNone(_resolved_schema_file_io_options(table))
+        table.file_io = LocalFileIO()
+        table.catalog_environment = CustomEnvironment()
+        self.assertIsNone(_resolved_schema_file_io_options(table))
+        table.catalog_environment = CatalogEnvironment.empty()
+        for loader_type in (RESTCatalogLoader, CustomLoader, CustomJdbcLoader):
+            table.catalog_environment.catalog_loader = loader_type(
+                CatalogContext.create_from_options(Options({})))
             self.assertIsNone(_resolved_schema_file_io_options(table))
-            table.file_io = LocalFileIO()
-            table.catalog_environment = CustomEnvironment()
-            self.assertIsNone(_resolved_schema_file_io_options(table))
-            table.catalog_environment = CatalogEnvironment.empty()
-            for loader_type in (RESTCatalogLoader, CustomLoader, 
CustomJdbcLoader):
-                table.catalog_environment.catalog_loader = loader_type(
-                    CatalogContext.create_from_options(Options({})))
+        for loader_type in (FileSystemCatalogLoader, JdbcCatalogLoader):
+            for attr in ('hadoop_conf', 'prefer_io_loader', 
'fallback_io_loader'):
+                context = CatalogContext.create_from_options(Options({}))
+                setattr(context, attr, object())
+                table.catalog_environment.catalog_loader = loader_type(context)
                 self.assertIsNone(_resolved_schema_file_io_options(table))
-            for loader_type in (FileSystemCatalogLoader, JdbcCatalogLoader):
-                for attr in ('hadoop_conf', 'prefer_io_loader', 
'fallback_io_loader'):
-                    context = CatalogContext.create_from_options(Options({}))
-                    setattr(context, attr, object())
-                    table.catalog_environment.catalog_loader = 
loader_type(context)
-                    self.assertIsNone(_resolved_schema_file_io_options(table))
 
     def test_switch_defaults_off(self):
         defaults = CoreOptions(Options({}))
@@ -200,9 +197,8 @@ class NativePlanTest(unittest.TestCase):
         arrow.properties = properties
         for file_io in (arrow, ResolvingFileIO(properties)):
             table = Mock(file_io=file_io, 
catalog_environment=CatalogEnvironment.empty())
-            with patch('pypaimon.read.native_plan.native_method_available', 
return_value=True):
-                self.assertEqual(_resolved_schema_file_io_options(table), {
-                    's3.path-style-access': 'true', 's3.endpoint': 
'http://localhost:9000'})
+            self.assertEqual(_resolved_schema_file_io_options(table), {
+                's3.path-style-access': 'true', 's3.endpoint': 
'http://localhost:9000'})
 
     def test_plan_uses_file_scanner_when_switch_off(self):
         fs = Mock()
@@ -387,8 +383,13 @@ class NativePlanTest(unittest.TestCase):
         check(lambda s, fs: (setattr(s.table, 'is_primary_key_table', True),
                              setattr(s.table, 'trimmed_primary_keys', [])))
         check(lambda s, fs: setattr(s.table.options, 'query_auth_enabled', 
True))
-        check(lambda s, fs: s.table.identifier.get_database_name.__setattr__(
-            'return_value', 'unknown'))
+
+        def unknown_rest_database(scan, fs):
+            scan.table.catalog_environment.catalog_loader = RESTCatalogLoader(
+                CatalogContext.create_from_options(Options({})))
+            scan.table.identifier.get_database_name.return_value = 'unknown'
+
+        check(unknown_rest_database)
         check(lambda s, fs: setattr(
             s.table.catalog_environment, 'catalog_loader', object()))   # no 
context()
         for attr in ('hadoop_conf', 'prefer_io_loader', 'fallback_io_loader'):
@@ -494,19 +495,17 @@ class NativePlanTest(unittest.TestCase):
             self.assertIs(scan.plan(), sentinel)
         fs.scan.assert_called_once_with()
 
-    def test_plan_falls_back_for_jdbc_catalog_loader(self):
+    def test_plan_uses_resolved_schema_for_jdbc_catalog_loader(self):
         fs = Mock(partition_key_predicate=None)
-        sentinel = object()
-        fs.scan.return_value = sentinel
         scan = _scan(native_enabled=True, file_scanner=fs)
         scan.table.catalog_environment.catalog_loader = JdbcCatalogLoader(
             CatalogContext.create_from_options(Options({})))
 
-        with patch('pypaimon.read.native_plan.native_plan') as np:
-            self.assertIs(scan.plan(), sentinel)
+        with patch('pypaimon.read.native_plan.native_plan', 
return_value=Plan([], 1)) as np:
+            self.assertEqual(scan.plan().snapshot_id, 1)
 
-        np.assert_not_called()
-        fs.scan.assert_called_once_with()
+        np.assert_called_once()
+        fs.scan.assert_not_called()
 
     def test_plan_falls_back_for_builtin_catalog_loader_subclasses(self):
         class RoutedFileSystemLoader(FileSystemCatalogLoader):
@@ -627,13 +626,14 @@ class NativePlanTest(unittest.TestCase):
     def test_blob_as_descriptor_is_forwarded_to_rust(self):
         for value in ('true', 'false', True, False):
             with self.subTest(value=value):
-                table = Mock()
-                table.options = CoreOptions(Options({'blob-as-descriptor': 
value}))
+                options = {'blob-as-descriptor': value}
+                table = SimpleNamespace(
+                    table_schema=TableSchema(0, [], options=options))
                 self.assertEqual(
-                    _read_options(table)['blob-as-descriptor'],
+                    
json.loads(_resolved_schema_json(table))['options']['blob-as-descriptor'],
                     str(value).lower())
 
-    def test_predicate_and_time_travel_are_converted_for_rust(self):
+    def test_predicate_is_converted_for_rust(self):
         predicate = PredicateBuilder.and_predicates([
             Predicate('greaterOrEqual', 0, 'k', [10]),
             Predicate('in', 1, 'v', ['a', 'b']),
@@ -646,26 +646,48 @@ class NativePlanTest(unittest.TestCase):
             ],
         })
 
-        table = Mock()
-        table.options.source_split_target_size.return_value = 1024
-        table.options.source_split_open_file_cost.return_value = 128
-        table.options.options = Options({
+    def test_resolved_schema_preserves_options_and_stringifies_values(self):
+        options = {
+            'source.split.target-size': '1 kb',
+            'source.split.open-file-cost': '128 b',
+            'deletion-vectors.merge-on-read': 'true',
             'scan.snapshot-id': '9',
+            'scan.watermark': 200,
             'global-index.search-mode': 'detail',
             'scalar-index.search-mode': 'full',
             'vector-index.search-mode': 'fast',
             'full-text-index.search-mode': 'fast',
-        })
-        self.assertEqual(_read_options(table), {
-            'source.split.target-size': '1024',
-            'source.split.open-file-cost': '128',
-            'deletion-vectors.merge-on-read': 'false',
+            'read.batch-size': 32,
+            'custom.read-option': True,
+            'removed.option': None,
+        }
+        table = SimpleNamespace(
+            table_schema=TableSchema(0, [], options=options))
+        self.assertEqual(json.loads(_resolved_schema_json(table))['options'], {
+            'source.split.target-size': '1 kb',
+            'source.split.open-file-cost': '128 b',
+            'deletion-vectors.merge-on-read': 'true',
             'scan.snapshot-id': '9',
+            'scan.watermark': '200',
             'global-index.search-mode': 'detail',
             'scalar-index.search-mode': 'full',
             'vector-index.search-mode': 'fast',
             'full-text-index.search-mode': 'fast',
+            'read.batch-size': '32',
+            'custom.read-option': 'true',
         })
+        self.assertEqual(table.table_schema.options, options)
+
+    def test_resolved_schema_preserves_timestamp_selectors(self):
+        for key, value in (('scan.timestamp', '2026-09-22T00:00:00'),
+                           ('scan.timestamp-millis', 1790035200000)):
+            with self.subTest(key=key):
+                options = {key: value}
+                table = SimpleNamespace(
+                    table_schema=TableSchema(0, [], options=options))
+                resolved = json.loads(_resolved_schema_json(table))['options']
+                self.assertEqual(resolved, {key: str(value)})
+                self.assertEqual(table.table_schema.options, options)
 
     @unittest.skipIf(sys.version_info < (3, 8),
                      "importlib.metadata requires Python 3.8")
@@ -771,29 +793,14 @@ class NativePlanTest(unittest.TestCase):
         table.current_branch.return_value = 'main'
         table.table_schema = Mock(fields=[], partition_keys=[])
         table.partition_keys = []
-        table.options.source_split_target_size.return_value = 1024
-        table.options.source_split_open_file_cost.return_value = 128
-        table.options.options = Options({})
-        table._applied_dynamic_options = {}
         split = Mock()
         split.serialize.return_value = b'bytes'
-        rt = Mock()
-        builder = rt.new_read_builder.return_value
+        builder = Mock()
         builder.with_row_ranges.return_value = builder
         builder.new_scan.return_value.plan.return_value.splits.return_value = 
[split]
         
builder.new_scan.return_value.plan.return_value.snapshot_id.return_value = 3
-        catalog = Mock()
-        catalog.get_table.return_value = rt
 
-        fake_df = ModuleType('pypaimon_rust.datafusion')
-        fake_df.PaimonCatalog = Mock(return_value=catalog)
-        fake_df.Split = type('Split', (), {'serialize': lambda self: b''})
-        fake_mod = ModuleType('pypaimon_rust')
-        fake_mod.datafusion = fake_df
-
-        with patch.dict(sys.modules,
-                        {'pypaimon_rust': fake_mod, 
'pypaimon_rust.datafusion': fake_df}), \
-                patch('pypaimon.read.native_plan._catalog_options', 
return_value={}), \
+        with patch('pypaimon.read.native_plan._native_read_builder', 
return_value=builder), \
                 patch('pypaimon.read.native_plan.deserialize_split_v1') as des:
             decoded = Mock()
             des.return_value = decoded
@@ -802,11 +809,6 @@ class NativePlanTest(unittest.TestCase):
         self.assertEqual(result.splits(), [decoded])
         self.assertIs(decoded._native_split, split)
         self.assertEqual(result.snapshot_id, 3)
-        rt.new_read_builder.assert_called_once_with({
-            CoreOptions.SOURCE_SPLIT_TARGET_SIZE.key(): '1024',
-            CoreOptions.SOURCE_SPLIT_OPEN_FILE_COST.key(): '128',
-            CoreOptions.DELETION_VECTORS_MERGE_ON_READ.key(): 'false',
-        })
         builder.with_row_ranges.assert_called_once_with([(1, 2)])
         des.assert_called_once_with(b'bytes', [], kfields)
 
@@ -819,10 +821,9 @@ class NativePlanTest(unittest.TestCase):
                 if not legacy:
                     rust_plan.snapshot_id = lambda: snapshot_id
                 scan = SimpleNamespace(plan=lambda: rust_plan)
-                rt = Mock()
-                rt.new_read_builder.return_value.new_scan.return_value = scan
-                with patch('pypaimon_rust.datafusion.PaimonCatalog') as 
catalog:
-                    catalog.return_value.get_table.return_value = rt
+                builder = Mock()
+                builder.new_scan.return_value = scan
+                with patch('pypaimon.read.native_plan._native_read_builder', 
return_value=builder):
                     if legacy:
                         with self.assertRaisesRegex(RuntimeError, "empty 
plan's snapshot"):
                             native_plan(table)
@@ -833,7 +834,8 @@ class NativePlanTest(unittest.TestCase):
 
     def test_native_plan_branch_resolution(self):
         table = _scan(True, Mock()).table
-        table.table_schema = Mock(fields=[], partition_keys=[])
+        table.table_schema = TableSchema(0, [])
+        table.options = CoreOptions(Options({}))
         table.current_branch.return_value = 'b1'
         rust_plan = SimpleNamespace(splits=lambda: [], snapshot_id=lambda: 7)
         scan = Mock()
@@ -841,8 +843,8 @@ class NativePlanTest(unittest.TestCase):
         rt = Mock()
         rt.branch.return_value = 'b1'
         rt.new_read_builder.return_value.new_scan.return_value = scan
-        with patch('pypaimon_rust.datafusion.PaimonCatalog') as catalog:
-            catalog.return_value.get_table.return_value = rt
+        with patch('pypaimon_rust.datafusion.Table', create=True) as 
native_table:
+            native_table.from_resolved_schema.return_value = rt
             plan = native_plan(table)
             self.assertEqual(plan.snapshot_id, 7)
             scan.plan.assert_called_once_with()
@@ -850,6 +852,23 @@ class NativePlanTest(unittest.TestCase):
             with self.assertRaisesRegex(RuntimeError, 'requested branch'):
                 native_plan(table)
 
+    def test_native_read_builder_requires_resolved_schema(self):
+        for loader_type in (FileSystemCatalogLoader, RESTCatalogLoader):
+            with self.subTest(loader=loader_type):
+                table = _scan(True, Mock()).table
+                table.table_schema = TableSchema(0, [], 
options={'read.batch-size': 32})
+                table.options = 
CoreOptions(Options(table.table_schema.options))
+                table.catalog_environment.catalog_loader = loader_type(
+                    CatalogContext.create_from_options(Options({})))
+                legacy_table = SimpleNamespace(
+                    location=lambda: table.table_path, new_read_builder=Mock())
+                with patch('pypaimon_rust.datafusion.Table', type('Table', (), 
{}), create=True), \
+                        patch('pypaimon_rust.datafusion.PaimonCatalog') as 
catalog:
+                    catalog.return_value.get_table.return_value = legacy_table
+                    with self.assertRaises(AttributeError):
+                        _native_read_builder(table)
+                legacy_table.new_read_builder.assert_not_called()
+
     def test_explicit_row_ranges_are_forwarded(self):
         for ranges in ([], [Range(1, 2), Range(5, 8)]):
             with self.subTest(ranges=ranges):
@@ -882,7 +901,6 @@ class NativePlanTest(unittest.TestCase):
         scan.table.options.options = Options({'scan.watermark': '200'})
         scan.table._applied_dynamic_options = {'scan.watermark': '200'}
         scan.table.schema_manager.latest.return_value.id = 2
-        self.assertEqual(_read_options(scan.table)['scan.watermark'], '200')
         with patch('pypaimon.read.native_plan.native_plan',
                    return_value=Plan([], 1)) as native:
             self.assertEqual(scan.plan().snapshot_id, 1)

Reply via email to