1use 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: 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 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 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 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 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 match strategy {
212 FillStrategy::Locf => prev_row.datum_at(col_idx).to_owned_datum(),
213 FillStrategy::Null => None,
214 FillStrategy::Interpolate => {
215 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 None
222 }
223 }
224 }
225 } else {
226 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 warn!(
239 "Gap fill stopped due to timestamp overflow after generating {} rows.",
240 filled_rows.len()
241 );
242 break;
243 }
244 };
245 }
246
247 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(¤t_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 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 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 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 = (¤t_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 ¤t_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, ¤t_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 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 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}