Skip to main content

risingwave_stream/executor/eowc/
eowc_gap_fill.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use risingwave_common::array::Op;
18use risingwave_common::array::stream_record::Record;
19use risingwave_common::gap_fill::{
20    FillStrategy, apply_interpolation_step, calculate_interpolation_step,
21};
22use risingwave_common::metrics::LabelGuardedIntCounter;
23use risingwave_common::must_match;
24use risingwave_common::row::{OwnedRow, RowExt};
25use risingwave_common::types::{CheckedAdd, Interval, ToOwnedDatum};
26use risingwave_expr::ExprError;
27use risingwave_expr::expr::NonStrictExpression;
28use tracing::warn;
29
30use crate::executor::prelude::*;
31
32pub struct EowcGapFillExecutor<S: StateStore> {
33    input: Executor,
34    inner: ExecutorInner<S>,
35}
36
37pub struct EowcGapFillExecutorArgs<S: StateStore> {
38    pub actor_ctx: ActorContextRef,
39
40    pub input: Executor,
41
42    pub schema: Schema,
43    pub prev_row_table: StateTable<S>,
44    pub chunk_size: usize,
45    pub time_column_index: usize,
46    pub fill_columns: HashMap<usize, FillStrategy>,
47    pub gap_interval: NonStrictExpression,
48    pub high_gap_fill_amplification_threshold: usize,
49    pub partition_by_indices: Vec<usize>,
50}
51
52pub struct GapFillMetrics {
53    pub gap_fill_generated_rows_count: LabelGuardedIntCounter,
54}
55
56struct GapFillGenerationContext<'a> {
57    metrics: &'a GapFillMetrics,
58    high_amplification_threshold: usize,
59    actor_ctx: &'a ActorContextRef,
60}
61
62struct ExecutorInner<S: StateStore> {
63    actor_ctx: ActorContextRef,
64
65    schema: Schema,
66    prev_row_table: StateTable<S>,
67    chunk_size: usize,
68    time_column_index: usize,
69    fill_columns: HashMap<usize, FillStrategy>,
70    gap_interval: NonStrictExpression,
71    high_gap_fill_amplification_threshold: usize,
72    partition_by_indices: Vec<usize>,
73    partition_col_mask: Vec<bool>,
74
75    // Metrics
76    metrics: GapFillMetrics,
77}
78
79struct ExecutionVars {
80    staging_prev_rows: HashMap<OwnedRow, OwnedRow>,
81}
82
83impl<S: StateStore> ExecutorInner<S> {
84    fn generate_filled_rows(
85        prev_row: &OwnedRow,
86        curr_row: &OwnedRow,
87        time_column_index: usize,
88        partition_col_mask: &[bool],
89        fill_columns: &HashMap<usize, FillStrategy>,
90        interval: risingwave_common::types::Interval,
91        generation_context: &GapFillGenerationContext<'_>,
92    ) -> Result<Vec<OwnedRow>, ExprError> {
93        let mut filled_rows = Vec::new();
94        let (Some(prev_time_scalar), Some(curr_time_scalar)) = (
95            prev_row.datum_at(time_column_index),
96            curr_row.datum_at(time_column_index),
97        ) else {
98            return Ok(filled_rows);
99        };
100
101        let prev_time = match prev_time_scalar {
102            ScalarRefImpl::Timestamp(ts) => ts,
103            ScalarRefImpl::Timestamptz(ts) => {
104                match risingwave_common::types::Timestamp::with_micros(ts.timestamp_micros()) {
105                    Ok(timestamp) => timestamp,
106                    Err(_) => {
107                        warn!("Failed to convert timestamptz to timestamp: {:?}", ts);
108                        return Ok(filled_rows);
109                    }
110                }
111            }
112            _ => {
113                warn!(
114                    "Failed to convert time column to timestamp, got {:?}. Skipping gap fill.",
115                    prev_time_scalar
116                );
117                return Ok(filled_rows);
118            }
119        };
120
121        let curr_time = match curr_time_scalar {
122            ScalarRefImpl::Timestamp(ts) => ts,
123            ScalarRefImpl::Timestamptz(ts) => {
124                match risingwave_common::types::Timestamp::with_micros(ts.timestamp_micros()) {
125                    Ok(timestamp) => timestamp,
126                    Err(_) => {
127                        warn!("Failed to convert timestamptz to timestamp: {:?}", ts);
128                        return Ok(filled_rows);
129                    }
130                }
131            }
132            _ => {
133                warn!(
134                    "Failed to convert time column to timestamp, got {:?}. Skipping gap fill.",
135                    curr_time_scalar
136                );
137                return Ok(filled_rows);
138            }
139        };
140        if prev_time >= curr_time {
141            return Ok(filled_rows);
142        }
143
144        let mut fill_time = match prev_time.checked_add(interval) {
145            Some(t) => t,
146            None => {
147                return Ok(filled_rows);
148            }
149        };
150        if fill_time >= curr_time {
151            return Ok(filled_rows);
152        }
153
154        // Calculate the number of rows to fill
155        let mut row_count = 0;
156        let mut temp_time = fill_time;
157        while temp_time < curr_time {
158            row_count += 1;
159            temp_time = match temp_time.checked_add(interval) {
160                Some(t) => t,
161                None => break,
162            };
163        }
164
165        // Pre-compute interpolation steps for each column that requires interpolation
166        let mut interpolation_steps: Vec<Option<ScalarImpl>> = Vec::new();
167        let mut interpolation_states: Vec<Datum> = Vec::new();
168
169        for i in 0..prev_row.len() {
170            if let Some(strategy) = fill_columns.get(&i) {
171                if matches!(strategy, FillStrategy::Interpolate) {
172                    let step = calculate_interpolation_step(
173                        prev_row.datum_at(i),
174                        curr_row.datum_at(i),
175                        row_count + 1,
176                    );
177                    interpolation_steps.push(step.clone());
178                    interpolation_states.push(prev_row.datum_at(i).to_owned_datum());
179                } else {
180                    interpolation_steps.push(None);
181                    interpolation_states.push(None);
182                }
183            } else {
184                interpolation_steps.push(None);
185                interpolation_states.push(None);
186            }
187        }
188
189        // Generate filled rows, applying the appropriate strategy for each column
190        while fill_time < curr_time {
191            let mut new_row_data = Vec::with_capacity(prev_row.len());
192
193            for col_idx in 0..prev_row.len() {
194                let datum = if col_idx == time_column_index {
195                    // Time column: use the incremented timestamp
196                    let fill_time_scalar = match prev_time_scalar {
197                        ScalarRefImpl::Timestamp(_) => ScalarImpl::Timestamp(fill_time),
198                        ScalarRefImpl::Timestamptz(_) => {
199                            let micros = fill_time.0.and_utc().timestamp_micros();
200                            ScalarImpl::Timestamptz(
201                                risingwave_common::types::Timestamptz::from_micros(micros),
202                            )
203                        }
204                        _ => unreachable!("Time column should be Timestamp or Timestamptz"),
205                    };
206                    Some(fill_time_scalar)
207                } else if partition_col_mask[col_idx] {
208                    prev_row.datum_at(col_idx).to_owned_datum()
209                } else if let Some(strategy) = fill_columns.get(&col_idx) {
210                    // Apply the fill strategy for this column
211                    match strategy {
212                        FillStrategy::Locf => prev_row.datum_at(col_idx).to_owned_datum(),
213                        FillStrategy::Null => None,
214                        FillStrategy::Interpolate => {
215                            // Apply interpolation step and update cumulative value
216                            if let Some(step) = &interpolation_steps[col_idx] {
217                                apply_interpolation_step(&mut interpolation_states[col_idx], step);
218                                interpolation_states[col_idx].clone()
219                            } else {
220                                // If interpolation step is None, fill with NULL
221                                None
222                            }
223                        }
224                    }
225                } else {
226                    // No strategy specified, default to NULL
227                    None
228                };
229                new_row_data.push(datum);
230            }
231
232            filled_rows.push(OwnedRow::new(new_row_data));
233
234            fill_time = match fill_time.checked_add(interval) {
235                Some(t) => t,
236                None => {
237                    // Time overflow during iteration, stop filling
238                    warn!(
239                        "Gap fill stopped due to timestamp overflow after generating {} rows.",
240                        filled_rows.len()
241                    );
242                    break;
243                }
244            };
245        }
246
247        // Update metrics with the number of generated rows
248        generation_context
249            .metrics
250            .gap_fill_generated_rows_count
251            .inc_by(filled_rows.len() as u64);
252
253        if filled_rows.len() > generation_context.high_amplification_threshold {
254            tracing::warn!(target: "high_gap_fill_amplification",
255                generated_rows_len = filled_rows.len(),
256                prev_time = ?prev_time,
257                curr_time = ?curr_time,
258                gap_interval = ?interval,
259                actor_id = %generation_context.actor_ctx.id,
260                fragment_id = %generation_context.actor_ctx.fragment_id,
261                "large rows generated by gap fill"
262            );
263        }
264
265        Ok(filled_rows)
266    }
267}
268
269impl<S: StateStore> Execute for EowcGapFillExecutor<S> {
270    fn execute(self: Box<Self>) -> BoxedMessageStream {
271        self.execute_inner().boxed()
272    }
273}
274
275impl<S: StateStore> EowcGapFillExecutor<S> {
276    pub fn new(args: EowcGapFillExecutorArgs<S>) -> Self {
277        let metrics = args.actor_ctx.streaming_metrics.clone();
278        let actor_id = args.actor_ctx.id.to_string();
279        let fragment_id = args.actor_ctx.fragment_id.to_string();
280        let gap_fill_metrics = GapFillMetrics {
281            gap_fill_generated_rows_count: metrics
282                .gap_fill_generated_rows_count
283                .with_guarded_label_values(&[&actor_id, &fragment_id]),
284        };
285        let mut partition_col_mask = vec![false; args.schema.len()];
286        for &idx in &args.partition_by_indices {
287            partition_col_mask[idx] = true;
288        }
289
290        Self {
291            input: args.input,
292
293            inner: ExecutorInner {
294                actor_ctx: args.actor_ctx,
295                schema: args.schema,
296                prev_row_table: args.prev_row_table,
297                chunk_size: args.chunk_size,
298                time_column_index: args.time_column_index,
299                fill_columns: args.fill_columns,
300                gap_interval: args.gap_interval,
301                high_gap_fill_amplification_threshold: args.high_gap_fill_amplification_threshold,
302                partition_by_indices: args.partition_by_indices,
303                partition_col_mask,
304                metrics: gap_fill_metrics,
305            },
306        }
307    }
308
309    async fn load_prev_row_for_partition(
310        partition_by_indices: &[usize],
311        partition_key: &OwnedRow,
312        prev_row_table: &StateTable<S>,
313        staging_prev_rows: &mut HashMap<OwnedRow, OwnedRow>,
314    ) -> StreamExecutorResult<Option<OwnedRow>> {
315        if let Some(row) = staging_prev_rows.get(partition_key) {
316            return Ok(Some(row.clone()));
317        }
318
319        let row = if partition_by_indices.is_empty() {
320            prev_row_table.get_from_one_row_table().await?
321        } else {
322            prev_row_table.get_row(partition_key).await?
323        };
324
325        if let Some(row) = row {
326            staging_prev_rows.insert(partition_key.clone(), row.clone());
327            Ok(Some(row))
328        } else {
329            Ok(None)
330        }
331    }
332
333    fn store_prev_row_for_partition(
334        partition_key: OwnedRow,
335        prev_row: Option<&OwnedRow>,
336        current_row: OwnedRow,
337        prev_row_table: &mut StateTable<S>,
338        staging_prev_rows: &mut HashMap<OwnedRow, OwnedRow>,
339    ) {
340        if let Some(old_row) = prev_row {
341            prev_row_table.delete(old_row);
342        }
343        prev_row_table.insert(&current_row);
344        staging_prev_rows.insert(partition_key, current_row);
345    }
346
347    #[try_stream(ok = Message, error = StreamExecutorError)]
348    async fn execute_inner(self) {
349        let Self {
350            input,
351            inner: mut this,
352        } = self;
353
354        let mut input = input.execute();
355
356        let barrier = expect_first_barrier(&mut input).await?;
357        let first_epoch = barrier.epoch;
358        yield Message::Barrier(barrier);
359        this.prev_row_table.init_epoch(first_epoch).await?;
360
361        // Calculate and validate gap interval once at initialization
362        let dummy_row = OwnedRow::new(vec![]);
363        let interval_datum = this.gap_interval.eval_row_infallible(&dummy_row).await;
364        let interval = interval_datum
365            .ok_or_else(|| anyhow::anyhow!("Gap interval expression returned null"))?
366            .into_interval();
367
368        // Validate that gap interval is positive.
369        if interval <= Interval::from_month_day_usec(0, 0, 0) {
370            Err(anyhow::anyhow!("Gap interval must be positive"))?;
371        }
372
373        let mut vars = ExecutionVars {
374            staging_prev_rows: HashMap::new(),
375        };
376
377        #[for_await]
378        for msg in input {
379            match msg? {
380                // Drop the time watermark: a late anchor makes gap fill back-fill below it.
381                // PARTITION BY column watermarks (if any) pass through unchanged.
382                Message::Watermark(watermark)
383                    if this.partition_by_indices.contains(&watermark.col_idx) =>
384                {
385                    yield Message::Watermark(watermark);
386                }
387                Message::Watermark(_) => continue,
388                Message::Chunk(chunk) => {
389                    let mut chunk_builder =
390                        StreamChunkBuilder::new(this.chunk_size, this.schema.data_types());
391
392                    for record in chunk.records() {
393                        let current_row =
394                            must_match!(record, Record::Insert { new_row } => new_row)
395                                .into_owned_row();
396                        let partition_key = (&current_row)
397                            .project(&this.partition_by_indices)
398                            .into_owned_row();
399                        let prev_row = Self::load_prev_row_for_partition(
400                            &this.partition_by_indices,
401                            &partition_key,
402                            &this.prev_row_table,
403                            &mut vars.staging_prev_rows,
404                        )
405                        .await?;
406                        if let Some(p_row) = &prev_row {
407                            let generation_context = GapFillGenerationContext {
408                                metrics: &this.metrics,
409                                high_amplification_threshold: this
410                                    .high_gap_fill_amplification_threshold,
411                                actor_ctx: &this.actor_ctx,
412                            };
413                            let filled_rows = ExecutorInner::<S>::generate_filled_rows(
414                                p_row,
415                                &current_row,
416                                this.time_column_index,
417                                &this.partition_col_mask,
418                                &this.fill_columns,
419                                interval,
420                                &generation_context,
421                            )?;
422                            for filled_row in filled_rows {
423                                if let Some(chunk) =
424                                    chunk_builder.append_row(Op::Insert, &filled_row)
425                                {
426                                    yield Message::Chunk(chunk);
427                                }
428                            }
429                        }
430                        if let Some(chunk) = chunk_builder.append_row(Op::Insert, &current_row) {
431                            yield Message::Chunk(chunk);
432                        }
433                        Self::store_prev_row_for_partition(
434                            partition_key,
435                            prev_row.as_ref(),
436                            current_row,
437                            &mut this.prev_row_table,
438                            &mut vars.staging_prev_rows,
439                        );
440                    }
441                    if let Some(chunk) = chunk_builder.take() {
442                        yield Message::Chunk(chunk);
443                    }
444                    this.prev_row_table.try_flush().await?;
445                }
446                Message::Barrier(barrier) => {
447                    let prev_row_post_commit = this.prev_row_table.commit(barrier.epoch).await?;
448                    // The prev-row state table is the source of truth after commit. Drop the
449                    // per-epoch cache so high-cardinality historical partitions do not remain
450                    // resident forever; the next active partition will be loaded on demand.
451                    vars.staging_prev_rows.clear();
452
453                    let update_vnode_bitmap = barrier.as_update_vnode_bitmap(this.actor_ctx.id);
454                    yield Message::Barrier(barrier);
455
456                    if prev_row_post_commit
457                        .post_yield_barrier(update_vnode_bitmap)
458                        .await?
459                        .is_some()
460                    {
461                        // Vnode ownership changed. The cache is already cleared after commit, so
462                        // subsequent rows reload only partitions owned by the current actor.
463                        vars.staging_prev_rows.clear();
464                    }
465                }
466            }
467        }
468    }
469}
470
471#[cfg(test)]
472mod tests {
473    use risingwave_common::array::stream_chunk::StreamChunkTestExt;
474    use risingwave_common::bitmap::Bitmap;
475    use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, TableId};
476    use risingwave_common::hash::VirtualNode;
477    use risingwave_common::types::Interval;
478    use risingwave_common::types::test_utils::IntervalTestExt;
479    use risingwave_common::util::epoch::test_epoch;
480    use risingwave_common::util::sort_util::OrderType;
481    use risingwave_expr::expr::LiteralExpression;
482    use risingwave_storage::memory::MemoryStateStore;
483
484    use super::*;
485    use crate::common::table::test_utils::gen_pbtable_with_dist_key;
486    use crate::executor::test_utils::{MessageSender, MockSource, StreamExecutorTestExt};
487
488    async fn create_executor<S: StateStore>(
489        time_column_index: usize,
490        fill_columns: HashMap<usize, FillStrategy>,
491        gap_interval: NonStrictExpression,
492        store: S,
493    ) -> (MessageSender, BoxedMessageStream) {
494        let input_schema = Schema::new(vec![
495            Field::unnamed(DataType::Timestamp),
496            Field::unnamed(DataType::Int32),
497            Field::unnamed(DataType::Int64),
498            Field::unnamed(DataType::Float32),
499            Field::unnamed(DataType::Float64),
500        ]);
501        let input_stream_key = vec![time_column_index];
502
503        let table_columns = vec![
504            ColumnDesc::unnamed(ColumnId::new(0), DataType::Timestamp),
505            ColumnDesc::unnamed(ColumnId::new(1), DataType::Int32),
506            ColumnDesc::unnamed(ColumnId::new(2), DataType::Int64),
507            ColumnDesc::unnamed(ColumnId::new(3), DataType::Float32),
508            ColumnDesc::unnamed(ColumnId::new(4), DataType::Float64),
509        ];
510
511        let prev_row_pk_indices = vec![0];
512        let prev_row_order_types = vec![OrderType::ascending()];
513        let prev_row_table = StateTable::from_table_catalog(
514            &gen_pbtable_with_dist_key(
515                TableId::new(1),
516                table_columns,
517                prev_row_order_types,
518                prev_row_pk_indices,
519                0,
520                vec![],
521            ),
522            store,
523            None,
524        )
525        .await;
526
527        let (tx, source) = MockSource::channel();
528        let source = source.into_executor(input_schema, input_stream_key);
529        let gap_fill_executor = EowcGapFillExecutor::new(EowcGapFillExecutorArgs {
530            actor_ctx: ActorContext::for_test(123),
531            schema: source.schema().clone(),
532            input: source,
533            prev_row_table,
534            chunk_size: 1024,
535            time_column_index,
536            fill_columns,
537            gap_interval,
538            high_gap_fill_amplification_threshold: 2048,
539            partition_by_indices: vec![],
540        });
541
542        (tx, gap_fill_executor.boxed().execute())
543    }
544
545    async fn create_partitioned_executor<S: StateStore>(
546        store: S,
547    ) -> (MessageSender, BoxedMessageStream) {
548        let input_schema = Schema::new(vec![
549            Field::unnamed(DataType::Int64),
550            Field::unnamed(DataType::Timestamp),
551            Field::unnamed(DataType::Int64),
552        ]);
553        let input_stream_key = vec![0, 1];
554
555        let table_columns = vec![
556            ColumnDesc::unnamed(ColumnId::new(0), DataType::Int64),
557            ColumnDesc::unnamed(ColumnId::new(1), DataType::Timestamp),
558            ColumnDesc::unnamed(ColumnId::new(2), DataType::Int64),
559        ];
560
561        let prev_row_table = StateTable::from_table_catalog(
562            &gen_pbtable_with_dist_key(
563                TableId::new(11),
564                table_columns,
565                vec![OrderType::ascending()],
566                vec![0],
567                0,
568                vec![0],
569            ),
570            store,
571            Some(Bitmap::ones(VirtualNode::COUNT_FOR_TEST).into()),
572        )
573        .await;
574
575        let (tx, source) = MockSource::channel();
576        let source = source.into_executor(input_schema, input_stream_key);
577        let gap_fill_executor = EowcGapFillExecutor::new(EowcGapFillExecutorArgs {
578            actor_ctx: ActorContext::for_test(123),
579            schema: source.schema().clone(),
580            input: source,
581            prev_row_table,
582            chunk_size: 1024,
583            time_column_index: 1,
584            fill_columns: HashMap::from([(2, FillStrategy::Locf)]),
585            gap_interval: NonStrictExpression::for_test(LiteralExpression::new(
586                DataType::Interval,
587                Some(Interval::from_days(1).into()),
588            )),
589            high_gap_fill_amplification_threshold: 2048,
590            partition_by_indices: vec![0],
591        });
592
593        (tx, gap_fill_executor.boxed().execute())
594    }
595
596    #[tokio::test]
597    async fn test_gap_fill_interpolate() {
598        let time_column_index = 0;
599        let gap_interval = Interval::from_days(1);
600        let fill_columns = HashMap::from([
601            (1, FillStrategy::Interpolate),
602            (2, FillStrategy::Interpolate),
603            (3, FillStrategy::Interpolate),
604            (4, FillStrategy::Interpolate),
605        ]);
606        let store = MemoryStateStore::new();
607        let (mut tx, mut gap_fill_executor) = create_executor(
608            time_column_index,
609            fill_columns,
610            NonStrictExpression::for_test(LiteralExpression::new(
611                DataType::Interval,
612                Some(gap_interval.into()),
613            )),
614            store.clone(),
615        )
616        .await;
617
618        tx.push_barrier(test_epoch(1), false);
619        gap_fill_executor.expect_barrier().await;
620
621        tx.push_int64_watermark(1, 0_i64);
622        tx.push_watermark(
623            0,
624            DataType::Timestamp,
625            "2023-03-06 18:27:03"
626                .parse::<risingwave_common::types::Timestamp>()
627                .unwrap()
628                .into(),
629        );
630        tx.push_chunk(StreamChunk::from_pretty(
631            " TS                  i   I    f     F
632            + 2023-04-01T10:00:00 10 100 1.0 100.0
633            + 2023-04-05T10:00:00 50 200 5.0 200.0",
634        ));
635
636        tx.push_int64_watermark(1, 0_i64);
637        tx.push_watermark(
638            0,
639            DataType::Timestamp,
640            "2023-04-05 18:27:03"
641                .parse::<risingwave_common::types::Timestamp>()
642                .unwrap()
643                .into(),
644        );
645
646        let chunk = gap_fill_executor.expect_chunk().await;
647        assert_eq!(
648            chunk,
649            StreamChunk::from_pretty(
650                " TS                  i   I    f     F
651                + 2023-04-01T10:00:00 10 100 1.0 100.0
652                + 2023-04-02T10:00:00 20 125 2.0 125.0
653                + 2023-04-03T10:00:00 30 150 3.0 150.0
654                + 2023-04-04T10:00:00 40 175 4.0 175.0
655                + 2023-04-05T10:00:00 50 200 5.0 200.0",
656            )
657        );
658    }
659
660    #[tokio::test]
661    async fn test_gap_fill_locf() {
662        let time_column_index = 0;
663        let gap_interval = Interval::from_days(1);
664        let fill_columns = HashMap::from([
665            (1, FillStrategy::Locf),
666            (2, FillStrategy::Locf),
667            (3, FillStrategy::Locf),
668            (4, FillStrategy::Locf),
669        ]);
670        let store = MemoryStateStore::new();
671        let (mut tx, mut gap_fill_executor) = create_executor(
672            time_column_index,
673            fill_columns,
674            NonStrictExpression::for_test(LiteralExpression::new(
675                DataType::Interval,
676                Some(gap_interval.into()),
677            )),
678            store.clone(),
679        )
680        .await;
681
682        tx.push_barrier(test_epoch(1), false);
683        gap_fill_executor.expect_barrier().await;
684
685        tx.push_int64_watermark(1, 0_i64);
686        tx.push_watermark(
687            0,
688            DataType::Timestamp,
689            "2023-03-06 18:27:03"
690                .parse::<risingwave_common::types::Timestamp>()
691                .unwrap()
692                .into(),
693        );
694        tx.push_chunk(StreamChunk::from_pretty(
695            " TS                  i   I    f     F
696            + 2023-04-01T10:00:00 10 100 1.0 100.0
697            + 2023-04-05T10:00:00 50 200 5.0 200.0",
698        ));
699
700        tx.push_int64_watermark(1, 0_i64);
701        tx.push_watermark(
702            0,
703            DataType::Timestamp,
704            "2023-04-05 18:27:03"
705                .parse::<risingwave_common::types::Timestamp>()
706                .unwrap()
707                .into(),
708        );
709
710        let chunk = gap_fill_executor.expect_chunk().await;
711        assert_eq!(
712            chunk,
713            StreamChunk::from_pretty(
714                " TS                  i   I    f     F
715                + 2023-04-01T10:00:00 10 100 1.0 100.0
716                + 2023-04-02T10:00:00 10 100 1.0 100.0
717                + 2023-04-03T10:00:00 10 100 1.0 100.0
718                + 2023-04-04T10:00:00 10 100 1.0 100.0
719                + 2023-04-05T10:00:00 50 200 5.0 200.0",
720            )
721        );
722    }
723
724    #[tokio::test]
725    async fn test_gap_fill_prev_row_reloaded_after_barrier() {
726        let store = MemoryStateStore::new();
727        let (mut tx, mut gap_fill_executor) = create_partitioned_executor(store).await;
728
729        tx.push_barrier(test_epoch(1), false);
730        gap_fill_executor.expect_barrier().await;
731
732        tx.push_chunk(StreamChunk::from_pretty(
733            " I TS                  I
734            + 1 2023-04-01T00:00:00 10
735            + 1 2023-04-03T00:00:00 30",
736        ));
737        tx.push_watermark(
738            1,
739            DataType::Timestamp,
740            "2023-04-04 00:00:00"
741                .parse::<risingwave_common::types::Timestamp>()
742                .unwrap()
743                .into(),
744        );
745
746        let chunk = gap_fill_executor.expect_chunk().await;
747        let expected = StreamChunk::from_pretty(
748            " I TS                  I
749            + 1 2023-04-01T00:00:00 10
750            + 1 2023-04-02T00:00:00 10
751            + 1 2023-04-03T00:00:00 30",
752        );
753        assert_eq!(
754            chunk,
755            expected,
756            "\nactual:\n{}\nexpected:\n{}",
757            chunk.to_pretty(),
758            expected.to_pretty()
759        );
760        tx.push_barrier(test_epoch(2), false);
761        gap_fill_executor.expect_barrier().await;
762
763        tx.push_chunk(StreamChunk::from_pretty(
764            " I TS                  I
765            + 1 2023-04-05T00:00:00 50",
766        ));
767        tx.push_watermark(
768            1,
769            DataType::Timestamp,
770            "2023-04-06 00:00:00"
771                .parse::<risingwave_common::types::Timestamp>()
772                .unwrap()
773                .into(),
774        );
775
776        let chunk = gap_fill_executor.expect_chunk().await;
777        let expected = StreamChunk::from_pretty(
778            " I TS                  I
779            + 1 2023-04-04T00:00:00 30
780            + 1 2023-04-05T00:00:00 50",
781        );
782        assert_eq!(
783            chunk,
784            expected,
785            "\nactual:\n{}\nexpected:\n{}",
786            chunk.to_pretty(),
787            expected.to_pretty()
788        );
789    }
790
791    #[tokio::test]
792    async fn test_gap_fill_locf_partition_by() {
793        let store = MemoryStateStore::new();
794        let (mut tx, mut gap_fill_executor) = create_partitioned_executor(store).await;
795
796        tx.push_barrier(test_epoch(1), false);
797        gap_fill_executor.expect_barrier().await;
798
799        tx.push_chunk(StreamChunk::from_pretty(
800            " I TS                  I
801            + 1 2023-04-01T00:00:00 10
802            + 2 2023-04-01T00:00:00 100
803            + 1 2023-04-03T00:00:00 30
804            + 2 2023-04-04T00:00:00 400",
805        ));
806
807        tx.push_int64_watermark(2, 0_i64);
808        tx.push_watermark(
809            1,
810            DataType::Timestamp,
811            "2023-04-05 00:00:00"
812                .parse::<risingwave_common::types::Timestamp>()
813                .unwrap()
814                .into(),
815        );
816
817        let chunk = gap_fill_executor.expect_chunk().await;
818        let expected = StreamChunk::from_pretty(
819            " I TS                  I
820            + 1 2023-04-01T00:00:00 10
821            + 2 2023-04-01T00:00:00 100
822            + 1 2023-04-02T00:00:00 10
823            + 1 2023-04-03T00:00:00 30
824            + 2 2023-04-02T00:00:00 100
825            + 2 2023-04-03T00:00:00 100
826            + 2 2023-04-04T00:00:00 400",
827        );
828        assert_eq!(
829            chunk,
830            expected,
831            "\nactual:\n{}\nexpected:\n{}",
832            chunk.to_pretty(),
833            expected.to_pretty()
834        );
835    }
836
837    #[tokio::test]
838    async fn test_gap_fill_null() {
839        let time_column_index = 0;
840        let gap_interval = Interval::from_days(1);
841        let fill_columns = HashMap::from([
842            (1, FillStrategy::Null),
843            (2, FillStrategy::Null),
844            (3, FillStrategy::Null),
845            (4, FillStrategy::Null),
846        ]);
847        let store = MemoryStateStore::new();
848        let (mut tx, mut gap_fill_executor) = create_executor(
849            time_column_index,
850            fill_columns,
851            NonStrictExpression::for_test(LiteralExpression::new(
852                DataType::Interval,
853                Some(gap_interval.into()),
854            )),
855            store.clone(),
856        )
857        .await;
858
859        tx.push_barrier(test_epoch(1), false);
860        gap_fill_executor.expect_barrier().await;
861
862        tx.push_int64_watermark(1, 0_i64);
863        tx.push_watermark(
864            0,
865            DataType::Timestamp,
866            "2023-03-06 18:27:03"
867                .parse::<risingwave_common::types::Timestamp>()
868                .unwrap()
869                .into(),
870        );
871        tx.push_chunk(StreamChunk::from_pretty(
872            " TS                  i   I    f     F
873            + 2023-04-01T10:00:00 10 100 1.0 100.0
874            + 2023-04-05T10:00:00 50 200 5.0 200.0",
875        ));
876
877        tx.push_int64_watermark(1, 0_i64);
878        tx.push_watermark(
879            0,
880            DataType::Timestamp,
881            "2023-04-05 18:27:03"
882                .parse::<risingwave_common::types::Timestamp>()
883                .unwrap()
884                .into(),
885        );
886
887        let chunk = gap_fill_executor.expect_chunk().await;
888        assert_eq!(
889            chunk,
890            StreamChunk::from_pretty(
891                " TS                  i   I    f     F
892                + 2023-04-01T10:00:00 10 100 1.0 100.0
893                + 2023-04-02T10:00:00 .  .    .    .
894                + 2023-04-03T10:00:00 .  .    .    .
895                + 2023-04-04T10:00:00 .  .    .    .
896                + 2023-04-05T10:00:00 50 200 5.0 200.0",
897            )
898        );
899    }
900
901    #[tokio::test]
902    async fn test_gap_fill_mixed_strategy() {
903        let time_column_index = 0;
904        let gap_interval = Interval::from_days(1);
905        let fill_columns = HashMap::from([
906            (1, FillStrategy::Interpolate),
907            (2, FillStrategy::Locf),
908            (3, FillStrategy::Null),
909            (4, FillStrategy::Interpolate),
910        ]);
911        let store = MemoryStateStore::new();
912        let (mut tx, mut gap_fill_executor) = create_executor(
913            time_column_index,
914            fill_columns,
915            NonStrictExpression::for_test(LiteralExpression::new(
916                DataType::Interval,
917                Some(gap_interval.into()),
918            )),
919            store.clone(),
920        )
921        .await;
922
923        tx.push_barrier(test_epoch(1), false);
924        gap_fill_executor.expect_barrier().await;
925
926        tx.push_int64_watermark(1, 0_i64);
927        tx.push_watermark(
928            0,
929            DataType::Timestamp,
930            "2023-03-06 18:27:03"
931                .parse::<risingwave_common::types::Timestamp>()
932                .unwrap()
933                .into(),
934        );
935        tx.push_chunk(StreamChunk::from_pretty(
936            " TS                  i   I    f     F
937            + 2023-04-01T10:00:00 10 100 1.0 100.0
938            + 2023-04-05T10:00:00 50 200 5.0 200.0",
939        ));
940
941        tx.push_int64_watermark(1, 0_i64);
942        tx.push_watermark(
943            0,
944            DataType::Timestamp,
945            "2023-04-05 18:27:03"
946                .parse::<risingwave_common::types::Timestamp>()
947                .unwrap()
948                .into(),
949        );
950
951        let chunk = gap_fill_executor.expect_chunk().await;
952        assert_eq!(
953            chunk,
954            StreamChunk::from_pretty(
955                " TS                  i   I    f     F
956                + 2023-04-01T10:00:00 10 100 1.0 100.0
957                + 2023-04-02T10:00:00 20 100 .    125.0
958                + 2023-04-03T10:00:00 30 100 .    150.0
959                + 2023-04-04T10:00:00 40 100 .    175.0
960                + 2023-04-05T10:00:00 50 200 5.0 200.0",
961            )
962        );
963    }
964
965    #[tokio::test]
966    async fn test_gap_fill_fail_over() {
967        let time_column_index = 0;
968        let gap_interval = Interval::from_days(1);
969        let fill_columns = HashMap::from([
970            (1, FillStrategy::Locf),
971            (2, FillStrategy::Interpolate),
972            (3, FillStrategy::Locf),
973            (4, FillStrategy::Locf),
974        ]);
975        let store = MemoryStateStore::new();
976        let (mut tx, mut gap_fill_executor) = create_executor(
977            time_column_index,
978            fill_columns.clone(),
979            NonStrictExpression::for_test(LiteralExpression::new(
980                DataType::Interval,
981                Some(gap_interval.into()),
982            )),
983            store.clone(),
984        )
985        .await;
986
987        tx.push_barrier(test_epoch(1), false);
988        gap_fill_executor.expect_barrier().await;
989
990        tx.push_chunk(StreamChunk::from_pretty(
991            " TS                  i   I    f     F
992            + 2023-04-01T10:00:00 10 100 1.0 100.0
993            + 2023-04-05T10:00:00 50 200 5.0 200.0",
994        ));
995
996        let chunk = gap_fill_executor.expect_chunk().await;
997        assert_eq!(
998            chunk,
999            StreamChunk::from_pretty(
1000                " TS                  i   I    f     F
1001                + 2023-04-01T10:00:00 10 100 1.0 100.0
1002                + 2023-04-02T10:00:00 10 125 1.0 100.0
1003                + 2023-04-03T10:00:00 10 150 1.0 100.0
1004                + 2023-04-04T10:00:00 10 175 1.0 100.0
1005                + 2023-04-05T10:00:00 50 200 5.0 200.0",
1006            )
1007        );
1008
1009        tx.push_barrier(test_epoch(2), false);
1010        gap_fill_executor.expect_barrier().await;
1011
1012        let (mut recovered_tx, mut recovered_gap_fill_executor) = create_executor(
1013            time_column_index,
1014            fill_columns.clone(),
1015            NonStrictExpression::for_test(LiteralExpression::new(
1016                DataType::Interval,
1017                Some(gap_interval.into()),
1018            )),
1019            store.clone(),
1020        )
1021        .await;
1022
1023        recovered_tx.push_barrier(test_epoch(2), false);
1024        recovered_gap_fill_executor.expect_barrier().await;
1025
1026        recovered_tx.push_watermark(
1027            0,
1028            DataType::Timestamp,
1029            "2023-04-06T10:00:00"
1030                .parse::<risingwave_common::types::Timestamp>()
1031                .unwrap()
1032                .into(),
1033        );
1034        recovered_tx.push_chunk(StreamChunk::from_pretty(
1035            " TS                  i   I    f     F
1036            + 2023-04-08T10:00:00 80 500 8.0 500.0",
1037        ));
1038
1039        let chunk = recovered_gap_fill_executor.expect_chunk().await;
1040        assert_eq!(
1041            chunk,
1042            StreamChunk::from_pretty(
1043                " TS                  i   I    f     F
1044                + 2023-04-06T10:00:00 50 300 5.0 200.0
1045                + 2023-04-07T10:00:00 50 400 5.0 200.0
1046                + 2023-04-08T10:00:00 80 500 8.0 500.0"
1047            )
1048        );
1049
1050        recovered_tx.push_barrier(test_epoch(3), false);
1051        recovered_gap_fill_executor.expect_barrier().await;
1052
1053        let (mut final_recovered_tx, mut final_recovered_gap_fill_executor) = create_executor(
1054            time_column_index,
1055            fill_columns,
1056            NonStrictExpression::for_test(LiteralExpression::new(
1057                DataType::Interval,
1058                Some(gap_interval.into()),
1059            )),
1060            store,
1061        )
1062        .await;
1063
1064        final_recovered_tx.push_barrier(test_epoch(3), false);
1065        final_recovered_gap_fill_executor.expect_barrier().await;
1066
1067        final_recovered_tx.push_chunk(StreamChunk::from_pretty(
1068            " TS                   i   I    f     F
1069            + 2023-04-10T10:00:00 100 700 10.0 700.0",
1070        ));
1071
1072        let chunk = final_recovered_gap_fill_executor.expect_chunk().await;
1073        assert_eq!(
1074            chunk,
1075            StreamChunk::from_pretty(
1076                " TS                   i   I    f     F
1077                + 2023-04-09T10:00:00  80 600  8.0 500.0
1078                + 2023-04-10T10:00:00 100 700 10.0 700.0"
1079            )
1080        );
1081    }
1082}