2026-06-03 11:48:33 +02:00

4.1 KiB

Query Ops

Physical operators for a small query-plan executor: atom scan, semijoin, and natural join over a binding relation. Operators compose by function application, so a query plan written by hand is just an expression.

Architecture Diagram

Public API

Item Type Description
scan_atom(&Table, &AtomPattern) -> Relation function Scans the table under the pattern and returns a binding relation with one column per distinct variable in first-occurrence order. Literal positions and repeated variables filter rows during the scan.
semijoin(&Relation, &Relation) -> Relation function Returns the rows of left whose values on the columns shared with right also appear in right. The output column list is the same as left.columns.
natural_join(&Relation, &Relation) -> Relation function Returns every pair of left and right rows that agree on shared columns. Each output row holds the columns of left followed by the non-shared columns of right.
Table struct Holds positional input rows of fixed arity and carries no column names. Construct it with Table::new(arity) or Table::from_rows(arity, rows).
AtomPattern struct Specifies, for each table column, either a variable to bind or a literal value to match. The pattern is a Vec<Term> whose length must equal the table's arity.
Term enum Represents one position of an AtomPattern. A term is either Var(String) to bind the cell to a named variable, or Lit(Value) to require the cell to equal a given value.
Relation struct Holds rows over named columns and is the type produced by every operator. Construct it with Relation::new(columns) or Relation::from_rows(columns, rows). Column names within a single relation must be unique.
Value enum Represents a single cell value stored in a Table or Relation. A value is either Int(i64) or Str(String).

Example

Q(X) :- edge(X, X), labeled(X). (labeled self-loops):

use query_ops::atom::{AtomPattern, Term, scan_atom};
use query_ops::join::semijoin;
use query_ops::table::Table;
use query_ops::value::Value;

fn main() {
    let edge = Table::from_rows(
        2,
        vec![
            vec![Value::Int(1), Value::Int(2)],
            vec![Value::Int(3), Value::Int(3)], // self-loop on 3
            vec![Value::Int(2), Value::Int(2)], // self-loop on 2
        ],
    );
    let labeled = Table::from_rows(1, vec![vec![Value::Int(2)]]);

    let self_loops = scan_atom(
        &edge,
        &AtomPattern {
            columns: vec![Term::Var("X".to_string()), Term::Var("X".to_string())],
        },
    );
    let labeled_x = scan_atom(
        &labeled,
        &AtomPattern {
            columns: vec![Term::Var("X".to_string())],
        },
    );
    let result = semijoin(&self_loops, &labeled_x);

    assert_eq!(result.columns, vec!["X".to_string()]);
    assert_eq!(result.rows, vec![vec![Value::Int(2)]]);
}

Test

cargo test -p query-ops