///|
// Validate every token rather than discarding clauses missing from the upstream
// single-name DropIndex AST. Catalog-dependent constraints remain outside scope.
fn index_identifier(word : String) -> Bool {
if word.has_prefix("\"") && word.has_suffix("\"") {
return word.length() > 2
}
let tokens = @sql.tokenize(dialect=@sql.Postgres::{ }, word) catch {
_ => return false
}
tokens[:] is [@sql.Identifier(_)]
}
///|
fn classify_drop(words : ArrayView[String]) -> Command {
let mut i = 2
let concurrent = words.get(i) == Some("CONCURRENTLY")
if concurrent {
i += 1
}
if words.get(i) == Some("IF") {
if words.get(i + 1) != Some("EXISTS") {
return Unsupported
}
i += 2
}
let mut count = 0
while i < words.length() {
if !index_identifier(words[i]) {
return Unsupported
}
i += 1
if words.get(i) == Some(".") {
i += 1
if i >= words.length() || !index_identifier(words[i]) {
return Unsupported
}
i += 1
}
count += 1
if words.get(i) != Some(",") {
break
}
i += 1
if i == words.length() {
return Unsupported
}
}
if count == 0 {
return Unsupported
}
let cascade = words.get(i) == Some("CASCADE")
if cascade || words.get(i) == Some("RESTRICT") {
i += 1
}
if i != words.length() {
return Unsupported
}
if concurrent && (count != 1 || cascade) {
InvalidIndex(
"Concurrent index drop requires exactly one name and forbids CASCADE",
)
} else {
Index(concurrent)
}
}