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

thisisnic pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 0241012442 GH-33432: [R] Match base/stringr semantics for 
str_replace() with NA replacement (#51197)
0241012442 is described below

commit 0241012442c00da87b777e24a37e1d155e80e806
Author: Sumit Chauhan <[email protected]>
AuthorDate: Sat Sep 12 16:48:53 2026 +0530

    GH-33432: [R] Match base/stringr semantics for str_replace() with NA 
replacement (#51197)
    
    ### Rationale for this change
    
    `base::sub()`/`gsub()` and `stringr::str_replace()`/`str_replace_all()` set 
the whole
    element to `NA` when the replacement is `NA` and the pattern matches. The 
Acero
    `replace_substring[_regex]` kernels don't support that and splice a literal 
`"NA"`
    into the string, e.g. `str_replace("one", "o", NA_character_)` returns 
`"NAne"`
    instead of `NA`.
    
    ### What changes are included in this PR?
    
    Special-case an `NA` `replacement` in the shared 
`sub`/`gsub`/`str_replace`/`str_replace_all`
    binding helper and rewrite it as `if_else(<pattern matches>, NA, x)` using
    `match_substring[_regex]`. Covers regex, fixed, and ignore-case patterns.
    
    ### Are these changes tested?
    
    Yes — new test in `test-dplyr-funcs-string.R` comparing against 
base/stringr for
    regex/fixed/ignore-case, empty strings, and `NA` input.
    
    ### Are there any user-facing changes?
    
    Bug fix only; `str_replace(x, p, NA)` now returns `NA` on match instead of a
    corrupted string.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    * GitHub Issue: #33432
    
    Lead-authored-by: Sumit Chauhan <[email protected]>
    Co-authored-by: Nic Crane <[email protected]>
    Signed-off-by: Nic Crane <[email protected]>
---
 r/R/dplyr-funcs-string.R                   | 18 ++++++++++++++++++
 r/tests/testthat/test-dplyr-funcs-string.R | 28 ++++++++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/r/R/dplyr-funcs-string.R b/r/R/dplyr-funcs-string.R
index 158bae2db8..da2f729e28 100644
--- a/r/R/dplyr-funcs-string.R
+++ b/r/R/dplyr-funcs-string.R
@@ -367,6 +367,24 @@ register_bindings_string_regex <- function() {
       if (length(replacement) != 1) {
         validation_error("`replacement` must be a length 1 character vector")
       }
+      # An NA replacement sets the whole string to NA wherever the pattern
+      # matches, matching base::sub()/gsub() and stringr::str_replace(). The
+      # replace_substring[_regex] kernels don't support this, so rewrite it as
+      # if_else(<pattern matches>, NA, x). GH-33432
+      if (is.na(replacement)) {
+        is_match <- create_string_match_expr(
+          ifelse(fixed, "match_substring", "match_substring_regex"),
+          string = x,
+          pattern = pattern,
+          ignore_case = ignore.case
+        )
+        return(Expression$create(
+          "if_else",
+          is_match,
+          Expression$scalar(NA_character_),
+          x
+        ))
+      }
       Expression$create(
         ifelse(fixed && !ignore.case, "replace_substring", 
"replace_substring_regex"),
         x,
diff --git a/r/tests/testthat/test-dplyr-funcs-string.R 
b/r/tests/testthat/test-dplyr-funcs-string.R
index 58da3ea233..59ae85afba 100644
--- a/r/tests/testthat/test-dplyr-funcs-string.R
+++ b/r/tests/testthat/test-dplyr-funcs-string.R
@@ -428,6 +428,34 @@ test_that("sub and gsub with namespacing", {
   )
 })
 
+test_that("str_replace/sub with an NA replacement match base/stringr 
(GH-33432)", {
+  df <- tibble(x = c("", "one", "two", "three", "four", NA))
+
+  # A match sets the whole value to NA; non-matches and NA input are unchanged
+  compare_dplyr_binding(
+    .input |>
+      transmute(
+        regex = str_replace(x, "o", NA_character_),
+        regex_all = str_replace_all(x, "o", NA_character_),
+        fixed = str_replace_all(x, fixed("o"), NA_character_),
+        ci = str_replace_all(x, regex("O", ignore_case = TRUE), NA_character_),
+        fixed_ci = str_replace_all(x, fixed("O", ignore_case = TRUE), 
NA_character_)
+      ) |>
+      collect(),
+    df
+  )
+
+  compare_dplyr_binding(
+    .input |>
+      transmute(
+        subbed = sub("o", NA_character_, x),
+        gsubbed = gsub("o", NA_character_, x)
+      ) |>
+      collect(),
+    df
+  )
+})
+
 test_that("str_replace and str_replace_all", {
   x <- Expression$field_ref("x")
 

Reply via email to