gqlite 1.8.1 → 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.
- checksums.yaml +4 -4
- data/ext/Cargo.toml +8 -8
- data/ext/gqlitedb/Cargo.toml +0 -2
- data/ext/gqlitedb/src/compiler/variables_manager.rs +109 -10
- data/ext/gqlitedb/src/compiler.rs +481 -47
- data/ext/gqlitedb/src/connection.rs +12 -0
- data/ext/gqlitedb/src/consts.rs +1 -1
- data/ext/gqlitedb/src/error.rs +24 -0
- data/ext/gqlitedb/src/interpreter/evaluators.rs +91 -325
- data/ext/gqlitedb/src/interpreter/instructions.rs +72 -0
- data/ext/gqlitedb/src/interpreter/mod.rs +1 -0
- data/ext/gqlitedb/src/interpreter/path/bfs.rs +494 -0
- data/ext/gqlitedb/src/interpreter/path/dfs.rs +267 -0
- data/ext/gqlitedb/src/interpreter/path/group.rs +418 -0
- data/ext/gqlitedb/src/interpreter/path.rs +542 -0
- data/ext/gqlitedb/src/planner.rs +218 -6
- data/ext/gqlitedb/src/query_result.rs +19 -4
- data/ext/gqlitedb/src/tests/compiler.rs +2453 -1
- data/ext/gqlitedb/src/tests/connection/redb.rs +8 -0
- data/ext/gqlitedb/src/tests/connection.rs +37 -0
- data/ext/gqlitedb/src/tests/evaluators.rs +2206 -1
- data/ext/gqlitedb/src/tests/parser.rs +1225 -0
- data/ext/gqlitedb/src/tests/planner.rs +432 -2
- data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +5 -5
- data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +3 -1
- data/ext/gqlitedb/src/tests/templates/ast.rs +284 -1
- data/ext/gqlitedb/src/tests/templates/programs.rs +306 -0
- data/ext/gqliterb/Cargo.toml +0 -1
- data/ext/gqlparser/src/oc/ast.rs +52 -3
- data/ext/gqlparser/src/oc/error.rs +18 -0
- data/ext/gqlparser/src/oc/lexer.rs +146 -0
- data/ext/gqlparser/src/oc/parser/tests.rs +365 -20
- data/ext/gqlparser/src/oc/parser.rs +1196 -143
- data/ext/graphcore/src/value.rs +12 -1
- metadata +5 -2
- data/ext/gqlitedb/src/parser/gql.pest +0 -198
data/ext/gqlitedb/src/planner.rs
CHANGED
|
@@ -6,6 +6,52 @@ use gqlparser::oc::ast;
|
|
|
6
6
|
/// Ordered list of logical plans.
|
|
7
7
|
pub type LogicalPlans = Vec<LogicalPlan>;
|
|
8
8
|
|
|
9
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
10
|
+
pub enum StopCondition
|
|
11
|
+
{
|
|
12
|
+
Shortest(ast::Expression),
|
|
13
|
+
ShortestGroups(ast::Expression),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
17
|
+
pub enum PathStrategy
|
|
18
|
+
{
|
|
19
|
+
Dfs,
|
|
20
|
+
Bfs
|
|
21
|
+
{
|
|
22
|
+
stop_condition: StopCondition,
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// A pattern the planner has finished with. `Node`/`Edge`/`Path` pass
|
|
27
|
+
/// through unchanged — there's nothing to plan for them yet.
|
|
28
|
+
/// `QuantifiedPath` carries the planner's own decision alongside the
|
|
29
|
+
/// parsed pattern, so the compiler can never see one without the other.
|
|
30
|
+
#[derive(Debug)]
|
|
31
|
+
#[allow(clippy::large_enum_variant)]
|
|
32
|
+
pub enum PlannedPattern
|
|
33
|
+
{
|
|
34
|
+
Node(ast::NodePattern),
|
|
35
|
+
Edge(ast::EdgePattern),
|
|
36
|
+
Path(ast::PathPattern),
|
|
37
|
+
QuantifiedPath
|
|
38
|
+
{
|
|
39
|
+
pattern: ast::QuantifiedPathPattern,
|
|
40
|
+
strategy: PathStrategy,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl PlannedPattern
|
|
45
|
+
{
|
|
46
|
+
/// Convenience for call sites (mainly test fixtures) that just want to
|
|
47
|
+
/// carry an ordinary pattern through planning without constructing a
|
|
48
|
+
/// `PlannedPattern` variant by hand.
|
|
49
|
+
pub fn unplanned(pattern: ast::Pattern) -> Self
|
|
50
|
+
{
|
|
51
|
+
plan_pattern(pattern)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
9
55
|
/// Logical representation of a statement.
|
|
10
56
|
#[derive(Debug)]
|
|
11
57
|
#[allow(clippy::large_enum_variant)]
|
|
@@ -37,13 +83,13 @@ pub enum LogicalPlan
|
|
|
37
83
|
Create
|
|
38
84
|
{
|
|
39
85
|
/// Patterns to create.
|
|
40
|
-
patterns: Vec<
|
|
86
|
+
patterns: Vec<PlannedPattern>,
|
|
41
87
|
},
|
|
42
88
|
/// Direct node scan produced from a MATCH clause.
|
|
43
89
|
NodeScan
|
|
44
90
|
{
|
|
45
91
|
/// Patterns to match, including any label constraints present in the AST.
|
|
46
|
-
patterns: Vec<
|
|
92
|
+
patterns: Vec<PlannedPattern>,
|
|
47
93
|
/// Whether the scan is optional.
|
|
48
94
|
optional: bool,
|
|
49
95
|
},
|
|
@@ -129,7 +175,7 @@ pub enum LogicalPlan
|
|
|
129
175
|
Merge
|
|
130
176
|
{
|
|
131
177
|
/// Patterns to match or create.
|
|
132
|
-
patterns: Vec<
|
|
178
|
+
patterns: Vec<PlannedPattern>,
|
|
133
179
|
/// Updates applied when entities are created.
|
|
134
180
|
on_create: Vec<ast::OneUpdate>,
|
|
135
181
|
/// Updates applied when entities are matched.
|
|
@@ -189,6 +235,172 @@ fn wrap_filter(plan: LogicalPlan, where_expression: Option<ast::Expression>) ->
|
|
|
189
235
|
}
|
|
190
236
|
}
|
|
191
237
|
|
|
238
|
+
fn one_literal() -> ast::Expression
|
|
239
|
+
{
|
|
240
|
+
ast::Value { value: 1i64.into() }.into()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
pub(crate) fn choose_path_strategy(selector: &ast::PathSelector) -> PathStrategy
|
|
244
|
+
{
|
|
245
|
+
match selector
|
|
246
|
+
{
|
|
247
|
+
ast::PathSelector::All => PathStrategy::Dfs,
|
|
248
|
+
ast::PathSelector::Any => PathStrategy::Bfs {
|
|
249
|
+
stop_condition: StopCondition::Shortest(one_literal()),
|
|
250
|
+
},
|
|
251
|
+
ast::PathSelector::AllShortest => PathStrategy::Bfs {
|
|
252
|
+
stop_condition: StopCondition::ShortestGroups(one_literal()),
|
|
253
|
+
},
|
|
254
|
+
ast::PathSelector::Shortest(k) => PathStrategy::Bfs {
|
|
255
|
+
stop_condition: StopCondition::Shortest(k.clone()),
|
|
256
|
+
},
|
|
257
|
+
ast::PathSelector::ShortestGroups(k) => PathStrategy::Bfs {
|
|
258
|
+
stop_condition: StopCondition::ShortestGroups(k.clone()),
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// The one place `choose_path_strategy` is called from. Every pattern
|
|
264
|
+
/// that becomes part of a `LogicalPlan` goes through this.
|
|
265
|
+
pub(crate) fn plan_pattern(pattern: ast::Pattern) -> PlannedPattern
|
|
266
|
+
{
|
|
267
|
+
match pattern
|
|
268
|
+
{
|
|
269
|
+
ast::Pattern::Node(n) => PlannedPattern::Node(n),
|
|
270
|
+
ast::Pattern::Edge(e) => PlannedPattern::Edge(e),
|
|
271
|
+
ast::Pattern::Path(p) => PlannedPattern::Path(p),
|
|
272
|
+
ast::Pattern::QuantifiedPath(qp) if qp.quantifier.is_none() =>
|
|
273
|
+
{
|
|
274
|
+
// Selector-scoped pre-filter wrapper (design §1d): promote the
|
|
275
|
+
// inner content to a degenerate single-repetition quantified
|
|
276
|
+
// pattern if it isn't already quantified, then apply the outer
|
|
277
|
+
// selector's strategy to the result. The inner content's own
|
|
278
|
+
// `.selector` is always `All` in this construct (nothing in the
|
|
279
|
+
// grammar lets it be otherwise), so this is a plain override, not
|
|
280
|
+
// a merge.
|
|
281
|
+
let inner = qp
|
|
282
|
+
.group
|
|
283
|
+
.into_iter()
|
|
284
|
+
.next()
|
|
285
|
+
.expect("selector-scoped wrapper must have exactly one inner pattern");
|
|
286
|
+
let strategy = choose_path_strategy(&qp.selector);
|
|
287
|
+
match inner
|
|
288
|
+
{
|
|
289
|
+
ast::Pattern::QuantifiedPath(mut inner_qp) =>
|
|
290
|
+
{
|
|
291
|
+
// Already quantified (e.g. `--+`) — keep its own quantifier,
|
|
292
|
+
// just override the strategy. The wrapper's predicate becomes
|
|
293
|
+
// path_where (whole-path filter), not group_where (per-repetition).
|
|
294
|
+
inner_qp.variable = qp.variable.or(inner_qp.variable);
|
|
295
|
+
inner_qp.path_where = qp.group_where; // Wrapper predicate → path_where
|
|
296
|
+
PlannedPattern::QuantifiedPath {
|
|
297
|
+
pattern: inner_qp,
|
|
298
|
+
strategy,
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
other =>
|
|
302
|
+
{
|
|
303
|
+
// Not already quantified — promote to a degenerate
|
|
304
|
+
// exactly-one-repetition group. The wrapper's predicate becomes
|
|
305
|
+
// path_where (whole-path filter), not group_where (per-repetition).
|
|
306
|
+
let promoted = ast::QuantifiedPathPattern {
|
|
307
|
+
variable: qp.variable,
|
|
308
|
+
source: qp.source,
|
|
309
|
+
group: vec![other],
|
|
310
|
+
group_where: None, // Wrapper-promoted patterns never have group_where
|
|
311
|
+
path_where: qp.group_where, // Wrapper predicate → path_where
|
|
312
|
+
quantifier: Some(1..1),
|
|
313
|
+
destination: qp.destination,
|
|
314
|
+
selector: qp.selector,
|
|
315
|
+
};
|
|
316
|
+
PlannedPattern::QuantifiedPath {
|
|
317
|
+
pattern: promoted,
|
|
318
|
+
strategy,
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
ast::Pattern::QuantifiedPath(qp) if !matches!(qp.selector, ast::PathSelector::All) =>
|
|
324
|
+
{
|
|
325
|
+
// Selector-scoped pattern with selector override (e.g., SHORTEST 1).
|
|
326
|
+
// Two cases:
|
|
327
|
+
// 1. quantifier.is_none(): pre-filter wrapper with no inner quantifier,
|
|
328
|
+
// needs promotion to 1..1 with inner pattern in group
|
|
329
|
+
// 2. quantifier.is_some(): already-quantified inner (like --+), wrapped
|
|
330
|
+
// with a selector override. The quantifier is already set; just
|
|
331
|
+
// apply the selector's strategy. Also move group_where to path_where
|
|
332
|
+
// (whole-path filter) if present.
|
|
333
|
+
let strategy = choose_path_strategy(&qp.selector);
|
|
334
|
+
|
|
335
|
+
if qp.quantifier.is_some()
|
|
336
|
+
{
|
|
337
|
+
// Already has a quantifier (e.g., from --+ operator).
|
|
338
|
+
// Move group_where to path_where if present, then apply the strategy.
|
|
339
|
+
let mut pattern = qp;
|
|
340
|
+
if pattern.group_where.is_some()
|
|
341
|
+
{
|
|
342
|
+
pattern.path_where = pattern.group_where.take();
|
|
343
|
+
}
|
|
344
|
+
PlannedPattern::QuantifiedPath { pattern, strategy }
|
|
345
|
+
}
|
|
346
|
+
else
|
|
347
|
+
{
|
|
348
|
+
// Pre-filter wrapper with no inner quantifier.
|
|
349
|
+
// Extract and process the inner pattern.
|
|
350
|
+
let inner = qp
|
|
351
|
+
.group
|
|
352
|
+
.into_iter()
|
|
353
|
+
.next()
|
|
354
|
+
.expect("selector-scoped wrapper must have exactly one inner pattern");
|
|
355
|
+
match inner
|
|
356
|
+
{
|
|
357
|
+
ast::Pattern::QuantifiedPath(mut inner_qp) =>
|
|
358
|
+
{
|
|
359
|
+
// Already quantified — keep its own quantifier and override the strategy.
|
|
360
|
+
inner_qp.variable = qp.variable.or(inner_qp.variable);
|
|
361
|
+
inner_qp.path_where = qp.group_where; // Wrapper predicate → path_where
|
|
362
|
+
PlannedPattern::QuantifiedPath {
|
|
363
|
+
pattern: inner_qp,
|
|
364
|
+
strategy,
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
other =>
|
|
368
|
+
{
|
|
369
|
+
// Not already quantified — promote to a degenerate 1..1 group.
|
|
370
|
+
let promoted = ast::QuantifiedPathPattern {
|
|
371
|
+
variable: qp.variable,
|
|
372
|
+
source: qp.source,
|
|
373
|
+
group: vec![other],
|
|
374
|
+
group_where: None,
|
|
375
|
+
path_where: qp.group_where,
|
|
376
|
+
quantifier: Some(1..1),
|
|
377
|
+
destination: qp.destination,
|
|
378
|
+
selector: qp.selector,
|
|
379
|
+
};
|
|
380
|
+
PlannedPattern::QuantifiedPath {
|
|
381
|
+
pattern: promoted,
|
|
382
|
+
strategy,
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
ast::Pattern::QuantifiedPath(qp) =>
|
|
389
|
+
{
|
|
390
|
+
let strategy = choose_path_strategy(&qp.selector);
|
|
391
|
+
PlannedPattern::QuantifiedPath {
|
|
392
|
+
pattern: qp,
|
|
393
|
+
strategy,
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
pub(crate) fn plan_patterns(patterns: Vec<ast::Pattern>) -> Vec<PlannedPattern>
|
|
400
|
+
{
|
|
401
|
+
patterns.into_iter().map(plan_pattern).collect()
|
|
402
|
+
}
|
|
403
|
+
|
|
192
404
|
fn wrap_modifiers(plan: LogicalPlan, modifiers: ast::Modifiers) -> LogicalPlan
|
|
193
405
|
{
|
|
194
406
|
let mut plan = plan;
|
|
@@ -266,7 +478,7 @@ fn plan_impl(statement: ast::Statement) -> Result<LogicalPlan>
|
|
|
266
478
|
name: use_graph.name,
|
|
267
479
|
}),
|
|
268
480
|
ast::Statement::Create(create) => Ok(LogicalPlan::Create {
|
|
269
|
-
patterns: create.patterns,
|
|
481
|
+
patterns: plan_patterns(create.patterns),
|
|
270
482
|
}),
|
|
271
483
|
ast::Statement::Match(match_statement) =>
|
|
272
484
|
{
|
|
@@ -289,7 +501,7 @@ fn plan_impl(statement: ast::Statement) -> Result<LogicalPlan>
|
|
|
289
501
|
// Standard node scan when no SEARCH
|
|
290
502
|
Ok(wrap_filter(
|
|
291
503
|
LogicalPlan::NodeScan {
|
|
292
|
-
patterns: match_statement.patterns,
|
|
504
|
+
patterns: plan_patterns(match_statement.patterns),
|
|
293
505
|
optional: match_statement.optional,
|
|
294
506
|
},
|
|
295
507
|
match_statement.where_expression,
|
|
@@ -326,7 +538,7 @@ fn plan_impl(statement: ast::Statement) -> Result<LogicalPlan>
|
|
|
326
538
|
updates: update_statement.updates,
|
|
327
539
|
}),
|
|
328
540
|
ast::Statement::Merge(merge_statement) => Ok(LogicalPlan::Merge {
|
|
329
|
-
patterns: merge_statement.patterns,
|
|
541
|
+
patterns: plan_patterns(merge_statement.patterns),
|
|
330
542
|
on_create: merge_statement.on_create,
|
|
331
543
|
on_match: merge_statement.on_match,
|
|
332
544
|
}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use graphcore::value_map;
|
|
2
2
|
use itertools::Itertools as _;
|
|
3
3
|
|
|
4
|
-
use crate::error::
|
|
4
|
+
use crate::error::Error;
|
|
5
5
|
|
|
6
6
|
/// Hold the result of executing a query.
|
|
7
7
|
#[derive(Debug, PartialEq, Clone)]
|
|
@@ -31,10 +31,25 @@ impl QueryResult
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
/// Try convert into table
|
|
34
|
-
pub fn try_into_table(self) -> Result<graphcore::Table,
|
|
34
|
+
pub fn try_into_table(self) -> Result<graphcore::Table, Error>
|
|
35
35
|
{
|
|
36
36
|
self.try_into()
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/// This is a convenience method for queries like `RETURN count(*) AS n`.
|
|
40
|
+
pub fn into_single_value<T>(self) -> Result<T, Error>
|
|
41
|
+
where
|
|
42
|
+
T: TryFrom<graphcore::Value>,
|
|
43
|
+
Error: From<<T as TryFrom<graphcore::Value>>::Error>,
|
|
44
|
+
{
|
|
45
|
+
let table = match self
|
|
46
|
+
{
|
|
47
|
+
QueryResult::Table(tbl) => tbl,
|
|
48
|
+
_ => return Err(crate::error::InternalError::InvalidQueryResultCast.into()),
|
|
49
|
+
};
|
|
50
|
+
let value = table.value(0, 0)?;
|
|
51
|
+
Ok(value.clone().try_into()?)
|
|
52
|
+
}
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
impl From<graphcore::Value> for QueryResult
|
|
@@ -55,13 +70,13 @@ impl From<graphcore::Table> for QueryResult
|
|
|
55
70
|
|
|
56
71
|
impl TryInto<graphcore::Table> for QueryResult
|
|
57
72
|
{
|
|
58
|
-
type Error =
|
|
73
|
+
type Error = Error;
|
|
59
74
|
fn try_into(self) -> Result<graphcore::Table, Self::Error>
|
|
60
75
|
{
|
|
61
76
|
match self
|
|
62
77
|
{
|
|
63
78
|
QueryResult::Table(tbl) => Ok(tbl),
|
|
64
|
-
_ => Err(InternalError::InvalidQueryResultCast),
|
|
79
|
+
_ => Err(crate::error::InternalError::InvalidQueryResultCast.into()),
|
|
65
80
|
}
|
|
66
81
|
}
|
|
67
82
|
}
|