gqlite 1.5.0 → 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 +12 -6
- 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 -8
- 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 +20 -24
- data/ext/gqlitedb/src/compiler/expression_analyser.rs +28 -20
- data/ext/gqlitedb/src/compiler/variables_manager.rs +104 -38
- data/ext/gqlitedb/src/compiler.rs +506 -227
- data/ext/gqlitedb/src/connection.rs +151 -11
- data/ext/gqlitedb/src/consts.rs +1 -2
- data/ext/gqlitedb/src/error.rs +123 -61
- data/ext/gqlitedb/src/functions/common.rs +64 -0
- data/ext/gqlitedb/src/functions/containers.rs +2 -46
- data/ext/gqlitedb/src/functions/edge.rs +1 -1
- data/ext/gqlitedb/src/functions/math.rs +87 -15
- data/ext/gqlitedb/src/functions/node.rs +1 -1
- data/ext/gqlitedb/src/functions/path.rs +48 -11
- data/ext/gqlitedb/src/functions/scalar.rs +47 -4
- data/ext/gqlitedb/src/functions/string.rs +123 -1
- data/ext/gqlitedb/src/functions/value.rs +1 -1
- data/ext/gqlitedb/src/functions.rs +165 -28
- 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/parser/gql.pest +1 -1
- 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 +742 -16
- 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 +576 -14
- data/ext/gqlitedb/src/store/vector_extract.rs +56 -0
- data/ext/gqlitedb/src/store.rs +140 -29
- 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 +93 -28
- 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,14 +3,29 @@ 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
|
-
sqlbase::{SqlBindingValue, SqlResultValue},
|
|
9
9
|
TransactionBoxable,
|
|
10
|
+
sqlbase::{SqlBindingValue, SqlResultValue},
|
|
10
11
|
},
|
|
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;
|
|
@@ -84,14 +99,12 @@ impl Drop for TransactionBase
|
|
|
84
99
|
fn drop(&mut self)
|
|
85
100
|
{
|
|
86
101
|
if *self.active.borrow()
|
|
102
|
+
&& let Err(e) = self.connection.execute("ROLLBACK", ())
|
|
87
103
|
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
e
|
|
93
|
-
);
|
|
94
|
-
}
|
|
104
|
+
log::error!(
|
|
105
|
+
"Rollback failed with error {:?}, future use of the connection are likely to fail.",
|
|
106
|
+
e
|
|
107
|
+
);
|
|
95
108
|
}
|
|
96
109
|
}
|
|
97
110
|
}
|
|
@@ -135,7 +148,7 @@ impl super::WriteTransaction for WriteTransaction
|
|
|
135
148
|
}
|
|
136
149
|
}
|
|
137
150
|
|
|
138
|
-
trait GetConnection
|
|
151
|
+
pub(crate) trait GetConnection
|
|
139
152
|
{
|
|
140
153
|
fn get_connection(&self) -> &rusqlite::Connection;
|
|
141
154
|
}
|
|
@@ -277,6 +290,16 @@ mod templates
|
|
|
277
290
|
// ___) | || (_) | | | __/
|
|
278
291
|
// |____/ \__\___/|_| \___|
|
|
279
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
|
+
|
|
280
303
|
type TransactionBox = store::TransactionBox<ReadTransaction, WriteTransaction>;
|
|
281
304
|
|
|
282
305
|
pub(crate) struct Store
|
|
@@ -292,7 +315,11 @@ impl Store
|
|
|
292
315
|
{
|
|
293
316
|
let path: PathBuf = path.as_ref().into();
|
|
294
317
|
let connection = Pool::new(
|
|
295
|
-
move ||
|
|
318
|
+
move || {
|
|
319
|
+
init_sqlite_vec();
|
|
320
|
+
let conn = rusqlite::Connection::open(&path)?;
|
|
321
|
+
Ok(conn)
|
|
322
|
+
},
|
|
296
323
|
pool::Options::default().minimum_pool_size(1).pool_size(3),
|
|
297
324
|
)?;
|
|
298
325
|
let s = Self { connection };
|
|
@@ -304,10 +331,12 @@ impl Store
|
|
|
304
331
|
let id = uuid::Uuid::new_v4().as_u128();
|
|
305
332
|
let connection = Pool::new(
|
|
306
333
|
move || {
|
|
307
|
-
|
|
334
|
+
init_sqlite_vec();
|
|
335
|
+
let conn = rusqlite::Connection::open_with_flags(
|
|
308
336
|
format!("file:{}?mode=memory&cache=shared", id),
|
|
309
337
|
rusqlite::OpenFlags::default(),
|
|
310
|
-
)
|
|
338
|
+
)?;
|
|
339
|
+
Ok(conn)
|
|
311
340
|
},
|
|
312
341
|
pool::Options::default().minimum_pool_size(1).pool_size(3),
|
|
313
342
|
)?;
|
|
@@ -329,6 +358,69 @@ impl Store
|
|
|
329
358
|
}
|
|
330
359
|
match (from.major, from.minor)
|
|
331
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
|
+
}
|
|
332
424
|
(1, 0) | (1, 1) =>
|
|
333
425
|
{
|
|
334
426
|
// uuid function is needed for upgrade
|
|
@@ -390,6 +482,10 @@ impl super::sqlbase::SqlMetaDataQueries for Store
|
|
|
390
482
|
{
|
|
391
483
|
Ok(include_str!("../../templates/sql/sqlite/metadata_set.sql").to_string())
|
|
392
484
|
}
|
|
485
|
+
fn metadata_delete_query() -> Result<String>
|
|
486
|
+
{
|
|
487
|
+
Ok(include_str!("../../templates/sql/sqlite/metadata_delete.sql").to_string())
|
|
488
|
+
}
|
|
393
489
|
}
|
|
394
490
|
|
|
395
491
|
impl super::sqlbase::SqlStore for Store
|
|
@@ -518,6 +614,472 @@ impl super::sqlbase::SqlStore for Store
|
|
|
518
614
|
|row| Ok(f(row)),
|
|
519
615
|
)?
|
|
520
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
|
+
}
|
|
521
1083
|
}
|
|
522
1084
|
|
|
523
1085
|
impl super::sqlbase::SqlQueries for Store
|
|
@@ -578,7 +1140,7 @@ impl super::sqlbase::SqlQueries for Store
|
|
|
578
1140
|
}
|
|
579
1141
|
|
|
580
1142
|
fn node_delete_query(graph_name: impl AsRef<str>, keys: impl AsRef<Vec<String>>)
|
|
581
|
-
|
|
1143
|
+
-> Result<String>
|
|
582
1144
|
{
|
|
583
1145
|
Ok(
|
|
584
1146
|
templates::NodeDelete {
|
|
@@ -627,7 +1189,7 @@ impl super::sqlbase::SqlQueries for Store
|
|
|
627
1189
|
)
|
|
628
1190
|
}
|
|
629
1191
|
fn edge_delete_query(graph_name: impl AsRef<str>, keys: impl AsRef<Vec<String>>)
|
|
630
|
-
|
|
1192
|
+
-> Result<String>
|
|
631
1193
|
{
|
|
632
1194
|
Ok(
|
|
633
1195
|
templates::EdgeDelete {
|