Skip to main content

risingwave_stream/common/table/
state_table.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{BTreeMap, HashMap};
16use std::marker::PhantomData;
17use std::ops::Bound;
18use std::ops::Bound::*;
19use std::sync::Arc;
20use std::time::Instant;
21
22use anyhow::anyhow;
23use bytes::Bytes;
24use educe::Educe;
25use either::Either;
26use foyer::Hint;
27use futures::future::{ready, try_join_all};
28use futures::stream::BoxStream;
29use futures::{Stream, StreamExt, TryStreamExt, pin_mut};
30use itertools::Itertools;
31use risingwave_common::array::stream_record::Record;
32use risingwave_common::array::{ArrayImplBuilder, ArrayRef, DataChunk, Op, StreamChunk};
33use risingwave_common::bitmap::Bitmap;
34use risingwave_common::catalog::{
35    ColumnDesc, ColumnId, TableId, TableOption, get_dist_key_in_pk_indices,
36};
37use risingwave_common::config::StreamingConfig;
38use risingwave_common::hash::{VirtualNode, VnodeBitmapExt, VnodeCountCompat};
39use risingwave_common::id::FragmentId;
40use risingwave_common::row::{self, OwnedRow, Row, RowExt};
41use risingwave_common::types::{DataType, ScalarImpl};
42use risingwave_common::util::column_index_mapping::ColIndexMapping;
43use risingwave_common::util::epoch::EpochPair;
44use risingwave_common::util::row_serde::OrderedRowSerde;
45use risingwave_common::util::sort_util::{OrderType, cmp_datum};
46use risingwave_common::util::value_encoding::BasicSerde;
47use risingwave_hummock_sdk::HummockReadEpoch;
48use risingwave_hummock_sdk::key::{
49    CopyFromSlice, TableKey, end_bound_of_prefix, next_key, prefix_slice_with_vnode,
50    prefixed_range_with_vnode, start_bound_of_excluded_prefix,
51};
52use risingwave_hummock_sdk::table_watermark::{
53    VnodeWatermark, WatermarkDirection, WatermarkSerdeType,
54};
55use risingwave_pb::catalog::Table;
56use risingwave_pb::plan_common::StorageTableDesc;
57use risingwave_storage::StateStore;
58use risingwave_storage::error::{ErrorKind, StorageError, StorageResult};
59use risingwave_storage::hummock::CachePolicy;
60use risingwave_storage::mem_table::MemTableError;
61use risingwave_storage::row_serde::find_columns_by_ids;
62use risingwave_storage::row_serde::row_serde_util::{
63    deserialize_pk_with_vnode, serialize_pk, serialize_pk_with_vnode, serialize_row,
64};
65use risingwave_storage::row_serde::value_serde::ValueRowSerde;
66use risingwave_storage::store::*;
67use risingwave_storage::table::{KeyedRow, TableDistribution, should_calculate_prefix_hint};
68use thiserror_ext::AsReport;
69use tracing::{Instrument, trace};
70
71use crate::cache::keyed_cache_may_stale;
72use crate::executor::monitor::streaming_stats::StateTableMetrics;
73use crate::executor::{StreamExecutorError, StreamExecutorResult};
74
75/// This macro is used to mark a point where we want to randomly discard the operation and early
76/// return, only in insane mode.
77macro_rules! insane_mode_discard_point {
78    () => {{
79        use rand::Rng;
80        if crate::consistency::insane() && rand::rng().random_bool(0.3) {
81            return;
82        }
83    }};
84}
85
86/// Per-vnode statistics for pruning. None means this stat is not maintained.
87/// For each vnode, we maintain the min and max storage table key (excluding the vnode part) observed in the vnode.
88/// The stat won't differentiate between tombstone and normal keys.
89struct VnodeStatistics {
90    min_key: Option<Bytes>,
91    max_key: Option<Bytes>,
92}
93
94impl VnodeStatistics {
95    fn new() -> Self {
96        Self {
97            min_key: None,
98            max_key: None,
99        }
100    }
101
102    fn update_with_key(&mut self, key: &Bytes) {
103        if let Some(min) = &self.min_key {
104            if key < min {
105                self.min_key = Some(key.clone());
106            }
107        } else {
108            self.min_key = Some(key.clone());
109        }
110
111        if let Some(max) = &self.max_key {
112            if key > max {
113                self.max_key = Some(key.clone());
114            }
115        } else {
116            self.max_key = Some(key.clone());
117        }
118    }
119
120    fn can_prune(&self, key: &Bytes) -> bool {
121        if let Some(min) = &self.min_key
122            && key < min
123        {
124            return true;
125        }
126        if let Some(max) = &self.max_key
127            && key > max
128        {
129            return true;
130        }
131        false
132    }
133
134    fn can_prune_range(&self, start: &Bound<Bytes>, end: &Bound<Bytes>) -> bool {
135        // Check if the range is completely outside vnode bounds
136        if let Some(max) = &self.max_key {
137            match start {
138                Included(s) if s > max => return true,
139                Excluded(s) if s >= max => return true,
140                _ => {}
141            }
142        }
143        if let Some(min) = &self.min_key {
144            match end {
145                Included(e) if e < min => return true,
146                Excluded(e) if e <= min => return true,
147                _ => {}
148            }
149        }
150        false
151    }
152
153    fn pruned_key_range(
154        &self,
155        start: &Bound<Bytes>,
156        end: &Bound<Bytes>,
157    ) -> Option<(Bound<Bytes>, Bound<Bytes>)> {
158        if self.can_prune_range(start, end) {
159            return None;
160        }
161        let new_start = if let Some(min) = &self.min_key {
162            match start {
163                Included(s) if s <= min => Included(min.clone()),
164                Excluded(s) if s < min => Included(min.clone()),
165                _ => start.clone(),
166            }
167        } else {
168            start.clone()
169        };
170
171        let new_end = if let Some(max) = &self.max_key {
172            match end {
173                Included(e) if e >= max => Included(max.clone()),
174                Excluded(e) if e > max => Included(max.clone()),
175                _ => end.clone(),
176            }
177        } else {
178            end.clone()
179        };
180
181        Some((new_start, new_end))
182    }
183}
184
185/// `StateTableInner` is the interface accessing relational data in KV(`StateStore`) with
186/// row-based encoding.
187pub struct StateTableInner<S, SD = BasicSerde, const IS_REPLICATED: bool = false>
188where
189    S: StateStore,
190    SD: ValueRowSerde,
191{
192    /// Id for this table.
193    table_id: TableId,
194
195    /// State store backend.
196    row_store: StateTableRowStore<S::Local, SD>,
197
198    /// State store for accessing snapshot data
199    store: S,
200
201    /// Current epoch
202    epoch: Option<EpochPair>,
203
204    /// Used for serializing and deserializing the primary key.
205    pk_serde: OrderedRowSerde,
206
207    /// Indices of primary key.
208    /// Note that the index is based on the all columns of the table, instead of the output ones.
209    // FIXME: revisit constructions and usages.
210    pk_indices: Vec<usize>,
211
212    /// Distribution of the state table.
213    ///
214    /// It holds vnode bitmap. Only the rows whose vnode of the primary key is in this set will be visible to the
215    /// executor. The table will also check whether the written rows
216    /// conform to this partition.
217    distribution: TableDistribution,
218
219    prefix_hint_len: usize,
220
221    value_indices: Option<Vec<usize>>,
222
223    /// The index of the watermark column used for state cleaning in all columns.
224    pub clean_watermark_index: Option<usize>,
225    /// Pending watermark for state cleaning. Old states below this watermark will be cleaned when committing.
226    pending_watermark: Option<ScalarImpl>,
227    /// Last committed watermark for state cleaning. Will be restored on state table recovery.
228    committed_watermark: Option<ScalarImpl>,
229    /// Serializer and serde type for the watermark column.
230    watermark_serde: Option<(OrderedRowSerde, WatermarkSerdeType)>,
231
232    /// Data Types
233    /// We will need to use to build data chunks from state table rows.
234    data_types: Vec<DataType>,
235
236    /// "i" here refers to the base `state_table`'s actual schema.
237    /// "o" here refers to the replicated state table's output schema.
238    /// This mapping is used to reconstruct a row being written from replicated state table.
239    /// Such that the schema of this row will match the full schema of the base state table.
240    /// It is only applicable for replication.
241    i2o_mapping: ColIndexMapping,
242
243    /// Output indices
244    /// Used for:
245    /// 1. Computing `output_value_indices` to ser/de replicated rows.
246    /// 2. Computing output pk indices to used them for backfill state.
247    pub output_indices: Vec<usize>,
248
249    op_consistency_level: StateTableOpConsistencyLevel,
250
251    /// Flag to indicate whether the state table has called `commit`, but has not called
252    /// `post_yield_barrier` on the `StateTablePostCommit` callback yet.
253    on_post_commit: bool,
254}
255
256/// `StateTable` will use `BasicSerde` as default
257pub type StateTable<S> = StateTableInner<S, BasicSerde>;
258/// `ReplicatedStateTable` is meant to replicate upstream shared buffer.
259/// Used for `ArrangementBackfill` executor.
260pub type ReplicatedStateTable<S, SD> = StateTableInner<S, SD, true>;
261
262pub type FlushedStateTableReader<S, SD = BasicSerde> = StateTableFlushedSnapshotReader<
263    <<S as StateStore>::Local as LocalStateStore>::FlushedSnapshotReader,
264    SD,
265>;
266
267#[derive(Educe)]
268#[educe(Clone)]
269pub struct StateTableFlushedSnapshotReader<R, SD = BasicSerde>
270where
271    R: StateStoreRead,
272    SD: ValueRowSerde,
273{
274    reader: Arc<R>,
275    pk_serde: OrderedRowSerde,
276    vnodes: Arc<Bitmap>,
277    row_serde: Arc<SD>,
278    metrics: Option<StateTableMetrics>,
279}
280
281// initialize
282impl<S, SD, const IS_REPLICATED: bool> StateTableInner<S, SD, IS_REPLICATED>
283where
284    S: StateStore,
285    SD: ValueRowSerde,
286{
287    /// In streaming executors, this methods must be called **after** receiving and yielding the first barrier,
288    /// and otherwise, deadlock can be likely to happen.
289    pub async fn init_epoch(&mut self, epoch: EpochPair) -> StreamExecutorResult<()> {
290        self.row_store
291            .init(epoch, self.distribution.vnodes())
292            .await?;
293        assert_eq!(None, self.epoch.replace(epoch), "should not init for twice");
294        Ok(())
295    }
296
297    pub async fn try_wait_committed_epoch(&self, prev_epoch: u64) -> StorageResult<()> {
298        self.store
299            .try_wait_epoch(
300                HummockReadEpoch::Committed(prev_epoch),
301                TryWaitEpochOptions {
302                    table_id: self.table_id,
303                },
304            )
305            .await
306    }
307
308    pub fn state_store(&self) -> &S {
309        &self.store
310    }
311}
312
313fn consistent_old_value_op(
314    row_serde: Arc<impl ValueRowSerde>,
315    is_log_store: bool,
316) -> OpConsistencyLevel {
317    OpConsistencyLevel::ConsistentOldValue {
318        check_old_value: Arc::new(move |first: &Bytes, second: &Bytes| {
319            if first == second {
320                return true;
321            }
322            let first = match row_serde.deserialize(first) {
323                Ok(rows) => rows,
324                Err(e) => {
325                    error!(error = %e.as_report(), value = ?first, "fail to deserialize serialized value");
326                    return false;
327                }
328            };
329            let second = match row_serde.deserialize(second) {
330                Ok(rows) => rows,
331                Err(e) => {
332                    error!(error = %e.as_report(), value = ?second, "fail to deserialize serialized value");
333                    return false;
334                }
335            };
336            if first != second {
337                error!(first = ?first, second = ?second, "sanity check fail");
338                false
339            } else {
340                true
341            }
342        }),
343        is_log_store,
344    }
345}
346
347macro_rules! dispatch_value_indices {
348    ($value_indices:expr, [$($row_var_name:ident),+], $body:expr) => {
349        if let Some(value_indices) = $value_indices {
350            $(
351                let $row_var_name = $row_var_name.project(value_indices);
352            )+
353            $body
354        } else {
355            $body
356        }
357    };
358}
359
360/// Extract the logic of `StateTableRowStore` from `StateTable`, which serves
361/// a similar functionality as a `BTreeMap<TableKey<Bytes>, OwnedRow>`, and
362/// provides method to read (`get` and `iter`) over serialized key (or key range)
363/// and returns `OwnedRow`, and write (`insert`, `delete`, `update`) on `(TableKey<Bytes>, OwnedRow)`.
364struct StateTableRowStore<LS: LocalStateStore, SD: ValueRowSerde> {
365    state_store: LS,
366    all_rows: Option<HashMap<VirtualNode, BTreeMap<Bytes, OwnedRow>>>,
367
368    table_id: TableId,
369    row_serde: Arc<SD>,
370    // should be only used for debugging in panic message of handle_mem_table_error
371    pk_serde: OrderedRowSerde,
372
373    // Per-vnode min/max key statistics for pruning
374    vnode_stats: Option<HashMap<VirtualNode, VnodeStatistics>>,
375    /// When false, vnode stats pruning is in dry-run mode:
376    /// we maintain stats and verify pruning correctness but don't actually apply pruning.
377    /// Reads still go to cache/storage even when pruning would indicate no results.
378    enable_state_table_vnode_stats_pruning: bool,
379    // Optional metrics for state table operations
380    pub metrics: Option<StateTableMetrics>,
381}
382
383impl<LS: LocalStateStore, SD: ValueRowSerde> StateTableRowStore<LS, SD> {
384    async fn may_load_vnode_stats(&mut self, vnode_bitmap: &Bitmap) -> StreamExecutorResult<()> {
385        if self.vnode_stats.is_none() {
386            return Ok(());
387        }
388
389        // vnode stats must be disabled when all rows are preloaded
390        assert!(self.all_rows.is_none());
391
392        let start_time = Instant::now();
393        let mut stats_map = HashMap::new();
394
395        // Scan each vnode to get min/max keys
396        for vnode in vnode_bitmap.iter_vnodes() {
397            let mut stats = VnodeStatistics::new();
398
399            // Get min key via forward iteration
400            let memcomparable_range_with_vnode = prefixed_range_with_vnode::<Bytes>(.., vnode);
401            let read_options = ReadOptions {
402                cache_policy: CachePolicy::Fill(Hint::Low),
403                ..Default::default()
404            };
405
406            let mut iter = self
407                .state_store
408                .iter(memcomparable_range_with_vnode.clone(), read_options.clone())
409                .await?;
410            if let Some(item) = iter.try_next().await? {
411                let (key_vnode, key_without_vnode) = item.0.user_key.table_key.split_vnode();
412                assert_eq!(vnode, key_vnode);
413                stats.min_key = Some(Bytes::copy_from_slice(key_without_vnode));
414            }
415
416            // Get max key via reverse iteration
417            let mut rev_iter = self
418                .state_store
419                .rev_iter(memcomparable_range_with_vnode, read_options)
420                .await?;
421            if let Some(item) = rev_iter.try_next().await? {
422                let (key_vnode, key_without_vnode) = item.0.user_key.table_key.split_vnode();
423                assert_eq!(vnode, key_vnode);
424                stats.max_key = Some(Bytes::copy_from_slice(key_without_vnode));
425            }
426
427            stats_map.insert(vnode, stats);
428        }
429
430        self.vnode_stats = Some(stats_map);
431
432        // avoid flooding e2e-test log
433        if !cfg!(debug_assertions) {
434            info!(
435                table_id = %self.table_id,
436                vnode_count = vnode_bitmap.count_ones(),
437                duration = ?start_time.elapsed(),
438                "finished initializing vnode statistics"
439            );
440        }
441
442        Ok(())
443    }
444
445    async fn may_reload_all_rows(&mut self, vnode_bitmap: &Bitmap) -> StreamExecutorResult<()> {
446        if let Some(rows) = &mut self.all_rows {
447            rows.clear();
448            let start_time = Instant::now();
449            *rows = try_join_all(vnode_bitmap.iter_vnodes().map(|vnode| {
450                let state_store = &self.state_store;
451                let row_serde = &self.row_serde;
452                async move {
453                    let mut rows = BTreeMap::new();
454                    let memcomparable_range_with_vnode =
455                        prefixed_range_with_vnode::<Bytes>(.., vnode);
456                    // TODO: set read options
457                    let stream = deserialize_keyed_row_stream::<Bytes>(
458                        state_store
459                            .iter(
460                                memcomparable_range_with_vnode,
461                                ReadOptions {
462                                    prefix_hint: None,
463                                    prefetch_options: Default::default(),
464                                    cache_policy: Default::default(),
465                                },
466                            )
467                            .await?,
468                        &**row_serde,
469                    );
470                    pin_mut!(stream);
471                    while let Some((encoded_key, row)) = stream.try_next().await? {
472                        let key = TableKey(encoded_key);
473                        let (iter_vnode, key) = key.split_vnode_bytes();
474                        assert_eq!(vnode, iter_vnode);
475                        rows.try_insert(key, row).expect("non-duplicated");
476                    }
477                    Ok((vnode, rows)) as StreamExecutorResult<_>
478                }
479            }))
480            .await?
481            .into_iter()
482            .collect();
483            // avoid flooding e2e-test log
484            if !cfg!(debug_assertions) {
485                info!(table_id = %self.table_id, vnode_count = vnode_bitmap.count_ones(), duration = ?start_time.elapsed(),"finished reloading all rows");
486            }
487        }
488        Ok(())
489    }
490
491    async fn init(&mut self, epoch: EpochPair, vnode_bitmap: &Bitmap) -> StreamExecutorResult<()> {
492        self.state_store.init(InitOptions::new(epoch)).await?;
493        self.may_reload_all_rows(vnode_bitmap).await?;
494        self.may_load_vnode_stats(vnode_bitmap).await
495    }
496
497    async fn update_vnode_bitmap(
498        &mut self,
499        vnodes: Arc<Bitmap>,
500    ) -> StreamExecutorResult<Arc<Bitmap>> {
501        let prev_vnodes = self.state_store.update_vnode_bitmap(vnodes.clone()).await?;
502        self.may_reload_all_rows(&vnodes).await?;
503        self.may_load_vnode_stats(&vnodes).await?;
504
505        Ok(prev_vnodes)
506    }
507
508    async fn try_flush(&mut self) -> StreamExecutorResult<()> {
509        self.state_store.try_flush().await?;
510        Ok(())
511    }
512
513    async fn seal_current_epoch(
514        &mut self,
515        next_epoch: u64,
516        table_watermarks: Option<(WatermarkDirection, Vec<VnodeWatermark>, WatermarkSerdeType)>,
517        switch_consistent_op: Option<StateTableOpConsistencyLevel>,
518    ) -> StreamExecutorResult<()> {
519        if let Some((direction, watermarks, serde_type)) = &table_watermarks
520            && let Some(rows) = &mut self.all_rows
521        {
522            match serde_type {
523                WatermarkSerdeType::PkPrefix => {
524                    for vnode_watermark in watermarks {
525                        match direction {
526                            WatermarkDirection::Ascending => {
527                                for vnode in vnode_watermark.vnode_bitmap().iter_vnodes() {
528                                    let rows = rows.get_mut(&vnode).expect("covered vnode");
529                                    // split_off returns everything after the given key, including the key.
530                                    *rows = rows.split_off(vnode_watermark.watermark());
531                                }
532                            }
533                            WatermarkDirection::Descending => {
534                                // Turn Exclude(vnode_watermark.watermark()) into Include(next_key(vnode_watermark.watermark())).
535                                let split_off_key = next_key(vnode_watermark.watermark());
536                                for vnode in vnode_watermark.vnode_bitmap().iter_vnodes() {
537                                    let rows = rows.get_mut(&vnode).expect("covered vnode");
538                                    // split_off away (Exclude(vnode_watermark.watermark())..) and keep
539                                    // (..Include(vnode_watermark.watermark()))
540                                    rows.split_off(split_off_key.as_slice());
541                                }
542                            }
543                        }
544                    }
545                }
546                WatermarkSerdeType::NonPkPrefix => {
547                    warn!(table_id = %self.table_id, "table enabled preloading rows got disabled by written non pk prefix watermark");
548                    self.all_rows = None;
549                }
550                WatermarkSerdeType::Value => {
551                    warn!(table_id = %self.table_id, "table enabled preloading rows got disabled by written value watermark");
552                    self.all_rows = None;
553                }
554            }
555        }
556        self.state_store
557            .flush()
558            .instrument(tracing::info_span!("state_table_flush"))
559            .await?;
560        let switch_op_consistency_level =
561            switch_consistent_op.map(|new_consistency_level| match new_consistency_level {
562                StateTableOpConsistencyLevel::Inconsistent => OpConsistencyLevel::Inconsistent,
563                StateTableOpConsistencyLevel::ConsistentOldValue => {
564                    consistent_old_value_op(self.row_serde.clone(), false)
565                }
566                StateTableOpConsistencyLevel::LogStoreEnabled => {
567                    consistent_old_value_op(self.row_serde.clone(), true)
568                }
569            });
570        self.state_store.seal_current_epoch(
571            next_epoch,
572            SealCurrentEpochOptions {
573                table_watermarks,
574                switch_op_consistency_level,
575            },
576        );
577        Ok(())
578    }
579}
580
581#[derive(Eq, PartialEq, Copy, Clone, Debug)]
582pub enum StateTableOpConsistencyLevel {
583    /// Op is inconsistent
584    Inconsistent,
585    /// Op is consistent.
586    /// - Insert op should ensure that the key does not exist previously
587    /// - Delete and Update op should ensure that the key exists and the previous value matches the passed old value
588    ConsistentOldValue,
589    /// The requirement on operation consistency is the same as `ConsistentOldValue`.
590    /// The difference is that in the `LogStoreEnabled`, the state table should also flush and store and old value.
591    LogStoreEnabled,
592}
593
594pub struct StateTableBuilder<S, SD, const IS_REPLICATED: bool, PreloadAllRow> {
595    // Extracted intermediate fields (source-agnostic)
596    table_id: TableId,
597    table_name_for_debug: String,
598    table_columns: Vec<ColumnDesc>,
599    order_types: Vec<OrderType>,
600    pk_indices: Vec<usize>,
601    dist_key_in_pk_indices: Vec<usize>,
602    vnode_col_idx_in_pk: Option<usize>,
603    expected_vnode_count: usize,
604    value_indices: Vec<usize>,
605    prefix_hint_len: usize,
606    retention_seconds: Option<u32>,
607    versioned: bool,
608    fragment_id: FragmentId,
609    clean_watermark_index: Option<usize>,
610
611    // Builder configuration fields
612    store: S,
613    vnodes: Option<Arc<Bitmap>>,
614    op_consistency_level: Option<StateTableOpConsistencyLevel>,
615    output_column_ids: Option<Vec<ColumnId>>,
616    preload_all_rows: PreloadAllRow,
617    enable_vnode_key_stats: Option<bool>,
618    /// When false, vnode stats pruning is in dry-run mode:
619    /// we maintain stats and verify pruning correctness but don't actually apply pruning.
620    enable_state_table_vnode_stats_pruning: bool,
621    metrics: Option<StateTableMetrics>,
622
623    _serde: PhantomData<SD>,
624}
625
626impl<S: StateStore, SD: ValueRowSerde, const IS_REPLICATED: bool>
627    StateTableBuilder<S, SD, IS_REPLICATED, ()>
628{
629    fn with_preload_all_rows(
630        self,
631        preload_all_rows: bool,
632    ) -> StateTableBuilder<S, SD, IS_REPLICATED, bool> {
633        StateTableBuilder {
634            table_id: self.table_id,
635            table_name_for_debug: self.table_name_for_debug,
636            table_columns: self.table_columns,
637            order_types: self.order_types,
638            pk_indices: self.pk_indices,
639            dist_key_in_pk_indices: self.dist_key_in_pk_indices,
640            vnode_col_idx_in_pk: self.vnode_col_idx_in_pk,
641            expected_vnode_count: self.expected_vnode_count,
642            value_indices: self.value_indices,
643            prefix_hint_len: self.prefix_hint_len,
644            retention_seconds: self.retention_seconds,
645            versioned: self.versioned,
646            fragment_id: self.fragment_id,
647            clean_watermark_index: self.clean_watermark_index,
648            store: self.store,
649            vnodes: self.vnodes,
650            op_consistency_level: self.op_consistency_level,
651            output_column_ids: self.output_column_ids,
652            preload_all_rows,
653            enable_vnode_key_stats: self.enable_vnode_key_stats,
654            enable_state_table_vnode_stats_pruning: self.enable_state_table_vnode_stats_pruning,
655            metrics: self.metrics,
656            _serde: Default::default(),
657        }
658    }
659
660    pub fn enable_preload_all_rows_by_config(
661        self,
662        config: &StreamingConfig,
663    ) -> StateTableBuilder<S, SD, IS_REPLICATED, bool> {
664        let developer = &config.developer;
665        let preload_all_rows = if developer.default_enable_mem_preload_state_table {
666            !developer
667                .mem_preload_state_table_ids_blacklist
668                .contains(&self.table_id.as_raw_id())
669        } else {
670            developer
671                .mem_preload_state_table_ids_whitelist
672                .contains(&self.table_id.as_raw_id())
673        };
674        self.with_preload_all_rows(preload_all_rows)
675    }
676
677    pub fn forbid_preload_all_rows(self) -> StateTableBuilder<S, SD, IS_REPLICATED, bool> {
678        self.with_preload_all_rows(false)
679    }
680}
681
682impl<S: StateStore, SD: ValueRowSerde, const IS_REPLICATED: bool, PreloadAllRow>
683    StateTableBuilder<S, SD, IS_REPLICATED, PreloadAllRow>
684{
685    pub fn with_op_consistency_level(
686        mut self,
687        op_consistency_level: StateTableOpConsistencyLevel,
688    ) -> Self {
689        self.op_consistency_level = Some(op_consistency_level);
690        self
691    }
692
693    pub fn enable_vnode_key_stats(mut self, enable: bool, config: &StreamingConfig) -> Self {
694        self.enable_vnode_key_stats = Some(enable);
695        self.enable_state_table_vnode_stats_pruning =
696            enable && config.developer.enable_state_table_vnode_stats_pruning;
697        self
698    }
699
700    pub fn with_metrics(mut self, metrics: StateTableMetrics) -> Self {
701        self.metrics = Some(metrics);
702        self
703    }
704}
705
706impl<S: StateStore, SD: ValueRowSerde, PreloadAllRow>
707    StateTableBuilder<S, SD, true, PreloadAllRow>
708{
709    pub fn with_output_column_ids(mut self, output_column_ids: Vec<ColumnId>) -> Self {
710        self.output_column_ids = Some(output_column_ids);
711        self
712    }
713}
714
715impl<S: StateStore, SD: ValueRowSerde, const IS_REPLICATED: bool>
716    StateTableBuilder<S, SD, IS_REPLICATED, bool>
717{
718    pub async fn build(self) -> StateTableInner<S, SD, IS_REPLICATED> {
719        let mut preload_all_rows = self.preload_all_rows;
720        if preload_all_rows
721            && let Err(e) =
722                risingwave_common::license::Feature::StateTableMemoryPreload.check_available()
723        {
724            warn!(table_id=%self.table_id, e=%e.as_report(), "table configured to preload rows to memory but disabled by license");
725            preload_all_rows = false;
726        }
727
728        let should_enable_vnode_key_stats = if preload_all_rows
729            && let Some(enable_vnode_key_stats) = self.enable_vnode_key_stats
730            && enable_vnode_key_stats
731        {
732            false
733        } else {
734            self.enable_vnode_key_stats.unwrap_or(false)
735        };
736        self.build_inner(preload_all_rows, should_enable_vnode_key_stats)
737            .await
738    }
739}
740
741// initialize
742// FIXME(kwannoel): Enforce that none of the constructors here
743// should be used by replicated state table.
744// Apart from from_table_catalog_inner.
745impl<S, SD, const IS_REPLICATED: bool> StateTableInner<S, SD, IS_REPLICATED>
746where
747    S: StateStore,
748    SD: ValueRowSerde,
749{
750    /// Create state table from table catalog and store.
751    ///
752    /// If `vnodes` is `None`, [`TableDistribution::singleton()`] will be used.
753    #[cfg(any(test, feature = "test"))]
754    pub async fn from_table_catalog(
755        table_catalog: &Table,
756        store: S,
757        vnodes: Option<Arc<Bitmap>>,
758    ) -> Self {
759        StateTableBuilder::new(table_catalog, store, vnodes)
760            .forbid_preload_all_rows()
761            .build()
762            .await
763    }
764
765    /// Create state table from table catalog and store with sanity check disabled.
766    pub async fn from_table_catalog_inconsistent_op(
767        table_catalog: &Table,
768        store: S,
769        vnodes: Option<Arc<Bitmap>>,
770    ) -> Self {
771        StateTableBuilder::new(table_catalog, store, vnodes)
772            .with_op_consistency_level(StateTableOpConsistencyLevel::Inconsistent)
773            .forbid_preload_all_rows()
774            .build()
775            .await
776    }
777}
778
779impl<S: StateStore, SD: ValueRowSerde, const IS_REPLICATED: bool>
780    StateTableBuilder<S, SD, IS_REPLICATED, ()>
781{
782    pub fn new(table_catalog: &Table, store: S, vnodes: Option<Arc<Bitmap>>) -> Self {
783        let table_id = table_catalog.id;
784        let table_columns: Vec<ColumnDesc> = table_catalog
785            .columns
786            .iter()
787            .map(|col| col.column_desc.as_ref().unwrap().into())
788            .collect();
789        let order_types: Vec<OrderType> = table_catalog
790            .pk
791            .iter()
792            .map(|col_order| OrderType::from_protobuf(col_order.get_order_type().unwrap()))
793            .collect();
794        let dist_key_indices: Vec<usize> = table_catalog
795            .distribution_key
796            .iter()
797            .map(|dist_index| *dist_index as usize)
798            .collect();
799
800        let pk_indices = table_catalog
801            .pk
802            .iter()
803            .map(|col_order| col_order.column_index as usize)
804            .collect_vec();
805
806        // FIXME(yuhao): only use `dist_key_in_pk` in the proto
807        let dist_key_in_pk_indices = if table_catalog.get_dist_key_in_pk().is_empty() {
808            get_dist_key_in_pk_indices(&dist_key_indices, &pk_indices).unwrap()
809        } else {
810            table_catalog
811                .get_dist_key_in_pk()
812                .iter()
813                .map(|idx| *idx as usize)
814                .collect()
815        };
816
817        let vnode_col_idx_in_pk = table_catalog.vnode_col_index.as_ref().and_then(|idx| {
818            let vnode_col_idx = *idx as usize;
819            pk_indices.iter().position(|&i| vnode_col_idx == i)
820        });
821        let value_indices = table_catalog
822            .value_indices
823            .iter()
824            .map(|val| *val as usize)
825            .collect_vec();
826        let clean_watermark_indices = table_catalog.get_clean_watermark_column_indices();
827        if clean_watermark_indices.len() > 1 {
828            unimplemented!("multiple clean watermark columns are not supported yet")
829        }
830        let clean_watermark_index = clean_watermark_indices.first().map(|&i| i as usize);
831
832        Self {
833            table_id,
834            table_name_for_debug: table_catalog.name.clone(),
835            table_columns,
836            order_types,
837            pk_indices,
838            dist_key_in_pk_indices,
839            vnode_col_idx_in_pk,
840            expected_vnode_count: table_catalog.vnode_count(),
841            value_indices,
842            prefix_hint_len: table_catalog.read_prefix_len_hint as usize,
843            retention_seconds: table_catalog.retention_seconds,
844            versioned: table_catalog.version.is_some(),
845            fragment_id: table_catalog.fragment_id,
846            clean_watermark_index,
847            store,
848            vnodes,
849            op_consistency_level: None,
850            output_column_ids: None,
851            preload_all_rows: (),
852            enable_vnode_key_stats: None,
853            enable_state_table_vnode_stats_pruning: false,
854            metrics: None,
855            _serde: Default::default(),
856        }
857    }
858}
859
860impl<S: StateStore, SD: ValueRowSerde, const IS_REPLICATED: bool>
861    StateTableBuilder<S, SD, IS_REPLICATED, bool>
862{
863    async fn build_inner(
864        self,
865        preload_all_rows: bool,
866        should_enable_vnode_key_stats: bool,
867    ) -> StateTableInner<S, SD, IS_REPLICATED> {
868        let table_id = self.table_id;
869        let table_columns = self.table_columns;
870        let order_types = self.order_types;
871        let pk_indices = self.pk_indices;
872        let dist_key_in_pk_indices = self.dist_key_in_pk_indices;
873        let vnode_col_idx_in_pk = self.vnode_col_idx_in_pk;
874        let prefix_hint_len = self.prefix_hint_len;
875        let metrics = self.metrics;
876
877        let op_consistency_level = self
878            .op_consistency_level
879            .unwrap_or(StateTableOpConsistencyLevel::ConsistentOldValue);
880
881        let output_column_ids = self.output_column_ids.unwrap_or_default();
882
883        let data_types: Vec<DataType> = table_columns
884            .iter()
885            .map(|col| col.data_type.clone())
886            .collect();
887
888        // For replicated state tables (used in hash temporal join), the join key (pk_prefix) is
889        // guaranteed by the optimizer to cover the distribution key, which is required by
890        // `compute_prefix_vnode`. Assert this invariant at build time.
891        if IS_REPLICATED && prefix_hint_len > 0 {
892            assert!(
893                dist_key_in_pk_indices.iter().all(|&d| d < prefix_hint_len),
894                "replicated state table: distribution key indices {:?} must all be covered by \
895                 prefix_hint_len {}",
896                dist_key_in_pk_indices,
897                prefix_hint_len,
898            );
899        }
900
901        let distribution =
902            TableDistribution::new(self.vnodes, dist_key_in_pk_indices, vnode_col_idx_in_pk);
903        assert_eq!(
904            distribution.vnode_count(),
905            self.expected_vnode_count,
906            "vnode count mismatch, scanning table {} under wrong distribution?",
907            self.table_name_for_debug,
908        );
909
910        let pk_data_types = pk_indices
911            .iter()
912            .map(|i| table_columns[*i].data_type.clone())
913            .collect();
914        let pk_serde = OrderedRowSerde::new(pk_data_types, order_types);
915
916        let input_value_indices = self.value_indices;
917
918        let no_shuffle_value_indices = (0..table_columns.len()).collect_vec();
919
920        // if value_indices is the no shuffle full columns.
921        let value_indices = match input_value_indices.len() == table_columns.len()
922            && input_value_indices == no_shuffle_value_indices
923        {
924            true => None,
925            false => Some(input_value_indices.clone()),
926        };
927
928        let row_serde = Arc::new(SD::new(
929            Arc::from_iter(input_value_indices.iter().copied()),
930            Arc::from(table_columns.clone().into_boxed_slice()),
931        ));
932
933        let state_table_op_consistency_level = op_consistency_level;
934        let op_consistency_level = match state_table_op_consistency_level {
935            StateTableOpConsistencyLevel::Inconsistent => OpConsistencyLevel::Inconsistent,
936            StateTableOpConsistencyLevel::ConsistentOldValue => {
937                consistent_old_value_op(row_serde.clone(), false)
938            }
939            StateTableOpConsistencyLevel::LogStoreEnabled => {
940                consistent_old_value_op(row_serde.clone(), true)
941            }
942        };
943
944        let table_option = TableOption::new(self.retention_seconds);
945        let new_local_options = if IS_REPLICATED {
946            NewLocalOptions::new_replicated(
947                table_id,
948                self.fragment_id,
949                op_consistency_level,
950                table_option,
951                distribution.vnodes().clone(),
952            )
953        } else {
954            NewLocalOptions::new(
955                table_id,
956                self.fragment_id,
957                op_consistency_level,
958                table_option,
959                distribution.vnodes().clone(),
960                true,
961            )
962        };
963        let local_state_store = self.store.new_local(new_local_options).await;
964
965        // If state table has versioning, that means it supports
966        // Schema change. In that case, the row encoding should be column aware as well.
967        // Otherwise both will be false.
968        // NOTE(kwannoel): Replicated table will follow upstream table's versioning. I'm not sure
969        // If ALTER TABLE will propagate to this replicated table as well. Ideally it won't
970        assert_eq!(self.versioned, row_serde.kind().is_column_aware());
971
972        // Get info for replicated state table.
973        let output_column_ids_to_input_idx = output_column_ids
974            .iter()
975            .enumerate()
976            .map(|(pos, id)| (*id, pos))
977            .collect::<HashMap<_, _>>();
978
979        let columns = table_columns;
980
981        // Compute i2o mapping
982        // Note that this can be a partial mapping, since we use the i2o mapping to get
983        // any 1 of the output columns, and use that to fill the input column.
984        let mut i2o_mapping = vec![None; columns.len()];
985        for (i, column) in columns.iter().enumerate() {
986            if let Some(pos) = output_column_ids_to_input_idx.get(&column.column_id) {
987                i2o_mapping[i] = Some(*pos);
988            }
989        }
990        // We can prune any duplicate column indices
991        let i2o_mapping = ColIndexMapping::new(i2o_mapping, output_column_ids.len());
992
993        // Compute output indices
994        let (_, output_indices) = find_columns_by_ids(&columns[..], &output_column_ids);
995
996        // For replicated state tables, pk columns must be explicitly provided by the write caller
997        // rather than being filled with None internally via i2o_mapping.
998        if IS_REPLICATED {
999            assert!(
1000                pk_indices
1001                    .iter()
1002                    .all(|&pk_idx| output_indices.contains(&pk_idx)),
1003                "all pk columns must be included in output_column_ids for replicated state table"
1004            );
1005        }
1006
1007        let clean_watermark_index = self.clean_watermark_index;
1008        let watermark_serde = clean_watermark_index.map(|idx| {
1009            let pk_idx = pk_indices.iter().position(|&i| i == idx);
1010            let (watermark_serde, watermark_serde_type) = match pk_idx {
1011                Some(0) => (pk_serde.index(0).into_owned(), WatermarkSerdeType::PkPrefix),
1012                Some(pk_idx) => (
1013                    pk_serde.index(pk_idx).into_owned(),
1014                    WatermarkSerdeType::NonPkPrefix,
1015                ),
1016                None => (
1017                    OrderedRowSerde::new(
1018                        vec![data_types[idx].clone()],
1019                        vec![OrderType::ascending()],
1020                    ),
1021                    WatermarkSerdeType::Value,
1022                ),
1023            };
1024            (watermark_serde, watermark_serde_type)
1025        });
1026
1027        // Restore persisted table watermark.
1028        let committed_watermark = if let Some((deser, _)) = watermark_serde.as_ref() {
1029            distribution
1030                .vnodes()
1031                .iter_vnodes()
1032                .filter_map(|vnode| {
1033                    let bytes = local_state_store.get_table_watermark(vnode)?;
1034                    let datum = deser.deserialize(&bytes).ok().and_then(|row| {
1035                        assert!(row.len() == 1);
1036                        row[0].clone()
1037                    });
1038                    if datum.is_none() {
1039                        tracing::error!(
1040                            ?vnode,
1041                            watermark = ?bytes,
1042                            "Failed to deserialize persisted watermark from state store.",
1043                        );
1044                    }
1045                    datum
1046                })
1047                .max_by(|a, b| cmp_datum(Some(a), Some(b), OrderType::ascending()))
1048        } else {
1049            None
1050        };
1051
1052        StateTableInner {
1053            table_id,
1054            row_store: StateTableRowStore {
1055                all_rows: preload_all_rows.then(HashMap::new),
1056                state_store: local_state_store,
1057                row_serde,
1058                pk_serde: pk_serde.clone(),
1059                table_id,
1060                // Need to maintain vnode min/max key stats when vnode key pruning is enabled
1061                vnode_stats: should_enable_vnode_key_stats.then(HashMap::new),
1062                enable_state_table_vnode_stats_pruning: self.enable_state_table_vnode_stats_pruning,
1063                metrics,
1064            },
1065            store: self.store,
1066            epoch: None,
1067            pk_serde,
1068            pk_indices,
1069            distribution,
1070            prefix_hint_len,
1071            value_indices,
1072            pending_watermark: None,
1073            committed_watermark,
1074            watermark_serde,
1075            data_types,
1076            output_indices,
1077            i2o_mapping,
1078            op_consistency_level: state_table_op_consistency_level,
1079            clean_watermark_index,
1080            on_post_commit: false,
1081        }
1082    }
1083}
1084
1085impl<S: StateStore, SD: ValueRowSerde, const IS_REPLICATED: bool>
1086    StateTableBuilder<S, SD, IS_REPLICATED, ()>
1087{
1088    pub fn new_from_storage_table_desc(
1089        table_desc: &StorageTableDesc,
1090        store: S,
1091        vnodes: Option<Arc<Bitmap>>,
1092        fragment_id: FragmentId,
1093    ) -> Self {
1094        let table_id = table_desc.table_id;
1095        let table_columns: Vec<ColumnDesc> =
1096            table_desc.columns.iter().map(ColumnDesc::from).collect();
1097        let order_types: Vec<OrderType> = table_desc
1098            .pk
1099            .iter()
1100            .map(|col_order| OrderType::from_protobuf(col_order.get_order_type().unwrap()))
1101            .collect();
1102        let pk_indices = table_desc
1103            .pk
1104            .iter()
1105            .map(|col_order| col_order.column_index as usize)
1106            .collect_vec();
1107        let dist_key_in_pk_indices = table_desc
1108            .dist_key_in_pk_indices
1109            .iter()
1110            .map(|&idx| idx as usize)
1111            .collect();
1112        // StorageTableDesc provides vnode_col_idx_in_pk directly (already pk-relative),
1113        // unlike Table which has an absolute column index that needs conversion.
1114        let vnode_col_idx_in_pk = table_desc.vnode_col_idx_in_pk.map(|k| k as usize);
1115        let raw_value_indices = table_desc
1116            .value_indices
1117            .iter()
1118            .map(|val| *val as usize)
1119            .collect_vec();
1120
1121        Self {
1122            table_id,
1123            table_name_for_debug: table_id.to_string(),
1124            table_columns,
1125            order_types,
1126            pk_indices,
1127            dist_key_in_pk_indices,
1128            vnode_col_idx_in_pk,
1129            expected_vnode_count: table_desc.vnode_count(),
1130            value_indices: raw_value_indices,
1131            prefix_hint_len: table_desc.read_prefix_len_hint as usize,
1132            retention_seconds: table_desc.retention_seconds,
1133            versioned: table_desc.versioned,
1134            fragment_id,
1135            clean_watermark_index: None,
1136            store,
1137            vnodes,
1138            op_consistency_level: None,
1139            output_column_ids: None,
1140            preload_all_rows: (),
1141            enable_vnode_key_stats: None,
1142            enable_state_table_vnode_stats_pruning: false,
1143            metrics: None,
1144            _serde: Default::default(),
1145        }
1146    }
1147}
1148
1149impl<S, SD, const IS_REPLICATED: bool> StateTableInner<S, SD, IS_REPLICATED>
1150where
1151    S: StateStore,
1152    SD: ValueRowSerde,
1153{
1154    pub fn get_data_types(&self) -> &[DataType] {
1155        &self.data_types
1156    }
1157
1158    pub fn table_id(&self) -> TableId {
1159        self.table_id
1160    }
1161
1162    /// Get the vnode value with given (prefix of) primary key
1163    fn compute_prefix_vnode(&self, pk_prefix: &impl Row) -> VirtualNode {
1164        self.distribution
1165            .try_compute_vnode_by_pk_prefix(pk_prefix)
1166            .expect("For streaming, the given prefix must be enough to calculate the vnode")
1167    }
1168
1169    /// Get the vnode value of the given primary key
1170    pub fn compute_vnode_by_pk(&self, pk: impl Row) -> VirtualNode {
1171        self.distribution.compute_vnode_by_pk(pk)
1172    }
1173
1174    /// NOTE(kwannoel): This is used by backfill.
1175    /// We want to check pk indices of upstream table.
1176    pub fn pk_indices(&self) -> &[usize] {
1177        &self.pk_indices
1178    }
1179
1180    /// Get the indices of the primary key columns in the output columns.
1181    ///
1182    /// Returns `None` if any of the primary key columns is not in the output columns.
1183    pub fn pk_in_output_indices(&self) -> Option<Vec<usize>> {
1184        assert!(IS_REPLICATED);
1185        self.pk_indices
1186            .iter()
1187            .map(|&i| self.output_indices.iter().position(|&j| i == j))
1188            .collect()
1189    }
1190
1191    pub fn pk_serde(&self) -> &OrderedRowSerde {
1192        &self.pk_serde
1193    }
1194
1195    pub fn vnodes(&self) -> &Arc<Bitmap> {
1196        self.distribution.vnodes()
1197    }
1198
1199    pub fn flushed_snapshot_reader(&self) -> FlushedStateTableReader<S, SD> {
1200        StateTableFlushedSnapshotReader {
1201            reader: Arc::new(self.row_store.state_store.new_flushed_snapshot_reader()),
1202            pk_serde: self.pk_serde.clone(),
1203            vnodes: self.distribution.vnodes().clone(),
1204            row_serde: self.row_store.row_serde.clone(),
1205            metrics: self.row_store.metrics.clone(),
1206        }
1207    }
1208
1209    pub fn value_indices(&self) -> &Option<Vec<usize>> {
1210        &self.value_indices
1211    }
1212
1213    pub fn is_consistent_op(&self) -> bool {
1214        matches!(
1215            self.op_consistency_level,
1216            StateTableOpConsistencyLevel::ConsistentOldValue
1217                | StateTableOpConsistencyLevel::LogStoreEnabled
1218        )
1219    }
1220
1221    pub fn metrics(&self) -> Option<&StateTableMetrics> {
1222        self.row_store.metrics.as_ref()
1223    }
1224}
1225
1226impl<S, SD> StateTableInner<S, SD, true>
1227where
1228    S: StateStore,
1229    SD: ValueRowSerde,
1230{
1231    /// Create replicated state table from table catalog with output indices
1232    pub async fn new_replicated(
1233        table_catalog: &Table,
1234        store: S,
1235        vnodes: Option<Arc<Bitmap>>,
1236        output_column_ids: Vec<ColumnId>,
1237    ) -> Self {
1238        // TODO: can it be ConsistentOldValue?
1239        // TODO: may enable preload_all_rows
1240        StateTableBuilder::new(table_catalog, store, vnodes)
1241            .with_op_consistency_level(StateTableOpConsistencyLevel::Inconsistent)
1242            .with_output_column_ids(output_column_ids)
1243            .forbid_preload_all_rows()
1244            .build()
1245            .await
1246    }
1247}
1248
1249// point get
1250impl<S, SD, const IS_REPLICATED: bool> StateTableInner<S, SD, IS_REPLICATED>
1251where
1252    S: StateStore,
1253    SD: ValueRowSerde,
1254{
1255    /// Get a single row from state table.
1256    pub async fn get_row(&self, pk: impl Row) -> StreamExecutorResult<Option<OwnedRow>> {
1257        let (serialized_pk, prefix_hint) = self.serialize_pk_and_get_prefix_hint(&pk);
1258        let row = self.row_store.get(serialized_pk, prefix_hint).await?;
1259        match row {
1260            Some(row) => {
1261                if IS_REPLICATED {
1262                    // If the table is replicated, we need to deserialize the row with the output
1263                    // indices.
1264                    let row = row.project(&self.output_indices);
1265                    Ok(Some(row.into_owned_row()))
1266                } else {
1267                    Ok(Some(row))
1268                }
1269            }
1270            None => Ok(None),
1271        }
1272    }
1273
1274    /// Get a raw encoded row from state table.
1275    pub async fn exists(&self, pk: impl Row) -> StreamExecutorResult<bool> {
1276        let (serialized_pk, prefix_hint) = self.serialize_pk_and_get_prefix_hint(&pk);
1277        self.row_store.exists(serialized_pk, prefix_hint).await
1278    }
1279
1280    fn serialize_pk(&self, pk: &impl Row) -> TableKey<Bytes> {
1281        assert!(pk.len() <= self.pk_indices.len());
1282        serialize_pk_with_vnode(pk, &self.pk_serde, self.compute_vnode_by_pk(pk))
1283    }
1284
1285    fn serialize_pk_and_get_prefix_hint(&self, pk: &impl Row) -> (TableKey<Bytes>, Option<Bytes>) {
1286        let serialized_pk = self.serialize_pk(&pk);
1287        let prefix_hint = if should_calculate_prefix_hint(self.prefix_hint_len, pk.len(), false) {
1288            Some(serialized_pk.slice(VirtualNode::SIZE..))
1289        } else {
1290            #[cfg(debug_assertions)]
1291            if self.prefix_hint_len != 0 {
1292                warn!(
1293                    "prefix_hint_len is not equal to pk.len(), may not be able to utilize bloom filter"
1294                );
1295            }
1296            None
1297        };
1298        (serialized_pk, prefix_hint)
1299    }
1300}
1301
1302impl<LS: LocalStateStore, SD: ValueRowSerde> StateTableRowStore<LS, SD> {
1303    async fn get(
1304        &self,
1305        key_bytes: TableKey<Bytes>,
1306        prefix_hint: Option<Bytes>,
1307    ) -> StreamExecutorResult<Option<OwnedRow>> {
1308        if let Some(m) = &self.metrics {
1309            m.get_count.inc();
1310        }
1311        if let Some(rows) = &self.all_rows {
1312            let (vnode, key) = key_bytes.split_vnode_bytes();
1313            return Ok(rows.get(&vnode).expect("covered vnode").get(&key).cloned());
1314        }
1315
1316        // Try to prune using vnode statistics
1317        let should_prune = if let Some(stats) = &self.vnode_stats
1318            && let (vnode, key) = key_bytes.split_vnode_bytes()
1319            && let Some(vnode_stat) = stats.get(&vnode)
1320            && vnode_stat.can_prune(&key)
1321        {
1322            if let Some(m) = &self.metrics {
1323                m.get_vnode_pruned_count.inc();
1324            }
1325            true
1326        } else {
1327            false
1328        };
1329
1330        if should_prune && self.enable_state_table_vnode_stats_pruning {
1331            return Ok(None);
1332        }
1333
1334        let read_options = ReadOptions {
1335            prefix_hint,
1336            cache_policy: CachePolicy::Fill(Hint::Normal),
1337            ..Default::default()
1338        };
1339
1340        let result = self
1341            .state_store
1342            .on_key_value(key_bytes, read_options, move |_, value| {
1343                let row = self.row_serde.deserialize(value)?;
1344                Ok(OwnedRow::new(row))
1345            })
1346            .await
1347            .map_err(Into::<StreamExecutorError>::into)?;
1348
1349        // In dry-run mode, verify that pruning would have been correct
1350        if should_prune && result.is_some() {
1351            tracing::warn!(
1352                table_id = %self.table_id,
1353                "vnode stats pruning dry run fails for get. This will not affect correctness."
1354            );
1355        }
1356
1357        Ok(result)
1358    }
1359
1360    async fn exists(
1361        &self,
1362        key_bytes: TableKey<Bytes>,
1363        prefix_hint: Option<Bytes>,
1364    ) -> StreamExecutorResult<bool> {
1365        if let Some(m) = &self.metrics {
1366            m.get_count.inc();
1367        }
1368        if let Some(rows) = &self.all_rows {
1369            let (vnode, key) = key_bytes.split_vnode_bytes();
1370            return Ok(rows.get(&vnode).expect("covered vnode").contains_key(&key));
1371        }
1372
1373        // Try to prune using vnode statistics
1374        let should_prune = if let Some(stats) = &self.vnode_stats
1375            && let (vnode, key) = key_bytes.split_vnode_bytes()
1376            && let Some(vnode_stat) = stats.get(&vnode)
1377            && vnode_stat.can_prune(&key)
1378        {
1379            if let Some(m) = &self.metrics {
1380                m.get_vnode_pruned_count.inc();
1381            }
1382            true
1383        } else {
1384            false
1385        };
1386
1387        if should_prune && self.enable_state_table_vnode_stats_pruning {
1388            return Ok(false);
1389        }
1390
1391        let read_options = ReadOptions {
1392            prefix_hint,
1393            cache_policy: CachePolicy::Fill(Hint::Normal),
1394            ..Default::default()
1395        };
1396        let result = self
1397            .state_store
1398            .on_key_value(key_bytes, read_options, move |_, _| Ok(()))
1399            .await?;
1400        let exists = result.is_some();
1401
1402        // In dry-run mode, verify that pruning would have been correct
1403        if should_prune && exists {
1404            tracing::warn!(
1405                table_id = %self.table_id,
1406                "vnode stats pruning dry run fails for exists. This will not affect correctness."
1407            );
1408        }
1409
1410        Ok(exists)
1411    }
1412}
1413
1414/// A callback struct returned from [`StateTableInner::commit`].
1415///
1416/// Introduced to support single barrier configuration change proposed in <https://github.com/risingwavelabs/risingwave/issues/18312>.
1417/// In brief, to correctly handle the configuration change, when each stateful executor receives an upstream barrier, it should handle
1418/// the barrier in the order of `state_table.commit()` -> `yield barrier` -> `update_vnode_bitmap`.
1419///
1420/// The `StateTablePostCommit` captures the mutable reference of `state_table` when calling `state_table.commit()`, and after the executor
1421/// runs `yield barrier`, it should call `StateTablePostCommit::post_yield_barrier` to apply the vnode bitmap update if there is any.
1422/// The `StateTablePostCommit` is marked with `must_use`. The method name `post_yield_barrier` indicates that it should be called after
1423/// we have yielded the barrier. In `StateTable`, we add a flag `on_post_commit`, to indicate that whether the `StateTablePostCommit` is handled
1424/// properly. On `state_table.commit()`, we will mark the `on_post_commit` as true, and in `StateTablePostCommit::post_yield_barrier`, we will
1425/// remark the flag as false, and on `state_table.commit()`, we will assert that the `on_post_commit` must be false. Note that, the `post_yield_barrier`
1426/// should be called for all barriers rather than only for the barrier with update vnode bitmap. In this way, though we don't have scale test for all
1427/// streaming executor, we can ensure that all executor covered by normal e2e test have properly handled the `StateTablePostCommit`.
1428#[must_use]
1429pub struct StateTablePostCommit<'a, S, SD = BasicSerde, const IS_REPLICATED: bool = false>
1430where
1431    S: StateStore,
1432    SD: ValueRowSerde,
1433{
1434    inner: &'a mut StateTableInner<S, SD, IS_REPLICATED>,
1435}
1436
1437impl<'a, S, SD, const IS_REPLICATED: bool> StateTablePostCommit<'a, S, SD, IS_REPLICATED>
1438where
1439    S: StateStore,
1440    SD: ValueRowSerde,
1441{
1442    /// Returns `Some((new_vnodes, old_vnodes, state_table), keyed_cache_may_stale)` if the vnode bitmap is updated.
1443    ///
1444    /// Note the `keyed_cache_may_stale` only applies to keyed cache. If the executor's cache is not keyed, but will
1445    /// be consumed with all vnodes it owns, the executor may need to ALWAYS clear the cache regardless of this flag.
1446    pub async fn post_yield_barrier(
1447        mut self,
1448        new_vnodes: Option<Arc<Bitmap>>,
1449    ) -> StreamExecutorResult<
1450        Option<(
1451            (
1452                Arc<Bitmap>,
1453                Arc<Bitmap>,
1454                &'a mut StateTableInner<S, SD, IS_REPLICATED>,
1455            ),
1456            bool,
1457        )>,
1458    > {
1459        self.inner.on_post_commit = false;
1460        Ok(if let Some(new_vnodes) = new_vnodes {
1461            let (old_vnodes, keyed_cache_may_stale) =
1462                self.update_vnode_bitmap(new_vnodes.clone()).await?;
1463            Some(((new_vnodes, old_vnodes, self.inner), keyed_cache_may_stale))
1464        } else {
1465            None
1466        })
1467    }
1468
1469    pub fn inner(&self) -> &StateTableInner<S, SD, IS_REPLICATED> {
1470        &*self.inner
1471    }
1472
1473    /// Update the vnode bitmap of the state table, returns the previous vnode bitmap.
1474    async fn update_vnode_bitmap(
1475        &mut self,
1476        new_vnodes: Arc<Bitmap>,
1477    ) -> StreamExecutorResult<(Arc<Bitmap>, bool)> {
1478        let prev_vnodes = self
1479            .inner
1480            .row_store
1481            .update_vnode_bitmap(new_vnodes.clone())
1482            .await?;
1483        assert_eq!(
1484            &prev_vnodes,
1485            self.inner.vnodes(),
1486            "state table and state store vnode bitmap mismatches"
1487        );
1488
1489        if self.inner.distribution.is_singleton() {
1490            assert_eq!(
1491                &new_vnodes,
1492                self.inner.vnodes(),
1493                "should not update vnode bitmap for singleton table"
1494            );
1495        }
1496        assert_eq!(self.inner.vnodes().len(), new_vnodes.len());
1497
1498        let keyed_cache_may_stale = keyed_cache_may_stale(self.inner.vnodes(), &new_vnodes);
1499
1500        if keyed_cache_may_stale {
1501            self.inner.pending_watermark = None;
1502        }
1503
1504        Ok((
1505            self.inner.distribution.update_vnode_bitmap(new_vnodes),
1506            keyed_cache_may_stale,
1507        ))
1508    }
1509}
1510
1511// write
1512impl<LS: LocalStateStore, SD: ValueRowSerde> StateTableRowStore<LS, SD> {
1513    fn handle_mem_table_error(&self, e: StorageError) {
1514        let e = match e.into_inner() {
1515            ErrorKind::MemTable(e) => e,
1516            _ => unreachable!("should only get memtable error"),
1517        };
1518        match *e {
1519            MemTableError::InconsistentOperation { key, prev, new, .. } => {
1520                let (vnode, key) = deserialize_pk_with_vnode(&key, &self.pk_serde).unwrap();
1521                panic!(
1522                    "mem-table operation inconsistent! table_id: {}, vnode: {}, key: {:?}, prev: {}, new: {}",
1523                    self.table_id,
1524                    vnode,
1525                    key,
1526                    prev.debug_fmt(&*self.row_serde),
1527                    new.debug_fmt(&*self.row_serde),
1528                )
1529            }
1530        }
1531    }
1532
1533    fn insert(&mut self, key: TableKey<Bytes>, value: impl Row) {
1534        insane_mode_discard_point!();
1535        let value_bytes = self.row_serde.serialize(&value).into();
1536
1537        let (vnode, key_without_vnode) = key.split_vnode_bytes();
1538
1539        // Update vnode statistics (skip if all_rows is present)
1540        if self.all_rows.is_none()
1541            && let Some(stats) = &mut self.vnode_stats
1542            && let Some(vnode_stat) = stats.get_mut(&vnode)
1543        {
1544            vnode_stat.update_with_key(&key_without_vnode);
1545        }
1546
1547        if let Some(rows) = &mut self.all_rows {
1548            rows.get_mut(&vnode)
1549                .expect("covered vnode")
1550                .insert(key_without_vnode, value.into_owned_row());
1551        }
1552        self.state_store
1553            .insert(key, value_bytes, None)
1554            .unwrap_or_else(|e| self.handle_mem_table_error(e));
1555    }
1556
1557    fn delete(&mut self, key: TableKey<Bytes>, value: impl Row) {
1558        insane_mode_discard_point!();
1559        let value_bytes = self.row_serde.serialize(value).into();
1560
1561        let (vnode, key_without_vnode) = key.split_vnode_bytes();
1562
1563        if self.all_rows.is_none()
1564            && let Some(stats) = &mut self.vnode_stats
1565            && let Some(vnode_stat) = stats.get_mut(&vnode)
1566        {
1567            vnode_stat.update_with_key(&key_without_vnode);
1568        }
1569
1570        if let Some(rows) = &mut self.all_rows {
1571            rows.get_mut(&vnode)
1572                .expect("covered vnode")
1573                .remove(&key_without_vnode);
1574        }
1575        self.state_store
1576            .delete(key, value_bytes)
1577            .unwrap_or_else(|e| self.handle_mem_table_error(e));
1578    }
1579
1580    fn update(&mut self, key_bytes: TableKey<Bytes>, old_value: impl Row, new_value: impl Row) {
1581        insane_mode_discard_point!();
1582        let new_value_bytes = self.row_serde.serialize(&new_value).into();
1583        let old_value_bytes = self.row_serde.serialize(old_value).into();
1584
1585        let (vnode, key_without_vnode) = key_bytes.split_vnode_bytes();
1586
1587        // Update does not change the key, so statistics remain valid (skip if all_rows is present)
1588        // But we update to ensure consistency
1589        if self.all_rows.is_none()
1590            && let Some(stats) = &mut self.vnode_stats
1591            && let Some(vnode_stat) = stats.get_mut(&vnode)
1592        {
1593            vnode_stat.update_with_key(&key_without_vnode);
1594        }
1595
1596        if let Some(rows) = &mut self.all_rows {
1597            rows.get_mut(&vnode)
1598                .expect("covered vnode")
1599                .insert(key_without_vnode, new_value.into_owned_row());
1600        }
1601        self.state_store
1602            .insert(key_bytes, new_value_bytes, Some(old_value_bytes))
1603            .unwrap_or_else(|e| self.handle_mem_table_error(e));
1604    }
1605}
1606
1607impl<S, SD, const IS_REPLICATED: bool> StateTableInner<S, SD, IS_REPLICATED>
1608where
1609    S: StateStore,
1610    SD: ValueRowSerde,
1611{
1612    /// Insert a row into state table. Must provide a full row corresponding to the column desc of
1613    /// the table.
1614    pub fn insert(&mut self, value: impl Row) {
1615        let pk_indices = &self.pk_indices;
1616        let pk = (&value).project(pk_indices);
1617
1618        let key_bytes = self.serialize_pk(&pk);
1619        dispatch_value_indices!(&self.value_indices, [value], {
1620            self.row_store.insert(key_bytes, value)
1621        })
1622    }
1623
1624    /// Delete a row from state table. Must provide a full row of old value corresponding to the
1625    /// column desc of the table.
1626    pub fn delete(&mut self, old_value: impl Row) {
1627        let pk_indices = &self.pk_indices;
1628        let pk = (&old_value).project(pk_indices);
1629
1630        let key_bytes = self.serialize_pk(&pk);
1631        dispatch_value_indices!(&self.value_indices, [old_value], {
1632            self.row_store.delete(key_bytes, old_value)
1633        })
1634    }
1635
1636    /// Update a row. The old and new value should have the same pk.
1637    pub fn update(&mut self, old_value: impl Row, new_value: impl Row) {
1638        let old_pk = (&old_value).project(self.pk_indices());
1639        let new_pk = (&new_value).project(self.pk_indices());
1640        debug_assert!(
1641            Row::eq(&old_pk, new_pk),
1642            "pk should not change: {old_pk:?} vs {new_pk:?}. {}",
1643            self.table_id
1644        );
1645
1646        let key_bytes = self.serialize_pk(&new_pk);
1647        dispatch_value_indices!(&self.value_indices, [old_value, new_value], {
1648            self.row_store.update(key_bytes, old_value, new_value)
1649        })
1650    }
1651
1652    /// Write a record into state table. Must have the same schema with the table.
1653    pub fn write_record(&mut self, record: Record<impl Row>) {
1654        match record {
1655            Record::Insert { new_row } => self.insert(new_row),
1656            Record::Delete { old_row } => self.delete(old_row),
1657            Record::Update { old_row, new_row } => self.update(old_row, new_row),
1658        }
1659    }
1660
1661    fn fill_non_output_indices(&self, chunk: StreamChunk) -> StreamChunk {
1662        fill_non_output_indices(&self.i2o_mapping, &self.data_types, chunk)
1663    }
1664
1665    /// Write batch with a `StreamChunk` which should have the same schema with the table.
1666    // allow(izip, which use zip instead of zip_eq)
1667    #[allow(clippy::disallowed_methods)]
1668    pub fn write_chunk(&mut self, chunk: StreamChunk) {
1669        let chunk = if IS_REPLICATED {
1670            self.fill_non_output_indices(chunk)
1671        } else {
1672            chunk
1673        };
1674
1675        let vnodes = self
1676            .distribution
1677            .compute_chunk_vnode(&chunk, &self.pk_indices);
1678
1679        for (idx, optional_row) in chunk.rows_with_holes().enumerate() {
1680            let Some((op, row)) = optional_row else {
1681                continue;
1682            };
1683            let pk = row.project(&self.pk_indices);
1684            let vnode = vnodes[idx];
1685            let key_bytes = serialize_pk_with_vnode(pk, &self.pk_serde, vnode);
1686            match op {
1687                Op::Insert | Op::UpdateInsert => {
1688                    dispatch_value_indices!(&self.value_indices, [row], {
1689                        self.row_store.insert(key_bytes, row);
1690                    });
1691                }
1692                Op::Delete | Op::UpdateDelete => {
1693                    dispatch_value_indices!(&self.value_indices, [row], {
1694                        self.row_store.delete(key_bytes, row);
1695                    });
1696                }
1697            }
1698        }
1699    }
1700
1701    /// Update watermark for state cleaning.
1702    ///
1703    /// # Arguments
1704    ///
1705    /// * `watermark` - Latest watermark received.
1706    pub fn update_watermark(&mut self, watermark: ScalarImpl) {
1707        trace!(table_id = %self.table_id, watermark = ?watermark, "update watermark");
1708        self.pending_watermark = Some(watermark);
1709    }
1710
1711    /// Get the committed watermark of the state table. Watermarks should be fed into the state
1712    /// table through `update_watermark` method.
1713    pub fn get_committed_watermark(&self) -> Option<&ScalarImpl> {
1714        self.committed_watermark.as_ref()
1715    }
1716
1717    pub async fn commit(
1718        &mut self,
1719        new_epoch: EpochPair,
1720    ) -> StreamExecutorResult<StateTablePostCommit<'_, S, SD, IS_REPLICATED>> {
1721        self.commit_inner(new_epoch, None).await
1722    }
1723
1724    #[cfg(test)]
1725    pub async fn commit_for_test(&mut self, new_epoch: EpochPair) -> StreamExecutorResult<()> {
1726        self.commit_assert_no_update_vnode_bitmap(new_epoch).await
1727    }
1728
1729    pub async fn commit_assert_no_update_vnode_bitmap(
1730        &mut self,
1731        new_epoch: EpochPair,
1732    ) -> StreamExecutorResult<()> {
1733        let post_commit = self.commit_inner(new_epoch, None).await?;
1734        post_commit.post_yield_barrier(None).await?;
1735        Ok(())
1736    }
1737
1738    pub async fn commit_may_switch_consistent_op(
1739        &mut self,
1740        new_epoch: EpochPair,
1741        op_consistency_level: StateTableOpConsistencyLevel,
1742    ) -> StreamExecutorResult<StateTablePostCommit<'_, S, SD, IS_REPLICATED>> {
1743        if self.op_consistency_level != op_consistency_level {
1744            // avoid flooding e2e-test log
1745            if !cfg!(debug_assertions) {
1746                info!(
1747                    ?new_epoch,
1748                    prev_op_consistency_level = ?self.op_consistency_level,
1749                    ?op_consistency_level,
1750                    table_id = %self.table_id,
1751                    "switch to new op consistency level"
1752                );
1753            }
1754            self.commit_inner(new_epoch, Some(op_consistency_level))
1755                .await
1756        } else {
1757            self.commit_inner(new_epoch, None).await
1758        }
1759    }
1760
1761    async fn commit_inner(
1762        &mut self,
1763        new_epoch: EpochPair,
1764        switch_consistent_op: Option<StateTableOpConsistencyLevel>,
1765    ) -> StreamExecutorResult<StateTablePostCommit<'_, S, SD, IS_REPLICATED>> {
1766        assert!(!self.on_post_commit);
1767        assert_eq!(
1768            self.epoch.expect("should only be called after init").curr,
1769            new_epoch.prev
1770        );
1771        if let Some(new_consistency_level) = switch_consistent_op {
1772            assert_ne!(self.op_consistency_level, new_consistency_level);
1773            self.op_consistency_level = new_consistency_level;
1774        }
1775        trace!(
1776            table_id = %self.table_id,
1777            epoch = ?self.epoch,
1778            "commit state table"
1779        );
1780
1781        let table_watermarks = self.commit_pending_watermark();
1782        self.row_store
1783            .seal_current_epoch(new_epoch.curr, table_watermarks, switch_consistent_op)
1784            .await?;
1785        self.epoch = Some(new_epoch);
1786
1787        self.on_post_commit = true;
1788        Ok(StateTablePostCommit { inner: self })
1789    }
1790
1791    /// Commit pending watermark and return vnode bitmap-watermark pairs to seal.
1792    fn commit_pending_watermark(
1793        &mut self,
1794    ) -> Option<(WatermarkDirection, Vec<VnodeWatermark>, WatermarkSerdeType)> {
1795        let watermark = self.pending_watermark.take()?;
1796        trace!(table_id = %self.table_id, watermark = ?watermark, "state cleaning");
1797
1798        assert!(
1799            !self.pk_indices().is_empty(),
1800            "see pending watermark on empty pk"
1801        );
1802        let (watermark_serializer, watermark_type) = self
1803            .watermark_serde
1804            .as_ref()
1805            .expect("watermark serde should be initialized to commit watermark");
1806        let watermark_suffix =
1807            serialize_row(row::once(Some(watermark.clone())), watermark_serializer);
1808        let vnode_watermark = VnodeWatermark::new(
1809            self.vnodes().clone(),
1810            Bytes::copy_from_slice(watermark_suffix.as_ref()),
1811        );
1812        trace!(table_id = %self.table_id, ?vnode_watermark, "table watermark");
1813
1814        let order_type = watermark_serializer.get_order_types().get(0).unwrap();
1815        let direction = if order_type.is_ascending() {
1816            WatermarkDirection::Ascending
1817        } else {
1818            WatermarkDirection::Descending
1819        };
1820
1821        self.committed_watermark = Some(watermark);
1822        Some((direction, vec![vnode_watermark], *watermark_type))
1823    }
1824
1825    pub async fn try_flush(&mut self) -> StreamExecutorResult<()> {
1826        self.row_store.try_flush().await?;
1827        Ok(())
1828    }
1829}
1830
1831// Manually expand trait alias for better IDE experience.
1832pub trait RowStream<'a>: Stream<Item = StreamExecutorResult<OwnedRow>> + 'a {}
1833impl<'a, S: Stream<Item = StreamExecutorResult<OwnedRow>> + 'a> RowStream<'a> for S {}
1834
1835pub trait KeyedRowStream<'a>: Stream<Item = StreamExecutorResult<KeyedRow<Bytes>>> + 'a {}
1836impl<'a, S: Stream<Item = StreamExecutorResult<KeyedRow<Bytes>>> + 'a> KeyedRowStream<'a> for S {}
1837
1838pub trait PkRowStream<'a, K>: Stream<Item = StreamExecutorResult<(K, OwnedRow)>> + 'a {}
1839impl<'a, K, S: Stream<Item = StreamExecutorResult<(K, OwnedRow)>> + 'a> PkRowStream<'a, K> for S {}
1840
1841pub type BoxedRowStream<'a> = BoxStream<'a, StreamExecutorResult<OwnedRow>>;
1842
1843pub trait FromVnodeBytes {
1844    fn from_vnode_bytes(vnode: VirtualNode, bytes: &Bytes) -> Self;
1845}
1846
1847impl FromVnodeBytes for Bytes {
1848    fn from_vnode_bytes(vnode: VirtualNode, bytes: &Bytes) -> Self {
1849        prefix_slice_with_vnode(vnode, bytes)
1850    }
1851}
1852
1853impl FromVnodeBytes for () {
1854    fn from_vnode_bytes(_vnode: VirtualNode, _bytes: &Bytes) -> Self {}
1855}
1856
1857impl<R, SD> StateTableFlushedSnapshotReader<R, SD>
1858where
1859    R: StateStoreRead,
1860    SD: ValueRowSerde,
1861{
1862    pub fn vnodes(&self) -> &Arc<Bitmap> {
1863        &self.vnodes
1864    }
1865
1866    /// Scans flushed local state without reading uncommitted mem-table data.
1867    pub async fn iter_with_vnode(
1868        &self,
1869        vnode: VirtualNode,
1870        pk_range: &(Bound<impl Row>, Bound<impl Row>),
1871        prefetch_options: PrefetchOptions,
1872    ) -> StreamExecutorResult<impl RowStream<'static>> {
1873        if let Some(m) = &self.metrics {
1874            m.iter_count.inc();
1875        }
1876
1877        let memcomparable_range = prefix_range_to_memcomparable(&self.pk_serde, pk_range);
1878        let iter = self
1879            .reader
1880            .iter(
1881                prefixed_range_with_vnode(memcomparable_range, vnode),
1882                ReadOptions {
1883                    prefix_hint: None,
1884                    prefetch_options,
1885                    cache_policy: CachePolicy::Fill(Hint::Normal),
1886                },
1887            )
1888            .await?;
1889        let row_serde = self.row_serde.clone();
1890        Ok(iter
1891            .into_stream(move |(_key, value)| Ok(OwnedRow::new(row_serde.deserialize(value)?)))
1892            .map_err(Into::into))
1893    }
1894}
1895
1896// Iterator functions
1897impl<S, SD, const IS_REPLICATED: bool> StateTableInner<S, SD, IS_REPLICATED>
1898where
1899    S: StateStore,
1900    SD: ValueRowSerde,
1901{
1902    /// This function scans rows from the relational table with specific `pk_range` under the same
1903    /// `vnode`.
1904    pub async fn iter_with_vnode(
1905        &self,
1906
1907        // Optional vnode that returns an iterator only over the given range under that vnode.
1908        // For now, we require this parameter, and will panic. In the future, when `None`, we can
1909        // iterate over each vnode that the `StateTableInner` owns.
1910        vnode: VirtualNode,
1911        pk_range: &(Bound<impl Row>, Bound<impl Row>),
1912        prefetch_options: PrefetchOptions,
1913    ) -> StreamExecutorResult<impl RowStream<'_>> {
1914        Ok(self
1915            .iter_kv_with_pk_range::<()>(pk_range, vnode, prefetch_options)
1916            .await?
1917            .map_ok(|(_, row)| {
1918                if IS_REPLICATED {
1919                    row.project(&self.output_indices).into_owned_row()
1920                } else {
1921                    row
1922                }
1923            }))
1924    }
1925
1926    pub async fn iter_keyed_row_with_vnode(
1927        &self,
1928        vnode: VirtualNode,
1929        pk_range: &(Bound<impl Row>, Bound<impl Row>),
1930        prefetch_options: PrefetchOptions,
1931    ) -> StreamExecutorResult<impl KeyedRowStream<'_>> {
1932        Ok(self
1933            .iter_kv_with_pk_range(pk_range, vnode, prefetch_options)
1934            .await?
1935            .map_ok(|(key, row)| KeyedRow::new(TableKey(key), row)))
1936    }
1937}
1938
1939impl<LS: LocalStateStore, SD: ValueRowSerde> StateTableRowStore<LS, SD> {
1940    // The lowest-level API.
1941    /// Middle-level APIs:
1942    /// - [`StateTableInner::iter_with_prefix_inner`]
1943    /// - [`StateTableInner::iter_kv_with_pk_range`]
1944    async fn iter_kv<K: CopyFromSlice + FromVnodeBytes>(
1945        &self,
1946        vnode: VirtualNode,
1947        (start, end): (Bound<Bytes>, Bound<Bytes>),
1948        prefix_hint: Option<Bytes>,
1949        prefetch_options: PrefetchOptions,
1950    ) -> StreamExecutorResult<impl PkRowStream<'_, K>> {
1951        if let Some(m) = &self.metrics {
1952            m.iter_count.inc();
1953        }
1954        // Check if we can prune the entire range using vnode statistics
1955        let (pruned_start, pruned_end, should_prune_entirely) = if let Some(stats) =
1956            &self.vnode_stats
1957            && let Some(vnode_stat) = stats.get(&vnode)
1958        {
1959            match vnode_stat.pruned_key_range(&start, &end) {
1960                Some((new_start, new_end)) => {
1961                    if self.enable_state_table_vnode_stats_pruning {
1962                        (new_start, new_end, false)
1963                    } else {
1964                        // In dry-run mode, we don't apply pruning but verify correctness
1965                        (start, end, false)
1966                    }
1967                }
1968                None => {
1969                    if let Some(m) = &self.metrics {
1970                        m.iter_vnode_pruned_count.inc();
1971                    }
1972                    // Mark that we should prune entirely, but handle dry-run below
1973                    (start.clone(), end.clone(), true)
1974                }
1975            }
1976        } else {
1977            (start, end, false)
1978        };
1979
1980        if should_prune_entirely && self.enable_state_table_vnode_stats_pruning {
1981            return Ok(futures::future::Either::Left(futures::stream::empty()));
1982        }
1983
1984        let table_id = self.table_id;
1985        let inspect_fn = move |result: &StreamExecutorResult<(K, OwnedRow)>| {
1986            // Only log when in dry-run mode and we would have pruned but got results
1987            if should_prune_entirely && result.is_ok() {
1988                tracing::warn!(
1989                    table_id = %table_id,
1990                    "vnode stats pruning dry run fails for iter. This will not affect correctness."
1991                );
1992            }
1993        };
1994
1995        if let Some(rows) = &self.all_rows {
1996            return Ok(futures::future::Either::Right(
1997                futures::future::Either::Left(
1998                    futures::stream::iter(
1999                        rows.get(&vnode)
2000                            .expect("covered vnode")
2001                            .range((pruned_start, pruned_end))
2002                            .map(move |(key, value)| {
2003                                Ok((K::from_vnode_bytes(vnode, key), value.clone()))
2004                            }),
2005                    )
2006                    .inspect(inspect_fn),
2007                ),
2008            ));
2009        }
2010        let read_options = ReadOptions {
2011            prefix_hint,
2012            prefetch_options,
2013            cache_policy: CachePolicy::Fill(Hint::Normal),
2014        };
2015
2016        Ok(futures::future::Either::Right(
2017            futures::future::Either::Right(
2018                deserialize_keyed_row_stream(
2019                    self.state_store
2020                        .iter(
2021                            prefixed_range_with_vnode((pruned_start, pruned_end), vnode),
2022                            read_options,
2023                        )
2024                        .await?,
2025                    &*self.row_serde,
2026                )
2027                .inspect(inspect_fn),
2028            ),
2029        ))
2030    }
2031
2032    async fn rev_iter_kv<K: CopyFromSlice + FromVnodeBytes>(
2033        &self,
2034        vnode: VirtualNode,
2035        (start, end): (Bound<Bytes>, Bound<Bytes>),
2036        prefix_hint: Option<Bytes>,
2037        prefetch_options: PrefetchOptions,
2038    ) -> StreamExecutorResult<impl PkRowStream<'_, K>> {
2039        if let Some(m) = &self.metrics {
2040            m.iter_count.inc();
2041        }
2042        // Check if we can prune the entire range using vnode statistics
2043        let (pruned_start, pruned_end, should_prune_entirely) = if let Some(stats) =
2044            &self.vnode_stats
2045            && let Some(vnode_stat) = stats.get(&vnode)
2046        {
2047            match vnode_stat.pruned_key_range(&start, &end) {
2048                Some((new_start, new_end)) => {
2049                    if self.enable_state_table_vnode_stats_pruning {
2050                        (new_start, new_end, false)
2051                    } else {
2052                        // In dry-run mode, we don't apply pruning but verify correctness
2053                        (start, end, false)
2054                    }
2055                }
2056                None => {
2057                    if let Some(m) = &self.metrics {
2058                        m.iter_vnode_pruned_count.inc();
2059                    }
2060                    // Mark that we should prune entirely, but handle dry-run below
2061                    (start, end, true)
2062                }
2063            }
2064        } else {
2065            (start, end, false)
2066        };
2067
2068        if should_prune_entirely && self.enable_state_table_vnode_stats_pruning {
2069            return Ok(futures::future::Either::Left(futures::stream::empty()));
2070        }
2071
2072        let table_id = self.table_id;
2073        let inspect_fn = move |result: &StreamExecutorResult<(K, OwnedRow)>| {
2074            // Only log when in dry-run mode and we would have pruned but got results
2075            if should_prune_entirely && result.is_ok() {
2076                tracing::warn!(
2077                    table_id = %table_id,
2078                    "vnode stats pruning dry run fails for rev_iter. This will not affect correctness."
2079                );
2080            }
2081        };
2082
2083        if let Some(rows) = &self.all_rows {
2084            return Ok(futures::future::Either::Right(
2085                futures::future::Either::Left(
2086                    futures::stream::iter(
2087                        rows.get(&vnode)
2088                            .expect("covered vnode")
2089                            .range((pruned_start, pruned_end))
2090                            .rev()
2091                            .map(move |(key, value)| {
2092                                Ok((K::from_vnode_bytes(vnode, key), value.clone()))
2093                            }),
2094                    )
2095                    .inspect(inspect_fn),
2096                ),
2097            ));
2098        }
2099        let read_options = ReadOptions {
2100            prefix_hint,
2101            prefetch_options,
2102            cache_policy: CachePolicy::Fill(Hint::Normal),
2103        };
2104
2105        Ok(futures::future::Either::Right(
2106            futures::future::Either::Right(
2107                deserialize_keyed_row_stream(
2108                    self.state_store
2109                        .rev_iter(
2110                            prefixed_range_with_vnode((pruned_start, pruned_end), vnode),
2111                            read_options,
2112                        )
2113                        .await?,
2114                    &*self.row_serde,
2115                )
2116                .inspect(inspect_fn),
2117            ),
2118        ))
2119    }
2120}
2121
2122impl<S, SD, const IS_REPLICATED: bool> StateTableInner<S, SD, IS_REPLICATED>
2123where
2124    S: StateStore,
2125    SD: ValueRowSerde,
2126{
2127    /// This function scans rows from the relational table with specific `prefix` and `sub_range` under the same
2128    /// `vnode`. If `sub_range` is (Unbounded, Unbounded), it scans rows from the relational table with specific `pk_prefix`.
2129    /// `pk_prefix` is used to identify the exact vnode the scan should perform on.
2130    pub async fn iter_with_prefix(
2131        &self,
2132        pk_prefix: impl Row,
2133        sub_range: &(Bound<impl Row>, Bound<impl Row>),
2134        prefetch_options: PrefetchOptions,
2135    ) -> StreamExecutorResult<impl RowStream<'_>> {
2136        let stream = self.iter_with_prefix_inner::</* REVERSE */ false, ()>(pk_prefix, sub_range, prefetch_options)
2137            .await?;
2138        Ok(stream.map_ok(|(_, row)| {
2139            if IS_REPLICATED {
2140                row.project(&self.output_indices).into_owned_row()
2141            } else {
2142                row
2143            }
2144        }))
2145    }
2146
2147    /// This function scans rows from the relational table with specific `prefix` and `sub_range` under the same
2148    /// `vnode`, and filters out rows based on watermarks. It calls `iter_with_prefix` and further filters rows
2149    /// based on the table watermark retrieved from the state store.
2150    ///
2151    /// The caller must ensure that `clean_watermark_index` is set before calling this method, otherwise it will return all rows without filtering.
2152    pub async fn iter_with_prefix_respecting_watermark(
2153        &self,
2154        pk_prefix: impl Row,
2155        sub_range: &(Bound<impl Row>, Bound<impl Row>),
2156        prefetch_options: PrefetchOptions,
2157    ) -> StreamExecutorResult<BoxedRowStream<'_>> {
2158        let vnode = self.compute_prefix_vnode(&pk_prefix);
2159        let Some(clean_watermark_index) = self.clean_watermark_index else {
2160            return self
2161                .iter_with_prefix(pk_prefix, sub_range, prefetch_options)
2162                .await
2163                .map(|s| s.boxed());
2164        };
2165        let Some((watermark_serde, watermark_type)) = &self.watermark_serde else {
2166            return Err(StreamExecutorError::from(anyhow!(
2167                "Missing watermark serde"
2168            )));
2169        };
2170        // Fast path. TableWatermarksIndex::rewrite_range_with_table_watermark has already filtered the rows.
2171        if matches!(watermark_type, WatermarkSerdeType::PkPrefix) {
2172            return self
2173                .iter_with_prefix(pk_prefix, sub_range, prefetch_options)
2174                .await
2175                .map(|s| s.boxed());
2176        }
2177
2178        let watermark_bytes = self.row_store.state_store.get_table_watermark(vnode);
2179        let Some(watermark_bytes) = watermark_bytes else {
2180            return self
2181                .iter_with_prefix(pk_prefix, sub_range, prefetch_options)
2182                .await
2183                .map(|s| s.boxed());
2184        };
2185        let watermark_row = watermark_serde.deserialize(&watermark_bytes)?;
2186        if watermark_row.len() != 1 {
2187            return Err(StreamExecutorError::from(format!(
2188                "Watermark row should have exactly 1 column, got {}",
2189                watermark_row.len()
2190            )));
2191        }
2192        let watermark_value = watermark_row[0].clone();
2193        // StateTableInner::update_watermark should ensure that the watermark is not NULL
2194        if watermark_value.is_none() {
2195            return Err(StreamExecutorError::from(anyhow!(
2196                "Watermark cannot be NULL"
2197            )));
2198        }
2199        let order_type = watermark_serde.get_order_types().get(0).ok_or_else(|| {
2200            StreamExecutorError::from(anyhow!(
2201                "Watermark serde should have at least one order type"
2202            ))
2203        })?;
2204
2205        let direction = if order_type.is_ascending() {
2206            WatermarkDirection::Ascending
2207        } else {
2208            WatermarkDirection::Descending
2209        };
2210        let clean_watermark_index_in_pk = self
2211            .pk_indices
2212            .iter()
2213            .position(|&i| i == clean_watermark_index);
2214        let clean_watermark_index_in_value = match &self.value_indices {
2215            Some(value_indices) => value_indices
2216                .iter()
2217                .position(|idx| *idx == clean_watermark_index)
2218                .ok_or_else(|| {
2219                    StreamExecutorError::from(anyhow!(
2220                        "clean watermark column index {} is not included in table value indices {:?}",
2221                        clean_watermark_index,
2222                        value_indices
2223                    ))
2224                })?,
2225            None => clean_watermark_index,
2226        };
2227
2228        let stream = self
2229            .iter_with_prefix_inner::</* REVERSE */ false, Bytes>(pk_prefix, sub_range, prefetch_options)
2230            .await?
2231            .try_filter_map(move |(pk, row)| {
2232                let should_filter =  match watermark_type {
2233                    WatermarkSerdeType::PkPrefix => unreachable!(),
2234                    WatermarkSerdeType::NonPkPrefix => {
2235                        let table_key = TableKey(pk);
2236                        let (vnode, key) = table_key.split_vnode();
2237                        let pk_cols = self.pk_serde
2238                        .deserialize(key)
2239                        .unwrap_or_else(|e| {
2240                            panic!("Failed to deserialize table {} vnode {:?} key {:?} error: {:?}", self.table_id(), vnode, key, e.as_report());
2241                        });
2242                        direction.datum_filter_by_watermark(
2243                            pk_cols.datum_at(clean_watermark_index_in_pk.unwrap()),
2244                            &watermark_value,
2245                            *order_type,
2246                        )
2247                    },
2248                    WatermarkSerdeType::Value => {
2249                        direction.datum_filter_by_watermark(
2250                            row.datum_at(clean_watermark_index_in_value),
2251                            &watermark_value,
2252                            *order_type,
2253                        )
2254                    }
2255                };
2256                if should_filter {
2257                    ready(Ok(None))
2258                } else {
2259                    ready(Ok(Some(row)))
2260                }
2261            });
2262        Ok(stream.boxed())
2263    }
2264
2265    /// Get the row from a state table with only 1 row.
2266    pub async fn get_from_one_row_table(&self) -> StreamExecutorResult<Option<OwnedRow>> {
2267        let sub_range: &(Bound<OwnedRow>, Bound<OwnedRow>) = &(Unbounded, Unbounded);
2268        let stream = self
2269            .iter_with_prefix(row::empty(), sub_range, Default::default())
2270            .await?;
2271        pin_mut!(stream);
2272
2273        if let Some(res) = stream.next().await {
2274            let value = res?.into_owned_row();
2275            assert!(stream.next().await.is_none());
2276            Ok(Some(value))
2277        } else {
2278            Ok(None)
2279        }
2280    }
2281
2282    /// Get the row from a state table with only 1 row, and the row has only 1 col.
2283    ///
2284    /// `None` can mean either the row is never persisted, or is a persisted `NULL`,
2285    /// which does not matter in the use case.
2286    pub async fn get_from_one_value_table(&self) -> StreamExecutorResult<Option<ScalarImpl>> {
2287        Ok(self
2288            .get_from_one_row_table()
2289            .await?
2290            .and_then(|row| row[0].clone()))
2291    }
2292
2293    pub async fn iter_keyed_row_with_prefix(
2294        &self,
2295        pk_prefix: impl Row,
2296        sub_range: &(Bound<impl Row>, Bound<impl Row>),
2297        prefetch_options: PrefetchOptions,
2298    ) -> StreamExecutorResult<impl KeyedRowStream<'_>> {
2299        Ok(
2300            self.iter_with_prefix_inner::</* REVERSE */ false, Bytes>(pk_prefix, sub_range, prefetch_options)
2301                .await?.map_ok(|(key, row)| KeyedRow::new(TableKey(key), row)),
2302        )
2303    }
2304
2305    pub async fn rev_iter_keyed_row_with_prefix(
2306        &self,
2307        pk_prefix: impl Row,
2308        sub_range: &(Bound<impl Row>, Bound<impl Row>),
2309        prefetch_options: PrefetchOptions,
2310    ) -> StreamExecutorResult<impl KeyedRowStream<'_>> {
2311        Ok(
2312            self.iter_with_prefix_inner::</* REVERSE */ true, Bytes>(pk_prefix, sub_range, prefetch_options)
2313            .await?.map_ok(|(key, row)| KeyedRow::new(TableKey(key), row)),
2314        )
2315    }
2316
2317    /// This function scans the table just like `iter_with_prefix`, but in reverse order.
2318    pub async fn rev_iter_with_prefix(
2319        &self,
2320        pk_prefix: impl Row,
2321        sub_range: &(Bound<impl Row>, Bound<impl Row>),
2322        prefetch_options: PrefetchOptions,
2323    ) -> StreamExecutorResult<impl RowStream<'_>> {
2324        Ok(
2325            self.iter_with_prefix_inner::</* REVERSE */ true, ()>(pk_prefix, sub_range, prefetch_options)
2326                .await?.map_ok(|(_, row)| row),
2327        )
2328    }
2329
2330    async fn iter_with_prefix_inner<const REVERSE: bool, K: CopyFromSlice + FromVnodeBytes>(
2331        &self,
2332        pk_prefix: impl Row,
2333        sub_range: &(Bound<impl Row>, Bound<impl Row>),
2334        prefetch_options: PrefetchOptions,
2335    ) -> StreamExecutorResult<impl PkRowStream<'_, K>> {
2336        let prefix_serializer = self.pk_serde.prefix(pk_prefix.len());
2337        let encoded_prefix = serialize_pk(&pk_prefix, &prefix_serializer);
2338
2339        // We assume that all usages of iterating the state table only access a single vnode.
2340        // If this assertion fails, then something must be wrong with the operator implementation or
2341        // the distribution derivation from the optimizer.
2342        let vnode = self.compute_prefix_vnode(&pk_prefix);
2343
2344        // Construct prefix hint for prefix bloom filter.
2345        let pk_prefix_indices = &self.pk_indices[..pk_prefix.len()];
2346        if self.prefix_hint_len != 0 && !IS_REPLICATED {
2347            debug_assert_eq!(self.prefix_hint_len, pk_prefix.len());
2348        }
2349        let prefix_hint = {
2350            if should_calculate_prefix_hint(self.prefix_hint_len, pk_prefix.len(), true) {
2351                let encoded_prefix_len = self
2352                    .pk_serde
2353                    .deserialize_prefix_len(&encoded_prefix, self.prefix_hint_len)?;
2354
2355                Some(Bytes::copy_from_slice(
2356                    &encoded_prefix[..encoded_prefix_len],
2357                ))
2358            } else {
2359                None
2360            }
2361        };
2362
2363        trace!(
2364            table_id = %self.table_id(),
2365            ?prefix_hint, ?pk_prefix,
2366            ?pk_prefix_indices,
2367            iter_direction = if REVERSE { "reverse" } else { "forward" },
2368            "storage_iter_with_prefix"
2369        );
2370
2371        let memcomparable_range =
2372            prefix_and_sub_range_to_memcomparable(&self.pk_serde, sub_range, pk_prefix);
2373
2374        Ok(if REVERSE {
2375            futures::future::Either::Left(
2376                self.row_store
2377                    .rev_iter_kv(vnode, memcomparable_range, prefix_hint, prefetch_options)
2378                    .await?,
2379            )
2380        } else {
2381            futures::future::Either::Right(
2382                self.row_store
2383                    .iter_kv(vnode, memcomparable_range, prefix_hint, prefetch_options)
2384                    .await?,
2385            )
2386        })
2387    }
2388
2389    /// This function scans raw key-values from the relational table with specific `pk_range` under
2390    /// the same `vnode`.
2391    async fn iter_kv_with_pk_range<'a, K: CopyFromSlice + FromVnodeBytes>(
2392        &'a self,
2393        pk_range: &(Bound<impl Row>, Bound<impl Row>),
2394        // Optional vnode that returns an iterator only over the given range under that vnode.
2395        // For now, we require this parameter, and will panic. In the future, when `None`, we can
2396        // iterate over each vnode that the `StateTableInner` owns.
2397        vnode: VirtualNode,
2398        prefetch_options: PrefetchOptions,
2399    ) -> StreamExecutorResult<impl PkRowStream<'a, K>> {
2400        let memcomparable_range = prefix_range_to_memcomparable(&self.pk_serde, pk_range);
2401
2402        // TODO: provide a trace of useful params.
2403        self.row_store
2404            .iter_kv(vnode, memcomparable_range, None, prefetch_options)
2405            .await
2406    }
2407}
2408
2409fn deserialize_keyed_row_stream<'a, K: CopyFromSlice>(
2410    iter: impl StateStoreIter + 'a,
2411    deserializer: &'a impl ValueRowSerde,
2412) -> impl PkRowStream<'a, K> {
2413    iter.into_stream(move |(key, value)| {
2414        Ok((
2415            K::copy_from_slice(key.user_key.table_key.as_ref()),
2416            deserializer.deserialize(value).map(OwnedRow::new)?,
2417        ))
2418    })
2419    .map_err(Into::into)
2420}
2421
2422pub fn prefix_range_to_memcomparable(
2423    pk_serde: &OrderedRowSerde,
2424    range: &(Bound<impl Row>, Bound<impl Row>),
2425) -> (Bound<Bytes>, Bound<Bytes>) {
2426    (
2427        start_range_to_memcomparable(pk_serde, &range.0),
2428        end_range_to_memcomparable(pk_serde, &range.1, None),
2429    )
2430}
2431
2432fn prefix_and_sub_range_to_memcomparable(
2433    pk_serde: &OrderedRowSerde,
2434    sub_range: &(Bound<impl Row>, Bound<impl Row>),
2435    pk_prefix: impl Row,
2436) -> (Bound<Bytes>, Bound<Bytes>) {
2437    let (range_start, range_end) = sub_range;
2438    let prefix_serializer = pk_serde.prefix(pk_prefix.len());
2439    let serialized_pk_prefix = serialize_pk(&pk_prefix, &prefix_serializer);
2440    let start_range = match range_start {
2441        Included(start_range) => Bound::Included(Either::Left((&pk_prefix).chain(start_range))),
2442        Excluded(start_range) => Bound::Excluded(Either::Left((&pk_prefix).chain(start_range))),
2443        Unbounded => Bound::Included(Either::Right(&pk_prefix)),
2444    };
2445    let end_range = match range_end {
2446        Included(end_range) => Bound::Included((&pk_prefix).chain(end_range)),
2447        Excluded(end_range) => Bound::Excluded((&pk_prefix).chain(end_range)),
2448        Unbounded => Unbounded,
2449    };
2450    (
2451        start_range_to_memcomparable(pk_serde, &start_range),
2452        end_range_to_memcomparable(pk_serde, &end_range, Some(serialized_pk_prefix)),
2453    )
2454}
2455
2456fn start_range_to_memcomparable<R: Row>(
2457    pk_serde: &OrderedRowSerde,
2458    bound: &Bound<R>,
2459) -> Bound<Bytes> {
2460    let serialize_pk_prefix = |pk_prefix: &R| {
2461        let prefix_serializer = pk_serde.prefix(pk_prefix.len());
2462        serialize_pk(pk_prefix, &prefix_serializer)
2463    };
2464    match bound {
2465        Unbounded => Unbounded,
2466        Included(r) => {
2467            let serialized = serialize_pk_prefix(r);
2468
2469            Included(serialized)
2470        }
2471        Excluded(r) => {
2472            let serialized = serialize_pk_prefix(r);
2473
2474            start_bound_of_excluded_prefix(&serialized)
2475        }
2476    }
2477}
2478
2479fn end_range_to_memcomparable<R: Row>(
2480    pk_serde: &OrderedRowSerde,
2481    bound: &Bound<R>,
2482    serialized_pk_prefix: Option<Bytes>,
2483) -> Bound<Bytes> {
2484    let serialize_pk_prefix = |pk_prefix: &R| {
2485        let prefix_serializer = pk_serde.prefix(pk_prefix.len());
2486        serialize_pk(pk_prefix, &prefix_serializer)
2487    };
2488    match bound {
2489        Unbounded => match serialized_pk_prefix {
2490            Some(serialized_pk_prefix) => end_bound_of_prefix(&serialized_pk_prefix),
2491            None => Unbounded,
2492        },
2493        Included(r) => {
2494            let serialized = serialize_pk_prefix(r);
2495            // TODO: may use Included(serialized)?
2496            end_bound_of_prefix(&serialized)
2497        }
2498        Excluded(r) => {
2499            let serialized = serialize_pk_prefix(r);
2500            Excluded(serialized)
2501        }
2502    }
2503}
2504
2505fn fill_non_output_indices(
2506    i2o_mapping: &ColIndexMapping,
2507    data_types: &[DataType],
2508    chunk: StreamChunk,
2509) -> StreamChunk {
2510    let cardinality = chunk.cardinality();
2511    let (ops, columns, vis) = chunk.into_inner();
2512    let mut full_columns = Vec::with_capacity(data_types.len());
2513    for (i, data_type) in data_types.iter().enumerate() {
2514        if let Some(j) = i2o_mapping.try_map(i) {
2515            full_columns.push(columns[j].clone());
2516        } else {
2517            let mut column_builder = ArrayImplBuilder::with_type(cardinality, data_type.clone());
2518            column_builder.append_n_null(cardinality);
2519            let column: ArrayRef = column_builder.finish().into();
2520            full_columns.push(column)
2521        }
2522    }
2523    let data_chunk = DataChunk::new(full_columns, vis);
2524    StreamChunk::from_parts(ops, data_chunk)
2525}
2526
2527#[cfg(test)]
2528mod tests {
2529    use std::fmt::Debug;
2530
2531    use expect_test::{Expect, expect};
2532
2533    use super::*;
2534
2535    fn check(actual: impl Debug, expect: Expect) {
2536        let actual = format!("{:#?}", actual);
2537        expect.assert_eq(&actual);
2538    }
2539
2540    #[test]
2541    fn test_fill_non_output_indices() {
2542        let data_types = vec![DataType::Int32, DataType::Int32, DataType::Int32];
2543        let replicated_chunk = [OwnedRow::new(vec![
2544            Some(222_i32.into()),
2545            Some(2_i32.into()),
2546        ])];
2547        let replicated_chunk = StreamChunk::from_parts(
2548            vec![Op::Insert],
2549            DataChunk::from_rows(&replicated_chunk, &[DataType::Int32, DataType::Int32]),
2550        );
2551        let i2o_mapping = ColIndexMapping::new(vec![Some(1), None, Some(0)], 2);
2552        let filled_chunk = fill_non_output_indices(&i2o_mapping, &data_types, replicated_chunk);
2553        check(
2554            filled_chunk,
2555            expect![[r#"
2556            StreamChunk { cardinality: 1, capacity: 1, data:
2557            +---+---+---+-----+
2558            | + | 2 |   | 222 |
2559            +---+---+---+-----+
2560             }"#]],
2561        );
2562    }
2563}