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