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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Cargo.toml +12 -6
  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 -8
  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 +20 -24
  26. data/ext/gqlitedb/src/compiler/expression_analyser.rs +28 -20
  27. data/ext/gqlitedb/src/compiler/variables_manager.rs +104 -38
  28. data/ext/gqlitedb/src/compiler.rs +506 -227
  29. data/ext/gqlitedb/src/connection.rs +151 -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 +64 -0
  33. data/ext/gqlitedb/src/functions/containers.rs +2 -46
  34. data/ext/gqlitedb/src/functions/edge.rs +1 -1
  35. data/ext/gqlitedb/src/functions/math.rs +87 -15
  36. data/ext/gqlitedb/src/functions/node.rs +1 -1
  37. data/ext/gqlitedb/src/functions/path.rs +48 -11
  38. data/ext/gqlitedb/src/functions/scalar.rs +47 -4
  39. data/ext/gqlitedb/src/functions/string.rs +123 -1
  40. data/ext/gqlitedb/src/functions/value.rs +1 -1
  41. data/ext/gqlitedb/src/functions.rs +165 -28
  42. data/ext/gqlitedb/src/graph.rs +1 -9
  43. data/ext/gqlitedb/src/interpreter/evaluators.rs +968 -130
  44. data/ext/gqlitedb/src/interpreter/instructions.rs +39 -2
  45. data/ext/gqlitedb/src/lib.rs +5 -4
  46. data/ext/gqlitedb/src/parser/gql.pest +1 -1
  47. data/ext/gqlitedb/src/planner.rs +329 -0
  48. data/ext/gqlitedb/src/prelude.rs +3 -3
  49. data/ext/gqlitedb/src/store/pgrx.rs +1 -1
  50. data/ext/gqlitedb/src/store/postgres.rs +742 -16
  51. data/ext/gqlitedb/src/store/redb/hnsw_store.rs +702 -0
  52. data/ext/gqlitedb/src/store/redb/index.rs +274 -0
  53. data/ext/gqlitedb/src/store/redb.rs +1268 -113
  54. data/ext/gqlitedb/src/store/sqlbase/sqlmetadata.rs +28 -0
  55. data/ext/gqlitedb/src/store/sqlbase/sqlstore.rs +103 -0
  56. data/ext/gqlitedb/src/store/sqlbase.rs +146 -16
  57. data/ext/gqlitedb/src/store/sqlite.rs +576 -14
  58. data/ext/gqlitedb/src/store/vector_extract.rs +56 -0
  59. data/ext/gqlitedb/src/store.rs +140 -29
  60. data/ext/gqlitedb/src/tests/compiler.rs +207 -10
  61. data/ext/gqlitedb/src/tests/connection/postgres.rs +38 -3
  62. data/ext/gqlitedb/src/tests/connection/redb.rs +23 -0
  63. data/ext/gqlitedb/src/tests/connection/sqlite.rs +31 -0
  64. data/ext/gqlitedb/src/tests/connection.rs +61 -1
  65. data/ext/gqlitedb/src/tests/evaluators.rs +162 -7
  66. data/ext/gqlitedb/src/tests/parser.rs +54 -23
  67. data/ext/gqlitedb/src/tests/planner.rs +511 -0
  68. data/ext/gqlitedb/src/tests/store/postgres.rs +7 -0
  69. data/ext/gqlitedb/src/tests/store/redb.rs +8 -0
  70. data/ext/gqlitedb/src/tests/store/sqlite.rs +8 -0
  71. data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +182 -0
  72. data/ext/gqlitedb/src/tests/store/vector_index/redb.rs +386 -0
  73. data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +166 -0
  74. data/ext/gqlitedb/src/tests/store/vector_index.rs +2313 -0
  75. data/ext/gqlitedb/src/tests/store.rs +78 -14
  76. data/ext/gqlitedb/src/tests/templates/ast.rs +92 -16
  77. data/ext/gqlitedb/src/tests/templates/programs.rs +14 -7
  78. data/ext/gqlitedb/src/tests.rs +15 -9
  79. data/ext/gqlitedb/src/utils.rs +6 -1
  80. data/ext/gqlitedb/src/value/compare.rs +61 -3
  81. data/ext/gqlitedb/src/value.rs +136 -7
  82. data/ext/gqlitedb/templates/sql/postgres/metadata_delete.sql +1 -0
  83. data/ext/gqlitedb/templates/sql/postgres/node_select.sql +1 -1
  84. data/ext/gqlitedb/templates/sql/sqlite/metadata_delete.sql +1 -0
  85. data/ext/gqliterb/src/lib.rs +53 -8
  86. data/ext/gqlparser/Cargo.toml +25 -0
  87. data/ext/gqlparser/README.MD +9 -0
  88. data/ext/gqlparser/benches/pokec_divan.rs +34 -0
  89. data/ext/gqlparser/src/common.rs +69 -0
  90. data/ext/gqlparser/src/gqls/ast.rs +69 -0
  91. data/ext/gqlparser/src/gqls/constraint.rs +79 -0
  92. data/ext/gqlparser/src/gqls/error.rs +22 -0
  93. data/ext/gqlparser/src/gqls/parser.rs +813 -0
  94. data/ext/gqlparser/src/gqls/prelude.rs +8 -0
  95. data/ext/gqlparser/src/gqls/properties.rs +115 -0
  96. data/ext/gqlparser/src/gqls/resolve.rs +207 -0
  97. data/ext/gqlparser/src/gqls.rs +265 -0
  98. data/ext/gqlparser/src/lib.rs +7 -0
  99. data/ext/gqlparser/src/oc/ast.rs +680 -0
  100. data/ext/gqlparser/src/oc/error.rs +172 -0
  101. data/ext/gqlparser/src/oc/lexer.rs +429 -0
  102. data/ext/gqlparser/src/oc/parser/tests.rs +2284 -0
  103. data/ext/gqlparser/src/oc/parser.rs +2005 -0
  104. data/ext/gqlparser/src/oc.rs +33 -0
  105. data/ext/gqlparser/src/prelude.rs +3 -0
  106. data/ext/graphcore/Cargo.toml +1 -0
  107. data/ext/graphcore/src/error.rs +26 -0
  108. data/ext/graphcore/src/graph.rs +177 -51
  109. data/ext/graphcore/src/lib.rs +4 -2
  110. data/ext/graphcore/src/open_cypher.rs +12 -0
  111. data/ext/graphcore/src/table.rs +50 -2
  112. data/ext/graphcore/src/timestamp.rs +127 -104
  113. data/ext/graphcore/src/value/tensor.rs +739 -0
  114. data/ext/graphcore/src/value/value_map.rs +1 -1
  115. data/ext/graphcore/src/value.rs +343 -19
  116. metadata +93 -28
  117. data/ext/gqlitedb/src/parser/ast.rs +0 -604
  118. data/ext/gqlitedb/src/parser/parser_impl.rs +0 -1213
  119. data/ext/gqlitedb/src/parser.rs +0 -4
@@ -0,0 +1,511 @@
1
+ use gqlparser::oc::ast::*;
2
+
3
+ use crate::{LogicalPlan, ProjectionKind, planner};
4
+
5
+ fn unwrap_filter(plan: &LogicalPlan) -> (&LogicalPlan, &Expression)
6
+ {
7
+ match plan
8
+ {
9
+ LogicalPlan::Filter { source, expression } => (source, expression),
10
+ _ => panic!("expected filter"),
11
+ }
12
+ }
13
+
14
+ fn unwrap_sort(plan: &LogicalPlan) -> (&LogicalPlan, &[OrderByExpression])
15
+ {
16
+ match plan
17
+ {
18
+ LogicalPlan::Sort {
19
+ source,
20
+ expressions,
21
+ } => (source, expressions),
22
+ _ => panic!("expected sort"),
23
+ }
24
+ }
25
+
26
+ fn unwrap_skip(plan: &LogicalPlan) -> (&LogicalPlan, &Expression)
27
+ {
28
+ match plan
29
+ {
30
+ LogicalPlan::Skip { source, expression } => (source, expression),
31
+ _ => panic!("expected skip"),
32
+ }
33
+ }
34
+
35
+ fn unwrap_limit(plan: &LogicalPlan) -> (&LogicalPlan, &Expression)
36
+ {
37
+ match plan
38
+ {
39
+ LogicalPlan::Limit { source, expression } => (source, expression),
40
+ _ => panic!("expected limit"),
41
+ }
42
+ }
43
+
44
+ #[test]
45
+ fn test_plan_match_creates_scan_and_filter()
46
+ {
47
+ let identifiers = VariableIdentifiers::default();
48
+ let node_identifier = identifiers.create_variable_from_name("n");
49
+
50
+ let plan = planner::plan(
51
+ Match {
52
+ patterns: vec![Pattern::Node(NodePattern {
53
+ variable: Some(node_identifier.clone()),
54
+ labels: LabelExpression::String("Person".into()),
55
+ properties: None,
56
+ })],
57
+ where_expression: Some(
58
+ Variable {
59
+ identifier: node_identifier,
60
+ }
61
+ .into(),
62
+ ),
63
+ optional: true,
64
+ search: None,
65
+ }
66
+ .into(),
67
+ )
68
+ .unwrap();
69
+
70
+ let (source, expression) = unwrap_filter(&plan);
71
+ assert!(matches!(expression, Expression::Variable(_)));
72
+ assert!(matches!(
73
+ source,
74
+ LogicalPlan::NodeScan { patterns, optional: true }
75
+ if matches!(
76
+ &patterns[0],
77
+ Pattern::Node(NodePattern {
78
+ labels: LabelExpression::String(label),
79
+ ..
80
+ }) if label == "Person"
81
+ )
82
+ ));
83
+ }
84
+
85
+ #[test]
86
+ fn test_plan_return_creates_projection_modifier_chain()
87
+ {
88
+ let identifiers = VariableIdentifiers::default();
89
+ let node_identifier = identifiers.create_variable_from_name("n");
90
+
91
+ let plan = planner::plan(
92
+ Return {
93
+ all: false,
94
+ expressions: vec![NamedExpression {
95
+ identifier: identifiers.create_variable_from_name("result"),
96
+ expression: Variable {
97
+ identifier: node_identifier.clone(),
98
+ }
99
+ .into(),
100
+ }],
101
+ modifiers: Modifiers {
102
+ skip: Some(Value { value: 2.into() }.into()),
103
+ limit: Some(Value { value: 4.into() }.into()),
104
+ order_by: Some(OrderBy {
105
+ expressions: vec![OrderByExpression {
106
+ asc: false,
107
+ expression: Variable {
108
+ identifier: node_identifier.clone(),
109
+ }
110
+ .into(),
111
+ }],
112
+ }),
113
+ },
114
+ where_expression: Some(
115
+ Variable {
116
+ identifier: node_identifier,
117
+ }
118
+ .into(),
119
+ ),
120
+ }
121
+ .into(),
122
+ )
123
+ .unwrap();
124
+
125
+ let (source, limit) = unwrap_limit(&plan);
126
+ assert!(matches!(limit, Expression::Value(Value { .. })));
127
+
128
+ let (source, skip) = unwrap_skip(source);
129
+ assert!(matches!(skip, Expression::Value(Value { .. })));
130
+
131
+ let (source, order_by) = unwrap_sort(source);
132
+ assert_eq!(order_by.len(), 1);
133
+ assert!(!order_by[0].asc);
134
+
135
+ let (source, expression) = unwrap_filter(source);
136
+ assert!(matches!(expression, Expression::Variable(_)));
137
+
138
+ assert!(matches!(
139
+ source,
140
+ LogicalPlan::Projection {
141
+ kind: ProjectionKind::Return,
142
+ all: false,
143
+ expressions,
144
+ } if expressions.len() == 1
145
+ ));
146
+ }
147
+
148
+ #[test]
149
+ fn test_plan_with_creates_projection()
150
+ {
151
+ let identifiers = VariableIdentifiers::default();
152
+
153
+ let plan = planner::plan(
154
+ With {
155
+ all: true,
156
+ expressions: vec![],
157
+ modifiers: Default::default(),
158
+ where_expression: None,
159
+ }
160
+ .into(),
161
+ )
162
+ .unwrap();
163
+
164
+ assert!(matches!(
165
+ plan,
166
+ LogicalPlan::Projection {
167
+ kind: ProjectionKind::With,
168
+ all: true,
169
+ expressions,
170
+ } if expressions.is_empty()
171
+ ));
172
+
173
+ let _ = identifiers;
174
+ }
175
+
176
+ #[test]
177
+ fn test_plan_maps_direct_statements()
178
+ {
179
+ let identifiers = VariableIdentifiers::default();
180
+ let node_identifier = identifiers.create_variable_from_name("n");
181
+
182
+ assert!(matches!(planner::plan(
183
+ CreateGraph {
184
+ name: "graph".into(),
185
+ if_not_exists: true,
186
+ }
187
+ .into(),
188
+ ).unwrap(),
189
+ LogicalPlan::CreateGraph {
190
+ name,
191
+ if_not_exists: true,
192
+ } if name == "graph"
193
+ ));
194
+
195
+ assert!(matches!(planner::plan(
196
+ DropGraph {
197
+ name: "graph".into(),
198
+ if_exists: true,
199
+ }
200
+ .into(),
201
+ ).unwrap(),
202
+ LogicalPlan::DropGraph {
203
+ name,
204
+ if_exists: true,
205
+ } if name == "graph"
206
+ ));
207
+
208
+ assert!(matches!(planner::plan(
209
+ UseGraph {
210
+ name: "graph".into(),
211
+ }
212
+ .into(),
213
+ ).unwrap(),
214
+ LogicalPlan::UseGraph { name } if name == "graph"
215
+ ));
216
+
217
+ assert!(matches!(planner::plan(
218
+ Create {
219
+ patterns: vec![Pattern::Node(NodePattern {
220
+ variable: Some(node_identifier.clone()),
221
+ labels: LabelExpression::None,
222
+ properties: None,
223
+ })],
224
+ }
225
+ .into(),
226
+ ).unwrap(),
227
+ LogicalPlan::Create { patterns } if patterns.len() == 1
228
+ ));
229
+
230
+ assert!(matches!(planner::plan(
231
+ Call {
232
+ name: "db.labels".into(),
233
+ arguments: vec![Value { value: 1.into() }.into()],
234
+ }
235
+ .into(),
236
+ ).unwrap(),
237
+ LogicalPlan::Call { name, arguments } if name == "db.labels" && arguments.len() == 1
238
+ ));
239
+
240
+ assert!(matches!(
241
+ planner::plan(
242
+ Unwind {
243
+ identifier: identifiers.create_variable_from_name("item"),
244
+ expression: Array {
245
+ array: vec![Value { value: 1.into() }.into()],
246
+ }
247
+ .into(),
248
+ }
249
+ .into(),
250
+ )
251
+ .unwrap(),
252
+ LogicalPlan::Unwind { .. }
253
+ ));
254
+
255
+ assert!(matches!(planner::plan(
256
+ Delete {
257
+ detach: true,
258
+ expressions: vec![
259
+ Variable {
260
+ identifier: node_identifier.clone(),
261
+ }
262
+ .into(),
263
+ ],
264
+ }
265
+ .into(),
266
+ ).unwrap(),
267
+ LogicalPlan::Delete {
268
+ detach: true,
269
+ expressions,
270
+ } if expressions.len() == 1
271
+ ));
272
+
273
+ assert!(matches!(planner::plan(
274
+ Update {
275
+ updates: vec![
276
+ OneUpdate::SetProperty(UpdateProperty {
277
+ target: node_identifier.clone(),
278
+ path: vec!["name".into()],
279
+ expression: Value {
280
+ value: "Alice".into(),
281
+ }
282
+ .into(),
283
+ }),
284
+ OneUpdate::RemoveProperty(RemoveProperty {
285
+ target: node_identifier,
286
+ path: vec!["age".into()],
287
+ }),
288
+ ],
289
+ }
290
+ .into(),
291
+ ).unwrap(),
292
+ LogicalPlan::Update { updates } if updates.len() == 2
293
+ ));
294
+ }
295
+
296
+ #[test]
297
+ fn test_plan_create_vector_index()
298
+ {
299
+ let plan = planner::plan(
300
+ CreateVectorIndex {
301
+ name: "doc_idx".into(),
302
+ label: "Doc".into(),
303
+ property: "embedding".into(),
304
+ dimension: Some(384),
305
+ metric: Some(Metric::Cosine),
306
+ if_not_exists: false,
307
+ }
308
+ .into(),
309
+ )
310
+ .unwrap();
311
+
312
+ assert!(matches!(
313
+ plan,
314
+ LogicalPlan::CreateVectorIndex {
315
+ name,
316
+ label,
317
+ property,
318
+ dimension,
319
+ metric,
320
+ if_not_exists: false,
321
+ }
322
+ if name == "doc_idx"
323
+ && label == "Doc"
324
+ && property == "embedding"
325
+ && dimension == Some(384)
326
+ && metric == Some(Metric::Cosine)
327
+ ));
328
+ }
329
+
330
+ #[test]
331
+ fn test_plan_match_with_search_emits_vector_search()
332
+ {
333
+ let identifiers = VariableIdentifiers::default();
334
+ let node_identifier = identifiers.create_variable_from_name("n");
335
+
336
+ let plan = planner::plan(
337
+ Match {
338
+ patterns: vec![Pattern::Node(NodePattern {
339
+ variable: Some(node_identifier.clone()),
340
+ labels: LabelExpression::String("Doc".into()),
341
+ properties: None,
342
+ })],
343
+ where_expression: None,
344
+ optional: false,
345
+ search: Some(Search {
346
+ variable: node_identifier.clone(),
347
+ index: "doc_idx".into(),
348
+ query: Array {
349
+ array: vec![
350
+ Value { value: 0.1.into() }.into(),
351
+ Value { value: 0.2.into() }.into(),
352
+ Value { value: 0.3.into() }.into(),
353
+ ],
354
+ }
355
+ .into(),
356
+ limit: 5,
357
+ score_alias: None,
358
+ }),
359
+ }
360
+ .into(),
361
+ )
362
+ .unwrap();
363
+
364
+ assert!(matches!(
365
+ plan,
366
+ LogicalPlan::VectorSearch {
367
+ index,
368
+ k,
369
+ result_var,
370
+ score_var,
371
+ ..
372
+ }
373
+ if index == "doc_idx"
374
+ && k == 5
375
+ && result_var.name() == "n"
376
+ && score_var.is_none()
377
+ ));
378
+ }
379
+
380
+ #[test]
381
+ fn test_plan_match_with_search_and_score()
382
+ {
383
+ let identifiers = VariableIdentifiers::default();
384
+ let node_identifier = identifiers.create_variable_from_name("n");
385
+ let score_identifier = identifiers.create_variable_from_name("score");
386
+
387
+ let plan = planner::plan(
388
+ Match {
389
+ patterns: vec![Pattern::Node(NodePattern {
390
+ variable: Some(node_identifier.clone()),
391
+ labels: LabelExpression::String("Doc".into()),
392
+ properties: None,
393
+ })],
394
+ where_expression: None,
395
+ optional: false,
396
+ search: Some(Search {
397
+ variable: node_identifier.clone(),
398
+ index: "doc_idx".into(),
399
+ query: Array {
400
+ array: vec![
401
+ Value { value: 0.1.into() }.into(),
402
+ Value { value: 0.2.into() }.into(),
403
+ ],
404
+ }
405
+ .into(),
406
+ limit: 10,
407
+ score_alias: Some(score_identifier.clone()),
408
+ }),
409
+ }
410
+ .into(),
411
+ )
412
+ .unwrap();
413
+
414
+ assert!(matches!(
415
+ plan,
416
+ LogicalPlan::VectorSearch {
417
+ index,
418
+ k,
419
+ result_var,
420
+ score_var,
421
+ ..
422
+ }
423
+ if index == "doc_idx"
424
+ && k == 10
425
+ && result_var.name() == "n"
426
+ && score_var.as_ref().unwrap().name() == "score"
427
+ ));
428
+ }
429
+
430
+ #[test]
431
+ fn test_plan_match_without_search_still_emits_node_scan()
432
+ {
433
+ let identifiers = VariableIdentifiers::default();
434
+ let node_identifier = identifiers.create_variable_from_name("n");
435
+
436
+ let plan = planner::plan(
437
+ Match {
438
+ patterns: vec![Pattern::Node(NodePattern {
439
+ variable: Some(node_identifier.clone()),
440
+ labels: LabelExpression::String("Person".into()),
441
+ properties: None,
442
+ })],
443
+ where_expression: None,
444
+ optional: false,
445
+ search: None,
446
+ }
447
+ .into(),
448
+ )
449
+ .unwrap();
450
+
451
+ assert!(matches!(
452
+ plan,
453
+ LogicalPlan::NodeScan {
454
+ patterns,
455
+ optional: false,
456
+ }
457
+ if matches!(
458
+ &patterns[0],
459
+ Pattern::Node(NodePattern {
460
+ labels: LabelExpression::String(label),
461
+ ..
462
+ }) if label == "Person"
463
+ )
464
+ ));
465
+ }
466
+
467
+ #[test]
468
+ fn test_plan_match_with_search_and_filter()
469
+ {
470
+ let identifiers = VariableIdentifiers::default();
471
+ let node_identifier = identifiers.create_variable_from_name("n");
472
+
473
+ let plan = planner::plan(
474
+ Match {
475
+ patterns: vec![Pattern::Node(NodePattern {
476
+ variable: Some(node_identifier.clone()),
477
+ labels: LabelExpression::String("Doc".into()),
478
+ properties: None,
479
+ })],
480
+ where_expression: Some(Value { value: true.into() }.into()),
481
+ optional: false,
482
+ search: Some(Search {
483
+ variable: node_identifier.clone(),
484
+ index: "doc_idx".into(),
485
+ query: Array {
486
+ array: vec![
487
+ Value { value: 0.5.into() }.into(),
488
+ Value { value: 0.6.into() }.into(),
489
+ ],
490
+ }
491
+ .into(),
492
+ limit: 3,
493
+ score_alias: None,
494
+ }),
495
+ }
496
+ .into(),
497
+ )
498
+ .unwrap();
499
+
500
+ let (source, _) = unwrap_filter(&plan);
501
+ assert!(matches!(
502
+ source,
503
+ LogicalPlan::VectorSearch {
504
+ index,
505
+ k,
506
+ ..
507
+ }
508
+ if index == "doc_idx"
509
+ && *k == 3
510
+ ));
511
+ }
@@ -39,3 +39,10 @@ fn test_update_edges()
39
39
  let (store, _db) = create_test_store(104);
40
40
  super::test_update_edges(store);
41
41
  }
42
+
43
+ #[test]
44
+ fn test_select_nodes_by_uuids()
45
+ {
46
+ let (store, _db) = create_test_store(105);
47
+ super::test_select_nodes_by_uuids(store);
48
+ }
@@ -44,3 +44,11 @@ fn test_update_edges()
44
44
  let store = crate::store::redb::Store::open(temp_file.path()).unwrap();
45
45
  super::test_update_edges(store);
46
46
  }
47
+
48
+ #[test]
49
+ fn test_select_nodes_by_uuids()
50
+ {
51
+ let temp_file = crate::tests::create_tmp_file();
52
+ let store = crate::store::redb::Store::open(temp_file.path()).unwrap();
53
+ super::test_select_nodes_by_uuids(store);
54
+ }
@@ -44,3 +44,11 @@ fn test_update_edges()
44
44
  let store = crate::store::sqlite::Store::open(temp_file.path()).unwrap();
45
45
  super::test_update_edges(store);
46
46
  }
47
+
48
+ #[test]
49
+ fn test_select_nodes_by_uuids()
50
+ {
51
+ let temp_file = crate::tests::create_tmp_file();
52
+ let store = crate::store::sqlite::Store::open(temp_file.path()).unwrap();
53
+ super::test_select_nodes_by_uuids(store);
54
+ }
@@ -0,0 +1,182 @@
1
+ use crate::store::sqlbase::SqlStore;
2
+ use crate::store::{Store, TransactionBoxable};
3
+ use crate::tests::postgres::create_tmp_db;
4
+
5
+ mod single_open
6
+ {
7
+ crate::vector_index_test_suite!(
8
+ setup = |idx: u16| {
9
+ let db = crate::tests::postgres::create_tmp_db(250 + idx);
10
+ let config: postgres::Config = db.connection_uri().parse().unwrap();
11
+ let store = crate::store::postgres::Store::connect(config).unwrap();
12
+ (db, store)
13
+ }
14
+ );
15
+ }
16
+
17
+ mod reopen
18
+ {
19
+ crate::vector_index_reopen_suite!(
20
+ setup = |idx: u16| {
21
+ let db = crate::tests::postgres::create_tmp_db(280 + idx);
22
+ let uri = db.connection_uri();
23
+ let factory = move || {
24
+ let config: postgres::Config = uri.parse().unwrap();
25
+ crate::store::postgres::Store::connect(config).unwrap()
26
+ };
27
+ (db, factory)
28
+ }
29
+ );
30
+ }
31
+
32
+ // PostgreSQL-specific tests (not part of the generic suite)
33
+
34
+ #[test]
35
+ fn test_postgres_node_update_syncs_to_vector_index()
36
+ {
37
+ let db = create_tmp_db(274);
38
+ let config: postgres::Config = db.connection_uri().parse().unwrap();
39
+ let store = crate::store::postgres::Store::connect(config).unwrap();
40
+ super::test_vector_updated_on_node_property_update(store);
41
+ }
42
+
43
+ #[test]
44
+ fn test_postgres_upsert_rejects_nan_component()
45
+ {
46
+ let db = create_tmp_db(275);
47
+ let config: postgres::Config = db.connection_uri().parse().unwrap();
48
+ let store = crate::store::postgres::Store::connect(config).unwrap();
49
+
50
+ let mut tx = store.begin_write().unwrap();
51
+ store
52
+ .create_vector_index(
53
+ &mut tx,
54
+ "default",
55
+ crate::store::VectorIndexSpec {
56
+ name: "idx".into(),
57
+ dimension: 3,
58
+ metric: crate::store::VectorMetric::Cosine,
59
+ ..Default::default()
60
+ },
61
+ )
62
+ .unwrap();
63
+
64
+ let result = store.upsert_vector(
65
+ &mut tx,
66
+ "default",
67
+ "idx",
68
+ crate::graph::Key::new(1),
69
+ &crate::value::Tensor::from_f32_slice(&[1.0, f32::NAN, 0.0]),
70
+ );
71
+ tx.close().unwrap();
72
+
73
+ assert!(result.is_err());
74
+ let message = result.unwrap_err().to_string().to_lowercase();
75
+ assert!(!message.contains("pgvector"));
76
+ assert!(!message.contains("pg_catalog"));
77
+ }
78
+
79
+ #[test]
80
+ fn test_postgres_upsert_rejects_inf_component()
81
+ {
82
+ let db = create_tmp_db(276);
83
+ let config: postgres::Config = db.connection_uri().parse().unwrap();
84
+ let store = crate::store::postgres::Store::connect(config).unwrap();
85
+
86
+ let mut tx = store.begin_write().unwrap();
87
+ store
88
+ .create_vector_index(
89
+ &mut tx,
90
+ "default",
91
+ crate::store::VectorIndexSpec {
92
+ name: "idx".into(),
93
+ dimension: 3,
94
+ metric: crate::store::VectorMetric::Cosine,
95
+ ..Default::default()
96
+ },
97
+ )
98
+ .unwrap();
99
+
100
+ let result = store.upsert_vector(
101
+ &mut tx,
102
+ "default",
103
+ "idx",
104
+ crate::graph::Key::new(1),
105
+ &crate::value::Tensor::from_f32_slice(&[1.0, f32::INFINITY, 0.0]),
106
+ );
107
+ tx.close().unwrap();
108
+
109
+ assert!(result.is_err());
110
+ let message = result.unwrap_err().to_string().to_lowercase();
111
+ assert!(!message.contains("pgvector"));
112
+ assert!(!message.contains("pg_catalog"));
113
+ }
114
+
115
+ #[test]
116
+ fn test_postgres_search_rejects_nan_query()
117
+ {
118
+ let db = create_tmp_db(277);
119
+ let config: postgres::Config = db.connection_uri().parse().unwrap();
120
+ let store = crate::store::postgres::Store::connect(config).unwrap();
121
+
122
+ let mut tx = store.begin_write().unwrap();
123
+ store
124
+ .create_vector_index(
125
+ &mut tx,
126
+ "default",
127
+ crate::store::VectorIndexSpec {
128
+ name: "idx".into(),
129
+ dimension: 3,
130
+ metric: crate::store::VectorMetric::Cosine,
131
+ ..Default::default()
132
+ },
133
+ )
134
+ .unwrap();
135
+ tx.close().unwrap();
136
+
137
+ let mut tx = store.begin_read().unwrap();
138
+ let result = store.vector_search(
139
+ &mut tx,
140
+ "default",
141
+ "idx",
142
+ &crate::value::Tensor::from_f32_slice(&[1.0, f32::NAN, 0.0]),
143
+ 5,
144
+ );
145
+ tx.close().unwrap();
146
+
147
+ assert!(result.is_err());
148
+ let message = result.unwrap_err().to_string().to_lowercase();
149
+ assert!(!message.contains("pgvector"));
150
+ assert!(!message.contains("pg_catalog"));
151
+ }
152
+
153
+ #[test]
154
+ fn test_postgres_delete_vectors_rejects_malformed_node_id()
155
+ {
156
+ let db = create_tmp_db(278);
157
+ let config: postgres::Config = db.connection_uri().parse().unwrap();
158
+ let store = crate::store::postgres::Store::connect(config).unwrap();
159
+
160
+ let mut tx = store.begin_write().unwrap();
161
+ store
162
+ .create_vector_index(
163
+ &mut tx,
164
+ "default",
165
+ crate::store::VectorIndexSpec {
166
+ name: "idx".into(),
167
+ dimension: 3,
168
+ metric: crate::store::VectorMetric::Cosine,
169
+ ..Default::default()
170
+ },
171
+ )
172
+ .unwrap();
173
+
174
+ let result = store.delete_vectors_for_nodes_impl(&mut tx, "default", &[String::from("not-hex")]);
175
+ tx.close().unwrap();
176
+
177
+ assert!(result.is_err());
178
+ assert!(
179
+ result.unwrap_err().to_string().contains("invalid node_id"),
180
+ "malformed node id should return a typed invalid node_id error"
181
+ );
182
+ }