use crate::error::{DbxError, DbxResult};
use crate::sql::planner::types::*;
use sqlparser::ast::Statement;
use super::LogicalPlanner;
impl LogicalPlanner {
pub(super) fn parse_custom_statement(&self, statement: &Statement) -> DbxResult<LogicalPlan> {
let sql = format!("{}", statement);
let sql_upper = sql.to_uppercase();
if sql_upper.contains("CREATE FUNCTION") {
return self.parse_create_function(&sql);
}
if sql_upper.contains("CREATE TRIGGER") {
return self.parse_create_trigger(&sql);
}
if sql_upper.contains("CREATE JOB") {
return self.parse_create_job(&sql);
}
Err(DbxError::SqlNotSupported {
feature: "Custom statement".to_string(),
hint: "Only CREATE FUNCTION, CREATE TRIGGER, and CREATE JOB are supported".to_string(),
})
}
fn parse_create_function(&self, sql: &str) -> DbxResult<LogicalPlan> {
let sql_upper = sql.to_uppercase();
let name = sql_upper
.split("FUNCTION")
.nth(1)
.and_then(|s| s.split_whitespace().next())
.map(ToString::to_string)
.ok_or_else(|| DbxError::SqlParse {
message: "Failed to parse function name".to_string(),
sql: sql.to_string(),
})?;
let params = if let (Some(open), Some(close)) = (sql.find('('), sql.find(')')) {
let param_str = &sql[open + 1..close];
param_str
.split(',')
.filter_map(|p| {
let parts: Vec<&str> = p.split_whitespace().collect();
if parts.len() >= 2 {
Some((parts[0].to_string(), parts[1].to_uppercase()))
} else {
None
}
})
.collect()
} else {
vec![]
};
let return_type = sql_upper
.split("RETURNS")
.nth(1)
.and_then(|s| s.split("LANGUAGE").next())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "VOID".to_string());
let language = sql_upper
.split("LANGUAGE")
.nth(1)
.and_then(|s| s.split("AS").next())
.map(|s| s.trim().to_lowercase())
.unwrap_or_else(|| "sql".to_string());
let body = sql
.split("AS")
.nth(1)
.map(|s| s.trim().trim_matches('\'').trim_matches('"').to_string())
.unwrap_or_default();
Ok(LogicalPlan::CreateFunction {
name,
params,
return_type,
language,
body,
})
}
fn parse_create_trigger(&self, sql: &str) -> DbxResult<LogicalPlan> {
let name = sql
.split("TRIGGER")
.nth(1)
.and_then(|s| s.split_whitespace().next())
.map(|s| s.trim().to_string())
.ok_or_else(|| DbxError::SqlParse {
message: "Failed to parse trigger name".to_string(),
sql: sql.to_string(),
})?;
let timing = if sql.to_uppercase().contains("BEFORE") {
TriggerTiming::Before
} else {
TriggerTiming::After
};
let event = if sql.to_uppercase().contains("INSERT") {
TriggerEventKind::Insert
} else if sql.to_uppercase().contains("UPDATE") {
TriggerEventKind::Update
} else {
TriggerEventKind::Delete
};
let table = sql
.split("ON")
.nth(1)
.and_then(|s| s.split_whitespace().next())
.map(|s| s.trim().to_string())
.unwrap_or_default();
let for_each = if sql.to_uppercase().contains("FOR EACH ROW") {
ForEachKind::Row
} else {
ForEachKind::Statement
};
let function = sql
.split("FUNCTION")
.nth(1)
.and_then(|s| s.split('(').next())
.map(|s| s.trim().to_string())
.unwrap_or_default();
Ok(LogicalPlan::CreateTrigger {
name,
timing,
event,
table,
for_each,
function,
})
}
fn parse_create_job(&self, sql: &str) -> DbxResult<LogicalPlan> {
let name = sql
.split("JOB")
.nth(1)
.and_then(|s| s.split_whitespace().next())
.map(|s| s.trim().to_string())
.ok_or_else(|| DbxError::SqlParse {
message: "Failed to parse job name".to_string(),
sql: sql.to_string(),
})?;
let schedule = sql
.split("SCHEDULE")
.nth(1)
.and_then(|s| s.split("EXECUTE").next())
.map(|s| s.trim().trim_matches('\'').trim_matches('"').to_string())
.unwrap_or_default();
let function = sql
.split("FUNCTION")
.nth(1)
.and_then(|s| s.split('(').next())
.map(|s| s.trim().to_string())
.unwrap_or_default();
Ok(LogicalPlan::CreateJob {
name,
schedule,
function,
})
}
}