dbx_core/sql/planner/logical/
custom.rs1use crate::error::{DbxError, DbxResult};
4use crate::sql::planner::types::*;
5use sqlparser::ast::Statement;
6
7use super::LogicalPlanner;
8
9impl LogicalPlanner {
10 pub(super) fn parse_custom_statement(&self, statement: &Statement) -> DbxResult<LogicalPlan> {
12 let sql = format!("{}", statement);
14 let sql_upper = sql.to_uppercase();
15
16 if sql_upper.contains("CREATE FUNCTION") {
18 return self.parse_create_function(&sql);
19 }
20
21 if sql_upper.contains("CREATE TRIGGER") {
23 return self.parse_create_trigger(&sql);
24 }
25
26 if sql_upper.contains("CREATE JOB") {
28 return self.parse_create_job(&sql);
29 }
30
31 Err(DbxError::SqlNotSupported {
32 feature: "Custom statement".to_string(),
33 hint: "Only CREATE FUNCTION, CREATE TRIGGER, and CREATE JOB are supported".to_string(),
34 })
35 }
36
37 fn parse_create_function(&self, sql: &str) -> DbxResult<LogicalPlan> {
39 let sql_upper = sql.to_uppercase();
43
44 let name = sql_upper
46 .split("FUNCTION")
47 .nth(1)
48 .and_then(|s| s.split_whitespace().next())
49 .map(ToString::to_string)
50 .ok_or_else(|| DbxError::SqlParse {
51 message: "Failed to parse function name".to_string(),
52 sql: sql.to_string(),
53 })?;
54
55 let params = if let (Some(open), Some(close)) = (sql.find('('), sql.find(')')) {
57 let param_str = &sql[open + 1..close];
58 param_str
59 .split(',')
60 .filter_map(|p| {
61 let parts: Vec<&str> = p.split_whitespace().collect();
62 if parts.len() >= 2 {
63 Some((parts[0].to_string(), parts[1].to_uppercase()))
64 } else {
65 None
66 }
67 })
68 .collect()
69 } else {
70 vec![]
71 };
72
73 let return_type = sql_upper
75 .split("RETURNS")
76 .nth(1)
77 .and_then(|s| s.split("LANGUAGE").next())
78 .map(|s| s.trim().to_string())
79 .unwrap_or_else(|| "VOID".to_string());
80
81 let language = sql_upper
83 .split("LANGUAGE")
84 .nth(1)
85 .and_then(|s| s.split("AS").next())
86 .map(|s| s.trim().to_lowercase())
87 .unwrap_or_else(|| "sql".to_string());
88
89 let body = sql
91 .split("AS")
92 .nth(1)
93 .map(|s| s.trim().trim_matches('\'').trim_matches('"').to_string())
94 .unwrap_or_default();
95
96 Ok(LogicalPlan::CreateFunction {
97 name,
98 params,
99 return_type,
100 language,
101 body,
102 })
103 }
104
105 fn parse_create_trigger(&self, sql: &str) -> DbxResult<LogicalPlan> {
107 let name = sql
111 .split("TRIGGER")
112 .nth(1)
113 .and_then(|s| s.split_whitespace().next())
114 .map(|s| s.trim().to_string())
115 .ok_or_else(|| DbxError::SqlParse {
116 message: "Failed to parse trigger name".to_string(),
117 sql: sql.to_string(),
118 })?;
119
120 let timing = if sql.to_uppercase().contains("BEFORE") {
122 TriggerTiming::Before
123 } else {
124 TriggerTiming::After
125 };
126
127 let event = if sql.to_uppercase().contains("INSERT") {
129 TriggerEventKind::Insert
130 } else if sql.to_uppercase().contains("UPDATE") {
131 TriggerEventKind::Update
132 } else {
133 TriggerEventKind::Delete
134 };
135
136 let table = sql
138 .split("ON")
139 .nth(1)
140 .and_then(|s| s.split_whitespace().next())
141 .map(|s| s.trim().to_string())
142 .unwrap_or_default();
143
144 let for_each = if sql.to_uppercase().contains("FOR EACH ROW") {
146 ForEachKind::Row
147 } else {
148 ForEachKind::Statement
149 };
150
151 let function = sql
153 .split("FUNCTION")
154 .nth(1)
155 .and_then(|s| s.split('(').next())
156 .map(|s| s.trim().to_string())
157 .unwrap_or_default();
158
159 Ok(LogicalPlan::CreateTrigger {
160 name,
161 timing,
162 event,
163 table,
164 for_each,
165 function,
166 })
167 }
168
169 fn parse_create_job(&self, sql: &str) -> DbxResult<LogicalPlan> {
171 let name = sql
175 .split("JOB")
176 .nth(1)
177 .and_then(|s| s.split_whitespace().next())
178 .map(|s| s.trim().to_string())
179 .ok_or_else(|| DbxError::SqlParse {
180 message: "Failed to parse job name".to_string(),
181 sql: sql.to_string(),
182 })?;
183
184 let schedule = sql
186 .split("SCHEDULE")
187 .nth(1)
188 .and_then(|s| s.split("EXECUTE").next())
189 .map(|s| s.trim().trim_matches('\'').trim_matches('"').to_string())
190 .unwrap_or_default();
191
192 let function = sql
194 .split("FUNCTION")
195 .nth(1)
196 .and_then(|s| s.split('(').next())
197 .map(|s| s.trim().to_string())
198 .unwrap_or_default();
199
200 Ok(LogicalPlan::CreateJob {
201 name,
202 schedule,
203 function,
204 })
205 }
206}