halo_space/
cte_query.rs

1//! CTEQueryBuilder: build a single CTE entry.
2
3use crate::args::Args;
4use crate::flavor::Flavor;
5use crate::injection::{Injection, InjectionMarker};
6use crate::macros::{IntoStrings, collect_into_strings};
7use crate::modifiers::{Arg, Builder};
8use crate::string_builder::StringBuilder;
9use std::cell::RefCell;
10use std::rc::Rc;
11
12const CTE_QUERY_MARKER_INIT: InjectionMarker = 0;
13const CTE_QUERY_MARKER_AFTER_TABLE: InjectionMarker = 1;
14const CTE_QUERY_MARKER_AFTER_AS: InjectionMarker = 2;
15
16#[derive(Default)]
17pub struct CTEQueryBuilder {
18    name: Option<String>,
19    cols: Vec<String>,
20    builder_var: Option<String>,
21    #[allow(clippy::type_complexity)]
22    builder: Option<Box<dyn Builder>>,
23    auto_add_to_table_list: bool,
24
25    args: Rc<RefCell<Args>>,
26    injection: Injection,
27    marker: InjectionMarker,
28}
29
30// Box<dyn Builder> cannot auto-derive Debug; keep Debug off to avoid failures.
31impl std::fmt::Debug for CTEQueryBuilder {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.debug_struct("CTEQueryBuilder")
34            .field("name", &self.name)
35            .field("cols", &self.cols)
36            .field("builder_var", &self.builder_var)
37            .field("auto_add_to_table_list", &self.auto_add_to_table_list)
38            .finish()
39    }
40}
41
42impl Clone for CTEQueryBuilder {
43    fn clone(&self) -> Self {
44        self.clone_builder()
45    }
46}
47
48impl CTEQueryBuilder {
49    pub fn new() -> Self {
50        Self {
51            name: None,
52            cols: Vec::new(),
53            builder_var: None,
54            builder: None,
55            auto_add_to_table_list: false,
56            args: Rc::new(RefCell::new(Args::default())),
57            injection: Injection::new(),
58            marker: CTE_QUERY_MARKER_INIT,
59        }
60    }
61
62    pub fn set_flavor(&mut self, flavor: Flavor) -> Flavor {
63        let mut a = self.args.borrow_mut();
64        let old = a.flavor;
65        a.flavor = flavor;
66        old
67    }
68
69    pub fn flavor(&self) -> Flavor {
70        self.args.borrow().flavor
71    }
72
73    pub fn clone_builder(&self) -> Self {
74        let cloned = Self {
75            name: self.name.clone(),
76            cols: self.cols.clone(),
77            builder_var: self.builder_var.clone(),
78            builder: self
79                .builder
80                .as_ref()
81                .map(|b| dyn_clone::clone_box(b.as_ref())),
82            auto_add_to_table_list: self.auto_add_to_table_list,
83            args: Rc::new(RefCell::new(self.args.borrow().clone())),
84            injection: self.injection.clone(),
85            marker: self.marker,
86        };
87
88        if let (Some(ph), Some(b)) = (&self.builder_var, &self.builder) {
89            cloned
90                .args
91                .borrow_mut()
92                .replace(ph, Arg::Builder(dyn_clone::clone_box(b.as_ref())));
93        }
94
95        cloned
96    }
97
98    fn var(&self, v: impl Into<Arg>) -> String {
99        self.args.borrow_mut().add(v)
100    }
101
102    pub fn table<T>(&mut self, name: impl Into<String>, cols: T) -> &mut Self
103    where
104        T: IntoStrings,
105    {
106        self.name = Some(name.into());
107        self.cols = collect_into_strings(cols);
108        self.marker = CTE_QUERY_MARKER_AFTER_TABLE;
109        self
110    }
111
112    pub fn as_(&mut self, builder: impl Builder + 'static) -> &mut Self {
113        let b: Box<dyn Builder> = Box::new(builder);
114        let ph = self.var(Arg::Builder(dyn_clone::clone_box(b.as_ref())));
115        self.builder = Some(b);
116        self.builder_var = Some(ph);
117        self.marker = CTE_QUERY_MARKER_AFTER_AS;
118        self
119    }
120
121    pub fn add_to_table_list(&mut self) -> &mut Self {
122        self.auto_add_to_table_list = true;
123        self
124    }
125
126    pub fn should_add_to_table_list(&self) -> bool {
127        self.auto_add_to_table_list
128    }
129
130    pub fn table_name(&self) -> Option<&str> {
131        self.name.as_deref()
132    }
133
134    pub fn sql(&mut self, sql: impl Into<String>) -> &mut Self {
135        self.injection.sql(self.marker, sql);
136        self
137    }
138}
139
140impl Builder for CTEQueryBuilder {
141    fn build_with_flavor(&self, flavor: Flavor, initial_arg: &[Arg]) -> (String, Vec<Arg>) {
142        let mut buf = StringBuilder::new();
143        write_injection(&mut buf, &self.injection, CTE_QUERY_MARKER_INIT);
144
145        if let Some(name) = &self.name {
146            buf.write_leading(name);
147            if !self.cols.is_empty() {
148                buf.write_str(" (");
149                buf.write_str(&self.cols.join(", "));
150                buf.write_str(")");
151            }
152            write_injection(&mut buf, &self.injection, CTE_QUERY_MARKER_AFTER_TABLE);
153        }
154
155        if let Some(ph) = &self.builder_var {
156            buf.write_leading("AS (");
157            buf.write_str(ph);
158            buf.write_str(")");
159            write_injection(&mut buf, &self.injection, CTE_QUERY_MARKER_AFTER_AS);
160        }
161
162        self.args
163            .borrow()
164            .compile_with_flavor(&buf.into_string(), flavor, initial_arg)
165    }
166
167    fn flavor(&self) -> Flavor {
168        self.flavor()
169    }
170}
171
172fn write_injection(buf: &mut StringBuilder, inj: &Injection, marker: InjectionMarker) {
173    let sqls = inj.at(marker);
174    if sqls.is_empty() {
175        return;
176    }
177    buf.write_leading("");
178    buf.write_str(&sqls.join(" "));
179}