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
@@ -1,7 +1,11 @@
1
1
  #[cfg(all(feature = "postgres", not(windows)))]
2
2
  pub(crate) mod postgres;
3
+ #[cfg(feature = "redb")]
4
+ mod redb;
5
+ #[cfg(feature = "sqlite")]
6
+ pub(crate) mod sqlite;
3
7
 
4
- fn test_connection(connection: crate::Connection)
8
+ fn test_create_match_return(connection: crate::Connection)
5
9
  {
6
10
  connection
7
11
  .execute_oc_query("CREATE (n:a)", Default::default())
@@ -14,6 +18,62 @@ fn test_connection(connection: crate::Connection)
14
18
  assert_eq!(table.rows(), 1);
15
19
  assert_eq!(*table.headers(), vec!["n".to_string()]);
16
20
  let node: graphcore::Node = table.value(0, 0).unwrap().try_into().unwrap();
21
+ println!("{node:?}");
22
+ assert_eq!(node.graph_name().as_ref().unwrap(), "default");
17
23
  assert_eq!(*node.labels(), vec!["a".to_string()]);
18
24
  assert_eq!(node.properties().len(), 0);
19
25
  }
26
+
27
+ fn test_create_return(connection: crate::Connection)
28
+ {
29
+ let r = connection
30
+ .execute_oc_query("CREATE (n:a) RETURN n", Default::default())
31
+ .unwrap();
32
+ let table = r.try_into_table().unwrap();
33
+ assert_eq!(table.columns(), 1);
34
+ assert_eq!(table.rows(), 1);
35
+ assert_eq!(*table.headers(), vec!["n".to_string()]);
36
+ let node: graphcore::Node = table.value(0, 0).unwrap().try_into().unwrap();
37
+ println!("{node:?}");
38
+ assert_eq!(node.graph_name().as_ref().unwrap(), "default");
39
+ assert_eq!(*node.labels(), vec!["a".to_string()]);
40
+ assert_eq!(node.properties().len(), 0);
41
+ }
42
+
43
+ fn test_create_edge_return(connection: crate::Connection)
44
+ {
45
+ let r = connection
46
+ .execute_oc_query("CREATE ()-[e:a]->() RETURN e", Default::default())
47
+ .unwrap();
48
+ let table = r.try_into_table().unwrap();
49
+ assert_eq!(table.columns(), 1);
50
+ assert_eq!(table.rows(), 1);
51
+ assert_eq!(*table.headers(), vec!["e".to_string()]);
52
+ let edge: graphcore::Edge = table.value(0, 0).unwrap().try_into().unwrap();
53
+ println!("{edge:?}");
54
+ assert_eq!(edge.graph_name().as_ref().unwrap(), "default");
55
+ assert_eq!(*edge.labels(), vec!["a".to_string()]);
56
+ assert_eq!(edge.properties().len(), 0);
57
+ }
58
+
59
+ fn test_vector_search_dimension_mismatch(connection: crate::Connection)
60
+ {
61
+ connection
62
+ .execute_oc_query(
63
+ "CREATE (:Doc { embedding: [0.1, 0.2, 0.3], title: 'doc1' })",
64
+ Default::default(),
65
+ )
66
+ .unwrap();
67
+ connection
68
+ .execute_oc_query(
69
+ "CREATE VECTOR INDEX doc_idx ON :Doc (embedding) OPTIONS { dimension: 3, metric: \"cosine\" }",
70
+ Default::default(),
71
+ )
72
+ .unwrap();
73
+
74
+ let result = connection.execute_oc_query(
75
+ "MATCH (n:Doc) SEARCH n IN (VECTOR INDEX doc_idx FOR [0.5, 0.5] LIMIT 1) RETURN n.title",
76
+ Default::default(),
77
+ );
78
+ assert!(result.is_err(), "dimension mismatch should raise an error");
79
+ }
@@ -1,6 +1,9 @@
1
1
  use crate::{
2
+ compiler::compile,
2
3
  interpreter::evaluators::eval_program,
4
+ planner,
3
5
  prelude::*,
6
+ query_result::QueryResult,
4
7
  store::{Store, TransactionBoxable},
5
8
  tests::{check_stats, templates::programs},
6
9
  };
@@ -74,18 +77,21 @@ fn test_evaluate_match_loop()
74
77
  let temp_file = crate::tests::create_tmp_file();
75
78
  let store = crate::store::redb::Store::open(temp_file.path()).unwrap();
76
79
 
77
- let node = graph::Node::new(graph::Key::new(1), vec![], Default::default());
80
+ let node = graph::Node::new(
81
+ Some("default".into()),
82
+ graph::Key::new(1),
83
+ vec![],
84
+ Default::default(),
85
+ );
78
86
  let mut tx = store.begin_write().unwrap();
79
87
  store.create_nodes(&mut tx, "default", vec![&node]).unwrap();
80
88
  store
81
89
  .create_edges(
82
90
  &mut tx,
83
91
  "default",
84
- vec![&graph::Path::new(
85
- graph::Key::new(2),
92
+ vec![&graph::SinglePath::new(
86
93
  node.clone(),
87
- vec![],
88
- Default::default(),
94
+ graph::Edge::new(None, graph::Key::new(2), vec![], Default::default()),
89
95
  node.clone(),
90
96
  )],
91
97
  )
@@ -125,7 +131,7 @@ fn test_evaluate_match_count()
125
131
  assert_eq!(value, crate::table![("count(*)"), [[0]]].into());
126
132
 
127
133
  // Count 1
128
- let node = graph::Node::new(graph::Key::new(1), vec![], Default::default());
134
+ let node = graph::Node::new(None, graph::Key::new(1), vec![], Default::default());
129
135
  let mut tx = store.begin_write().unwrap();
130
136
  store.create_nodes(&mut tx, "default", vec![&node]).unwrap();
131
137
  tx.close().unwrap();
@@ -147,12 +153,19 @@ fn test_evaluate_aggregation()
147
153
 
148
154
  let nodes = [
149
155
  graph::Node::new(
156
+ None,
150
157
  graph::Key::new(1),
151
158
  vec![],
152
159
  value::value_map!("name" => "a", "num" => 33),
153
160
  ),
154
- graph::Node::new(graph::Key::new(2), vec![], value::value_map!("name" => "a")),
155
161
  graph::Node::new(
162
+ None,
163
+ graph::Key::new(2),
164
+ vec![],
165
+ value::value_map!("name" => "a"),
166
+ ),
167
+ graph::Node::new(
168
+ None,
156
169
  graph::Key::new(3),
157
170
  vec![],
158
171
  value::value_map!("name" => "b", "num" => 42),
@@ -176,3 +189,145 @@ fn test_evaluate_aggregation()
176
189
  crate::table![("n.name", "count(n.num)"), [["a", 1], ["b", 1]]],
177
190
  );
178
191
  }
192
+
193
+ fn run_query(store: &impl Store, source: &str) -> Result<QueryResult>
194
+ {
195
+ let function_manager = functions::Manager::new();
196
+ let queries = gqlparser::oc::parse_oc_query(source).map_err(|_| {
197
+ error::Error::CompileTime(error::CompileTimeError::ParseError {
198
+ error_kind: gqlparser::oc::ErrorKind::UnexpectedToken,
199
+ text: source.to_string(),
200
+ span: 0..source.len(),
201
+ })
202
+ })?;
203
+ let statements = queries
204
+ .into_iter()
205
+ .next()
206
+ .and_then(|q| match q
207
+ {
208
+ gqlparser::oc::ast::Query::Statements(s) => Some(s),
209
+ _ => None,
210
+ })
211
+ .ok_or_else(|| {
212
+ error::Error::CompileTime(error::CompileTimeError::UnexpectedSyntax {
213
+ expected: "statements".to_string(),
214
+ got: "other".to_string(),
215
+ line: 0,
216
+ col: 0,
217
+ })
218
+ })?;
219
+ let program = compile(
220
+ &function_manager,
221
+ statements
222
+ .into_iter()
223
+ .map(planner::plan)
224
+ .collect::<Result<Vec<_>, _>>()?,
225
+ )?;
226
+ eval_program(store, &program, &Default::default())
227
+ }
228
+
229
+ #[test]
230
+ fn test_vector_search_on_non_default_graph_after_use()
231
+ {
232
+ let temp_file = crate::tests::create_tmp_file();
233
+ let store = crate::store::redb::Store::open(temp_file.path()).unwrap();
234
+
235
+ // Single index, single graph, never touching "default" at all -- the
236
+ // minimal reproduction of the bug report.
237
+ run_query(&store, "CREATE GRAPH g1").unwrap();
238
+ run_query(
239
+ &store,
240
+ "USE g1 CREATE (:Doc {title: 'A', embedding: [1.0, 0.0, 0.0]})",
241
+ )
242
+ .unwrap();
243
+ run_query(
244
+ &store,
245
+ "USE g1 CREATE VECTOR INDEX idx ON :Doc (embedding) OPTIONS { dimension: 3, metric: \"cosine\" }",
246
+ )
247
+ .unwrap();
248
+
249
+ // EXPECTED TO FAIL before #444: this currently raises
250
+ // StoreError::InvalidFormat("Vector index 'idx' does not exist in graph 'default'")
251
+ // because LogicalPlan::VectorSearch hardcodes graph: "default" at plan
252
+ // time (planner.rs), regardless of the USE g1 immediately above.
253
+ let result = run_query(
254
+ &store,
255
+ "USE g1 MATCH (n:Doc) SEARCH n IN (VECTOR INDEX idx FOR [1.0, 0.0, 0.0] LIMIT 1) RETURN n.title",
256
+ );
257
+ assert!(
258
+ result.is_ok(),
259
+ "expected search on g1 to find idx registered on g1, got: {:?}",
260
+ result
261
+ );
262
+ }
263
+
264
+ #[test]
265
+ fn test_vector_search_multiple_indexes_same_graph()
266
+ {
267
+ let temp_file = crate::tests::create_tmp_file();
268
+ let store = crate::store::redb::Store::open(temp_file.path()).unwrap();
269
+
270
+ run_query(&store, "CREATE GRAPH g1").unwrap();
271
+ run_query(
272
+ &store,
273
+ "USE g1 CREATE (:Doc {title: 'A', embedding: [1.0, 0.0, 0.0]}) \
274
+ CREATE (:Note {title: 'B', tags: [0.0, 1.0, 0.0]})",
275
+ )
276
+ .unwrap();
277
+ run_query(
278
+ &store,
279
+ "USE g1 CREATE VECTOR INDEX idx_doc ON :Doc (embedding) OPTIONS { dimension: 3, metric: \"cosine\" }",
280
+ )
281
+ .unwrap();
282
+ run_query(
283
+ &store,
284
+ "USE g1 CREATE VECTOR INDEX idx_note ON :Note (tags) OPTIONS { dimension: 3, metric: \"cosine\" }",
285
+ )
286
+ .unwrap();
287
+
288
+ // EXPECTED TO FAIL before #444, same root cause as above.
289
+ let doc_result = run_query(
290
+ &store,
291
+ "USE g1 MATCH (n:Doc) SEARCH n IN (VECTOR INDEX idx_doc FOR [1.0, 0.0, 0.0] LIMIT 1) RETURN n.title",
292
+ );
293
+ assert!(doc_result.is_ok());
294
+
295
+ let note_result = run_query(
296
+ &store,
297
+ "USE g1 MATCH (n:Note) SEARCH n IN (VECTOR INDEX idx_note FOR [0.0, 1.0, 0.0] LIMIT 1) RETURN n.title",
298
+ );
299
+ assert!(note_result.is_ok());
300
+ }
301
+
302
+ #[test]
303
+ fn test_vector_search_after_switching_between_two_graphs()
304
+ {
305
+ let temp_file = crate::tests::create_tmp_file();
306
+ let store = crate::store::redb::Store::open(temp_file.path()).unwrap();
307
+
308
+ run_query(&store, "CREATE GRAPH g1 CREATE GRAPH g2").unwrap();
309
+ run_query(
310
+ &store,
311
+ "USE g1 CREATE (:Doc {title: 'A', embedding: [1.0, 0.0, 0.0]}) \
312
+ CREATE VECTOR INDEX idx1 ON :Doc (embedding) OPTIONS { dimension: 3, metric: \"cosine\" }",
313
+ )
314
+ .unwrap();
315
+ run_query(
316
+ &store,
317
+ "USE g2 CREATE (:Doc {title: 'B', embedding: [0.0, 1.0, 0.0]}) \
318
+ CREATE VECTOR INDEX idx2 ON :Doc (embedding) OPTIONS { dimension: 3, metric: \"cosine\" }",
319
+ )
320
+ .unwrap();
321
+
322
+ // After switching back to g1, idx1 must resolve against g1 -- direct
323
+ // multi-USE regression case. EXPECTED TO FAIL before WI 3.
324
+ let result = run_query(
325
+ &store,
326
+ "USE g1 MATCH (n:Doc) SEARCH n IN (VECTOR INDEX idx1 FOR [1.0, 0.0, 0.0] LIMIT 1) RETURN n.title",
327
+ );
328
+ assert!(
329
+ result.is_ok(),
330
+ "idx1 must be found on g1 after switching away and back: {:?}",
331
+ result
332
+ );
333
+ }
@@ -1,81 +1,112 @@
1
- use crate::{
2
- parser::{self, parse},
3
- tests::templates::ast,
4
- };
1
+ use gqlparser::oc::{self, parse_oc_query as parse};
5
2
 
6
- fn compare_ast(actual: Vec<parser::ast::Statement>, expected: Vec<parser::ast::Statement>)
3
+ use crate::tests::templates::ast;
4
+
5
+ fn compare_ast(actual: Vec<oc::ast::Statement>, expected: Vec<oc::ast::Statement>)
7
6
  {
8
7
  assert_eq!(format!("{:?}", actual), format!("{:?}", expected));
9
8
  }
10
9
 
10
+ fn parse_query(source: &str) -> Vec<oc::ast::Statement>
11
+ {
12
+ let queries = parse(source).unwrap();
13
+ let oc::ast::Query::Statements(ast) = queries.into_iter().next().unwrap()
14
+ else
15
+ {
16
+ panic!("Expected Statements query");
17
+ };
18
+ ast
19
+ }
20
+
11
21
  #[test]
12
22
  fn test_parse_simple_create_node()
13
23
  {
14
- let ast = parse("CREATE ()").unwrap();
15
- let ast = ast.into_iter().next().unwrap();
24
+ let ast = parse_query("CREATE ()");
16
25
  compare_ast(ast, ast::simple_create_node())
17
26
  }
18
27
 
19
28
  #[test]
20
29
  fn test_parse_create_named_node()
21
30
  {
22
- let ast = parse("CREATE (n {name: 'foo'}) RETURN n.name AS p").unwrap();
23
- let ast = ast.into_iter().next().unwrap();
31
+ let ast = parse_query("CREATE (n {name: 'foo'}) RETURN n.name AS p");
24
32
  compare_ast(ast, ast::create_named_node())
25
33
  }
26
34
 
27
35
  #[test]
28
36
  fn test_parse_create_named_node_double_return()
29
37
  {
30
- let ast = parse("CREATE (n {id: 12, name: 'foo'}) RETURN n.id AS id, n.name AS p").unwrap();
31
- let ast = ast.into_iter().next().unwrap();
38
+ let ast = parse_query("CREATE (n {id: 12, name: 'foo'}) RETURN n.id AS id, n.name AS p");
32
39
  compare_ast(ast, ast::create_named_node_double_return())
33
40
  }
34
41
 
35
42
  #[test]
36
43
  fn test_parse_double_with_return()
37
44
  {
38
- let ast = parse("WITH 1 AS n, 2 AS m WITH n AS a, m AS b RETURN a").unwrap();
39
- let ast = ast.into_iter().next().unwrap();
45
+ let ast = parse_query("WITH 1 AS n, 2 AS m WITH n AS a, m AS b RETURN a");
40
46
  compare_ast(ast, ast::double_with_return())
41
47
  }
42
48
 
43
49
  #[test]
44
50
  fn test_parse_unwind()
45
51
  {
46
- let ast = parse("UNWIND [0] AS i RETURN i").unwrap();
47
- let ast = ast.into_iter().next().unwrap();
52
+ let ast = parse_query("UNWIND [0] AS i RETURN i");
48
53
  compare_ast(ast, ast::unwind())
49
54
  }
50
55
 
51
56
  #[test]
52
57
  fn test_parse_match_loop()
53
58
  {
54
- let ast = parse("MATCH (n)-[]->(n) RETURN n").unwrap();
55
- let ast = ast.into_iter().next().unwrap();
59
+ let ast = parse_query("MATCH (n)-[]->(n) RETURN n");
56
60
  compare_ast(ast, ast::match_loop());
57
61
  }
58
62
 
59
63
  #[test]
60
64
  fn test_parse_optional_match()
61
65
  {
62
- let ast = parse("OPTIONAL MATCH (a) RETURN a").unwrap();
63
- let ast = ast.into_iter().next().unwrap();
66
+ let ast = parse_query("OPTIONAL MATCH (a) RETURN a");
64
67
  compare_ast(ast, ast::optional_match());
65
68
  }
66
69
 
67
70
  #[test]
68
71
  fn test_parse_match_count()
69
72
  {
70
- let ast = parse("MATCH (a) RETURN count(*)").unwrap();
71
- let ast = ast.into_iter().next().unwrap();
73
+ let ast = parse_query("MATCH (a) RETURN count(*)");
72
74
  compare_ast(ast, ast::match_count());
73
75
  }
74
76
 
75
77
  #[test]
76
78
  fn test_parse_aggregation()
77
79
  {
78
- let ast = parse("MATCH (n) RETURN n.name, count(n.num)").unwrap();
79
- let ast = ast.into_iter().next().unwrap();
80
+ let ast = parse_query("MATCH (n) RETURN n.name, count(n.num)");
80
81
  compare_ast(ast, ast::aggregation());
81
82
  }
83
+
84
+ #[test]
85
+ fn test_parse_create_vector_index()
86
+ {
87
+ let ast = parse_query(
88
+ "CREATE VECTOR INDEX idx ON :Doc (embedding) OPTIONS { dimension: 3, metric: \"cosine\" }",
89
+ );
90
+ compare_ast(ast, ast::create_vector_index("idx"))
91
+ }
92
+
93
+ #[test]
94
+ fn test_parse_match_with_vector_search()
95
+ {
96
+ let ast = parse_query(
97
+ "MATCH (n:Doc) SEARCH n IN (VECTOR INDEX idx FOR [0.1, 0.2, 0.3] LIMIT 2) RETURN n.title AS p",
98
+ );
99
+ compare_ast(ast, ast::match_with_vector_search("idx"))
100
+ }
101
+
102
+ #[test]
103
+ fn test_parse_multiple_vector_index_creation()
104
+ {
105
+ let ast = parse_query(
106
+ "CREATE VECTOR INDEX idx1 ON :Doc (embedding) OPTIONS { dimension: 3, metric: \"cosine\" } \
107
+ CREATE VECTOR INDEX idx2 ON :Doc (embedding) OPTIONS { dimension: 3, metric: \"cosine\" }",
108
+ );
109
+ let mut expected = ast::create_vector_index("idx1");
110
+ expected.extend(ast::create_vector_index("idx2"));
111
+ compare_ast(ast, expected)
112
+ }