## Plan Exporter Haskell tool that turns a hand-authored scenario into the JSON IR consumed by [`crates/plan-runner`](../../crates/plan-runner). Each scenario carries a small schema, a set of ground facts, a list of conjunctive-query atoms, and an expected-bindings oracle. The exporter parses the scenario, runs `Geolog.DB.Plan.planConjunction` from [`external/geolog`](../../external/geolog) to produce a Yannakakis-style plan, self-checks the planned bindings against the oracle, then writes the runner IR to stdout. This is the only producer of runner IR today; any other frontend that emits the same JSON shape can drive the runner instead. ### Layout ```text tools/exporter/ ├── plan-exporter.cabal # cabal package: executable `plan-export` ├── cabal.project # points at geolog-lang and its siblings in external/geolog ├── src/Main.hs # the exporter itself; rustdoc-style header explains the IR └── examples/ # hand-authored scenario inputs ├── cartesian.scenario.json ├── self_loop.scenario.json ├── three_atom_chain.scenario.json └── two_atom_join.scenario.json ``` ### Run It > ⚠️ The exporter needs GHC 9.12 and Cabal. > The repository's Nix dev shell provides both; > enter it with `make shell` (or `nix develop`) before running the commands below. > A system GHC older than 9.12 will fail to compile geolog-lang's `GHC2024` modules. ```sh # Build the executable: cd tools/exporter && cabal build plan-export # Export one scenario to runner IR JSON: cabal run -v0 plan-export -- examples/two_atom_join.scenario.json # Regenerate every fixture in crates/plan-runner/fixtures/ from these scenarios: make export-fixtures # run from the repository root ``` ### Scenario Format Each `.scenario.json` carries four blocks: | Block | Maps to | |---------------------|----------------------------------------------------------------------| | `schema` | `FlatTheory` from `geolog-lang`: relation paths and column types. | | `facts` | Ground tuples per relation, in the shape `InMemory` ingests. | | `atoms` | The conjunctive-query body, as a list of `QAtom`. | | `expected_bindings` | Variables and rows the planner is expected to produce. Used as a self-check; lifted through to the runner-side oracle. | See the four committed examples for the exact JSON shape. The runner-side IR shape (what the exporter emits) is documented in [`crates/plan-runner/src/lib.rs`](../../crates/plan-runner/src/lib.rs). ### Self-Check Before emitting JSON, the exporter runs the planned query through `Geolog.DB.InMemory.evalConjunctionPlanned` and asserts the bindings match the scenario's `expected_bindings`. A mismatched scenario fails at export time, so the Rust side never sees a broken fixture. ### Notes - **Cross-language contract.** The JSON shape is the contract between this tool and `plan-runner`. Changing it means editing both sides. - **Subset of the source language.** The exporter only handles conjunctive queries that fit `planConjunction`. Negation, recursion, and laws are out of scope here; they belong to a different planner pass. - **No durable references to ignored paths.** This tool depends on `external/geolog` (a submodule), not on any ad-hoc clone or generated copy in an ignored path.