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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Cargo.toml +10 -4
  3. data/ext/db-index/Cargo.toml +29 -0
  4. data/ext/db-index/src/hnsw/error.rs +66 -0
  5. data/ext/db-index/src/hnsw/graph_store.rs +272 -0
  6. data/ext/db-index/src/hnsw/id.rs +36 -0
  7. data/ext/db-index/src/hnsw/implementation.rs +1517 -0
  8. data/ext/db-index/src/hnsw/kernels.rs +1228 -0
  9. data/ext/db-index/src/hnsw/metric.rs +244 -0
  10. data/ext/db-index/src/hnsw/scalar.rs +72 -0
  11. data/ext/db-index/src/hnsw/simple_store.rs +140 -0
  12. data/ext/db-index/src/hnsw/store.rs +472 -0
  13. data/ext/db-index/src/hnsw/vector.rs +105 -0
  14. data/ext/db-index/src/hnsw/vectors.rs +568 -0
  15. data/ext/db-index/src/hnsw.rs +42 -0
  16. data/ext/db-index/src/lib.rs +3 -0
  17. data/ext/gqlitedb/Cargo.toml +19 -9
  18. data/ext/gqlitedb/benches/common/pokec.rs +62 -2
  19. data/ext/gqlitedb/benches/pokec_divan.rs +66 -2
  20. data/ext/gqlitedb/benches/pokec_iai.rs +60 -3
  21. data/ext/gqlitedb/release.toml +2 -2
  22. data/ext/gqlitedb/src/aggregators/arithmetic.rs +3 -1
  23. data/ext/gqlitedb/src/aggregators/containers.rs +1 -1
  24. data/ext/gqlitedb/src/aggregators/stats.rs +21 -12
  25. data/ext/gqlitedb/src/capi.rs +1 -1
  26. data/ext/gqlitedb/src/compiler/expression_analyser.rs +28 -20
  27. data/ext/gqlitedb/src/compiler/variables_manager.rs +97 -29
  28. data/ext/gqlitedb/src/compiler.rs +505 -225
  29. data/ext/gqlitedb/src/connection.rs +149 -11
  30. data/ext/gqlitedb/src/consts.rs +1 -2
  31. data/ext/gqlitedb/src/error.rs +123 -61
  32. data/ext/gqlitedb/src/functions/common.rs +39 -2
  33. data/ext/gqlitedb/src/functions/math.rs +8 -3
  34. data/ext/gqlitedb/src/functions/path.rs +48 -11
  35. data/ext/gqlitedb/src/functions/value.rs +1 -1
  36. data/ext/gqlitedb/src/functions.rs +23 -7
  37. data/ext/gqlitedb/src/graph.rs +1 -9
  38. data/ext/gqlitedb/src/interpreter/evaluators.rs +968 -130
  39. data/ext/gqlitedb/src/interpreter/instructions.rs +39 -2
  40. data/ext/gqlitedb/src/lib.rs +5 -4
  41. data/ext/gqlitedb/src/planner.rs +329 -0
  42. data/ext/gqlitedb/src/prelude.rs +3 -3
  43. data/ext/gqlitedb/src/store/pgrx.rs +1 -1
  44. data/ext/gqlitedb/src/store/postgres.rs +735 -7
  45. data/ext/gqlitedb/src/store/redb/hnsw_store.rs +702 -0
  46. data/ext/gqlitedb/src/store/redb/index.rs +274 -0
  47. data/ext/gqlitedb/src/store/redb.rs +1268 -113
  48. data/ext/gqlitedb/src/store/sqlbase/sqlmetadata.rs +28 -0
  49. data/ext/gqlitedb/src/store/sqlbase/sqlstore.rs +103 -0
  50. data/ext/gqlitedb/src/store/sqlbase.rs +146 -16
  51. data/ext/gqlitedb/src/store/sqlite.rs +569 -5
  52. data/ext/gqlitedb/src/store/vector_extract.rs +56 -0
  53. data/ext/gqlitedb/src/store.rs +123 -3
  54. data/ext/gqlitedb/src/tests/compiler.rs +207 -10
  55. data/ext/gqlitedb/src/tests/connection/postgres.rs +38 -3
  56. data/ext/gqlitedb/src/tests/connection/redb.rs +23 -0
  57. data/ext/gqlitedb/src/tests/connection/sqlite.rs +31 -0
  58. data/ext/gqlitedb/src/tests/connection.rs +61 -1
  59. data/ext/gqlitedb/src/tests/evaluators.rs +162 -7
  60. data/ext/gqlitedb/src/tests/parser.rs +54 -23
  61. data/ext/gqlitedb/src/tests/planner.rs +511 -0
  62. data/ext/gqlitedb/src/tests/store/postgres.rs +7 -0
  63. data/ext/gqlitedb/src/tests/store/redb.rs +8 -0
  64. data/ext/gqlitedb/src/tests/store/sqlite.rs +8 -0
  65. data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +182 -0
  66. data/ext/gqlitedb/src/tests/store/vector_index/redb.rs +386 -0
  67. data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +166 -0
  68. data/ext/gqlitedb/src/tests/store/vector_index.rs +2313 -0
  69. data/ext/gqlitedb/src/tests/store.rs +78 -14
  70. data/ext/gqlitedb/src/tests/templates/ast.rs +92 -16
  71. data/ext/gqlitedb/src/tests/templates/programs.rs +14 -7
  72. data/ext/gqlitedb/src/tests.rs +15 -9
  73. data/ext/gqlitedb/src/utils.rs +6 -1
  74. data/ext/gqlitedb/src/value/compare.rs +61 -3
  75. data/ext/gqlitedb/src/value.rs +136 -7
  76. data/ext/gqlitedb/templates/sql/postgres/metadata_delete.sql +1 -0
  77. data/ext/gqlitedb/templates/sql/postgres/node_select.sql +1 -1
  78. data/ext/gqlitedb/templates/sql/sqlite/metadata_delete.sql +1 -0
  79. data/ext/gqliterb/src/lib.rs +53 -8
  80. data/ext/gqlparser/Cargo.toml +25 -0
  81. data/ext/gqlparser/README.MD +9 -0
  82. data/ext/gqlparser/benches/pokec_divan.rs +34 -0
  83. data/ext/gqlparser/src/common.rs +69 -0
  84. data/ext/gqlparser/src/gqls/ast.rs +69 -0
  85. data/ext/gqlparser/src/gqls/constraint.rs +79 -0
  86. data/ext/gqlparser/src/gqls/error.rs +22 -0
  87. data/ext/gqlparser/src/gqls/parser.rs +813 -0
  88. data/ext/gqlparser/src/gqls/prelude.rs +8 -0
  89. data/ext/gqlparser/src/gqls/properties.rs +115 -0
  90. data/ext/gqlparser/src/gqls/resolve.rs +207 -0
  91. data/ext/gqlparser/src/gqls.rs +265 -0
  92. data/ext/gqlparser/src/lib.rs +7 -0
  93. data/ext/gqlparser/src/oc/ast.rs +680 -0
  94. data/ext/gqlparser/src/oc/error.rs +172 -0
  95. data/ext/gqlparser/src/oc/lexer.rs +429 -0
  96. data/ext/gqlparser/src/oc/parser/tests.rs +2284 -0
  97. data/ext/gqlparser/src/oc/parser.rs +2005 -0
  98. data/ext/gqlparser/src/oc.rs +33 -0
  99. data/ext/gqlparser/src/prelude.rs +3 -0
  100. data/ext/graphcore/Cargo.toml +1 -0
  101. data/ext/graphcore/src/error.rs +26 -0
  102. data/ext/graphcore/src/graph.rs +177 -51
  103. data/ext/graphcore/src/lib.rs +4 -2
  104. data/ext/graphcore/src/open_cypher.rs +12 -0
  105. data/ext/graphcore/src/table.rs +50 -2
  106. data/ext/graphcore/src/timestamp.rs +127 -104
  107. data/ext/graphcore/src/value/tensor.rs +739 -0
  108. data/ext/graphcore/src/value/value_map.rs +1 -1
  109. data/ext/graphcore/src/value.rs +343 -19
  110. metadata +90 -22
  111. data/ext/gqlitedb/src/parser/ast.rs +0 -604
  112. data/ext/gqlitedb/src/parser/parser_impl.rs +0 -1213
  113. 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;
@@ -0,0 +1,3 @@
1
+ //! db-index is a collection of implemation for key-value databases
2
+
3
+ pub mod hnsw;
@@ -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
- _cmake = ["postgres", "redb", "sqlite"]
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 = "0.2"
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.16", optional = true }
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.9"
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
- substring = "1"
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 = "0.1"
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"