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