From dd092324c56a73bcb539f50f454f5a159f55c505 Mon Sep 17 00:00:00 2001 From: Ivan K Date: Mon, 19 Jan 2026 19:14:53 +0300 Subject: [PATCH 1/5] Regression tests --- inst/tests/tests.Rraw | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 4156e541c..5fc15c8a1 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21497,3 +21497,15 @@ test(2363.1, foverlaps(x, y, by.x, by.y), foverlaps(x, y2, by.x, by.y)) test(2363.2, foverlaps(x, y2, by.x, by.y, type="any", mult="all"), foverlaps(x, y2, by.x, by.y, type="any", mult="first")) test(2363.3, foverlaps(x, y, by.x, by.y, which=TRUE, mult="first", nomatch=NULL), foverlaps(x, y2, by.x, by.y, which=TRUE, mult="first", nomatch=NULL)) rm(x, y, y2) + +# internal use of set() causes non-resizable data.tables to be re-assigned in the wrong frame, #7604 +# bmerge -> coerce_col +y = structure(list(x = as.double(2:3), y = list("foo", "bar")), class = c("data.table", "data.frame")) +x = structure(list(x = 1:3), class = c("data.table", "data.frame")) +test(2364.1, y[x, on = "x"], data.table(x = 1:3, y = list(NULL, "foo", "bar"))) +x = structure(list(a = factor("a", levels = letters)), class = c("data.table", "data.frame")) +y = data.table(a = factor("a", levels = letters)) +setdroplevels(x) +setdroplevels(y) +test(2364.2, levels(x$a), levels(y$a)) +rm(x, y) From 84f895f6a2ef4500a4855a8300ee1068f4aaf3d3 Mon Sep 17 00:00:00 2001 From: Ivan K Date: Mon, 19 Jan 2026 19:15:04 +0300 Subject: [PATCH 2/5] set(): only reallocate if resizing would fail --- R/data.table.R | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/R/data.table.R b/R/data.table.R index 06a7f0437..b1c84b09a 100644 --- a/R/data.table.R +++ b/R/data.table.R @@ -2986,20 +2986,32 @@ setcolorder = function(x, neworder=key(x), before=NULL, after=NULL, skip_absent= invisible(x) } -.set_needs_alloccol = function(x, value) { +.set_needs_alloccol = function(x, j, value) { + # set() will try to resize x when adding or removing columns + # when removing a column, value can be NULL or list with NULLs inside + removing = is.null(value) || (is.list(value) && length(value) == length(j) && any(vapply_1b(value, is.null))) + # columns can be created by name + adding = is.character(j) && any(j %notin% names(x)) + + if (!(removing || adding)) return(FALSE) + # automatically allocate more space when tl <= ncol (either full or loaded from disk) - if (truelength(x) <= length(x)) return(TRUE) - if (selfrefok(x, verbose=FALSE) >= 1L) return(FALSE) - # value can be NULL or list with NULLs inside - if (is.null(value)) return(TRUE) - if (!is.list(value)) return(FALSE) - any(vapply_1b(value, is.null)) + # (or if a resize operation would otherwise fail) + if (selfrefok(x, verbose=FALSE) < 1L || truelength(x) <= length(x)) + return(TRUE) + + if (adding) { + extra = sum(j %notin% names(x)) + return(truelength(x) < length(x) + extra) + } + + FALSE } set = function(x,i=NULL,j,value) # low overhead, loopable { # If removing columns from a table that's not selfrefok, need to call setalloccol first, #7488 - if (.set_needs_alloccol(x, value)) { + if (.set_needs_alloccol(x, j, value)) { name = substitute(x) setalloccol(x, verbose=FALSE) if (is.name(name)) { From e54ac2f44278061a7f52b19d5b25ff38e15bbcdf Mon Sep 17 00:00:00 2001 From: aitap Date: Tue, 20 Jan 2026 07:28:33 +0000 Subject: [PATCH 3/5] Update R/data.table.R Co-authored-by: Michael Chirico --- R/data.table.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/data.table.R b/R/data.table.R index b1c84b09a..35a88dfd3 100644 --- a/R/data.table.R +++ b/R/data.table.R @@ -2991,7 +2991,7 @@ setcolorder = function(x, neworder=key(x), before=NULL, after=NULL, skip_absent= # when removing a column, value can be NULL or list with NULLs inside removing = is.null(value) || (is.list(value) && length(value) == length(j) && any(vapply_1b(value, is.null))) # columns can be created by name - adding = is.character(j) && any(j %notin% names(x)) + adding = is.character(j) && !all(j %chin% names(x)) if (!(removing || adding)) return(FALSE) From ba05965f44bb2910eac3f3e904f20a82cea87820 Mon Sep 17 00:00:00 2001 From: Ivan K Date: Tue, 20 Jan 2026 10:36:43 +0300 Subject: [PATCH 4/5] Rename test variables Co-Authored-By: Michael Chirico --- inst/tests/tests.Rraw | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 5fc15c8a1..27ad21eaf 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21500,9 +21500,9 @@ rm(x, y, y2) # internal use of set() causes non-resizable data.tables to be re-assigned in the wrong frame, #7604 # bmerge -> coerce_col -y = structure(list(x = as.double(2:3), y = list("foo", "bar")), class = c("data.table", "data.frame")) -x = structure(list(x = 1:3), class = c("data.table", "data.frame")) -test(2364.1, y[x, on = "x"], data.table(x = 1:3, y = list(NULL, "foo", "bar"))) +x = structure(list(a = as.double(2:3), b = list("foo", "bar")), class = c("data.table", "data.frame")) +y = structure(list(a = 1:3), class = c("data.table", "data.frame")) +test(2364.1, x[y, on = "a"], data.table(a = 1:3, b = list(NULL, "foo", "bar"))) x = structure(list(a = factor("a", levels = letters)), class = c("data.table", "data.frame")) y = data.table(a = factor("a", levels = letters)) setdroplevels(x) From 599f1cbfe0640578283e77055ac927381ad82679 Mon Sep 17 00:00:00 2001 From: Ivan K Date: Tue, 20 Jan 2026 12:09:22 +0300 Subject: [PATCH 5/5] Cache j %chin% names(x) Co-Authored-By: Benjamin Schwendinger <52290390+ben-schwen@users.noreply.github.com --- R/data.table.R | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/R/data.table.R b/R/data.table.R index 35a88dfd3..85d623d39 100644 --- a/R/data.table.R +++ b/R/data.table.R @@ -2991,7 +2991,10 @@ setcolorder = function(x, neworder=key(x), before=NULL, after=NULL, skip_absent= # when removing a column, value can be NULL or list with NULLs inside removing = is.null(value) || (is.list(value) && length(value) == length(j) && any(vapply_1b(value, is.null))) # columns can be created by name - adding = is.character(j) && !all(j %chin% names(x)) + adding = if (is.character(j)) { + jexists = j %chin% names(x) + !all(jexists) + } else FALSE if (!(removing || adding)) return(FALSE) @@ -3000,10 +3003,8 @@ setcolorder = function(x, neworder=key(x), before=NULL, after=NULL, skip_absent= if (selfrefok(x, verbose=FALSE) < 1L || truelength(x) <= length(x)) return(TRUE) - if (adding) { - extra = sum(j %notin% names(x)) - return(truelength(x) < length(x) + extra) - } + if (adding) + return(truelength(x) < length(x) + sum(!jexists)) FALSE }