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 676b40c91f [python] Match newlines with the LIKE single-character 
wildcard (#10847)
676b40c91f is described below

commit 676b40c91f17b621f7bb08e4c70c779e765749cd
Author: chenjian2664 <[email protected]>
AuthorDate: Wed Oct 28 20:00:35 2026 +0800

    [python] Match newlines with the LIKE single-character wildcard (#10847)
---
 paimon-python/pypaimon/common/predicate.py      |  2 +-
 paimon-python/pypaimon/tests/predicates_test.py | 40 +++++++++++++++++++++++++
 2 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/paimon-python/pypaimon/common/predicate.py 
b/paimon-python/pypaimon/common/predicate.py
index 6bd6801e6f..80f1d0b91e 100644
--- a/paimon-python/pypaimon/common/predicate.py
+++ b/paimon-python/pypaimon/common/predicate.py
@@ -601,7 +601,7 @@ class Like(Tester):
                     raise ValueError(
                         f"Invalid escape sequence '{pattern}' at position 
{index}")
             elif char == '_':
-                regex_parts.append('.')
+                regex_parts.append('(?s:.)')
             elif char == '%':
                 regex_parts.append('(?s:.*)')
             else:
diff --git a/paimon-python/pypaimon/tests/predicates_test.py 
b/paimon-python/pypaimon/tests/predicates_test.py
index 3e07631c76..684ea038d5 100644
--- a/paimon-python/pypaimon/tests/predicates_test.py
+++ b/paimon-python/pypaimon/tests/predicates_test.py
@@ -442,6 +442,33 @@ class PredicateTest(unittest.TestCase):
         predicate = predicate_builder.like('f1', '%bc')
         
_check_filtered_result(table.new_read_builder().with_filter(predicate), 
self.df.loc[0:2])
 
+    @pytest.mark.python_plan
+    def test_like_newline_pk_merge(self):
+        pa_schema = pa.schema([('f0', pa.int64()), ('f1', pa.string())])
+        self.catalog.create_table('default.test_like_newline', 
Schema.from_pyarrow_schema(
+            pa_schema, primary_keys=['f0'], options={
+                'bucket': '1', 'file.format': 'parquet',
+                'scan.native-plan.enabled': 'false', 'read.native.enabled': 
'false',
+            }), False)
+        table = self.catalog.get_table('default.test_like_newline')
+        df = pd.DataFrame({
+            'f0': [1, 2, 3, 4, 5],
+            'f1': ['a\nc', 'abc', 'a\r\nc', 'ac', None],
+        })
+        # A second commit overlaps the first file and requires row-by-row 
filtering.
+        for data in (df, df.loc[[0]]):
+            builder = table.new_batch_write_builder()
+            write, commit = builder.new_write(), builder.new_commit()
+            try:
+                write.write_pandas(data)
+                commit.commit(write.prepare_commit())
+            finally:
+                write.close()
+                commit.close()
+            read_builder = table.new_read_builder()
+            predicate = read_builder.new_predicate_builder().like('f1', 'a_c')
+            _check_filtered_result(read_builder.with_filter(predicate), 
df.loc[[0, 1]])
+
     def test_null_literal_matches_nothing(self):
         pa_schema = pa.schema([('f0', pa.int64()), ('f1', pa.string())])
         # without stats no file is pruned, so the Arrow filter decides
@@ -647,6 +674,19 @@ class PredicateTest(unittest.TestCase):
         self.assertTrue(underscore_pred.test(OffsetRow(['abc'], 0, 1)))
         self.assertFalse(underscore_pred.test(OffsetRow(['aXYc'], 0, 1)))
 
+    def test_like_single_character_wildcard_matches_line_terminators(self):
+        predicate = Predicate(method='like', index=0, field='name', 
literals=['a_c'])
+        for terminator in ('\n', '\r', '\u0085', '\u2028', '\u2029'):
+            with self.subTest(terminator=terminator):
+                self.assertTrue(predicate.test(OffsetRow(['a' + terminator + 
'c'], 0, 1)))
+
+        self.assertFalse(predicate.test(OffsetRow(['a\r\nc'], 0, 1)))
+        two_characters = predicate.new_literals(['a__c'])
+        self.assertTrue(two_characters.test(OffsetRow(['a\r\nc'], 0, 1)))
+        escaped = predicate.new_literals([r'a\_c'])
+        self.assertTrue(escaped.test(OffsetRow(['a_c'], 0, 1)))
+        self.assertFalse(escaped.test(OffsetRow(['a\nc'], 0, 1)))
+
     def test_not_between_value(self):
         predicate = Predicate(method='notBetween', index=0, field='val', 
literals=[3, 7])
         self.assertTrue(predicate.test(OffsetRow([1], 0, 1)))

Reply via email to