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.
- checksums.yaml +4 -4
- data/ext/Cargo.toml +8 -8
- data/ext/db-index/Cargo.toml +2 -2
- data/ext/gqlitedb/Cargo.toml +0 -2
- data/ext/gqlitedb/src/aggregators.rs +28 -0
- data/ext/gqlitedb/src/compiler/expression_analyser.rs +33 -0
- data/ext/gqlitedb/src/compiler/variables_manager.rs +111 -11
- data/ext/gqlitedb/src/compiler.rs +506 -48
- data/ext/gqlitedb/src/connection.rs +14 -2
- data/ext/gqlitedb/src/consts.rs +1 -1
- data/ext/gqlitedb/src/error.rs +31 -0
- data/ext/gqlitedb/src/interpreter/evaluators.rs +153 -331
- data/ext/gqlitedb/src/interpreter/instructions.rs +77 -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 +231 -6
- data/ext/gqlitedb/src/query_result.rs +19 -4
- data/ext/gqlitedb/src/store/redb.rs +1 -1
- data/ext/gqlitedb/src/tests/compiler.rs +2454 -1
- data/ext/gqlitedb/src/tests/connection/postgres.rs +22 -0
- data/ext/gqlitedb/src/tests/connection/redb.rs +24 -0
- data/ext/gqlitedb/src/tests/connection/sqlite.rs +16 -0
- data/ext/gqlitedb/src/tests/connection.rs +101 -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 +433 -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 +294 -1
- data/ext/gqlitedb/src/tests/templates/programs.rs +318 -0
- data/ext/gqliterb/Cargo.toml +0 -1
- data/ext/gqlparser/src/oc/ast.rs +72 -3
- data/ext/gqlparser/src/oc/error.rs +18 -0
- data/ext/gqlparser/src/oc/lexer.rs +157 -2
- data/ext/gqlparser/src/oc/parser/tests.rs +735 -29
- data/ext/gqlparser/src/oc/parser.rs +1312 -182
- data/ext/gqlparser/src/oc/string.rs +172 -0
- data/ext/gqlparser/src/oc.rs +1 -0
- data/ext/graphcore/src/value/value_map.rs +2 -2
- data/ext/graphcore/src/value.rs +13 -1
- metadata +6 -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
|
},
|
|
@@ -89,6 +135,12 @@ pub enum LogicalPlan
|
|
|
89
135
|
/// Limit expression.
|
|
90
136
|
expression: ast::Expression,
|
|
91
137
|
},
|
|
138
|
+
/// Make sure values only appear onces.
|
|
139
|
+
Distinct
|
|
140
|
+
{
|
|
141
|
+
/// Input operator.
|
|
142
|
+
source: Box<LogicalPlan>,
|
|
143
|
+
},
|
|
92
144
|
/// Invoke a callable.
|
|
93
145
|
Call
|
|
94
146
|
{
|
|
@@ -123,7 +175,7 @@ pub enum LogicalPlan
|
|
|
123
175
|
Merge
|
|
124
176
|
{
|
|
125
177
|
/// Patterns to match or create.
|
|
126
|
-
patterns: Vec<
|
|
178
|
+
patterns: Vec<PlannedPattern>,
|
|
127
179
|
/// Updates applied when entities are created.
|
|
128
180
|
on_create: Vec<ast::OneUpdate>,
|
|
129
181
|
/// Updates applied when entities are matched.
|
|
@@ -183,6 +235,172 @@ fn wrap_filter(plan: LogicalPlan, where_expression: Option<ast::Expression>) ->
|
|
|
183
235
|
}
|
|
184
236
|
}
|
|
185
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
|
+
|
|
186
404
|
fn wrap_modifiers(plan: LogicalPlan, modifiers: ast::Modifiers) -> LogicalPlan
|
|
187
405
|
{
|
|
188
406
|
let mut plan = plan;
|
|
@@ -211,6 +429,13 @@ fn wrap_modifiers(plan: LogicalPlan, modifiers: ast::Modifiers) -> LogicalPlan
|
|
|
211
429
|
};
|
|
212
430
|
}
|
|
213
431
|
|
|
432
|
+
if modifiers.distinct
|
|
433
|
+
{
|
|
434
|
+
plan = LogicalPlan::Distinct {
|
|
435
|
+
source: Box::new(plan),
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
214
439
|
plan
|
|
215
440
|
}
|
|
216
441
|
|
|
@@ -253,7 +478,7 @@ fn plan_impl(statement: ast::Statement) -> Result<LogicalPlan>
|
|
|
253
478
|
name: use_graph.name,
|
|
254
479
|
}),
|
|
255
480
|
ast::Statement::Create(create) => Ok(LogicalPlan::Create {
|
|
256
|
-
patterns: create.patterns,
|
|
481
|
+
patterns: plan_patterns(create.patterns),
|
|
257
482
|
}),
|
|
258
483
|
ast::Statement::Match(match_statement) =>
|
|
259
484
|
{
|
|
@@ -276,7 +501,7 @@ fn plan_impl(statement: ast::Statement) -> Result<LogicalPlan>
|
|
|
276
501
|
// Standard node scan when no SEARCH
|
|
277
502
|
Ok(wrap_filter(
|
|
278
503
|
LogicalPlan::NodeScan {
|
|
279
|
-
patterns: match_statement.patterns,
|
|
504
|
+
patterns: plan_patterns(match_statement.patterns),
|
|
280
505
|
optional: match_statement.optional,
|
|
281
506
|
},
|
|
282
507
|
match_statement.where_expression,
|
|
@@ -313,7 +538,7 @@ fn plan_impl(statement: ast::Statement) -> Result<LogicalPlan>
|
|
|
313
538
|
updates: update_statement.updates,
|
|
314
539
|
}),
|
|
315
540
|
ast::Statement::Merge(merge_statement) => Ok(LogicalPlan::Merge {
|
|
316
|
-
patterns: merge_statement.patterns,
|
|
541
|
+
patterns: plan_patterns(merge_statement.patterns),
|
|
317
542
|
on_create: merge_statement.on_create,
|
|
318
543
|
on_match: merge_statement.on_match,
|
|
319
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
|
}
|
|
@@ -570,7 +570,7 @@ impl Store
|
|
|
570
570
|
/// database forward exactly one step; the function recurses until `from` matches current.
|
|
571
571
|
fn upgrade_database(&self, from: utils::Version, tx: &mut TransactionBox) -> Result<()>
|
|
572
572
|
{
|
|
573
|
-
if from == consts::GQLITE_VERSION
|
|
573
|
+
if from.major == consts::GQLITE_VERSION.major && from.minor == consts::GQLITE_VERSION.minor
|
|
574
574
|
{
|
|
575
575
|
return Ok(());
|
|
576
576
|
}
|