This is an automated email from the ASF dual-hosted git repository. dianfu pushed a commit to branch release-2.2 in repository https://gitbox.apache.org/repos/asf/flink.git
commit e93762d4eec052c210aa8a7753a4e04bf6202093 Author: Biao Geng <[email protected]> AuthorDate: Wed Oct 14 13:20:38 2026 +0800 [FLINK-40986][python] Make named Row hashing consistent with equality (#29472) --- flink-python/pyflink/common/tests/test_types.py | 71 +++++++++++++++++++++++++ flink-python/pyflink/common/types.py | 2 + 2 files changed, 73 insertions(+) diff --git a/flink-python/pyflink/common/tests/test_types.py b/flink-python/pyflink/common/tests/test_types.py new file mode 100644 index 00000000000..50d462d8986 --- /dev/null +++ b/flink-python/pyflink/common/tests/test_types.py @@ -0,0 +1,71 @@ +################################################################################ +# 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. +################################################################################ + +import unittest + +from pyflink.common import Row, RowKind + + +class RowTests(unittest.TestCase): + + def test_named_row_hash(self): + for row_kind in RowKind: + for other in [Row(b=2, a=1), Row('b', 'a')(2, 1)]: + with self.subTest(row_kind=row_kind, other=other): + row = Row(a=1, b=2) + row.set_row_kind(row_kind) + other.set_row_kind(row_kind) + self.assertEqual(row, other) + self.assertEqual(hash(row), hash(other)) + self.assertEqual(tuple(other), (2, 1)) + self.assertEqual(list(other.as_dict()), ['b', 'a']) + + def test_named_row_set(self): + self.assertEqual(len({Row(a=1, b=2), Row(b=2, a=1)}), 1) + + def test_named_row_dict(self): + values = {Row(a=1, b=2): 'value'} + self.assertEqual(values[Row(b=2, a=1)], 'value') + values[Row(b=2, a=1)] = 'updated' + self.assertEqual(len(values), 1) + self.assertEqual(values[Row(a=1, b=2)], 'updated') + + def test_positional_row_hash(self): + for values in [(), (1, 2), (2, 1)]: + with self.subTest(values=values): + self.assertEqual(hash(Row(*values)), hash(values)) + self.assertEqual(len({Row(*values), Row(*values)}), 1) + self.assertEqual(len({Row(1, 2), Row(2, 1)}), 2) + + def test_nested_row_hash(self): + row = Row(a=Row(x=1, y=2), b=3) + other = Row(b=3, a=Row(y=2, x=1)) + self.assertEqual(row, other) + self.assertEqual(hash(row), hash(other)) + self.assertEqual(len({row, other}), 1) + + def test_unhashable_row_fields(self): + for value in [[1, 2], {'a': 1}]: + for row in [Row(value), Row(a=value)]: + with self.subTest(row=row): + with self.assertRaises(TypeError): + hash(row) + + +if __name__ == '__main__': + unittest.main() diff --git a/flink-python/pyflink/common/types.py b/flink-python/pyflink/common/types.py index 894a4e89cd2..2547266581a 100644 --- a/flink-python/pyflink/common/types.py +++ b/flink-python/pyflink/common/types.py @@ -278,6 +278,8 @@ class Row(object): self._values == other._values) def __hash__(self): + if hasattr(self, "_fields"): + return hash(frozenset(zip(self._fields, self._values))) return tuple(self).__hash__() def __iter__(self):
