///|
// Return the token following a complete option list and each explicit setting.
// Accept declared boolean names or INDEX_CLEANUP's AUTO/ON/OFF. For that option,
// true means "not explicitly OFF", never proof that cleanup will occur.
fn maintenance_options(
words : ArrayView[String],
allowed : ArrayView[String],
) -> (Int32, Map[String, Bool])? {
let values : Map[String, Bool] = Map([])
if words.get(1) != Some("(") {
return Some((1, values))
}
let mut i = 2
while i < words.length() {
let option = words[i]
if !allowed.contains(option) || values.contains(option) {
return None
}
i += 1
let enabled = if option == "INDEX_CLEANUP" {
match words.get(i) {
Some("AUTO" | "ON") => {
i += 1
true
}
Some("OFF") => {
i += 1
false
}
_ => return None
}
} else {
match words.get(i) {
Some("TRUE" | "ON" | "1") => {
i += 1
true
}
Some("FALSE" | "OFF" | "0") => {
i += 1
false
}
Some("," | ")") => true
_ => return None
}
}
values[option] = enabled
match words.get(i) {
Some(")") => return Some((i + 1, values))
Some(",") => i += 1
_ => return None
}
}
None
}
///|
// Each target is [ONLY] [schema.]name [*] [(column, ...)]; no expressions,
// aliases or catalog-qualified names are reduced to a supported target.
// Some(true) means an intentionally empty, database-wide target list.
fn maintenance_targets(
words : ArrayView[String],
allow_columns~ : Bool,
) -> Bool? {
if words.is_empty() {
return Some(true)
}
let mut i = 0
while i < words.length() {
let only = words.get(i) == Some("ONLY")
if only {
i += 1
}
if i >= words.length() || !index_identifier(words[i]) {
return None
}
i += 1
if words.get(i) == Some(".") {
i += 1
if i >= words.length() || !index_identifier(words[i]) {
return None
}
i += 1
}
if words.get(i) == Some("*") {
if only {
return None
}
i += 1
}
if words.get(i) == Some("(") {
if !allow_columns {
return None
}
i += 1
while i < words.length() {
if !index_identifier(words[i]) {
return None
}
i += 1
if words.get(i) == Some(")") {
i += 1
break
}
if words.get(i) != Some(",") {
return None
}
i += 1
}
// The last consumed token must close a nonempty column list.
if words.get(i - 1) != Some(")") {
return None
}
}
if i == words.length() {
return Some(false)
}
if words.get(i) != Some(",") {
return None
}
i += 1
}
None
}
///|
fn classify_analyze(words : ArrayView[String]) -> Command {
let (start, options) = match
maintenance_options(words, ["VERBOSE", "SKIP_LOCKED"][:]) {
Some(parsed) => parsed
None => return Unsupported
}
let start = if start == 1 && words.get(1) == Some("VERBOSE") {
2
} else {
start
}
match maintenance_targets(words[start:], allow_columns=true) {
Some(database_wide) =>
Analyze(
skip_locked=options.get("SKIP_LOCKED").unwrap_or(false),
database_wide~,
)
None => Unsupported
}
}