use rustyline::completion::{Completer, Pair};
use rustyline::Context;
use rustyline::Result as RustyResult;
pub struct SqlCompleter {
keywords: Vec<String>,
table_names: Vec<String>,
}
impl SqlCompleter {
pub fn new() -> Self {
Self {
keywords: Self::default_keywords(),
table_names: Vec::new(),
}
}
pub fn set_table_names(&mut self, tables: Vec<String>) {
self.table_names = tables;
}
fn default_keywords() -> Vec<String> {
vec![
"CREATE", "DROP", "ALTER", "TRUNCATE",
"TABLE", "INDEX", "VIEW", "DATABASE", "SCHEMA",
"SELECT", "INSERT", "UPDATE", "DELETE",
"FROM", "WHERE", "JOIN", "ON", "USING",
"GROUP BY", "HAVING", "ORDER BY", "LIMIT", "OFFSET",
"INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN",
"CROSS JOIN",
"AND", "OR", "NOT", "IN", "BETWEEN", "LIKE", "IS",
"NULL", "TRUE", "FALSE",
"AS", "DISTINCT", "ALL", "ANY", "SOME", "EXISTS",
"COUNT", "SUM", "AVG", "MIN", "MAX",
"UPPER", "LOWER", "LENGTH", "SUBSTR", "TRIM",
"NOW", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP",
"INT", "INTEGER", "BIGINT", "SMALLINT",
"FLOAT", "DOUBLE", "NUMERIC", "DECIMAL",
"TEXT", "VARCHAR", "CHAR",
"BOOLEAN", "BOOL",
"DATE", "TIME", "TIMESTAMP", "TIMESTAMPTZ",
"BYTEA", "JSONB",
"PRIMARY KEY", "FOREIGN KEY", "UNIQUE", "NOT NULL",
"CHECK", "DEFAULT", "REFERENCES",
"BEGIN", "COMMIT", "ROLLBACK", "SAVEPOINT",
"START TRANSACTION",
"SET", "SHOW", "EXPLAIN", "ANALYZE",
"UNION", "INTERSECT", "EXCEPT",
"WITH", "CASE", "WHEN", "THEN", "ELSE", "END",
]
.iter()
.map(|s| s.to_string())
.collect()
}
fn find_completions(&self, word: &str) -> Vec<String> {
let word_upper = word.to_uppercase();
let mut completions = Vec::new();
for keyword in &self.keywords {
if keyword.starts_with(&word_upper) {
completions.push(keyword.clone());
}
}
for table in &self.table_names {
if table.to_uppercase().starts_with(&word_upper) {
completions.push(table.clone());
}
}
completions.sort();
completions.dedup();
completions
}
}
impl Completer for SqlCompleter {
type Candidate = Pair;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &Context<'_>,
) -> RustyResult<(usize, Vec<Pair>)> {
let start = line[..pos]
.rfind(|c: char| c.is_whitespace() || "(),;".contains(c))
.map(|i| i + 1)
.unwrap_or(0);
let word = &line[start..pos];
if word.is_empty() {
return Ok((pos, Vec::new()));
}
let completions = self.find_completions(word);
let pairs = completions
.into_iter()
.map(|s| Pair {
display: s.clone(),
replacement: s,
})
.collect();
Ok((start, pairs))
}
}
impl Default for SqlCompleter {
fn default() -> Self {
Self::new()
}
}