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