pub struct ColumnDef {
pub name: String,
pub typ: String,
pub nullable: bool,
pub primary_key: bool,
pub unique: bool,
pub auto_increment: bool,
}
pub struct IndexDef {
#[allow(dead_code)]
pub name: String,
pub table_name: String,
pub field: String,
pub index_type: String,
}
pub struct TableDef {
pub name: String,
pub columns: Vec<ColumnDef>,
pub indices: Vec<IndexDef>,
pub is_time_series: bool,
}
pub fn parse_ddl(ddl: &str) -> Result<Vec<TableDef>, String> {
let mut table_defs = Vec::new();
let mut current_table: Option<TableDef> = None;
let mut indices = Vec::new();
let statements = ddl.split(';').map(|s| s.trim()).filter(|s| !s.is_empty());
for stmt in statements {
let stmt_lower = stmt.to_lowercase();
if stmt_lower.starts_with("create table")
|| stmt_lower.starts_with("create timeseries table")
{
let table = parse_create_table(stmt)?;
if let Some(existing_table) = current_table.take() {
table_defs.push(existing_table);
}
current_table = Some(table);
} else if stmt_lower.starts_with("create index") {
let index = parse_create_index(stmt)?;
indices.push(index);
}
}
if let Some(table) = current_table.take() {
table_defs.push(table);
}
for index in indices {
if let Some(table) = table_defs.iter_mut().find(|t| t.name == index.table_name) {
table.indices.push(index);
}
}
Ok(table_defs)
}
fn parse_create_table(stmt: &str) -> Result<TableDef, String> {
let stmt = stmt.to_lowercase();
let is_time_series = stmt.starts_with("create timeseries table");
let table_keyword_pos = stmt.find("table").ok_or("Invalid CREATE TABLE statement")?;
let table_name_start = if is_time_series {
table_keyword_pos + 6
} else {
table_keyword_pos + 6
};
let left_paren = stmt.find('(').ok_or("Invalid CREATE TABLE statement")?;
let table_name = stmt[table_name_start..left_paren].trim().to_string();
let columns_part =
&stmt[left_paren + 1..stmt.rfind(')').ok_or("Invalid CREATE TABLE statement")?];
let columns = parse_columns(columns_part)?;
Ok(TableDef {
name: table_name,
columns,
indices: Vec::new(),
is_time_series,
})
}
fn parse_columns(columns_part: &str) -> Result<Vec<ColumnDef>, String> {
let mut columns = Vec::new();
let column_defs = columns_part
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty());
for column_def in column_defs {
let mut parts = column_def.split_whitespace();
let name = parts.next().ok_or("Invalid column definition")?.to_string();
let mut is_unsigned = false;
let mut typ = String::new();
#[allow(clippy::while_let_on_iterator)]
while let Some(part) = parts.next() {
match part {
"unsigned" => {
is_unsigned = true;
}
_ => {
typ = part.to_string();
break;
}
}
}
if typ.is_empty() {
return Err("Invalid column definition: missing type".to_string());
}
let full_typ = if is_unsigned {
format!("unsigned {}", typ)
} else {
typ
};
let mut nullable = true;
let mut primary_key = false;
let mut unique = false;
let mut auto_increment = false;
#[allow(clippy::while_let_on_iterator)]
while let Some(part) = parts.next() {
match part {
"not" => {
let next = parts.next().unwrap_or("");
if next == "null" {
nullable = false;
}
}
"primary" => {
let next = parts.next().unwrap_or("");
if next == "key" {
primary_key = true;
}
}
"unique" => {
unique = true;
}
"autoincrement" | "auto_increment" => {
auto_increment = true;
}
_ => {}
}
}
columns.push(ColumnDef {
name,
typ: full_typ,
nullable,
primary_key,
unique,
auto_increment,
});
}
Ok(columns)
}
fn parse_create_index(stmt: &str) -> Result<IndexDef, String> {
let stmt = stmt.to_lowercase();
let index_name_start = stmt.find("index").ok_or("Invalid CREATE INDEX statement")? + 6;
let on_pos = stmt.find("on").ok_or("Invalid CREATE INDEX statement")?;
let index_name = stmt[index_name_start..on_pos].trim().to_string();
let table_name_start = on_pos + 3;
let using_pos = stmt.find("using").unwrap_or_else(|| {
stmt.find('(').unwrap_or(stmt.len())
});
let table_name = stmt[table_name_start..using_pos].trim().to_string();
let mut index_type = "btree".to_string();
let mut field_start = using_pos;
if stmt.contains("using") {
let index_type_start = using_pos + 6;
let left_paren = stmt.find('(').ok_or("Invalid CREATE INDEX statement")?;
index_type = stmt[index_type_start..left_paren].trim().to_string();
field_start = left_paren;
}
let left_paren = field_start;
let right_paren = stmt.rfind(')').ok_or("Invalid CREATE INDEX statement")?;
let field = stmt[left_paren + 1..right_paren].trim().to_string();
Ok(IndexDef {
name: index_name,
table_name,
field,
index_type,
})
}