gqlite 1.5.1 → 1.8.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
|
@@ -3,6 +3,7 @@ use std::{cell::RefCell, path::PathBuf};
|
|
|
3
3
|
use ccutils::pool::{self, Pool};
|
|
4
4
|
|
|
5
5
|
use crate::{
|
|
6
|
+
error::StoreError,
|
|
6
7
|
prelude::*,
|
|
7
8
|
store::{
|
|
8
9
|
TransactionBoxable,
|
|
@@ -11,6 +12,20 @@ use crate::{
|
|
|
11
12
|
utils::hex,
|
|
12
13
|
};
|
|
13
14
|
|
|
15
|
+
// Initialize sqlite-vec extension
|
|
16
|
+
fn init_sqlite_vec()
|
|
17
|
+
{
|
|
18
|
+
static INIT: std::sync::Once = std::sync::Once::new();
|
|
19
|
+
|
|
20
|
+
INIT.call_once(|| unsafe {
|
|
21
|
+
use rusqlite::ffi::sqlite3_auto_extension;
|
|
22
|
+
use sqlite_vec::sqlite3_vec_init;
|
|
23
|
+
|
|
24
|
+
#[allow(clippy::missing_transmute_annotations)]
|
|
25
|
+
sqlite3_auto_extension(Some(std::mem::transmute(sqlite3_vec_init as *const ())));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
ccutils::assert_impl_all!(Store: Sync, Send);
|
|
15
30
|
|
|
16
31
|
use askama::Template;
|
|
@@ -86,7 +101,7 @@ impl Drop for TransactionBase
|
|
|
86
101
|
if *self.active.borrow()
|
|
87
102
|
&& let Err(e) = self.connection.execute("ROLLBACK", ())
|
|
88
103
|
{
|
|
89
|
-
|
|
104
|
+
log::error!(
|
|
90
105
|
"Rollback failed with error {:?}, future use of the connection are likely to fail.",
|
|
91
106
|
e
|
|
92
107
|
);
|
|
@@ -133,7 +148,7 @@ impl super::WriteTransaction for WriteTransaction
|
|
|
133
148
|
}
|
|
134
149
|
}
|
|
135
150
|
|
|
136
|
-
trait GetConnection
|
|
151
|
+
pub(crate) trait GetConnection
|
|
137
152
|
{
|
|
138
153
|
fn get_connection(&self) -> &rusqlite::Connection;
|
|
139
154
|
}
|
|
@@ -275,6 +290,16 @@ mod templates
|
|
|
275
290
|
// ___) | || (_) | | | __/
|
|
276
291
|
// |____/ \__\___/|_| \___|
|
|
277
292
|
|
|
293
|
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
|
294
|
+
struct VectorIndexMetadata
|
|
295
|
+
{
|
|
296
|
+
name: String,
|
|
297
|
+
label: Option<String>,
|
|
298
|
+
property: Option<String>,
|
|
299
|
+
dimension: usize,
|
|
300
|
+
metric: store::VectorMetric,
|
|
301
|
+
}
|
|
302
|
+
|
|
278
303
|
type TransactionBox = store::TransactionBox<ReadTransaction, WriteTransaction>;
|
|
279
304
|
|
|
280
305
|
pub(crate) struct Store
|
|
@@ -290,7 +315,11 @@ impl Store
|
|
|
290
315
|
{
|
|
291
316
|
let path: PathBuf = path.as_ref().into();
|
|
292
317
|
let connection = Pool::new(
|
|
293
|
-
move ||
|
|
318
|
+
move || {
|
|
319
|
+
init_sqlite_vec();
|
|
320
|
+
let conn = rusqlite::Connection::open(&path)?;
|
|
321
|
+
Ok(conn)
|
|
322
|
+
},
|
|
294
323
|
pool::Options::default().minimum_pool_size(1).pool_size(3),
|
|
295
324
|
)?;
|
|
296
325
|
let s = Self { connection };
|
|
@@ -302,10 +331,12 @@ impl Store
|
|
|
302
331
|
let id = uuid::Uuid::new_v4().as_u128();
|
|
303
332
|
let connection = Pool::new(
|
|
304
333
|
move || {
|
|
305
|
-
|
|
334
|
+
init_sqlite_vec();
|
|
335
|
+
let conn = rusqlite::Connection::open_with_flags(
|
|
306
336
|
format!("file:{}?mode=memory&cache=shared", id),
|
|
307
337
|
rusqlite::OpenFlags::default(),
|
|
308
|
-
)
|
|
338
|
+
)?;
|
|
339
|
+
Ok(conn)
|
|
309
340
|
},
|
|
310
341
|
pool::Options::default().minimum_pool_size(1).pool_size(3),
|
|
311
342
|
)?;
|
|
@@ -327,6 +358,69 @@ impl Store
|
|
|
327
358
|
}
|
|
328
359
|
match (from.major, from.minor)
|
|
329
360
|
{
|
|
361
|
+
(1, 2) =>
|
|
362
|
+
{
|
|
363
|
+
// Migrate vector index metadata from the 1.2 custom-table format:
|
|
364
|
+
// _vector_indices_{graph} (SQL table with columns index_name, label, property, dimension, metric)
|
|
365
|
+
// to the 1.3 unified metadata format:
|
|
366
|
+
// {graph}/vector_indices -> Vec<VectorIndexMetadata>
|
|
367
|
+
|
|
368
|
+
let graphs: Vec<String> = self.get_metadata_value_json_or_default(transaction, "graphs")?;
|
|
369
|
+
|
|
370
|
+
for graph_name in &graphs
|
|
371
|
+
{
|
|
372
|
+
let metadata_table = format!("_vector_indices_{}", graph_name);
|
|
373
|
+
if !self.check_if_table_exists(transaction, &metadata_table)?
|
|
374
|
+
{
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
type LegacyIndexMetadata = (String, Option<String>, Option<String>, usize, String);
|
|
379
|
+
let rows: Vec<LegacyIndexMetadata> = {
|
|
380
|
+
let conn = transaction.get_connection();
|
|
381
|
+
let mut stmt = conn.prepare(&format!(
|
|
382
|
+
"SELECT index_name, label, property, dimension, metric FROM {}",
|
|
383
|
+
metadata_table
|
|
384
|
+
))?;
|
|
385
|
+
stmt
|
|
386
|
+
.query_map([], |row| {
|
|
387
|
+
Ok((
|
|
388
|
+
row.get::<_, String>(0)?,
|
|
389
|
+
row.get::<_, Option<String>>(1)?,
|
|
390
|
+
row.get::<_, Option<String>>(2)?,
|
|
391
|
+
row.get::<_, i32>(3)? as usize,
|
|
392
|
+
row.get::<_, String>(4)?,
|
|
393
|
+
))
|
|
394
|
+
})?
|
|
395
|
+
.collect::<Result<Vec<_>, _>>()?
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
let new_list: Vec<VectorIndexMetadata> = rows
|
|
399
|
+
.into_iter()
|
|
400
|
+
.map(
|
|
401
|
+
|(name, label, property, dimension, metric_str)| VectorIndexMetadata {
|
|
402
|
+
name,
|
|
403
|
+
label,
|
|
404
|
+
property,
|
|
405
|
+
dimension,
|
|
406
|
+
metric: store::VectorMetric::from_debug_str(&metric_str),
|
|
407
|
+
},
|
|
408
|
+
)
|
|
409
|
+
.collect();
|
|
410
|
+
|
|
411
|
+
self.set_metadata_value_json(
|
|
412
|
+
transaction,
|
|
413
|
+
format!("{}/vector_indices", graph_name),
|
|
414
|
+
&new_list,
|
|
415
|
+
)?;
|
|
416
|
+
|
|
417
|
+
transaction
|
|
418
|
+
.get_connection()
|
|
419
|
+
.execute(&format!("DROP TABLE IF EXISTS {}", metadata_table), ())?;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
Ok(())
|
|
423
|
+
}
|
|
330
424
|
(1, 0) | (1, 1) =>
|
|
331
425
|
{
|
|
332
426
|
// uuid function is needed for upgrade
|
|
@@ -388,6 +482,10 @@ impl super::sqlbase::SqlMetaDataQueries for Store
|
|
|
388
482
|
{
|
|
389
483
|
Ok(include_str!("../../templates/sql/sqlite/metadata_set.sql").to_string())
|
|
390
484
|
}
|
|
485
|
+
fn metadata_delete_query() -> Result<String>
|
|
486
|
+
{
|
|
487
|
+
Ok(include_str!("../../templates/sql/sqlite/metadata_delete.sql").to_string())
|
|
488
|
+
}
|
|
391
489
|
}
|
|
392
490
|
|
|
393
491
|
impl super::sqlbase::SqlStore for Store
|
|
@@ -516,6 +614,472 @@ impl super::sqlbase::SqlStore for Store
|
|
|
516
614
|
|row| Ok(f(row)),
|
|
517
615
|
)?
|
|
518
616
|
}
|
|
617
|
+
|
|
618
|
+
fn create_vector_index_impl(
|
|
619
|
+
&self,
|
|
620
|
+
transaction: &mut Self::TransactionBox,
|
|
621
|
+
graph_name: &str,
|
|
622
|
+
spec: &store::VectorIndexSpec,
|
|
623
|
+
) -> Result<()>
|
|
624
|
+
{
|
|
625
|
+
if !spec.dtype.is_float()
|
|
626
|
+
{
|
|
627
|
+
return Err(
|
|
628
|
+
crate::error::StoreError::InvalidFormat(format!(
|
|
629
|
+
"SQLite vector index only supports floating-point dtypes (F32 or F64); got {:?}",
|
|
630
|
+
spec.dtype
|
|
631
|
+
))
|
|
632
|
+
.into(),
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
let vec_table = format!("vec_{}_{}", graph_name, spec.name);
|
|
637
|
+
|
|
638
|
+
// Check if index already exists
|
|
639
|
+
let mut indices: Vec<VectorIndexMetadata> = self
|
|
640
|
+
.get_metadata_value_json_or_default(transaction, format!("{}/vector_indices", graph_name))?;
|
|
641
|
+
if indices.iter().any(|m| m.name == spec.name)
|
|
642
|
+
{
|
|
643
|
+
return Err(
|
|
644
|
+
crate::error::StoreError::DuplicatedVectorIndex {
|
|
645
|
+
index_name: spec.name.clone(),
|
|
646
|
+
}
|
|
647
|
+
.into(),
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
transaction.get_connection().execute(
|
|
652
|
+
&format!(
|
|
653
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS {} USING vec0(embedding float[{}], node_id text)",
|
|
654
|
+
vec_table, spec.dimension
|
|
655
|
+
),
|
|
656
|
+
(),
|
|
657
|
+
)?;
|
|
658
|
+
|
|
659
|
+
// Persist metadata for the new index
|
|
660
|
+
indices.push(VectorIndexMetadata {
|
|
661
|
+
name: spec.name.clone(),
|
|
662
|
+
label: spec.label.clone(),
|
|
663
|
+
property: spec.property.clone(),
|
|
664
|
+
dimension: spec.dimension,
|
|
665
|
+
metric: spec.metric,
|
|
666
|
+
});
|
|
667
|
+
self.set_metadata_value_json(
|
|
668
|
+
transaction,
|
|
669
|
+
format!("{}/vector_indices", graph_name),
|
|
670
|
+
&indices,
|
|
671
|
+
)?;
|
|
672
|
+
|
|
673
|
+
// Retroactively populate the index from existing nodes that match label+property
|
|
674
|
+
if let Some(label) = &spec.label
|
|
675
|
+
&& let Some(property) = &spec.property
|
|
676
|
+
{
|
|
677
|
+
let nodes_table = format!("gqlite_{}_nodes", graph_name);
|
|
678
|
+
let sql = format!(
|
|
679
|
+
"SELECT hex(node_key), json_extract(properties, '$.{prop}') \
|
|
680
|
+
FROM {tbl} \
|
|
681
|
+
WHERE EXISTS ( \
|
|
682
|
+
SELECT 1 FROM json_each(labels) AS lbl WHERE lbl.value = ? \
|
|
683
|
+
) \
|
|
684
|
+
AND json_extract(properties, '$.{prop}') IS NOT NULL",
|
|
685
|
+
prop = property,
|
|
686
|
+
tbl = nodes_table,
|
|
687
|
+
);
|
|
688
|
+
let rows: Vec<(String, String)> = {
|
|
689
|
+
let conn = transaction.get_connection();
|
|
690
|
+
match conn.prepare(&sql)
|
|
691
|
+
{
|
|
692
|
+
Ok(mut stmt) => stmt
|
|
693
|
+
.query_map(rusqlite::params![label], |row| {
|
|
694
|
+
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
|
695
|
+
})?
|
|
696
|
+
.collect::<Result<Vec<_>, _>>()?,
|
|
697
|
+
Err(_) => Vec::new(),
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
for (node_key_hex, embedding_json) in rows
|
|
701
|
+
{
|
|
702
|
+
if let Some(vector) =
|
|
703
|
+
crate::store::vector_extract::extract_f32_vec_from_json(&embedding_json)
|
|
704
|
+
&& !vector.is_empty()
|
|
705
|
+
&& let Ok(uuid_val) = u128::from_str_radix(&node_key_hex, 16)
|
|
706
|
+
{
|
|
707
|
+
let key = graph::Key::new(uuid_val);
|
|
708
|
+
let tensor = value::Tensor::from_f32_slice(&vector);
|
|
709
|
+
let _ = self.upsert_vector_impl(transaction, graph_name, &spec.name, key, &tensor);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
Ok(())
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
fn upsert_vector_impl(
|
|
718
|
+
&self,
|
|
719
|
+
transaction: &mut Self::TransactionBox,
|
|
720
|
+
graph_name: &str,
|
|
721
|
+
index_name: &str,
|
|
722
|
+
node_id: graph::Key,
|
|
723
|
+
tensor: &value::Tensor,
|
|
724
|
+
) -> Result<()>
|
|
725
|
+
{
|
|
726
|
+
let f32_data: std::borrow::Cow<[f32]> = match tensor.dtype()
|
|
727
|
+
{
|
|
728
|
+
value::DType::F32 => match tensor.as_f32_slice()
|
|
729
|
+
{
|
|
730
|
+
Some(s) => std::borrow::Cow::Borrowed(s),
|
|
731
|
+
None => std::borrow::Cow::Owned(tensor.to_f32_vec()),
|
|
732
|
+
},
|
|
733
|
+
value::DType::F64 => std::borrow::Cow::Owned(tensor.to_f32_vec()),
|
|
734
|
+
other =>
|
|
735
|
+
{
|
|
736
|
+
return Err(
|
|
737
|
+
StoreError::InvalidFormat(format!(
|
|
738
|
+
"SQLite vector index only accepts F32 or F64 tensors; got {:?}",
|
|
739
|
+
other
|
|
740
|
+
))
|
|
741
|
+
.into(),
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
let vec_table = format!("vec_{}_{}", graph_name, index_name);
|
|
747
|
+
let indices: Vec<VectorIndexMetadata> = self
|
|
748
|
+
.get_metadata_value_json_or_default(transaction, format!("{}/vector_indices", graph_name))?;
|
|
749
|
+
let dimension = indices
|
|
750
|
+
.iter()
|
|
751
|
+
.find(|m| m.name == index_name)
|
|
752
|
+
.map(|m| m.dimension)
|
|
753
|
+
.ok_or_else(|| {
|
|
754
|
+
StoreError::InvalidFormat(format!(
|
|
755
|
+
"vector index '{}' does not exist in graph '{}'",
|
|
756
|
+
index_name, graph_name
|
|
757
|
+
))
|
|
758
|
+
})?;
|
|
759
|
+
|
|
760
|
+
if f32_data.len() != dimension
|
|
761
|
+
{
|
|
762
|
+
return Err(
|
|
763
|
+
StoreError::InvalidFormat(format!(
|
|
764
|
+
"Vector dimension {} does not match index dimension {}",
|
|
765
|
+
f32_data.len(),
|
|
766
|
+
dimension
|
|
767
|
+
))
|
|
768
|
+
.into(),
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
let vector_bytes: Vec<u8> = f32_data.iter().flat_map(|f| f.to_le_bytes()).collect();
|
|
773
|
+
|
|
774
|
+
let node_id_hex = hex(node_id);
|
|
775
|
+
|
|
776
|
+
transaction.get_connection().execute(
|
|
777
|
+
&format!("DELETE FROM {} WHERE node_id = ?", vec_table),
|
|
778
|
+
rusqlite::params![&node_id_hex],
|
|
779
|
+
)?;
|
|
780
|
+
|
|
781
|
+
transaction.get_connection().execute(
|
|
782
|
+
&format!(
|
|
783
|
+
"INSERT INTO {} (embedding, node_id) VALUES (?, ?)",
|
|
784
|
+
vec_table
|
|
785
|
+
),
|
|
786
|
+
rusqlite::params![&vector_bytes[..], &node_id_hex],
|
|
787
|
+
)?;
|
|
788
|
+
|
|
789
|
+
Ok(())
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
fn list_vector_indexes_impl(
|
|
793
|
+
&self,
|
|
794
|
+
transaction: &mut Self::TransactionBox,
|
|
795
|
+
graph_name: &str,
|
|
796
|
+
) -> Result<Vec<store::VectorIndexSpec>>
|
|
797
|
+
{
|
|
798
|
+
let indices: Vec<VectorIndexMetadata> = self
|
|
799
|
+
.get_metadata_value_json_or_default(transaction, format!("{}/vector_indices", graph_name))?;
|
|
800
|
+
|
|
801
|
+
let specs = indices
|
|
802
|
+
.into_iter()
|
|
803
|
+
.map(|m| store::VectorIndexSpec {
|
|
804
|
+
name: m.name,
|
|
805
|
+
label: m.label,
|
|
806
|
+
property: m.property,
|
|
807
|
+
dimension: m.dimension,
|
|
808
|
+
metric: m.metric,
|
|
809
|
+
..Default::default()
|
|
810
|
+
})
|
|
811
|
+
.collect();
|
|
812
|
+
|
|
813
|
+
Ok(specs)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
fn vector_search_impl(
|
|
817
|
+
&self,
|
|
818
|
+
transaction: &mut Self::TransactionBox,
|
|
819
|
+
graph_name: &str,
|
|
820
|
+
index_name: &str,
|
|
821
|
+
query: &value::Tensor,
|
|
822
|
+
k: usize,
|
|
823
|
+
) -> Result<Vec<store::VectorSearchResult>>
|
|
824
|
+
{
|
|
825
|
+
if k == 0
|
|
826
|
+
{
|
|
827
|
+
return Ok(Vec::new());
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
let f32_query: std::borrow::Cow<[f32]> = match query.dtype()
|
|
831
|
+
{
|
|
832
|
+
value::DType::F32 => match query.as_f32_slice()
|
|
833
|
+
{
|
|
834
|
+
Some(s) => std::borrow::Cow::Borrowed(s),
|
|
835
|
+
None => std::borrow::Cow::Owned(query.to_f32_vec()),
|
|
836
|
+
},
|
|
837
|
+
value::DType::F64 => std::borrow::Cow::Owned(query.to_f32_vec()),
|
|
838
|
+
other =>
|
|
839
|
+
{
|
|
840
|
+
return Err(
|
|
841
|
+
StoreError::InvalidFormat(format!(
|
|
842
|
+
"SQLite vector search only accepts F32 or F64 tensors; got {:?}",
|
|
843
|
+
other
|
|
844
|
+
))
|
|
845
|
+
.into(),
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
let vec_table = format!("vec_{}_{}", graph_name, index_name);
|
|
851
|
+
|
|
852
|
+
let indices: Vec<VectorIndexMetadata> = self
|
|
853
|
+
.get_metadata_value_json_or_default(transaction, format!("{}/vector_indices", graph_name))?;
|
|
854
|
+
let dimension = indices
|
|
855
|
+
.iter()
|
|
856
|
+
.find(|m| m.name == index_name)
|
|
857
|
+
.map(|m| m.dimension)
|
|
858
|
+
.ok_or_else(|| {
|
|
859
|
+
StoreError::InvalidFormat(format!(
|
|
860
|
+
"Vector index '{}' does not exist in graph '{}'",
|
|
861
|
+
index_name, graph_name
|
|
862
|
+
))
|
|
863
|
+
})?;
|
|
864
|
+
|
|
865
|
+
if f32_query.len() != dimension
|
|
866
|
+
{
|
|
867
|
+
return Err(
|
|
868
|
+
StoreError::InvalidFormat(format!(
|
|
869
|
+
"Vector dimension {} does not match index dimension {}",
|
|
870
|
+
f32_query.len(),
|
|
871
|
+
dimension
|
|
872
|
+
))
|
|
873
|
+
.into(),
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
let mut check_stmt = transaction
|
|
878
|
+
.get_connection()
|
|
879
|
+
.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?")?;
|
|
880
|
+
let table_exists = check_stmt.exists(rusqlite::params![&vec_table])?;
|
|
881
|
+
|
|
882
|
+
if !table_exists
|
|
883
|
+
{
|
|
884
|
+
return Err(
|
|
885
|
+
StoreError::InvalidFormat(format!(
|
|
886
|
+
"Vector index '{}' does not exist in graph '{}'",
|
|
887
|
+
index_name, graph_name
|
|
888
|
+
))
|
|
889
|
+
.into(),
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
let query_json = format!(
|
|
894
|
+
"[{}]",
|
|
895
|
+
f32_query
|
|
896
|
+
.iter()
|
|
897
|
+
.map(|f| f.to_string())
|
|
898
|
+
.collect::<Vec<_>>()
|
|
899
|
+
.join(", ")
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
let mut stmt = transaction.get_connection().prepare(&format!(
|
|
903
|
+
"SELECT node_id, distance FROM {}
|
|
904
|
+
WHERE embedding MATCH ?
|
|
905
|
+
ORDER BY distance
|
|
906
|
+
LIMIT ?",
|
|
907
|
+
vec_table
|
|
908
|
+
))?;
|
|
909
|
+
|
|
910
|
+
let results = stmt
|
|
911
|
+
.query_map(rusqlite::params![&query_json, k as i64], |row| {
|
|
912
|
+
let node_id_hex: String = row.get(0)?;
|
|
913
|
+
let distance: f32 = row.get(1)?;
|
|
914
|
+
Ok((node_id_hex, distance))
|
|
915
|
+
})?
|
|
916
|
+
.collect::<Result<Vec<_>, _>>()?;
|
|
917
|
+
|
|
918
|
+
let mut search_results = Vec::new();
|
|
919
|
+
for (node_id_hex, distance) in results
|
|
920
|
+
{
|
|
921
|
+
if node_id_hex.len() != 32
|
|
922
|
+
{
|
|
923
|
+
return Err(
|
|
924
|
+
StoreError::InvalidFormat(format!(
|
|
925
|
+
"Invalid node_id encoding: expected 32 hex chars, got {}",
|
|
926
|
+
node_id_hex.len()
|
|
927
|
+
))
|
|
928
|
+
.into(),
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
let key_uuid = u128::from_str_radix(&node_id_hex, 16).map_err(|_| {
|
|
933
|
+
StoreError::InvalidFormat(format!(
|
|
934
|
+
"Invalid hex encoding in node_id: '{}'",
|
|
935
|
+
node_id_hex
|
|
936
|
+
))
|
|
937
|
+
})?;
|
|
938
|
+
let key = graph::Key::new(key_uuid);
|
|
939
|
+
search_results.push(store::VectorSearchResult {
|
|
940
|
+
node_id: key,
|
|
941
|
+
score: distance,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
Ok(search_results)
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
fn drop_vector_index_impl(
|
|
949
|
+
&self,
|
|
950
|
+
transaction: &mut Self::TransactionBox,
|
|
951
|
+
graph_name: &str,
|
|
952
|
+
index_name: &str,
|
|
953
|
+
) -> Result<()>
|
|
954
|
+
{
|
|
955
|
+
let mut indices: Vec<VectorIndexMetadata> = self
|
|
956
|
+
.get_metadata_value_json_or_default(transaction, format!("{}/vector_indices", graph_name))?;
|
|
957
|
+
|
|
958
|
+
if !indices.iter().any(|m| m.name == index_name)
|
|
959
|
+
{
|
|
960
|
+
return Err(
|
|
961
|
+
StoreError::InvalidFormat(format!(
|
|
962
|
+
"Vector index '{}' does not exist in graph '{}'",
|
|
963
|
+
index_name, graph_name
|
|
964
|
+
))
|
|
965
|
+
.into(),
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
let vec_table = format!("vec_{}_{}", graph_name, index_name);
|
|
970
|
+
transaction
|
|
971
|
+
.get_connection()
|
|
972
|
+
.execute(&format!("DROP TABLE IF EXISTS {}", vec_table), ())?;
|
|
973
|
+
|
|
974
|
+
indices.retain(|m| m.name != index_name);
|
|
975
|
+
self.set_metadata_value_json(
|
|
976
|
+
transaction,
|
|
977
|
+
format!("{}/vector_indices", graph_name),
|
|
978
|
+
&indices,
|
|
979
|
+
)?;
|
|
980
|
+
|
|
981
|
+
Ok(())
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
fn drop_graph_vector_indices_impl(
|
|
985
|
+
&self,
|
|
986
|
+
transaction: &mut Self::TransactionBox,
|
|
987
|
+
graph_name: &str,
|
|
988
|
+
) -> Result<()>
|
|
989
|
+
{
|
|
990
|
+
let indices_key = format!("{}/vector_indices", graph_name);
|
|
991
|
+
let indices: Vec<VectorIndexMetadata> =
|
|
992
|
+
self.get_metadata_value_json_or_default(transaction, &indices_key)?;
|
|
993
|
+
|
|
994
|
+
for m in &indices
|
|
995
|
+
{
|
|
996
|
+
let vec_table = format!("vec_{}_{}", graph_name, m.name);
|
|
997
|
+
transaction
|
|
998
|
+
.get_connection()
|
|
999
|
+
.execute(&format!("DROP TABLE IF EXISTS {}", vec_table), ())?;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
self.delete_metadata(transaction, &indices_key)?;
|
|
1003
|
+
Ok(())
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
fn get_vector_indices_for_property_impl(
|
|
1007
|
+
&self,
|
|
1008
|
+
transaction: &mut Self::TransactionBox,
|
|
1009
|
+
graph_name: &str,
|
|
1010
|
+
label: &str,
|
|
1011
|
+
property: &str,
|
|
1012
|
+
) -> Result<Vec<(String, usize, store::VectorMetric)>>
|
|
1013
|
+
{
|
|
1014
|
+
let indices: Vec<VectorIndexMetadata> = self
|
|
1015
|
+
.get_metadata_value_json_or_default(transaction, format!("{}/vector_indices", graph_name))?;
|
|
1016
|
+
|
|
1017
|
+
let result = indices
|
|
1018
|
+
.into_iter()
|
|
1019
|
+
.filter(|m| m.label.as_deref() == Some(label) && m.property.as_deref() == Some(property))
|
|
1020
|
+
.map(|m| (m.name, m.dimension, m.metric))
|
|
1021
|
+
.collect();
|
|
1022
|
+
|
|
1023
|
+
Ok(result)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
fn apply_vector_indices_for_node_impl(
|
|
1027
|
+
&self,
|
|
1028
|
+
transaction: &mut Self::TransactionBox,
|
|
1029
|
+
graph_name: &str,
|
|
1030
|
+
node: &crate::graph::Node,
|
|
1031
|
+
) -> Result<()>
|
|
1032
|
+
{
|
|
1033
|
+
for label in node.labels()
|
|
1034
|
+
{
|
|
1035
|
+
for property in node.properties().keys()
|
|
1036
|
+
{
|
|
1037
|
+
let indices =
|
|
1038
|
+
self.get_vector_indices_for_property_impl(transaction, graph_name, label, property)?;
|
|
1039
|
+
|
|
1040
|
+
for (index_name, _dimension, _metric) in indices
|
|
1041
|
+
{
|
|
1042
|
+
if let Some(val) = node.properties().get(property)
|
|
1043
|
+
&& let Some(vector) = crate::store::vector_extract::extract_f32_vec(val)
|
|
1044
|
+
&& !vector.is_empty()
|
|
1045
|
+
{
|
|
1046
|
+
let tensor = value::Tensor::from_f32_slice(&vector);
|
|
1047
|
+
self
|
|
1048
|
+
.upsert_vector_impl(transaction, graph_name, &index_name, node.key(), &tensor)
|
|
1049
|
+
.ok();
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
Ok(())
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
fn delete_vectors_for_nodes_impl(
|
|
1059
|
+
&self,
|
|
1060
|
+
transaction: &mut Self::TransactionBox,
|
|
1061
|
+
graph_name: &str,
|
|
1062
|
+
node_ids: &[String],
|
|
1063
|
+
) -> Result<()>
|
|
1064
|
+
{
|
|
1065
|
+
let indices: Vec<VectorIndexMetadata> = self
|
|
1066
|
+
.get_metadata_value_json_or_default(transaction, format!("{}/vector_indices", graph_name))
|
|
1067
|
+
.unwrap_or_default();
|
|
1068
|
+
|
|
1069
|
+
for m in &indices
|
|
1070
|
+
{
|
|
1071
|
+
let vec_table = format!("vec_{}_{}", graph_name, m.name);
|
|
1072
|
+
for node_id in node_ids
|
|
1073
|
+
{
|
|
1074
|
+
let _ = transaction.get_connection().execute(
|
|
1075
|
+
&format!("DELETE FROM {} WHERE node_id = ?", vec_table),
|
|
1076
|
+
rusqlite::params![node_id],
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
Ok(())
|
|
1082
|
+
}
|
|
519
1083
|
}
|
|
520
1084
|
|
|
521
1085
|
impl super::sqlbase::SqlQueries for Store
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
use crate::value::Value;
|
|
2
|
+
|
|
3
|
+
/// Extract a flat Vec<f32> from a Value.
|
|
4
|
+
/// Handles Value::Tensor (new) and Value::Array (legacy).
|
|
5
|
+
pub(crate) fn extract_f32_vec(value: &Value) -> Option<Vec<f32>>
|
|
6
|
+
{
|
|
7
|
+
match value
|
|
8
|
+
{
|
|
9
|
+
Value::Tensor(t) => Some(t.to_f32_vec()),
|
|
10
|
+
Value::Array(arr) => arr
|
|
11
|
+
.iter()
|
|
12
|
+
.map(|v| match v
|
|
13
|
+
{
|
|
14
|
+
Value::Float(f) => Some(*f as f32),
|
|
15
|
+
Value::Integer(i) => Some(*i as f32),
|
|
16
|
+
_ => None,
|
|
17
|
+
})
|
|
18
|
+
.collect(),
|
|
19
|
+
_ => None,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "_pgrx"))]
|
|
24
|
+
fn extract_f32_from_json_value(val: &serde_json::Value) -> Option<Vec<f32>>
|
|
25
|
+
{
|
|
26
|
+
match val
|
|
27
|
+
{
|
|
28
|
+
serde_json::Value::Array(arr) => arr.iter().map(|e| e.as_f64().map(|f| f as f32)).collect(),
|
|
29
|
+
serde_json::Value::Object(map)
|
|
30
|
+
if map.get("__gql_type").and_then(|v| v.as_str()) == Some("tensor") =>
|
|
31
|
+
{
|
|
32
|
+
let arr = map.get("data")?.as_array()?;
|
|
33
|
+
arr.iter().map(|v| v.as_f64().map(|f| f as f32)).collect()
|
|
34
|
+
}
|
|
35
|
+
_ => None,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Extract a flat Vec<f32> from a raw JSON string.
|
|
40
|
+
/// Handles:
|
|
41
|
+
/// - legacy form: plain JSON array [1.0, 0.0, ...]
|
|
42
|
+
/// - new form: {"__gql_type":"tensor","dtype":"F32","shape":[...],"data":[...]}
|
|
43
|
+
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "_pgrx"))]
|
|
44
|
+
pub(crate) fn extract_f32_vec_from_json(json_str: &str) -> Option<Vec<f32>>
|
|
45
|
+
{
|
|
46
|
+
let val: serde_json::Value = serde_json::from_str(json_str).ok()?;
|
|
47
|
+
extract_f32_from_json_value(&val)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Same as extract_f32_vec_from_json but for callers that already hold a serde_json::Value
|
|
51
|
+
/// (PostgreSQL retroactive population, where properties is a JSONB column).
|
|
52
|
+
#[cfg(any(feature = "postgres", feature = "_pgrx"))]
|
|
53
|
+
pub(crate) fn extract_f32_vec_from_serde_value(val: &serde_json::Value) -> Option<Vec<f32>>
|
|
54
|
+
{
|
|
55
|
+
extract_f32_from_json_value(val)
|
|
56
|
+
}
|