diff --git a/examples/rfl/datalog.rfl b/examples/rfl/datalog.rfl
index 0b354e20..dd22a3a5 100644
--- a/examples/rfl/datalog.rfl
+++ b/examples/rfl/datalog.rfl
@@ -75,7 +75,7 @@
; ── 10. sym-name: readable output ───────────────────────
(println "--- sym-name: convert intern IDs ---")
(set p (pull db 1))
-(set name-id (get p 1))
+(set name-id (get p 'name))
(println name-id)
(println (sym-name name-id))
diff --git a/examples/rfl/flips.rfl b/examples/rfl/flips.rfl
index 8e6125b5..aad74b86 100644
--- a/examples/rfl/flips.rfl
+++ b/examples/rfl/flips.rfl
@@ -12,13 +12,13 @@
(set flips (except (.csv.read
[DATE I64 I64 SYMBOL I64 SYMBOL TIME F64 F64 I64
- I64 I64 SYMBOL GUID SYMBOL C8 C8 I64 I64 F64 I64 SYMBOL
- SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL C8 SYMBOL SYMBOL F64
- C8 C8 C8 C8 C8 C8 SYMBOL GUID F64 C8 I64 C8
- SYMBOL SYMBOL I64 C8 C8 SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL
+ I64 I64 SYMBOL GUID SYMBOL STR STR I64 I64 F64 I64 SYMBOL
+ SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL STR SYMBOL SYMBOL F64
+ STR STR STR STR STR STR SYMBOL GUID F64 STR I64 STR
+ SYMBOL SYMBOL I64 STR STR SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL
SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL
SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL SYMBOL
- SYMBOL SYMBOL F64 C8 I64 I64 I64 F64 F64 F64 F64 I64 I64 I64 I64
+ SYMBOL SYMBOL F64 STR I64 I64 I64 F64 F64 F64 F64 I64 I64 I64 I64
SYMBOL I64 I64 SYMBOL SYMBOL SYMBOL TIME SYMBOL I64]
csvpath) 'date))
diff --git a/examples/rfl/journal.rfl b/examples/rfl/journal.rfl
index 95293b92..901811c0 100644
--- a/examples/rfl/journal.rfl
+++ b/examples/rfl/journal.rfl
@@ -1,15 +1,31 @@
-;; Journaling example
-(set f (fn [x y] (println "RES: %" (+ x y))))
-
-;; Write journal
-(set h (.ipc.open "/tmp/jou.log"))
-(write h (list 'f 1 2))
-(write h (list 'f 2 3))
-(write h (list 'f 3 4))
-(.ipc.close h)
-
-;; Replay journal
-(set h (.ipc.open "/tmp/jou.log"))
-(read h)
-(.ipc.close h)
+;; Transaction-log journaling example (the `.log.*` API).
+;;
+;; In production you start the server with `-l ` (async) or `-L `
+;; (sync, fsync per write): every mutation that arrives over IPC is appended to
+;; .log and replayed automatically on restart, so user state survives a
+;; crash. The `.log.*` verbs expose that machinery directly.
+
+(set base "/tmp/jou_ex")
+(.sys.exec "rm -f /tmp/jou_ex*")
+
+;; Open a journal in async mode (writes buffered; use 'sync for fsync-per-write).
+(.log.open 'async base)
+
+;; Append entries. `.log.write` serializes its (already-evaluated) argument
+;; into the log; replay re-evaluates each payload in order.
+(.log.write 100)
+(.log.write [1 2 3])
+(.log.write {sym: 'AAPL px: 191.5})
+
+;; Flush buffered writes to disk and close.
+(.log.sync)
+(.log.close)
+
+;; Inspect the log: (.log.validate path) -> [chunks valid_bytes].
+(println "validate %.log -> %" base (.log.validate (format "%.log" base)))
+
+;; Replay the journal — returns the number of entries replayed.
+(println "replayed % entries" (.log.replay (format "%.log" base)))
+
+(.sys.exec "rm -f /tmp/jou_ex*")
(exit 0)
diff --git a/examples/rfl/window.rfl b/examples/rfl/window.rfl
index f4dffbb1..86987f07 100644
--- a/examples/rfl/window.rfl
+++ b/examples/rfl/window.rfl
@@ -1,6 +1,26 @@
+;; Window join example.
+;;
+;; A window join aggregates, for each trade, the quotes whose timestamps fall
+;; in a per-trade time window. The `intervals` argument is two parallel
+;; vectors — (lo_vec hi_vec) — with one entry per trade row: the window for
+;; trade r is [lo_vec[r], hi_vec[r]].
+;;
+;; window-join (wj) seeds each window with the PREVAILING quote — the last
+;; quote at/before lo — then extends through hi.
+;; window-join1 (wj1) is strict: only quotes whose time is inside [lo, hi].
+
(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))
(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))
-
-(set intervals (list [11:59:59 12:00:02 12:00:04] [12:00:03 12:00:06 12:00:08]))
-;; (window-join [Sym Time] intervals trades quotes {a: (sum Bid) b: (sum Ask)})
+;; Per-trade windows: two parallel vectors (lo_vec hi_vec) built with map-left,
+;; here ±1.5s around each trade time. The 1.5s offset makes each window open
+;; *between* quotes, so wj's prevailing-quote seed differs visibly from wj1.
+(set intervals (map-left + [-1500 1500] (at trades 'Time)))
+
+(println "window-join (prevailing quote seeded):")
+(println "%" (window-join [Sym Time] intervals trades quotes {n: (count Size) lo: (min Size) hi: (max Size) total: (sum Size)}))
+
+(println "window-join1 (strict window):")
+(println "%" (window-join1 [Sym Time] intervals trades quotes {n: (count Size) lo: (min Size) hi: (max Size) total: (sum Size)}))
+
+(exit 0)
diff --git a/src/lang/eval.c b/src/lang/eval.c
index 496143c9..d6901e3c 100644
--- a/src/lang/eval.c
+++ b/src/lang/eval.c
@@ -2728,7 +2728,7 @@ static void ray_register_builtins(void) {
register_vary("inner-join", RAY_FN_NONE, ray_inner_join_fn);
register_vary("anti-join", RAY_FN_NONE, ray_anti_join_fn);
register_vary("window-join", RAY_FN_SPECIAL_FORM, ray_window_join_fn);
- register_vary("window-join1", RAY_FN_SPECIAL_FORM, ray_window_join_fn);
+ register_vary("window-join1", RAY_FN_SPECIAL_FORM, ray_window_join1_fn);
register_vary("asof-join", RAY_FN_NONE, ray_asof_join_fn);
/* I/O builtins */
diff --git a/src/lang/eval.h b/src/lang/eval.h
index ca1630cc..7d7139d0 100644
--- a/src/lang/eval.h
+++ b/src/lang/eval.h
@@ -278,6 +278,7 @@ ray_t* ray_xbar_fn(ray_t* col, ray_t* bucket);
ray_t* ray_left_join_fn(ray_t** args, int64_t n);
ray_t* ray_inner_join_fn(ray_t** args, int64_t n);
ray_t* ray_window_join_fn(ray_t** args, int64_t n);
+ray_t* ray_window_join1_fn(ray_t** args, int64_t n);
/* I/O */
ray_t* ray_println_fn(ray_t** args, int64_t n);
diff --git a/src/lang/internal.h b/src/lang/internal.h
index 1c5c349d..0b6157e5 100644
--- a/src/lang/internal.h
+++ b/src/lang/internal.h
@@ -606,6 +606,7 @@ ray_t* ray_left_join_fn(ray_t** args, int64_t n);
ray_t* ray_inner_join_fn(ray_t** args, int64_t n);
ray_t* ray_anti_join_fn(ray_t** args, int64_t n);
ray_t* ray_window_join_fn(ray_t** args, int64_t n);
+ray_t* ray_window_join1_fn(ray_t** args, int64_t n);
ray_t* ray_asof_join_fn(ray_t** args, int64_t n);
/* Graph builtins (.graph.* family). Implemented in src/ops/graph_builtin.c —
diff --git a/src/ops/builtins.c b/src/ops/builtins.c
index b838e851..44f17182 100644
--- a/src/ops/builtins.c
+++ b/src/ops/builtins.c
@@ -461,7 +461,10 @@ ray_t* ray_timeit_fn(ray_t** args, int64_t n) {
int64_t t0 = ray_profile_now_ns();
ray_t* result = ray_eval(args[0]);
int64_t t1 = ray_profile_now_ns();
- if (result && !RAY_IS_ERR(result)) ray_release(result);
+ /* Propagate errors instead of swallowing them — otherwise a failing
+ * expression silently reports a timing as if it had succeeded. */
+ if (result && RAY_IS_ERR(result)) return result;
+ if (result) ray_release(result);
double ms = (double)(t1 - t0) / 1e6;
return make_f64(ms);
}
diff --git a/src/ops/query.c b/src/ops/query.c
index a03cf8ce..61933fa0 100644
--- a/src/ops/query.c
+++ b/src/ops/query.c
@@ -11153,6 +11153,7 @@ typedef struct {
int64_t right_nrows;
int64_t n_eq;
int64_t n_agg;
+ int mode; /* 0 = wj (prevailing-quote seeded), 1 = wj1 (strict) */
/* Left-row metadata — pre-extracted to int64 so workers can read
* without touching any ray_t objects (no locking, no allocation). */
@@ -11183,6 +11184,18 @@ typedef struct {
uint8_t* result_null[WJ_MAX_AGG]; /* 1 byte per row: 1 = null */
} wj_scan_ctx_t;
+/* Compare the equality tuple of sorted-right row `ri` against `target_eq`.
+ * Returns <0, 0, >0. Used to bracket a left row's eq partition. */
+static inline int wj_eq_cmp(const wj_scan_ctx_t* c, int64_t ri,
+ const int64_t* target_eq, int64_t n_eq) {
+ for (int64_t e = 0; e < n_eq; e++) {
+ int64_t rv = read_col_i64(c->eq_data[e], ri, c->eq_type[e], c->eq_attrs[e]);
+ if (rv < target_eq[e]) return -1;
+ if (rv > target_eq[e]) return 1;
+ }
+ return 0;
+}
+
static void wj_scan_fn(void* ctx_, uint32_t worker_id, int64_t start, int64_t end) {
(void)worker_id;
wj_scan_ctx_t* c = (wj_scan_ctx_t*)ctx_;
@@ -11201,33 +11214,49 @@ static void wj_scan_fn(void* ctx_, uint32_t worker_id, int64_t start, int64_t en
for (int64_t e = 0; e < n_eq; e++)
target_eq[e] = c->left_eq_arr[e][lr];
- /* lower_bound: first rank with (eq, time) >= (target_eq, lo) */
- int64_t lb = 0, lb_hi = rn;
- while (lb < lb_hi) {
- int64_t m = (lb + lb_hi) >> 1;
- int64_t ri = right_sort[m];
- int cmp = 0;
- for (int64_t e = 0; e < n_eq && cmp == 0; e++) {
- int64_t rv = read_col_i64(c->eq_data[e], ri, c->eq_type[e], c->eq_attrs[e]);
- if (rv < target_eq[e]) cmp = -1;
- else if (rv > target_eq[e]) cmp = 1;
- }
- if (cmp == 0 && rt_time_i[ri] < lo) cmp = -1;
- if (cmp < 0) lb = m + 1; else lb_hi = m;
- }
- int64_t ub = lb, ub_hi = rn;
+ /* Locate the eq partition [ps, pe) in the sorted right table — the
+ * contiguous run of ranks whose equality tuple == target_eq. Compare
+ * eq keys only (the sort is (eq..., time), so the run is contiguous). */
+ int64_t ps = 0, ps_hi = rn;
+ while (ps < ps_hi) {
+ int64_t m = (ps + ps_hi) >> 1;
+ if (wj_eq_cmp(c, right_sort[m], target_eq, n_eq) < 0) ps = m + 1;
+ else ps_hi = m;
+ }
+ int64_t pe = ps, pe_hi = rn;
+ while (pe < pe_hi) {
+ int64_t m = (pe + pe_hi) >> 1;
+ if (wj_eq_cmp(c, right_sort[m], target_eq, n_eq) <= 0) pe = m + 1;
+ else pe_hi = m;
+ }
+
+ /* Upper bound (both modes): first rank in [ps, pe) with time > hi. */
+ int64_t ub = ps, ub_hi = pe;
while (ub < ub_hi) {
int64_t m = (ub + ub_hi) >> 1;
- int64_t ri = right_sort[m];
- int cmp = 0;
- for (int64_t e = 0; e < n_eq && cmp == 0; e++) {
- int64_t rv = read_col_i64(c->eq_data[e], ri, c->eq_type[e], c->eq_attrs[e]);
- if (rv < target_eq[e]) cmp = -1;
- else if (rv > target_eq[e]) cmp = 1;
+ if (rt_time_i[right_sort[m]] <= hi) ub = m + 1; else ub_hi = m;
+ }
+
+ /* Lower bound differs by mode:
+ * wj1 (mode 1): first rank in [ps, pe) with time >= lo (strict window).
+ * wj (mode 0): the prevailing quote — rightmost rank with time <= lo,
+ * i.e. (first rank with time > lo) - 1, clamped to ps. */
+ int64_t lb;
+ if (c->mode == 1) {
+ lb = ps; int64_t lbh = pe;
+ while (lb < lbh) {
+ int64_t m = (lb + lbh) >> 1;
+ if (rt_time_i[right_sort[m]] < lo) lb = m + 1; else lbh = m;
+ }
+ } else {
+ int64_t r = ps, rh = pe;
+ while (r < rh) {
+ int64_t m = (r + rh) >> 1;
+ if (rt_time_i[right_sort[m]] <= lo) r = m + 1; else rh = m;
}
- if (cmp == 0 && rt_time_i[ri] <= hi) cmp = -1;
- if (cmp < 0) ub = m + 1; else ub_hi = m;
+ lb = (r > ps) ? r - 1 : ps;
}
+ if (lb > ub) lb = ub; /* empty window / no partition → null result */
memset(acc, 0, sizeof(acc));
for (int64_t a = 0; a < n_agg; a++) {
@@ -11477,10 +11506,21 @@ static void wj_scan_fn(void* ctx_, uint32_t worker_id, int64_t start, int64_t en
}
}
-/* (window-join t1 t2 [eq-keys] time-col)
- * ASOF join: for each left row, find closest right row with time <= left.time
- * within the same equality partition. */
-ray_t* ray_window_join_fn(ray_t** args, int64_t n) {
+/* Window join (Rayforce convention):
+ * (window-join [eq-keys.. timeKey] intervals left right {agg})
+ * (window-join1 [eq-keys.. timeKey] intervals left right {agg})
+ * `intervals` is a 2-element list (lo_vec hi_vec) of parallel vectors, one
+ * entry per left row: the window for left row r is [lo_vec[r], hi_vec[r]].
+ *
+ * mode 0 (window-join / wj): each window is seeded with the PREVAILING quote
+ * — the rightmost quote with time <= lo[r] within the eq partition — then
+ * extended through the last quote with time <= hi[r].
+ * mode 1 (window-join1 / wj1): strict window — only quotes whose time falls in
+ * [lo[r], hi[r]].
+ *
+ * The legacy asof fall-through ((window-join L R [keys] time)) is mode-agnostic.
+ */
+static ray_t* window_join_impl(ray_t** args, int64_t n, int mode) {
if (n < 4) return ray_error("domain", NULL);
/* Special form: evaluate first 4 args, keep agg dict (args[4]) unevaluated */
@@ -11812,11 +11852,20 @@ ray_t* ray_window_join_fn(ray_t** args, int64_t n) {
for (int i = 0; i < 4; i++) ray_release(eargs[i]);
return ray_error("oom", NULL);
}
- for (int64_t lr = 0; lr < left_nrows; lr++) {
- int alloc_iv = 0;
- ray_t* iv = collection_elem(intervals, lr, &alloc_iv);
- if (!iv || RAY_IS_ERR(iv) || ray_len(iv) < 2) {
- if (alloc_iv && iv) ray_release(iv);
+ /* `intervals` is the v1 two-parallel-vector form: a 2-element list
+ * (lo_vec hi_vec), each a vector with one entry per left row. The
+ * window for left row r is [lo_vec[r], hi_vec[r]]. */
+ {
+ int alloc_lo_v = 0, alloc_hi_v = 0;
+ ray_t* lo_vec = (ray_len(intervals) >= 2)
+ ? collection_elem(intervals, 0, &alloc_lo_v) : NULL;
+ ray_t* hi_vec = (ray_len(intervals) >= 2)
+ ? collection_elem(intervals, 1, &alloc_hi_v) : NULL;
+ if (!lo_vec || !hi_vec || RAY_IS_ERR(lo_vec) || RAY_IS_ERR(hi_vec) ||
+ !ray_is_vec(lo_vec) || !ray_is_vec(hi_vec) ||
+ ray_len(lo_vec) != left_nrows || ray_len(hi_vec) != left_nrows) {
+ if (alloc_lo_v && lo_vec) ray_release(lo_vec);
+ if (alloc_hi_v && hi_vec) ray_release(hi_vec);
if (lo_hdr) scratch_free(lo_hdr);
if (hi_hdr) scratch_free(hi_hdr);
WJ_CLEANUP_TEMP();
@@ -11824,14 +11873,16 @@ ray_t* ray_window_join_fn(ray_t** args, int64_t n) {
for (int i = 0; i < 4; i++) ray_release(eargs[i]);
return ray_error("domain", NULL);
}
- int alloc_lo = 0, alloc_hi = 0;
- ray_t* lo_atom = collection_elem(iv, 0, &alloc_lo);
- ray_t* hi_atom = collection_elem(iv, 1, &alloc_hi);
- lo_arr[lr] = as_i64(lo_atom);
- hi_arr[lr] = as_i64(hi_atom);
- if (alloc_lo) ray_release(lo_atom);
- if (alloc_hi) ray_release(hi_atom);
- if (alloc_iv) ray_release(iv);
+ const void* lo_d = ray_data(lo_vec);
+ const void* hi_d = ray_data(hi_vec);
+ int8_t lo_t = lo_vec->type, hi_t = hi_vec->type;
+ uint8_t lo_a = lo_vec->attrs, hi_a = hi_vec->attrs;
+ for (int64_t lr = 0; lr < left_nrows; lr++) {
+ lo_arr[lr] = read_col_i64(lo_d, lr, lo_t, lo_a);
+ hi_arr[lr] = read_col_i64(hi_d, lr, hi_t, hi_a);
+ }
+ if (alloc_lo_v) ray_release(lo_vec);
+ if (alloc_hi_v) ray_release(hi_vec);
}
ray_t* left_eq_hdr[WJ_MAX_AGG] = {0};
@@ -11883,6 +11934,7 @@ ray_t* ray_window_join_fn(ray_t** args, int64_t n) {
wctx.right_nrows = right_nrows;
wctx.n_eq = n_eq;
wctx.n_agg = n_agg;
+ wctx.mode = mode;
wctx.lo_arr = lo_arr;
wctx.hi_arr = hi_arr;
wctx.right_sort = right_sort;
@@ -12005,6 +12057,16 @@ ray_t* ray_window_join_fn(ray_t** args, int64_t n) {
return result;
}
+/* window-join (wj): prevailing-quote-seeded window aggregation. */
+ray_t* ray_window_join_fn(ray_t** args, int64_t n) {
+ return window_join_impl(args, n, 0);
+}
+
+/* window-join1 (wj1): strict-window aggregation (no prevailing quote). */
+ray_t* ray_window_join1_fn(ray_t** args, int64_t n) {
+ return window_join_impl(args, n, 1);
+}
+
/* (asof-join [key1 key2 ... timeKey] leftTable rightTable)
* Last key is the time/asof column, rest are equality keys. The equality
* keys are OPTIONAL: a lone time key (asof-join [timeKey] L R) performs an
diff --git a/test/rfl/integration/joins.rfl b/test/rfl/integration/joins.rfl
index fbafe89a..1f6c962e 100644
--- a/test/rfl/integration/joins.rfl
+++ b/test/rfl/integration/joins.rfl
@@ -74,45 +74,45 @@
(sum (at ijmk 'val1)) -- 400
(sum (at ijmk 'val2)) -- 4000
;; window-join
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {minBid: (min Bid)}) 'minBid) -- [99 101]
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {minBid: (min Bid)}) 'minBid) -- [99 100]
;; window-join1
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(at (window-join1 [Sym Time] intervals trades quotes {minBid: (min Bid)}) 'minBid) -- [99 101]
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(at (window-join1 [Sym Time] intervals trades quotes {minBid: (min Bid)}) 'minBid) -- [99 101]
;; window-join with multiple aggregations — both columns must be present
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid Ask] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101] [110 111 112])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(set r (window-join [Sym Time] intervals trades quotes {lo: (min Bid) hi: (max Ask)}))(at r 'lo) -- [99 101]
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid Ask] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101] [110 111 112])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(set r (window-join [Sym Time] intervals trades quotes {lo: (min Bid) hi: (max Ask)}))(at r 'hi) -- [111 112]
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid Ask] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101] [110 111 112])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(set r (window-join [Sym Time] intervals trades quotes {lo: (min Bid) hi: (max Ask)}))(at r 'lo) -- [99 100]
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid Ask] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101] [110 111 112])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(set r (window-join [Sym Time] intervals trades quotes {lo: (min Bid) hi: (max Ask)}))(at r 'hi) -- [111 112]
;; window-join rayforce1 canonical example (docs/queries-joins.html)
;; trades at 12:00:01, 12:00:04, 12:00:06 ± 1s windows
;; quotes at 12:00:00..12:00:09 sizes [928 528 648 914 918 626 577 817 620 698]
;; trade @ 01 window [00,02] -> sizes [928 528 648], min=528, max=928
;; trade @ 04 window [03,05] -> sizes [914 918 626], min=626, max=918
;; trade @ 06 window [05,07] -> sizes [626 577 817], min=577, max=817
-(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {size_min: (min Size) size_max: (max Size)}) 'size_min) -- [528 626 577]
-(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {size_min: (min Size) size_max: (max Size)}) 'size_max) -- [928 918 817]
+(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {size_min: (min Size) size_max: (max Size)}) 'size_min) -- [528 626 577]
+(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {size_min: (min Size) size_max: (max Size)}) 'size_max) -- [928 918 817]
;; window-join sum/count/avg over the same canonical example
;; trade @ 01 window [00,02]: sum=2104 count=3 avg≈701.3333
;; trade @ 04 window [03,05]: sum=2458 count=3 avg≈819.3333
;; trade @ 06 window [05,07]: sum=2020 count=3 avg≈673.3333
-(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {s: (sum Size)}) 's) -- [2104 2458 2020]
-(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {c: (count Size)}) 'c) -- [3 3 3]
+(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {s: (sum Size)}) 's) -- [2104 2458 2020]
+(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {c: (count Size)}) 'c) -- [3 3 3]
;; window-join first/last over the canonical example
;; trade @ 01 window [00,02]: first=928 last=648
;; trade @ 04 window [03,05]: first=914 last=626
;; trade @ 06 window [05,07]: first=626 last=817
-(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {f: (first Size)}) 'f) -- [928 914 626]
-(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {l: (last Size)}) 'l) -- [648 626 817]
+(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {f: (first Size)}) 'f) -- [928 914 626]
+(set trades (table [Sym Time Price] (list [x x x] [12:00:01 12:00:04 12:00:06] [89.17 70.5 80.54])))(set quotes (table [Sym Time Size] (list [x x x x x x x x x x] [12:00:00 12:00:01 12:00:02 12:00:03 12:00:04 12:00:05 12:00:06 12:00:07 12:00:08 12:00:09] [928 528 648 914 918 626 577 817 620 698])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {l: (last Size)}) 'l) -- [648 626 817]
;; window-join raw bare-column form must accept non-numeric columns (regression)
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Tag] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [foo bar baz])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(count (at (window-join [Sym Time] intervals trades quotes {tags: Tag}) 'tags)) -- 2
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Tag] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [foo bar baz])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(count (at (window-join [Sym Time] intervals trades quotes {tags: Tag}) 'tags)) -- 2
;; window-join (count Col) must accept non-numeric source columns.
;; trades at 10:00:01 and 10:00:05 with ±1s windows; Tag rows at 10:00:00/02/04:
;; trade @ 01 window [00, 02] -> matches at 00 and 02 (2)
;; trade @ 05 window [04, 06] -> matches at 04 (1)
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Tag] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [foo bar baz])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {n: (count Tag)}) 'n) -- [2 1]
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Tag] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [foo bar baz])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {n: (count Tag)}) 'n) -- [2 1]
;; window-join COUNT must include window matches whose source value is null
;; ((count Col) is COUNT(*) semantics, not COUNT(non-null Col)).
;; trade @ 01 window [00, 02]: Bid rows at 00(99) and 02(NULL) -> count=2, min=99
;; trade @ 05 window [04, 06]: Bid row at 04(101) -> count=1, min=101
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 0Nl 101])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {c: (count Bid)}) 'c) -- [2 1]
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 0Nl 101])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {m: (min Bid)}) 'm) -- [99 101]
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 0Nl 101])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {c: (count Bid)}) 'c) -- [2 1]
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 0Nl 101])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {m: (min Bid)}) 'm) -- [99 101]
;; stddev/stddev_pop/var/var_pop must work as standalone aggregation
;; builtins so resolve_agg_opcode support in window-join is fully backed
;; by an eval-level implementation.
@@ -136,14 +136,14 @@
;; if the compare assumed 8-byte stride).
;; AAPL trade @ 01 window [00, 02]: AAPL quotes at 00, 02 -> min=100 max=201
;; MSFT trade @ 01 window [00, 02]: MSFT quotes at 00, 02 -> min=300 max=401
-(set trades (table [Sym Time Price] (list [AAPL MSFT] [10:00:01.000 10:00:01.000] [10 20])))(set quotes (table [Sym Time Bid Ask] (list [AAPL AAPL AAPL MSFT MSFT MSFT] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000 10:00:04.000] [100 101 102 300 301 302] [200 201 202 400 401 402])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {lo: (min Bid)}) 'lo) -- [100 300]
-(set trades (table [Sym Time Price] (list [AAPL MSFT] [10:00:01.000 10:00:01.000] [10 20])))(set quotes (table [Sym Time Bid Ask] (list [AAPL AAPL AAPL MSFT MSFT MSFT] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000 10:00:04.000] [100 101 102 300 301 302] [200 201 202 400 401 402])))(set intervals (map-right + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {hi: (max Ask)}) 'hi) -- [201 401]
+(set trades (table [Sym Time Price] (list [AAPL MSFT] [10:00:01.000 10:00:01.000] [10 20])))(set quotes (table [Sym Time Bid Ask] (list [AAPL AAPL AAPL MSFT MSFT MSFT] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000 10:00:04.000] [100 101 102 300 301 302] [200 201 202 400 401 402])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {lo: (min Bid)}) 'lo) -- [100 300]
+(set trades (table [Sym Time Price] (list [AAPL MSFT] [10:00:01.000 10:00:01.000] [10 20])))(set quotes (table [Sym Time Bid Ask] (list [AAPL AAPL AAPL MSFT MSFT MSFT] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000 10:00:04.000] [100 101 102 300 301 302] [200 201 202 400 401 402])))(set intervals (map-left + [-1000 1000] (at trades 'Time)))(at (window-join [Sym Time] intervals trades quotes {hi: (max Ask)}) 'hi) -- [201 401]
;; window-join with raw column (TYPE_MAPGROUP)
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(count (at (window-join [Sym Time] intervals trades quotes {bids: Bid}) 'bids)) -- 2
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(count (at (window-join [Sym Time] intervals trades quotes {bids: Bid}) 'bids)) -- 2
;; window-join1 with raw column (TYPE_MAPGROUP)
-(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-right + [-2000 2000] (at trades 'Time)))(count (at (window-join1 [Sym Time] intervals trades quotes {bids: Bid}) 'bids)) -- 2
+(set trades (table [Sym Time Price] (list [a a] [10:00:01.000 10:00:05.000] [100 200])))(set quotes (table [Sym Time Bid] (list [a a a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))(set intervals (map-left + [-2000 2000] (at trades 'Time)))(count (at (window-join1 [Sym Time] intervals trades quotes {bids: Bid}) 'bids)) -- 2
;; window-join with Enum columns (xasc converts Enum to Symbol)
-(set trades (table [s time price] (list ['a 'a 'b] [10:00:01.000 10:00:05.000 10:00:03.000] [100 200 150])))(set quotes (table [s time bid] (list ['a 'a 'a 'b 'b] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:01.000 10:00:04.000] [99 100 101 149 151])))(set intervals (map-right + [-2000 2000] (at trades 'time)))(at (window-join [s time] intervals trades quotes {minBid: (min bid)}) 'minBid) -- [99 101 149]
+(set trades (table [s time price] (list ['a 'a 'b] [10:00:01.000 10:00:05.000 10:00:03.000] [100 200 150])))(set quotes (table [s time bid] (list ['a 'a 'a 'b 'b] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:01.000 10:00:04.000] [99 100 101 149 151])))(set intervals (map-left + [-2000 2000] (at trades 'time)))(at (window-join [s time] intervals trades quotes {minBid: (min bid)}) 'minBid) -- [99 100 149]
;; empty left table
(set t1 (table [id val1] (list (take [1] 0) (take [1] 0))))(set t2 (table [id val2] (list [1 2 3] [100 200 300])))(count (left-join [id] t1 t2)) -- 0
;; empty right table — left-join keeps all 3 left rows with val2 = Null.
diff --git a/test/rfl/io/dump.rfl b/test/rfl/io/dump.rfl
index 714df328..cea39c54 100644
--- a/test/rfl/io/dump.rfl
+++ b/test/rfl/io/dump.rfl
@@ -85,8 +85,10 @@
;; window-join + window-join1 both compile to OP_WINDOW_JOIN.
(set tr (table [s tm p] (list ['a 'a] [10:00:01.000 10:00:05.000] [100 200])))
(set qt (table [s tm b] (list ['a 'a 'a] [10:00:00.000 10:00:02.000 10:00:04.000] [99 100 101])))
-(set iv (map-right + [-2000 2000] (at tr 'tm)))
-(at (window-join [s tm] iv tr qt {mb: (min b)}) 'mb) -- [99 101]
+(set iv (map-left + [-2000 2000] (at tr 'tm)))
+;; wj seeds the prevailing quote (row1 picks up the @02 quote at b=100);
+;; wj1 is strict so row1 sees only the @04 quote (b=101).
+(at (window-join [s tm] iv tr qt {mb: (min b)}) 'mb) -- [99 100]
(at (window-join1 [s tm] iv tr qt {mb: (min b)}) 'mb) -- [99 101]
;; ──────────── OP_PIVOT (heavy) ────────────
diff --git a/test/rfl/ops/exec_coverage.rfl b/test/rfl/ops/exec_coverage.rfl
index 3c95ff24..a3bdea9d 100644
--- a/test/rfl/ops/exec_coverage.rfl
+++ b/test/rfl/ops/exec_coverage.rfl
@@ -122,7 +122,7 @@
;; ====================================================================
(set tr (table [Sym Time Price] (list ['a 'a 'a 'b] [10:00:01.000 10:00:05.000 10:00:09.000 10:00:01.000] [100 200 300 400])))
(set qu (table [Sym Time Bid] (list ['a 'a 'a 'b 'b] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000 10:00:02.000] [99 100 101 199 200])))
-(set iv (map-right + [-2000 2000] (at tr 'Time)))
+(set iv (map-left + [-2000 2000] (at tr 'Time)))
(count (at (window-join [Sym Time] iv tr qu {m: (min Bid)}) 'm)) -- 4
;; ====================================================================
diff --git a/test/rfl/ops/opt_advanced.rfl b/test/rfl/ops/opt_advanced.rfl
index cc5183df..1116ff9b 100644
--- a/test/rfl/ops/opt_advanced.rfl
+++ b/test/rfl/ops/opt_advanced.rfl
@@ -295,7 +295,7 @@
;; Every TR1 row gets at least one matching TQ1 row in its window.
(set TR1 (table [Sym Time Price] (list ['a 'a 'b] [10:00:01.000 10:00:05.000 10:00:01.000] [100 200 400])))
(set TQ1 (table [Sym Time Bid] (list ['a 'a 'a 'b] [10:00:00.000 10:00:02.000 10:00:04.000 10:00:00.000] [99 100 101 199])))
-(set IV (map-right + [-2000 2000] (at TR1 'Time)))
+(set IV (map-left + [-2000 2000] (at TR1 'Time)))
(count (at (window-join [Sym Time] IV TR1 TQ1 {m: (min Bid)}) 'm)) -- 3
;; ====================================================================
diff --git a/test/rfl/ops/opt_branch_cov.rfl b/test/rfl/ops/opt_branch_cov.rfl
index 30ae65d3..5eba1b19 100644
--- a/test/rfl/ops/opt_branch_cov.rfl
+++ b/test/rfl/ops/opt_branch_cov.rfl
@@ -378,7 +378,7 @@
;; ════════════════════════════════════════════════════════════════════════
(set TWJ_T (table [Sym Time Price] (list ['a 'a 'b 'b] [10:00:01.000 10:00:05.000 10:00:01.000 10:00:05.000] [100 200 400 500])))
(set TWJ_Q (table [Sym Time Bid] (list ['a 'a 'b 'b] [10:00:00.000 10:00:04.000 10:00:00.000 10:00:04.000] [99 101 399 501])))
-(set TWJ_IV (map-right + [-2000 2000] (at TWJ_T 'Time)))
+(set TWJ_IV (map-left + [-2000 2000] (at TWJ_T 'Time)))
(count (at (window-join [Sym Time] TWJ_IV TWJ_T TWJ_Q {m: (min Bid)}) 'm)) -- 4
;; ════════════════════════════════════════════════════════════════════════
diff --git a/test/rfl/ops/query_coverage.rfl b/test/rfl/ops/query_coverage.rfl
index c46d20aa..4a4776fa 100644
--- a/test/rfl/ops/query_coverage.rfl
+++ b/test/rfl/ops/query_coverage.rfl
@@ -560,30 +560,32 @@
(set wjl (table [Sym Time] (list ['a 'a] [10:00:01.000 10:00:05.000])))
(set wjr (table [Sym Time Price] (list ['a 'a 'a] [10:00:00.000 10:00:02.000 10:00:04.000] (as 'F64 [99.5 100.5 101.5]))))
-(set wjiv (map-right + [-2000 2000] (at wjl 'Time)))
-;; F64 sum: row0 interval [09:59:59,10:00:03] → prices 99.5+100.5=200.0
-;; row1 interval [10:00:03,10:00:07] → price 101.5 only
-(at (window-join [Sym Time] wjiv wjl wjr {total: (sum Price)}) 'total) -- [200.0 101.5]
+(set wjiv (map-left + [-2000 2000] (at wjl 'Time)))
+;; wj seeds the prevailing quote, so row1's window [10:00:03,10:00:07]
+;; includes the @02 quote (100.5) at/before lo plus the @04 quote (101.5).
+;; F64 sum: row0 [09:59:59,10:00:03] → 99.5+100.5=200.0
+;; row1 [10:00:03,10:00:07] → 100.5+101.5=202.0
+(at (window-join [Sym Time] wjiv wjl wjr {total: (sum Price)}) 'total) -- [200.0 202.0]
;; F64 avg aggregation in window-join → hits sorted_f OP_AVG arm.
-;; row0: avg(99.5,100.5)=100.0; row1: avg(101.5)=101.5
-(at (window-join [Sym Time] wjiv wjl wjr {avg_p: (avg Price)}) 'avg_p) -- [100.0 101.5]
+;; row0: avg(99.5,100.5)=100.0; row1: avg(100.5,101.5)=101.0
+(at (window-join [Sym Time] wjiv wjl wjr {avg_p: (avg Price)}) 'avg_p) -- [100.0 101.0]
;; F64 min aggregation → sorted_f OP_MIN arm.
-;; row0: min(99.5,100.5)=99.5; row1: min(101.5)=101.5
-(at (window-join [Sym Time] wjiv wjl wjr {lo: (min Price)}) 'lo) -- [99.5 101.5]
+;; row0: min(99.5,100.5)=99.5; row1: min(100.5,101.5)=100.5
+(at (window-join [Sym Time] wjiv wjl wjr {lo: (min Price)}) 'lo) -- [99.5 100.5]
;; F64 max aggregation → sorted_f OP_MAX arm.
-;; row0: max(99.5,100.5)=100.5; row1: max(101.5)=101.5
+;; row0: max(99.5,100.5)=100.5; row1: max(100.5,101.5)=101.5
(at (window-join [Sym Time] wjiv wjl wjr {hi: (max Price)}) 'hi) -- [100.5 101.5]
;; F64 prod aggregation → sorted_f OP_PROD arm.
-;; row0: prod(99.5,100.5)=10000.0 approx (99.5*100.5=9999.75); row1: prod(101.5)=101.5
-(at (window-join [Sym Time] wjiv wjl wjr {pr: (prod Price)}) 'pr) -- [9999.75 101.5]
+;; row0: prod(99.5,100.5)=9999.75; row1: prod(100.5,101.5)=10200.75
+(at (window-join [Sym Time] wjiv wjl wjr {pr: (prod Price)}) 'pr) -- [9999.75 10200.75]
;; F64 var/stddev aggregation → sorted_f OP_VAR/OP_STDDEV arm.
-;; row0: var(99.5,100.5) = sample var = 0.5 (2 values)
-;; row1: var([101.5]) = null (undefined for n=1 sample var)
+;; row0: var(99.5,100.5) sample var over 2 values
+;; row1: var(100.5,101.5) sample var over 2 values (prevailing-seeded)
(count (window-join [Sym Time] wjiv wjl wjr {v: (var Price)})) -- 2
;; ====================================================================
@@ -633,8 +635,10 @@
(set wjl2 (table [Sym Time] (list ['a 'a] [10:00:01.000 10:00:05.000])))
(set wjr2 (table [Sym Time Price] (list ['a 'a 'a] [10:00:00.000 10:00:02.000 10:00:04.000] (as 'F64 [99.5 100.5 101.5]))))
-(set wjiv2 (map-right + [-2000 2000] (at wjl2 'Time)))
-(at (window-join [Sym Time] wjiv2 wjl2 wjr2 {f: (first Price)}) 'f) -- [99.5 101.5]
+(set wjiv2 (map-left + [-2000 2000] (at wjl2 'Time)))
+;; wj prevailing seed: row1 window [10:00:03,10:00:07] starts at the @02
+;; quote (100.5), so first=100.5 (last still the @04 quote, 101.5).
+(at (window-join [Sym Time] wjiv2 wjl2 wjr2 {f: (first Price)}) 'f) -- [99.5 100.5]
(at (window-join [Sym Time] wjiv2 wjl2 wjr2 {l: (last Price)}) 'l) -- [100.5 101.5]
;; window-join integer VAR/PROD/STDDEV — sorted_i OP_VAR/OP_PROD/OP_STDDEV.
diff --git a/test/rfl/query/query_branch_cov.rfl b/test/rfl/query/query_branch_cov.rfl
index 1e9add05..a218641d 100644
--- a/test/rfl/query/query_branch_cov.rfl
+++ b/test/rfl/query/query_branch_cov.rfl
@@ -828,28 +828,29 @@
(set wjR (table [Sym Time Bid] (list ['x 'x 'y 'y] [10:00:00.500 10:00:02.500 10:00:01.500 10:00:02.500] [99.0 101.0 199.0 201.0])))
;; intervals = per-left-row [lo hi] time window (in nanoseconds offset
;; via map-left + to each left Time).
-(set wjIv (map-right + [-2000 2000] (at wjL 'Time)))
+(set wjIv (map-left + [-2000 2000] (at wjL 'Time)))
;; Window [-2000ns, +2000ns] around each left Time:
;; (x, 10:00:01) ∩ window 09:59:59..10:00:03 — x-bids @00.5, @02.5 → {99, 101}
;; (y, 10:00:02) ∩ window 10:00:00..10:00:04 — y-bids @01.5, @02.5 → {199, 201}
-;; (x, 10:00:03) ∩ window 10:00:01..10:00:05 — x-bid @02.5 → {101}
+;; (x, 10:00:03) ∩ window 10:00:01..10:00:05 — x-bid @02.5, plus the
+;; prevailing x-bid @00.5 (wj seeds the last quote at/before lo) → {99, 101}
;; agg dict with min agg.
(set wjMin (window-join [Sym Time] wjIv wjL wjR {mb: (min Bid)}))
(count wjMin) -- 3
-(at wjMin 'mb) -- [99.0 199.0 101.0]
+(at wjMin 'mb) -- [99.0 199.0 99.0]
;; agg dict with multiple aggs.
(set wjMx (window-join [Sym Time] wjIv wjL wjR {mb: (min Bid) xb: (max Bid)}))
(count wjMx) -- 3
-(at wjMx 'mb) -- [99.0 199.0 101.0]
+(at wjMx 'mb) -- [99.0 199.0 99.0]
(at wjMx 'xb) -- [101.0 201.0 101.0]
;; agg dict with avg.
(set wjAvg (window-join [Sym Time] wjIv wjL wjR {av: (avg Bid)}))
(count wjAvg) -- 3
-(at wjAvg 'av) -- [100.0 200.0 101.0]
+(at wjAvg 'av) -- [100.0 200.0 100.0]
;; agg dict with count (no source read).
(set wjCnt (window-join [Sym Time] wjIv wjL wjR {n: (count Bid)}))
(count wjCnt) -- 3
-(at wjCnt 'n) -- [2 2 1]
+(at wjCnt 'n) -- [2 2 2]
;; arity error.
(window-join [Sym Time] wjL wjR) !- domain
diff --git a/test/rfl/query/query_update_coverage.rfl b/test/rfl/query/query_update_coverage.rfl
index 0c82e74e..aabfaba2 100644
--- a/test/rfl/query/query_update_coverage.rfl
+++ b/test/rfl/query/query_update_coverage.rfl
@@ -360,7 +360,7 @@
;; ────────────────────────────────────────────────────────────────────
(set wjt_null (table [Sym Time] (list ['a] [10:00:03.000])))
(set wjq_f64null (table [Sym Time Price] (list ['a 'a 'a 'a] [10:00:00.000 10:00:01.000 10:00:02.000 10:00:04.000] [0Nf 2.0 0Nf 4.0])))
-(set wji_null (map-right + [-3000 3000] (at wjt_null 'Time)))
+(set wji_null (map-left + [-3000 3000] (at wjt_null 'Time)))
;; F64 null sum (line 10059): nn != NULL → sum skips nulls → 2+4=6
(at (window-join [Sym Time] wji_null wjt_null wjq_f64null {s: (sum Price)}) 's) -- [6.0]
@@ -433,7 +433,7 @@
;; ────────────────────────────────────────────────────────────────────
(set wjt_i32 (table [Sym Time] (list ['a] [10:00:01.000])))
(set wjq_i32 (table [Sym Time Price] (list ['a 'a] [10:00:00.000 10:00:02.000] (as 'I32 [10 20]))))
-(set wji_i32 (map-right + [-2000 2000] (at wjt_i32 'Time)))
+(set wji_i32 (map-left + [-2000 2000] (at wjt_i32 'Time)))
;; I32 result type (lines 10273-10274): first/max on I32 col → I32 output
;; Element at [0] of result is I32 atom 10i (first Price = 10i, max Price = 20i)
@@ -447,7 +447,7 @@
;; I64 null var/stddev (lines 10140-10141): null in I64 col + var → nn != NULL
(set wjt_nullv (table [Sym Time] (list ['a] [10:00:03.000])))
(set wjq_i64nv (table [Sym Time Price] (list ['a 'a 'a 'a] [10:00:00.000 10:00:01.000 10:00:02.000 10:00:04.000] [0Nl 2 0Nl 4])))
-(set wji_nullv (map-right + [-3000 3000] (at wjt_nullv 'Time)))
+(set wji_nullv (map-left + [-3000 3000] (at wjt_nullv 'Time)))
;; var of non-null [2, 4] = 2.0 (sample variance)
(at (window-join [Sym Time] wji_nullv wjt_nullv wjq_i64nv {v: (var Price)}) 'v) -- [2.0]
;; stddev of non-null [2, 4] = sqrt(2.0) ≈ 1.41