Skip to main content

risingwave_stream/from_proto/
temporal_join.rs

1// Copyright 2023 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::sync::Arc;
16
17use risingwave_common::catalog::ColumnId;
18use risingwave_common::hash::{HashKey, HashKeyDispatcher};
19use risingwave_common::types::DataType;
20use risingwave_common::util::value_encoding::BasicSerde;
21use risingwave_common::util::value_encoding::column_aware_row_encoding::ColumnAwareSerde;
22use risingwave_expr::expr::{NonStrictExpression, build_non_strict_from_prost};
23use risingwave_pb::plan_common::{JoinType as JoinTypeProto, StorageTableDesc};
24use risingwave_storage::row_serde::value_serde::ValueRowSerde;
25
26use super::*;
27use crate::common::table::state_table::{
28    ReplicatedStateTable, StateTable, StateTableBuilder, StateTableOpConsistencyLevel,
29};
30use crate::executor::monitor::StreamingMetrics;
31use crate::executor::{
32    ActorContextRef, JoinType, NestedLoopTemporalJoinExecutor, TemporalJoinExecutor,
33};
34use crate::task::AtomicU64Ref;
35
36pub struct TemporalJoinExecutorBuilder;
37
38impl_stream_node_body!(TemporalJoin(TemporalJoinNode) => TemporalJoinExecutorBuilder);
39
40impl ExecutorBuilder for TemporalJoinExecutorBuilder {
41    type Node = TemporalJoinNode;
42
43    async fn new_boxed_executor(
44        params: ExecutorParams,
45        node: &Self::Node,
46        store: impl StateStore,
47    ) -> StreamResult<Executor> {
48        let table_desc: &StorageTableDesc = node.get_table_desc()?;
49        let condition = match node.get_condition() {
50            Ok(cond_prost) => Some(build_non_strict_from_prost(
51                cond_prost,
52                params.eval_error_report,
53            )?),
54            Err(_) => None,
55        };
56
57        let table_output_indices = node
58            .get_table_output_indices()
59            .iter()
60            .map(|&x| x as usize)
61            .collect_vec();
62
63        let output_indices = node
64            .get_output_indices()
65            .iter()
66            .map(|&x| x as usize)
67            .collect_vec();
68        let [source_l, source_r]: [_; 2] = params.input.try_into().unwrap();
69
70        let versioned = table_desc.versioned;
71        // Use table_output_indices to select only the column IDs that the right-side
72        // upstream actually delivers. The planner may prune unused columns from the
73        // right table scan, so the upstream chunks may have fewer columns than the
74        // full table.
75        let output_column_ids = table_output_indices
76            .iter()
77            .map(|&x| ColumnId::new(table_desc.columns[x].column_id))
78            .collect_vec();
79        let vnodes = params.vnode_bitmap.clone().map(Arc::new);
80
81        if node.get_is_nested_loop() {
82            macro_rules! build_nested_loop {
83                ($SD:ident) => {{
84                    let right_table =
85                        StateTableBuilder::<_, $SD, true, _>::new_from_storage_table_desc(
86                            table_desc,
87                            store.clone(),
88                            vnodes.clone(),
89                            params.fragment_id,
90                        )
91                        .with_op_consistency_level(StateTableOpConsistencyLevel::Inconsistent)
92                        .with_output_column_ids(output_column_ids.clone())
93                        .forbid_preload_all_rows()
94                        .build()
95                        .await;
96
97                    let dispatcher_args = NestedLoopTemporalJoinExecutorDispatcherArgs {
98                        ctx: params.actor_context,
99                        info: params.info.clone(),
100                        left: source_l,
101                        right: source_r,
102                        right_table,
103                        condition,
104                        output_indices,
105                        chunk_size: params.config.developer.chunk_size,
106                        metrics: params.executor_stats,
107                        join_type_proto: node.get_join_type()?,
108                    };
109                    Ok((params.info, dispatcher_args.dispatch()?).into())
110                }};
111            }
112            if versioned {
113                build_nested_loop!(ColumnAwareSerde)
114            } else {
115                build_nested_loop!(BasicSerde)
116            }
117        } else {
118            // `ReplicatedStateTable::iter_with_prefix` returns rows in `table_output_indices`
119            // order. The hash temporal join cache therefore needs stream-key positions within
120            // that projected row, not within the full table schema.
121            let table_stream_key_indices = table_desc
122                .stream_key
123                .iter()
124                .map(|&k| {
125                    table_output_indices
126                        .iter()
127                        .position(|&idx| idx == k as usize)
128                        .expect("stream key should be included in table output")
129                })
130                .collect_vec();
131
132            let left_join_keys = node
133                .get_left_key()
134                .iter()
135                .map(|key| *key as usize)
136                .collect_vec();
137
138            let right_join_keys = node
139                .get_right_key()
140                .iter()
141                .map(|key| *key as usize)
142                .collect_vec();
143
144            let null_safe = node.get_null_safe().clone();
145
146            let join_key_data_types = left_join_keys
147                .iter()
148                .map(|idx| source_l.schema().fields[*idx].data_type())
149                .collect_vec();
150
151            let memo_table = node.get_memo_table();
152            let memo_table = match memo_table {
153                Ok(memo_table) => {
154                    let vnodes = Arc::new(
155                        params
156                            .vnode_bitmap
157                            .expect("vnodes not set for temporal join"),
158                    );
159                    Some(
160                        StateTableBuilder::new(memo_table, store.clone(), Some(vnodes.clone()))
161                            .enable_preload_all_rows_by_config(&params.config)
162                            .build()
163                            .await,
164                    )
165                }
166                Err(_) => None,
167            };
168            let append_only = memo_table.is_none();
169
170            macro_rules! build_hash {
171                ($SD:ident) => {{
172                    let right_table =
173                        StateTableBuilder::<_, $SD, true, _>::new_from_storage_table_desc(
174                            table_desc,
175                            store.clone(),
176                            vnodes.clone(),
177                            params.fragment_id,
178                        )
179                        .with_op_consistency_level(StateTableOpConsistencyLevel::Inconsistent)
180                        .with_output_column_ids(output_column_ids.clone())
181                        .forbid_preload_all_rows()
182                        .build()
183                        .await;
184
185                    let dispatcher_args = TemporalJoinExecutorDispatcherArgs::<_, $SD> {
186                        ctx: params.actor_context,
187                        info: params.info.clone(),
188                        left: source_l,
189                        right: source_r,
190                        right_table,
191                        left_join_keys,
192                        right_join_keys,
193                        null_safe,
194                        condition,
195                        output_indices,
196                        table_stream_key_indices,
197                        watermark_epoch: params.watermark_epoch,
198                        chunk_size: params.config.developer.chunk_size,
199                        metrics: params.executor_stats,
200                        join_type_proto: node.get_join_type()?,
201                        join_key_data_types,
202                        memo_table,
203                        append_only,
204                    };
205
206                    Ok((params.info, dispatcher_args.dispatch()?).into())
207                }};
208            }
209            if versioned {
210                build_hash!(ColumnAwareSerde)
211            } else {
212                build_hash!(BasicSerde)
213            }
214        }
215    }
216}
217
218struct TemporalJoinExecutorDispatcherArgs<S: StateStore, SD: ValueRowSerde> {
219    ctx: ActorContextRef,
220    info: ExecutorInfo,
221    left: Executor,
222    right: Executor,
223    right_table: ReplicatedStateTable<S, SD>,
224    left_join_keys: Vec<usize>,
225    right_join_keys: Vec<usize>,
226    null_safe: Vec<bool>,
227    condition: Option<NonStrictExpression>,
228    output_indices: Vec<usize>,
229    table_stream_key_indices: Vec<usize>,
230    watermark_epoch: AtomicU64Ref,
231    chunk_size: usize,
232    metrics: Arc<StreamingMetrics>,
233    join_type_proto: JoinTypeProto,
234    join_key_data_types: Vec<DataType>,
235    memo_table: Option<StateTable<S>>,
236    append_only: bool,
237}
238
239impl<S: StateStore, SD: ValueRowSerde> HashKeyDispatcher
240    for TemporalJoinExecutorDispatcherArgs<S, SD>
241{
242    type Output = StreamResult<Box<dyn Execute>>;
243
244    fn dispatch_impl<K: HashKey>(self) -> Self::Output {
245        /// This macro helps to fill the const generic type parameter.
246        macro_rules! build {
247            ($join_type:ident, $append_only:ident) => {
248                Ok(Box::new(TemporalJoinExecutor::<
249                    K,
250                    S,
251                    SD,
252                    { JoinType::$join_type },
253                    { $append_only },
254                >::new(
255                    self.ctx,
256                    self.info,
257                    self.left,
258                    self.right,
259                    self.right_table,
260                    self.left_join_keys,
261                    self.right_join_keys,
262                    self.null_safe,
263                    self.condition,
264                    self.output_indices,
265                    self.table_stream_key_indices,
266                    self.watermark_epoch,
267                    self.metrics,
268                    self.chunk_size,
269                    self.join_key_data_types,
270                    self.memo_table,
271                )))
272            };
273        }
274        match self.join_type_proto {
275            JoinTypeProto::Inner => {
276                if self.append_only {
277                    build!(Inner, true)
278                } else {
279                    build!(Inner, false)
280                }
281            }
282            JoinTypeProto::LeftOuter => {
283                if self.append_only {
284                    build!(LeftOuter, true)
285                } else {
286                    build!(LeftOuter, false)
287                }
288            }
289            _ => unreachable!(),
290        }
291    }
292
293    fn data_types(&self) -> &[DataType] {
294        &self.join_key_data_types
295    }
296}
297
298struct NestedLoopTemporalJoinExecutorDispatcherArgs<S: StateStore, SD: ValueRowSerde> {
299    ctx: ActorContextRef,
300    info: ExecutorInfo,
301    left: Executor,
302    right: Executor,
303    right_table: ReplicatedStateTable<S, SD>,
304    condition: Option<NonStrictExpression>,
305    output_indices: Vec<usize>,
306    chunk_size: usize,
307    metrics: Arc<StreamingMetrics>,
308    join_type_proto: JoinTypeProto,
309}
310
311impl<S: StateStore, SD: ValueRowSerde> NestedLoopTemporalJoinExecutorDispatcherArgs<S, SD> {
312    fn dispatch(self) -> StreamResult<Box<dyn Execute>> {
313        /// This macro helps to fill the const generic type parameter.
314        macro_rules! build {
315            ($join_type:ident) => {
316                Ok(Box::new(NestedLoopTemporalJoinExecutor::<
317                    S,
318                    SD,
319                    { JoinType::$join_type },
320                >::new(
321                    self.ctx,
322                    self.info,
323                    self.left,
324                    self.right,
325                    self.right_table,
326                    self.condition,
327                    self.output_indices,
328                    self.metrics,
329                    self.chunk_size,
330                )))
331            };
332        }
333        match self.join_type_proto {
334            JoinTypeProto::Inner => {
335                build!(Inner)
336            }
337            JoinTypeProto::LeftOuter => {
338                build!(LeftOuter)
339            }
340            _ => unreachable!(),
341        }
342    }
343}