Skip to main content

shape_jit/ffi/
window.rs

1// Heap allocation audit (PR-9 V8 Gap Closure):
2//   Category A (NaN-boxed returns): 0 sites
3//   Category B (intermediate/consumed): 0 sites
4//   Category C (heap islands): 0 sites
5//     (Window functions only operate on NaN-boxed numbers, no heap allocations)
6//!
7//! Window Function FFI for JIT
8//!
9//! External C functions for SQL-style window functions called from JIT-compiled code.
10//! Window functions require partition and ordering context, so these FFI functions
11//! operate on pre-computed partition data passed from the query executor.
12
13use super::super::nan_boxing::*;
14
15// ============================================================================
16// Ranking Window Functions
17// ============================================================================
18
19/// ROW_NUMBER() - Returns sequential row number within partition
20/// Args: current_idx (0-based position in partition)
21#[unsafe(no_mangle)]
22pub extern "C" fn jit_window_row_number(current_idx: u64) -> u64 {
23    let idx = if is_number(current_idx) {
24        unbox_number(current_idx) as usize
25    } else {
26        return box_number(f64::NAN);
27    };
28    // ROW_NUMBER is 1-based
29    box_number((idx + 1) as f64)
30}
31
32/// RANK() - Returns rank with gaps for ties
33/// Args: current_idx, partition_size
34/// Note: Simplified - assumes ORDER BY already applied and no ties
35#[unsafe(no_mangle)]
36pub extern "C" fn jit_window_rank(current_idx: u64, _partition_size: u64) -> u64 {
37    let idx = if is_number(current_idx) {
38        unbox_number(current_idx) as usize
39    } else {
40        return box_number(f64::NAN);
41    };
42    // Simplified: each row gets sequential rank (no tie detection)
43    box_number((idx + 1) as f64)
44}
45
46/// DENSE_RANK() - Returns rank without gaps for ties
47/// Args: current_idx, partition_size
48/// Note: Simplified - assumes ORDER BY already applied and no ties
49#[unsafe(no_mangle)]
50pub extern "C" fn jit_window_dense_rank(current_idx: u64, _partition_size: u64) -> u64 {
51    let idx = if is_number(current_idx) {
52        unbox_number(current_idx) as usize
53    } else {
54        return box_number(f64::NAN);
55    };
56    // Simplified: each row gets sequential rank (no tie detection)
57    box_number((idx + 1) as f64)
58}
59
60/// NTILE(n) - Distributes rows into n buckets
61/// Args: current_idx, partition_size, num_buckets
62#[unsafe(no_mangle)]
63pub extern "C" fn jit_window_ntile(current_idx: u64, partition_size: u64, num_buckets: u64) -> u64 {
64    let idx = if is_number(current_idx) {
65        unbox_number(current_idx) as usize
66    } else {
67        return box_number(f64::NAN);
68    };
69    let size = if is_number(partition_size) {
70        unbox_number(partition_size) as usize
71    } else {
72        return box_number(f64::NAN);
73    };
74    let n = if is_number(num_buckets) {
75        unbox_number(num_buckets) as usize
76    } else {
77        return box_number(f64::NAN);
78    };
79
80    if size == 0 || n == 0 {
81        return box_number(1.0);
82    }
83
84    let bucket = (idx * n / size) + 1;
85    box_number(bucket as f64)
86}
87
88// ============================================================================
89// Navigation Window Functions
90// ============================================================================
91
92/// LAG(value, offset) - Returns value from offset rows before current
93/// Args: current_value, offset, default_value, is_valid (1 if in bounds, 0 if out)
94#[unsafe(no_mangle)]
95pub extern "C" fn jit_window_lag(
96    current_value: u64,
97    _offset: u64,
98    default_value: u64,
99    is_valid: u64,
100) -> u64 {
101    let valid = if is_number(is_valid) {
102        unbox_number(is_valid) as i32
103    } else {
104        0
105    };
106
107    if valid != 0 {
108        current_value
109    } else {
110        default_value
111    }
112}
113
114/// LEAD(value, offset) - Returns value from offset rows after current
115/// Args: current_value, offset, default_value, is_valid (1 if in bounds, 0 if out)
116#[unsafe(no_mangle)]
117pub extern "C" fn jit_window_lead(
118    current_value: u64,
119    _offset: u64,
120    default_value: u64,
121    is_valid: u64,
122) -> u64 {
123    let valid = if is_number(is_valid) {
124        unbox_number(is_valid) as i32
125    } else {
126        0
127    };
128
129    if valid != 0 {
130        current_value
131    } else {
132        default_value
133    }
134}
135
136/// FIRST_VALUE(expr) - Returns first value in the window frame
137#[unsafe(no_mangle)]
138pub extern "C" fn jit_window_first_value(first_value: u64) -> u64 {
139    first_value
140}
141
142/// LAST_VALUE(expr) - Returns last value in the window frame
143#[unsafe(no_mangle)]
144pub extern "C" fn jit_window_last_value(last_value: u64) -> u64 {
145    last_value
146}
147
148/// NTH_VALUE(expr, n) - Returns nth value in the window frame
149/// Args: nth_value, is_valid (1 if nth exists, 0 if not)
150#[unsafe(no_mangle)]
151pub extern "C" fn jit_window_nth_value(nth_value: u64, is_valid: u64) -> u64 {
152    let valid = if is_number(is_valid) {
153        unbox_number(is_valid) as i32
154    } else {
155        0
156    };
157
158    if valid != 0 { nth_value } else { TAG_NULL }
159}
160
161// ============================================================================
162// Aggregate Window Functions
163// ============================================================================
164
165/// SUM() over window frame
166/// Args: running_sum (pre-computed sum over frame)
167#[unsafe(no_mangle)]
168pub extern "C" fn jit_window_sum(running_sum: u64) -> u64 {
169    running_sum
170}
171
172/// AVG() over window frame
173/// Args: running_sum, count
174#[unsafe(no_mangle)]
175pub extern "C" fn jit_window_avg(running_sum: u64, count: u64) -> u64 {
176    let sum = if is_number(running_sum) {
177        unbox_number(running_sum)
178    } else {
179        return box_number(f64::NAN);
180    };
181    let cnt = if is_number(count) {
182        unbox_number(count)
183    } else {
184        return box_number(f64::NAN);
185    };
186
187    if cnt == 0.0 {
188        return TAG_NULL;
189    }
190
191    box_number(sum / cnt)
192}
193
194/// MIN() over window frame
195/// Args: running_min (pre-computed min over frame)
196#[unsafe(no_mangle)]
197pub extern "C" fn jit_window_min(running_min: u64) -> u64 {
198    running_min
199}
200
201/// MAX() over window frame
202/// Args: running_max (pre-computed max over frame)
203#[unsafe(no_mangle)]
204pub extern "C" fn jit_window_max(running_max: u64) -> u64 {
205    running_max
206}
207
208/// COUNT() over window frame
209/// Args: count (pre-computed count over frame)
210#[unsafe(no_mangle)]
211pub extern "C" fn jit_window_count(count: u64) -> u64 {
212    count
213}
214
215// ============================================================================
216// Window Frame Helpers
217// ============================================================================
218
219/// Calculate frame start index
220/// Args: current_idx, preceding (n preceding or -1 for unbounded)
221#[unsafe(no_mangle)]
222pub extern "C" fn jit_window_frame_start(current_idx: u64, preceding: u64) -> u64 {
223    let idx = if is_number(current_idx) {
224        unbox_number(current_idx) as i64
225    } else {
226        return box_number(0.0);
227    };
228    let prec = if is_number(preceding) {
229        unbox_number(preceding) as i64
230    } else {
231        return box_number(0.0);
232    };
233
234    if prec < 0 {
235        // Unbounded preceding
236        box_number(0.0)
237    } else {
238        box_number((idx - prec).max(0) as f64)
239    }
240}
241
242/// Calculate frame end index
243/// Args: current_idx, following (n following or -1 for unbounded), partition_size
244#[unsafe(no_mangle)]
245pub extern "C" fn jit_window_frame_end(
246    current_idx: u64,
247    following: u64,
248    partition_size: u64,
249) -> u64 {
250    let idx = if is_number(current_idx) {
251        unbox_number(current_idx) as i64
252    } else {
253        return box_number(0.0);
254    };
255    let foll = if is_number(following) {
256        unbox_number(following) as i64
257    } else {
258        return box_number(0.0);
259    };
260    let size = if is_number(partition_size) {
261        unbox_number(partition_size) as i64
262    } else {
263        return box_number(0.0);
264    };
265
266    if foll < 0 {
267        // Unbounded following
268        box_number((size - 1).max(0) as f64)
269    } else {
270        box_number((idx + foll).min(size - 1).max(0) as f64)
271    }
272}