Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 39 additions & 16 deletions src/ops/query.c
Original file line number Diff line number Diff line change
Expand Up @@ -9659,13 +9659,18 @@ ray_t* ray_update(ray_t** args, int64_t n) {
if (RAY_IS_ERR(bcast)) { ray_release(expr_vec); ray_release(new_col); ray_release(result); ray_release(mask_vec); ray_release(tbl); return bcast; }
}
} else {
size_t esz = (ct == RAY_BOOL) ? 1 : 8;
uint8_t elem[8] = {0};
if (ct == RAY_F64 && expr_vec->type == -RAY_I64) {
/* elem is wide enough for every fixed-width type incl.
* GUID (16 B), whose payload lives in ->obj — copying
* ray_elem_size(ct) bytes from ->i64 would over-read an
* 8-byte buffer and write the wrong source for GUID. */
uint8_t elem[16] = {0};
if (ct == RAY_GUID) {
if (expr_vec->obj) memcpy(elem, ray_data(expr_vec->obj), 16);
} else if (ct == RAY_F64 && expr_vec->type == -RAY_I64) {
double promoted = (double)expr_vec->i64;
memcpy(elem, &promoted, 8);
memcpy(elem, &promoted, sizeof promoted);
} else {
memcpy(elem, &expr_vec->i64, esz);
memcpy(elem, &expr_vec->i64, ray_elem_size(ct));
}
for (int64_t r = 0; r < nrows; r++) {
bcast = ray_vec_append(bcast, elem);
Expand Down Expand Up @@ -9897,13 +9902,17 @@ ray_t* ray_update(ray_t** args, int64_t n) {
if (RAY_IS_ERR(bcast)) { ray_release(expr_vec); ray_release(result); ray_release(tbl); return bcast; }
}
} else {
size_t esz = (ct == RAY_BOOL) ? 1 : 8;
uint8_t elem[8] = {0};
if (ct == RAY_F64 && expr_vec->type == -RAY_I64) {
/* Wide enough for every fixed-width type incl. GUID (16 B,
* payload in ->obj); ray_elem_size(ct) bytes from ->i64
* would over-read an 8-byte buffer for GUID. */
uint8_t elem[16] = {0};
if (ct == RAY_GUID) {
if (expr_vec->obj) memcpy(elem, ray_data(expr_vec->obj), 16);
} else if (ct == RAY_F64 && expr_vec->type == -RAY_I64) {
double promoted = (double)expr_vec->i64;
memcpy(elem, &promoted, 8);
memcpy(elem, &promoted, sizeof promoted);
} else {
memcpy(elem, &expr_vec->i64, esz);
memcpy(elem, &expr_vec->i64, ray_elem_size(ct));
}
for (int64_t r = 0; r < nrows; r++) {
bcast = ray_vec_append(bcast, elem);
Expand Down Expand Up @@ -10022,12 +10031,26 @@ ray_t* ray_update(ray_t** args, int64_t n) {
int8_t ct = -expr_vec->type;
ray_t* bcast = ray_vec_new(ct, nrows);
if (RAY_IS_ERR(bcast)) { ray_release(expr_vec); ray_release(result); ray_release(tbl); return bcast; }
size_t esz = ray_elem_size(ct);
uint8_t elem[8] = {0};
memcpy(elem, &expr_vec->i64, esz > 8 ? 8 : esz);
for (int64_t r = 0; r < nrows; r++) {
bcast = ray_vec_append(bcast, elem);
if (RAY_IS_ERR(bcast)) { ray_release(expr_vec); ray_release(result); ray_release(tbl); return bcast; }
if (ct == RAY_STR) {
const char* sp = (expr_vec->type == -RAY_STR) ? ray_str_ptr(expr_vec) : "";
size_t sl = (expr_vec->type == -RAY_STR) ? ray_str_len(expr_vec) : 0;
for (int64_t r = 0; r < nrows; r++) {
bcast = ray_str_vec_append(bcast, sp, sl);
if (RAY_IS_ERR(bcast)) { ray_release(expr_vec); ray_release(result); ray_release(tbl); return bcast; }
}
} else {
/* elem holds any fixed-width payload incl. GUID's 16 B (in
* ->obj); copying from ->i64 would be wrong/over-read for GUID. */
uint8_t elem[16] = {0};
if (ct == RAY_GUID) {
if (expr_vec->obj) memcpy(elem, ray_data(expr_vec->obj), 16);
} else {
memcpy(elem, &expr_vec->i64, ray_elem_size(ct));
}
for (int64_t r = 0; r < nrows; r++) {
bcast = ray_vec_append(bcast, elem);
if (RAY_IS_ERR(bcast)) { ray_release(expr_vec); ray_release(result); ray_release(tbl); return bcast; }
}
}
/* Preserve typed-null markers across broadcast (mirrors the
* existing-column branches above). Without this,
Expand Down
21 changes: 21 additions & 0 deletions test/rfl/table/update.rfl
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,24 @@ t -- (table [ID Name Value] (list [1 2] [alice bob] [10.0 20.0]))
;; Wrong-type atoms into typed columns still rejected.
(insert (table [a] (list (as 'Timestamp (list)))) (list 'bad)) !- type
(insert (table [a] (list (as 'Date (list)))) (list 1)) !- type

;; ══════════════════════════════════════════════════════════════════
;; UPDATE scalar-broadcast into typed columns — regression for the
;; broadcast sites that copied the scalar through an 8-byte `elem`
;; buffer with a (ct==BOOL?1:8) stride. That over-read the buffer for
;; GUID columns (16-byte payload, stored in ->obj) — an ASan stack-
;; buffer-overflow / crash — and relied on union aliasing for the
;; narrow-int / temporal types.
;; ══════════════════════════════════════════════════════════════════

;; Narrow-int and temporal columns keep their type after a scalar update.
(set t (table [k a] (list [1 2 3] (as 'I32 [10 20 30]))))(at (update {a: 99i from: 't}) 'a) -- [99i 99i 99i]
(set t (table [k a] (list [1 2 3] (as 'I16 [1 2 3]))))(at (update {a: 7h from: 't}) 'a) -- [7h 7h 7h]
(set t (table [k a] (list [1 2 3] (as 'Date [2020.01.01 2020.01.02 2020.01.03]))))(at (update {a: 2030.06.15 from: 't}) 'a) -- [2030.06.15 2030.06.15 2030.06.15]
(set t (table [k a] (list [1 2 3] (as 'Timestamp [2024.01.01D00:00:00.0 2024.01.02D00:00:00.0 2024.01.03D00:00:00.0]))))(at (update {a: 2030.01.01D00:00:00.0 from: 't}) 'a) -- [2030.01.01D00:00:00.000000000 2030.01.01D00:00:00.000000000 2030.01.01D00:00:00.000000000]

;; GUID column scalar update (all-rows): every row becomes the scalar guid.
;; (guid is random, so compare cells to the scalar rather than a literal.)
(set g (first (guid 1)))(set t (table [k a] (list [1 2 3] (guid 3))))(== (at (at (update {a: g from: 't}) 'a) 2) g) -- true
;; GUID column scalar update (WHERE): only matching rows change, no overflow.
(set g (first (guid 1)))(set t (table [k a] (list [1 2 3] (guid 3))))(== (at (at (update {a: g from: 't where: (== k 2)}) 'a) 1) g) -- true
Loading