From c631892be841c07b9aa37814a39bdcaaa87a469b Mon Sep 17 00:00:00 2001 From: Nic Crane Date: Sun, 6 Sep 2026 12:59:38 +0100 Subject: [PATCH 1/2] Clarify docs --- r/R/dplyr-funcs-doc.R | 12 ++++++------ r/R/dplyr-funcs-string.R | 42 ++++++++++++++++++++++++++++++++-------- r/man/acero.Rd | 12 ++++++------ 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/r/R/dplyr-funcs-doc.R b/r/R/dplyr-funcs-doc.R index 1adf23fba1b9..1296469efb6a 100644 --- a/r/R/dplyr-funcs-doc.R +++ b/r/R/dplyr-funcs-doc.R @@ -148,7 +148,7 @@ #' * [`floor()`][base::floor()] #' * [`format()`][base::format()] #' * [`grepl()`][base::grepl()] -#' * [`gsub()`][base::gsub()] +#' * [`gsub()`][base::gsub()]: multiple replacements not supported; `pattern` and `replacement` must be length 1 character vectors #' * [`ifelse()`][base::ifelse()] #' * [`is.character()`][base::is.character()] #' * [`is.double()`][base::is.double()] @@ -186,7 +186,7 @@ #' Valid values are "s", "ms" (default), "us", "ns". #' * [`strrep()`][base::strrep()] #' * [`strsplit()`][base::strsplit()] -#' * [`sub()`][base::sub()] +#' * [`sub()`][base::sub()]: multiple replacements not supported; `pattern` and `replacement` must be length 1 character vectors #' * [`substr()`][base::substr()]: `start` and `stop` must be length 1 #' * [`substring()`][base::substring()] #' * [`sum()`][base::sum()] @@ -344,10 +344,10 @@ #' * [`str_length()`][stringr::str_length()] #' * [`str_like()`][stringr::str_like()] #' * [`str_pad()`][stringr::str_pad()] -#' * [`str_remove()`][stringr::str_remove()] -#' * [`str_remove_all()`][stringr::str_remove_all()] -#' * [`str_replace()`][stringr::str_replace()] -#' * [`str_replace_all()`][stringr::str_replace_all()] +#' * [`str_remove()`][stringr::str_remove()]: multiple patterns not supported; `pattern` must be a length 1 character vector +#' * [`str_remove_all()`][stringr::str_remove_all()]: multiple patterns not supported; `pattern` must be a length 1 character vector +#' * [`str_replace()`][stringr::str_replace()]: multiple replacements not supported; `pattern` and `replacement` must be length 1 character vectors +#' * [`str_replace_all()`][stringr::str_replace_all()]: multiple replacements not supported; `pattern` and `replacement` must be length 1 character vectors #' * [`str_replace_na()`][stringr::str_replace_na()] #' * [`str_split()`][stringr::str_split()]: Case-insensitive string splitting and splitting into 0 parts not supported #' * [`str_starts()`][stringr::str_starts()] diff --git a/r/R/dplyr-funcs-string.R b/r/R/dplyr-funcs-string.R index 158bae2db87c..04920da8603e 100644 --- a/r/R/dplyr-funcs-string.R +++ b/r/R/dplyr-funcs-string.R @@ -362,10 +362,14 @@ register_bindings_string_regex <- function() { arrow_r_string_replace_function <- function(max_replacements) { function(pattern, replacement, x, ignore.case = FALSE, fixed = FALSE) { if (length(pattern) != 1) { - validation_error("`pattern` must be a length 1 character vector") + validation_error( + "Multiple replacements not supported: `pattern` must be a length 1 character vector" + ) } if (length(replacement) != 1) { - validation_error("`replacement` must be a length 1 character vector") + validation_error( + "Multiple replacements not supported: `replacement` must be a length 1 character vector" + ) } Expression$create( ifelse(fixed && !ignore.case, "replace_substring", "replace_substring_regex"), @@ -407,12 +411,34 @@ register_bindings_string_regex <- function() { } } - register_binding("base::sub", arrow_r_string_replace_function(1L)) - register_binding("base::gsub", arrow_r_string_replace_function(-1L)) - register_binding("stringr::str_replace", arrow_stringr_string_replace_function(1L)) - register_binding("stringr::str_replace_all", arrow_stringr_string_replace_function(-1L)) - register_binding("stringr::str_remove", arrow_stringr_string_remove_function(1L)) - register_binding("stringr::str_remove_all", arrow_stringr_string_remove_function(-1L)) + replace_notes <- paste( + "multiple replacements not supported;", + "`pattern` and `replacement` must be length 1 character vectors" + ) + remove_notes <- "multiple patterns not supported; `pattern` must be a length 1 character vector" + + register_binding("base::sub", arrow_r_string_replace_function(1L), notes = replace_notes) + register_binding("base::gsub", arrow_r_string_replace_function(-1L), notes = replace_notes) + register_binding( + "stringr::str_replace", + arrow_stringr_string_replace_function(1L), + notes = replace_notes + ) + register_binding( + "stringr::str_replace_all", + arrow_stringr_string_replace_function(-1L), + notes = replace_notes + ) + register_binding( + "stringr::str_remove", + arrow_stringr_string_remove_function(1L), + notes = remove_notes + ) + register_binding( + "stringr::str_remove_all", + arrow_stringr_string_remove_function(-1L), + notes = remove_notes + ) register_binding("stringr::str_replace_na", function(string, replacement = "NA") { if (!is.character(replacement) || length(replacement) != 1) { diff --git a/r/man/acero.Rd b/r/man/acero.Rd index 0cd6e284e44d..98abade19437 100644 --- a/r/man/acero.Rd +++ b/r/man/acero.Rd @@ -137,7 +137,7 @@ Consider using the lubridate specialised parsing functions \code{ymd()}, \code{y \item \code{\link[base:floor]{floor()}} \item \code{\link[base:format]{format()}} \item \code{\link[base:grepl]{grepl()}} -\item \code{\link[base:gsub]{gsub()}} +\item \code{\link[base:gsub]{gsub()}}: multiple replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors \item \code{\link[base:ifelse]{ifelse()}} \item \code{\link[base:is.character]{is.character()}} \item \code{\link[base:is.double]{is.double()}} @@ -175,7 +175,7 @@ Consider using the lubridate specialised parsing functions \code{ymd()}, \code{y Valid values are "s", "ms" (default), "us", "ns". \item \code{\link[base:strrep]{strrep()}} \item \code{\link[base:strsplit]{strsplit()}} -\item \code{\link[base:sub]{sub()}} +\item \code{\link[base:sub]{sub()}}: multiple replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors \item \code{\link[base:substr]{substr()}}: \code{start} and \code{stop} must be length 1 \item \code{\link[base:substring]{substring()}} \item \code{\link[base:sum]{sum()}} @@ -351,10 +351,10 @@ Pattern modifiers \code{coll()} and \code{boundary()} are not supported in any f \item \code{\link[stringr:str_length]{str_length()}} \item \code{\link[stringr:str_like]{str_like()}} \item \code{\link[stringr:str_pad]{str_pad()}} -\item \code{\link[stringr:str_remove]{str_remove()}} -\item \code{\link[stringr:str_remove_all]{str_remove_all()}} -\item \code{\link[stringr:str_replace]{str_replace()}} -\item \code{\link[stringr:str_replace_all]{str_replace_all()}} +\item \code{\link[stringr:str_remove]{str_remove()}}: multiple patterns not supported; \code{pattern} must be a length 1 character vector +\item \code{\link[stringr:str_remove_all]{str_remove_all()}}: multiple patterns not supported; \code{pattern} must be a length 1 character vector +\item \code{\link[stringr:str_replace]{str_replace()}}: multiple replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors +\item \code{\link[stringr:str_replace_all]{str_replace_all()}}: multiple replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors \item \code{\link[stringr:str_replace_na]{str_replace_na()}} \item \code{\link[stringr:str_split]{str_split()}}: Case-insensitive string splitting and splitting into 0 parts not supported \item \code{\link[stringr:str_starts]{str_starts()}} From dc3f4fe77369b22f24fee950c5943841bb51b1fe Mon Sep 17 00:00:00 2001 From: Nic Crane Date: Sun, 6 Sep 2026 13:29:48 +0100 Subject: [PATCH 2/2] Rephrase --- r/R/dplyr-funcs-doc.R | 8 ++++---- r/R/dplyr-funcs-string.R | 4 ++-- r/man/acero.Rd | 8 ++++---- r/tests/testthat/test-dplyr-funcs-string.R | 19 +++++++++++++++++++ 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/r/R/dplyr-funcs-doc.R b/r/R/dplyr-funcs-doc.R index 1296469efb6a..046047406a59 100644 --- a/r/R/dplyr-funcs-doc.R +++ b/r/R/dplyr-funcs-doc.R @@ -148,7 +148,7 @@ #' * [`floor()`][base::floor()] #' * [`format()`][base::format()] #' * [`grepl()`][base::grepl()] -#' * [`gsub()`][base::gsub()]: multiple replacements not supported; `pattern` and `replacement` must be length 1 character vectors +#' * [`gsub()`][base::gsub()]: multiple patterns or replacements not supported; `pattern` and `replacement` must be length 1 character vectors #' * [`ifelse()`][base::ifelse()] #' * [`is.character()`][base::is.character()] #' * [`is.double()`][base::is.double()] @@ -186,7 +186,7 @@ #' Valid values are "s", "ms" (default), "us", "ns". #' * [`strrep()`][base::strrep()] #' * [`strsplit()`][base::strsplit()] -#' * [`sub()`][base::sub()]: multiple replacements not supported; `pattern` and `replacement` must be length 1 character vectors +#' * [`sub()`][base::sub()]: multiple patterns or replacements not supported; `pattern` and `replacement` must be length 1 character vectors #' * [`substr()`][base::substr()]: `start` and `stop` must be length 1 #' * [`substring()`][base::substring()] #' * [`sum()`][base::sum()] @@ -346,8 +346,8 @@ #' * [`str_pad()`][stringr::str_pad()] #' * [`str_remove()`][stringr::str_remove()]: multiple patterns not supported; `pattern` must be a length 1 character vector #' * [`str_remove_all()`][stringr::str_remove_all()]: multiple patterns not supported; `pattern` must be a length 1 character vector -#' * [`str_replace()`][stringr::str_replace()]: multiple replacements not supported; `pattern` and `replacement` must be length 1 character vectors -#' * [`str_replace_all()`][stringr::str_replace_all()]: multiple replacements not supported; `pattern` and `replacement` must be length 1 character vectors +#' * [`str_replace()`][stringr::str_replace()]: multiple patterns or replacements not supported; `pattern` and `replacement` must be length 1 character vectors +#' * [`str_replace_all()`][stringr::str_replace_all()]: multiple patterns or replacements not supported; `pattern` and `replacement` must be length 1 character vectors #' * [`str_replace_na()`][stringr::str_replace_na()] #' * [`str_split()`][stringr::str_split()]: Case-insensitive string splitting and splitting into 0 parts not supported #' * [`str_starts()`][stringr::str_starts()] diff --git a/r/R/dplyr-funcs-string.R b/r/R/dplyr-funcs-string.R index 04920da8603e..ef062c847359 100644 --- a/r/R/dplyr-funcs-string.R +++ b/r/R/dplyr-funcs-string.R @@ -363,7 +363,7 @@ register_bindings_string_regex <- function() { function(pattern, replacement, x, ignore.case = FALSE, fixed = FALSE) { if (length(pattern) != 1) { validation_error( - "Multiple replacements not supported: `pattern` must be a length 1 character vector" + "Multiple patterns not supported: `pattern` must be a length 1 character vector" ) } if (length(replacement) != 1) { @@ -412,7 +412,7 @@ register_bindings_string_regex <- function() { } replace_notes <- paste( - "multiple replacements not supported;", + "multiple patterns or replacements not supported;", "`pattern` and `replacement` must be length 1 character vectors" ) remove_notes <- "multiple patterns not supported; `pattern` must be a length 1 character vector" diff --git a/r/man/acero.Rd b/r/man/acero.Rd index 98abade19437..d580600c965a 100644 --- a/r/man/acero.Rd +++ b/r/man/acero.Rd @@ -137,7 +137,7 @@ Consider using the lubridate specialised parsing functions \code{ymd()}, \code{y \item \code{\link[base:floor]{floor()}} \item \code{\link[base:format]{format()}} \item \code{\link[base:grepl]{grepl()}} -\item \code{\link[base:gsub]{gsub()}}: multiple replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors +\item \code{\link[base:gsub]{gsub()}}: multiple patterns or replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors \item \code{\link[base:ifelse]{ifelse()}} \item \code{\link[base:is.character]{is.character()}} \item \code{\link[base:is.double]{is.double()}} @@ -175,7 +175,7 @@ Consider using the lubridate specialised parsing functions \code{ymd()}, \code{y Valid values are "s", "ms" (default), "us", "ns". \item \code{\link[base:strrep]{strrep()}} \item \code{\link[base:strsplit]{strsplit()}} -\item \code{\link[base:sub]{sub()}}: multiple replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors +\item \code{\link[base:sub]{sub()}}: multiple patterns or replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors \item \code{\link[base:substr]{substr()}}: \code{start} and \code{stop} must be length 1 \item \code{\link[base:substring]{substring()}} \item \code{\link[base:sum]{sum()}} @@ -353,8 +353,8 @@ Pattern modifiers \code{coll()} and \code{boundary()} are not supported in any f \item \code{\link[stringr:str_pad]{str_pad()}} \item \code{\link[stringr:str_remove]{str_remove()}}: multiple patterns not supported; \code{pattern} must be a length 1 character vector \item \code{\link[stringr:str_remove_all]{str_remove_all()}}: multiple patterns not supported; \code{pattern} must be a length 1 character vector -\item \code{\link[stringr:str_replace]{str_replace()}}: multiple replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors -\item \code{\link[stringr:str_replace_all]{str_replace_all()}}: multiple replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors +\item \code{\link[stringr:str_replace]{str_replace()}}: multiple patterns or replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors +\item \code{\link[stringr:str_replace_all]{str_replace_all()}}: multiple patterns or replacements not supported; \code{pattern} and \code{replacement} must be length 1 character vectors \item \code{\link[stringr:str_replace_na]{str_replace_na()}} \item \code{\link[stringr:str_split]{str_split()}}: Case-insensitive string splitting and splitting into 0 parts not supported \item \code{\link[stringr:str_starts]{str_starts()}} diff --git a/r/tests/testthat/test-dplyr-funcs-string.R b/r/tests/testthat/test-dplyr-funcs-string.R index 58da3ea23358..10634adc74fa 100644 --- a/r/tests/testthat/test-dplyr-funcs-string.R +++ b/r/tests/testthat/test-dplyr-funcs-string.R @@ -1586,3 +1586,22 @@ test_that("str_replace_na", { df ) }) + +test_that("GH-45314: multiple patterns or replacements give an informative error", { + x <- Expression$field_ref("x") + + expect_error( + call_binding("str_replace_all", x, c("F" = "_", "b" = "")), + regexp = "Multiple patterns not supported: `pattern` must be a length 1 character vector" + ) + + expect_error( + call_binding("gsub", "o", c("u", "a"), x), + regexp = "Multiple replacements not supported: `replacement` must be a length 1 character vector" + ) + + expect_error( + call_binding("str_remove_all", x, c("F", "b")), + regexp = "Multiple patterns not supported: `pattern` must be a length 1 character vector" + ) +})