1use core::fmt::Formatter;
16use std::cell::{Cell, RefCell, RefMut};
17use std::collections::HashMap;
18use std::marker::PhantomData;
19use std::rc::{Rc, Weak};
20use std::sync::Arc;
21
22use risingwave_sqlparser::ast::{ExplainFormat, ExplainOptions, ExplainType};
23
24use super::property::WatermarkGroupId;
25use crate::expr::{CorrelatedId, SessionTimezone};
26use crate::handler::HandlerArgs;
27use crate::optimizer::plan_node::generic::Share;
28use crate::optimizer::plan_node::{LogicalPlanRef, PlanNodeId, StreamPlanRef};
29use crate::session::SessionImpl;
30use crate::utils::{OverwriteOptions, WithOptions};
31use crate::{Explain, TableCatalog};
32
33const RESERVED_ID_NUM: u16 = 10000;
34
35type PhantomUnsend = PhantomData<Rc<()>>;
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
42pub struct ShareId(u32);
43
44#[derive(Debug)]
45pub(in crate::optimizer) struct ShareEntry<P> {
46 current: RefCell<P>,
47 plan_node_id: PlanNodeId,
48}
49
50impl<P> ShareEntry<P> {
51 fn update_current(&self, current: P) {
52 *self.current.borrow_mut() = current;
53 }
54
55 pub(in crate::optimizer) fn plan_node_id(&self) -> PlanNodeId {
56 self.plan_node_id
57 }
58}
59
60impl<P: Clone> ShareEntry<P> {
61 fn current(&self) -> P {
62 self.current.borrow().clone()
63 }
64}
65
66pub(in crate::optimizer) type ShareEntryRef<P> = Rc<ShareEntry<P>>;
67
68struct ShareTable<P> {
69 entries: HashMap<ShareId, Weak<ShareEntry<P>>>,
72}
73
74impl<P> Default for ShareTable<P> {
75 fn default() -> Self {
76 Self {
77 entries: HashMap::new(),
78 }
79 }
80}
81
82pub struct OptimizerContext {
83 session_ctx: Arc<SessionImpl>,
84 sql: Arc<str>,
86 normalized_sql: String,
88 explain_options: ExplainOptions,
90 optimizer_trace: RefCell<Vec<String>>,
92 logical_explain: RefCell<Option<String>>,
94 with_options: WithOptions,
96 session_timezone: RefCell<SessionTimezone>,
98 total_rule_applied: RefCell<usize>,
100 overwrite_options: OverwriteOptions,
103 iceberg_snapshot_id_map: RefCell<HashMap<String, Option<i64>>>,
106 batch_mview_candidates: RefCell<Vec<MaterializedViewCandidate>>,
108
109 last_plan_node_id: Cell<i32>,
111 last_share_id: Cell<u32>,
113 last_correlated_id: Cell<u32>,
115 last_expr_display_id: Cell<usize>,
117 last_watermark_group_id: Cell<u32>,
119
120 logical_share_table: RefCell<ShareTable<LogicalPlanRef>>,
122 stream_share_table: RefCell<ShareTable<StreamPlanRef>>,
123
124 _phantom: PhantomUnsend,
125}
126
127#[derive(Clone, Debug)]
128pub struct MaterializedViewCandidate {
129 pub plan: LogicalPlanRef,
130 pub table: Arc<TableCatalog>,
131}
132
133pub(in crate::optimizer) struct LastAssignedIds {
134 last_plan_node_id: i32,
135 last_correlated_id: u32,
136 last_expr_display_id: usize,
137 last_watermark_group_id: u32,
138}
139
140pub type OptimizerContextRef = Rc<OptimizerContext>;
141
142impl OptimizerContext {
143 pub fn from_handler_args(handler_args: HandlerArgs) -> Self {
146 Self::new(handler_args, ExplainOptions::default())
147 }
148
149 pub fn new(mut handler_args: HandlerArgs, explain_options: ExplainOptions) -> Self {
151 let session_timezone = RefCell::new(SessionTimezone::new(
152 handler_args.session.config().timezone(),
153 ));
154 let overwrite_options = OverwriteOptions::new(&mut handler_args);
155 Self {
156 session_ctx: handler_args.session,
157 sql: handler_args.sql,
158 normalized_sql: handler_args.normalized_sql,
159 explain_options,
160 optimizer_trace: RefCell::new(vec![]),
161 logical_explain: RefCell::new(None),
162 with_options: handler_args.with_options,
163 session_timezone,
164 total_rule_applied: RefCell::new(0),
165 overwrite_options,
166 iceberg_snapshot_id_map: RefCell::new(HashMap::new()),
167 batch_mview_candidates: RefCell::new(Vec::new()),
168
169 last_plan_node_id: Cell::new(RESERVED_ID_NUM.into()),
170 last_share_id: Cell::new(0),
171 last_correlated_id: Cell::new(0),
172 last_expr_display_id: Cell::new(RESERVED_ID_NUM.into()),
173 last_watermark_group_id: Cell::new(RESERVED_ID_NUM.into()),
174
175 logical_share_table: RefCell::new(ShareTable::default()),
176 stream_share_table: RefCell::new(ShareTable::default()),
177
178 _phantom: Default::default(),
179 }
180 }
181
182 #[cfg(test)]
183 pub fn mock() -> OptimizerContextRef {
184 Self {
185 session_ctx: Arc::new(SessionImpl::mock()),
186 sql: Arc::from(""),
187 normalized_sql: "".to_owned(),
188 explain_options: ExplainOptions::default(),
189 optimizer_trace: RefCell::new(vec![]),
190 logical_explain: RefCell::new(None),
191 with_options: Default::default(),
192 session_timezone: RefCell::new(SessionTimezone::new("UTC".into())),
193 total_rule_applied: RefCell::new(0),
194 overwrite_options: OverwriteOptions::default(),
195 iceberg_snapshot_id_map: RefCell::new(HashMap::new()),
196 batch_mview_candidates: RefCell::new(Vec::new()),
197
198 last_plan_node_id: Cell::new(0),
199 last_share_id: Cell::new(0),
200 last_correlated_id: Cell::new(0),
201 last_expr_display_id: Cell::new(0),
202 last_watermark_group_id: Cell::new(0),
203
204 logical_share_table: RefCell::new(ShareTable::default()),
205 stream_share_table: RefCell::new(ShareTable::default()),
206
207 _phantom: Default::default(),
208 }
209 .into()
210 }
211
212 pub fn next_plan_node_id(&self) -> PlanNodeId {
213 self.last_plan_node_id.update(|id| id + 1);
214 PlanNodeId(self.last_plan_node_id.get())
215 }
216
217 fn next_share_id(&self) -> ShareId {
218 self.last_share_id.update(|id| id + 1);
219 ShareId(self.last_share_id.get())
220 }
221
222 fn share_entry<P>(
223 table: &RefCell<ShareTable<P>>,
224 share_id: ShareId,
225 convention: &str,
226 ) -> ShareEntryRef<P> {
227 table
228 .borrow()
229 .entries
230 .get(&share_id)
231 .unwrap_or_else(|| panic!("{convention} share {share_id:?} is not registered"))
232 .upgrade()
233 .unwrap_or_else(|| panic!("{convention} share {share_id:?} is no longer live"))
234 }
235
236 fn register_share<P: Clone>(&self, table: &RefCell<ShareTable<P>>, input: P) -> Share<P> {
237 let share_id = self.next_share_id();
238 let entry = Rc::new(ShareEntry {
239 current: RefCell::new(input.clone()),
240 plan_node_id: self.next_plan_node_id(),
241 });
242 table
243 .borrow_mut()
244 .entries
245 .try_insert(share_id, Rc::downgrade(&entry))
246 .expect("share id must be unique");
247 Share::new(share_id, input, entry)
248 }
249
250 fn share<P: Clone>(
251 &self,
252 table: &RefCell<ShareTable<P>>,
253 share_id: ShareId,
254 convention: &str,
255 ) -> Share<P> {
256 let entry = Self::share_entry(table, share_id, convention);
257 let input = entry.current();
258 Share::new(share_id, input, entry)
259 }
260
261 fn update_share<P>(
262 table: &RefCell<ShareTable<P>>,
263 share_id: ShareId,
264 new_input: P,
265 convention: &str,
266 ) {
267 let entry = Self::share_entry(table, share_id, convention);
268 entry.update_current(new_input);
269 }
270
271 pub(in crate::optimizer) fn register_logical_share(
272 &self,
273 input: LogicalPlanRef,
274 ) -> Share<LogicalPlanRef> {
275 self.register_share(&self.logical_share_table, input)
276 }
277
278 pub(in crate::optimizer) fn logical_share(&self, share_id: ShareId) -> Share<LogicalPlanRef> {
279 self.share(&self.logical_share_table, share_id, "logical")
280 }
281
282 pub(in crate::optimizer) fn update_logical_share(
283 &self,
284 share_id: ShareId,
285 new_input: LogicalPlanRef,
286 ) {
287 Self::update_share(&self.logical_share_table, share_id, new_input, "logical");
288 }
289
290 pub(in crate::optimizer) fn register_stream_share(
291 &self,
292 input: StreamPlanRef,
293 ) -> Share<StreamPlanRef> {
294 self.register_share(&self.stream_share_table, input)
295 }
296
297 pub(in crate::optimizer) fn update_stream_share(
298 &self,
299 share_id: ShareId,
300 new_input: StreamPlanRef,
301 ) {
302 Self::update_share(&self.stream_share_table, share_id, new_input, "stream");
303 }
304
305 pub fn next_correlated_id(&self) -> CorrelatedId {
306 self.last_correlated_id.update(|id| id + 1);
307 self.last_correlated_id.get()
308 }
309
310 pub fn next_expr_display_id(&self) -> usize {
311 self.last_expr_display_id.update(|id| id + 1);
312 self.last_expr_display_id.get()
313 }
314
315 pub fn next_watermark_group_id(&self) -> WatermarkGroupId {
316 self.last_watermark_group_id.update(|id| id + 1);
317 self.last_watermark_group_id.get()
318 }
319
320 pub(in crate::optimizer) fn backup_elem_ids(&self) -> LastAssignedIds {
321 LastAssignedIds {
322 last_plan_node_id: self.last_plan_node_id.get(),
323 last_correlated_id: self.last_correlated_id.get(),
324 last_expr_display_id: self.last_expr_display_id.get(),
325 last_watermark_group_id: self.last_watermark_group_id.get(),
326 }
327 }
328
329 pub(in crate::optimizer) fn reset_elem_ids(&self) {
331 self.last_plan_node_id.set(0);
332 self.last_correlated_id.set(0);
333 self.last_expr_display_id.set(0);
334 self.last_watermark_group_id.set(0);
335 }
336
337 pub(in crate::optimizer) fn restore_elem_ids(&self, backup: LastAssignedIds) {
338 self.last_plan_node_id.set(backup.last_plan_node_id);
339 self.last_correlated_id.set(backup.last_correlated_id);
340 self.last_expr_display_id.set(backup.last_expr_display_id);
341 self.last_watermark_group_id
342 .set(backup.last_watermark_group_id);
343 }
344
345 pub fn add_rule_applied(&self, num: usize) {
346 *self.total_rule_applied.borrow_mut() += num;
347 }
348
349 pub fn total_rule_applied(&self) -> usize {
350 *self.total_rule_applied.borrow()
351 }
352
353 pub fn is_explain_verbose(&self) -> bool {
354 self.explain_options.verbose
355 }
356
357 pub fn is_explain_trace(&self) -> bool {
358 self.explain_options.trace
359 }
360
361 fn is_explain_logical(&self) -> bool {
362 self.explain_options.explain_type == ExplainType::Logical
363 }
364
365 pub fn trace(&self, str: impl Into<String>) {
366 if self.is_explain_logical() && self.logical_explain.borrow().is_some() {
368 return;
369 }
370 let mut optimizer_trace = self.optimizer_trace.borrow_mut();
371 let string = str.into();
372 tracing::info!(target: "explain_trace", "\n{}", string);
373 optimizer_trace.push(string);
374 optimizer_trace.push("\n".to_owned());
375 }
376
377 pub fn warn_to_user(&self, str: impl Into<String>) {
378 self.session_ctx().notice_to_user(str);
379 }
380
381 fn explain_plan_impl(&self, plan: &impl Explain) -> String {
382 match self.explain_options.explain_format {
383 ExplainFormat::Text => plan.explain_to_string(),
384 ExplainFormat::Json => plan.explain_to_json(),
385 ExplainFormat::Xml => plan.explain_to_xml(),
386 ExplainFormat::Yaml => plan.explain_to_yaml(),
387 ExplainFormat::Dot => plan.explain_to_dot(),
388 }
389 }
390
391 pub fn may_store_explain_logical(&self, plan: &LogicalPlanRef) {
392 if self.is_explain_logical() {
393 let str = self.explain_plan_impl(plan);
394 *self.logical_explain.borrow_mut() = Some(str);
395 }
396 }
397
398 pub fn take_logical(&self) -> Option<String> {
399 self.logical_explain.borrow_mut().take()
400 }
401
402 pub fn take_trace(&self) -> Vec<String> {
403 self.optimizer_trace.borrow_mut().drain(..).collect()
404 }
405
406 pub fn with_options(&self) -> &WithOptions {
407 &self.with_options
408 }
409
410 pub fn overwrite_options(&self) -> &OverwriteOptions {
411 &self.overwrite_options
412 }
413
414 pub fn add_batch_mview_candidate(&self, table: Arc<TableCatalog>, plan: LogicalPlanRef) {
415 self.batch_mview_candidates
416 .borrow_mut()
417 .push(MaterializedViewCandidate { plan, table });
418 }
419
420 pub fn batch_mview_candidates(&self) -> std::cell::Ref<'_, Vec<MaterializedViewCandidate>> {
421 self.batch_mview_candidates.borrow()
422 }
423
424 pub fn session_ctx(&self) -> &Arc<SessionImpl> {
425 &self.session_ctx
426 }
427
428 pub fn batch_plan_dml_wait_persistence(&self) -> bool {
429 let session_config = self.session_ctx.config();
430 !session_config.implicit_flush() && session_config.dml_wait_persistence()
431 }
432
433 pub fn sql(&self) -> &str {
435 &self.sql
436 }
437
438 pub fn normalized_sql(&self) -> &str {
440 &self.normalized_sql
441 }
442
443 pub fn session_timezone(&self) -> RefMut<'_, SessionTimezone> {
444 self.session_timezone.borrow_mut()
445 }
446
447 pub fn get_session_timezone(&self) -> String {
448 self.session_timezone.borrow().timezone()
449 }
450
451 pub fn iceberg_snapshot_id_map(&self) -> RefMut<'_, HashMap<String, Option<i64>>> {
452 self.iceberg_snapshot_id_map.borrow_mut()
453 }
454}
455
456impl std::fmt::Debug for OptimizerContext {
457 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
458 write!(
459 f,
460 "QueryContext {{ sql = {}, explain_options = {}, with_options = {:?}, last_plan_node_id = {}, last_correlated_id = {} }}",
461 self.sql,
462 self.explain_options,
463 self.with_options,
464 self.last_plan_node_id.get(),
465 self.last_correlated_id.get(),
466 )
467 }
468}