feat(query):Support filter in match and support save db. - #145
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends MiniGU’s query engine to support MATCH ... WHERE ... predicates (including binary and property expressions) and adds initial on-disk database persistence by saving/loading a lightweight catalog plus file-backed graph data.
Changes:
- Add
WHEREpredicate planning forMATCHby introducing a physicalFilterover the generated scan/expand plan, and fix multi-hop expand input column indexing. - Extend expression binding/execution to support binary expressions and property access, including executor-time injection of vertex property scans when needed for predicate evaluation.
- Implement on-disk database open/load behavior, plus catalog persistence (
catalog.json) and file-backed graph storage for import/test-graph procedures; add persistence E2E tests and snapshots.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| minigu/gql/planner/src/plan/scan.rs | Attach var_labels to node scan schema for downstream property/type resolution. |
| minigu/gql/planner/src/plan/expand.rs | Propagate var_labels through expand schema and set labels for target vertex vars. |
| minigu/gql/planner/src/optimizer/mod.rs | Allow MATCH predicate and emit PhysicalFilter; fix multi-hop expand input column indexing. |
| minigu/gql/planner/src/bound/value_expr.rs | Add bound expression kinds for Binary and Property, plus constructors. |
| minigu/gql/planner/src/binder/value_expr.rs | Bind binary expressions and property access to bound expressions. |
| minigu/gql/planner/src/binder/common.rs | Simplify vertex predicate handling (still not supported at vertex pattern level). |
| minigu/gql/execution/src/builder.rs | Build executors with “actual schema” tracking; inject property scans for filters/projects; add evaluator support for new bound expr kinds. |
| minigu/core/src/procedures/import_graph.rs | Import graphs into file-backed storage when db_path is set; persist catalog. |
| minigu/core/src/procedures/export_graph.rs | Update test callsite for new import_internal signature. |
| minigu/core/src/procedures/create_test_graph_data.rs | Create test graphs using file-backed storage and persist catalog when on-disk. |
| minigu/core/src/lib.rs | Export new catalog_persistence module. |
| minigu/core/src/error.rs | Add IO/JSON/storage error variants for persistence flows. |
| minigu/core/src/database.rs | Implement Database::open to create/load on-disk DB, restore graphs from catalog. |
| minigu/core/src/catalog_persistence.rs | New module: serialize/deserialize graph type catalog and derive graph data file paths. |
| minigu/core/src/procedures/mod.rs | Minor module header change. |
| minigu-test/src/insta_test.rs | Add new match_filter tests and persistence reopen E2E tests. |
| minigu-test/gql/** | Add new snapshots and .gql test cases for filtering and persistence. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for property in vertex_type.properties().iter() { | ||
| property_ids.push(property.0); | ||
| property_names | ||
| .push(property.1.name().to_string()); | ||
| } |
There was a problem hiding this comment.
This scans all properties for each referenced vertex variable (iterating vertex_type.properties()), even if the predicate only touches one property. That can be a significant overhead for wide schemas. Consider collecting the specific (source, property) accesses from the predicate and scanning only the required property IDs.
| let mut new_fields = updated_schema.fields().to_vec(); | ||
| for prop_name in property_names.iter() { | ||
| let qualified_name = format!("{}_{}", var_name, prop_name); |
There was a problem hiding this comment.
In this property-scan insertion path, updated_schema is later rebuilt from new_fields only (dropping var_labels from the existing schema). Since build_evaluator relies on schema.get_var_label(variable) for VertexConstructor, this can lead to incorrect vertex _label values after a scan is inserted. Preserve/copy var_labels when reconstructing the schema (similar to how the planner propagates var_labels in Expand).
| Expr::Binary { op, left, right } => { | ||
| let bound_left = self.bind_value_expression(left.value())?; | ||
| let bound_right = self.bind_value_expression(right.value())?; | ||
| let bound_op = bind_binary_op(op.value()); | ||
| Ok(BoundExpr::binary(bound_op, bound_left, bound_right)) | ||
| } |
There was a problem hiding this comment.
Expr::Binary now binds every BinaryOp via bind_binary_op, but the execution layer still panics for some ops (e.g., Concat/Xor are unimplemented!() in the evaluator). To avoid runtime panics, either reject unsupported binary ops here with a proper NotImplemented bind error, or add full evaluator support for the newly-bindable ops.
|
|
||
| /// Get the `.minigu` file path for a graph. | ||
| pub fn graph_data_path(db_path: &Path, graph_name: &str) -> std::path::PathBuf { | ||
| db_path.join(format!("{}.minigu", graph_name)) |
There was a problem hiding this comment.
graph_data_path uses graph_name directly to form a filename. If graph_name can contain path separators or .., this allows writing/reading outside db_path (path traversal) when creating/importing graphs. Validate/sanitize graph_name (e.g., allow only [A-Za-z0-9_\-]), or encode it (hash/escape) before building the path.
| db_path.join(format!("{}.minigu", graph_name)) | |
| // Sanitize graph_name to prevent path traversal / invalid filenames. | |
| // Allow only ASCII alphanumeric characters, '_' and '-'; replace others with '_'. | |
| let sanitized_name: String = graph_name | |
| .chars() | |
| .map(|c| { | |
| if c.is_ascii_alphanumeric() || c == '_' || c == '-' { | |
| c | |
| } else { | |
| '_' | |
| } | |
| }) | |
| .collect(); | |
| db_path.join(format!("{}.minigu", sanitized_name)) |
| let label_id = graph_type | ||
| .add_label(label_def.name.clone()) | ||
| .expect("add label failed"); | ||
| debug_assert_eq!(label_id.get(), label_def.id, "label ID mismatch on restore"); |
There was a problem hiding this comment.
Restoring label IDs relies on add_label assigning sequential IDs and only checks mismatches with debug_assert_eq!, which is compiled out in release builds. If label IDs are non-contiguous (e.g., labels were removed) or otherwise differ, this can silently restore an incorrect mapping in production. Prefer validating and returning an error on mismatch (or add an API to insert labels with explicit IDs).
| debug_assert_eq!(label_id.get(), label_def.id, "label ID mismatch on restore"); | |
| assert_eq!(label_id.get(), label_def.id, "label ID mismatch on restore"); |
| @@ -1,3 +1,4 @@ | |||
|
|
|||
There was a problem hiding this comment.
File starts with an extra blank line. This is likely accidental and can create unnecessary diffs in future edits; consider removing the leading empty line to match the style of other modules.
| true, | ||
| )); | ||
| } | ||
| updated_schema = Arc::new(DataSchema::new(new_fields)); |
There was a problem hiding this comment.
When extending updated_schema with property columns, this rebuilds the DataSchema from fields only, which drops any var_labels metadata from the child schema. That causes downstream VertexConstructor to lose label specs (schema.get_var_label(...) becomes None) and can produce incorrect _label values. Preserve/copy var_labels into the new schema (or mutate a cloned DataSchema instead of recreating it).
| updated_schema = Arc::new(DataSchema::new(new_fields)); | |
| // Preserve existing var_labels metadata when extending the schema | |
| let var_labels = updated_schema.var_labels().clone(); | |
| let mut new_schema = DataSchema::new(new_fields); | |
| new_schema.set_var_labels(var_labels); | |
| updated_schema = Arc::new(new_schema); |
| new_fields.push(DataField::new( | ||
| qualified_name, | ||
| LogicalType::String, | ||
| true, | ||
| )); |
There was a problem hiding this comment.
Property columns added for WHERE evaluation are declared as LogicalType::String regardless of the actual property type. This makes the executor-time schema inconsistent with the data returned by scan_vertex_property and can break any later logic that depends on schema types. Prefer using the real logical type from the catalog (or derive it from the scanned Arrow array types) when creating these DataFields.
5f12d39 to
b29bc4a
Compare
da88d0f to
72f6855
Compare
|
Conflicts to be solved |
qishipengqsp
left a comment
There was a problem hiding this comment.
Test case reviewed. LGTM
Title
Type
feat: (new feature)fix: (bug fix)docs: (doc update)refactor: (refactor code)test: (test code)chore: (other updates)Scope
query: (query engine)parser: (frontend parser)planner: (frontend planner)optimizer: (query optimizer)executor: (execution engine)op: (operators)storage: (storage engine)mvcc: (multi version concurrency control)schema: (graph model and topology)tool: (tools)cli: (cli)sdk: (sdk)none: (N/A)Description
Issue: #
Checklist
masterbranch.