gqlite 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/Cargo.toml +10 -4
- data/ext/db-index/Cargo.toml +29 -0
- data/ext/db-index/src/hnsw/error.rs +66 -0
- data/ext/db-index/src/hnsw/graph_store.rs +272 -0
- data/ext/db-index/src/hnsw/id.rs +36 -0
- data/ext/db-index/src/hnsw/implementation.rs +1517 -0
- data/ext/db-index/src/hnsw/kernels.rs +1228 -0
- data/ext/db-index/src/hnsw/metric.rs +244 -0
- data/ext/db-index/src/hnsw/scalar.rs +72 -0
- data/ext/db-index/src/hnsw/simple_store.rs +140 -0
- data/ext/db-index/src/hnsw/store.rs +472 -0
- data/ext/db-index/src/hnsw/vector.rs +105 -0
- data/ext/db-index/src/hnsw/vectors.rs +568 -0
- data/ext/db-index/src/hnsw.rs +42 -0
- data/ext/db-index/src/lib.rs +3 -0
- data/ext/gqlitedb/Cargo.toml +19 -9
- data/ext/gqlitedb/benches/common/pokec.rs +62 -2
- data/ext/gqlitedb/benches/pokec_divan.rs +66 -2
- data/ext/gqlitedb/benches/pokec_iai.rs +60 -3
- data/ext/gqlitedb/release.toml +2 -2
- data/ext/gqlitedb/src/aggregators/arithmetic.rs +3 -1
- data/ext/gqlitedb/src/aggregators/containers.rs +1 -1
- data/ext/gqlitedb/src/aggregators/stats.rs +21 -12
- data/ext/gqlitedb/src/capi.rs +1 -1
- data/ext/gqlitedb/src/compiler/expression_analyser.rs +28 -20
- data/ext/gqlitedb/src/compiler/variables_manager.rs +97 -29
- data/ext/gqlitedb/src/compiler.rs +505 -225
- data/ext/gqlitedb/src/connection.rs +149 -11
- data/ext/gqlitedb/src/consts.rs +1 -2
- data/ext/gqlitedb/src/error.rs +123 -61
- data/ext/gqlitedb/src/functions/common.rs +39 -2
- data/ext/gqlitedb/src/functions/math.rs +8 -3
- data/ext/gqlitedb/src/functions/path.rs +48 -11
- data/ext/gqlitedb/src/functions/value.rs +1 -1
- data/ext/gqlitedb/src/functions.rs +23 -7
- data/ext/gqlitedb/src/graph.rs +1 -9
- data/ext/gqlitedb/src/interpreter/evaluators.rs +968 -130
- data/ext/gqlitedb/src/interpreter/instructions.rs +39 -2
- data/ext/gqlitedb/src/lib.rs +5 -4
- data/ext/gqlitedb/src/planner.rs +329 -0
- data/ext/gqlitedb/src/prelude.rs +3 -3
- data/ext/gqlitedb/src/store/pgrx.rs +1 -1
- data/ext/gqlitedb/src/store/postgres.rs +735 -7
- data/ext/gqlitedb/src/store/redb/hnsw_store.rs +702 -0
- data/ext/gqlitedb/src/store/redb/index.rs +274 -0
- data/ext/gqlitedb/src/store/redb.rs +1268 -113
- data/ext/gqlitedb/src/store/sqlbase/sqlmetadata.rs +28 -0
- data/ext/gqlitedb/src/store/sqlbase/sqlstore.rs +103 -0
- data/ext/gqlitedb/src/store/sqlbase.rs +146 -16
- data/ext/gqlitedb/src/store/sqlite.rs +569 -5
- data/ext/gqlitedb/src/store/vector_extract.rs +56 -0
- data/ext/gqlitedb/src/store.rs +123 -3
- data/ext/gqlitedb/src/tests/compiler.rs +207 -10
- data/ext/gqlitedb/src/tests/connection/postgres.rs +38 -3
- data/ext/gqlitedb/src/tests/connection/redb.rs +23 -0
- data/ext/gqlitedb/src/tests/connection/sqlite.rs +31 -0
- data/ext/gqlitedb/src/tests/connection.rs +61 -1
- data/ext/gqlitedb/src/tests/evaluators.rs +162 -7
- data/ext/gqlitedb/src/tests/parser.rs +54 -23
- data/ext/gqlitedb/src/tests/planner.rs +511 -0
- data/ext/gqlitedb/src/tests/store/postgres.rs +7 -0
- data/ext/gqlitedb/src/tests/store/redb.rs +8 -0
- data/ext/gqlitedb/src/tests/store/sqlite.rs +8 -0
- data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +182 -0
- data/ext/gqlitedb/src/tests/store/vector_index/redb.rs +386 -0
- data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +166 -0
- data/ext/gqlitedb/src/tests/store/vector_index.rs +2313 -0
- data/ext/gqlitedb/src/tests/store.rs +78 -14
- data/ext/gqlitedb/src/tests/templates/ast.rs +92 -16
- data/ext/gqlitedb/src/tests/templates/programs.rs +14 -7
- data/ext/gqlitedb/src/tests.rs +15 -9
- data/ext/gqlitedb/src/utils.rs +6 -1
- data/ext/gqlitedb/src/value/compare.rs +61 -3
- data/ext/gqlitedb/src/value.rs +136 -7
- data/ext/gqlitedb/templates/sql/postgres/metadata_delete.sql +1 -0
- data/ext/gqlitedb/templates/sql/postgres/node_select.sql +1 -1
- data/ext/gqlitedb/templates/sql/sqlite/metadata_delete.sql +1 -0
- data/ext/gqliterb/src/lib.rs +53 -8
- data/ext/gqlparser/Cargo.toml +25 -0
- data/ext/gqlparser/README.MD +9 -0
- data/ext/gqlparser/benches/pokec_divan.rs +34 -0
- data/ext/gqlparser/src/common.rs +69 -0
- data/ext/gqlparser/src/gqls/ast.rs +69 -0
- data/ext/gqlparser/src/gqls/constraint.rs +79 -0
- data/ext/gqlparser/src/gqls/error.rs +22 -0
- data/ext/gqlparser/src/gqls/parser.rs +813 -0
- data/ext/gqlparser/src/gqls/prelude.rs +8 -0
- data/ext/gqlparser/src/gqls/properties.rs +115 -0
- data/ext/gqlparser/src/gqls/resolve.rs +207 -0
- data/ext/gqlparser/src/gqls.rs +265 -0
- data/ext/gqlparser/src/lib.rs +7 -0
- data/ext/gqlparser/src/oc/ast.rs +680 -0
- data/ext/gqlparser/src/oc/error.rs +172 -0
- data/ext/gqlparser/src/oc/lexer.rs +429 -0
- data/ext/gqlparser/src/oc/parser/tests.rs +2284 -0
- data/ext/gqlparser/src/oc/parser.rs +2005 -0
- data/ext/gqlparser/src/oc.rs +33 -0
- data/ext/gqlparser/src/prelude.rs +3 -0
- data/ext/graphcore/Cargo.toml +1 -0
- data/ext/graphcore/src/error.rs +26 -0
- data/ext/graphcore/src/graph.rs +177 -51
- data/ext/graphcore/src/lib.rs +4 -2
- data/ext/graphcore/src/open_cypher.rs +12 -0
- data/ext/graphcore/src/table.rs +50 -2
- data/ext/graphcore/src/timestamp.rs +127 -104
- data/ext/graphcore/src/value/tensor.rs +739 -0
- data/ext/graphcore/src/value/value_map.rs +1 -1
- data/ext/graphcore/src/value.rs +343 -19
- metadata +90 -22
- data/ext/gqlitedb/src/parser/ast.rs +0 -604
- data/ext/gqlitedb/src/parser/parser_impl.rs +0 -1213
- data/ext/gqlitedb/src/parser.rs +0 -4
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
use super::error::Error;
|
|
2
|
+
use super::error::Result;
|
|
3
|
+
use super::scalar::Dtype;
|
|
4
|
+
use super::scalar::Scalar;
|
|
5
|
+
use crate::hnsw::id::NodeId;
|
|
6
|
+
use crate::hnsw::vector::Dense;
|
|
7
|
+
use crate::hnsw::vector::Qi8;
|
|
8
|
+
use crate::hnsw::vector::Qi8Ref;
|
|
9
|
+
use crate::hnsw::vector::VectorFamily;
|
|
10
|
+
use crate::hnsw::vector::VectorView;
|
|
11
|
+
use arc_swap::ArcSwapOption;
|
|
12
|
+
use std::io::Read;
|
|
13
|
+
use std::io::Write;
|
|
14
|
+
use std::mem::size_of;
|
|
15
|
+
use std::sync::Arc;
|
|
16
|
+
|
|
17
|
+
const VECTOR_STORE_FILE_VERSION: u32 = 1;
|
|
18
|
+
|
|
19
|
+
#[derive(serde::Serialize, serde::Deserialize)]
|
|
20
|
+
struct VectorStoreHeader
|
|
21
|
+
{
|
|
22
|
+
version: u32,
|
|
23
|
+
dtype: Dtype,
|
|
24
|
+
dim: u32,
|
|
25
|
+
max_nodes: u32,
|
|
26
|
+
node_count: u32,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Clone)]
|
|
30
|
+
pub struct ArcVector<S: Scalar>(Arc<Box<[S]>>);
|
|
31
|
+
|
|
32
|
+
impl<S: Scalar> ArcVector<S>
|
|
33
|
+
{
|
|
34
|
+
pub fn new(data: Box<[S]>) -> Self
|
|
35
|
+
{
|
|
36
|
+
ArcVector(Arc::new(data))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl<S: Scalar> VectorView<Dense<S>> for ArcVector<S>
|
|
41
|
+
{
|
|
42
|
+
fn view(&self) -> &[S]
|
|
43
|
+
{
|
|
44
|
+
self.0.as_ref().as_ref()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[derive(Clone)]
|
|
49
|
+
struct Qi8Vector
|
|
50
|
+
{
|
|
51
|
+
data: Box<[i8]>,
|
|
52
|
+
scale: f32,
|
|
53
|
+
zero_point: i8,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[derive(Clone)]
|
|
57
|
+
pub struct ArcQi8Vector(Arc<Qi8Vector>);
|
|
58
|
+
|
|
59
|
+
impl VectorView<Qi8> for ArcQi8Vector
|
|
60
|
+
{
|
|
61
|
+
fn view<'a>(&'a self) -> Qi8Ref<'a>
|
|
62
|
+
{
|
|
63
|
+
Qi8Ref {
|
|
64
|
+
data: self.0.data.as_ref(),
|
|
65
|
+
scale: self.0.scale,
|
|
66
|
+
zero_point: self.0.zero_point,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub trait VectorStore: Send + Sync
|
|
72
|
+
{
|
|
73
|
+
type Family: VectorFamily;
|
|
74
|
+
type Vector<'a>: VectorView<Self::Family>
|
|
75
|
+
where
|
|
76
|
+
Self: 'a;
|
|
77
|
+
|
|
78
|
+
fn dim(&self) -> usize;
|
|
79
|
+
fn vector<'a>(&'a self, id: NodeId) -> Option<Self::Vector<'a>>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub trait VectorStoreMut: VectorStore
|
|
83
|
+
{
|
|
84
|
+
fn set<'a>(&self, id: NodeId, vector: <Self::Family as VectorFamily>::Ref<'a>) -> Result<()>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub struct InMemoryVectorStore<S: Scalar>
|
|
88
|
+
{
|
|
89
|
+
dim: usize,
|
|
90
|
+
vectors: Vec<ArcSwapOption<Box<[S]>>>,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
impl<S: Scalar> InMemoryVectorStore<S>
|
|
94
|
+
{
|
|
95
|
+
pub fn new(dim: usize, max_nodes: usize) -> Self
|
|
96
|
+
{
|
|
97
|
+
let mut vectors = Vec::with_capacity(max_nodes);
|
|
98
|
+
vectors.resize_with(max_nodes, ArcSwapOption::empty);
|
|
99
|
+
Self { dim, vectors }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pub fn max_nodes(&self) -> usize
|
|
103
|
+
{
|
|
104
|
+
self.vectors.len()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub fn save_to<W: Write>(&self, w: &mut W, node_count: usize) -> Result<()>
|
|
108
|
+
{
|
|
109
|
+
#[cfg(not(target_endian = "little"))]
|
|
110
|
+
{
|
|
111
|
+
return Err(Error::InvalidIndexFormat(
|
|
112
|
+
"vector store persistence requires little-endian".to_string(),
|
|
113
|
+
));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if node_count > self.max_nodes()
|
|
117
|
+
{
|
|
118
|
+
return Err(Error::InvalidIndexFormat(
|
|
119
|
+
"node_count exceeds vector store capacity".to_string(),
|
|
120
|
+
));
|
|
121
|
+
}
|
|
122
|
+
if self.dim == 0
|
|
123
|
+
{
|
|
124
|
+
return Err(Error::InvalidIndexFormat("dim must be > 0".to_string()));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let header = VectorStoreHeader {
|
|
128
|
+
version: VECTOR_STORE_FILE_VERSION,
|
|
129
|
+
dtype: S::DTYPE,
|
|
130
|
+
dim: self
|
|
131
|
+
.dim
|
|
132
|
+
.try_into()
|
|
133
|
+
.map_err(|_| Error::InvalidIndexFormat("dim exceeds u32::MAX".to_string()))?,
|
|
134
|
+
max_nodes: self
|
|
135
|
+
.max_nodes()
|
|
136
|
+
.try_into()
|
|
137
|
+
.map_err(|_| Error::InvalidIndexFormat("max_nodes exceeds u32::MAX".to_string()))?,
|
|
138
|
+
node_count: node_count
|
|
139
|
+
.try_into()
|
|
140
|
+
.map_err(|_| Error::InvalidIndexFormat("node_count exceeds u32::MAX".to_string()))?,
|
|
141
|
+
};
|
|
142
|
+
bincode::serialize_into(&mut *w, &header)
|
|
143
|
+
.map_err(|e| Error::InvalidIndexFormat(format!("bincode error: {e}")))?;
|
|
144
|
+
|
|
145
|
+
for internal_id in 0..node_count
|
|
146
|
+
{
|
|
147
|
+
let v = self
|
|
148
|
+
.vectors
|
|
149
|
+
.get(internal_id)
|
|
150
|
+
.and_then(|slot| slot.load_full())
|
|
151
|
+
.ok_or(Error::MissingVector)?;
|
|
152
|
+
let slice: &[S] = v.as_ref().as_ref();
|
|
153
|
+
if slice.len() != self.dim
|
|
154
|
+
{
|
|
155
|
+
return Err(Error::DimensionMismatch {
|
|
156
|
+
expected: self.dim,
|
|
157
|
+
actual: slice.len(),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
w.write_all(bytemuck::cast_slice(slice))?;
|
|
161
|
+
}
|
|
162
|
+
w.flush()?;
|
|
163
|
+
Ok(())
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
pub fn load_from<R: Read>(r: &mut R) -> Result<(Self, usize)>
|
|
167
|
+
{
|
|
168
|
+
#[cfg(not(target_endian = "little"))]
|
|
169
|
+
{
|
|
170
|
+
return Err(Error::InvalidIndexFormat(
|
|
171
|
+
"vector store persistence requires little-endian".to_string(),
|
|
172
|
+
));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let header: VectorStoreHeader = bincode::deserialize_from(&mut *r)
|
|
176
|
+
.map_err(|e| Error::InvalidIndexFormat(format!("bincode error: {e}")))?;
|
|
177
|
+
if header.version != VECTOR_STORE_FILE_VERSION
|
|
178
|
+
{
|
|
179
|
+
return Err(Error::InvalidIndexFormat(format!(
|
|
180
|
+
"unsupported vector store version {}",
|
|
181
|
+
header.version
|
|
182
|
+
)));
|
|
183
|
+
}
|
|
184
|
+
if header.dtype != S::DTYPE
|
|
185
|
+
{
|
|
186
|
+
return Err(Error::InvalidIndexFormat(format!(
|
|
187
|
+
"dtype mismatch: file has {:?}, but this store is {:?}",
|
|
188
|
+
header.dtype,
|
|
189
|
+
S::DTYPE
|
|
190
|
+
)));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let dim = header.dim as usize;
|
|
194
|
+
let max_nodes = header.max_nodes as usize;
|
|
195
|
+
let node_count = header.node_count as usize;
|
|
196
|
+
|
|
197
|
+
if dim == 0
|
|
198
|
+
{
|
|
199
|
+
return Err(Error::InvalidIndexFormat("dim must be > 0".to_string()));
|
|
200
|
+
}
|
|
201
|
+
if node_count > max_nodes
|
|
202
|
+
{
|
|
203
|
+
return Err(Error::InvalidIndexFormat(
|
|
204
|
+
"node_count exceeds max_nodes".to_string(),
|
|
205
|
+
));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let bytes_per_vector = dim
|
|
209
|
+
.checked_mul(size_of::<S>())
|
|
210
|
+
.ok_or_else(|| Error::InvalidIndexFormat("vector byte size overflow".to_string()))?;
|
|
211
|
+
|
|
212
|
+
let store = InMemoryVectorStore::new(dim, max_nodes);
|
|
213
|
+
for internal_id in 0..node_count
|
|
214
|
+
{
|
|
215
|
+
let mut v = vec![S::from_f32(0.0); dim];
|
|
216
|
+
let bytes = bytemuck::cast_slice_mut(&mut v);
|
|
217
|
+
debug_assert_eq!(bytes.len(), bytes_per_vector);
|
|
218
|
+
r.read_exact(bytes)?;
|
|
219
|
+
store.vectors[internal_id].store(Some(Arc::new(v.into_boxed_slice())));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
Ok((store, node_count))
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
impl<S: Scalar> VectorStore for InMemoryVectorStore<S>
|
|
227
|
+
{
|
|
228
|
+
type Family = Dense<S>;
|
|
229
|
+
type Vector<'a>
|
|
230
|
+
= ArcVector<S>
|
|
231
|
+
where
|
|
232
|
+
Self: 'a;
|
|
233
|
+
|
|
234
|
+
fn dim(&self) -> usize
|
|
235
|
+
{
|
|
236
|
+
self.dim
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fn vector<'a>(&'a self, id: NodeId) -> Option<Self::Vector<'a>>
|
|
240
|
+
{
|
|
241
|
+
self.vectors.get(id.as_usize())?.load_full().map(ArcVector)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
impl<S: Scalar> VectorStoreMut for InMemoryVectorStore<S>
|
|
246
|
+
{
|
|
247
|
+
fn set(&self, id: NodeId, vector: &[S]) -> Result<()>
|
|
248
|
+
{
|
|
249
|
+
if vector.len() != self.dim
|
|
250
|
+
{
|
|
251
|
+
return Err(Error::DimensionMismatch {
|
|
252
|
+
expected: self.dim,
|
|
253
|
+
actual: vector.len(),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
let slot = self
|
|
257
|
+
.vectors
|
|
258
|
+
.get(id.as_usize())
|
|
259
|
+
.ok_or_else(|| Error::InvalidIndexFormat("node id out of bounds".to_string()))?;
|
|
260
|
+
slot.store(Some(Arc::new(vector.to_vec().into_boxed_slice())));
|
|
261
|
+
Ok(())
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
pub struct InMemoryQi8VectorStore
|
|
266
|
+
{
|
|
267
|
+
dim: usize,
|
|
268
|
+
vectors: Vec<ArcSwapOption<Qi8Vector>>,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
impl InMemoryQi8VectorStore
|
|
272
|
+
{
|
|
273
|
+
pub fn new(dim: usize, max_nodes: usize) -> Self
|
|
274
|
+
{
|
|
275
|
+
let mut vectors = Vec::with_capacity(max_nodes);
|
|
276
|
+
vectors.resize_with(max_nodes, ArcSwapOption::empty);
|
|
277
|
+
Self { dim, vectors }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
pub fn max_nodes(&self) -> usize
|
|
281
|
+
{
|
|
282
|
+
self.vectors.len()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
pub fn save_to<W: Write>(&self, w: &mut W, node_count: usize) -> Result<()>
|
|
286
|
+
{
|
|
287
|
+
#[cfg(not(target_endian = "little"))]
|
|
288
|
+
{
|
|
289
|
+
return Err(Error::InvalidIndexFormat(
|
|
290
|
+
"vector store persistence requires little-endian".to_string(),
|
|
291
|
+
));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if node_count > self.max_nodes()
|
|
295
|
+
{
|
|
296
|
+
return Err(Error::InvalidIndexFormat(
|
|
297
|
+
"node_count exceeds vector store capacity".to_string(),
|
|
298
|
+
));
|
|
299
|
+
}
|
|
300
|
+
if self.dim == 0
|
|
301
|
+
{
|
|
302
|
+
return Err(Error::InvalidIndexFormat("dim must be > 0".to_string()));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let header = VectorStoreHeader {
|
|
306
|
+
version: VECTOR_STORE_FILE_VERSION,
|
|
307
|
+
dtype: Dtype::QI8,
|
|
308
|
+
dim: self
|
|
309
|
+
.dim
|
|
310
|
+
.try_into()
|
|
311
|
+
.map_err(|_| Error::InvalidIndexFormat("dim exceeds u32::MAX".to_string()))?,
|
|
312
|
+
max_nodes: self
|
|
313
|
+
.max_nodes()
|
|
314
|
+
.try_into()
|
|
315
|
+
.map_err(|_| Error::InvalidIndexFormat("max_nodes exceeds u32::MAX".to_string()))?,
|
|
316
|
+
node_count: node_count
|
|
317
|
+
.try_into()
|
|
318
|
+
.map_err(|_| Error::InvalidIndexFormat("node_count exceeds u32::MAX".to_string()))?,
|
|
319
|
+
};
|
|
320
|
+
bincode::serialize_into(&mut *w, &header)
|
|
321
|
+
.map_err(|e| Error::InvalidIndexFormat(format!("bincode error: {e}")))?;
|
|
322
|
+
|
|
323
|
+
for internal_id in 0..node_count
|
|
324
|
+
{
|
|
325
|
+
let v = self
|
|
326
|
+
.vectors
|
|
327
|
+
.get(internal_id)
|
|
328
|
+
.and_then(|slot| slot.load_full())
|
|
329
|
+
.ok_or(Error::MissingVector)?;
|
|
330
|
+
if v.data.len() != self.dim
|
|
331
|
+
{
|
|
332
|
+
return Err(Error::DimensionMismatch {
|
|
333
|
+
expected: self.dim,
|
|
334
|
+
actual: v.data.len(),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
w.write_all(&v.scale.to_le_bytes())?;
|
|
338
|
+
w.write_all(&[v.zero_point as u8])?;
|
|
339
|
+
w.write_all(bytemuck::cast_slice(v.data.as_ref()))?;
|
|
340
|
+
}
|
|
341
|
+
w.flush()?;
|
|
342
|
+
Ok(())
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
pub fn load_from<R: Read>(r: &mut R) -> Result<(Self, usize)>
|
|
346
|
+
{
|
|
347
|
+
#[cfg(not(target_endian = "little"))]
|
|
348
|
+
{
|
|
349
|
+
return Err(Error::InvalidIndexFormat(
|
|
350
|
+
"vector store persistence requires little-endian".to_string(),
|
|
351
|
+
));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let header: VectorStoreHeader = bincode::deserialize_from(&mut *r)
|
|
355
|
+
.map_err(|e| Error::InvalidIndexFormat(format!("bincode error: {e}")))?;
|
|
356
|
+
if header.version != VECTOR_STORE_FILE_VERSION
|
|
357
|
+
{
|
|
358
|
+
return Err(Error::InvalidIndexFormat(format!(
|
|
359
|
+
"unsupported vector store version {}",
|
|
360
|
+
header.version
|
|
361
|
+
)));
|
|
362
|
+
}
|
|
363
|
+
if header.dtype != Dtype::QI8
|
|
364
|
+
{
|
|
365
|
+
return Err(Error::InvalidIndexFormat(format!(
|
|
366
|
+
"dtype mismatch: file has {:?}, but this store is {:?}",
|
|
367
|
+
header.dtype,
|
|
368
|
+
Dtype::QI8
|
|
369
|
+
)));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let dim = header.dim as usize;
|
|
373
|
+
let max_nodes = header.max_nodes as usize;
|
|
374
|
+
let node_count = header.node_count as usize;
|
|
375
|
+
|
|
376
|
+
if dim == 0
|
|
377
|
+
{
|
|
378
|
+
return Err(Error::InvalidIndexFormat("dim must be > 0".to_string()));
|
|
379
|
+
}
|
|
380
|
+
if node_count > max_nodes
|
|
381
|
+
{
|
|
382
|
+
return Err(Error::InvalidIndexFormat(
|
|
383
|
+
"node_count exceeds max_nodes".to_string(),
|
|
384
|
+
));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let store = InMemoryQi8VectorStore::new(dim, max_nodes);
|
|
388
|
+
for internal_id in 0..node_count
|
|
389
|
+
{
|
|
390
|
+
let mut scale_bytes = [0u8; 4];
|
|
391
|
+
r.read_exact(&mut scale_bytes)?;
|
|
392
|
+
let scale = f32::from_le_bytes(scale_bytes);
|
|
393
|
+
|
|
394
|
+
let mut zp = [0u8; 1];
|
|
395
|
+
r.read_exact(&mut zp)?;
|
|
396
|
+
let zero_point = zp[0] as i8;
|
|
397
|
+
|
|
398
|
+
let mut data = vec![0i8; dim];
|
|
399
|
+
r.read_exact(bytemuck::cast_slice_mut(&mut data))?;
|
|
400
|
+
store.vectors[internal_id].store(Some(Arc::new(Qi8Vector {
|
|
401
|
+
data: data.into_boxed_slice(),
|
|
402
|
+
scale,
|
|
403
|
+
zero_point,
|
|
404
|
+
})));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
Ok((store, node_count))
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
impl VectorStore for InMemoryQi8VectorStore
|
|
412
|
+
{
|
|
413
|
+
type Family = Qi8;
|
|
414
|
+
type Vector<'a>
|
|
415
|
+
= ArcQi8Vector
|
|
416
|
+
where
|
|
417
|
+
Self: 'a;
|
|
418
|
+
|
|
419
|
+
fn dim(&self) -> usize
|
|
420
|
+
{
|
|
421
|
+
self.dim
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fn vector<'a>(&'a self, id: NodeId) -> Option<Self::Vector<'a>>
|
|
425
|
+
{
|
|
426
|
+
self
|
|
427
|
+
.vectors
|
|
428
|
+
.get(id.as_usize())?
|
|
429
|
+
.load_full()
|
|
430
|
+
.map(ArcQi8Vector)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
impl VectorStoreMut for InMemoryQi8VectorStore
|
|
435
|
+
{
|
|
436
|
+
fn set<'a>(&self, id: NodeId, vector: Qi8Ref<'a>) -> Result<()>
|
|
437
|
+
{
|
|
438
|
+
if vector.data.len() != self.dim
|
|
439
|
+
{
|
|
440
|
+
return Err(Error::DimensionMismatch {
|
|
441
|
+
expected: self.dim,
|
|
442
|
+
actual: vector.data.len(),
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
let slot = self
|
|
446
|
+
.vectors
|
|
447
|
+
.get(id.as_usize())
|
|
448
|
+
.ok_or_else(|| Error::InvalidIndexFormat("node id out of bounds".to_string()))?;
|
|
449
|
+
slot.store(Some(Arc::new(Qi8Vector {
|
|
450
|
+
data: vector.data.to_vec().into_boxed_slice(),
|
|
451
|
+
scale: vector.scale,
|
|
452
|
+
zero_point: vector.zero_point,
|
|
453
|
+
})));
|
|
454
|
+
Ok(())
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
#[cfg(test)]
|
|
459
|
+
mod tests
|
|
460
|
+
{
|
|
461
|
+
use super::*;
|
|
462
|
+
use crate::hnsw::vector::VectorRef as _;
|
|
463
|
+
use approx::assert_relative_eq;
|
|
464
|
+
use tempfile::tempdir;
|
|
465
|
+
|
|
466
|
+
#[test]
|
|
467
|
+
fn in_memory_vector_store_save_load_roundtrip()
|
|
468
|
+
{
|
|
469
|
+
let dir = tempdir().unwrap();
|
|
470
|
+
let path = dir.path().join("vectors.bin");
|
|
471
|
+
|
|
472
|
+
let dim = 8;
|
|
473
|
+
let max_nodes = 100;
|
|
474
|
+
let node_count = 25;
|
|
475
|
+
|
|
476
|
+
let store = InMemoryVectorStore::<f32>::new(dim, max_nodes);
|
|
477
|
+
for i in 0..node_count
|
|
478
|
+
{
|
|
479
|
+
let v = (0..dim).map(|j| (i * 100 + j) as f32).collect::<Vec<_>>();
|
|
480
|
+
store.set(NodeId::new(i as u32), v.as_slice()).unwrap();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
{
|
|
484
|
+
let mut f = std::fs::File::create(&path).unwrap();
|
|
485
|
+
store.save_to(&mut f, node_count).unwrap();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
let (loaded, loaded_count) = {
|
|
489
|
+
let mut f = std::fs::File::open(&path).unwrap();
|
|
490
|
+
InMemoryVectorStore::<f32>::load_from(&mut f).unwrap()
|
|
491
|
+
};
|
|
492
|
+
assert_eq!(loaded_count, node_count);
|
|
493
|
+
assert_eq!(loaded.dim(), dim);
|
|
494
|
+
assert_eq!(loaded.max_nodes(), max_nodes);
|
|
495
|
+
|
|
496
|
+
for i in 0..node_count
|
|
497
|
+
{
|
|
498
|
+
let got = loaded.vector(NodeId::new(i as u32)).unwrap();
|
|
499
|
+
let got = got.view();
|
|
500
|
+
let expected = (0..dim).map(|j| (i * 100 + j) as f32).collect::<Vec<_>>();
|
|
501
|
+
assert_eq!(got, expected.as_slice());
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
#[test]
|
|
506
|
+
fn qi8_vector_store_save_load_roundtrip()
|
|
507
|
+
{
|
|
508
|
+
let dir = tempdir().unwrap();
|
|
509
|
+
let path = dir.path().join("vectors_qi8.bin");
|
|
510
|
+
|
|
511
|
+
let dim = 16;
|
|
512
|
+
let max_nodes = 100;
|
|
513
|
+
let node_count = 10;
|
|
514
|
+
|
|
515
|
+
let store = InMemoryQi8VectorStore::new(dim, max_nodes);
|
|
516
|
+
for i in 0..node_count
|
|
517
|
+
{
|
|
518
|
+
let data = (0..dim).map(|j| (i * 3 + j) as i8).collect::<Vec<_>>();
|
|
519
|
+
store
|
|
520
|
+
.set(
|
|
521
|
+
NodeId::new(i as u32),
|
|
522
|
+
Qi8Ref {
|
|
523
|
+
data: data.as_slice(),
|
|
524
|
+
scale: 0.01 * (i as f32 + 1.0),
|
|
525
|
+
zero_point: -3,
|
|
526
|
+
},
|
|
527
|
+
)
|
|
528
|
+
.unwrap();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
{
|
|
532
|
+
let mut f = std::fs::File::create(&path).unwrap();
|
|
533
|
+
store.save_to(&mut f, node_count).unwrap();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let (loaded, loaded_count) = {
|
|
537
|
+
let mut f = std::fs::File::open(&path).unwrap();
|
|
538
|
+
InMemoryQi8VectorStore::load_from(&mut f).unwrap()
|
|
539
|
+
};
|
|
540
|
+
assert_eq!(loaded_count, node_count);
|
|
541
|
+
assert_eq!(loaded.dim(), dim);
|
|
542
|
+
assert_eq!(loaded.max_nodes(), max_nodes);
|
|
543
|
+
|
|
544
|
+
for i in 0..node_count
|
|
545
|
+
{
|
|
546
|
+
let got = loaded.vector(NodeId::new(i as u32)).unwrap();
|
|
547
|
+
let got = got.view();
|
|
548
|
+
assert_eq!(got.len(), dim);
|
|
549
|
+
assert_eq!(got.zero_point, -3);
|
|
550
|
+
assert_relative_eq!(got.scale, 0.01 * (i as f32 + 1.0), epsilon = 1e-6);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
#[test]
|
|
555
|
+
fn save_errors_on_missing_vector()
|
|
556
|
+
{
|
|
557
|
+
let dir = tempdir().unwrap();
|
|
558
|
+
let path = dir.path().join("vectors.bin");
|
|
559
|
+
|
|
560
|
+
let store = InMemoryVectorStore::<f32>::new(4, 10);
|
|
561
|
+
store.set(NodeId::new(0), &[1.0, 2.0, 3.0, 4.0]).unwrap();
|
|
562
|
+
let err = {
|
|
563
|
+
let mut f = std::fs::File::create(&path).unwrap();
|
|
564
|
+
store.save_to(&mut f, 2).unwrap_err()
|
|
565
|
+
};
|
|
566
|
+
assert!(matches!(err, Error::MissingVector));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
pub mod error;
|
|
2
|
+
pub mod graph_store;
|
|
3
|
+
pub mod id;
|
|
4
|
+
pub mod implementation;
|
|
5
|
+
pub(crate) mod kernels;
|
|
6
|
+
pub mod metric;
|
|
7
|
+
pub mod scalar;
|
|
8
|
+
pub mod simple_store;
|
|
9
|
+
pub mod store;
|
|
10
|
+
pub mod vector;
|
|
11
|
+
pub mod vectors;
|
|
12
|
+
|
|
13
|
+
pub use error::Error;
|
|
14
|
+
pub use error::Result;
|
|
15
|
+
pub use graph_store::GraphStoreRead;
|
|
16
|
+
pub use graph_store::GraphStoreWrite;
|
|
17
|
+
pub use id::NodeId;
|
|
18
|
+
pub use implementation::HnswConfig;
|
|
19
|
+
pub use implementation::HnswIndex;
|
|
20
|
+
pub use implementation::SearchHit;
|
|
21
|
+
pub use implementation::SetOutcome;
|
|
22
|
+
pub use metric::Cosine;
|
|
23
|
+
pub use metric::CosineQi8;
|
|
24
|
+
pub use metric::InnerProduct;
|
|
25
|
+
pub use metric::InnerProductQi8;
|
|
26
|
+
pub use metric::L2Qi8;
|
|
27
|
+
pub use metric::Metric;
|
|
28
|
+
pub use metric::L2;
|
|
29
|
+
pub use scalar::Dtype;
|
|
30
|
+
pub use scalar::Scalar;
|
|
31
|
+
pub use simple_store::SimpleGraphStore;
|
|
32
|
+
pub use store::VectorEncoding;
|
|
33
|
+
pub use vector::Dense;
|
|
34
|
+
pub use vector::Qi8;
|
|
35
|
+
pub use vector::Qi8Ref;
|
|
36
|
+
pub use vector::VectorFamily;
|
|
37
|
+
pub use vector::VectorRef;
|
|
38
|
+
pub use vector::VectorView;
|
|
39
|
+
pub use vectors::InMemoryQi8VectorStore;
|
|
40
|
+
pub use vectors::InMemoryVectorStore;
|
|
41
|
+
pub use vectors::VectorStore;
|
|
42
|
+
pub use vectors::VectorStoreMut;
|
data/ext/gqlitedb/Cargo.toml
CHANGED
|
@@ -15,7 +15,7 @@ crate-type = ["cdylib", "lib"]
|
|
|
15
15
|
default = ["capi", "redb"]
|
|
16
16
|
_backtrace = []
|
|
17
17
|
capi = []
|
|
18
|
-
redb = ["dep:redb", "dep:redb2"]
|
|
18
|
+
redb = ["dep:redb", "dep:redb2", "dep:db-index"]
|
|
19
19
|
_pgrx = ["dep:pgrx"]
|
|
20
20
|
_pg13 = ["pgrx/pg13"]
|
|
21
21
|
_pg14 = ["pgrx/pg14"]
|
|
@@ -23,41 +23,51 @@ _pg15 = ["pgrx/pg15"]
|
|
|
23
23
|
_pg16 = ["pgrx/pg16"]
|
|
24
24
|
_pg17 = ["pgrx/pg17"]
|
|
25
25
|
postgres = ["dep:postgres", "dep:askama"]
|
|
26
|
-
sqlite = ["dep:rusqlite", "dep:askama"]
|
|
27
|
-
|
|
26
|
+
sqlite = ["dep:rusqlite", "dep:askama", "dep:sqlite-vec"]
|
|
27
|
+
all-backends = ["postgres", "redb", "sqlite"]
|
|
28
|
+
_cmake = ["all-backends"]
|
|
28
29
|
|
|
29
30
|
[dependencies]
|
|
30
31
|
graphcore = { workspace = true }
|
|
32
|
+
gqlparser = { workspace = true }
|
|
31
33
|
|
|
32
34
|
askama = { workspace = true, optional = true }
|
|
33
|
-
ccutils = { workspace = true, features = ["alias", "pool", "sync"] }
|
|
34
|
-
ciborium =
|
|
35
|
+
ccutils = { workspace = true, features = ["alias", "pool", "sync", "log"] }
|
|
36
|
+
ciborium.workspace = true
|
|
37
|
+
db-index = { workspace = true, optional = true }
|
|
38
|
+
indexmap = { version = "2", features = ["serde"] }
|
|
35
39
|
itertools = { workspace = true }
|
|
40
|
+
log = { workspace = true }
|
|
36
41
|
pest = "2"
|
|
37
42
|
pest_derive = "2"
|
|
38
|
-
pgrx = { version = "0.
|
|
43
|
+
pgrx = { version = "0.18", optional = true }
|
|
39
44
|
postgres = { version = "0.19", optional = true, features = [
|
|
40
45
|
"with-uuid-1",
|
|
41
46
|
"with-serde_json-1",
|
|
42
47
|
] }
|
|
43
|
-
rand = "0.
|
|
48
|
+
rand = "0.10"
|
|
44
49
|
redb = { version = "3", optional = true }
|
|
45
50
|
redb2 = { version = "2", optional = true, package = "redb" }
|
|
46
51
|
rusqlite = { workspace = true, optional = true, features = [
|
|
47
52
|
"functions",
|
|
48
53
|
"uuid",
|
|
49
54
|
] }
|
|
55
|
+
sqlite-vec = { version = "0.1", optional = true }
|
|
56
|
+
rustc-hash = "2"
|
|
50
57
|
serde = { workspace = true }
|
|
51
58
|
serde_json = "1"
|
|
59
|
+
substring = "1"
|
|
52
60
|
thiserror = { workspace = true }
|
|
53
61
|
uuid = { workspace = true }
|
|
54
|
-
|
|
62
|
+
zerocopy = { version = "0.8", features = ["derive"] }
|
|
63
|
+
smart-default = "0.7.1"
|
|
55
64
|
|
|
56
65
|
[dev-dependencies]
|
|
57
66
|
ccutils = { workspace = true, features = ["alias", "temporary"] }
|
|
58
|
-
divan =
|
|
67
|
+
divan.workspace = true
|
|
59
68
|
iai-callgrind = { version = "0.16" }
|
|
60
69
|
regex = "1"
|
|
70
|
+
serial_test = "3"
|
|
61
71
|
|
|
62
72
|
[target.'cfg(not(windows))'.dev-dependencies]
|
|
63
73
|
pgtemp = "0.7"
|