8000 fix(query): a pseudo-attribute reads the nearest earlier pattern with its entity (#594) by adityamukho · Pull Request #602 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

fix(query): a pseudo-attribute reads the nearest earlier pattern with its entity (#594) - #602

Merged
adityamukho merged 1 commit into
v3from
feat/594-pseudo-sources
Oct 15, 2026
Merged

adityamukho merged 1 commit into
v3from
feat/594-pseudo-sources

Conversation

@adityamukho
Copy link
Copy Markdown
Collaborator

PR 2 of 3 for #594 / #596 (plan: docs/superpowers/plans/2026-10-15-594-596-plan-independence.md, spec: docs/superpowers/specs/2026-10-15-plan-independent-answers-design.md §3.1–3.6, §4.1–4.4). #594 is closed by hand after merge.

What changes (breaking for v3.0.0)

  • Source at parse time. parser::resolve_pseudo_sources runs on every query :where, rule body and prepared template. A per-fact pseudo-attribute (:db/valid-from, :db/valid-to, :db/tx-count, :db/tx-id) reads the fact of its source: the nearest earlier pattern in the text with the same entity term and a stored attribute. The search covers its own clause list, then the clauses before each enclosing not/or. It crosses not-join/or-join only for a listed entity variable, which gains the hidden variable as a join variable, and it never goes past a rule body or into a sibling or branch. Terms match as spec §2 describes: variables and $slots by name, constants by entity id. With no source, the query, rule or prepared query fails with the new PRS-081 (src/error.rs, docs/ERROR_REFERENCE.md).
  • Hidden variables. The source binds ?#<n>.<attr> (Pattern::meta), and the pseudo-attribute pattern reads it (Pattern::pseudo_source). It binds its value term or checks it against the metadata, and never scans facts. The per-entity __fvf_/__fvt_/__ftc_/__fti_ keys, the matcher's fast path and the "every fact of the entity" fallback are all gone, as are the __f filters in not.
  • Planner. optimizer::plan places a pseudo-attribute pattern after its source, and after the pattern that binds its value variable, rather than by selectivity. A not whose body reads a source now waits for that source.
  • not / or. A body or branch holding a pseudo-attribute takes the per-row paths: no exclusion set for not/not-join, and seeded branches for or. or-join gains a per-row path seeded with its join variables only.
  • Engine. A source's scan or join fills the hidden variable's metadata column. Step::Pseudo { col, value } binds or checks against it. pseudo_forms, the per-row pick and the [e _ _] form for per-fact attributes are removed; :db/valid-at keeps its [e _ _] join. Queries that used to stay on the executor because of plan-dependent pseudo-attributes now either run on the engine or are PRS-081.

Deviations from the spec/plan

  • Pattern::pseudo_source is Option<EdnValue> rather than Option<String>: a symbol, or the metadata integer once a not body or another substituting path has put it in. As a result, not_body_matches, evaluate_not_join and not_clause_violated_via_rules get the outer metadata through substitute_pattern without any seeding code. Plan decision 9 had proposed seeding the match; the per-row fallbacks it describes are kept.

Tests

  • tests/pseudo_source_test.rs (12 tests): every PR 2 item in spec §7. The successful $slot case is checked at prepare time only, because of the separate bug below.
  • Parser (4), optimizer (1) and matcher unit tests rewritten for the new model. The engine tests now run the shapes that have a source; the old fallback shapes are asserted as PRS-081 (pseudo_attributes_without_a_source_are_prs_081).
  • The plan-independence matrix gains 11 pseudo-attribute shapes (with reordering on and off, through execute and a cursor).
  • Changed tests, all because they relied on the removed fallback:
    • temporal_metadata_test::pseudo_attr_as_sole_pattern: now expects PRS-081, then shows the [:alice ?a _] form.
    • executor test_execute_query_with_rules_valid_at_timestamp: the INT-026 query gains a source.
    • engine queries(): five no-source shapes gained [?e ?b _] sources.
    • other_queries_stay_on_the_executor: split into the executor-only shapes and the new PRS-081 test.
    • Four matcher unit tests rewritten.

Local: cargo test 1532 (1522 passed, 10 ignored); cargo test --features test-oracle and --features wasm pass. Clippy is clean with --all-features, default, wasm, test-oracle, wasm32 browser and wasm32-wasip1; cargo check in fuzz/ passes. Codex review: no findings.

Found while testing (not fixed here)

prepared::substitute calls query.as_of.take() / query.valid_at.take() before matching a slot, so a literal :as-of, :valid-at or :any-valid-time in a prepared query is silently dropped. For example, prepare("[:find ?v :as-of 1 …]") returns the current rows. It has been there since 0ef578e (2026-04-04) and is on main too. Raised separately.

Measurement

#394 envelope A/B on the bench machine (origin/v3 vs this branch, 100K and 1M): see the comment below.

Docs

CHANGELOG (Breaking changes entry, the #545 engine entry updated, a Tests entry), CLAUDE.md, README, TEST_COVERAGE, ERROR_REFERENCE. Docs site: project-minigraf/minigraf-docs PR (pseudo-attributes @since/@until v3.0.0, architecture streaming-engine bullet).

🤖 Generated with Claude Code

https://claude.ai/code/session_01HyLscCUuJ1GQpdAeX79EdR

… its entity (#594)

:db/valid-from, :db/valid-to, :db/tx-count and :db/tx-id read the fact of
the last pattern the planner ran on the same entity (per-entity __f* keys),
so the answer depended on the plan, and with no such pattern matched every
fact of the entity. Now parser::resolve_pseudo_sources names each one's
source at parse time: the nearest earlier pattern in the text with the same
entity term and a stored attribute, in its own clause list, then before
each enclosing not/or (not-join/or-join only for a listed variable, which
gains the hidden variable), never past a rule body; PRS-081 when there is
none. The source binds a hidden variable ?#<n>.<attr> (Pattern::meta); the
pseudo-attribute pattern binds or checks against it (Pattern::pseudo_source,
substituted to the metadata in not bodies) and never scans. The optimizer
places it after its source; not/or bodies that read metadata take the
per-row paths (or-join gains one). The engine's scans fill the hidden
variable's column; the per-row Step::Pseudo choice and the [e _ _] fallback
for per-fact attributes are gone. :db/valid-at is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyLscCUuJ1GQpdAeX79EdR
@codecov
codecov Bot commented Oct 15, 2026
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.95082% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/query/datalog/parser.rs 97.45% 3 Missing ⚠️
src/query/datalog/executor.rs 97.05% 1 Missing ⚠️
src/query/datalog/optimizer.rs 95.65% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0