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

dianfu pushed a commit to branch release-2.4
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/release-2.4 by this push:
     new 1e66e575e90 [FLINK-41104][python] Return true from MapState.contains 
for keys mapped to None
1e66e575e90 is described below

commit 1e66e575e90227fe16f5f704c4e1a7e189d91278
Author: Sangkyoon Nam <[email protected]>
AuthorDate: Sat Oct 24 02:58:18 2026 +0900

    [FLINK-41104][python] Return true from MapState.contains for keys mapped to 
None
    
    This closes #29599.
---
 flink-python/pyflink/fn_execution/state_impl.py    | 10 ++-
 .../pyflink/fn_execution/tests/test_state_impl.py  | 72 +++++++++++++++++++++-
 2 files changed, 78 insertions(+), 4 deletions(-)

diff --git a/flink-python/pyflink/fn_execution/state_impl.py 
b/flink-python/pyflink/fn_execution/state_impl.py
index 5c7ee243197..42cd672cc7f 100644
--- a/flink-python/pyflink/fn_execution/state_impl.py
+++ b/flink-python/pyflink/fn_execution/state_impl.py
@@ -823,10 +823,14 @@ class InternalSynchronousMapRuntimeState(object):
     def contains(self, map_key):
         if self._is_empty:
             return False
-        if self.get(map_key) is None:
+        if map_key in self._write_cache:
+            exists, _ = self._write_cache[map_key]
+            return exists
+        if self._cleared:
             return False
-        else:
-            return True
+        exists, _ = self._map_state_handler.blocking_get(
+            self._state_key, map_key, self._map_key_encoder, 
self._map_value_decoder)
+        return exists
 
     def is_empty(self):
         if self._is_empty:
diff --git a/flink-python/pyflink/fn_execution/tests/test_state_impl.py 
b/flink-python/pyflink/fn_execution/tests/test_state_impl.py
index 73a3f2ada5b..8522b9dde05 100644
--- a/flink-python/pyflink/fn_execution/tests/test_state_impl.py
+++ b/flink-python/pyflink/fn_execution/tests/test_state_impl.py
@@ -21,13 +21,14 @@ import unittest
 from unittest import mock
 
 from apache_beam.coders import VarIntCoder, StrUtf8Coder
+from apache_beam.portability.api import beam_fn_api_pb2
 from apache_beam.runners.worker.sdk_worker import GlobalCachingStateHandler, 
StateHandler
 from apache_beam.runners.worker.statecache import StateCache
 
 from pyflink.common import Time, Types
 from pyflink.datastream.state import StateTtlConfig, MapStateDescriptor
 from pyflink.fn_execution.state_impl import RemoteKeyedStateBackend, 
RemoteOperatorStateBackend, \
-    CachingMapStateHandler
+    CachingMapStateHandler, InternalSynchronousMapRuntimeState
 
 
 class StateTtlReadCacheTests(unittest.TestCase):
@@ -432,5 +433,74 @@ class StateTtlReadCacheTests(unittest.TestCase):
         self.assertFalse(handler._state_cache.is_cache_enabled())
 
 
+class InternalSynchronousMapRuntimeStateTests(unittest.TestCase):
+
+    def setUp(self):
+        self.state = InternalSynchronousMapRuntimeState(
+            InMemoryMapStateHandler(), beam_fn_api_pb2.StateKey(),
+            StrUtf8Coder(), StrUtf8Coder(), 16)
+
+    def test_contains_distinguishes_missing_keys_from_none_values(self):
+        values = {'none': None, 'empty': '', 'value': 'value'}
+        for key, value in values.items():
+            self.state.put(key, value)
+
+        for committed in (False, True):
+            if committed:
+                self.state.commit()
+            with self.subTest(committed=committed):
+                for key, value in values.items():
+                    self.assertEqual(self.state.get(key), value)
+                    self.assertTrue(self.state.contains(key))
+                self.assertIsNone(self.state.get('missing'))
+                self.assertFalse(self.state.contains('missing'))
+
+    def test_contains_after_remove(self):
+        self.state.put('x', None)
+        self.state.commit()
+        self.state.remove('x')
+
+        self.assertFalse(self.state.contains('x'))
+        self.state.commit()
+        self.assertFalse(self.state.contains('x'))
+
+    def test_contains_after_clear_and_put(self):
+        self.state.put('x', 'old')
+        self.state.commit()
+        self.state.clear()
+        self.assertFalse(self.state.contains('x'))
+
+        self.state.put('y', None)
+
+        self.assertFalse(self.state.contains('x'))
+        self.assertTrue(self.state.contains('y'))
+        self.state.commit()
+        self.assertFalse(self.state.contains('x'))
+        self.assertTrue(self.state.contains('y'))
+
+
+class InMemoryMapStateHandler:
+
+    def __init__(self):
+        self.values = {}
+
+    def blocking_get(self, state_key, map_key, map_key_encoder, 
map_value_decoder):
+        return map_key in self.values, self.values.get(map_key)
+
+    def extend(self, state_key, items, map_key_encoder, map_value_encoder):
+        for flag, key, value in items:
+            if flag == CachingMapStateHandler.DELETE:
+                self.values.pop(key, None)
+            elif flag == CachingMapStateHandler.SET_NONE:
+                self.values[key] = None
+            elif flag == CachingMapStateHandler.SET_VALUE:
+                self.values[key] = value
+            else:
+                raise ValueError('Unknown map state update flag: %s' % flag)
+
+    def clear(self, state_key):
+        self.values.clear()
+
+
 if __name__ == '__main__':
     unittest.main()

Reply via email to