From d1a275869920211444ead90a8082d83844ab55b4 Mon Sep 17 00:00:00 2001 From: Evgen Belozerov <199082388+belowzeroff@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:31:48 -0400 Subject: [PATCH 1/3] fix(table): normalize head and tail counts --- rayforce/src/query.rs | 11 ++++++++--- rayforce/tests/query.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/rayforce/src/query.rs b/rayforce/src/query.rs index 5bdcd92..bf84200 100644 --- a/rayforce/src/query.rs +++ b/rayforce/src/query.rs @@ -4,7 +4,7 @@ //! `(select dict)` and evaluated; `update`/`insert`/`upsert` call the core //! query builtins directly. Mirrors `rayforce-py/types/table.py`. -use crate::error::{check, materialize, Result}; +use crate::error::{check, materialize, RayError, Result}; use crate::expr::Expr; use crate::ops::Operation; use crate::runtime::eval_value; @@ -304,12 +304,12 @@ impl Table { /// First `n` rows. pub fn head(&self, n: i64) -> Result { - Table::from_value(take_rows(self.as_value(), n)?) + Table::from_value(take_rows(self.as_value(), take_magnitude(n, "head")?)?) } /// Last `n` rows. pub fn tail(&self, n: i64) -> Result
{ - Table::from_value(take_rows(self.as_value(), -n)?) + Table::from_value(take_rows(self.as_value(), -take_magnitude(n, "tail")?)?) } /// Take `n` rows (negative counts from the end). @@ -349,3 +349,8 @@ fn take_rows(table: &Value, n: i64) -> Result { let ast = Value::list(&[Value::name_ref("take"), table.clone(), Value::i64(n)]); eval_value(&ast) } + +fn take_magnitude(n: i64, op: &str) -> Result { + n.checked_abs() + .ok_or_else(|| RayError::binding(format!("{op}: count magnitude overflows i64"))) +} diff --git a/rayforce/tests/query.rs b/rayforce/tests/query.rs index 4ab430b..1dfa115 100644 --- a/rayforce/tests/query.rs +++ b/rayforce/tests/query.rs @@ -196,6 +196,38 @@ fn head_tail_take() { tl.column("size").unwrap().get(0).unwrap().as_i64().unwrap(), 50 ); + let h_neg = t.head(-2).unwrap(); + assert_eq!( + h_neg + .column("size") + .unwrap() + .as_slice::() + .unwrap() + .to_vec(), + vec![10, 20] + ); + let tl_neg = t.tail(-2).unwrap(); + assert_eq!( + tl_neg + .column("size") + .unwrap() + .as_slice::() + .unwrap() + .to_vec(), + vec![40, 50] + ); + let take_neg = t.take(-2).unwrap(); + assert_eq!( + take_neg + .column("size") + .unwrap() + .as_slice::() + .unwrap() + .to_vec(), + vec![40, 50] + ); + assert!(t.head(i64::MIN).is_err()); + assert!(t.tail(i64::MIN).is_err()); Ok(()) }) .unwrap(); From 7c42cda8ed101ebb7701b9d111d2e853d247973a Mon Sep 17 00:00:00 2001 From: Evgen Belozerov <199082388+belowzeroff@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:31:48 -0400 Subject: [PATCH 2/3] fix(vector): return empty slices without reading data pointers --- rayforce/src/vector.rs | 3 +++ rayforce/tests/containers.rs | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/rayforce/src/vector.rs b/rayforce/src/vector.rs index 1660e61..0ed1ff1 100644 --- a/rayforce/src/vector.rs +++ b/rayforce/src/vector.rs @@ -186,6 +186,9 @@ impl Value { } unsafe { let n = self.len(); + if n == 0 { + return Ok(&[]); + } let ptr = raw::data(self.as_ptr()) as *const T; Ok(std::slice::from_raw_parts(ptr, n)) } diff --git a/rayforce/tests/containers.rs b/rayforce/tests/containers.rs index a38d51e..2321d5f 100644 --- a/rayforce/tests/containers.rs +++ b/rayforce/tests/containers.rs @@ -246,8 +246,9 @@ fn bool_and_temporal_slices() { assert_eq!(b.bool_slice().unwrap(), &[1u8, 0, 1]); let dates = Value::empty_vec(rayforce::sys::RAY_DATE as i8, 0); - let _ = dates; // construct-by-slice for temporals comes via Value::vec on i32 raw later - // date/time/timestamp readers reject a plain i64 vector + assert_eq!(dates.date_days_slice().unwrap(), &[]); + // construct-by-slice for temporals comes via Value::vec on i32 raw later + // date/time/timestamp readers reject a plain i64 vector let v = Value::vec(&[1i64, 2, 3]); assert!(v.date_days_slice().is_err()); assert!(v.timestamp_nanos_slice().is_err()); From f3925105b4b2955a585901d96f8242b96b062211 Mon Sep 17 00:00:00 2001 From: Evgen Belozerov <199082388+belowzeroff@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:31:48 -0400 Subject: [PATCH 3/3] ci: serialize rayforce runtime tests --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 362080e..c012542 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,6 +148,8 @@ jobs: echo "${{ matrix.core }} archive: $n ray_dfd_check_live symbols, as expected" - name: Test + env: + RUST_TEST_THREADS: 1 run: cargo test --workspace # The vendored sources must actually land in the .crate — that is the