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