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