This commit is contained in:
pedrocarlo
2025-02-21 01:45:45 -03:00
parent 9f1b43d06d
commit ca574651d9
5 changed files with 331 additions and 1 deletions

8
Cargo.lock generated
View File

@@ -1620,6 +1620,14 @@ dependencies = [
"rustyline",
]
[[package]]
name = "limbo_completion"
version = "0.0.15"
dependencies = [
"limbo_ext",
"mimalloc",
]
[[package]]
name = "limbo_core"
version = "0.0.15"

View File

@@ -9,7 +9,8 @@ members = [
"bindings/rust",
"bindings/wasm",
"cli",
"core",
"core",
"extensions/completion",
"extensions/core",
"extensions/crypto",
"extensions/kvstore",

View File

@@ -0,0 +1,19 @@
[package]
authors.workspace = true
edition.workspace = true
license.workspace = true
name = "limbo_completion"
repository.workspace = true
version.workspace = true
[dependencies]
limbo_ext = { path = "../core", features = ["static"] }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
mimalloc = { version = "0.1", default-features = false }
[lib]
crate-type = ["cdylib", "lib"]
[features]
static = ["limbo_ext/static"]

View File

@@ -0,0 +1,138 @@
pub(crate) static KEYWORDS: [&str; 136] = [
"ABORT",
"ACTION",
"ADD",
"AFTER",
"ALL",
"ALTER",
"ANALYZE",
"AND",
"AS",
"ASC",
"ATTACH",
"AUTOINCREMENT",
"BEFORE",
"BEGIN",
"BETWEEN",
"BY",
"CASCADE",
"CASE",
"CAST",
"CHECK",
"COLLATE",
"COLUMN",
"COMMIT",
"CONFLICT",
"CONSTRAINT",
"CREATE",
"CROSS",
"CURRENT",
"CURRENT_DATE",
"CURRENT_TIME",
"CURRENT_TIMESTAMP",
"DATABASE",
"DEFAULT",
"DEFERRABLE",
"DEFERRED",
"DELETE",
"DESC",
"DETACH",
"DISTINCT",
"DO",
"DROP",
"EACH",
"ELSE",
"END",
"ESCAPE",
"EXCEPT",
"EXCLUSIVE",
"EXISTS",
"EXPLAIN",
"FAIL",
"FILTER",
"FOLLOWING",
"FOR",
"FOREIGN",
"FROM",
"FULL",
"GLO",
"GROUP",
"HAVING",
"IF",
"IGNORE",
"IMMEDIATE",
"IN",
"INDEX",
"INDEXED",
"INITIALLY",
"INNER",
"INSERT",
"INSTEAD",
"INTERSECT",
"INTO",
"IS",
"ISNULL",
"JOIN",
"KEY",
"LEFT",
"LIKE",
"LIMIT",
"MATCH",
"NATURAL",
"NO",
"NOT",
"NOTHING",
"NOTNULL",
"NULL",
"OF",
"OFFSET",
"ON",
"OR",
"ORDER",
"OUTER",
"OVER",
"PARTITION",
"PLAN",
"PRAGMA",
"PRECEDING",
"PRIMARY",
"QUERY",
"RAISE",
"RANGE",
"RECURSIVE",
"REFERENCES",
"REGEXP",
"REINDEX",
"RELEASE",
"RENAME",
"REPLACE",
"RESTRICT",
"RIGHT",
"ROLLBACK",
"ROW",
"ROWS",
"SAVEPOINT",
"SELECT",
"SET",
"TABLE",
"TEMP",
"TEMPORARY",
"THEN",
"TO",
"TRANSACTION",
"TRIGGER",
"UNBOUNDED",
"UNION",
"UNIQUE",
"UPDATE",
"USING",
"VACUUM",
"VALUES",
"VIEW",
"VIRTUAL",
"WHEN",
"WHERE",
"WINDOW",
"WITH",
"WITHOUT",
];

View File

@@ -0,0 +1,164 @@
mod keywords;
use keywords::KEYWORDS;
use limbo_ext::{
register_extension, ExtensionApi, ResultCode, VTabCursor, VTabModule, VTabModuleDerive, Value,
};
register_extension! {
vtabs: { CompletionVTab }
}
macro_rules! try_option {
($expr:expr, $err:expr) => {
match $expr {
Some(val) => val,
None => return $err,
}
};
}
#[derive(Debug, Default, PartialEq, Clone)]
enum CompletionPhase {
#[default]
FirstPhase = 0,
Keywords = 1,
Pragmas = 2,
Functions = 3,
Collations = 4,
Indexes = 5,
Triggers = 6,
Databases = 7,
Tables = 8, // Also VIEWs and TRIGGERs
Columns = 9,
Modules = 10,
Eof = 11,
}
impl Into<i64> for CompletionPhase {
fn into(self) -> i64 {
use self::CompletionPhase::*;
match self {
FirstPhase => 0,
Keywords => 1,
Pragmas => 2,
Functions => 3,
Collations => 4,
Indexes => 5,
Triggers => 6,
Databases => 7,
Tables => 8,
Columns => 9,
Modules => 10,
Eof => 11,
}
}
}
/// A virtual table that generates a sequence of integers
#[derive(Debug, VTabModuleDerive)]
struct CompletionVTab {}
impl VTabModule for CompletionVTab {
type VCursor = CompletionCursor;
const NAME: &'static str = "completion";
fn connect(api: &ExtensionApi) -> ResultCode {
// Create table schema
let sql = "CREATE TABLE completion(
candidate TEXT,
prefix TEXT HIDDEN,
wholeline TEXT HIDDEN,
phase INT HIDDEN
)";
api.declare_virtual_table(Self::NAME, sql)
}
fn open() -> Self::VCursor {
CompletionCursor::default()
}
fn column(cursor: &Self::VCursor, idx: u32) -> Value {
cursor.column(idx)
}
fn next(cursor: &mut Self::VCursor) -> ResultCode {
cursor.next()
}
fn eof(cursor: &Self::VCursor) -> bool {
cursor.eof()
}
fn filter(cursor: &mut Self::VCursor, arg_count: i32, args: &[Value]) -> ResultCode {
todo!()
}
}
/// The cursor for iterating over the generated sequence
#[derive(Debug, Default)]
struct CompletionCursor {
line: String,
prefix: String,
curr_row: String,
rowid: i64,
phase: CompletionPhase,
inter_phase_counter: usize,
// stmt: Statement
// conn: Connection
}
impl CompletionCursor {}
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 {
match self.phase {
CompletionPhase::Keywords => {
if self.inter_phase_counter >= KEYWORDS.len() {
self.curr_row.clear();
self.phase = CompletionPhase::Databases;
} else {
self.inter_phase_counter += 1;
self.curr_row.push_str(KEYWORDS[self.inter_phase_counter]);
}
}
CompletionPhase::Databases => {
// TODO implement this when
// self.stmt = self.conn.prepare("PRAGMA database_list")
curr_col = 1;
next_phase = CompletionPhase::Tables;
}
_ => (),
}
}
ResultCode::OK
}
fn eof(&self) -> bool {
self.phase == CompletionPhase::Eof
}
fn column(&self, idx: u32) -> Value {
match idx {
0 => Value::from_text(self.curr_row.clone()), // COMPLETION_COLUMN_CANDIDATE
1 => Value::from_text(self.prefix.clone()), // COMPLETION_COLUMN_PREFIX
2 => Value::from_text(self.line.clone()), // COMPLETION_COLUMN_WHOLELINE
3 => Value::from_integer(self.phase.clone().into()), // COMPLETION_COLUMN_PHASE
_ => Value::null(),
}
}
fn rowid(&self) -> i64 {
self.rowid
}
}
#[cfg(test)]
mod tests {}