rbatis_codegen/codegen/syntax_tree_pysql/
mod.rs

1/// this the py_sql syntax tree
2pub mod bind_node;
3pub mod break_node;
4pub mod choose_node;
5pub mod continue_node;
6pub mod error;
7pub mod foreach_node;
8pub mod if_node;
9pub mod otherwise_node;
10pub mod set_node;
11pub mod sql_node;
12pub mod string_node;
13pub mod trim_node;
14pub mod when_node;
15pub mod where_node;
16
17use crate::codegen::syntax_tree_pysql::bind_node::BindNode;
18use crate::codegen::syntax_tree_pysql::break_node::BreakNode;
19use crate::codegen::syntax_tree_pysql::choose_node::ChooseNode;
20use crate::codegen::syntax_tree_pysql::continue_node::ContinueNode;
21use crate::codegen::syntax_tree_pysql::foreach_node::ForEachNode;
22use crate::codegen::syntax_tree_pysql::if_node::IfNode;
23use crate::codegen::syntax_tree_pysql::otherwise_node::OtherwiseNode;
24use crate::codegen::syntax_tree_pysql::set_node::SetNode;
25use crate::codegen::syntax_tree_pysql::sql_node::SqlNode;
26use crate::codegen::syntax_tree_pysql::string_node::StringNode;
27use crate::codegen::syntax_tree_pysql::trim_node::TrimNode;
28use crate::codegen::syntax_tree_pysql::when_node::WhenNode;
29use crate::codegen::syntax_tree_pysql::where_node::WhereNode;
30
31/// PySQL Syntax Tree
32/// 
33/// The syntax of PySQL is based on Python-like indentation and line structure.
34/// Each node type below represents a different structure in the PySQL language.
35/// 
36/// Syntax Rules:
37/// 
38/// 1. Nodes that define a block end with a colon ':' and their children are indented.
39/// 
40/// 2. `NString` - Plain text or SQL fragments. Can preserve whitespace with backticks:
41/// ```sql
42/// SELECT * FROM users
43/// `  SELECT   column1,    column2   FROM table  `
44/// ```
45/// 
46/// 3. `NIf` - Conditional execution, similar to Python's if statement:
47/// ```pysql
48/// if condition:
49///   SQL fragment
50/// ```
51/// 
52/// 4. `NTrim` - Removes specified characters from start/end of the content:
53/// ```pysql
54/// trim ',':                   # Removes ',' from both start and end
55/// trim start=',',end=')':     # Removes ',' from start and ')' from end
56/// ```
57/// 
58/// 5. `NForEach` - Iterates over collections:
59/// ```pysql
60/// for item in items:          # Simple iteration
61///   #{item}
62/// for key,item in items:      # With key/index
63///   #{key}: #{item}
64/// ```
65/// 
66/// 6. `NChoose`/`NWhen`/`NOtherwise` - Switch-like structure:
67/// ```pysql
68/// choose:
69///   when condition1:
70///     SQL fragment 1
71///   when condition2:
72///     SQL fragment 2
73///   otherwise:                # Or use '_:'
74///     Default SQL fragment
75/// ```
76/// 
77/// 7. `NBind` - Variable binding:
78/// ```pysql
79/// bind name = 'value':        # Or use 'let name = value:'
80///   SQL using #{name}
81/// ```
82/// 
83/// 8. `NSet` - For UPDATE statements, handles comma separation.
84///    It can also define a collection to iterate over for generating SET clauses.
85/// ```pysql
86/// // Simple set for direct updates
87/// set:
88///   if name != null:
89///     name = #{name},
90///   if age != null:
91///     age = #{age}
92///
93/// // Set with collection to iterate (e.g., from a map or struct)
94/// // Assuming 'user_updates' is a map like {'name': 'new_name', 'status': 'active'}
95/// set collection="user_updates" skips="id,created_at" skip_null="true":
96///   // This will generate: name = #{user_updates.name}, status = #{user_updates.status}
97///   // 'id' and 'created_at' fields from 'user_updates' will be skipped.
98///   // If a value in 'user_updates' is null and skip_null is true, it will be skipped.
99/// ```
100/// 
101/// 9. `NWhere` - For WHERE clauses, handles AND/OR prefixes:
102/// ```pysql
103/// where:
104///   if id != null:
105///     AND id = #{id}
106///   if name != null:
107///     AND name = #{name}
108/// ```
109/// 
110/// 10. `NContinue`/`NBreak` - Loop control, must be inside a for loop:
111/// ```pysql
112/// for item in items:
113///   if item == null:
114///     break:
115///   if item == 0:
116///     continue:
117/// ```
118/// 
119/// 11. `NSql` - Reusable SQL fragments with an ID:
120/// ```pysql
121/// sql id='userColumns':
122///   id, name, age
123/// ```
124///     
125/// Note: All control nodes require a colon at the end, and their child content
126/// must be indented with more spaces than the parent node.
127#[derive(Clone, Debug, Eq, PartialEq)]
128pub enum NodeType {
129    NString(StringNode),
130    NIf(IfNode),
131    NTrim(TrimNode),
132    NForEach(ForEachNode),
133    NChoose(ChooseNode),
134    NOtherwise(OtherwiseNode),
135    NWhen(WhenNode),
136    NBind(BindNode),
137    NSet(SetNode),
138    NWhere(WhereNode),
139    NContinue(ContinueNode),
140    NBreak(BreakNode),
141    NSql(SqlNode),
142}
143
144/// the node name
145pub trait Name {
146    fn name() -> &'static str;
147}
148
149/// node default name
150pub trait DefaultName {
151    fn default_name() -> &'static str;
152}
153
154/// Convert syntax tree to HTML deconstruction
155pub trait AsHtml {
156    fn as_html(&self) -> String;
157}
158
159impl AsHtml for StringNode {
160    fn as_html(&self) -> String {
161        if self.value.starts_with("`") && self.value.ends_with("`") {
162            self.value.to_string()
163        } else {
164            let mut v = self.value.clone();
165            v.insert(0, '`');
166            v.push('`');
167            v
168        }
169    }
170}
171
172impl AsHtml for IfNode {
173    fn as_html(&self) -> String {
174        let mut childs = String::new();
175        for x in &self.childs {
176            childs.push_str(&x.as_html());
177        }
178        format!("<if test=\"{}\">{}</if>", self.test, childs)
179    }
180}
181
182impl AsHtml for TrimNode {
183    fn as_html(&self) -> String {
184        let mut childs = String::new();
185        for x in &self.childs {
186            childs.push_str(&x.as_html());
187        }
188        format!(
189            "<trim prefixOverrides=\"{}\" suffixOverrides=\"{}\">{}</trim>",
190            self.start, self.end, childs
191        )
192    }
193}
194
195impl AsHtml for ForEachNode {
196    fn as_html(&self) -> String {
197        let mut childs = String::new();
198        for x in &self.childs {
199            childs.push_str(&x.as_html());
200        }
201        format!(
202            "<foreach collection=\"{}\" index=\"{}\" item=\"{}\" >{}</foreach>",
203            self.collection, self.index, self.item, childs
204        )
205    }
206}
207
208impl AsHtml for ChooseNode {
209    fn as_html(&self) -> String {
210        let mut childs = String::new();
211        for x in &self.when_nodes {
212            childs.push_str(&x.as_html());
213        }
214        let mut other_html = String::new();
215        match &self.otherwise_node {
216            None => {}
217            Some(v) => {
218                other_html = v.as_html();
219            }
220        }
221        format!("<choose>{}{}</choose>", childs, other_html)
222    }
223}
224
225impl AsHtml for OtherwiseNode {
226    fn as_html(&self) -> String {
227        let mut childs = String::new();
228        for x in &self.childs {
229            childs.push_str(&x.as_html());
230        }
231        format!("<otherwise>{}</otherwise>", childs)
232    }
233}
234
235impl AsHtml for WhenNode {
236    fn as_html(&self) -> String {
237        let mut childs = String::new();
238        for x in &self.childs {
239            childs.push_str(&x.as_html());
240        }
241        format!("<when test=\"{}\">{}</when>", self.test, childs)
242    }
243}
244
245impl AsHtml for BindNode {
246    fn as_html(&self) -> String {
247        format!("<bind name=\"{}\" value=\"{}\"/>", self.name, self.value)
248    }
249}
250
251impl AsHtml for SetNode {
252    fn as_html(&self) -> String {
253        let mut childs_html = String::new();
254        for x in &self.childs {
255            childs_html.push_str(&x.as_html());
256        }
257
258        let mut attrs_string = String::new();
259        if !self.collection.is_empty() {
260            attrs_string.push_str(&format!(" collection=\"{}\"", self.collection));
261        }
262        if !self.skips.is_empty() {
263            attrs_string.push_str(&format!(" skips=\"{}\"", self.skips));
264        }
265        if self.skip_null {
266            attrs_string.push_str(" skip_null=\"true\"");
267        }
268
269        format!("<set{}>{}</set>", attrs_string, childs_html)
270    }
271}
272
273impl AsHtml for WhereNode {
274    fn as_html(&self) -> String {
275        let mut childs = String::new();
276        for x in &self.childs {
277            childs.push_str(&x.as_html());
278        }
279        format!("<where>{}</where>", childs)
280    }
281}
282
283impl AsHtml for NodeType {
284    fn as_html(&self) -> String {
285        match self {
286            NodeType::NString(n) => n.as_html(),
287            NodeType::NIf(n) => n.as_html(),
288            NodeType::NTrim(n) => n.as_html(),
289            NodeType::NForEach(n) => n.as_html(),
290            NodeType::NChoose(n) => n.as_html(),
291            NodeType::NOtherwise(n) => n.as_html(),
292            NodeType::NWhen(n) => n.as_html(),
293            NodeType::NBind(n) => n.as_html(),
294            NodeType::NSet(n) => n.as_html(),
295            NodeType::NWhere(n) => n.as_html(),
296            NodeType::NContinue(n) => n.as_html(),
297            NodeType::NBreak(n) => n.as_html(),
298            NodeType::NSql(n) => n.as_html(),
299        }
300    }
301}
302
303impl AsHtml for Vec<NodeType> {
304    fn as_html(&self) -> String {
305        let mut htmls = String::new();
306        for x in self {
307            htmls.push_str(&x.as_html());
308        }
309        htmls
310    }
311}
312
313pub fn to_html(args: &Vec<NodeType>, is_select: bool, fn_name: &str) -> String {
314    let htmls = args.as_html();
315    if is_select {
316        format!(
317            "<mapper><select id=\"{}\">{}</select></mapper>",
318            fn_name, htmls
319        )
320    } else {
321        format!(
322            "<mapper><update id=\"{}\">{}</update></mapper>",
323            fn_name, htmls
324        )
325    }
326}