use super::helpers::extract_name_after_if_exists;
use crate::ddl_ast::statement::{CollectionStmt, NodedbStatement};
use crate::error::SqlError;
pub(super) fn try_parse(
upper: &str,
parts: &[&str],
trimmed: &str,
) -> Option<Result<NodedbStatement, SqlError>> {
(|| -> Result<Option<NodedbStatement>, SqlError> {
if upper.starts_with("CREATE UNIQUE INDEX ") || upper.starts_with("CREATE UNIQUE IND") {
return Ok(Some(parse_create_index(true, upper, parts, trimmed)));
}
if upper.starts_with("CREATE INDEX ") {
return Ok(Some(parse_create_index(false, upper, parts, trimmed)));
}
if upper.starts_with("DROP INDEX ") {
let if_exists = upper.contains("IF EXISTS");
let name = match extract_name_after_if_exists(parts, "INDEX") {
None => return Ok(None),
Some(r) => r?,
};
return Ok(Some(NodedbStatement::Collection(
CollectionStmt::DropIndex {
name,
collection: None,
if_exists,
},
)));
}
if upper.starts_with("SHOW INDEX") {
let collection = parts.get(2).map(|s| s.to_string());
return Ok(Some(NodedbStatement::Collection(
CollectionStmt::ShowIndexes { collection },
)));
}
if upper.starts_with("REINDEX ") {
let mut offset = 1usize;
let mut index_name: Option<String> = None;
let mut concurrent = false;
if parts
.get(offset)
.map(|p| p.eq_ignore_ascii_case("TABLE"))
.unwrap_or(false)
{
offset += 1;
}
if parts
.get(offset)
.map(|p| p.eq_ignore_ascii_case("INDEX"))
.unwrap_or(false)
{
offset += 1;
let name = parts.get(offset).ok_or_else(|| SqlError::Parse {
detail: "REINDEX INDEX requires an index name".to_string(),
})?;
if name.eq_ignore_ascii_case("CONCURRENTLY") {
return Err(SqlError::Parse {
detail: "REINDEX INDEX requires an index name before CONCURRENTLY"
.to_string(),
});
}
index_name = Some(name.to_lowercase());
offset += 1;
}
if parts
.get(offset)
.map(|p| p.eq_ignore_ascii_case("CONCURRENTLY"))
.unwrap_or(false)
{
concurrent = true;
offset += 1;
}
let collection = match parts.get(offset) {
None => return Ok(None),
Some(s) => s.to_lowercase(),
};
return Ok(Some(NodedbStatement::Collection(CollectionStmt::Reindex {
collection,
index_name,
concurrent,
})));
}
Ok(None)
})()
.transpose()
}
fn parse_create_index(
unique: bool,
upper: &str,
parts: &[&str],
_trimmed: &str,
) -> NodedbStatement {
let idx_offset: usize = if unique { 3 } else { 2 };
let (index_name, on_offset) = if parts
.get(idx_offset)
.map(|p| p.eq_ignore_ascii_case("ON"))
.unwrap_or(false)
{
(None, idx_offset)
} else {
let name = parts
.get(idx_offset)
.map(|s| s.to_lowercase())
.unwrap_or_default();
(
if name.is_empty() { None } else { Some(name) },
idx_offset + 1,
)
};
let raw_collection_token = parts.get(on_offset + 1).copied().unwrap_or("");
let (collection, field) = if let Some(paren_pos) = raw_collection_token.find('(') {
let coll = raw_collection_token[..paren_pos].to_lowercase();
let fld = raw_collection_token[paren_pos..]
.trim_matches(|c| c == '(' || c == ')')
.to_string();
(coll, fld)
} else if parts
.get(on_offset + 2)
.map(|p| p.eq_ignore_ascii_case("FIELDS"))
.unwrap_or(false)
{
let coll = raw_collection_token.to_lowercase();
let fld = parts.get(on_offset + 3).copied().unwrap_or("").to_string();
(coll, fld)
} else {
let coll = raw_collection_token.to_lowercase();
let fld = parts
.get(on_offset + 2)
.map(|s| s.trim_matches(|c| c == '(' || c == ')').to_string())
.unwrap_or_default();
(coll, fld)
};
let where_condition = if let Some(_pos) = upper.find(" WHERE ") {
let where_tok_idx = parts.iter().position(|p| p.eq_ignore_ascii_case("WHERE"));
where_tok_idx.map(|i| parts[i + 1..].join(" ").trim_end_matches(';').to_string())
} else {
None
};
let case_insensitive = upper.contains("COLLATE NOCASE") || upper.contains("COLLATE CI");
NodedbStatement::Collection(CollectionStmt::CreateIndex {
unique,
index_name,
collection,
field,
case_insensitive,
where_condition,
})
}