gqlite 1.8.0 → 1.8.2

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Cargo.toml +8 -8
  3. data/ext/db-index/Cargo.toml +2 -2
  4. data/ext/gqlitedb/Cargo.toml +0 -2
  5. data/ext/gqlitedb/src/aggregators.rs +28 -0
  6. data/ext/gqlitedb/src/compiler/expression_analyser.rs +33 -0
  7. data/ext/gqlitedb/src/compiler/variables_manager.rs +111 -11
  8. data/ext/gqlitedb/src/compiler.rs +506 -48
  9. data/ext/gqlitedb/src/connection.rs +14 -2
  10. data/ext/gqlitedb/src/consts.rs +1 -1
  11. data/ext/gqlitedb/src/error.rs +31 -0
  12. data/ext/gqlitedb/src/interpreter/evaluators.rs +153 -331
  13. data/ext/gqlitedb/src/interpreter/instructions.rs +77 -0
  14. data/ext/gqlitedb/src/interpreter/mod.rs +1 -0
  15. data/ext/gqlitedb/src/interpreter/path/bfs.rs +494 -0
  16. data/ext/gqlitedb/src/interpreter/path/dfs.rs +267 -0
  17. data/ext/gqlitedb/src/interpreter/path/group.rs +418 -0
  18. data/ext/gqlitedb/src/interpreter/path.rs +542 -0
  19. data/ext/gqlitedb/src/planner.rs +231 -6
  20. data/ext/gqlitedb/src/query_result.rs +19 -4
  21. data/ext/gqlitedb/src/store/redb.rs +1 -1
  22. data/ext/gqlitedb/src/tests/compiler.rs +2454 -1
  23. data/ext/gqlitedb/src/tests/connection/postgres.rs +22 -0
  24. data/ext/gqlitedb/src/tests/connection/redb.rs +24 -0
  25. data/ext/gqlitedb/src/tests/connection/sqlite.rs +16 -0
  26. data/ext/gqlitedb/src/tests/connection.rs +101 -0
  27. data/ext/gqlitedb/src/tests/evaluators.rs +2206 -1
  28. data/ext/gqlitedb/src/tests/parser.rs +1225 -0
  29. data/ext/gqlitedb/src/tests/planner.rs +433 -2
  30. data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +5 -5
  31. data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +3 -1
  32. data/ext/gqlitedb/src/tests/templates/ast.rs +294 -1
  33. data/ext/gqlitedb/src/tests/templates/programs.rs +318 -0
  34. data/ext/gqliterb/Cargo.toml +0 -1
  35. data/ext/gqlparser/src/oc/ast.rs +72 -3
  36. data/ext/gqlparser/src/oc/error.rs +18 -0
  37. data/ext/gqlparser/src/oc/lexer.rs +157 -2
  38. data/ext/gqlparser/src/oc/parser/tests.rs +735 -29
  39. data/ext/gqlparser/src/oc/parser.rs +1312 -182
  40. data/ext/gqlparser/src/oc/string.rs +172 -0
  41. data/ext/gqlparser/src/oc.rs +1 -0
  42. data/ext/graphcore/src/value/value_map.rs +2 -2
  43. data/ext/graphcore/src/value.rs +13 -1
  44. metadata +6 -2
  45. data/ext/gqlitedb/src/parser/gql.pest +0 -198
@@ -32,8 +32,8 @@ impl ConnectionBuilder
32
32
  /// Merge options. This might overwrite value from the builder
33
33
  pub fn options(mut self, options: impl Into<value::ValueMap>) -> Self
34
34
  {
35
- let mut options = options.into();
36
- self.map.extend(options.drain());
35
+ let options = options.into();
36
+ self.map.extend(options);
37
37
  self
38
38
  }
39
39
  /// Set the option value for the given key.
@@ -122,6 +122,18 @@ where
122
122
  text: query.substring(e.span().start, e.span().end).to_string(),
123
123
  span: e.span().clone(),
124
124
  },
125
+ ErrorKind::CreatingWithPredicate => error::CompileTimeError::CreatingWithPredicate {
126
+ text: query.substring(e.span().start, e.span().end).to_string(),
127
+ span: e.span().clone(),
128
+ },
129
+ ErrorKind::ConflictingQuantifiers => error::CompileTimeError::ConflictingQuantifiers {
130
+ text: query.substring(e.span().start, e.span().end).to_string(),
131
+ span: e.span().clone(),
132
+ },
133
+ ErrorKind::EmptyQuantifiedGroup => error::CompileTimeError::EmptyQuantifiedGroup {
134
+ text: query.substring(e.span().start, e.span().end).to_string(),
135
+ span: e.span().clone(),
136
+ },
125
137
  _ => error::CompileTimeError::ParseError {
126
138
  error_kind: e.error_kind().clone(),
127
139
  text: query.substring(e.span().start, e.span().end).to_string(),
@@ -5,5 +5,5 @@ pub(crate) const SHOW_EVALUATOR_STATE: bool = false;
5
5
  pub(crate) const GQLITE_VERSION: crate::utils::Version = crate::utils::Version {
6
6
  major: 1,
7
7
  minor: 8,
8
- patch: 0,
8
+ patch: 2,
9
9
  };
@@ -71,6 +71,30 @@ pub enum CompileTimeError
71
71
  text: String,
72
72
  span: std::ops::Range<usize>,
73
73
  },
74
+ #[error(
75
+ "CreatingWithPredicate: CREATE patterns cannot use an inline WHERE predicate at: '{text}' at {span:?}."
76
+ )]
77
+ CreatingWithPredicate
78
+ {
79
+ text: String,
80
+ span: std::ops::Range<usize>,
81
+ },
82
+ #[error(
83
+ "ConflictingQuantifiers: cannot specify both a legacy `[*min..max]` range and a `{{min,max}}`/`*`/`+` quantifier on the same relationship at: '{text}' at {span:?}."
84
+ )]
85
+ ConflictingQuantifiers
86
+ {
87
+ text: String,
88
+ span: std::ops::Range<usize>,
89
+ },
90
+ #[error(
91
+ "EmptyQuantifiedGroup: a quantified group must contain at least one relationship at: '{text}' at {span:?}."
92
+ )]
93
+ EmptyQuantifiedGroup
94
+ {
95
+ text: String,
96
+ span: std::ops::Range<usize>,
97
+ },
74
98
  #[error("NotComparable: values are not comparable.")]
75
99
  NotComparable,
76
100
  #[error("UnknownFunction: {name}.")]
@@ -78,6 +102,13 @@ pub enum CompileTimeError
78
102
  {
79
103
  name: String
80
104
  },
105
+ #[error(
106
+ "DistinctOnNonAggregateFunction: DISTINCT can only be used with aggregate functions, got '{name}'."
107
+ )]
108
+ DistinctOnNonAggregateFunction
109
+ {
110
+ name: String
111
+ },
81
112
  #[error("InvalidAggregation: aggregation is not accepted in this expression.")]
82
113
  InvalidAggregation,
83
114
  #[error("ColumnNameConflict: Column '{name}' is duplicated.")]
@@ -1,9 +1,10 @@
1
- use std::collections::HashMap;
2
-
3
- use rustc_hash::FxHashSet;
1
+ use std::collections::{HashMap, HashSet};
4
2
 
5
3
  use crate::{
6
- interpreter::instructions::{Block, VariablesSizes},
4
+ interpreter::{
5
+ instructions::{Block, VariablesSizes},
6
+ path,
7
+ },
7
8
  prelude::*,
8
9
  store::{SelectNodeQuery, TransactionBoxable},
9
10
  value_table::{MutableRowInterface, Row, RowInterface},
@@ -12,7 +13,7 @@ use interpreter::instructions;
12
13
 
13
14
  #[derive(Debug, Clone)]
14
15
  #[allow(clippy::enum_variant_names)]
15
- enum Value
16
+ pub(crate) enum Value
16
17
  {
17
18
  GraphValue(value::Value),
18
19
  NodeQuery(store::SelectNodeQuery),
@@ -157,18 +158,18 @@ macro_rules! contain_to_value {
157
158
  }
158
159
 
159
160
  #[derive(Default, Debug)]
160
- struct Stack
161
+ pub(crate) struct Stack
161
162
  {
162
163
  stack: Vec<Value>,
163
164
  }
164
165
 
165
166
  impl Stack
166
167
  {
167
- fn push(&mut self, value: impl Into<Value>)
168
+ pub(crate) fn push(&mut self, value: impl Into<Value>)
168
169
  {
169
170
  self.stack.push(value.into());
170
171
  }
171
- fn try_pop(&mut self) -> Result<Value>
172
+ pub(crate) fn try_pop(&mut self) -> Result<Value>
172
173
  {
173
174
  self.stack.pop().ok_or(InternalError::EmptyStack.into())
174
175
  }
@@ -191,7 +192,7 @@ impl Stack
191
192
  {
192
193
  self.stack
193
194
  }
194
- fn try_pop_as_boolean(&mut self) -> Result<bool>
195
+ pub(crate) fn try_pop_as_boolean(&mut self) -> Result<bool>
195
196
  {
196
197
  let v = self.try_pop()?;
197
198
  match v
@@ -205,7 +206,7 @@ impl Stack
205
206
  }
206
207
  }
207
208
 
208
- trait TryPopInto<T>
209
+ pub(crate) trait TryPopInto<T>
209
210
  {
210
211
  fn try_pop_into(&mut self) -> Result<T>;
211
212
  fn try_drain_into(&mut self, n: usize) -> Result<Vec<T>>;
@@ -333,7 +334,28 @@ fn execute_binary_operator<T: Into<crate::value::Value>>(
333
334
  Ok(())
334
335
  }
335
336
 
336
- fn eval_instructions(
337
+ fn execute_string_operator(
338
+ stack: &mut Stack,
339
+ operand: impl FnOnce(String, String) -> bool,
340
+ ) -> Result<()>
341
+ {
342
+ let a: Value = stack.try_pop()?;
343
+ let b: Value = stack.try_pop()?;
344
+
345
+ let a: graphcore::Value = a.try_into()?;
346
+ let b: graphcore::Value = b.try_into()?;
347
+
348
+ let a = a.try_into();
349
+ let b = b.try_into();
350
+ match (a, b)
351
+ {
352
+ (Ok(a), Ok(b)) => stack.push(operand(a, b)),
353
+ _ => stack.push(graphcore::Value::Null),
354
+ }
355
+ Ok(())
356
+ }
357
+
358
+ pub(crate) fn eval_instructions(
337
359
  stack: &mut Stack,
338
360
  row: &impl value_table::RowInterface,
339
361
  instructions: &instructions::Instructions,
@@ -807,6 +829,18 @@ fn eval_instructions(
807
829
  {
808
830
  execute_binary_operator(stack, |a, b| a.pow(b))?;
809
831
  }
832
+ &instructions::Instruction::StringContainsOperator =>
833
+ {
834
+ execute_string_operator(stack, |a, b| a.contains(&b))?;
835
+ }
836
+ &instructions::Instruction::StringStartsWithOperator =>
837
+ {
838
+ execute_string_operator(stack, |a, b| a.starts_with(&b))?;
839
+ }
840
+ &instructions::Instruction::StringEndsWithOperator =>
841
+ {
842
+ execute_string_operator(stack, |a, b| a.ends_with(&b))?;
843
+ }
810
844
  }
811
845
  }
812
846
  Ok(())
@@ -921,13 +955,20 @@ fn create_aggregations_states(
921
955
  &agg.init_instructions,
922
956
  parameters,
923
957
  )?;
924
- let state = agg.aggregator.create(
958
+ let mut state = agg.aggregator.create(
925
959
  stack
926
960
  .into_vec()
927
961
  .into_iter()
928
962
  .map(|v| v.try_into())
929
963
  .collect::<Result<_>>()?,
930
964
  )?;
965
+ if agg.distinct
966
+ {
967
+ state = Box::new(aggregators::DistinctAggregatorState {
968
+ inner: state,
969
+ seen: Vec::new(),
970
+ });
971
+ }
931
972
 
932
973
  Ok((name.to_owned(), state))
933
974
  })
@@ -1059,7 +1100,6 @@ fn compute_return_with_table(
1059
1100
  {
1060
1101
  output_table = filter_rows(output_table.into_row_iter(), filter, parameters)?.try_into()?;
1061
1102
  }
1062
- // Apply modifiers
1063
1103
  // Sort the table according to order_by
1064
1104
  if !modifiers.order_by.is_empty()
1065
1105
  {
@@ -1125,7 +1165,7 @@ fn compute_return_with_table(
1125
1165
  }
1126
1166
  }
1127
1167
 
1128
- output_table
1168
+ let output_table_iter = output_table
1129
1169
  .into_row_iter()
1130
1170
  .map(|mut row| {
1131
1171
  Ok(Row::new(
@@ -1136,8 +1176,25 @@ fn compute_return_with_table(
1136
1176
  0,
1137
1177
  ))
1138
1178
  })
1139
- .map(value_table::RowResult)
1140
- .collect()
1179
+ .map(value_table::RowResult);
1180
+
1181
+ // Apply modifiers
1182
+ if modifiers.distinct
1183
+ {
1184
+ let mut seen = HashSet::new();
1185
+
1186
+ output_table_iter
1187
+ .filter(|row| match &row.0
1188
+ {
1189
+ Ok(row) => seen.insert(RowKey(row.clone())),
1190
+ Err(_) => true,
1191
+ })
1192
+ .collect()
1193
+ }
1194
+ else
1195
+ {
1196
+ output_table_iter.collect()
1197
+ }
1141
1198
  }
1142
1199
 
1143
1200
  fn filter_rows(
@@ -1172,6 +1229,39 @@ fn filter_rows(
1172
1229
  .collect()
1173
1230
  }
1174
1231
 
1232
+ /// Executes a chain of `BlockMatch` blocks sequentially against `current_rows`.
1233
+ /// Each block transforms the rows for the next block in the sequence.
1234
+ ///
1235
+ /// This is the reusable helper for executing a `Vec<BlockMatch>` chain, used by
1236
+ /// `Block::Match` handlers, group-repetition steps (WI4), and similar contexts.
1237
+ #[allow(clippy::too_many_arguments)]
1238
+ pub(crate) fn execute_block_matches<TStore: store::Store>(
1239
+ blocks: &[instructions::BlockMatch],
1240
+ store: &TStore,
1241
+ tx: &mut TStore::TransactionBox,
1242
+ graph_name: &str,
1243
+ stack: &mut Stack,
1244
+ parameters: &crate::value::ValueMap,
1245
+ mut current_rows: Vec<value_table::Row>,
1246
+ allow_recursive: bool,
1247
+ ) -> Result<Vec<value_table::Row>>
1248
+ {
1249
+ for block in blocks
1250
+ {
1251
+ current_rows = execute_block_match(
1252
+ block,
1253
+ store,
1254
+ tx,
1255
+ graph_name,
1256
+ stack,
1257
+ parameters,
1258
+ current_rows,
1259
+ allow_recursive,
1260
+ )?;
1261
+ }
1262
+ Ok(current_rows)
1263
+ }
1264
+
1175
1265
  /// Executes a single `BlockMatch` (node or edge) against every row in `current_rows`,
1176
1266
  /// appending matching rows into `new_rows`.
1177
1267
  ///
@@ -1234,309 +1324,34 @@ fn execute_block_match<TStore: store::Store>(
1234
1324
  }
1235
1325
  }
1236
1326
 
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
- } =>
1327
+ instructions::BlockMatch::MatchEdge { .. } =>
1247
1328
  {
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
- }
1329
+ super::path::execute_match_edge(
1330
+ block,
1331
+ store,
1332
+ tx,
1333
+ graph_name,
1334
+ stack,
1335
+ parameters,
1336
+ &row,
1337
+ allow_recursive,
1338
+ &mut new_rows,
1339
+ )?;
1340
+ }
1530
1341
 
1531
- if let Some(frame) = push_next
1532
- {
1533
- dfs_stack.push(frame);
1534
- continue;
1535
- }
1536
- }
1537
- }
1538
- }
1539
- }
1342
+ instructions::BlockMatch::MatchQuantifiedPath { .. } =>
1343
+ {
1344
+ path::execute_match_quantified_path(
1345
+ block,
1346
+ store,
1347
+ tx,
1348
+ graph_name,
1349
+ stack,
1350
+ parameters,
1351
+ &row,
1352
+ allow_recursive,
1353
+ &mut new_rows,
1354
+ )?;
1540
1355
  }
1541
1356
  }
1542
1357
  }
@@ -1714,20 +1529,17 @@ pub(crate) fn eval_program<TStore: store::Store>(
1714
1529
  let mut output_table = value_table::ValueTable::new(variables_size.persistent_variables);
1715
1530
  for row in input_table.into_row_iter()
1716
1531
  {
1717
- let mut current_rows: Vec<_> = vec![row.clone().extended(variables_size.total_size())?];
1718
- for block in blocks.iter()
1719
- {
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
- )?;
1730
- }
1532
+ let current_rows: Vec<_> = vec![row.clone().extended(variables_size.total_size())?];
1533
+ let mut current_rows = execute_block_matches(
1534
+ blocks,
1535
+ store,
1536
+ &mut tx,
1537
+ &graph_name,
1538
+ &mut stack,
1539
+ parameters,
1540
+ current_rows,
1541
+ true, // recursive patterns allowed in MATCH
1542
+ )?;
1731
1543
  if !filter.is_empty()
1732
1544
  {
1733
1545
  current_rows = filter_rows(current_rows.into_iter(), filter, parameters)?;
@@ -2384,6 +2196,16 @@ pub(crate) fn eval_program<TStore: store::Store>(
2384
2196
  );
2385
2197
  return Ok(crate::value::Value::Map(res).into());
2386
2198
  }
2199
+ else if name == "gqlite.db.list_graphs"
2200
+ {
2201
+ let mut graphs = store.graphs_list(&mut tx)?;
2202
+ graphs.sort();
2203
+ let list: Vec<crate::value::Value> = graphs
2204
+ .into_iter()
2205
+ .map(crate::value::Value::String)
2206
+ .collect();
2207
+ return Ok(crate::value::Value::Array(list).into());
2208
+ }
2387
2209
  else
2388
2210
  {
2389
2211
  return Err(InternalError::Unimplemented("call for any other function").into());