Skip to main content

linkrs_query/executor/streaming/operators/blocking/
window_operator.rs

1use std::collections::BTreeMap;
2
3use linkrs_core::error::QueryError;
4use linkrs_core::types::expr::Expression;
5use linkrs_core::value::NullType;
6use linkrs_core::Value;
7
8use crate::executor::base::{MemoryBudget, MemoryTracker};
9use crate::executor::expression::evaluator::ExpressionEvaluator;
10use crate::executor::streaming::chunk::DataChunk;
11use crate::executor::streaming::executor::{SortDirection, StreamingExecutor, ValueRowContext};
12use crate::executor::streaming::spill::{
13    finalize_partitions_with_runtime, HashPartitionConfig, HashPartitionSpiller, SpillManager,
14};
15
16use super::helpers::{emit_batch_slice, BlockingContext};
17use super::window::{
18    compute_window_partition_result, sort_partition_rows, WindowFunctionState, WindowState,
19};
20
21pub(super) fn open_window_function(state: &mut Option<WindowFunctionState>) {
22    *state = Some(WindowFunctionState {
23        batch: linkrs_core::columnar::MaterializedBatch::new(0, 0),
24        col_names: vec![],
25        result_batch: linkrs_core::columnar::MaterializedBatch::new(0, 0),
26        emitted_offset: 0,
27        partition_spiller: None,
28        spilled_runs: vec![],
29        current_partition: 0,
30        has_spilled: false,
31        output_complete: false,
32    });
33}
34
35pub(super) fn open_window(state: &mut Option<WindowState>) {
36    *state = Some(WindowState {
37        batch: linkrs_core::columnar::MaterializedBatch::new(0, 0),
38        col_names: vec![],
39        result_batch: linkrs_core::columnar::MaterializedBatch::new(0, 0),
40        emitted_offset: 0,
41        partition_spiller: None,
42        spilled_runs: vec![],
43        current_partition: 0,
44        has_spilled: false,
45        output_complete: false,
46    });
47}
48
49#[allow(clippy::too_many_arguments)]
50pub(super) fn next_window_function(
51    window_exprs: &[Expression],
52    partition_by_exprs: &[Expression],
53    order_by_exprs: &[Expression],
54    order_by_directions: &[SortDirection],
55    memory_tracker: &mut MemoryTracker,
56    state: &mut WindowFunctionState,
57    ctx: &BlockingContext<'_>,
58    input: &mut StreamingExecutor,
59) -> Result<Option<DataChunk>, QueryError> {
60    use crate::executor::streaming::spill::RunReader;
61
62    let eval_partition_key = |row: &[Value], col_names: &[String]| -> Vec<Value> {
63        if partition_by_exprs.is_empty() {
64            return vec![Value::Null(NullType::Null)];
65        }
66        let mut key = Vec::with_capacity(partition_by_exprs.len());
67        for expr in partition_by_exprs.iter() {
68            let mut ctx = ValueRowContext::from_names(row.to_vec(), col_names.to_vec());
69            key.push(
70                ExpressionEvaluator::evaluate(expr, &mut ctx)
71                    .unwrap_or(Value::Null(NullType::Null)),
72            );
73        }
74        key
75    };
76
77    loop {
78        if state.output_complete {
79            return Ok(None);
80        }
81
82        // Output phase: serve slices from the result batch.
83        if state.emitted_offset < state.result_batch.num_rows() {
84            return Ok(Some(emit_batch_slice(
85                &state.result_batch,
86                &mut state.emitted_offset,
87                ctx,
88            )));
89        }
90        if state.emitted_offset > 0 {
91            state.emitted_offset = 0;
92            state.result_batch.clear();
93            if !state.has_spilled || state.current_partition >= state.spilled_runs.len() {
94                state.output_complete = true;
95                return Ok(None);
96            }
97        }
98
99        // Replay phase
100        if state.has_spilled && state.partition_spiller.is_none() {
101            while state.current_partition < state.spilled_runs.len() {
102                if let Some(rt) = ctx.runtime.as_ref() {
103                    rt.ensure_not_cancelled()?;
104                }
105
106                let run = match &state.spilled_runs[state.current_partition] {
107                    Some(r) => r,
108                    None => {
109                        state.current_partition += 1;
110                        continue;
111                    }
112                };
113
114                let mut reader = RunReader::open(run)?;
115                let mut partitions: BTreeMap<Vec<Value>, Vec<(usize, Vec<Value>)>> =
116                    BTreeMap::new();
117                let mut run_row_count: u64 = 0;
118                while let Some(row) = reader.read_row()? {
119                    memory_tracker.try_reserve_row(&row)?;
120                    let partition_key = eval_partition_key(&row, &state.col_names);
121                    partitions
122                        .entry(partition_key)
123                        .or_default()
124                        .push((run_row_count as usize, row));
125                    run_row_count += 1;
126                }
127
128                let mut result_rows = Vec::with_capacity(run_row_count as usize);
129                for (_key, mut partition_rows) in partitions {
130                    sort_partition_rows(
131                        &mut partition_rows,
132                        &state.col_names,
133                        order_by_exprs,
134                        order_by_directions,
135                    );
136                    result_rows.extend(compute_window_partition_result(
137                        &partition_rows,
138                        &state.col_names,
139                        window_exprs,
140                    ));
141                }
142
143                let _ = std::fs::remove_file(&run.path);
144                state.current_partition += 1;
145                memory_tracker.reset();
146
147                if !result_rows.is_empty() {
148                    state.result_batch.clear();
149                    state.emitted_offset = 0;
150                    for result_row in result_rows {
151                        state.result_batch.append_row(result_row);
152                    }
153                    return Ok(Some(emit_batch_slice(
154                        &state.result_batch,
155                        &mut state.emitted_offset,
156                        ctx,
157                    )));
158                }
159            }
160            state.output_complete = true;
161            return Ok(None);
162        }
163
164        // Accumulation phase
165        let mut accumulating = true;
166        while accumulating {
167            match input.advance()? {
168                Some(mut chunk) => {
169                    chunk.normalize_for_opaque("WindowFunction");
170                    if let Some(rt) = ctx.runtime.as_ref() {
171                        rt.ensure_not_cancelled()?;
172                    }
173                    if state.col_names.is_empty() {
174                        state.col_names = chunk.col_names();
175                    }
176                    for row in chunk.rows {
177                        if let Some(ref mut spiller) = state.partition_spiller {
178                            let manager = ctx
179                                .runtime
180                                .as_ref()
181                                .and_then(|rt| rt.get_spill_manager())
182                                .ok_or_else(|| {
183                                    QueryError::execution("Spill manager not available".to_string())
184                                })?;
185                            let partition_key = eval_partition_key(&row, &state.col_names);
186                            let p = crate::executor::streaming::spill::hash_row_partition(
187                                &partition_key,
188                                spiller.num_partitions(),
189                            ) as usize;
190                            spiller.insert_row_to_partition(&row, p, &manager)?;
191                            continue;
192                        }
193                        if let Err(e) = memory_tracker.try_reserve_row(&row) {
194                            if let Some(sm) =
195                                ctx.runtime.as_ref().and_then(|rt| rt.get_spill_manager())
196                            {
197                                let config = HashPartitionConfig::default();
198                                let num_partitions = config.num_partitions;
199                                let mut spiller = HashPartitionSpiller::new(config, &sm, 0)?;
200
201                                for pending in state.batch.into_rows() {
202                                    let partition_key =
203                                        eval_partition_key(&pending, &state.col_names);
204                                    let p = crate::executor::streaming::spill::hash_row_partition(
205                                        &partition_key,
206                                        num_partitions,
207                                    ) as usize;
208                                    spiller.insert_row_to_partition(&pending, p, &sm)?;
209                                    memory_tracker
210                                        .release(MemoryBudget::estimate_row_memory(&pending));
211                                }
212
213                                let partition_key = eval_partition_key(&row, &state.col_names);
214                                let p = crate::executor::streaming::spill::hash_row_partition(
215                                    &partition_key,
216                                    num_partitions,
217                                ) as usize;
218                                spiller.insert_row_to_partition(&row, p, &sm)?;
219
220                                state.partition_spiller = Some(spiller);
221                                state.has_spilled = true;
222                                continue;
223                            } else {
224                                return Err(e);
225                            }
226                        }
227                        state.batch.append_row(row);
228                    }
229                }
230                None => {
231                    accumulating = false;
232                }
233            }
234        }
235
236        // Finalize spilled runs
237        if state.partition_spiller.is_some() {
238            let runs = finalize_partitions_with_runtime(
239                state.partition_spiller.take().unwrap(),
240                ctx.runtime.as_ref(),
241            )?;
242            state.spilled_runs = runs;
243            state.current_partition = 0;
244            continue;
245        }
246
247        // In-memory output
248        if state.batch.is_empty() {
249            state.output_complete = true;
250            return Ok(None);
251        }
252        let mut partitions: BTreeMap<Vec<Value>, Vec<(usize, Vec<Value>)>> = BTreeMap::new();
253        for (idx, row) in state.batch.into_rows().into_iter().enumerate() {
254            let partition_key = eval_partition_key(&row, &state.col_names);
255            partitions
256                .entry(partition_key)
257                .or_default()
258                .push((idx, row));
259        }
260        let mut result_rows = Vec::new();
261        for (_key, mut partition_rows) in partitions {
262            sort_partition_rows(
263                &mut partition_rows,
264                &state.col_names,
265                order_by_exprs,
266                order_by_directions,
267            );
268            result_rows.extend(compute_window_partition_result(
269                &partition_rows,
270                &state.col_names,
271                window_exprs,
272            ));
273        }
274
275        state.result_batch.clear();
276        state.emitted_offset = 0;
277        for result_row in result_rows {
278            state.result_batch.append_row(result_row);
279        }
280        if state.result_batch.is_empty() {
281            state.output_complete = true;
282            return Ok(None);
283        }
284        return Ok(Some(emit_batch_slice(
285            &state.result_batch,
286            &mut state.emitted_offset,
287            ctx,
288        )));
289    }
290}
291
292#[allow(clippy::too_many_arguments)]
293pub(super) fn next_window(
294    window_exprs: &[Expression],
295    partition_by_exprs: &[Expression],
296    order_by_exprs: &[Expression],
297    order_by_directions: &[SortDirection],
298    memory_tracker: &mut MemoryTracker,
299    state: &mut WindowState,
300    ctx: &BlockingContext<'_>,
301    input: &mut StreamingExecutor,
302) -> Result<Option<DataChunk>, QueryError> {
303    use crate::executor::streaming::spill::RunReader;
304
305    let eval_partition_key = |row: &[Value], col_names: &[String]| -> Vec<Value> {
306        if partition_by_exprs.is_empty() {
307            return vec![Value::Null(NullType::Null)];
308        }
309        let mut key = Vec::with_capacity(partition_by_exprs.len());
310        for expr in partition_by_exprs.iter() {
311            let mut ctx = ValueRowContext::from_names(row.to_vec(), col_names.to_vec());
312            key.push(
313                ExpressionEvaluator::evaluate(expr, &mut ctx)
314                    .unwrap_or(Value::Null(NullType::Null)),
315            );
316        }
317        key
318    };
319
320    loop {
321        if state.output_complete {
322            return Ok(None);
323        }
324
325        // Output phase: serve slices from the result batch.
326        if state.emitted_offset < state.result_batch.num_rows() {
327            return Ok(Some(emit_batch_slice(
328                &state.result_batch,
329                &mut state.emitted_offset,
330                ctx,
331            )));
332        }
333        if state.emitted_offset > 0 {
334            state.emitted_offset = 0;
335            state.result_batch.clear();
336            if !state.has_spilled || state.current_partition >= state.spilled_runs.len() {
337                state.output_complete = true;
338                return Ok(None);
339            }
340        }
341
342        // Replay phase
343        if state.has_spilled && state.partition_spiller.is_none() {
344            while state.current_partition < state.spilled_runs.len() {
345                if let Some(rt) = ctx.runtime.as_ref() {
346                    rt.ensure_not_cancelled()?;
347                }
348
349                let run = match &state.spilled_runs[state.current_partition] {
350                    Some(r) => r,
351                    None => {
352                        state.current_partition += 1;
353                        continue;
354                    }
355                };
356
357                let mut reader = RunReader::open(run)?;
358                let mut partitions: BTreeMap<Vec<Value>, Vec<(usize, Vec<Value>)>> =
359                    BTreeMap::new();
360                let mut run_row_count: u64 = 0;
361                while let Some(row) = reader.read_row()? {
362                    memory_tracker.try_reserve_row(&row)?;
363                    let partition_key = eval_partition_key(&row, &state.col_names);
364                    partitions
365                        .entry(partition_key)
366                        .or_default()
367                        .push((run_row_count as usize, row));
368                    run_row_count += 1;
369                }
370
371                let mut result_rows = Vec::with_capacity(run_row_count as usize);
372                for (_key, mut partition_rows) in partitions {
373                    sort_partition_rows(
374                        &mut partition_rows,
375                        &state.col_names,
376                        order_by_exprs,
377                        order_by_directions,
378                    );
379                    result_rows.extend(compute_window_partition_result(
380                        &partition_rows,
381                        &state.col_names,
382                        window_exprs,
383                    ));
384                }
385
386                let _ = std::fs::remove_file(&run.path);
387                state.current_partition += 1;
388                memory_tracker.reset();
389
390                if !result_rows.is_empty() {
391                    state.result_batch.clear();
392                    state.emitted_offset = 0;
393                    for result_row in result_rows {
394                        state.result_batch.append_row(result_row);
395                    }
396                    return Ok(Some(emit_batch_slice(
397                        &state.result_batch,
398                        &mut state.emitted_offset,
399                        ctx,
400                    )));
401                }
402            }
403            state.output_complete = true;
404            return Ok(None);
405        }
406
407        // Accumulation phase
408        let mut accumulating = true;
409        while accumulating {
410            match input.advance()? {
411                Some(mut chunk) => {
412                    chunk.normalize_for_opaque("Window");
413                    if let Some(rt) = ctx.runtime.as_ref() {
414                        rt.ensure_not_cancelled()?;
415                    }
416                    if state.col_names.is_empty() {
417                        state.col_names = chunk.col_names();
418                    }
419                    for row in chunk.rows {
420                        if let Some(ref mut spiller) = state.partition_spiller {
421                            let manager = ctx
422                                .runtime
423                                .as_ref()
424                                .and_then(|rt| rt.get_spill_manager())
425                                .ok_or_else(|| {
426                                    QueryError::execution("Spill manager not available".to_string())
427                                })?;
428                            let partition_key = eval_partition_key(&row, &state.col_names);
429                            let p = crate::executor::streaming::spill::hash_row_partition(
430                                &partition_key,
431                                spiller.num_partitions(),
432                            ) as usize;
433                            spiller.insert_row_to_partition(&row, p, &manager)?;
434                            continue;
435                        }
436                        if let Err(e) = memory_tracker.try_reserve_row(&row) {
437                            if let Some(sm) =
438                                ctx.runtime.as_ref().and_then(|rt| rt.get_spill_manager())
439                            {
440                                let config = HashPartitionConfig::default();
441                                let num_partitions = config.num_partitions;
442                                let mut spiller = HashPartitionSpiller::new(config, &sm, 0)?;
443
444                                for pending in state.batch.into_rows() {
445                                    let partition_key =
446                                        eval_partition_key(&pending, &state.col_names);
447                                    let p = crate::executor::streaming::spill::hash_row_partition(
448                                        &partition_key,
449                                        num_partitions,
450                                    ) as usize;
451                                    spiller.insert_row_to_partition(&pending, p, &sm)?;
452                                    memory_tracker
453                                        .release(MemoryBudget::estimate_row_memory(&pending));
454                                }
455
456                                let partition_key = eval_partition_key(&row, &state.col_names);
457                                let p = crate::executor::streaming::spill::hash_row_partition(
458                                    &partition_key,
459                                    num_partitions,
460                                ) as usize;
461                                spiller.insert_row_to_partition(&row, p, &sm)?;
462
463                                state.partition_spiller = Some(spiller);
464                                state.has_spilled = true;
465                                continue;
466                            } else {
467                                return Err(e);
468                            }
469                        }
470                        state.batch.append_row(row);
471                    }
472                }
473                None => {
474                    accumulating = false;
475                }
476            }
477        }
478
479        // Finalize spilled runs
480        if state.partition_spiller.is_some() {
481            let runs = finalize_partitions_with_runtime(
482                state.partition_spiller.take().unwrap(),
483                ctx.runtime.as_ref(),
484            )?;
485            state.spilled_runs = runs;
486            state.current_partition = 0;
487            continue;
488        }
489
490        // In-memory output
491        if state.batch.is_empty() {
492            state.output_complete = true;
493            return Ok(None);
494        }
495        let mut partitions: BTreeMap<Vec<Value>, Vec<(usize, Vec<Value>)>> = BTreeMap::new();
496        for (idx, row) in state.batch.into_rows().into_iter().enumerate() {
497            let partition_key = eval_partition_key(&row, &state.col_names);
498            partitions
499                .entry(partition_key)
500                .or_default()
501                .push((idx, row));
502        }
503        let mut result_rows = Vec::new();
504        for (_key, mut partition_rows) in partitions {
505            sort_partition_rows(
506                &mut partition_rows,
507                &state.col_names,
508                order_by_exprs,
509                order_by_directions,
510            );
511            result_rows.extend(compute_window_partition_result(
512                &partition_rows,
513                &state.col_names,
514                window_exprs,
515            ));
516        }
517
518        state.result_batch.clear();
519        state.emitted_offset = 0;
520        for result_row in result_rows {
521            state.result_batch.append_row(result_row);
522        }
523        if state.result_batch.is_empty() {
524            state.output_complete = true;
525            return Ok(None);
526        }
527        return Ok(Some(emit_batch_slice(
528            &state.result_batch,
529            &mut state.emitted_offset,
530            ctx,
531        )));
532    }
533}
534
535pub(super) fn close_window_function(state: &mut Option<WindowFunctionState>) {
536    if let Some(ref s) = state {
537        for r in s.spilled_runs.iter().flatten() {
538            let _ = std::fs::remove_file(&r.path);
539        }
540    }
541    *state = None;
542}
543
544pub(super) fn close_window(state: &mut Option<WindowState>) {
545    if let Some(ref s) = state {
546        for r in s.spilled_runs.iter().flatten() {
547            let _ = std::fs::remove_file(&r.path);
548        }
549    }
550    *state = None;
551}
552
553pub(super) fn spill_window_function(
554    state: &mut WindowFunctionState,
555    memory_tracker: &mut MemoryTracker,
556    partition_by_exprs: &[Expression],
557    sm: &SpillManager,
558) -> Result<(), QueryError> {
559    if state.partition_spiller.is_none() && !state.batch.is_empty() {
560        let config = HashPartitionConfig::default();
561        let num_partitions = config.num_partitions;
562        let mut spiller = HashPartitionSpiller::new(config, sm, 0)?;
563        for row in state.batch.into_rows() {
564            let mut partition_key = Vec::new();
565            if partition_by_exprs.is_empty() {
566                partition_key.push(Value::Null(NullType::Null));
567            } else {
568                for expr in partition_by_exprs.iter() {
569                    let mut ctx = ValueRowContext::from_names(row.clone(), state.col_names.clone());
570                    partition_key.push(
571                        ExpressionEvaluator::evaluate(expr, &mut ctx)
572                            .unwrap_or(Value::Null(NullType::Null)),
573                    );
574                }
575            }
576            let p = crate::executor::streaming::spill::hash_row_partition(
577                &partition_key,
578                num_partitions,
579            ) as usize;
580            spiller.insert_row_to_partition(&row, p, sm)?;
581            memory_tracker.release(MemoryBudget::estimate_row_memory(&row));
582        }
583        state.partition_spiller = Some(spiller);
584        state.has_spilled = true;
585    }
586    Ok(())
587}
588
589pub(super) fn spill_window(
590    state: &mut WindowState,
591    memory_tracker: &mut MemoryTracker,
592    partition_by_exprs: &[Expression],
593    sm: &SpillManager,
594) -> Result<(), QueryError> {
595    if state.partition_spiller.is_none() && !state.batch.is_empty() {
596        let config = HashPartitionConfig::default();
597        let num_partitions = config.num_partitions;
598        let mut spiller = HashPartitionSpiller::new(config, sm, 0)?;
599        for row in state.batch.into_rows() {
600            let mut partition_key = Vec::new();
601            if partition_by_exprs.is_empty() {
602                partition_key.push(Value::Null(NullType::Null));
603            } else {
604                for expr in partition_by_exprs.iter() {
605                    let mut ctx = ValueRowContext::from_names(row.clone(), state.col_names.clone());
606                    partition_key.push(
607                        ExpressionEvaluator::evaluate(expr, &mut ctx)
608                            .unwrap_or(Value::Null(NullType::Null)),
609                    );
610                }
611            }
612            let p = crate::executor::streaming::spill::hash_row_partition(
613                &partition_key,
614                num_partitions,
615            ) as usize;
616            spiller.insert_row_to_partition(&row, p, sm)?;
617            memory_tracker.release(MemoryBudget::estimate_row_memory(&row));
618        }
619        state.partition_spiller = Some(spiller);
620        state.has_spilled = true;
621    }
622    Ok(())
623}