Skip to main content

dbx_core/sql/planner/logical/
custom.rs

1//! Custom statement planning - CREATE FUNCTION, CREATE TRIGGER, CREATE JOB
2
3use crate::error::{DbxError, DbxResult};
4use crate::sql::planner::types::*;
5use sqlparser::ast::Statement;
6
7use super::LogicalPlanner;
8
9impl LogicalPlanner {
10    /// Custom statement 파싱 (CREATE FUNCTION, CREATE TRIGGER, CREATE JOB)
11    pub(super) fn parse_custom_statement(&self, statement: &Statement) -> DbxResult<LogicalPlan> {
12        // Statement를 문자열로 변환
13        let sql = format!("{}", statement);
14        let sql_upper = sql.to_uppercase();
15
16        // CREATE FUNCTION 파싱
17        if sql_upper.contains("CREATE FUNCTION") {
18            return self.parse_create_function(&sql);
19        }
20
21        // CREATE TRIGGER 파싱
22        if sql_upper.contains("CREATE TRIGGER") {
23            return self.parse_create_trigger(&sql);
24        }
25
26        // CREATE JOB 파싱
27        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    /// CREATE FUNCTION 파싱
38    fn parse_create_function(&self, sql: &str) -> DbxResult<LogicalPlan> {
39        // 간단한 문자열 파싱 (실제로는 더 정교한 파서 필요)
40        // 예: CREATE FUNCTION add(a INT, b INT) RETURNS INT LANGUAGE rust AS 'fn add(a: i32, b: i32) -> i32 { a + b }'
41
42        let sql_upper = sql.to_uppercase();
43
44        // 함수 이름 추출 - FUNCTION 다음의 첫 단어
45        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        // 파라미터 추출: FUNCTION name(a INT, b INT) 형식에서 괄호 내 파라미터 파싱
56        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        // 반환 타입 추출
74        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        // 언어 추출
82        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        // 함수 본문 추출
90        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    /// CREATE TRIGGER 파싱
106    fn parse_create_trigger(&self, sql: &str) -> DbxResult<LogicalPlan> {
107        // 예: CREATE TRIGGER audit_log AFTER INSERT ON users FOR EACH ROW EXECUTE FUNCTION log_insert()
108
109        // 트리거 이름 추출
110        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        // 타이밍 추출 (BEFORE/AFTER)
121        let timing = if sql.to_uppercase().contains("BEFORE") {
122            TriggerTiming::Before
123        } else {
124            TriggerTiming::After
125        };
126
127        // 이벤트 추출 (INSERT/UPDATE/DELETE)
128        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        // 테이블 이름 추출
137        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        // FOR EACH 추출
145        let for_each = if sql.to_uppercase().contains("FOR EACH ROW") {
146            ForEachKind::Row
147        } else {
148            ForEachKind::Statement
149        };
150
151        // 함수 이름 추출
152        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    /// CREATE JOB 파싱
170    fn parse_create_job(&self, sql: &str) -> DbxResult<LogicalPlan> {
171        // 예: CREATE JOB cleanup_old_data SCHEDULE '0 0 * * *' EXECUTE FUNCTION cleanup()
172
173        // 작업 이름 추출
174        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        // 스케줄 추출
185        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        // 함수 이름 추출
193        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}