forked from tursodatabase/turso
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmod.rs
More file actions
173 lines (155 loc) · 7.92 KB
/
mod.rs
File metadata and controls
173 lines (155 loc) · 7.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
use std::{collections::HashMap, sync::Arc};
use turso_parser::ast;
use crate::{
schema::IndexColumn,
storage::btree::BTreeCursor,
types::{IOResult, IndexInfo, KeyInfo},
vdbe::Register,
Connection, LimboError, Result, Value,
};
pub mod backing_btree;
pub mod toy_vector_sparse_ivf;
pub const BACKING_BTREE_INDEX_METHOD_NAME: &str = "backing_btree";
pub const TOY_VECTOR_SPARSE_IVF_INDEX_METHOD_NAME: &str = "toy_vector_sparse_ivf";
/// index method "entry point" which can create attachment of the method to the table with given configuration
/// (this trait acts like a "factory")
pub trait IndexMethod: std::fmt::Debug + Send + Sync {
/// create attachment of the index method to the specific table with specific method configuration
fn attach(
&self,
configuration: &IndexMethodConfiguration,
) -> Result<Arc<dyn IndexMethodAttachment>>;
}
#[derive(Debug, Clone)]
pub struct IndexMethodConfiguration {
/// table name for which index_method is defined
pub table_name: String,
/// index name
pub index_name: String,
/// columns c1, c2, c3, ... provided to the index method (e.g. create index t_idx on t using method (c1, c2, c3, ...))
pub columns: Vec<IndexColumn>,
/// optional parameters provided to the index method through WITH clause
pub parameters: HashMap<String, Value>,
}
/// index method attached to the table with specific configuration
/// the attachment is capable of generating SELECT patterns where index can be used and also can create cursor for query execution
pub trait IndexMethodAttachment: std::fmt::Debug + Send + Sync {
fn definition<'a>(&'a self) -> IndexMethodDefinition<'a>;
fn init(&self) -> Result<Box<dyn IndexMethodCursor>>;
}
#[derive(Debug)]
pub struct IndexMethodDefinition<'a> {
/// index method name
pub method_name: &'a str,
/// index name
pub index_name: &'a str,
/// SELECT patterns where index method can be used
/// the patterns can contain positional placeholder which will make planner to capture parameters from the original query and provide them to the index method
/// (for example, pattern 'SELECT * FROM {table} LIMIT ?' will capture LIMIT parameter and provide its value from the query to the index method query_start(...) call)
pub patterns: &'a [ast::Select],
/// special marker which forces tursodb core to treat index method as backing btree - so it will allocate real btree on disk for that index method
pub backing_btree: bool,
}
/// cursor opened for index method and capable of executing DML/DDL/DQL queries for the index method over fixed table
pub trait IndexMethodCursor {
/// create necessary components for index method (usually, this is a bunch of btree-s)
fn create(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>>;
/// destroy components created in the create(...) call for index method
fn destroy(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>>;
/// open necessary components for reading the index
fn open_read(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>>;
/// open necessary components for writing the index
fn open_write(&mut self, connection: &Arc<Connection>) -> Result<IOResult<()>>;
/// handle insert action
/// "values" argument contains registers with values for index columns followed by rowid Integer register
/// (e.g. for "CREATE INDEX i ON t USING method (x, z)" insert(...) call will have 3 registers in values: [x, z, rowid])
fn insert(&mut self, values: &[Register]) -> Result<IOResult<()>>;
/// handle delete action
/// "values" argument contains registers with values for index columns followed by rowid Integer register
/// (e.g. for "CREATE INDEX i ON t USING method (x, z)" insert(...) call will have 3 registers in values: [x, z, rowid])
fn delete(&mut self, values: &[Register]) -> Result<IOResult<()>>;
/// initialize query to the index method
/// first element of "values" slice is the Integer register which holds index of the chosen [IndexMethodDefinition::patterns] by query planner
/// next arguments of the "values" slice are values from the original query expression captured by pattern
///
/// For example, for 2 patterns ["SELECT * FROM {table} LIMIT ?", "SELECT * FROM {table} WHERE x = ?"], query_start(...) call can have following arguments:
/// - [Integer(0), Integer(10)] - pattern "SELECT * FROM {table} LIMIT ?" was chosen with LIMIT parameter equals to 10
/// - [Integer(1), Text("turso")] - pattern "SELECT * FROM {table} WHERE x = ?" was chosen with equality comparison equals to "turso"
///
/// Returns false if query will produce no rows (similar to VFilter/Rewind op codes)
fn query_start(&mut self, values: &[Register]) -> Result<IOResult<bool>>;
/// Moves cursor to the next response row
/// Returns false if query exhausted all rows
fn query_next(&mut self) -> Result<IOResult<bool>>;
/// Return column with given idx (zero-based) from current row
fn query_column(&mut self, idx: usize) -> Result<IOResult<Value>>;
/// Return rowid of the original table row which corresponds to the current cursor row
///
/// This method is used by tursodb core in order to "enrich" response from query pattern with additional fields from original table
/// For example, consider pattern like this:
///
/// > SELECT vector_distance_jaccard(embedding, ?) as d FROM table ORDER BY d LIMIT 10
///
/// It can be used in more complex query:
///
/// > SELECT name, comment, rating, vector_distance_jaccard(embedding, ?) as d FROM table ORDER BY d LIMIT 10
///
/// In this case query planner will execute index method query first, and then
/// enrich its result with name, comment, rating columns from original table accessing original row by its rowid
/// returned from query_rowid(...) method
fn query_rowid(&mut self) -> Result<IOResult<Option<i64>>>;
}
/// helper method to open table BTree cursor in the index method implementation
pub(crate) fn open_table_cursor(connection: &Connection, table: &str) -> Result<BTreeCursor> {
let pager = connection.pager.load().clone();
let schema = connection.schema.read();
let Some(table) = schema.get_table(table) else {
return Err(LimboError::InternalError(format!(
"table {table} not found",
)));
};
let cursor = BTreeCursor::new_table(pager, table.get_root_page(), table.columns().len());
Ok(cursor)
}
/// helper method to open index BTree cursor in the index method implementation
pub(crate) fn open_index_cursor(
connection: &Connection,
table: &str,
index: &str,
keys: Vec<KeyInfo>,
) -> Result<BTreeCursor> {
let pager = connection.pager.load().clone();
let schema = connection.schema.read();
let Some(scratch) = schema.get_index(table, index) else {
return Err(LimboError::InternalError(format!(
"index {index} for table {table} not found",
)));
};
let mut cursor = BTreeCursor::new(pager, scratch.root_page, keys.len());
cursor.index_info = Some(Arc::new(IndexInfo {
has_rowid: false,
num_cols: keys.len(),
key_info: keys,
}));
Ok(cursor)
}
/// helper method to parse select patterns for [IndexMethodAttachment::definition] call
pub(crate) fn parse_patterns(patterns: &[&str]) -> Result<Vec<ast::Select>> {
let mut parsed = Vec::new();
for pattern in patterns {
let mut parser = turso_parser::parser::Parser::new(pattern.as_bytes());
let Some(ast) = parser.next() else {
return Err(LimboError::ParseError(format!(
"unable to parse pattern statement: {pattern}",
)));
};
let ast = ast?;
let ast::Cmd::Stmt(ast::Stmt::Select(select)) = ast else {
return Err(LimboError::ParseError(format!(
"only select patterns are allowed: {pattern}",
)));
};
parsed.push(select);
}
Ok(parsed)
}