Skip to content

Commit f95a0ab

Browse files
committed
Transpile and rewrite inline and scoped flags
Case-insensitive runs are folded to match either case; unsafe folds and any other positive flags fall back to the CPU; negated non-i flags are no-ops, since they're off by default. Inline flags are zero-width, so they are skipped by the anchor and empty-repetition guards. A scoped-flags group is treated like a non-capturing one so it is not needlessly rejected after $ or when repeated. The rlike fast path is taught to see through case-sensitive scoped-flag groups so they keep their contains/prefix optimization. Signed-off-by: Igor Peshansky <ipeshansky@nvidia.com>
1 parent d20967f commit f95a0ab

4 files changed

Lines changed: 498 additions & 75 deletions

File tree

integration_tests/src/main/python/regexp_test.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,18 @@ def test_regexp_extract():
669669
'regexp_extract(a, "(a)(b)|(c)(d)", 4)'),
670670
conf=_regexp_conf)
671671

672+
def test_regexp_extract_case_insensitive_inline_flag():
673+
gen = mk_str_gen('[abcABC]{1,3}[0-9]{1,2}[abcABC]{1,3}')
674+
assert_gpu_and_cpu_are_equal_collect(
675+
lambda spark: unary_op_df(spark, gen).selectExpr(
676+
# (?i) folds the capture-group contents but does not change group numbering
677+
'regexp_extract(a, "(?i)([a-c]+)([0-9]+)", 1)',
678+
'regexp_extract(a, "(?i)([a-c]+)([0-9]+)", 2)',
679+
# the flag turns on partway through, so only group 2 is case-insensitive
680+
'regexp_extract(a, "([a-c]+)(?i)([a-c]+)", 2)',
681+
'regexp_extract(a, "(?i)(abc)", 1)'),
682+
conf=_regexp_conf)
683+
672684
def test_regexp_extract_no_match():
673685
gen = mk_str_gen('[abcd]{1,3}[0-9]{1,3}[abcd]{1,3}')
674686
assert_gpu_and_cpu_are_equal_collect(
@@ -1120,6 +1132,69 @@ def test_rlike_fallback_lookarounds_independent_named():
11201132
'RLike',
11211133
conf=_regexp_conf)
11221134

1135+
def test_rlike_case_insensitive_inline_flag():
1136+
gen = mk_str_gen('[a-dA-D]{1,4}')
1137+
assert_gpu_and_cpu_are_equal_collect(
1138+
lambda spark: unary_op_df(spark, gen).selectExpr(
1139+
'a rlike "(?i)abc"',
1140+
'a rlike "(?i)[a-c]"',
1141+
'a rlike "a(?i)b"',
1142+
'a rlike "(?i)a(?-i)b"',
1143+
'a rlike "a|(?i)b"',
1144+
'a rlike "(?i)(a|b)c"',
1145+
'a rlike "(?i:abc)"',
1146+
'a rlike "(?i:a|b)"',
1147+
'a rlike "(?-i:a)b"',
1148+
# nested scoped-flags groups
1149+
'a rlike "(?i:(?-i:a)b)"',
1150+
'a rlike "(?-i:a(?i:b)c)"',
1151+
'a rlike "(?i:a(?-i:b(?i:c))d)"',
1152+
# a bare inline flag inside a scoped-flags group
1153+
'a rlike "(?i:a(?-i)b)"',
1154+
'a rlike "(?-i:a(?i)b)"',
1155+
# a negated non-case-insensitive flag is a no-op (mode off by default)
1156+
'a rlike "(?-s:abc)"',
1157+
'a rlike "(?i-s:abc)"'),
1158+
conf=_regexp_conf)
1159+
1160+
def test_regexp_replace_case_insensitive_inline_flag():
1161+
from pyspark.sql.functions import regexp_replace, col
1162+
gen = mk_str_gen('[a-cA-C]{0,4}')
1163+
# case folding must also work in replace mode, including a backref into a case-insensitive
1164+
# capture group (uses the DataFrame API so Spark SQL does not expand ${...} first)
1165+
assert_gpu_and_cpu_are_equal_collect(
1166+
lambda spark: unary_op_df(spark, gen).select(
1167+
regexp_replace(col('a'), '(?i)abc', 'X'),
1168+
regexp_replace(col('a'), '(?i:abc)', 'Y'),
1169+
regexp_replace(col('a'), '(?i)a(?-i)b', 'Z'),
1170+
regexp_replace(col('a'), '(?i)(abc)', '[${1}]')),
1171+
conf=_regexp_conf)
1172+
1173+
@allow_non_gpu('ProjectExec', 'RLike')
1174+
def test_rlike_fallback_unsupported_inline_flags():
1175+
gen = mk_str_gen('[abcd]{1,3}')
1176+
# (?m)/(?s) are unsupported (positive) flags; (?i) that precedes a choice alternative cannot
1177+
# be folded (in Java `a(?i)b|c` makes both `b` and `c` case-insensitive); scoped groups with a
1178+
# positive non-case-insensitive flag are likewise unsupported
1179+
for pattern in ['(?m)a', '(?s)a', '(?i)a|b', '(?i)a|b|c', 'a(?i)b|c', '(?m:a)', '(?is:a)']:
1180+
assert_gpu_fallback_collect(
1181+
lambda spark, pattern=pattern: unary_op_df(spark, gen).selectExpr(
1182+
f'a rlike "{pattern}"'),
1183+
'RLike',
1184+
conf=_regexp_conf)
1185+
1186+
@allow_non_gpu('ProjectExec', 'RLike')
1187+
def test_rlike_fallback_inline_flags_with_anchors():
1188+
gen = mk_str_gen('[abcd]{1,3}')
1189+
# a zero-width (?i) must not let an otherwise-unsupported anchor context (\n$, $^, ^$, or an
1190+
# anchors-only sequence) reach the GPU; these all fall back like their flag-free forms
1191+
for pattern in ['$(?i)^', '^(?i)$', '^(?i)', '(?i)$']:
1192+
assert_gpu_fallback_collect(
1193+
lambda spark, pattern=pattern: unary_op_df(spark, gen).selectExpr(
1194+
f'a rlike "{pattern}"'),
1195+
'RLike',
1196+
conf=_regexp_conf)
1197+
11231198
def test_regexp_extract_all_idx_zero():
11241199
gen = mk_str_gen('[abcd]{0,3}[0-9]{0,3}-[0-9]{0,3}[abcd]{1,3}')
11251200
assert_gpu_and_cpu_are_equal_collect(

0 commit comments

Comments
 (0)