1use std::collections::HashSet;
16use std::ops::Bound;
17
18use bytes::Bytes;
19use futures::future::Either;
20use futures::stream::{self, select_with_strategy};
21use futures_async_stream::try_stream;
22use itertools::Itertools;
23use risingwave_common::array::Op;
24use risingwave_common::bitmap::Bitmap;
25use risingwave_common::catalog::{
26 ColumnDesc, ConflictBehavior, TableId, checked_conflict_behaviors,
27};
28use risingwave_common::hash::{VirtualNode, VnodeBitmapExt};
29use risingwave_common::row::{OwnedRow, RowExt};
30use risingwave_common::types::DataType;
31use risingwave_common::util::sort_util::ColumnOrder;
32use risingwave_common::util::value_encoding::BasicSerde;
33use risingwave_pb::catalog::Table;
34use risingwave_pb::catalog::table::Engine;
35use risingwave_pb::id::{SourceId, SubscriberId};
36use risingwave_pb::stream_plan::SubscriptionUpstreamInfo;
37use risingwave_storage::row_serde::value_serde::{ValueRowSerde, ValueRowSerdeNew};
38use risingwave_storage::store::PrefetchOptions;
39use risingwave_storage::table::KeyedRow;
40
41use crate::common::change_buffer::output_kind as cb_kind;
42use crate::common::metrics::MetricsInfo;
43use crate::common::table::state_table::{
44 StateTableBuilder, StateTableInner, StateTableOpConsistencyLevel,
45};
46use crate::consistency::consistency_panic;
47use crate::executor::error::ErrorKind;
48use crate::executor::monitor::MaterializeMetrics;
49use crate::executor::mview::RefreshProgressTable;
50use crate::executor::mview::cache::MaterializeCache;
51use crate::executor::prelude::*;
52use crate::executor::{BarrierInner, BarrierMutationType};
53use crate::task::LocalBarrierManager;
54
55#[derive(Debug, Clone)]
56pub enum MaterializeStreamState<M> {
57 NormalIngestion,
58 MergingData,
59 CleanUp,
60 CommitAndYieldBarrier {
61 barrier: BarrierInner<M>,
62 expect_next_state: Box<MaterializeStreamState<M>>,
63 },
64 RefreshEnd,
65}
66
67pub struct MaterializeExecutor<S: StateStore, SD: ValueRowSerde> {
69 input: Executor,
70
71 schema: Schema,
72
73 state_table: StateTableInner<S, SD>,
74
75 stream_key_indices: Vec<usize>,
86
87 arrange_key_indices: Vec<usize>,
89
90 actor_context: ActorContextRef,
91
92 materialize_cache: Option<MaterializeCache>,
94
95 toastable_column_indices: Option<Vec<usize>>,
100
101 conflict_behavior: ConflictBehavior,
102
103 cleaned_by_ttl_watermark: bool,
105
106 version_column_indices: Vec<u32>,
107
108 may_have_downstream: bool,
109
110 subscriber_ids: HashSet<SubscriberId>,
111
112 metrics: MaterializeMetrics,
113
114 is_dummy_table: bool,
117
118 refresh_args: Option<RefreshableMaterializeArgs<S, SD>>,
120
121 local_barrier_manager: LocalBarrierManager,
123}
124
125pub struct RefreshableMaterializeArgs<S: StateStore, SD: ValueRowSerde> {
127 pub table_catalog: Table,
129
130 pub staging_table_catalog: Table,
132
133 pub staging_table: StateTableInner<S, SD>,
142
143 pub progress_table: RefreshProgressTable<S>,
145
146 pub refresh_start_prev_epoch: Option<u64>,
150
151 pub table_id: TableId,
153}
154
155impl<S: StateStore, SD: ValueRowSerde> RefreshableMaterializeArgs<S, SD> {
156 fn is_refreshing(&self) -> bool {
157 self.refresh_start_prev_epoch.is_some()
158 }
159
160 pub async fn new(
162 store: S,
163 table_catalog: &Table,
164 staging_table_catalog: &Table,
165 progress_state_table: &Table,
166 vnodes: Option<Arc<Bitmap>>,
167 ) -> Self {
168 let table_id = table_catalog.id;
169
170 let staging_table = StateTableInner::from_table_catalog_inconsistent_op(
172 staging_table_catalog,
173 store.clone(),
174 vnodes.clone(),
175 )
176 .await;
177
178 let progress_state_table = StateTableInner::from_table_catalog_inconsistent_op(
179 progress_state_table,
180 store,
181 vnodes,
182 )
183 .await;
184
185 let pk_len = table_catalog.pk.len();
187 let progress_table = RefreshProgressTable::new(progress_state_table, pk_len);
188
189 debug_assert_eq!(staging_table.vnodes(), progress_table.vnodes());
190
191 Self {
192 table_catalog: table_catalog.clone(),
193 staging_table_catalog: staging_table_catalog.clone(),
194 staging_table,
195 progress_table,
196 refresh_start_prev_epoch: None,
197 table_id,
198 }
199 }
200}
201
202fn get_op_consistency_level(
203 cleaned_by_ttl_watermark: bool,
204 conflict_behavior: ConflictBehavior,
205 may_have_downstream: bool,
206 subscriber_ids: &HashSet<SubscriberId>,
207) -> StateTableOpConsistencyLevel {
208 if cleaned_by_ttl_watermark {
209 StateTableOpConsistencyLevel::Inconsistent
213 } else if !subscriber_ids.is_empty() {
214 StateTableOpConsistencyLevel::LogStoreEnabled
215 } else if !may_have_downstream && matches!(conflict_behavior, ConflictBehavior::Overwrite) {
216 StateTableOpConsistencyLevel::Inconsistent
219 } else {
220 StateTableOpConsistencyLevel::ConsistentOldValue
221 }
222}
223
224impl<S: StateStore, SD: ValueRowSerde> MaterializeExecutor<S, SD> {
225 #[expect(clippy::too_many_arguments)]
229 pub async fn new(
230 input: Executor,
231 schema: Schema,
232 store: S,
233 arrange_key: Vec<ColumnOrder>,
234 actor_context: ActorContextRef,
235 vnodes: Option<Arc<Bitmap>>,
236 table_catalog: &Table,
237 watermark_epoch: AtomicU64Ref,
238 conflict_behavior: ConflictBehavior,
239 version_column_indices: Vec<u32>,
240 metrics: Arc<StreamingMetrics>,
241 refresh_args: Option<RefreshableMaterializeArgs<S, SD>>,
242 cleaned_by_ttl_watermark: bool,
243 local_barrier_manager: LocalBarrierManager,
244 ) -> Self {
245 let table_columns: Vec<ColumnDesc> = table_catalog
246 .columns
247 .iter()
248 .map(|col| col.column_desc.as_ref().unwrap().into())
249 .collect();
250
251 let toastable_column_indices = if table_catalog.cdc_table_type()
254 == risingwave_pb::catalog::table::CdcTableType::Postgres
255 {
256 let toastable_indices: Vec<usize> = table_columns
257 .iter()
258 .enumerate()
259 .filter_map(|(index, column)| match &column.data_type {
260 DataType::Varchar
270 | DataType::List(_)
271 | DataType::Bytea
272 | DataType::Jsonb
273 | DataType::Vector(_) => Some(index),
274 _ => None,
275 })
276 .collect();
277
278 if toastable_indices.is_empty() {
279 None
280 } else {
281 Some(toastable_indices)
282 }
283 } else {
284 None
285 };
286
287 let row_serde: BasicSerde = BasicSerde::new(
288 Arc::from_iter(table_catalog.value_indices.iter().map(|val| *val as usize)),
289 Arc::from(table_columns.into_boxed_slice()),
290 );
291
292 let stream_key_indices: Vec<usize> = table_catalog
293 .stream_key
294 .iter()
295 .map(|idx| *idx as usize)
296 .collect();
297 let arrange_key_indices: Vec<usize> = arrange_key.iter().map(|k| k.column_index).collect();
298 let may_have_downstream = actor_context.initial_dispatch_num != 0;
299 let subscriber_ids = actor_context.initial_subscriber_ids.clone();
300 let op_consistency_level = get_op_consistency_level(
301 cleaned_by_ttl_watermark,
302 conflict_behavior,
303 may_have_downstream,
304 &subscriber_ids,
305 );
306 let state_table_metrics = metrics.new_state_table_metrics(
307 table_catalog.id,
308 actor_context.id,
309 actor_context.fragment_id,
310 );
311 let state_table = StateTableBuilder::new(table_catalog, store, vnodes)
313 .with_op_consistency_level(op_consistency_level)
314 .enable_preload_all_rows_by_config(&actor_context.config)
315 .enable_vnode_key_stats(
316 actor_context
317 .config
318 .developer
319 .enable_vnode_key_stats_for_materialize,
320 &actor_context.config,
321 )
322 .with_metrics(state_table_metrics)
323 .build()
324 .await;
325
326 let mv_metrics = metrics.new_materialize_metrics(
327 table_catalog.id,
328 actor_context.id,
329 actor_context.fragment_id,
330 );
331 let cache_metrics = metrics.new_materialize_cache_metrics(
332 table_catalog.id,
333 actor_context.id,
334 actor_context.fragment_id,
335 );
336
337 let metrics_info =
338 MetricsInfo::new(metrics, table_catalog.id, actor_context.id, "Materialize");
339
340 let is_dummy_table =
341 table_catalog.engine == Some(Engine::Iceberg as i32) && table_catalog.append_only;
342
343 Self {
344 input,
345 schema,
346 state_table,
347 stream_key_indices,
348 arrange_key_indices,
349 actor_context,
350 materialize_cache: MaterializeCache::new(
351 watermark_epoch,
352 metrics_info,
353 row_serde,
354 version_column_indices.clone(),
355 conflict_behavior,
356 toastable_column_indices.clone(),
357 cache_metrics,
358 ),
359 toastable_column_indices,
360 conflict_behavior,
361 cleaned_by_ttl_watermark,
362 version_column_indices,
363 is_dummy_table,
364 may_have_downstream,
365 subscriber_ids,
366 metrics: mv_metrics,
367 refresh_args,
368 local_barrier_manager,
369 }
370 }
371
372 #[try_stream(ok = Message, error = StreamExecutorError)]
373 async fn execute_inner(mut self) {
374 let mv_table_id = self.state_table.table_id();
375 let data_types = self.schema.data_types();
376 let mut input = self.input.execute();
377
378 let barrier = expect_first_barrier(&mut input).await?;
379 let first_epoch = barrier.epoch;
380 let _barrier_epoch = barrier.epoch; yield Message::Barrier(barrier);
383 self.state_table.init_epoch(first_epoch).await?;
384
385 let mut inner_state =
387 Box::new(MaterializeStreamState::<BarrierMutationType>::NormalIngestion);
388 if let Some(ref mut refresh_args) = self.refresh_args {
389 refresh_args.staging_table.init_epoch(first_epoch).await?;
390 refresh_args.progress_table.recover(first_epoch).await?;
391
392 let progress_stats = refresh_args.progress_table.get_progress_stats();
395 if progress_stats.total_vnodes > 0 && !progress_stats.is_complete() {
396 tracing::info!(
397 table_id = %refresh_args.table_id,
398 completed_vnodes = progress_stats.completed_vnodes,
399 total_vnodes = progress_stats.total_vnodes,
400 "found progress of a refresh interrupted by recovery"
401 );
402 }
403 }
404
405 'main_loop: loop {
407 match *inner_state {
408 MaterializeStreamState::NormalIngestion => {
409 #[for_await]
410 '_normal_ingest: for msg in input.by_ref() {
411 let msg = msg?;
412 if let Some(cache) = &mut self.materialize_cache {
413 cache.evict();
414 }
415
416 match msg {
417 Message::Watermark(w) => {
418 if self.cleaned_by_ttl_watermark
419 && self.state_table.clean_watermark_index == Some(w.col_idx)
420 {
421 self.state_table.update_watermark(w.val.clone());
422 }
423 yield Message::Watermark(w);
424 }
425 Message::Chunk(chunk) if self.is_dummy_table => {
426 self.metrics
427 .materialize_input_row_count
428 .inc_by(chunk.cardinality() as u64);
429 yield Message::Chunk(chunk);
430 }
431 Message::Chunk(chunk) => {
432 self.metrics
433 .materialize_input_row_count
434 .inc_by(chunk.cardinality() as u64);
435
436 let optimized_conflict_behavior = if let ConflictBehavior::Overwrite =
449 self.conflict_behavior
450 && !self.state_table.is_consistent_op()
451 && !self.cleaned_by_ttl_watermark
452 && self.version_column_indices.is_empty()
453 && self.toastable_column_indices.is_none()
454 {
455 ConflictBehavior::NoCheck
456 } else {
457 self.conflict_behavior
458 };
459
460 match optimized_conflict_behavior {
461 checked_conflict_behaviors!() => {
462 if chunk.cardinality() == 0 {
463 continue;
465 }
466
467 if let Some(ref mut refresh_args) = self.refresh_args
470 && refresh_args.is_refreshing()
471 {
472 let key_chunk = chunk
473 .clone()
474 .project(self.state_table.pk_indices());
475 tracing::trace!(
476 staging_chunk = %key_chunk.to_pretty(),
477 input_chunk = %chunk.to_pretty(),
478 "writing to staging table"
479 );
480 if cfg!(debug_assertions) {
481 assert!(
483 key_chunk
484 .ops()
485 .iter()
486 .all(|op| op == &Op::Insert)
487 );
488 }
489 refresh_args
490 .staging_table
491 .write_chunk(key_chunk.clone());
492 refresh_args.staging_table.try_flush().await?;
493 }
494
495 let cache = self.materialize_cache.as_mut().unwrap();
496 let change_buffer =
497 cache.handle_new(chunk, &self.state_table).await?;
498
499 let output_chunk = if self.stream_key_indices
500 == self.state_table.pk_indices()
501 {
502 change_buffer.into_chunk::<{ cb_kind::RETRACT }>(
503 data_types.clone(),
504 )
505 } else {
506 debug_assert!(
509 self.state_table
510 .pk_indices()
511 .iter()
512 .all(|&i| self.stream_key_indices.contains(&i))
513 );
514 change_buffer.into_chunk_with_key(
515 data_types.clone(),
516 &self.stream_key_indices,
517 )
518 };
519
520 match output_chunk {
521 Some(output_chunk) => {
522 self.state_table.write_chunk(output_chunk.clone());
523 self.state_table.try_flush().await?;
524 yield Message::Chunk(output_chunk);
525 }
526 None => continue,
527 }
528 }
529 ConflictBehavior::NoCheck => {
530 self.state_table.write_chunk(chunk.clone());
531 self.state_table.try_flush().await?;
532
533 if let Some(ref mut refresh_args) = self.refresh_args
535 && refresh_args.is_refreshing()
536 {
537 let key_chunk = chunk
538 .clone()
539 .project(self.state_table.pk_indices());
540 tracing::trace!(
541 staging_chunk = %key_chunk.to_pretty(),
542 input_chunk = %chunk.to_pretty(),
543 "writing to staging table"
544 );
545 if cfg!(debug_assertions) {
546 assert!(
548 key_chunk
549 .ops()
550 .iter()
551 .all(|op| op == &Op::Insert)
552 );
553 }
554 refresh_args
555 .staging_table
556 .write_chunk(key_chunk.clone());
557 refresh_args.staging_table.try_flush().await?;
558 }
559
560 yield Message::Chunk(chunk);
561 }
562 }
563 }
564 Message::Barrier(barrier) => {
565 *inner_state = MaterializeStreamState::CommitAndYieldBarrier {
566 barrier,
567 expect_next_state: Box::new(
568 MaterializeStreamState::NormalIngestion,
569 ),
570 };
571 continue 'main_loop;
572 }
573 }
574 }
575
576 return Err(StreamExecutorError::from(ErrorKind::Uncategorized(
577 anyhow::anyhow!(
578 "Input stream terminated unexpectedly during normal ingestion"
579 ),
580 )));
581 }
582 MaterializeStreamState::MergingData => {
583 let Some(refresh_args) = self.refresh_args.as_mut() else {
584 panic!(
585 "MaterializeExecutor entered CleanUp state without refresh_args configured"
586 );
587 };
588 tracing::info!(table_id = %refresh_args.table_id, "on_load_finish: Starting table replacement operation");
589
590 debug_assert_eq!(
591 self.state_table.vnodes(),
592 refresh_args.staging_table.vnodes()
593 );
594 debug_assert_eq!(
595 refresh_args.staging_table.vnodes(),
596 refresh_args.progress_table.vnodes()
597 );
598
599 let mut rows_to_delete = vec![];
600 let mut merge_complete = false;
601 let mut pending_barrier: Option<Barrier> = None;
602
603 {
605 let left_input = input.by_ref().map(Either::Left);
606 let right_merge_sort = pin!(
607 Self::make_mergesort_stream(
608 &self.state_table,
609 &refresh_args.staging_table,
610 &mut refresh_args.progress_table
611 )
612 .map(Either::Right)
613 );
614
615 let mut merge_stream =
618 select_with_strategy(left_input, right_merge_sort, |_: &mut ()| {
619 stream::PollNext::Left
620 });
621
622 #[for_await]
623 'merge_stream: for either in &mut merge_stream {
624 match either {
625 Either::Left(msg) => {
626 let msg = msg?;
627 match msg {
628 Message::Watermark(w) => yield Message::Watermark(w),
629 Message::Chunk(chunk) => {
630 tracing::warn!(chunk = %chunk.to_pretty(), "chunk is ignored during merge phase");
631 }
632 Message::Barrier(b) => {
633 pending_barrier = Some(b);
634 break 'merge_stream;
635 }
636 }
637 }
638 Either::Right(result) => {
639 match result? {
640 Some((_vnode, row)) => {
641 rows_to_delete.push(row);
642 }
643 None => {
644 merge_complete = true;
646
647 }
649 }
650 }
651 }
652 }
653 }
654
655 for row in &rows_to_delete {
657 self.state_table.delete(row);
658 }
659 if let Some(cache) = &mut self.materialize_cache {
660 cache.invalidate_rows(&rows_to_delete, &self.state_table);
661 }
662 if !rows_to_delete.is_empty() {
663 let to_delete_chunk = StreamChunk::from_rows(
664 &rows_to_delete
665 .iter()
666 .map(|row| (Op::Delete, row))
667 .collect_vec(),
668 &self.schema.data_types(),
669 );
670
671 yield Message::Chunk(to_delete_chunk);
672 }
673
674 assert!(pending_barrier.is_some(), "pending barrier is not set");
676
677 *inner_state = MaterializeStreamState::CommitAndYieldBarrier {
678 barrier: pending_barrier.unwrap(),
679 expect_next_state: if merge_complete {
680 Box::new(MaterializeStreamState::CleanUp)
681 } else {
682 Box::new(MaterializeStreamState::MergingData)
683 },
684 };
685 continue 'main_loop;
686 }
687 MaterializeStreamState::CleanUp => {
688 let Some(refresh_args) = self.refresh_args.as_mut() else {
689 panic!(
690 "MaterializeExecutor entered MergingData state without refresh_args configured"
691 );
692 };
693 tracing::info!(table_id = %refresh_args.table_id, "on_load_finish: resuming CleanUp Stage");
694
695 #[for_await]
696 for msg in input.by_ref() {
697 let msg = msg?;
698 match msg {
699 Message::Watermark(w) => {
700 if self.cleaned_by_ttl_watermark
701 && self.state_table.clean_watermark_index == Some(w.col_idx)
702 {
703 self.state_table.update_watermark(w.val.clone());
704 }
705 yield Message::Watermark(w)
706 }
707 Message::Chunk(chunk) => {
708 tracing::warn!(chunk = %chunk.to_pretty(), "chunk is ignored during merge phase");
709 }
710 Message::Barrier(barrier) if !barrier.is_checkpoint() => {
711 *inner_state = MaterializeStreamState::CommitAndYieldBarrier {
712 barrier,
713 expect_next_state: Box::new(MaterializeStreamState::CleanUp),
714 };
715 continue 'main_loop;
716 }
717 Message::Barrier(barrier) => {
718 self.local_barrier_manager.report_refresh_finished(
719 barrier.epoch,
720 self.actor_context.id,
721 refresh_args.table_id,
722 );
723 tracing::debug!(table_id = %refresh_args.table_id, "on_load_finish: reported refresh finished");
724
725 *inner_state = MaterializeStreamState::CommitAndYieldBarrier {
726 barrier,
727 expect_next_state: Box::new(MaterializeStreamState::RefreshEnd),
728 };
729 continue 'main_loop;
730 }
731 }
732 }
733 }
734 MaterializeStreamState::RefreshEnd => {
735 let Some(refresh_args) = self.refresh_args.as_mut() else {
736 panic!(
737 "MaterializeExecutor entered RefreshEnd state without refresh_args configured"
738 );
739 };
740 tracing::info!(table_id = %refresh_args.table_id, "RefreshEnd: Refresh completed");
741 refresh_args.refresh_start_prev_epoch = None;
742 *inner_state = MaterializeStreamState::NormalIngestion;
743 continue 'main_loop;
744 }
745 MaterializeStreamState::CommitAndYieldBarrier {
746 barrier,
747 mut expect_next_state,
748 } => {
749 let mut wait_staging_visible = None;
750 if let Some(ref mut refresh_args) = self.refresh_args {
751 match barrier.mutation.as_deref() {
752 Some(Mutation::RefreshStart {
753 table_id: refresh_table_id,
754 associated_source_id: _,
755 }) if *refresh_table_id == refresh_args.table_id => {
756 if refresh_args.is_refreshing() {
757 consistency_panic!(
758 table_id = %refresh_table_id,
759 "RefreshStart received while a refresh is in progress"
760 );
761 }
762 refresh_args.refresh_start_prev_epoch = Some(barrier.epoch.prev);
763 tracing::info!(table_id = %refresh_table_id, "RefreshStart barrier received");
764
765 Self::init_refresh_progress(
767 &self.state_table,
768 &mut refresh_args.progress_table,
769 barrier.epoch.curr,
770 )?;
771 }
772 Some(Mutation::LoadFinish {
773 associated_source_id: load_finish_source_id,
774 }) => {
775 let associated_source_id: SourceId = match refresh_args
777 .table_catalog
778 .optional_associated_source_id
779 {
780 Some(id) => id.into(),
781 None => unreachable!("associated_source_id is not set"),
782 };
783
784 if *load_finish_source_id == associated_source_id {
785 if !refresh_args.is_refreshing() {
786 consistency_panic!(
788 table_id = %refresh_args.table_id,
789 "LoadFinish received without a refresh in progress"
790 );
791 } else {
792 tracing::info!(
793 %load_finish_source_id,
794 "LoadFinish received, starting data replacement"
795 );
796 *expect_next_state =
797 MaterializeStreamState::<_>::MergingData;
798 wait_staging_visible =
799 refresh_args.refresh_start_prev_epoch;
800 }
801 }
802 }
803 _ => {}
804 }
805 }
806
807 if !self.may_have_downstream
811 && barrier.has_more_downstream_fragments(self.actor_context.id)
812 {
813 self.may_have_downstream = true;
814 }
815 Self::may_update_depended_subscriptions(
816 &mut self.subscriber_ids,
817 &barrier,
818 mv_table_id,
819 );
820 let op_consistency_level = get_op_consistency_level(
821 self.cleaned_by_ttl_watermark,
822 self.conflict_behavior,
823 self.may_have_downstream,
824 &self.subscriber_ids,
825 );
826 let post_commit = self
827 .state_table
828 .commit_may_switch_consistent_op(barrier.epoch, op_consistency_level)
829 .await?;
830
831 let update_vnode_bitmap = barrier.as_update_vnode_bitmap(self.actor_context.id);
832
833 let refresh_post_commit = if let Some(ref mut refresh_args) = self.refresh_args
835 {
836 Some((
837 refresh_args.staging_table.commit(barrier.epoch).await?,
838 refresh_args.progress_table.commit(barrier.epoch).await?,
839 ))
840 } else {
841 None
842 };
843
844 let b_epoch = barrier.epoch;
845 yield Message::Barrier(barrier);
846
847 if let Some((_, cache_may_stale)) = post_commit
849 .post_yield_barrier(update_vnode_bitmap.clone())
850 .await?
851 && cache_may_stale
852 && let Some(cache) = &mut self.materialize_cache
853 {
854 cache.clear();
855 }
856
857 if let Some((staging_post_commit, progress_post_commit)) = refresh_post_commit {
859 staging_post_commit
860 .post_yield_barrier(update_vnode_bitmap.clone())
861 .await?;
862 progress_post_commit
863 .post_yield_barrier(update_vnode_bitmap)
864 .await?;
865 }
866
867 if let (Some(epoch), Some(refresh_args)) =
868 (wait_staging_visible, self.refresh_args.as_ref())
869 {
870 refresh_args
873 .staging_table
874 .try_wait_committed_epoch(epoch)
875 .await?;
876 }
877
878 self.metrics
879 .materialize_current_epoch
880 .set(b_epoch.curr as i64);
881
882 *inner_state = *expect_next_state;
885 }
886 }
887 }
888 }
889
890 #[try_stream(ok = Option<(VirtualNode, OwnedRow)>, error = StreamExecutorError)]
894 async fn make_mergesort_stream<'a>(
895 main_table: &'a StateTableInner<S, SD>,
896 staging_table: &'a StateTableInner<S, SD>,
897 progress_table: &'a mut RefreshProgressTable<S>,
898 ) {
899 for vnode in main_table.vnodes().clone().iter_vnodes() {
900 let mut processed_rows = 0;
901 let pk_range: (Bound<OwnedRow>, Bound<OwnedRow>) =
903 if let Some(current_entry) = progress_table.get_progress(vnode) {
904 if current_entry.is_completed {
905 tracing::debug!(vnode = vnode.to_index(), "vnode already merged");
906 continue;
907 }
908 processed_rows += current_entry.processed_rows;
909 tracing::debug!(vnode = vnode.to_index(), "Started merging VNode");
910
911 if let Some(current_state) = ¤t_entry.current_pos {
912 (Bound::Excluded(current_state.clone()), Bound::Unbounded)
913 } else {
914 (Bound::Unbounded, Bound::Unbounded)
915 }
916 } else {
917 (Bound::Unbounded, Bound::Unbounded)
918 };
919
920 let iter_main = main_table
921 .iter_keyed_row_with_vnode(
922 vnode,
923 &pk_range,
924 PrefetchOptions::prefetch_for_large_range_scan(),
925 )
926 .await?;
927 let iter_staging = staging_table
928 .iter_keyed_row_with_vnode(
929 vnode,
930 &pk_range,
931 PrefetchOptions::prefetch_for_large_range_scan(),
932 )
933 .await?;
934
935 pin_mut!(iter_main);
936 pin_mut!(iter_staging);
937
938 let mut main_item: Option<KeyedRow<Bytes>> = iter_main.next().await.transpose()?;
940 let mut staging_item: Option<KeyedRow<Bytes>> =
941 iter_staging.next().await.transpose()?;
942
943 while let Some(main_kv) = main_item {
944 let main_key = main_kv.key();
945
946 let mut should_delete = false;
948 while let Some(staging_kv) = &staging_item {
949 let staging_key = staging_kv.key();
950 match main_key.cmp(staging_key) {
951 std::cmp::Ordering::Greater => {
952 staging_item = iter_staging.next().await.transpose()?;
954 }
955 std::cmp::Ordering::Equal => {
956 break;
958 }
959 std::cmp::Ordering::Less => {
960 should_delete = true;
962 break;
963 }
964 }
965 }
966
967 if staging_item.is_none() {
969 should_delete = true;
970 }
971
972 if should_delete {
973 yield Some((vnode, main_kv.row().clone()));
974 }
975
976 processed_rows += 1;
978 tracing::debug!(
979 "set progress table: vnode = {:?}, processed_rows = {:?}",
980 vnode,
981 processed_rows
982 );
983 progress_table.set_progress(
984 vnode,
985 Some(
986 main_kv
987 .row()
988 .project(main_table.pk_indices())
989 .to_owned_row(),
990 ),
991 false,
992 processed_rows,
993 )?;
994 main_item = iter_main.next().await.transpose()?;
995 }
996
997 if let Some(current_entry) = progress_table.get_progress(vnode) {
999 progress_table.set_progress(
1000 vnode,
1001 current_entry.current_pos.clone(),
1002 true, current_entry.processed_rows,
1004 )?;
1005
1006 tracing::debug!(vnode = vnode.to_index(), "Completed merging VNode");
1007 }
1008 }
1009
1010 yield None;
1012 }
1013
1014 fn may_update_depended_subscriptions(
1016 depended_subscriptions: &mut HashSet<SubscriberId>,
1017 barrier: &Barrier,
1018 mv_table_id: TableId,
1019 ) {
1020 for subscriber_id in barrier.added_subscriber_on_mv_table(mv_table_id) {
1021 if !depended_subscriptions.insert(subscriber_id) {
1022 warn!(
1023 ?depended_subscriptions,
1024 %mv_table_id,
1025 %subscriber_id,
1026 "subscription id already exists"
1027 );
1028 }
1029 }
1030
1031 if let Some(subscriptions_to_drop) = barrier.as_subscriptions_to_drop() {
1032 for SubscriptionUpstreamInfo {
1033 subscriber_id,
1034 upstream_mv_table_id,
1035 } in subscriptions_to_drop
1036 {
1037 if *upstream_mv_table_id == mv_table_id
1038 && !depended_subscriptions.remove(subscriber_id)
1039 {
1040 warn!(
1041 ?depended_subscriptions,
1042 %mv_table_id,
1043 %subscriber_id,
1044 "drop non existing subscriber_id id"
1045 );
1046 }
1047 }
1048 }
1049 }
1050
1051 fn init_refresh_progress(
1053 state_table: &StateTableInner<S, SD>,
1054 progress_table: &mut RefreshProgressTable<S>,
1055 _epoch: u64,
1056 ) -> StreamExecutorResult<()> {
1057 debug_assert_eq!(state_table.vnodes(), progress_table.vnodes());
1058
1059 for vnode in state_table.vnodes().iter_vnodes() {
1061 progress_table.set_progress(
1062 vnode, None, false, 0, )?;
1066 }
1067
1068 tracing::info!(
1069 vnodes_count = state_table.vnodes().count_ones(),
1070 "Initialized refresh progress tracking for all VNodes"
1071 );
1072
1073 Ok(())
1074 }
1075}
1076
1077impl<S: StateStore> MaterializeExecutor<S, BasicSerde> {
1078 #[cfg(any(test, feature = "test"))]
1080 pub async fn for_test(
1081 input: Executor,
1082 store: S,
1083 table_id: TableId,
1084 keys: Vec<ColumnOrder>,
1085 column_ids: Vec<risingwave_common::catalog::ColumnId>,
1086 watermark_epoch: AtomicU64Ref,
1087 conflict_behavior: ConflictBehavior,
1088 ) -> Self {
1089 Self::for_test_inner(
1090 input,
1091 store,
1092 table_id,
1093 keys,
1094 column_ids,
1095 watermark_epoch,
1096 conflict_behavior,
1097 None,
1098 None,
1099 )
1100 .await
1101 }
1102
1103 #[cfg(any(test, feature = "test"))]
1104 #[expect(clippy::too_many_arguments)]
1105 pub async fn for_test_with_stream_key(
1106 input: Executor,
1107 store: S,
1108 table_id: TableId,
1109 keys: Vec<ColumnOrder>,
1110 stream_key: Vec<usize>,
1111 column_ids: Vec<risingwave_common::catalog::ColumnId>,
1112 watermark_epoch: AtomicU64Ref,
1113 conflict_behavior: ConflictBehavior,
1114 ) -> Self {
1115 Self::for_test_inner(
1116 input,
1117 store,
1118 table_id,
1119 keys,
1120 column_ids,
1121 watermark_epoch,
1122 conflict_behavior,
1123 Some(stream_key),
1124 None,
1125 )
1126 .await
1127 }
1128
1129 #[cfg(any(test, feature = "test"))]
1132 #[expect(clippy::too_many_arguments)]
1133 pub async fn for_test_with_ttl(
1134 input: Executor,
1135 store: S,
1136 table_id: TableId,
1137 keys: Vec<ColumnOrder>,
1138 column_ids: Vec<risingwave_common::catalog::ColumnId>,
1139 watermark_epoch: AtomicU64Ref,
1140 conflict_behavior: ConflictBehavior,
1141 ttl_column: usize,
1142 ) -> Self {
1143 let stream_key = keys
1144 .iter()
1145 .map(|k| k.column_index)
1146 .chain(std::iter::once(ttl_column))
1147 .collect();
1148 Self::for_test_inner(
1149 input,
1150 store,
1151 table_id,
1152 keys,
1153 column_ids,
1154 watermark_epoch,
1155 conflict_behavior,
1156 Some(stream_key),
1157 Some(ttl_column),
1158 )
1159 .await
1160 }
1161
1162 #[cfg(any(test, feature = "test"))]
1163 #[expect(clippy::too_many_arguments)]
1164 async fn for_test_inner(
1165 input: Executor,
1166 store: S,
1167 table_id: TableId,
1168 keys: Vec<ColumnOrder>,
1169 column_ids: Vec<risingwave_common::catalog::ColumnId>,
1170 watermark_epoch: AtomicU64Ref,
1171 conflict_behavior: ConflictBehavior,
1172 stream_key: Option<Vec<usize>>,
1173 ttl_column: Option<usize>,
1174 ) -> Self {
1175 use risingwave_common::util::iter_util::ZipEqFast;
1176
1177 let arrange_columns: Vec<usize> = keys.iter().map(|k| k.column_index).collect();
1178 let arrange_order_types = keys.iter().map(|k| k.order_type).collect();
1179 let schema = input.schema().clone();
1180 let columns: Vec<ColumnDesc> = column_ids
1181 .into_iter()
1182 .zip_eq_fast(schema.fields.iter())
1183 .map(|(column_id, field)| ColumnDesc::unnamed(column_id, field.data_type()))
1184 .collect_vec();
1185
1186 let row_serde = BasicSerde::new(
1187 Arc::from((0..columns.len()).collect_vec()),
1188 Arc::from(columns.clone().into_boxed_slice()),
1189 );
1190 let stream_key_indices = stream_key.unwrap_or_else(|| arrange_columns.clone());
1191 let mut table_catalog = crate::common::table::test_utils::gen_pbtable(
1192 table_id,
1193 columns,
1194 arrange_order_types,
1195 arrange_columns.clone(),
1196 0,
1197 );
1198 table_catalog.stream_key = stream_key_indices.iter().map(|i| *i as i32).collect();
1199 let state_table = if let Some(ttl_column) = ttl_column {
1200 table_catalog.clean_watermark_indices = vec![ttl_column as u32];
1201 StateTableInner::from_table_catalog_inconsistent_op(&table_catalog, store, None).await
1202 } else {
1203 StateTableInner::from_table_catalog(&table_catalog, store, None).await
1204 };
1205
1206 let unused = StreamingMetrics::unused();
1207 let metrics = unused.new_materialize_metrics(table_id, 1.into(), 2.into());
1208 let cache_metrics = unused.new_materialize_cache_metrics(table_id, 1.into(), 2.into());
1209
1210 Self {
1211 input,
1212 schema,
1213 state_table,
1214 stream_key_indices,
1215 arrange_key_indices: arrange_columns.clone(),
1216 actor_context: ActorContext::for_test(0),
1217 materialize_cache: MaterializeCache::new(
1218 watermark_epoch,
1219 MetricsInfo::for_test(),
1220 row_serde,
1221 vec![],
1222 conflict_behavior,
1223 None,
1224 cache_metrics,
1225 ),
1226 toastable_column_indices: None,
1227 conflict_behavior,
1228 cleaned_by_ttl_watermark: ttl_column.is_some(),
1229 version_column_indices: vec![],
1230 is_dummy_table: false,
1231 may_have_downstream: true,
1232 subscriber_ids: HashSet::new(),
1233 metrics,
1234 refresh_args: None, local_barrier_manager: LocalBarrierManager::for_test(),
1236 }
1237 }
1238}
1239
1240impl<S: StateStore, SD: ValueRowSerde> Execute for MaterializeExecutor<S, SD> {
1241 fn execute(self: Box<Self>) -> BoxedMessageStream {
1242 self.execute_inner().boxed()
1243 }
1244}
1245
1246impl<S: StateStore, SD: ValueRowSerde> std::fmt::Debug for MaterializeExecutor<S, SD> {
1247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1248 f.debug_struct("MaterializeExecutor")
1249 .field("arrange_key_indices", &self.arrange_key_indices)
1250 .field("stream_key_indices", &self.stream_key_indices)
1251 .finish()
1252 }
1253}
1254
1255#[cfg(test)]
1256mod tests {
1257
1258 use std::iter;
1259 use std::sync::atomic::AtomicU64;
1260
1261 use rand::rngs::SmallRng;
1262 use rand::{Rng, RngCore, SeedableRng};
1263 use risingwave_common::array::stream_chunk::{StreamChunkMut, StreamChunkTestExt};
1264 use risingwave_common::catalog::Field;
1265 use risingwave_common::util::epoch::test_epoch;
1266 use risingwave_common::util::sort_util::OrderType;
1267 use risingwave_hummock_sdk::HummockReadEpoch;
1268 use risingwave_storage::memory::MemoryStateStore;
1269 use risingwave_storage::table::batch_table::BatchTable;
1270
1271 use super::*;
1272 use crate::executor::test_utils::*;
1273
1274 #[tokio::test]
1275 async fn test_materialize_executor() {
1276 let memory_state_store = MemoryStateStore::new();
1278 let table_id = TableId::new(1);
1279 let schema = Schema::new(vec![
1281 Field::unnamed(DataType::Int32),
1282 Field::unnamed(DataType::Int32),
1283 ]);
1284 let column_ids = vec![0.into(), 1.into()];
1285
1286 let chunk1 = StreamChunk::from_pretty(
1288 " i i
1289 + 1 4
1290 + 2 5
1291 + 3 6",
1292 );
1293 let chunk2 = StreamChunk::from_pretty(
1294 " i i
1295 + 7 8
1296 - 3 6",
1297 );
1298
1299 let source = MockSource::with_messages(vec![
1301 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1302 Message::Chunk(chunk1),
1303 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1304 Message::Chunk(chunk2),
1305 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1306 ])
1307 .into_executor(schema.clone(), StreamKey::new());
1308
1309 let order_types = vec![OrderType::ascending()];
1310 let column_descs = vec![
1311 ColumnDesc::unnamed(column_ids[0], DataType::Int32),
1312 ColumnDesc::unnamed(column_ids[1], DataType::Int32),
1313 ];
1314
1315 let table = BatchTable::for_test(
1316 memory_state_store.clone(),
1317 table_id,
1318 column_descs,
1319 order_types,
1320 vec![0],
1321 vec![0, 1],
1322 );
1323
1324 let mut materialize_executor = MaterializeExecutor::for_test(
1325 source,
1326 memory_state_store,
1327 table_id,
1328 vec![ColumnOrder::new(0, OrderType::ascending())],
1329 column_ids,
1330 Arc::new(AtomicU64::new(0)),
1331 ConflictBehavior::NoCheck,
1332 )
1333 .await
1334 .boxed()
1335 .execute();
1336 materialize_executor.next().await.transpose().unwrap();
1337
1338 materialize_executor.next().await.transpose().unwrap();
1339
1340 match materialize_executor.next().await.transpose().unwrap() {
1342 Some(Message::Barrier(_)) => {
1343 let row = table
1344 .get_row(
1345 &OwnedRow::new(vec![Some(3_i32.into())]),
1346 HummockReadEpoch::NoWait(u64::MAX),
1347 )
1348 .await
1349 .unwrap();
1350 assert_eq!(
1351 row,
1352 Some(OwnedRow::new(vec![Some(3_i32.into()), Some(6_i32.into())]))
1353 );
1354 }
1355 _ => unreachable!(),
1356 }
1357 materialize_executor.next().await.transpose().unwrap();
1358 match materialize_executor.next().await.transpose().unwrap() {
1360 Some(Message::Barrier(_)) => {
1361 let row = table
1362 .get_row(
1363 &OwnedRow::new(vec![Some(7_i32.into())]),
1364 HummockReadEpoch::NoWait(u64::MAX),
1365 )
1366 .await
1367 .unwrap();
1368 assert_eq!(
1369 row,
1370 Some(OwnedRow::new(vec![Some(7_i32.into()), Some(8_i32.into())]))
1371 );
1372 }
1373 _ => unreachable!(),
1374 }
1375 }
1376
1377 #[tokio::test]
1379 async fn test_upsert_stream() {
1380 let memory_state_store = MemoryStateStore::new();
1382 let table_id = TableId::new(1);
1383 let schema = Schema::new(vec![
1385 Field::unnamed(DataType::Int32),
1386 Field::unnamed(DataType::Int32),
1387 ]);
1388 let column_ids = vec![0.into(), 1.into()];
1389
1390 let chunk1 = StreamChunk::from_pretty(
1392 " i i
1393 + 1 1",
1394 );
1395
1396 let chunk2 = StreamChunk::from_pretty(
1397 " i i
1398 + 1 2
1399 - 1 2",
1400 );
1401
1402 let source = MockSource::with_messages(vec![
1404 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1405 Message::Chunk(chunk1),
1406 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1407 Message::Chunk(chunk2),
1408 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1409 ])
1410 .into_executor(schema.clone(), StreamKey::new());
1411
1412 let order_types = vec![OrderType::ascending()];
1413 let column_descs = vec![
1414 ColumnDesc::unnamed(column_ids[0], DataType::Int32),
1415 ColumnDesc::unnamed(column_ids[1], DataType::Int32),
1416 ];
1417
1418 let table = BatchTable::for_test(
1419 memory_state_store.clone(),
1420 table_id,
1421 column_descs,
1422 order_types,
1423 vec![0],
1424 vec![0, 1],
1425 );
1426
1427 let mut materialize_executor = MaterializeExecutor::for_test(
1428 source,
1429 memory_state_store,
1430 table_id,
1431 vec![ColumnOrder::new(0, OrderType::ascending())],
1432 column_ids,
1433 Arc::new(AtomicU64::new(0)),
1434 ConflictBehavior::Overwrite,
1435 )
1436 .await
1437 .boxed()
1438 .execute();
1439 materialize_executor.next().await.transpose().unwrap();
1440
1441 materialize_executor.next().await.transpose().unwrap();
1442 materialize_executor.next().await.transpose().unwrap();
1443 materialize_executor.next().await.transpose().unwrap();
1444
1445 match materialize_executor.next().await.transpose().unwrap() {
1446 Some(Message::Barrier(_)) => {
1447 let row = table
1448 .get_row(
1449 &OwnedRow::new(vec![Some(1_i32.into())]),
1450 HummockReadEpoch::NoWait(u64::MAX),
1451 )
1452 .await
1453 .unwrap();
1454 assert!(row.is_none());
1455 }
1456 _ => unreachable!(),
1457 }
1458 }
1459
1460 #[tokio::test]
1461 async fn test_check_insert_conflict() {
1462 let memory_state_store = MemoryStateStore::new();
1464 let table_id = TableId::new(1);
1465 let schema = Schema::new(vec![
1467 Field::unnamed(DataType::Int32),
1468 Field::unnamed(DataType::Int32),
1469 ]);
1470 let column_ids = vec![0.into(), 1.into()];
1471
1472 let chunk1 = StreamChunk::from_pretty(
1474 " i i
1475 + 1 3
1476 + 1 4
1477 + 2 5
1478 + 3 6",
1479 );
1480
1481 let chunk2 = StreamChunk::from_pretty(
1482 " i i
1483 + 1 3
1484 + 2 6",
1485 );
1486
1487 let chunk3 = StreamChunk::from_pretty(
1489 " i i
1490 + 1 4",
1491 );
1492
1493 let source = MockSource::with_messages(vec![
1495 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1496 Message::Chunk(chunk1),
1497 Message::Chunk(chunk2),
1498 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1499 Message::Chunk(chunk3),
1500 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1501 ])
1502 .into_executor(schema.clone(), StreamKey::new());
1503
1504 let order_types = vec![OrderType::ascending()];
1505 let column_descs = vec![
1506 ColumnDesc::unnamed(column_ids[0], DataType::Int32),
1507 ColumnDesc::unnamed(column_ids[1], DataType::Int32),
1508 ];
1509
1510 let table = BatchTable::for_test(
1511 memory_state_store.clone(),
1512 table_id,
1513 column_descs,
1514 order_types,
1515 vec![0],
1516 vec![0, 1],
1517 );
1518
1519 let mut materialize_executor = MaterializeExecutor::for_test(
1520 source,
1521 memory_state_store,
1522 table_id,
1523 vec![ColumnOrder::new(0, OrderType::ascending())],
1524 column_ids,
1525 Arc::new(AtomicU64::new(0)),
1526 ConflictBehavior::Overwrite,
1527 )
1528 .await
1529 .boxed()
1530 .execute();
1531 materialize_executor.next().await.transpose().unwrap();
1532
1533 materialize_executor.next().await.transpose().unwrap();
1534 materialize_executor.next().await.transpose().unwrap();
1535
1536 match materialize_executor.next().await.transpose().unwrap() {
1538 Some(Message::Barrier(_)) => {
1539 let row = table
1540 .get_row(
1541 &OwnedRow::new(vec![Some(3_i32.into())]),
1542 HummockReadEpoch::NoWait(u64::MAX),
1543 )
1544 .await
1545 .unwrap();
1546 assert_eq!(
1547 row,
1548 Some(OwnedRow::new(vec![Some(3_i32.into()), Some(6_i32.into())]))
1549 );
1550
1551 let row = table
1552 .get_row(
1553 &OwnedRow::new(vec![Some(1_i32.into())]),
1554 HummockReadEpoch::NoWait(u64::MAX),
1555 )
1556 .await
1557 .unwrap();
1558 assert_eq!(
1559 row,
1560 Some(OwnedRow::new(vec![Some(1_i32.into()), Some(3_i32.into())]))
1561 );
1562
1563 let row = table
1564 .get_row(
1565 &OwnedRow::new(vec![Some(2_i32.into())]),
1566 HummockReadEpoch::NoWait(u64::MAX),
1567 )
1568 .await
1569 .unwrap();
1570 assert_eq!(
1571 row,
1572 Some(OwnedRow::new(vec![Some(2_i32.into()), Some(6_i32.into())]))
1573 );
1574 }
1575 _ => unreachable!(),
1576 }
1577 }
1578
1579 #[tokio::test]
1580 async fn test_delete_and_update_conflict() {
1581 let memory_state_store = MemoryStateStore::new();
1583 let table_id = TableId::new(1);
1584 let schema = Schema::new(vec![
1586 Field::unnamed(DataType::Int32),
1587 Field::unnamed(DataType::Int32),
1588 ]);
1589 let column_ids = vec![0.into(), 1.into()];
1590
1591 let chunk1 = StreamChunk::from_pretty(
1593 " i i
1594 + 1 4
1595 + 2 5
1596 + 3 6
1597 U- 8 1
1598 U+ 8 2
1599 + 8 3",
1600 );
1601
1602 let chunk2 = StreamChunk::from_pretty(
1604 " i i
1605 + 7 8
1606 - 3 4
1607 - 5 0",
1608 );
1609
1610 let chunk3 = StreamChunk::from_pretty(
1612 " i i
1613 + 1 5
1614 U- 2 4
1615 U+ 2 8
1616 U- 9 0
1617 U+ 9 1",
1618 );
1619
1620 let source = MockSource::with_messages(vec![
1622 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1623 Message::Chunk(chunk1),
1624 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1625 Message::Chunk(chunk2),
1626 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1627 Message::Chunk(chunk3),
1628 Message::Barrier(Barrier::new_test_barrier(test_epoch(4))),
1629 ])
1630 .into_executor(schema.clone(), StreamKey::new());
1631
1632 let order_types = vec![OrderType::ascending()];
1633 let column_descs = vec![
1634 ColumnDesc::unnamed(column_ids[0], DataType::Int32),
1635 ColumnDesc::unnamed(column_ids[1], DataType::Int32),
1636 ];
1637
1638 let table = BatchTable::for_test(
1639 memory_state_store.clone(),
1640 table_id,
1641 column_descs,
1642 order_types,
1643 vec![0],
1644 vec![0, 1],
1645 );
1646
1647 let mut materialize_executor = MaterializeExecutor::for_test(
1648 source,
1649 memory_state_store,
1650 table_id,
1651 vec![ColumnOrder::new(0, OrderType::ascending())],
1652 column_ids,
1653 Arc::new(AtomicU64::new(0)),
1654 ConflictBehavior::Overwrite,
1655 )
1656 .await
1657 .boxed()
1658 .execute();
1659 materialize_executor.next().await.transpose().unwrap();
1660
1661 materialize_executor.next().await.transpose().unwrap();
1662
1663 match materialize_executor.next().await.transpose().unwrap() {
1665 Some(Message::Barrier(_)) => {
1666 let row = table
1668 .get_row(
1669 &OwnedRow::new(vec![Some(8_i32.into())]),
1670 HummockReadEpoch::NoWait(u64::MAX),
1671 )
1672 .await
1673 .unwrap();
1674 assert_eq!(
1675 row,
1676 Some(OwnedRow::new(vec![Some(8_i32.into()), Some(3_i32.into())]))
1677 );
1678 }
1679 _ => unreachable!(),
1680 }
1681 materialize_executor.next().await.transpose().unwrap();
1682
1683 match materialize_executor.next().await.transpose().unwrap() {
1684 Some(Message::Barrier(_)) => {
1685 let row = table
1686 .get_row(
1687 &OwnedRow::new(vec![Some(7_i32.into())]),
1688 HummockReadEpoch::NoWait(u64::MAX),
1689 )
1690 .await
1691 .unwrap();
1692 assert_eq!(
1693 row,
1694 Some(OwnedRow::new(vec![Some(7_i32.into()), Some(8_i32.into())]))
1695 );
1696
1697 let row = table
1699 .get_row(
1700 &OwnedRow::new(vec![Some(3_i32.into())]),
1701 HummockReadEpoch::NoWait(u64::MAX),
1702 )
1703 .await
1704 .unwrap();
1705 assert_eq!(row, None);
1706
1707 let row = table
1709 .get_row(
1710 &OwnedRow::new(vec![Some(5_i32.into())]),
1711 HummockReadEpoch::NoWait(u64::MAX),
1712 )
1713 .await
1714 .unwrap();
1715 assert_eq!(row, None);
1716 }
1717 _ => unreachable!(),
1718 }
1719
1720 materialize_executor.next().await.transpose().unwrap();
1721 match materialize_executor.next().await.transpose().unwrap() {
1723 Some(Message::Barrier(_)) => {
1724 let row = table
1725 .get_row(
1726 &OwnedRow::new(vec![Some(1_i32.into())]),
1727 HummockReadEpoch::NoWait(u64::MAX),
1728 )
1729 .await
1730 .unwrap();
1731 assert_eq!(
1732 row,
1733 Some(OwnedRow::new(vec![Some(1_i32.into()), Some(5_i32.into())]))
1734 );
1735
1736 let row = table
1738 .get_row(
1739 &OwnedRow::new(vec![Some(2_i32.into())]),
1740 HummockReadEpoch::NoWait(u64::MAX),
1741 )
1742 .await
1743 .unwrap();
1744 assert_eq!(
1745 row,
1746 Some(OwnedRow::new(vec![Some(2_i32.into()), Some(8_i32.into())]))
1747 );
1748
1749 let row = table
1751 .get_row(
1752 &OwnedRow::new(vec![Some(9_i32.into())]),
1753 HummockReadEpoch::NoWait(u64::MAX),
1754 )
1755 .await
1756 .unwrap();
1757 assert_eq!(
1758 row,
1759 Some(OwnedRow::new(vec![Some(9_i32.into()), Some(1_i32.into())]))
1760 );
1761 }
1762 _ => unreachable!(),
1763 }
1764 }
1765
1766 #[tokio::test]
1767 async fn test_change_buffer_into_chunk_with_stream_key() {
1768 let memory_state_store = MemoryStateStore::new();
1771 let table_id = TableId::new(1);
1772 let schema = Schema::new(vec![
1773 Field::unnamed(DataType::Int32),
1774 Field::unnamed(DataType::Int32),
1775 ]);
1776 let column_ids = vec![0.into(), 1.into()];
1777
1778 let chunk1 = StreamChunk::from_pretty(
1779 " i i
1780 + 1 4",
1781 );
1782 let chunk2 = StreamChunk::from_pretty(
1783 " i i
1784 + 1 5",
1785 );
1786
1787 let source = MockSource::with_messages(vec![
1788 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1789 Message::Chunk(chunk1),
1790 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1791 Message::Chunk(chunk2),
1792 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1793 ])
1794 .into_executor(schema.clone(), StreamKey::new());
1795
1796 let mut materialize_executor = MaterializeExecutor::for_test_with_stream_key(
1797 source,
1798 memory_state_store,
1799 table_id,
1800 vec![ColumnOrder::new(0, OrderType::ascending())],
1801 vec![0, 1],
1802 column_ids,
1803 Arc::new(AtomicU64::new(0)),
1804 ConflictBehavior::Overwrite,
1805 )
1806 .await
1807 .boxed()
1808 .execute();
1809
1810 materialize_executor.next().await.transpose().unwrap();
1812 materialize_executor.next().await.transpose().unwrap();
1813
1814 materialize_executor.next().await.transpose().unwrap();
1816
1817 match materialize_executor.next().await.transpose().unwrap() {
1819 Some(Message::Chunk(chunk)) => {
1820 assert_eq!(
1821 chunk.compact_vis(),
1822 StreamChunk::from_pretty(
1823 " i i
1824 - 1 4
1825 + 1 5"
1826 )
1827 );
1828 }
1829 other => panic!("expect chunk, got {other:?}"),
1830 }
1831 }
1832
1833 #[tokio::test]
1834 async fn test_ignore_insert_conflict() {
1835 let memory_state_store = MemoryStateStore::new();
1837 let table_id = TableId::new(1);
1838 let schema = Schema::new(vec![
1840 Field::unnamed(DataType::Int32),
1841 Field::unnamed(DataType::Int32),
1842 ]);
1843 let column_ids = vec![0.into(), 1.into()];
1844
1845 let chunk1 = StreamChunk::from_pretty(
1847 " i i
1848 + 1 3
1849 + 1 4
1850 + 2 5
1851 + 3 6",
1852 );
1853
1854 let chunk2 = StreamChunk::from_pretty(
1855 " i i
1856 + 1 5
1857 + 2 6",
1858 );
1859
1860 let chunk3 = StreamChunk::from_pretty(
1862 " i i
1863 + 1 6",
1864 );
1865
1866 let source = MockSource::with_messages(vec![
1868 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1869 Message::Chunk(chunk1),
1870 Message::Chunk(chunk2),
1871 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1872 Message::Chunk(chunk3),
1873 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
1874 ])
1875 .into_executor(schema.clone(), StreamKey::new());
1876
1877 let order_types = vec![OrderType::ascending()];
1878 let column_descs = vec![
1879 ColumnDesc::unnamed(column_ids[0], DataType::Int32),
1880 ColumnDesc::unnamed(column_ids[1], DataType::Int32),
1881 ];
1882
1883 let table = BatchTable::for_test(
1884 memory_state_store.clone(),
1885 table_id,
1886 column_descs,
1887 order_types,
1888 vec![0],
1889 vec![0, 1],
1890 );
1891
1892 let mut materialize_executor = MaterializeExecutor::for_test(
1893 source,
1894 memory_state_store,
1895 table_id,
1896 vec![ColumnOrder::new(0, OrderType::ascending())],
1897 column_ids,
1898 Arc::new(AtomicU64::new(0)),
1899 ConflictBehavior::IgnoreConflict,
1900 )
1901 .await
1902 .boxed()
1903 .execute();
1904 materialize_executor.next().await.transpose().unwrap();
1905
1906 materialize_executor.next().await.transpose().unwrap();
1907 materialize_executor.next().await.transpose().unwrap();
1908
1909 match materialize_executor.next().await.transpose().unwrap() {
1911 Some(Message::Barrier(_)) => {
1912 let row = table
1913 .get_row(
1914 &OwnedRow::new(vec![Some(3_i32.into())]),
1915 HummockReadEpoch::NoWait(u64::MAX),
1916 )
1917 .await
1918 .unwrap();
1919 assert_eq!(
1920 row,
1921 Some(OwnedRow::new(vec![Some(3_i32.into()), Some(6_i32.into())]))
1922 );
1923
1924 let row = table
1925 .get_row(
1926 &OwnedRow::new(vec![Some(1_i32.into())]),
1927 HummockReadEpoch::NoWait(u64::MAX),
1928 )
1929 .await
1930 .unwrap();
1931 assert_eq!(
1932 row,
1933 Some(OwnedRow::new(vec![Some(1_i32.into()), Some(3_i32.into())]))
1934 );
1935
1936 let row = table
1937 .get_row(
1938 &OwnedRow::new(vec![Some(2_i32.into())]),
1939 HummockReadEpoch::NoWait(u64::MAX),
1940 )
1941 .await
1942 .unwrap();
1943 assert_eq!(
1944 row,
1945 Some(OwnedRow::new(vec![Some(2_i32.into()), Some(5_i32.into())]))
1946 );
1947 }
1948 _ => unreachable!(),
1949 }
1950 }
1951
1952 #[tokio::test]
1953 async fn test_ignore_delete_then_insert() {
1954 let memory_state_store = MemoryStateStore::new();
1956 let table_id = TableId::new(1);
1957 let schema = Schema::new(vec![
1959 Field::unnamed(DataType::Int32),
1960 Field::unnamed(DataType::Int32),
1961 ]);
1962 let column_ids = vec![0.into(), 1.into()];
1963
1964 let chunk1 = StreamChunk::from_pretty(
1966 " i i
1967 + 1 3
1968 - 1 3
1969 + 1 6",
1970 );
1971
1972 let source = MockSource::with_messages(vec![
1974 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
1975 Message::Chunk(chunk1),
1976 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
1977 ])
1978 .into_executor(schema.clone(), StreamKey::new());
1979
1980 let order_types = vec![OrderType::ascending()];
1981 let column_descs = vec![
1982 ColumnDesc::unnamed(column_ids[0], DataType::Int32),
1983 ColumnDesc::unnamed(column_ids[1], DataType::Int32),
1984 ];
1985
1986 let table = BatchTable::for_test(
1987 memory_state_store.clone(),
1988 table_id,
1989 column_descs,
1990 order_types,
1991 vec![0],
1992 vec![0, 1],
1993 );
1994
1995 let mut materialize_executor = MaterializeExecutor::for_test(
1996 source,
1997 memory_state_store,
1998 table_id,
1999 vec![ColumnOrder::new(0, OrderType::ascending())],
2000 column_ids,
2001 Arc::new(AtomicU64::new(0)),
2002 ConflictBehavior::IgnoreConflict,
2003 )
2004 .await
2005 .boxed()
2006 .execute();
2007 let _msg1 = materialize_executor
2008 .next()
2009 .await
2010 .transpose()
2011 .unwrap()
2012 .unwrap()
2013 .as_barrier()
2014 .unwrap();
2015 let _msg2 = materialize_executor
2016 .next()
2017 .await
2018 .transpose()
2019 .unwrap()
2020 .unwrap()
2021 .as_chunk()
2022 .unwrap();
2023 let _msg3 = materialize_executor
2024 .next()
2025 .await
2026 .transpose()
2027 .unwrap()
2028 .unwrap()
2029 .as_barrier()
2030 .unwrap();
2031
2032 let row = table
2033 .get_row(
2034 &OwnedRow::new(vec![Some(1_i32.into())]),
2035 HummockReadEpoch::NoWait(u64::MAX),
2036 )
2037 .await
2038 .unwrap();
2039 assert_eq!(
2040 row,
2041 Some(OwnedRow::new(vec![Some(1_i32.into()), Some(6_i32.into())]))
2042 );
2043 }
2044
2045 #[tokio::test]
2046 async fn test_ignore_delete_and_update_conflict() {
2047 let memory_state_store = MemoryStateStore::new();
2049 let table_id = TableId::new(1);
2050 let schema = Schema::new(vec![
2052 Field::unnamed(DataType::Int32),
2053 Field::unnamed(DataType::Int32),
2054 ]);
2055 let column_ids = vec![0.into(), 1.into()];
2056
2057 let chunk1 = StreamChunk::from_pretty(
2059 " i i
2060 + 1 4
2061 + 2 5
2062 + 3 6
2063 U- 8 1
2064 U+ 8 2
2065 + 8 3",
2066 );
2067
2068 let chunk2 = StreamChunk::from_pretty(
2070 " i i
2071 + 7 8
2072 - 3 4
2073 - 5 0",
2074 );
2075
2076 let chunk3 = StreamChunk::from_pretty(
2078 " i i
2079 + 1 5
2080 U- 2 4
2081 U+ 2 8
2082 U- 9 0
2083 U+ 9 1",
2084 );
2085
2086 let source = MockSource::with_messages(vec![
2088 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
2089 Message::Chunk(chunk1),
2090 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
2091 Message::Chunk(chunk2),
2092 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
2093 Message::Chunk(chunk3),
2094 Message::Barrier(Barrier::new_test_barrier(test_epoch(4))),
2095 ])
2096 .into_executor(schema.clone(), StreamKey::new());
2097
2098 let order_types = vec![OrderType::ascending()];
2099 let column_descs = vec![
2100 ColumnDesc::unnamed(column_ids[0], DataType::Int32),
2101 ColumnDesc::unnamed(column_ids[1], DataType::Int32),
2102 ];
2103
2104 let table = BatchTable::for_test(
2105 memory_state_store.clone(),
2106 table_id,
2107 column_descs,
2108 order_types,
2109 vec![0],
2110 vec![0, 1],
2111 );
2112
2113 let mut materialize_executor = MaterializeExecutor::for_test(
2114 source,
2115 memory_state_store,
2116 table_id,
2117 vec![ColumnOrder::new(0, OrderType::ascending())],
2118 column_ids,
2119 Arc::new(AtomicU64::new(0)),
2120 ConflictBehavior::IgnoreConflict,
2121 )
2122 .await
2123 .boxed()
2124 .execute();
2125 materialize_executor.next().await.transpose().unwrap();
2126
2127 materialize_executor.next().await.transpose().unwrap();
2128
2129 match materialize_executor.next().await.transpose().unwrap() {
2131 Some(Message::Barrier(_)) => {
2132 let row = table
2134 .get_row(
2135 &OwnedRow::new(vec![Some(8_i32.into())]),
2136 HummockReadEpoch::NoWait(u64::MAX),
2137 )
2138 .await
2139 .unwrap();
2140 assert_eq!(
2141 row,
2142 Some(OwnedRow::new(vec![Some(8_i32.into()), Some(2_i32.into())]))
2143 );
2144 }
2145 _ => unreachable!(),
2146 }
2147 materialize_executor.next().await.transpose().unwrap();
2148
2149 match materialize_executor.next().await.transpose().unwrap() {
2150 Some(Message::Barrier(_)) => {
2151 let row = table
2152 .get_row(
2153 &OwnedRow::new(vec![Some(7_i32.into())]),
2154 HummockReadEpoch::NoWait(u64::MAX),
2155 )
2156 .await
2157 .unwrap();
2158 assert_eq!(
2159 row,
2160 Some(OwnedRow::new(vec![Some(7_i32.into()), Some(8_i32.into())]))
2161 );
2162
2163 let row = table
2165 .get_row(
2166 &OwnedRow::new(vec![Some(3_i32.into())]),
2167 HummockReadEpoch::NoWait(u64::MAX),
2168 )
2169 .await
2170 .unwrap();
2171 assert_eq!(row, None);
2172
2173 let row = table
2175 .get_row(
2176 &OwnedRow::new(vec![Some(5_i32.into())]),
2177 HummockReadEpoch::NoWait(u64::MAX),
2178 )
2179 .await
2180 .unwrap();
2181 assert_eq!(row, None);
2182 }
2183 _ => unreachable!(),
2184 }
2185
2186 materialize_executor.next().await.transpose().unwrap();
2187 match materialize_executor.next().await.transpose().unwrap() {
2190 Some(Message::Barrier(_)) => {
2191 let row = table
2192 .get_row(
2193 &OwnedRow::new(vec![Some(1_i32.into())]),
2194 HummockReadEpoch::NoWait(u64::MAX),
2195 )
2196 .await
2197 .unwrap();
2198 assert_eq!(
2199 row,
2200 Some(OwnedRow::new(vec![Some(1_i32.into()), Some(4_i32.into())]))
2201 );
2202
2203 let row = table
2205 .get_row(
2206 &OwnedRow::new(vec![Some(2_i32.into())]),
2207 HummockReadEpoch::NoWait(u64::MAX),
2208 )
2209 .await
2210 .unwrap();
2211 assert_eq!(
2212 row,
2213 Some(OwnedRow::new(vec![Some(2_i32.into()), Some(8_i32.into())]))
2214 );
2215
2216 let row = table
2218 .get_row(
2219 &OwnedRow::new(vec![Some(9_i32.into())]),
2220 HummockReadEpoch::NoWait(u64::MAX),
2221 )
2222 .await
2223 .unwrap();
2224 assert_eq!(
2225 row,
2226 Some(OwnedRow::new(vec![Some(9_i32.into()), Some(1_i32.into())]))
2227 );
2228 }
2229 _ => unreachable!(),
2230 }
2231 }
2232
2233 #[tokio::test]
2234 async fn test_do_update_if_not_null_conflict() {
2235 let memory_state_store = MemoryStateStore::new();
2237 let table_id = TableId::new(1);
2238 let schema = Schema::new(vec![
2240 Field::unnamed(DataType::Int32),
2241 Field::unnamed(DataType::Int32),
2242 ]);
2243 let column_ids = vec![0.into(), 1.into()];
2244
2245 let chunk1 = StreamChunk::from_pretty(
2247 " i i
2248 + 1 4
2249 + 2 .
2250 + 3 6
2251 U- 8 .
2252 U+ 8 2
2253 + 8 .",
2254 );
2255
2256 let chunk2 = StreamChunk::from_pretty(
2258 " i i
2259 + 7 8
2260 - 3 4
2261 - 5 0",
2262 );
2263
2264 let chunk3 = StreamChunk::from_pretty(
2266 " i i
2267 + 1 5
2268 + 7 .
2269 U- 2 4
2270 U+ 2 .
2271 U- 9 0
2272 U+ 9 1",
2273 );
2274
2275 let source = MockSource::with_messages(vec![
2277 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
2278 Message::Chunk(chunk1),
2279 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
2280 Message::Chunk(chunk2),
2281 Message::Barrier(Barrier::new_test_barrier(test_epoch(3))),
2282 Message::Chunk(chunk3),
2283 Message::Barrier(Barrier::new_test_barrier(test_epoch(4))),
2284 ])
2285 .into_executor(schema.clone(), StreamKey::new());
2286
2287 let order_types = vec![OrderType::ascending()];
2288 let column_descs = vec![
2289 ColumnDesc::unnamed(column_ids[0], DataType::Int32),
2290 ColumnDesc::unnamed(column_ids[1], DataType::Int32),
2291 ];
2292
2293 let table = BatchTable::for_test(
2294 memory_state_store.clone(),
2295 table_id,
2296 column_descs,
2297 order_types,
2298 vec![0],
2299 vec![0, 1],
2300 );
2301
2302 let mut materialize_executor = MaterializeExecutor::for_test(
2303 source,
2304 memory_state_store,
2305 table_id,
2306 vec![ColumnOrder::new(0, OrderType::ascending())],
2307 column_ids,
2308 Arc::new(AtomicU64::new(0)),
2309 ConflictBehavior::DoUpdateIfNotNull,
2310 )
2311 .await
2312 .boxed()
2313 .execute();
2314 materialize_executor.next().await.transpose().unwrap();
2315
2316 materialize_executor.next().await.transpose().unwrap();
2317
2318 match materialize_executor.next().await.transpose().unwrap() {
2320 Some(Message::Barrier(_)) => {
2321 let row = table
2322 .get_row(
2323 &OwnedRow::new(vec![Some(8_i32.into())]),
2324 HummockReadEpoch::NoWait(u64::MAX),
2325 )
2326 .await
2327 .unwrap();
2328 assert_eq!(
2329 row,
2330 Some(OwnedRow::new(vec![Some(8_i32.into()), Some(2_i32.into())]))
2331 );
2332
2333 let row = table
2334 .get_row(
2335 &OwnedRow::new(vec![Some(2_i32.into())]),
2336 HummockReadEpoch::NoWait(u64::MAX),
2337 )
2338 .await
2339 .unwrap();
2340 assert_eq!(row, Some(OwnedRow::new(vec![Some(2_i32.into()), None])));
2341 }
2342 _ => unreachable!(),
2343 }
2344 materialize_executor.next().await.transpose().unwrap();
2345
2346 match materialize_executor.next().await.transpose().unwrap() {
2347 Some(Message::Barrier(_)) => {
2348 let row = table
2349 .get_row(
2350 &OwnedRow::new(vec![Some(7_i32.into())]),
2351 HummockReadEpoch::NoWait(u64::MAX),
2352 )
2353 .await
2354 .unwrap();
2355 assert_eq!(
2356 row,
2357 Some(OwnedRow::new(vec![Some(7_i32.into()), Some(8_i32.into())]))
2358 );
2359
2360 let row = table
2362 .get_row(
2363 &OwnedRow::new(vec![Some(3_i32.into())]),
2364 HummockReadEpoch::NoWait(u64::MAX),
2365 )
2366 .await
2367 .unwrap();
2368 assert_eq!(row, None);
2369
2370 let row = table
2372 .get_row(
2373 &OwnedRow::new(vec![Some(5_i32.into())]),
2374 HummockReadEpoch::NoWait(u64::MAX),
2375 )
2376 .await
2377 .unwrap();
2378 assert_eq!(row, None);
2379 }
2380 _ => unreachable!(),
2381 }
2382
2383 materialize_executor.next().await.transpose().unwrap();
2384 match materialize_executor.next().await.transpose().unwrap() {
2387 Some(Message::Barrier(_)) => {
2388 let row = table
2389 .get_row(
2390 &OwnedRow::new(vec![Some(7_i32.into())]),
2391 HummockReadEpoch::NoWait(u64::MAX),
2392 )
2393 .await
2394 .unwrap();
2395 assert_eq!(
2396 row,
2397 Some(OwnedRow::new(vec![Some(7_i32.into()), Some(8_i32.into())]))
2398 );
2399
2400 let row = table
2402 .get_row(
2403 &OwnedRow::new(vec![Some(2_i32.into())]),
2404 HummockReadEpoch::NoWait(u64::MAX),
2405 )
2406 .await
2407 .unwrap();
2408 assert_eq!(row, Some(OwnedRow::new(vec![Some(2_i32.into()), None])));
2409
2410 let row = table
2412 .get_row(
2413 &OwnedRow::new(vec![Some(9_i32.into())]),
2414 HummockReadEpoch::NoWait(u64::MAX),
2415 )
2416 .await
2417 .unwrap();
2418 assert_eq!(
2419 row,
2420 Some(OwnedRow::new(vec![Some(9_i32.into()), Some(1_i32.into())]))
2421 );
2422 }
2423 _ => unreachable!(),
2424 }
2425 }
2426
2427 fn gen_fuzz_data(row_number: usize, chunk_size: usize) -> Vec<StreamChunk> {
2428 const KN: u32 = 4;
2429 const SEED: u64 = 998244353;
2430 let mut ret = vec![];
2431 let mut builder =
2432 StreamChunkBuilder::new(chunk_size, vec![DataType::Int32, DataType::Int32]);
2433 let mut rng = SmallRng::seed_from_u64(SEED);
2434
2435 let random_vis = |c: StreamChunk, rng: &mut SmallRng| -> StreamChunk {
2436 let len = c.data_chunk().capacity();
2437 let mut c = StreamChunkMut::from(c);
2438 for i in 0..len {
2439 c.set_vis(i, rng.random_bool(0.5));
2440 }
2441 c.into()
2442 };
2443 for _ in 0..row_number {
2444 let k = (rng.next_u32() % KN) as i32;
2445 let v = rng.next_u32() as i32;
2446 let op = if rng.random_bool(0.5) {
2447 Op::Insert
2448 } else {
2449 Op::Delete
2450 };
2451 if let Some(c) =
2452 builder.append_row(op, OwnedRow::new(vec![Some(k.into()), Some(v.into())]))
2453 {
2454 ret.push(random_vis(c, &mut rng));
2455 }
2456 }
2457 if let Some(c) = builder.take() {
2458 ret.push(random_vis(c, &mut rng));
2459 }
2460 ret
2461 }
2462
2463 async fn fuzz_test_stream_consistent_inner(conflict_behavior: ConflictBehavior) {
2464 const N: usize = 100000;
2465
2466 let memory_state_store = MemoryStateStore::new();
2468 let table_id = TableId::new(1);
2469 let schema = Schema::new(vec![
2471 Field::unnamed(DataType::Int32),
2472 Field::unnamed(DataType::Int32),
2473 ]);
2474 let column_ids = vec![0.into(), 1.into()];
2475
2476 let chunks = gen_fuzz_data(N, 128);
2477 let messages = iter::once(Message::Barrier(Barrier::new_test_barrier(test_epoch(1))))
2478 .chain(chunks.into_iter().map(Message::Chunk))
2479 .chain(iter::once(Message::Barrier(Barrier::new_test_barrier(
2480 test_epoch(2),
2481 ))))
2482 .collect();
2483 let source =
2485 MockSource::with_messages(messages).into_executor(schema.clone(), StreamKey::new());
2486
2487 let mut materialize_executor = MaterializeExecutor::for_test(
2488 source,
2489 memory_state_store.clone(),
2490 table_id,
2491 vec![ColumnOrder::new(0, OrderType::ascending())],
2492 column_ids,
2493 Arc::new(AtomicU64::new(0)),
2494 conflict_behavior,
2495 )
2496 .await
2497 .boxed()
2498 .execute();
2499 materialize_executor.expect_barrier().await;
2500
2501 let order_types = vec![OrderType::ascending()];
2502 let column_descs = vec![
2503 ColumnDesc::unnamed(0.into(), DataType::Int32),
2504 ColumnDesc::unnamed(1.into(), DataType::Int32),
2505 ];
2506 let pk_indices = vec![0];
2507
2508 let mut table = StateTable::from_table_catalog(
2509 &crate::common::table::test_utils::gen_pbtable(
2510 TableId::from(1002),
2511 column_descs.clone(),
2512 order_types,
2513 pk_indices,
2514 0,
2515 ),
2516 memory_state_store.clone(),
2517 None,
2518 )
2519 .await;
2520
2521 while let Message::Chunk(c) = materialize_executor.next().await.unwrap().unwrap() {
2522 table.write_chunk(c);
2524 }
2525 }
2526
2527 #[tokio::test]
2528 async fn fuzz_test_stream_consistent_upsert() {
2529 fuzz_test_stream_consistent_inner(ConflictBehavior::Overwrite).await
2530 }
2531
2532 #[tokio::test]
2533 async fn fuzz_test_stream_consistent_ignore() {
2534 fuzz_test_stream_consistent_inner(ConflictBehavior::IgnoreConflict).await
2535 }
2536
2537 async fn ttl_output_after_watermark(
2541 conflict_behavior: ConflictBehavior,
2542 chunk: &str,
2543 barrier_between: bool,
2544 cold_cache: bool,
2545 ) -> Vec<(Op, OwnedRow)> {
2546 let schema = Schema::new(vec![
2547 Field::unnamed(DataType::Int32),
2548 Field::unnamed(DataType::Int32),
2549 Field::unnamed(DataType::Int32),
2550 ]);
2551 let mut messages = vec![
2552 Message::Barrier(Barrier::new_test_barrier(test_epoch(1))),
2553 Message::Chunk(StreamChunk::from_pretty(
2554 " i i i
2555 + 25 10 100",
2556 )),
2557 Message::Barrier(Barrier::new_test_barrier(test_epoch(2))),
2558 Message::Watermark(Watermark::new(1, DataType::Int32, ScalarImpl::Int32(20))),
2559 ];
2560 let mut last_epoch = 3;
2561 if barrier_between {
2562 messages.push(Message::Barrier(Barrier::new_test_barrier(test_epoch(3))));
2563 last_epoch = 4;
2564 }
2565 messages.push(Message::Chunk(StreamChunk::from_pretty(chunk)));
2566 messages.push(Message::Barrier(Barrier::new_test_barrier(test_epoch(
2567 last_epoch,
2568 ))));
2569 let source = MockSource::with_messages(messages).into_executor(schema, vec![0, 1]);
2570
2571 let cache_sequence = if cold_cache { u64::MAX } else { 0 };
2573 let mut materialize = MaterializeExecutor::for_test_with_ttl(
2574 source,
2575 MemoryStateStore::new(),
2576 TableId::new(1),
2577 vec![ColumnOrder::new(0, OrderType::ascending())],
2578 vec![0.into(), 1.into(), 2.into()],
2579 Arc::new(AtomicU64::new(cache_sequence)),
2580 conflict_behavior,
2581 1,
2582 )
2583 .await
2584 .boxed()
2585 .execute();
2586
2587 let mut watermark_seen = false;
2588 let mut rows = vec![];
2589 while let Some(msg) = materialize.next().await.transpose().unwrap() {
2590 match msg {
2591 Message::Watermark(_) => watermark_seen = true,
2592 Message::Chunk(chunk) if watermark_seen => {
2593 rows.extend(chunk.rows().map(|(op, row)| (op, row.into_owned_row())))
2594 }
2595 Message::Barrier(barrier) if barrier.epoch.curr == test_epoch(last_epoch) => break,
2596 _ => {}
2597 }
2598 }
2599 assert!(watermark_seen);
2600 rows
2601 }
2602
2603 #[tokio::test]
2605 async fn test_ttl_expired_by_forwarded_watermark() {
2606 let new_row = OwnedRow::new(vec![
2607 Some(25i32.into()),
2608 Some(30i32.into()),
2609 Some(300i32.into()),
2610 ]);
2611 let cases = [
2612 (
2614 ConflictBehavior::Overwrite,
2615 " i i i\n + 25 30 300",
2616 vec![(Op::Insert, new_row.clone())],
2617 ),
2618 (ConflictBehavior::Overwrite, " i i i\n - 25 . .", vec![]),
2620 (
2622 ConflictBehavior::IgnoreConflict,
2623 " i i i\n + 25 30 300",
2624 vec![(Op::Insert, new_row)],
2625 ),
2626 ];
2627 for (conflict_behavior, chunk, expected) in cases {
2628 for barrier_between in [false, true] {
2629 for cold_cache in [false, true] {
2630 assert_eq!(
2631 ttl_output_after_watermark(
2632 conflict_behavior,
2633 chunk,
2634 barrier_between,
2635 cold_cache
2636 )
2637 .await,
2638 expected,
2639 "{conflict_behavior:?}, {chunk:?}, barrier_between: {barrier_between}, \
2640 cold_cache: {cold_cache}"
2641 );
2642 }
2643 }
2644 }
2645 }
2646}