first version of vtable with keyword autocomplete

This commit is contained in:
pedrocarlo 2025-02-22 02:21:27 -03:00
parent ca574651d9
commit 99d979eb80
8 changed files with 89 additions and 18 deletions

1
Cargo.lock generated
View file

@ -1647,6 +1647,7 @@ dependencies = [
"julian_day_converter",
"libc",
"libloading",
"limbo_completion",
"limbo_crypto",
"limbo_ext",
"limbo_ipaddr",

View file

@ -44,6 +44,7 @@ limbo_regexp = { path = "extensions/regexp", version = "0.0.15" }
limbo_series = { path = "extensions/series", version = "0.0.15" }
limbo_time = { path = "extensions/time", version = "0.0.15" }
limbo_uuid = { path = "extensions/uuid", version = "0.0.15" }
limbo_completion = { path = "extensions/completion", version = "0.0.15" }
limbo_sqlite3_parser = { path = "vendored/sqlite3-parser", version = "0.0.15" }
limbo_ipaddr = { path = "extensions/ipaddr", version = "0.0.15" }
# Config for 'cargo dist'

View file

@ -24,7 +24,7 @@ clap = { version = "4.5", features = ["derive"] }
comfy-table = "7.1.4"
dirs = "5.0.1"
env_logger = "0.10.1"
limbo_core = { path = "../core" }
limbo_core = { path = "../core", default-features = true, features = ["completion"]}
rustyline = "12.0.0"
ctrlc = "3.4.4"
csv = "1.3.1"

View file

@ -25,6 +25,7 @@ time = ["limbo_time/static"]
crypto = ["limbo_crypto/static"]
series = ["limbo_series/static"]
ipaddr = ["limbo_ipaddr/static"]
completion = ["limbo_completion/static"]
[target.'cfg(target_os = "linux")'.dependencies]
io-uring = { version = "0.6.1", optional = true }
@ -64,6 +65,7 @@ limbo_time = { workspace = true, optional = true, features = ["static"] }
limbo_crypto = { workspace = true, optional = true, features = ["static"] }
limbo_series = { workspace = true, optional = true, features = ["static"] }
limbo_ipaddr = { workspace = true, optional = true, features = ["static"] }
limbo_completion = { workspace = true, optional = true, features = ["static"] }
miette = "7.4.0"
strum = "0.26"
parking_lot = "0.12.3"

View file

@ -154,6 +154,10 @@ impl Database {
if unsafe { !limbo_ipaddr::register_extension_static(&ext_api).is_ok() } {
return Err("Failed to register ipaddr extension".to_string());
}
#[cfg(feature = "completion")]
if unsafe { !limbo_completion::register_extension_static(&ext_api).is_ok() } {
return Err("Failed to register completion extension".to_string());
}
Ok(())
}
}

View file

@ -7,7 +7,7 @@ repository.workspace = true
version.workspace = true
[dependencies]
limbo_ext = { path = "../core", features = ["static"] }
limbo_ext = { workspace = true, features = ["static"] }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
mimalloc = { version = "0.1", default-features = false }

View file

@ -1,10 +1,11 @@
pub(crate) static KEYWORDS: [&str; 136] = [
pub(crate) static KEYWORDS: [&str; 147] = [
"ABORT",
"ACTION",
"ADD",
"AFTER",
"ALL",
"ALTER",
"ALWAYS",
"ANALYZE",
"AND",
"AS",
@ -45,18 +46,22 @@ pub(crate) static KEYWORDS: [&str; 136] = [
"END",
"ESCAPE",
"EXCEPT",
"EXCLUDE",
"EXCLUSIVE",
"EXISTS",
"EXPLAIN",
"FAIL",
"FILTER",
"FIRST",
"FOLLOWING",
"FOR",
"FOREIGN",
"FROM",
"FULL",
"GLO",
"GENERATED",
"GLOB",
"GROUP",
"GROUPS",
"HAVING",
"IF",
"IGNORE",
@ -74,21 +79,25 @@ pub(crate) static KEYWORDS: [&str; 136] = [
"ISNULL",
"JOIN",
"KEY",
"LAST",
"LEFT",
"LIKE",
"LIMIT",
"MATCH",
"MATERIALIZED",
"NATURAL",
"NO",
"NOT",
"NOTHING",
"NOTNULL",
"NULL",
"NULLS",
"OF",
"OFFSET",
"ON",
"OR",
"ORDER",
"OTHERS",
"OUTER",
"OVER",
"PARTITION",
@ -107,6 +116,7 @@ pub(crate) static KEYWORDS: [&str; 136] = [
"RENAME",
"REPLACE",
"RESTRICT",
"RETURNING",
"RIGHT",
"ROLLBACK",
"ROW",
@ -118,6 +128,7 @@ pub(crate) static KEYWORDS: [&str; 136] = [
"TEMP",
"TEMPORARY",
"THEN",
"TIES",
"TO",
"TRANSACTION",
"TRIGGER",

View file

@ -1,3 +1,6 @@
//! Reference for implementation
//! https://github.com/sqlite/sqlite/blob/a80089c5167856f0aadc9c878bd65843df724c06/ext/misc/completion.c
mod keywords;
use keywords::KEYWORDS;
@ -21,7 +24,6 @@ macro_rules! try_option {
#[derive(Debug, Default, PartialEq, Clone)]
enum CompletionPhase {
#[default]
FirstPhase = 0,
Keywords = 1,
Pragmas = 2,
Functions = 3,
@ -39,7 +41,6 @@ impl Into<i64> for CompletionPhase {
fn into(self) -> i64 {
use self::CompletionPhase::*;
match self {
FirstPhase => 0,
Keywords => 1,
Pragmas => 2,
Functions => 3,
@ -91,7 +92,37 @@ impl VTabModule for CompletionVTab {
}
fn filter(cursor: &mut Self::VCursor, arg_count: i32, args: &[Value]) -> ResultCode {
todo!()
if arg_count == 0 || arg_count > 2 {
return ResultCode::InvalidArgs;
}
cursor.reset();
let prefix = try_option!(args[0].to_text(), ResultCode::InvalidArgs);
let wholeline = args.get(1).map(|v| v.to_text().unwrap_or("")).unwrap_or("");
cursor.line = wholeline.to_string();
cursor.prefix = prefix.to_string();
// Currently best index is not implemented so the correct arg parsing is not done here
if !cursor.line.is_empty() && cursor.prefix.is_empty() {
let mut i = cursor.line.len();
while let Some(ch) = cursor.line.chars().next() {
if i > 0 && (ch.is_alphanumeric() || ch == '_') {
i -= 1;
} else {
break;
}
}
if cursor.line.len() - i > 0 {
// TODO see if need to inclusive range
cursor.prefix = cursor.line[..i].to_string();
}
}
cursor.rowid = 0;
cursor.phase = CompletionPhase::Keywords;
Self::next(cursor)
}
}
@ -108,35 +139,56 @@ struct CompletionCursor {
// conn: Connection
}
impl CompletionCursor {}
impl CompletionCursor {
fn reset(&mut self) {
self.line.clear();
self.prefix.clear();
self.inter_phase_counter = 0;
}
}
impl VTabCursor for CompletionCursor {
type Error = ResultCode;
fn next(&mut self) -> ResultCode {
let mut curr_col = -1 as isize;
self.rowid += 1;
let mut next_phase = CompletionPhase::FirstPhase;
while self.phase != CompletionPhase::Eof {
// dbg!(&self.phase, &self.prefix, &self.curr_row);
match self.phase {
CompletionPhase::Keywords => {
if self.inter_phase_counter >= KEYWORDS.len() {
self.curr_row.clear();
self.phase = CompletionPhase::Databases;
self.phase = CompletionPhase::Eof;
} else {
self.inter_phase_counter += 1;
self.curr_row.clear();
self.curr_row.push_str(KEYWORDS[self.inter_phase_counter]);
self.inter_phase_counter += 1;
}
}
CompletionPhase::Databases => {
// TODO implement this when
// self.stmt = self.conn.prepare("PRAGMA database_list")
curr_col = 1;
next_phase = CompletionPhase::Tables;
// CompletionPhase::Databases => {
// // TODO implement this when db conn is available
// // self.stmt = self.conn.prepare("PRAGMA database_list")
// curr_col = 1;
// next_phase = CompletionPhase::Tables;
// self.phase = CompletionPhase::Eof; // for now skip other phases
// }
_ => {
return ResultCode::EOF;
}
_ => (),
}
if self.prefix.is_empty() {
break;
}
if self.prefix.len() <= self.curr_row.len()
&& self.prefix == self.curr_row.to_lowercase()[..self.prefix.len()]
{
break;
}
}
if self.phase == CompletionPhase::Eof {
return ResultCode::EOF;
}
ResultCode::OK
}