Skip to main content

qail_core/ast/cmd/
merge.rs

1//! Builder methods for PostgreSQL MERGE.
2
3use crate::ast::{
4    Condition, Expr, Merge, MergeAction, MergeClause, MergeMatchKind, MergeSource, Operator, Qail,
5    Value,
6};
7
8impl Qail {
9    /// Set a target alias for `MERGE INTO`.
10    pub fn target_alias(mut self, alias: impl Into<String>) -> Self {
11        self.ensure_merge().target_alias = Some(alias.into());
12        self
13    }
14
15    /// Set a table source for `MERGE USING`.
16    pub fn using_table(mut self, table: impl Into<String>) -> Self {
17        self.ensure_merge().source = MergeSource::Table {
18            name: table.into(),
19            alias: None,
20        };
21        self
22    }
23
24    /// Set an aliased table source for `MERGE USING`.
25    pub fn using_table_as(mut self, table: impl Into<String>, alias: impl Into<String>) -> Self {
26        self.ensure_merge().source = MergeSource::Table {
27            name: table.into(),
28            alias: Some(alias.into()),
29        };
30        self
31    }
32
33    /// Set an aliased query source for `MERGE USING`.
34    pub fn using_query_as(mut self, query: Qail, alias: impl Into<String>) -> Self {
35        self.ensure_merge().source = MergeSource::Query {
36            query: Box::new(query),
37            alias: Some(alias.into()),
38        };
39        self
40    }
41
42    /// Add an `ON` condition comparing the target side to a source column.
43    pub fn merge_on_column(
44        mut self,
45        left: impl Into<String>,
46        op: Operator,
47        right: impl Into<String>,
48    ) -> Self {
49        self.ensure_merge().on.push(Condition {
50            left: Expr::Named(left.into()),
51            op,
52            value: Value::Column(right.into()),
53            is_array_unnest: false,
54        });
55        self
56    }
57
58    /// Add an arbitrary `ON` condition.
59    pub fn merge_on_condition(mut self, condition: Condition) -> Self {
60        self.ensure_merge().on.push(condition);
61        self
62    }
63
64    /// Add `WHEN MATCHED THEN UPDATE SET ...`.
65    pub fn when_matched_update<S>(mut self, assignments: &[(S, Expr)]) -> Self
66    where
67        S: AsRef<str>,
68    {
69        self.push_merge_clause(
70            MergeMatchKind::Matched,
71            Vec::new(),
72            MergeAction::Update {
73                assignments: assignments
74                    .iter()
75                    .map(|(col, expr)| (col.as_ref().to_string(), expr.clone()))
76                    .collect(),
77            },
78        );
79        self
80    }
81
82    /// Add `WHEN MATCHED AND ... THEN UPDATE SET ...`.
83    pub fn when_matched_update_if<S>(
84        mut self,
85        condition: Vec<Condition>,
86        assignments: &[(S, Expr)],
87    ) -> Self
88    where
89        S: AsRef<str>,
90    {
91        self.push_merge_clause(
92            MergeMatchKind::Matched,
93            condition,
94            MergeAction::Update {
95                assignments: assignments
96                    .iter()
97                    .map(|(col, expr)| (col.as_ref().to_string(), expr.clone()))
98                    .collect(),
99            },
100        );
101        self
102    }
103
104    /// Add `WHEN MATCHED THEN DELETE`.
105    pub fn when_matched_delete(mut self) -> Self {
106        self.push_merge_clause(MergeMatchKind::Matched, Vec::new(), MergeAction::Delete);
107        self
108    }
109
110    /// Add `WHEN MATCHED THEN DO NOTHING`.
111    pub fn when_matched_do_nothing(mut self) -> Self {
112        self.push_merge_clause(MergeMatchKind::Matched, Vec::new(), MergeAction::DoNothing);
113        self
114    }
115
116    /// Add `WHEN NOT MATCHED [BY TARGET] THEN INSERT (...) VALUES (...)`.
117    pub fn when_not_matched_insert<S>(mut self, columns: &[S], values: &[Expr]) -> Self
118    where
119        S: AsRef<str>,
120    {
121        self.push_merge_clause(
122            MergeMatchKind::NotMatchedByTarget,
123            Vec::new(),
124            MergeAction::Insert {
125                columns: columns.iter().map(|col| col.as_ref().to_string()).collect(),
126                values: values.to_vec(),
127            },
128        );
129        self
130    }
131
132    /// Add `WHEN NOT MATCHED [BY TARGET] AND ... THEN INSERT (...) VALUES (...)`.
133    pub fn when_not_matched_insert_if<S>(
134        mut self,
135        condition: Vec<Condition>,
136        columns: &[S],
137        values: &[Expr],
138    ) -> Self
139    where
140        S: AsRef<str>,
141    {
142        self.push_merge_clause(
143            MergeMatchKind::NotMatchedByTarget,
144            condition,
145            MergeAction::Insert {
146                columns: columns.iter().map(|col| col.as_ref().to_string()).collect(),
147                values: values.to_vec(),
148            },
149        );
150        self
151    }
152
153    /// Add `WHEN NOT MATCHED [BY TARGET] THEN DO NOTHING`.
154    pub fn when_not_matched_do_nothing(mut self) -> Self {
155        self.push_merge_clause(
156            MergeMatchKind::NotMatchedByTarget,
157            Vec::new(),
158            MergeAction::DoNothing,
159        );
160        self
161    }
162
163    /// Add `WHEN NOT MATCHED BY SOURCE THEN DELETE`.
164    pub fn when_not_matched_by_source_delete(mut self) -> Self {
165        self.push_merge_clause(
166            MergeMatchKind::NotMatchedBySource,
167            Vec::new(),
168            MergeAction::Delete,
169        );
170        self
171    }
172
173    /// Add `WHEN NOT MATCHED BY SOURCE THEN UPDATE SET ...`.
174    pub fn when_not_matched_by_source_update<S>(mut self, assignments: &[(S, Expr)]) -> Self
175    where
176        S: AsRef<str>,
177    {
178        self.push_merge_clause(
179            MergeMatchKind::NotMatchedBySource,
180            Vec::new(),
181            MergeAction::Update {
182                assignments: assignments
183                    .iter()
184                    .map(|(col, expr)| (col.as_ref().to_string(), expr.clone()))
185                    .collect(),
186            },
187        );
188        self
189    }
190
191    /// Add `WHEN NOT MATCHED BY SOURCE THEN DO NOTHING`.
192    pub fn when_not_matched_by_source_do_nothing(mut self) -> Self {
193        self.push_merge_clause(
194            MergeMatchKind::NotMatchedBySource,
195            Vec::new(),
196            MergeAction::DoNothing,
197        );
198        self
199    }
200
201    fn push_merge_clause(
202        &mut self,
203        match_kind: MergeMatchKind,
204        condition: Vec<Condition>,
205        action: MergeAction,
206    ) {
207        self.ensure_merge().clauses.push(MergeClause {
208            match_kind,
209            condition,
210            action,
211        });
212    }
213
214    fn ensure_merge(&mut self) -> &mut Merge {
215        self.merge.get_or_insert_with(|| Merge {
216            target_alias: None,
217            source: MergeSource::Table {
218                name: String::new(),
219                alias: None,
220            },
221            on: Vec::new(),
222            clauses: Vec::new(),
223        })
224    }
225}