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,9 +1,11 @@
1
1
  use std::collections::HashMap;
2
2
 
3
+ use rustc_hash::FxHashSet;
4
+
3
5
  use crate::{
4
6
  interpreter::instructions::{Block, VariablesSizes},
5
7
  prelude::*,
6
- store::TransactionBoxable,
8
+ store::{SelectNodeQuery, TransactionBoxable},
7
9
  value_table::{MutableRowInterface, Row, RowInterface},
8
10
  };
9
11
  use interpreter::instructions;
@@ -60,6 +62,7 @@ try_into_gv_impl! {f64}
60
62
  try_into_gv_impl! {String}
61
63
  try_into_gv_impl! {graph::Node}
62
64
  try_into_gv_impl! {graph::Edge}
65
+ try_into_gv_impl! {value::Tensor}
63
66
 
64
67
  macro_rules! try_into_impl {
65
68
  ($typename:tt, $type:ty, $errorname:tt) => {
@@ -357,11 +360,14 @@ fn eval_instructions(
357
360
  let props: value::Value = stack.try_pop_into()?;
358
361
  let dst: graph::Node = stack.try_pop_into()?;
359
362
  let src: graph::Node = stack.try_pop_into()?;
360
- stack.push(crate::graph::Path::new(
361
- graph::Key::default(),
363
+ stack.push(crate::graph::SinglePath::new(
362
364
  src,
363
- labels.to_owned(),
364
- props.into_map(),
365
+ graphcore::Edge::new(
366
+ None,
367
+ graph::Key::default(),
368
+ labels.to_owned(),
369
+ props.into_map(),
370
+ ),
365
371
  dst,
366
372
  ));
367
373
  }
@@ -369,6 +375,7 @@ fn eval_instructions(
369
375
  {
370
376
  let props: value::Value = stack.try_pop_into()?;
371
377
  stack.push(crate::graph::Node::new(
378
+ None,
372
379
  crate::graph::Key::default(),
373
380
  labels.clone(),
374
381
  props.into_map(),
@@ -865,7 +872,7 @@ pub(crate) fn eval_update_property<TStore: store::Store>(
865
872
  }
866
873
  value::Value::Null =>
867
874
  {}
868
- _ => Err(InternalError::ExpectedEdge {
875
+ _ => Err(RunTimeError::ExpectedEdge {
869
876
  context: "evaluator/eval_program",
870
877
  })?,
871
878
  }
@@ -1006,10 +1013,8 @@ fn compute_return_with_table(
1006
1013
  for (row, aggregations_states) in aggregation_table
1007
1014
  {
1008
1015
  let mut out_row = value_table::Row::new(Default::default(), variables_sizes.total_size());
1009
- for (idx, (rw_expr, aggregation_states)) in variables
1010
- .iter()
1011
- .zip(aggregations_states.into_iter())
1012
- .enumerate()
1016
+ for (idx, (rw_expr, aggregation_states)) in
1017
+ variables.iter().zip(aggregations_states).enumerate()
1013
1018
  {
1014
1019
  if rw_expr.aggregations.is_empty()
1015
1020
  {
@@ -1167,6 +1172,378 @@ fn filter_rows(
1167
1172
  .collect()
1168
1173
  }
1169
1174
 
1175
+ /// Executes a single `BlockMatch` (node or edge) against every row in `current_rows`,
1176
+ /// appending matching rows into `new_rows`.
1177
+ ///
1178
+ /// `allow_recursive` controls whether a recursive edge range pattern is permitted.
1179
+ /// Pass `true` for `Block::Match` and `false` for `Block::Merge` — the latter
1180
+ /// returns `RunTimeError::UnsupportedRecursivePatternInMerge` when a recursive
1181
+ /// range is encountered.
1182
+ #[allow(clippy::too_many_arguments)]
1183
+ fn execute_block_match<TStore: store::Store>(
1184
+ block: &instructions::BlockMatch,
1185
+ store: &TStore,
1186
+ tx: &mut TStore::TransactionBox,
1187
+ graph_name: &str,
1188
+ stack: &mut Stack,
1189
+ parameters: &crate::value::ValueMap,
1190
+ current_rows: Vec<value_table::Row>,
1191
+ allow_recursive: bool,
1192
+ ) -> Result<Vec<value_table::Row>>
1193
+ {
1194
+ let mut new_rows = Vec::<value_table::Row>::default();
1195
+
1196
+ for row in current_rows
1197
+ {
1198
+ match block
1199
+ {
1200
+ instructions::BlockMatch::MatchNode {
1201
+ instructions,
1202
+ variable,
1203
+ filter,
1204
+ } =>
1205
+ {
1206
+ eval_instructions(stack, &row, instructions, parameters)?;
1207
+ let query: store::SelectNodeQuery = stack.try_pop_into()?;
1208
+ let nodes = store.select_nodes(tx, graph_name, query)?;
1209
+
1210
+ for node in nodes.into_iter()
1211
+ {
1212
+ let mut new_row = row.clone();
1213
+ if let Some(variable) = variable
1214
+ {
1215
+ new_row.set_if_unset(*variable, node.to_owned().into())?;
1216
+ }
1217
+ let should_add_row = if filter.is_empty()
1218
+ {
1219
+ true
1220
+ }
1221
+ else
1222
+ {
1223
+ let mut stack = Stack::default();
1224
+ stack.push(true);
1225
+ stack.push(node);
1226
+ eval_instructions(&mut stack, &new_row, filter, parameters)?;
1227
+ stack.try_pop()?; // discard the sentinel
1228
+ stack.try_pop_into()?
1229
+ };
1230
+ if should_add_row
1231
+ {
1232
+ new_rows.push(new_row);
1233
+ }
1234
+ }
1235
+ }
1236
+
1237
+ instructions::BlockMatch::MatchEdge {
1238
+ instructions,
1239
+ left_variable,
1240
+ edge_variable,
1241
+ right_variable,
1242
+ path_variable,
1243
+ filter,
1244
+ directivity,
1245
+ recursive_range,
1246
+ } =>
1247
+ {
1248
+ eval_instructions(stack, &row, instructions, parameters)?;
1249
+ let query = stack.try_pop_into()?;
1250
+
1251
+ match recursive_range
1252
+ {
1253
+ None =>
1254
+ {
1255
+ let edges = store.select_edges(tx, graph_name, query, *directivity)?;
1256
+
1257
+ for edge in edges.into_iter()
1258
+ {
1259
+ let mut new_row = row.clone();
1260
+ let (left, right) = if edge.reversed
1261
+ {
1262
+ (edge.path.destination(), edge.path.source())
1263
+ }
1264
+ else
1265
+ {
1266
+ (edge.path.source(), edge.path.destination())
1267
+ };
1268
+
1269
+ if let Some(left_variable) = left_variable
1270
+ {
1271
+ new_row.set(*left_variable, left.to_owned().into())?;
1272
+ }
1273
+ if let Some(right_variable) = right_variable
1274
+ {
1275
+ new_row.set(*right_variable, right.to_owned().into())?;
1276
+ }
1277
+ if let Some(edge_variable) = edge_variable
1278
+ {
1279
+ new_row.set(*edge_variable, edge.path.to_edge().into())?;
1280
+ }
1281
+ if let Some(path_variable) = path_variable
1282
+ {
1283
+ new_row.set(*path_variable, edge.path.to_owned().into())?;
1284
+ }
1285
+
1286
+ let should_add_row = if filter.is_empty()
1287
+ {
1288
+ true
1289
+ }
1290
+ else
1291
+ {
1292
+ let mut stack = Stack::default();
1293
+ stack.push(true);
1294
+ stack.push(edge.path.to_edge());
1295
+ eval_instructions(&mut stack, &new_row, filter, parameters)?;
1296
+ stack.try_pop()?;
1297
+ stack.try_pop_into()?
1298
+ };
1299
+
1300
+ if should_add_row
1301
+ {
1302
+ new_rows.push(new_row);
1303
+ }
1304
+ }
1305
+ }
1306
+
1307
+ Some(recursive_range) =>
1308
+ {
1309
+ if !allow_recursive
1310
+ {
1311
+ return Err(InternalError::UnsupportedRecursivePatternInMerge.into());
1312
+ }
1313
+
1314
+ // Recursive DFS
1315
+ let min_hops = recursive_range.start;
1316
+ let max_hops = recursive_range.end;
1317
+ let capacity = max_hops.min(100);
1318
+
1319
+ let bound_dest_key: Option<graph::Key> = if let Some(right_col) = right_variable
1320
+ {
1321
+ match row.get(*right_col)?
1322
+ {
1323
+ value::Value::Node(node) => Some(node.key()),
1324
+ _ => None,
1325
+ }
1326
+ }
1327
+ else
1328
+ {
1329
+ None
1330
+ };
1331
+
1332
+ let start_nodes: Vec<graph::Node> = {
1333
+ let source_query = query.source_query();
1334
+ store.select_nodes(tx, graph_name, source_query)?
1335
+ };
1336
+
1337
+ struct Frame
1338
+ {
1339
+ node: graph::Node,
1340
+ edges: Vec<store::EdgeResult>,
1341
+ edge_index: usize,
1342
+ depth: usize,
1343
+ }
1344
+
1345
+ for start_node in start_nodes
1346
+ {
1347
+ let mut path_edges: Vec<graph::Edge> = Vec::with_capacity(capacity);
1348
+ let mut used_edges: FxHashSet<graph::Key> =
1349
+ FxHashSet::with_capacity_and_hasher(capacity, Default::default());
1350
+ let mut dedup_set: FxHashSet<(Option<graph::Key>, Option<graph::Key>, usize)> =
1351
+ FxHashSet::with_capacity_and_hasher(capacity, Default::default());
1352
+
1353
+ let initial_edges = {
1354
+ let q = store::SelectEdgeQuery::select_source_keys(
1355
+ store::SelectNodeQuery::select_keys(vec![start_node.key()]),
1356
+ );
1357
+ store.select_edges(tx, graph_name, q, *directivity)?
1358
+ };
1359
+
1360
+ let mut dfs_stack: Vec<Frame> = vec![Frame {
1361
+ node: start_node.clone(),
1362
+ edges: initial_edges,
1363
+ edge_index: 0,
1364
+ depth: 0,
1365
+ }];
1366
+
1367
+ while !dfs_stack.is_empty()
1368
+ {
1369
+ let mut push_next: Option<Frame> = None;
1370
+ let mut should_pop = false;
1371
+
1372
+ let dfs_intermediate_nodes: Vec<graph::Node> = if dfs_stack.len() >= 2
1373
+ {
1374
+ dfs_stack[1..dfs_stack.len() - 1]
1375
+ .iter()
1376
+ .map(|f| f.node.clone())
1377
+ .collect()
1378
+ }
1379
+ else
1380
+ {
1381
+ Vec::new()
1382
+ };
1383
+
1384
+ {
1385
+ let frame = dfs_stack.last_mut().unwrap();
1386
+
1387
+ // Emit row
1388
+ if frame.edge_index == 0 && frame.depth >= min_hops
1389
+ {
1390
+ let current_node = &frame.node;
1391
+
1392
+ let mut new_row = row.clone();
1393
+
1394
+ if let Some(left_var) = left_variable
1395
+ {
1396
+ new_row.set(*left_var, start_node.clone().into())?;
1397
+ }
1398
+ if let Some(right_var) = right_variable
1399
+ {
1400
+ new_row.set(*right_var, current_node.clone().into())?;
1401
+ }
1402
+ if let Some(edge_var) = edge_variable
1403
+ && let Some(last_edge) = path_edges.last()
1404
+ {
1405
+ new_row.set(*edge_var, last_edge.clone().into())?;
1406
+ }
1407
+ if let Some(path_var) = path_variable
1408
+ {
1409
+ let path = graph::Path::new(
1410
+ start_node.clone(),
1411
+ path_edges
1412
+ .clone()
1413
+ .into_iter()
1414
+ .zip(
1415
+ dfs_intermediate_nodes
1416
+ .clone()
1417
+ .into_iter()
1418
+ .chain(std::iter::once(current_node.clone())),
1419
+ )
1420
+ .collect(),
1421
+ );
1422
+ new_row.set(*path_var, path.into())?;
1423
+ }
1424
+
1425
+ let passes_filter = if filter.is_empty()
1426
+ {
1427
+ true
1428
+ }
1429
+ else
1430
+ {
1431
+ let mut eval_stack = Stack::default();
1432
+ eval_stack.push(true);
1433
+ if let Some(last_edge) = path_edges.last()
1434
+ {
1435
+ eval_stack.push(last_edge.clone());
1436
+ }
1437
+ eval_instructions(&mut eval_stack, &new_row, filter, parameters)?;
1438
+ eval_stack.try_pop()?;
1439
+ eval_stack.try_pop_into()?
1440
+ };
1441
+
1442
+ let matches_dest = if let Some(dest_key) = bound_dest_key
1443
+ {
1444
+ current_node.key() == dest_key
1445
+ }
1446
+ else
1447
+ {
1448
+ true
1449
+ };
1450
+
1451
+ if passes_filter && matches_dest
1452
+ {
1453
+ let left_key_opt: Option<graph::Key> = if left_variable.is_some()
1454
+ {
1455
+ Some(start_node.key())
1456
+ }
1457
+ else
1458
+ {
1459
+ None
1460
+ };
1461
+ let right_key_opt: Option<graph::Key> = Some(current_node.key());
1462
+ let path_len = path_edges.len();
1463
+ let sig = (left_key_opt, right_key_opt, path_len);
1464
+
1465
+ if !dedup_set.contains(&sig)
1466
+ {
1467
+ dedup_set.insert(sig);
1468
+ new_rows.push(new_row);
1469
+ }
1470
+ }
1471
+ }
1472
+
1473
+ // Stop for max depth
1474
+ if frame.depth == max_hops || frame.edge_index >= frame.edges.len()
1475
+ {
1476
+ should_pop = true;
1477
+ }
1478
+ else
1479
+ {
1480
+ // Next hop
1481
+ let edge_result = &frame.edges[frame.edge_index];
1482
+ frame.edge_index += 1;
1483
+
1484
+ let edge = edge_result.path.to_edge();
1485
+ let edge_key = edge.key();
1486
+
1487
+ if used_edges.contains(&edge_key)
1488
+ {
1489
+ continue;
1490
+ }
1491
+
1492
+ let next_node = if edge_result.reversed
1493
+ {
1494
+ edge_result.path.source().clone()
1495
+ }
1496
+ else
1497
+ {
1498
+ edge_result.path.destination().clone()
1499
+ };
1500
+
1501
+ used_edges.insert(edge_key);
1502
+ path_edges.push(edge.clone());
1503
+
1504
+ let next_edges = {
1505
+ let q = store::SelectEdgeQuery::select_source_keys(
1506
+ store::SelectNodeQuery::select_keys(vec![next_node.key()]),
1507
+ );
1508
+ store.select_edges(tx, graph_name, q, *directivity)?
1509
+ };
1510
+
1511
+ push_next = Some(Frame {
1512
+ node: next_node,
1513
+ edges: next_edges,
1514
+ edge_index: 0,
1515
+ depth: frame.depth + 1,
1516
+ });
1517
+ }
1518
+ }
1519
+
1520
+ // Mutate stack
1521
+ if should_pop
1522
+ {
1523
+ dfs_stack.pop();
1524
+ if let Some(last_edge) = path_edges.pop()
1525
+ {
1526
+ used_edges.remove(&last_edge.key());
1527
+ }
1528
+ continue;
1529
+ }
1530
+
1531
+ if let Some(frame) = push_next
1532
+ {
1533
+ dfs_stack.push(frame);
1534
+ continue;
1535
+ }
1536
+ }
1537
+ }
1538
+ }
1539
+ }
1540
+ }
1541
+ }
1542
+ }
1543
+
1544
+ Ok(new_rows)
1545
+ }
1546
+
1170
1547
  fn is_write_program(program: &super::Program) -> bool
1171
1548
  {
1172
1549
  program.iter().any(|b| match b
@@ -1176,12 +1553,15 @@ fn is_write_program(program: &super::Program) -> bool
1176
1553
  | Block::Return { .. }
1177
1554
  | Block::Unwind { .. }
1178
1555
  | Block::Call { .. }
1179
- | Block::With { .. } => false,
1556
+ | Block::With { .. }
1557
+ | Block::VectorSearch { .. } => false,
1180
1558
  Block::CreateGraph { .. }
1181
1559
  | Block::DropGraph { .. }
1182
1560
  | Block::Create { .. }
1183
1561
  | Block::Update { .. }
1184
- | Block::Delete { .. } => true,
1562
+ | Block::Merge { .. }
1563
+ | Block::Delete { .. }
1564
+ | Block::CreateVectorIndex { .. } => true,
1185
1565
  })
1186
1566
  }
1187
1567
 
@@ -1267,17 +1647,50 @@ pub(crate) fn eval_program<TStore: store::Store>(
1267
1647
  crate::value::Value::Node(n) =>
1268
1648
  {
1269
1649
  store.create_nodes(&mut tx, &graph_name, vec![&n])?;
1270
- if let Some(var) = var
1650
+ if let Some(var) = var.element
1271
1651
  {
1272
- new_row.set_if_unset(*var, crate::value::Value::Node(n))?;
1652
+ new_row.set_if_unset(
1653
+ var,
1654
+ crate::value::Value::Node(n.with_graph_name(graph_name.to_owned())),
1655
+ )?;
1273
1656
  }
1274
1657
  }
1275
- crate::value::Value::Path(p) =>
1658
+ crate::value::Value::SinglePath(p) =>
1276
1659
  {
1277
- store.create_edges(&mut tx, &graph_name, vec![&p])?;
1278
- if let Some(var) = var
1660
+ store.create_edges(&mut tx, &graph_name, vec![p.as_ref()])?;
1661
+
1662
+ match (var.element, var.path)
1279
1663
  {
1280
- new_row.set(*var, crate::value::Value::Edge(p.into()))?;
1664
+ (Some(edge_var), Some(path_var)) =>
1665
+ {
1666
+ new_row.set(
1667
+ edge_var,
1668
+ crate::value::Value::Edge(
1669
+ p.clone().into_edge().with_graph_name(graph_name.to_owned()),
1670
+ ),
1671
+ )?;
1672
+ new_row.set(
1673
+ path_var,
1674
+ crate::value::Value::SinglePath(
1675
+ p.with_graph_name(graph_name.to_owned()).into(),
1676
+ ),
1677
+ )?;
1678
+ }
1679
+ (None, Some(path_var)) =>
1680
+ {
1681
+ new_row.set(path_var, crate::value::Value::SinglePath(p))?;
1682
+ }
1683
+ (Some(edge_var), None) =>
1684
+ {
1685
+ new_row.set(
1686
+ edge_var,
1687
+ crate::value::Value::Edge(
1688
+ p.into_edge().with_graph_name(graph_name.to_owned()),
1689
+ ),
1690
+ )?;
1691
+ }
1692
+ (None, None) =>
1693
+ {}
1281
1694
  }
1282
1695
  }
1283
1696
  _ =>
@@ -1304,111 +1717,16 @@ pub(crate) fn eval_program<TStore: store::Store>(
1304
1717
  let mut current_rows: Vec<_> = vec![row.clone().extended(variables_size.total_size())?];
1305
1718
  for block in blocks.iter()
1306
1719
  {
1307
- let mut new_rows = Vec::<value_table::Row>::default();
1308
- for row in current_rows
1309
- {
1310
- match block
1311
- {
1312
- instructions::BlockMatch::MatchNode {
1313
- instructions,
1314
- variable,
1315
- filter,
1316
- } =>
1317
- {
1318
- eval_instructions(&mut stack, &row, instructions, parameters)?;
1319
- let query: store::SelectNodeQuery = stack.try_pop_into()?;
1320
- let nodes = store.select_nodes(&mut tx, &graph_name, query)?;
1321
-
1322
- for node in nodes.into_iter()
1323
- {
1324
- let mut new_row = row.clone();
1325
- if let Some(variable) = variable
1326
- {
1327
- new_row.set_if_unset(*variable, node.to_owned().into())?;
1328
- }
1329
- let should_add_row = if filter.is_empty()
1330
- {
1331
- true
1332
- }
1333
- else
1334
- {
1335
- let mut stack = Stack::default();
1336
- stack.push(true);
1337
- stack.push(node);
1338
- eval_instructions(&mut stack, &new_row, filter, parameters)?;
1339
- stack.try_pop()?; // Get rid of the edge
1340
- stack.try_pop_into()?
1341
- };
1342
- if should_add_row
1343
- {
1344
- new_rows.push(new_row);
1345
- }
1346
- }
1347
- }
1348
- instructions::BlockMatch::MatchEdge {
1349
- instructions,
1350
- left_variable,
1351
- edge_variable,
1352
- right_variable,
1353
- path_variable,
1354
- filter,
1355
- directivity,
1356
- } =>
1357
- {
1358
- eval_instructions(&mut stack, &row, instructions, parameters)?;
1359
- let query = stack.try_pop_into()?;
1360
-
1361
- let edges = store.select_edges(&mut tx, &graph_name, query, *directivity)?;
1362
-
1363
- for edge in edges.into_iter()
1364
- {
1365
- let mut new_row = row.clone();
1366
- let (left, right) = if edge.reversed
1367
- {
1368
- (edge.path.destination(), edge.path.source())
1369
- }
1370
- else
1371
- {
1372
- (edge.path.source(), edge.path.destination())
1373
- };
1374
- if let Some(left_variable) = left_variable.to_owned()
1375
- {
1376
- new_row.set(left_variable, left.to_owned().into())?;
1377
- }
1378
- if let Some(right_variable) = right_variable.to_owned()
1379
- {
1380
- new_row.set(right_variable, right.to_owned().into())?;
1381
- }
1382
- if let Some(edge_variable) = edge_variable.to_owned()
1383
- {
1384
- new_row.set(edge_variable, edge.path.to_edge().into())?;
1385
- }
1386
- if let Some(path_variable) = path_variable.to_owned()
1387
- {
1388
- new_row.set(path_variable, edge.path.to_owned().into())?;
1389
- }
1390
- let should_add_row = if filter.is_empty()
1391
- {
1392
- true
1393
- }
1394
- else
1395
- {
1396
- let mut stack = Stack::default();
1397
- stack.push(true);
1398
- stack.push(edge.path.into_edge());
1399
- eval_instructions(&mut stack, &new_row, filter, parameters)?;
1400
- stack.try_pop()?; // Get rid of the edge
1401
- stack.try_pop_into()?
1402
- };
1403
- if should_add_row
1404
- {
1405
- new_rows.push(new_row);
1406
- }
1407
- }
1408
- }
1409
- }
1410
- }
1411
- current_rows = new_rows;
1720
+ current_rows = execute_block_match(
1721
+ block,
1722
+ store,
1723
+ &mut tx,
1724
+ &graph_name,
1725
+ &mut stack,
1726
+ parameters,
1727
+ current_rows,
1728
+ true, // recursive patterns allowed in MATCH
1729
+ )?;
1412
1730
  }
1413
1731
  if !filter.is_empty()
1414
1732
  {
@@ -1445,7 +1763,7 @@ pub(crate) fn eval_program<TStore: store::Store>(
1445
1763
  let mut data = Vec::<crate::value::Value>::new();
1446
1764
  for row in output_table.into_row_iter()
1447
1765
  {
1448
- data.extend(row.into_iter());
1766
+ data.extend(row);
1449
1767
  }
1450
1768
  tx.close()?;
1451
1769
  return Ok(graphcore::Table::new(headers, data)?.into());
@@ -1517,8 +1835,8 @@ pub(crate) fn eval_program<TStore: store::Store>(
1517
1835
  let value: value::Value = stack.try_pop_into()?;
1518
1836
  match value
1519
1837
  {
1520
- value::Value::Node(node) => nodes_keys.push(node.unpack().0),
1521
- value::Value::Edge(edge) => edges_keys.push(edge.unpack().0),
1838
+ value::Value::Node(node) => nodes_keys.push(node.key()),
1839
+ value::Value::Edge(edge) => edges_keys.push(edge.key()),
1522
1840
  value::Value::Null =>
1523
1841
  {}
1524
1842
  _ => return Err(RunTimeError::InvalidDelete.into()),
@@ -1530,7 +1848,7 @@ pub(crate) fn eval_program<TStore: store::Store>(
1530
1848
  &mut tx,
1531
1849
  &graph_name,
1532
1850
  store::SelectEdgeQuery::select_keys(edges_keys),
1533
- graph::EdgeDirectivity::Directed,
1851
+ graphcore::EdgeDirectivity::Undirected,
1534
1852
  )?;
1535
1853
  store.delete_nodes(
1536
1854
  &mut tx,
@@ -1610,7 +1928,7 @@ pub(crate) fn eval_program<TStore: store::Store>(
1610
1928
  }
1611
1929
  value::Value::Null =>
1612
1930
  {}
1613
- _ => Err(InternalError::ExpectedEdge {
1931
+ _ => Err(RunTimeError::ExpectedEdge {
1614
1932
  context: "evaluator/eval_program",
1615
1933
  })?,
1616
1934
  }
@@ -1660,7 +1978,7 @@ pub(crate) fn eval_program<TStore: store::Store>(
1660
1978
  }
1661
1979
  value::Value::Null =>
1662
1980
  {}
1663
- _ => Err(InternalError::ExpectedEdge {
1981
+ _ => Err(RunTimeError::ExpectedEdge {
1664
1982
  context: "evaluator/eval_program",
1665
1983
  })?,
1666
1984
  }
@@ -1671,6 +1989,383 @@ pub(crate) fn eval_program<TStore: store::Store>(
1671
1989
  }
1672
1990
  input_table = output_table;
1673
1991
  }
1992
+ instructions::Block::Merge {
1993
+ match_blocks,
1994
+ match_filter,
1995
+ create_actions,
1996
+ on_create_updates,
1997
+ on_match_updates,
1998
+ variables_size,
1999
+ } =>
2000
+ {
2001
+ let mut output_table = value_table::ValueTable::new(variables_size.persistent_variables);
2002
+
2003
+ for row in input_table.into_row_iter()
2004
+ {
2005
+ // Step 1: Try to match the pattern using the compiled match blocks
2006
+ let mut current_rows: Vec<_> = vec![row.clone().extended(variables_size.total_size())?];
2007
+
2008
+ for block in match_blocks.iter()
2009
+ {
2010
+ current_rows = execute_block_match(
2011
+ block,
2012
+ store,
2013
+ &mut tx,
2014
+ &graph_name,
2015
+ &mut stack,
2016
+ parameters,
2017
+ current_rows,
2018
+ false,
2019
+ )?;
2020
+ }
2021
+
2022
+ // Apply WHERE filter
2023
+ let matched_rows = if !match_filter.is_empty()
2024
+ {
2025
+ filter_rows(current_rows.into_iter(), match_filter, parameters)?
2026
+ }
2027
+ else
2028
+ {
2029
+ current_rows
2030
+ };
2031
+
2032
+ if matched_rows.is_empty()
2033
+ {
2034
+ // Step 2: No match - create new entities
2035
+ let mut new_row = row.extended(variables_size.total_size())?;
2036
+ for action in create_actions.iter()
2037
+ {
2038
+ eval_instructions(&mut stack, &new_row, &action.instructions, parameters)?;
2039
+ for (v, var) in stack
2040
+ .try_drain_into(action.variables.len())?
2041
+ .into_iter()
2042
+ .zip(action.variables.iter())
2043
+ {
2044
+ match v
2045
+ {
2046
+ crate::value::Value::Node(n) =>
2047
+ {
2048
+ if n.properties().values().any(|v| v.is_null())
2049
+ {
2050
+ return Err(RunTimeError::MergeReadOwnWrites.into());
2051
+ }
2052
+ store.create_nodes(&mut tx, &graph_name, vec![&n])?;
2053
+ if let Some(var) = var.element
2054
+ {
2055
+ new_row.set_if_unset(
2056
+ var,
2057
+ crate::value::Value::Node(n.with_graph_name(graph_name.to_owned())),
2058
+ )?;
2059
+ }
2060
+ }
2061
+ crate::value::Value::SinglePath(p) =>
2062
+ {
2063
+ if p.to_edge().properties().values().any(|v| v.is_null())
2064
+ {
2065
+ return Err(RunTimeError::MergeReadOwnWrites.into());
2066
+ }
2067
+ store.create_edges(&mut tx, &graph_name, vec![p.as_ref()])?;
2068
+
2069
+ match (var.element, var.path)
2070
+ {
2071
+ (Some(edge_var), Some(path_var)) =>
2072
+ {
2073
+ new_row.set(
2074
+ edge_var,
2075
+ crate::value::Value::Edge(
2076
+ p.clone().into_edge().with_graph_name(graph_name.to_owned()),
2077
+ ),
2078
+ )?;
2079
+ new_row.set(
2080
+ path_var,
2081
+ crate::value::Value::SinglePath(
2082
+ p.with_graph_name(graph_name.to_owned()).into(),
2083
+ ),
2084
+ )?;
2085
+ }
2086
+ (None, Some(path_var)) =>
2087
+ {
2088
+ new_row.set(path_var, crate::value::Value::SinglePath(p))?;
2089
+ }
2090
+ (Some(edge_var), None) =>
2091
+ {
2092
+ new_row.set(
2093
+ edge_var,
2094
+ crate::value::Value::Edge(
2095
+ p.into_edge().with_graph_name(graph_name.to_owned()),
2096
+ ),
2097
+ )?;
2098
+ }
2099
+ (None, None) =>
2100
+ {}
2101
+ }
2102
+ }
2103
+ _ =>
2104
+ {
2105
+ return Err(InternalError::Unimplemented("executor/eval/create").into());
2106
+ }
2107
+ }
2108
+ }
2109
+ }
2110
+
2111
+ // Apply on_create updates
2112
+ for update in on_create_updates.iter()
2113
+ {
2114
+ match update
2115
+ {
2116
+ instructions::UpdateOne::SetProperty {
2117
+ target,
2118
+ path,
2119
+ instructions,
2120
+ } =>
2121
+ {
2122
+ eval_update_property(
2123
+ store,
2124
+ &mut tx,
2125
+ &graph_name,
2126
+ &mut new_row,
2127
+ *target,
2128
+ path,
2129
+ instructions,
2130
+ parameters,
2131
+ true,
2132
+ )?;
2133
+ }
2134
+ instructions::UpdateOne::AddProperty {
2135
+ target,
2136
+ path,
2137
+ instructions,
2138
+ } =>
2139
+ {
2140
+ eval_update_property(
2141
+ store,
2142
+ &mut tx,
2143
+ &graph_name,
2144
+ &mut new_row,
2145
+ *target,
2146
+ path,
2147
+ instructions,
2148
+ parameters,
2149
+ false,
2150
+ )?;
2151
+ }
2152
+ instructions::UpdateOne::RemoveProperty { target, path } =>
2153
+ {
2154
+ let var = new_row.get(*target)?;
2155
+ let mut piter = path.iter();
2156
+ match var
2157
+ {
2158
+ value::Value::Node(n) =>
2159
+ {
2160
+ let mut n = n.to_owned();
2161
+ n.properties_mut().remove_value(piter.next(), piter)?;
2162
+ store.update_node(&mut tx, &graph_name, &n)?;
2163
+ new_row.set(*target, n.into())?;
2164
+ }
2165
+ value::Value::Edge(e) =>
2166
+ {
2167
+ let mut e = e.to_owned();
2168
+ e.properties_mut().remove_value(piter.next(), piter)?;
2169
+ store.update_edge(&mut tx, &graph_name, &e)?;
2170
+ new_row.set(*target, e.into())?;
2171
+ }
2172
+ value::Value::Null =>
2173
+ {}
2174
+ _ => Err(RunTimeError::ExpectedEdge {
2175
+ context: "evaluator/eval_program",
2176
+ })?,
2177
+ }
2178
+ }
2179
+ instructions::UpdateOne::AddLabels { target, labels }
2180
+ | instructions::UpdateOne::RemoveLabels { target, labels } =>
2181
+ {
2182
+ let add_labels = match update
2183
+ {
2184
+ instructions::UpdateOne::AddLabels { .. } => true,
2185
+ instructions::UpdateOne::RemoveLabels { .. } => false,
2186
+ _ => Err(InternalError::Unreachable {
2187
+ context: "evaluator/eval_program/add_remove_labels",
2188
+ })?,
2189
+ };
2190
+
2191
+ let var = new_row.get(*target)?;
2192
+ match var
2193
+ {
2194
+ value::Value::Node(n) =>
2195
+ {
2196
+ let mut n = n.to_owned();
2197
+ if add_labels
2198
+ {
2199
+ n.labels_mut().append(&mut labels.clone());
2200
+ }
2201
+ else
2202
+ {
2203
+ n.labels_edit(|l| l.into_iter().filter(|x| !labels.contains(x)).collect());
2204
+ }
2205
+ store.update_node(&mut tx, &graph_name, &n)?;
2206
+ new_row.set(*target, n.into())?;
2207
+ }
2208
+ value::Value::Edge(e) =>
2209
+ {
2210
+ let mut e = e.to_owned();
2211
+ if add_labels
2212
+ {
2213
+ e.labels_mut().append(&mut labels.clone());
2214
+ }
2215
+ else
2216
+ {
2217
+ e.labels_edit(|l| l.into_iter().filter(|x| !labels.contains(x)).collect());
2218
+ }
2219
+ store.update_edge(&mut tx, &graph_name, &e)?;
2220
+ new_row.set(*target, e.into())?;
2221
+ }
2222
+ value::Value::Null =>
2223
+ {}
2224
+ _ => Err(RunTimeError::ExpectedEdge {
2225
+ context: "evaluator/eval_program",
2226
+ })?,
2227
+ }
2228
+ }
2229
+ }
2230
+ }
2231
+
2232
+ output_table.add_truncated_row(new_row)?;
2233
+ }
2234
+ else
2235
+ {
2236
+ // Step 3: Match succeeded - apply on_match updates to each matched row
2237
+ for mut matched_row in matched_rows
2238
+ {
2239
+ for update in on_match_updates.iter()
2240
+ {
2241
+ match update
2242
+ {
2243
+ instructions::UpdateOne::SetProperty {
2244
+ target,
2245
+ path,
2246
+ instructions,
2247
+ } =>
2248
+ {
2249
+ eval_update_property(
2250
+ store,
2251
+ &mut tx,
2252
+ &graph_name,
2253
+ &mut matched_row,
2254
+ *target,
2255
+ path,
2256
+ instructions,
2257
+ parameters,
2258
+ true,
2259
+ )?;
2260
+ }
2261
+ instructions::UpdateOne::AddProperty {
2262
+ target,
2263
+ path,
2264
+ instructions,
2265
+ } =>
2266
+ {
2267
+ eval_update_property(
2268
+ store,
2269
+ &mut tx,
2270
+ &graph_name,
2271
+ &mut matched_row,
2272
+ *target,
2273
+ path,
2274
+ instructions,
2275
+ parameters,
2276
+ false,
2277
+ )?;
2278
+ }
2279
+ instructions::UpdateOne::RemoveProperty { target, path } =>
2280
+ {
2281
+ let var = matched_row.get(*target)?;
2282
+ let mut piter = path.iter();
2283
+ match var
2284
+ {
2285
+ value::Value::Node(n) =>
2286
+ {
2287
+ let mut n = n.to_owned();
2288
+ n.properties_mut().remove_value(piter.next(), piter)?;
2289
+ store.update_node(&mut tx, &graph_name, &n)?;
2290
+ matched_row.set(*target, n.into())?;
2291
+ }
2292
+ value::Value::Edge(e) =>
2293
+ {
2294
+ let mut e = e.to_owned();
2295
+ e.properties_mut().remove_value(piter.next(), piter)?;
2296
+ store.update_edge(&mut tx, &graph_name, &e)?;
2297
+ matched_row.set(*target, e.into())?;
2298
+ }
2299
+ value::Value::Null =>
2300
+ {}
2301
+ _ => Err(RunTimeError::ExpectedEdge {
2302
+ context: "evaluator/eval_program",
2303
+ })?,
2304
+ }
2305
+ }
2306
+ instructions::UpdateOne::AddLabels { target, labels }
2307
+ | instructions::UpdateOne::RemoveLabels { target, labels } =>
2308
+ {
2309
+ let add_labels = match update
2310
+ {
2311
+ instructions::UpdateOne::AddLabels { .. } => true,
2312
+ instructions::UpdateOne::RemoveLabels { .. } => false,
2313
+ _ => Err(InternalError::Unreachable {
2314
+ context: "evaluator/eval_program/add_remove_labels",
2315
+ })?,
2316
+ };
2317
+
2318
+ let var = matched_row.get(*target)?;
2319
+ match var
2320
+ {
2321
+ value::Value::Node(n) =>
2322
+ {
2323
+ let mut n = n.to_owned();
2324
+ if add_labels
2325
+ {
2326
+ n.labels_mut().append(&mut labels.clone());
2327
+ }
2328
+ else
2329
+ {
2330
+ n.labels_edit(|l| {
2331
+ l.into_iter().filter(|x| !labels.contains(x)).collect()
2332
+ });
2333
+ }
2334
+ store.update_node(&mut tx, &graph_name, &n)?;
2335
+ matched_row.set(*target, n.into())?;
2336
+ }
2337
+ value::Value::Edge(e) =>
2338
+ {
2339
+ let mut e = e.to_owned();
2340
+ if add_labels
2341
+ {
2342
+ e.labels_mut().append(&mut labels.clone());
2343
+ }
2344
+ else
2345
+ {
2346
+ e.labels_edit(|l| {
2347
+ l.into_iter().filter(|x| !labels.contains(x)).collect()
2348
+ });
2349
+ }
2350
+ store.update_edge(&mut tx, &graph_name, &e)?;
2351
+ matched_row.set(*target, e.into())?;
2352
+ }
2353
+ value::Value::Null =>
2354
+ {}
2355
+ _ => Err(RunTimeError::ExpectedEdge {
2356
+ context: "evaluator/eval_program",
2357
+ })?,
2358
+ }
2359
+ }
2360
+ }
2361
+ }
2362
+ output_table.add_truncated_row(matched_row)?;
2363
+ }
2364
+ }
2365
+ }
2366
+
2367
+ input_table = output_table;
2368
+ }
1674
2369
  instructions::Block::Call { arguments: _, name } =>
1675
2370
  {
1676
2371
  if name == "gqlite.internal.stats"
@@ -1694,6 +2389,149 @@ pub(crate) fn eval_program<TStore: store::Store>(
1694
2389
  return Err(InternalError::Unimplemented("call for any other function").into());
1695
2390
  }
1696
2391
  }
2392
+ instructions::Block::VectorSearch {
2393
+ index,
2394
+ query_instructions,
2395
+ k,
2396
+ out_node_col_id,
2397
+ out_score_col_id,
2398
+ variables_size,
2399
+ } =>
2400
+ {
2401
+ let mut output_table = value_table::ValueTable::new(variables_size.persistent_variables);
2402
+ for row in input_table.into_row_iter()
2403
+ {
2404
+ let mut query_stack = Stack::default();
2405
+ eval_instructions(&mut query_stack, &row, query_instructions, parameters)?;
2406
+
2407
+ let query_tensor: value::Tensor = query_stack.try_pop_into()?;
2408
+
2409
+ let index_dimension = store
2410
+ .list_vector_indexes(&mut tx, &graph_name)?
2411
+ .into_iter()
2412
+ .find(|spec| spec.name == *index)
2413
+ .ok_or_else(|| {
2414
+ StoreError::InvalidFormat(format!(
2415
+ "Vector index '{}' does not exist in graph '{}'",
2416
+ index, graph_name
2417
+ ))
2418
+ })?
2419
+ .dimension;
2420
+ if query_tensor.len() != index_dimension
2421
+ {
2422
+ return Err(
2423
+ StoreError::InvalidFormat(format!(
2424
+ "Vector dimension {} does not match index dimension {}",
2425
+ query_tensor.len(),
2426
+ index_dimension
2427
+ ))
2428
+ .into(),
2429
+ );
2430
+ }
2431
+
2432
+ // Perform vector search
2433
+ let results = store.vector_search(&mut tx, &graph_name, index, &query_tensor, *k)?;
2434
+
2435
+ // Batch fetch all nodes using select_nodes by keys
2436
+ let node_keys: Vec<crate::graph::Key> = results.iter().map(|r| r.node_id).collect();
2437
+ let mut nodes_by_key = std::collections::HashMap::new();
2438
+ if !node_keys.is_empty()
2439
+ {
2440
+ let nodes = store.select_nodes(
2441
+ &mut tx,
2442
+ &graph_name,
2443
+ SelectNodeQuery::select_keys(node_keys),
2444
+ )?;
2445
+ for node in nodes
2446
+ {
2447
+ nodes_by_key.insert(node.key(), node);
2448
+ }
2449
+ }
2450
+
2451
+ // Add result rows to output table
2452
+ for result in results
2453
+ {
2454
+ let mut out_row = row.clone().extended(variables_size.total_size())?;
2455
+ // Look up the node from our pre-fetched map; skip the row if the node no longer
2456
+ // exists (stale vector index entry from a deleted node)
2457
+ if let Some(node) = nodes_by_key.get(&result.node_id)
2458
+ {
2459
+ out_row.set(*out_node_col_id, value::Value::Node(node.clone()))?;
2460
+ if let Some(score_col_id) = out_score_col_id
2461
+ {
2462
+ out_row.set(*score_col_id, (result.score as f64).into())?;
2463
+ }
2464
+ output_table.add_truncated_row(out_row)?;
2465
+ }
2466
+ }
2467
+ }
2468
+ input_table = output_table;
2469
+ }
2470
+ instructions::Block::CreateVectorIndex {
2471
+ name,
2472
+ label,
2473
+ property,
2474
+ dimension,
2475
+ metric,
2476
+ if_not_exists,
2477
+ } =>
2478
+ {
2479
+ let vector_metric = match metric
2480
+ {
2481
+ Some(gqlparser::oc::ast::Metric::Cosine) | None => store::VectorMetric::Cosine,
2482
+ Some(gqlparser::oc::ast::Metric::L2) => store::VectorMetric::L2,
2483
+ Some(gqlparser::oc::ast::Metric::DotProduct) => store::VectorMetric::DotProduct,
2484
+ };
2485
+ let spec = store::VectorIndexSpec {
2486
+ name: name.clone(),
2487
+ label: Some(label.clone()),
2488
+ property: Some(property.clone()),
2489
+ dimension: dimension.unwrap_or(0),
2490
+ metric: vector_metric,
2491
+ hnsw_params: Default::default(),
2492
+ dtype: value::DType::F32,
2493
+ };
2494
+
2495
+ let result = store.create_vector_index(&mut tx, &graph_name, spec);
2496
+ match result
2497
+ {
2498
+ Ok(()) =>
2499
+ {
2500
+ let existing_nodes = store.select_nodes(
2501
+ &mut tx,
2502
+ &graph_name,
2503
+ store::SelectNodeQuery::select_labels(vec![label.clone()]),
2504
+ )?;
2505
+
2506
+ for node in existing_nodes
2507
+ {
2508
+ store.apply_vector_indices_for_node(&mut tx, &graph_name, &node)?;
2509
+ }
2510
+ }
2511
+ Err(e) =>
2512
+ {
2513
+ if *if_not_exists
2514
+ {
2515
+ if let Error::StoreError(StoreError::DuplicatedVectorIndex { .. }) = e.error()
2516
+ {
2517
+ // silently skip — index already exists
2518
+ }
2519
+ else
2520
+ {
2521
+ return Err(e);
2522
+ }
2523
+ }
2524
+ else
2525
+ {
2526
+ return Err(
2527
+ error::map_error!(e, Error::StoreError(StoreError::DuplicatedVectorIndex { index_name }) => RunTimeError::DuplicatedVectorIndex {
2528
+ index_name: index_name.clone(),
2529
+ }),
2530
+ );
2531
+ }
2532
+ }
2533
+ }
2534
+ }
1697
2535
  }
1698
2536
  }
1699
2537
  tx.close()?;
@@ -1708,7 +2546,7 @@ mod tests
1708
2546
  prelude::*,
1709
2547
  };
1710
2548
 
1711
- use super::{execute_boolean_operator, TryPopInto};
2549
+ use super::{TryPopInto, execute_boolean_operator};
1712
2550
 
1713
2551
  fn test_execute_boolean_operator_(
1714
2552
  instruction: &interpreter::instructions::Instruction,