Simplify the project
@ -8,7 +8,7 @@ indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.rs]
|
||||
[*.{rs,hs,py}]
|
||||
max_line_length = 100
|
||||
|
||||
[*.md]
|
||||
|
||||
@ -50,7 +50,7 @@ Expected durable areas may include:
|
||||
|
||||
- `src/`: Rust source for parser, catalog, planner, execution experiments, and storage prototypes.
|
||||
- `tests/`: integration tests for rule planning, evaluation, and storage behavior.
|
||||
- `examples/`: small runnable Datalog-like programs or storage scenarios.
|
||||
- `tools/exporter/examples/`: hand-authored scenario JSON consumed by the Haskell exporter to produce runner fixtures.
|
||||
- `fixtures/`: committed input facts and expected outputs.
|
||||
- `notes/`: local design notes that belong to this project.
|
||||
- `flowlog/`: project-local notes or sketches derived from the FlowLog line of work.
|
||||
|
||||
21
Cargo.lock
generated
@ -555,16 +555,6 @@ dependencies = [
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glog-runner"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"query-ops",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"storage",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "guardian"
|
||||
version = "1.3.0"
|
||||
@ -1156,6 +1146,17 @@ version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
|
||||
[[package]]
|
||||
name = "plan-runner"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"query-ops",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"storage",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
|
||||
20
Makefile
@ -77,22 +77,28 @@ clean: ## Remove build output
|
||||
fi
|
||||
|
||||
EXPORTER_DIR := tools/exporter
|
||||
EXPORTER_FIXTURES := crates/glog-runner/fixtures
|
||||
EXPORTER_SCENARIOS := three-atom-chain
|
||||
EXPORTER_FIXTURES := crates/plan-runner/fixtures
|
||||
EXAMPLES_DIR := $(EXPORTER_DIR)/examples
|
||||
|
||||
.PHONY: export-fixtures
|
||||
export-fixtures: ## Regenerate JSON plan fixtures from the Haskell exporter (needs Cabal and GHC; use `make shell` first).
|
||||
export-fixtures: ## Regenerate plan JSON for every tools/exporter/examples/*.scenario.json (needs Cabal and GHC; use `make shell` first).
|
||||
@if ! command -v cabal >/dev/null 2>&1; then \
|
||||
echo "cabal not found. Enter the dev shell with 'make shell' (or 'nix develop') first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@cd $(EXPORTER_DIR) && cabal build glog-export
|
||||
@for sc in $(EXPORTER_SCENARIOS); do \
|
||||
out=$(EXPORTER_FIXTURES)/$$(echo $$sc | tr '-' '_').json; \
|
||||
@cd $(EXPORTER_DIR) && cabal build plan-export
|
||||
@mkdir -p $(EXPORTER_FIXTURES)
|
||||
@for sc in $(EXAMPLES_DIR)/*.scenario.json; do \
|
||||
base=$$(basename $$sc .scenario.json); \
|
||||
out=$(EXPORTER_FIXTURES)/$$base.json; \
|
||||
echo "exporting $$sc -> $$out"; \
|
||||
(cd $(EXPORTER_DIR) && cabal run -v0 glog-export -- $$sc) > $$out; \
|
||||
(cd $(EXPORTER_DIR) && cabal run -v0 plan-export -- examples/$$base.scenario.json) > $$out; \
|
||||
done
|
||||
|
||||
.PHONY: examples
|
||||
examples: export-fixtures ## Regenerate fixtures from scenarios and run them through plan-runner against their oracles.
|
||||
@cargo test -p plan-runner --test examples
|
||||
|
||||
.PHONY: shell
|
||||
shell: ## Enter the Nix dev shell defined in flake.nix
|
||||
@nix develop
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
## Storage Engine Playground
|
||||
|
||||
This repo is a playground for running small experiments related to things like FlowLog, DBSP, Geomerge, etc.
|
||||
This repo is a playground for running small experiments related to storage side of things.
|
||||
|
||||
### Development
|
||||
|
||||
@ -23,3 +23,7 @@ make test # Run tests
|
||||
```
|
||||
|
||||
Run `make help` to see all the available targets.
|
||||
|
||||
### Crates
|
||||
|
||||
Check out the [crates](crates) directory for more details.
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
## Crates
|
||||
## Crate Overview
|
||||
|
||||
Each subdirectory should be a normal Cargo package (or crate) with its own `Cargo.toml`.
|
||||
Something like this:
|
||||
|
||||
```text
|
||||
crates/
|
||||
app-name/
|
||||
Cargo.toml
|
||||
src/
|
||||
main.rs
|
||||
```
|
||||
| Crate | Kind | Responsibility |
|
||||
|-----------------|--------------------|-----------------------------------------------------------------------------------------------------------------|
|
||||
| `storage` | library | Defines a unified interface to use different storage backends (like Geomerge, SQLite, LMDB, etc.) |
|
||||
| `query-ops` | library | Provides a set of operators (like different types of joins) to execute a query plan. |
|
||||
| `plan-runner` | library and binary | Provides a CLI to run a query plan against a given storage backend using the operatirs provided by `query-ops`. |
|
||||
| `geomerge-demo` | binary | An example that shows how to write and read to Geomerge (as a storage). |
|
||||
|
||||
@ -5,381 +5,239 @@
|
||||
-->
|
||||
<!-- Title: GeomergeDemoWorkflow Pages: 1 -->
|
||||
<svg width="3020pt" height="407pt"
|
||||
viewBox="0.00 0.00 3020.25 407.00" xmlns="http://www.w3.org/2000/svg">
|
||||
viewBox="0.00 0.00 3020.25 407.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 403)">
|
||||
<title>GeomergeDemoWorkflow</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-403 3016.25,-403 3016.25,4 -4,4"/>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_inputs</title>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2"
|
||||
points="8,-11 8,-226 202.5,-226 202.5,-11 8,-11"/>
|
||||
<text text-anchor="middle" x="105.25" y="-208.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#555555">Inputs
|
||||
</text>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2" points="8,-11 8,-226 202.5,-226 202.5,-11 8,-11"/>
|
||||
<text text-anchor="middle" x="105.25" y="-208.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#555555">Inputs</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_demo</title>
|
||||
<path fill="#fafafa" stroke="#666666"
|
||||
d="M284.75,-8C284.75,-8 2574.75,-8 2574.75,-8 2580.75,-8 2586.75,-14 2586.75,-20 2586.75,-20 2586.75,-304 2586.75,-304 2586.75,-310 2580.75,-316 2574.75,-316 2574.75,-316 284.75,-316 284.75,-316 278.75,-316 272.75,-310 272.75,-304 272.75,-304 272.75,-20 272.75,-20 272.75,-14 278.75,-8 284.75,-8"/>
|
||||
<text text-anchor="middle" x="1429.75" y="-298.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#333333">geomerge-demo (run_demo)
|
||||
</text>
|
||||
<path fill="#fafafa" stroke="#666666" d="M284.75,-8C284.75,-8 2574.75,-8 2574.75,-8 2580.75,-8 2586.75,-14 2586.75,-20 2586.75,-20 2586.75,-304 2586.75,-304 2586.75,-310 2580.75,-316 2574.75,-316 2574.75,-316 284.75,-316 284.75,-316 278.75,-316 272.75,-310 272.75,-304 272.75,-304 272.75,-20 272.75,-20 272.75,-14 278.75,-8 284.75,-8"/>
|
||||
<text text-anchor="middle" x="1429.75" y="-298.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#333333">geomerge-demo (run_demo)</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_loading</title>
|
||||
<polygon fill="#fafafa" stroke="#9c27b0" stroke-dasharray="5,2"
|
||||
points="292.75,-116 292.75,-267 609.25,-267 609.25,-116 292.75,-116"/>
|
||||
<text text-anchor="middle" x="451" y="-249.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#7b1fa2">Theory Loading
|
||||
</text>
|
||||
<polygon fill="#fafafa" stroke="#9c27b0" stroke-dasharray="5,2" points="292.75,-116 292.75,-267 609.25,-267 609.25,-116 292.75,-116"/>
|
||||
<text text-anchor="middle" x="451" y="-249.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#7b1fa2">Theory Loading</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_store</title>
|
||||
<polygon fill="#fafafa" stroke="#4caf50" stroke-dasharray="5,2"
|
||||
points="648.25,-28 648.25,-267 1744,-267 1744,-28 648.25,-28"/>
|
||||
<text text-anchor="middle" x="1196.12" y="-249.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#388e3c">Storage and Transaction
|
||||
</text>
|
||||
<polygon fill="#fafafa" stroke="#4caf50" stroke-dasharray="5,2" points="648.25,-28 648.25,-267 1744,-267 1744,-28 648.25,-28"/>
|
||||
<text text-anchor="middle" x="1196.12" y="-249.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#388e3c">Storage and Transaction</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_persist</title>
|
||||
<polygon fill="#fafafa" stroke="#ff9800" stroke-dasharray="5,2"
|
||||
points="1809,-77 1809,-267 2566.75,-267 2566.75,-77 1809,-77"/>
|
||||
<text text-anchor="middle" x="2187.88" y="-249.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#f57c00">Persistence Round Trip
|
||||
</text>
|
||||
<polygon fill="#fafafa" stroke="#ff9800" stroke-dasharray="5,2" points="1809,-77 1809,-267 2566.75,-267 2566.75,-77 1809,-77"/>
|
||||
<text text-anchor="middle" x="2187.88" y="-249.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#f57c00">Persistence Round Trip</text>
|
||||
</g>
|
||||
<g id="clust6" class="cluster">
|
||||
<title>cluster_report</title>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2"
|
||||
points="2610.75,-174 2610.75,-391 3004.25,-391 3004.25,-174 2610.75,-174"/>
|
||||
<text text-anchor="middle" x="2807.5" y="-373.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#555555">Report
|
||||
</text>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2" points="2610.75,-174 2610.75,-391 3004.25,-391 3004.25,-174 2610.75,-174"/>
|
||||
<text text-anchor="middle" x="2807.5" y="-373.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#555555">Report</text>
|
||||
</g>
|
||||
<!-- paths_schema -->
|
||||
<g id="node1" class="node">
|
||||
<title>paths_schema</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M159,-179C159,-179 51.5,-179 51.5,-179 45.5,-179 39.5,-173 39.5,-167 39.5,-167 39.5,-141 39.5,-141 39.5,-135 45.5,-129 51.5,-129 51.5,-129 159,-129 159,-129 165,-129 171,-135 171,-141 171,-141 171,-167 171,-167 171,-173 165,-179 159,-179"/>
|
||||
<text text-anchor="middle" x="105.25" y="-161.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
paths.json
|
||||
</text>
|
||||
<text text-anchor="middle" x="105.25" y="-140.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
(compiled schema)
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M159,-179C159,-179 51.5,-179 51.5,-179 45.5,-179 39.5,-173 39.5,-167 39.5,-167 39.5,-141 39.5,-141 39.5,-135 45.5,-129 51.5,-129 51.5,-129 159,-129 159,-129 165,-129 171,-135 171,-141 171,-141 171,-167 171,-167 171,-173 165,-179 159,-179"/>
|
||||
<text text-anchor="middle" x="105.25" y="-161.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">paths.json</text>
|
||||
<text text-anchor="middle" x="105.25" y="-140.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">(compiled schema)</text>
|
||||
</g>
|
||||
<!-- load_theory -->
|
||||
<g id="node3" class="node">
|
||||
<title>load_theory</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M422.5,-191C422.5,-191 318.75,-191 318.75,-191 312.75,-191 306.75,-185 306.75,-179 306.75,-179 306.75,-153 306.75,-153 306.75,-147 312.75,-141 318.75,-141 318.75,-141 422.5,-141 422.5,-141 428.5,-141 434.5,-147 434.5,-153 434.5,-153 434.5,-179 434.5,-179 434.5,-185 428.5,-191 422.5,-191"/>
|
||||
<text text-anchor="middle" x="370.62" y="-173.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
load_paths_theory
|
||||
</text>
|
||||
<text text-anchor="middle" x="370.62" y="-152.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
(serde_json)
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M422.5,-191C422.5,-191 318.75,-191 318.75,-191 312.75,-191 306.75,-185 306.75,-179 306.75,-179 306.75,-153 306.75,-153 306.75,-147 312.75,-141 318.75,-141 318.75,-141 422.5,-141 422.5,-141 428.5,-141 434.5,-147 434.5,-153 434.5,-153 434.5,-179 434.5,-179 434.5,-185 428.5,-191 422.5,-191"/>
|
||||
<text text-anchor="middle" x="370.62" y="-173.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">load_paths_theory</text>
|
||||
<text text-anchor="middle" x="370.62" y="-152.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">(serde_json)</text>
|
||||
</g>
|
||||
<!-- paths_schema->load_theory -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>paths_schema->load_theory</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2"
|
||||
d="M171.35,-156.97C208.77,-158.67 255.91,-160.82 294.83,-162.59"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2"
|
||||
points="294.67,-166.09 304.82,-163.05 294.99,-159.1 294.67,-166.09"/>
|
||||
<text text-anchor="middle" x="241.12" y="-166.02" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">include_str!
|
||||
</text>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" d="M171.35,-156.97C208.77,-158.67 255.91,-160.82 294.83,-162.59"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="294.67,-166.09 304.82,-163.05 294.99,-159.1 294.67,-166.09"/>
|
||||
<text text-anchor="middle" x="241.12" y="-166.02" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">include_str!</text>
|
||||
</g>
|
||||
<!-- fixture_rows -->
|
||||
<g id="node2" class="node">
|
||||
<title>fixture_rows</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M172.5,-79C172.5,-79 38,-79 38,-79 32,-79 26,-73 26,-67 26,-67 26,-41 26,-41 26,-35 32,-29 38,-29 38,-29 172.5,-29 172.5,-29 178.5,-29 184.5,-35 184.5,-41 184.5,-41 184.5,-67 184.5,-67 184.5,-73 178.5,-79 172.5,-79"/>
|
||||
<text text-anchor="middle" x="105.25" y="-61.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Fixture Rows
|
||||
</text>
|
||||
<text text-anchor="middle" x="105.25" y="-40.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
(graphs, vertices, edge)
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M172.5,-79C172.5,-79 38,-79 38,-79 32,-79 26,-73 26,-67 26,-67 26,-41 26,-41 26,-35 32,-29 38,-29 38,-29 172.5,-29 172.5,-29 178.5,-29 184.5,-35 184.5,-41 184.5,-41 184.5,-67 184.5,-67 184.5,-73 178.5,-79 172.5,-79"/>
|
||||
<text text-anchor="middle" x="105.25" y="-61.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Fixture Rows</text>
|
||||
<text text-anchor="middle" x="105.25" y="-40.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">(graphs, vertices, edge)</text>
|
||||
</g>
|
||||
<!-- transact -->
|
||||
<g id="node6" class="node">
|
||||
<title>transact</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M1147.25,-223.62C1147.25,-223.62 950.75,-223.62 950.75,-223.62 944.75,-223.62 938.75,-217.62 938.75,-211.62 938.75,-211.62 938.75,-54.38 938.75,-54.38 938.75,-48.38 944.75,-42.38 950.75,-42.38 950.75,-42.38 1147.25,-42.38 1147.25,-42.38 1153.25,-42.38 1159.25,-48.38 1159.25,-54.38 1159.25,-54.38 1159.25,-211.62 1159.25,-211.62 1159.25,-217.62 1153.25,-223.62 1147.25,-223.62"/>
|
||||
<text text-anchor="start" x="954.88" y="-203.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">add_paths_data (tx.insert ×7)
|
||||
</text>
|
||||
<text text-anchor="start" x="950.75" y="-174.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
• insert Graphs rows
|
||||
</text>
|
||||
<text text-anchor="start" x="950.75" y="-145.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
• insert G0, G1 rows
|
||||
</text>
|
||||
<text text-anchor="start" x="950.75" y="-116.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
• insert G.V vertices
|
||||
</text>
|
||||
<text text-anchor="start" x="950.75" y="-87.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
insert G.E edge
|
||||
</text>
|
||||
<text text-anchor="start" x="950.75" y="-58.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
pending RowIds reused as FKs
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M1147.25,-223.62C1147.25,-223.62 950.75,-223.62 950.75,-223.62 944.75,-223.62 938.75,-217.62 938.75,-211.62 938.75,-211.62 938.75,-54.38 938.75,-54.38 938.75,-48.38 944.75,-42.38 950.75,-42.38 950.75,-42.38 1147.25,-42.38 1147.25,-42.38 1153.25,-42.38 1159.25,-48.38 1159.25,-54.38 1159.25,-54.38 1159.25,-211.62 1159.25,-211.62 1159.25,-217.62 1153.25,-223.62 1147.25,-223.62"/>
|
||||
<text text-anchor="start" x="954.88" y="-203.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">add_paths_data (tx.insert ×7)</text>
|
||||
<text text-anchor="start" x="950.75" y="-174.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• insert Graphs rows</text>
|
||||
<text text-anchor="start" x="950.75" y="-145.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• insert G0, G1 rows</text>
|
||||
<text text-anchor="start" x="950.75" y="-116.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• insert G.V vertices</text>
|
||||
<text text-anchor="start" x="950.75" y="-87.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• insert G.E edge</text>
|
||||
<text text-anchor="start" x="950.75" y="-58.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• pending RowIds reused as FKs</text>
|
||||
</g>
|
||||
<!-- fixture_rows->transact -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>fixture_rows->transact</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M184.91,-54C237.25,-54 307.55,-54 369.62,-54 369.62,-54 369.62,-54 768,-54 821.42,-54 878.49,-67.48 927.08,-83.31"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2"
|
||||
points="925.95,-86.62 936.55,-86.47 928.17,-79.98 925.95,-86.62"/>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" stroke-dasharray="5,2" d="M184.91,-54C237.25,-54 307.55,-54 369.62,-54 369.62,-54 369.62,-54 768,-54 821.42,-54 878.49,-67.48 927.08,-83.31"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="925.95,-86.62 936.55,-86.47 928.17,-79.98 925.95,-86.62"/>
|
||||
</g>
|
||||
<!-- flat_theory -->
|
||||
<g id="node4" class="node">
|
||||
<title>flat_theory</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M583.25,-224.12C583.25,-224.12 513.5,-224.12 513.5,-224.12 507.5,-224.12 501.5,-218.12 501.5,-212.12 501.5,-212.12 501.5,-141.88 501.5,-141.88 501.5,-135.88 507.5,-129.88 513.5,-129.88 513.5,-129.88 583.25,-129.88 583.25,-129.88 589.25,-129.88 595.25,-135.88 595.25,-141.88 595.25,-141.88 595.25,-212.12 595.25,-212.12 595.25,-218.12 589.25,-224.12 583.25,-224.12"/>
|
||||
<text text-anchor="start" x="513.5" y="-203.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">FlatTheory
|
||||
</text>
|
||||
<text text-anchor="start" x="513.5" y="-174.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
10 tables
|
||||
</text>
|
||||
<text text-anchor="start" x="513.5" y="-145.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
12 laws
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M583.25,-224.12C583.25,-224.12 513.5,-224.12 513.5,-224.12 507.5,-224.12 501.5,-218.12 501.5,-212.12 501.5,-212.12 501.5,-141.88 501.5,-141.88 501.5,-135.88 507.5,-129.88 513.5,-129.88 513.5,-129.88 583.25,-129.88 583.25,-129.88 589.25,-129.88 595.25,-135.88 595.25,-141.88 595.25,-141.88 595.25,-212.12 595.25,-212.12 595.25,-218.12 589.25,-224.12 583.25,-224.12"/>
|
||||
<text text-anchor="start" x="513.5" y="-203.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">FlatTheory</text>
|
||||
<text text-anchor="start" x="513.5" y="-174.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• 10 tables</text>
|
||||
<text text-anchor="start" x="513.5" y="-145.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• 12 laws</text>
|
||||
</g>
|
||||
<!-- load_theory->flat_theory -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>load_theory->flat_theory</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2"
|
||||
d="M434.58,-169.94C452.51,-171.06 471.95,-172.28 489.56,-173.38"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="489.11,-176.86 499.31,-173.99 489.55,-169.87 489.11,-176.86"/>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M434.58,-169.94C452.51,-171.06 471.95,-172.28 489.56,-173.38"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="489.11,-176.86 499.31,-173.99 489.55,-169.87 489.11,-176.86"/>
|
||||
</g>
|
||||
<!-- build_store -->
|
||||
<g id="node5" class="node">
|
||||
<title>build_store</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M859.75,-186C859.75,-186 674.25,-186 674.25,-186 668.25,-186 662.25,-180 662.25,-174 662.25,-174 662.25,-148 662.25,-148 662.25,-142 668.25,-136 674.25,-136 674.25,-136 859.75,-136 859.75,-136 865.75,-136 871.75,-142 871.75,-148 871.75,-148 871.75,-174 871.75,-174 871.75,-180 865.75,-186 859.75,-186"/>
|
||||
<text text-anchor="middle" x="767" y="-168.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
GeomergeStorage::from_theory
|
||||
</text>
|
||||
<text text-anchor="middle" x="767" y="-147.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
(Store::try_from_theory)
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M859.75,-186C859.75,-186 674.25,-186 674.25,-186 668.25,-186 662.25,-180 662.25,-174 662.25,-174 662.25,-148 662.25,-148 662.25,-142 668.25,-136 674.25,-136 674.25,-136 859.75,-136 859.75,-136 865.75,-136 871.75,-142 871.75,-148 871.75,-148 871.75,-174 871.75,-174 871.75,-180 865.75,-186 859.75,-186"/>
|
||||
<text text-anchor="middle" x="767" y="-168.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">GeomergeStorage::from_theory</text>
|
||||
<text text-anchor="middle" x="767" y="-147.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">(Store::try_from_theory)</text>
|
||||
</g>
|
||||
<!-- flat_theory->build_store -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>flat_theory->build_store</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2"
|
||||
d="M595.58,-173.59C611.81,-172.39 630.85,-170.98 650.22,-169.55"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="650.44,-173.05 660.15,-168.82 649.92,-166.06 650.44,-173.05"/>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M595.58,-173.59C611.81,-172.39 630.85,-170.98 650.22,-169.55"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="650.44,-173.05 660.15,-168.82 649.92,-166.06 650.44,-173.05"/>
|
||||
</g>
|
||||
<!-- demo_report -->
|
||||
<g id="node13" class="node">
|
||||
<title>demo_report</title>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5"
|
||||
d="M2810.25,-344.12C2810.25,-344.12 2640.75,-344.12 2640.75,-344.12 2634.75,-344.12 2628.75,-338.12 2628.75,-332.12 2628.75,-332.12 2628.75,-203.88 2628.75,-203.88 2628.75,-197.88 2634.75,-191.88 2640.75,-191.88 2640.75,-191.88 2810.25,-191.88 2810.25,-191.88 2816.25,-191.88 2822.25,-197.88 2822.25,-203.88 2822.25,-203.88 2822.25,-332.12 2822.25,-332.12 2822.25,-338.12 2816.25,-344.12 2810.25,-344.12"/>
|
||||
<text text-anchor="start" x="2684.25" y="-323.82" font-family="Helvetica,Arial,sans-serif"
|
||||
font-weight="bold" font-size="14.00">DemoReport
|
||||
</text>
|
||||
<text text-anchor="start" x="2640.75" y="-294.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• table_count, law_count
|
||||
</text>
|
||||
<text text-anchor="start" x="2640.75" y="-265.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• graph, vertex, edge counts
|
||||
</text>
|
||||
<text text-anchor="start" x="2640.75" y="-236.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• edge endpoints
|
||||
</text>
|
||||
<text text-anchor="start" x="2640.75" y="-207.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• persisted_bytes
|
||||
</text>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5" d="M2810.25,-344.12C2810.25,-344.12 2640.75,-344.12 2640.75,-344.12 2634.75,-344.12 2628.75,-338.12 2628.75,-332.12 2628.75,-332.12 2628.75,-203.88 2628.75,-203.88 2628.75,-197.88 2634.75,-191.88 2640.75,-191.88 2640.75,-191.88 2810.25,-191.88 2810.25,-191.88 2816.25,-191.88 2822.25,-197.88 2822.25,-203.88 2822.25,-203.88 2822.25,-332.12 2822.25,-332.12 2822.25,-338.12 2816.25,-344.12 2810.25,-344.12"/>
|
||||
<text text-anchor="start" x="2684.25" y="-323.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">DemoReport</text>
|
||||
<text text-anchor="start" x="2640.75" y="-294.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• table_count, law_count</text>
|
||||
<text text-anchor="start" x="2640.75" y="-265.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• graph, vertex, edge counts</text>
|
||||
<text text-anchor="start" x="2640.75" y="-236.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• edge endpoints</text>
|
||||
<text text-anchor="start" x="2640.75" y="-207.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• persisted_bytes</text>
|
||||
</g>
|
||||
<!-- flat_theory->demo_report -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>flat_theory->demo_report</title>
|
||||
<path fill="none" stroke="#607d8b" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M586.43,-224.45C603.41,-243.31 624.95,-263.65 648.25,-277 695.11,-303.85 711.99,-307 766,-307 766,-307 766,-307 2483.88,-307 2527.78,-307 2575.62,-300.59 2616.99,-292.98"/>
|
||||
<polygon fill="#607d8b" stroke="#607d8b" stroke-width="1.2"
|
||||
points="2617.52,-296.44 2626.7,-291.14 2616.22,-289.56 2617.52,-296.44"/>
|
||||
<text text-anchor="middle" x="1776.5" y="-311.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">counts
|
||||
</text>
|
||||
<path fill="none" stroke="#607d8b" stroke-width="1.2" stroke-dasharray="5,2" d="M586.43,-224.45C603.41,-243.31 624.95,-263.65 648.25,-277 695.11,-303.85 711.99,-307 766,-307 766,-307 766,-307 2483.88,-307 2527.78,-307 2575.62,-300.59 2616.99,-292.98"/>
|
||||
<polygon fill="#607d8b" stroke="#607d8b" stroke-width="1.2" points="2617.52,-296.44 2626.7,-291.14 2616.22,-289.56 2617.52,-296.44"/>
|
||||
<text text-anchor="middle" x="1776.5" y="-311.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">counts</text>
|
||||
</g>
|
||||
<!-- build_store->transact -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>build_store->transact</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2"
|
||||
d="M871.86,-150.61C889.82,-148.82 908.63,-146.94 927,-145.1"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="927.27,-148.59 936.87,-144.11 926.57,-141.63 927.27,-148.59"/>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M871.86,-150.61C889.82,-148.82 908.63,-146.94 927,-145.1"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="927.27,-148.59 936.87,-144.11 926.57,-141.63 927.27,-148.59"/>
|
||||
</g>
|
||||
<!-- commit -->
|
||||
<g id="node7" class="node">
|
||||
<title>commit</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M1483.5,-180.12C1483.5,-180.12 1238.25,-180.12 1238.25,-180.12 1232.25,-180.12 1226.25,-174.12 1226.25,-168.12 1226.25,-168.12 1226.25,-97.88 1226.25,-97.88 1226.25,-91.88 1232.25,-85.88 1238.25,-85.88 1238.25,-85.88 1483.5,-85.88 1483.5,-85.88 1489.5,-85.88 1495.5,-91.88 1495.5,-97.88 1495.5,-97.88 1495.5,-168.12 1495.5,-168.12 1495.5,-174.12 1489.5,-180.12 1483.5,-180.12"/>
|
||||
<text text-anchor="start" x="1323.75" y="-159.82" font-family="Helvetica,Arial,sans-serif"
|
||||
font-weight="bold" font-size="14.00">tx.commit()
|
||||
</text>
|
||||
<text text-anchor="start" x="1238.25" y="-130.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• law validation
|
||||
</text>
|
||||
<text text-anchor="start" x="1238.25" y="-101.58" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• CommittedTx resolves pending RowIds
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M1483.5,-180.12C1483.5,-180.12 1238.25,-180.12 1238.25,-180.12 1232.25,-180.12 1226.25,-174.12 1226.25,-168.12 1226.25,-168.12 1226.25,-97.88 1226.25,-97.88 1226.25,-91.88 1232.25,-85.88 1238.25,-85.88 1238.25,-85.88 1483.5,-85.88 1483.5,-85.88 1489.5,-85.88 1495.5,-91.88 1495.5,-97.88 1495.5,-97.88 1495.5,-168.12 1495.5,-168.12 1495.5,-174.12 1489.5,-180.12 1483.5,-180.12"/>
|
||||
<text text-anchor="start" x="1323.75" y="-159.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">tx.commit()</text>
|
||||
<text text-anchor="start" x="1238.25" y="-130.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• law validation</text>
|
||||
<text text-anchor="start" x="1238.25" y="-101.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• CommittedTx resolves pending RowIds</text>
|
||||
</g>
|
||||
<!-- transact->commit -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>transact->commit</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2"
|
||||
d="M1159.69,-133C1177.34,-133 1195.85,-133 1214.18,-133"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="1214.06,-136.5 1224.06,-133 1214.06,-129.5 1214.06,-136.5"/>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M1159.69,-133C1177.34,-133 1195.85,-133 1214.18,-133"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="1214.06,-136.5 1224.06,-133 1214.06,-129.5 1214.06,-136.5"/>
|
||||
</g>
|
||||
<!-- assert_edge -->
|
||||
<g id="node8" class="node">
|
||||
<title>assert_edge</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M1718,-158C1718,-158 1574.5,-158 1574.5,-158 1568.5,-158 1562.5,-152 1562.5,-146 1562.5,-146 1562.5,-120 1562.5,-120 1562.5,-114 1568.5,-108 1574.5,-108 1574.5,-108 1718,-108 1718,-108 1724,-108 1730,-114 1730,-120 1730,-120 1730,-146 1730,-146 1730,-152 1724,-158 1718,-158"/>
|
||||
<text text-anchor="middle" x="1646.25" y="-140.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">assert_edge_was_stored
|
||||
</text>
|
||||
<text text-anchor="middle" x="1646.25" y="-119.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">(storage.scan(G.E))
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M1718,-158C1718,-158 1574.5,-158 1574.5,-158 1568.5,-158 1562.5,-152 1562.5,-146 1562.5,-146 1562.5,-120 1562.5,-120 1562.5,-114 1568.5,-108 1574.5,-108 1574.5,-108 1718,-108 1718,-108 1724,-108 1730,-114 1730,-120 1730,-120 1730,-146 1730,-146 1730,-152 1724,-158 1718,-158"/>
|
||||
<text text-anchor="middle" x="1646.25" y="-140.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">assert_edge_was_stored</text>
|
||||
<text text-anchor="middle" x="1646.25" y="-119.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">(storage.scan(G.E))</text>
|
||||
</g>
|
||||
<!-- commit->assert_edge -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>commit->assert_edge</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2"
|
||||
d="M1495.68,-133C1514.16,-133 1532.83,-133 1550.41,-133"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="1550.27,-136.5 1560.27,-133 1550.27,-129.5 1550.27,-136.5"/>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M1495.68,-133C1514.16,-133 1532.83,-133 1550.41,-133"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="1550.27,-136.5 1560.27,-133 1550.27,-129.5 1550.27,-136.5"/>
|
||||
</g>
|
||||
<!-- dump_before -->
|
||||
<g id="node9" class="node">
|
||||
<title>dump_before</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M1917.75,-159C1917.75,-159 1835,-159 1835,-159 1829,-159 1823,-153 1823,-147 1823,-147 1823,-121 1823,-121 1823,-115 1829,-109 1835,-109 1835,-109 1917.75,-109 1917.75,-109 1923.75,-109 1929.75,-115 1929.75,-121 1929.75,-121 1929.75,-147 1929.75,-147 1929.75,-153 1923.75,-159 1917.75,-159"/>
|
||||
<text text-anchor="middle" x="1876.38" y="-141.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">store.dump()
|
||||
</text>
|
||||
<text text-anchor="middle" x="1876.38" y="-120.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">(before persist)
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M1917.75,-159C1917.75,-159 1835,-159 1835,-159 1829,-159 1823,-153 1823,-147 1823,-147 1823,-121 1823,-121 1823,-115 1829,-109 1835,-109 1835,-109 1917.75,-109 1917.75,-109 1923.75,-109 1929.75,-115 1929.75,-121 1929.75,-121 1929.75,-147 1929.75,-147 1929.75,-153 1923.75,-159 1917.75,-159"/>
|
||||
<text text-anchor="middle" x="1876.38" y="-141.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">store.dump()</text>
|
||||
<text text-anchor="middle" x="1876.38" y="-120.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">(before persist)</text>
|
||||
</g>
|
||||
<!-- assert_edge->dump_before -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>assert_edge->dump_before</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2"
|
||||
d="M1730.25,-133.36C1756.87,-133.48 1785.93,-133.61 1811,-133.72"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2"
|
||||
points="1810.76,-137.22 1820.77,-133.76 1810.79,-130.22 1810.76,-137.22"/>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" d="M1730.25,-133.36C1756.87,-133.48 1785.93,-133.61 1811,-133.72"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="1810.76,-137.22 1820.77,-133.76 1810.79,-130.22 1810.76,-137.22"/>
|
||||
</g>
|
||||
<!-- encode -->
|
||||
<g id="node10" class="node">
|
||||
<title>encode</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M2108,-159C2108,-159 2008.75,-159 2008.75,-159 2002.75,-159 1996.75,-153 1996.75,-147 1996.75,-147 1996.75,-121 1996.75,-121 1996.75,-115 2002.75,-109 2008.75,-109 2008.75,-109 2108,-109 2108,-109 2114,-109 2120,-115 2120,-121 2120,-121 2120,-147 2120,-147 2120,-153 2114,-159 2108,-159"/>
|
||||
<text text-anchor="middle" x="2058.38" y="-141.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">pst::encode_store
|
||||
</text>
|
||||
<text text-anchor="middle" x="2058.38" y="-120.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">-> bytes
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M2108,-159C2108,-159 2008.75,-159 2008.75,-159 2002.75,-159 1996.75,-153 1996.75,-147 1996.75,-147 1996.75,-121 1996.75,-121 1996.75,-115 2002.75,-109 2008.75,-109 2008.75,-109 2108,-109 2108,-109 2114,-109 2120,-115 2120,-121 2120,-121 2120,-147 2120,-147 2120,-153 2114,-159 2108,-159"/>
|
||||
<text text-anchor="middle" x="2058.38" y="-141.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">pst::encode_store</text>
|
||||
<text text-anchor="middle" x="2058.38" y="-120.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">-> bytes</text>
|
||||
</g>
|
||||
<!-- dump_before->encode -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>dump_before->encode</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2"
|
||||
d="M1929.92,-134C1947.11,-134 1966.52,-134 1984.92,-134"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2"
|
||||
points="1984.7,-137.5 1994.7,-134 1984.7,-130.5 1984.7,-137.5"/>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" d="M1929.92,-134C1947.11,-134 1966.52,-134 1984.92,-134"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="1984.7,-137.5 1994.7,-134 1984.7,-130.5 1984.7,-137.5"/>
|
||||
</g>
|
||||
<!-- compare -->
|
||||
<g id="node12" class="node">
|
||||
<title>compare</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M2540.75,-224C2540.75,-224 2425,-224 2425,-224 2419,-224 2413,-218 2413,-212 2413,-212 2413,-200 2413,-200 2413,-194 2419,-188 2425,-188 2425,-188 2540.75,-188 2540.75,-188 2546.75,-188 2552.75,-194 2552.75,-200 2552.75,-200 2552.75,-212 2552.75,-212 2552.75,-218 2546.75,-224 2540.75,-224"/>
|
||||
<text text-anchor="middle" x="2482.88" y="-203.2" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">dump equality check
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M2540.75,-224C2540.75,-224 2425,-224 2425,-224 2419,-224 2413,-218 2413,-212 2413,-212 2413,-200 2413,-200 2413,-194 2419,-188 2425,-188 2425,-188 2540.75,-188 2540.75,-188 2546.75,-188 2552.75,-194 2552.75,-200 2552.75,-200 2552.75,-212 2552.75,-212 2552.75,-218 2546.75,-224 2540.75,-224"/>
|
||||
<text text-anchor="middle" x="2482.88" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">dump equality check</text>
|
||||
</g>
|
||||
<!-- dump_before->compare -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>dump_before->compare</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M1927.2,-159.48C1948.13,-168.95 1973.07,-178.68 1996.75,-184 2136.12,-215.31 2303.52,-214.81 2400.78,-210.9"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2"
|
||||
points="2400.93,-214.39 2410.77,-210.47 2400.63,-207.4 2400.93,-214.39"/>
|
||||
<text text-anchor="middle" x="2171.38" y="-214.27" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">expected
|
||||
</text>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" stroke-dasharray="5,2" d="M1927.2,-159.48C1948.13,-168.95 1973.07,-178.68 1996.75,-184 2136.12,-215.31 2303.52,-214.81 2400.78,-210.9"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="2400.93,-214.39 2410.77,-210.47 2400.63,-207.4 2400.93,-214.39"/>
|
||||
<text text-anchor="middle" x="2171.38" y="-214.27" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">expected</text>
|
||||
</g>
|
||||
<!-- decode -->
|
||||
<g id="node11" class="node">
|
||||
<title>decode</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M2334,-159C2334,-159 2234.75,-159 2234.75,-159 2228.75,-159 2222.75,-153 2222.75,-147 2222.75,-147 2222.75,-121 2222.75,-121 2222.75,-115 2228.75,-109 2234.75,-109 2234.75,-109 2334,-109 2334,-109 2340,-109 2346,-115 2346,-121 2346,-121 2346,-147 2346,-147 2346,-153 2340,-159 2334,-159"/>
|
||||
<text text-anchor="middle" x="2284.38" y="-141.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">pst::decode_store
|
||||
</text>
|
||||
<text text-anchor="middle" x="2284.38" y="-120.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">-> restored Store
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M2334,-159C2334,-159 2234.75,-159 2234.75,-159 2228.75,-159 2222.75,-153 2222.75,-147 2222.75,-147 2222.75,-121 2222.75,-121 2222.75,-115 2228.75,-109 2234.75,-109 2234.75,-109 2334,-109 2334,-109 2340,-109 2346,-115 2346,-121 2346,-121 2346,-147 2346,-147 2346,-153 2340,-159 2334,-159"/>
|
||||
<text text-anchor="middle" x="2284.38" y="-141.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">pst::decode_store</text>
|
||||
<text text-anchor="middle" x="2284.38" y="-120.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">-> restored Store</text>
|
||||
</g>
|
||||
<!-- encode->decode -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>encode->decode</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2"
|
||||
d="M2120.29,-134C2148.18,-134 2181.46,-134 2210.66,-134"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2"
|
||||
points="2210.66,-137.5 2220.66,-134 2210.66,-130.5 2210.66,-137.5"/>
|
||||
<text text-anchor="middle" x="2171.38" y="-138.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">bytes
|
||||
</text>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" d="M2120.29,-134C2148.18,-134 2181.46,-134 2210.66,-134"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="2210.66,-137.5 2220.66,-134 2210.66,-130.5 2210.66,-137.5"/>
|
||||
<text text-anchor="middle" x="2171.38" y="-138.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">bytes</text>
|
||||
</g>
|
||||
<!-- decode->compare -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>decode->compare</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2"
|
||||
d="M2346.47,-156.39C2370.11,-165.05 2397.1,-174.94 2420.74,-183.6"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2"
|
||||
points="2419.39,-186.83 2429.99,-186.99 2421.8,-180.26 2419.39,-186.83"/>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" d="M2346.47,-156.39C2370.11,-165.05 2397.1,-174.94 2420.74,-183.6"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="2419.39,-186.83 2429.99,-186.99 2421.8,-180.26 2419.39,-186.83"/>
|
||||
</g>
|
||||
<!-- compare->demo_report -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>compare->demo_report</title>
|
||||
<path fill="none" stroke="#607d8b" stroke-width="1.2"
|
||||
d="M2553.15,-223.85C2573.13,-229 2595.42,-234.74 2617.13,-240.34"/>
|
||||
<polygon fill="#607d8b" stroke="#607d8b" stroke-width="1.2"
|
||||
points="2616.11,-243.69 2626.67,-242.79 2617.85,-236.91 2616.11,-243.69"/>
|
||||
<path fill="none" stroke="#607d8b" stroke-width="1.2" d="M2553.15,-223.85C2573.13,-229 2595.42,-234.74 2617.13,-240.34"/>
|
||||
<polygon fill="#607d8b" stroke="#607d8b" stroke-width="1.2" points="2616.11,-243.69 2626.67,-242.79 2617.85,-236.91 2616.11,-243.69"/>
|
||||
</g>
|
||||
<!-- stdout -->
|
||||
<g id="node14" class="node">
|
||||
<title>stdout</title>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5"
|
||||
d="M2974.25,-293C2974.25,-293 2901.25,-293 2901.25,-293 2895.25,-293 2889.25,-287 2889.25,-281 2889.25,-281 2889.25,-255 2889.25,-255 2889.25,-249 2895.25,-243 2901.25,-243 2901.25,-243 2974.25,-243 2974.25,-243 2980.25,-243 2986.25,-249 2986.25,-255 2986.25,-255 2986.25,-281 2986.25,-281 2986.25,-287 2980.25,-293 2974.25,-293"/>
|
||||
<text text-anchor="middle" x="2937.75" y="-275.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">stdout
|
||||
</text>
|
||||
<text text-anchor="middle" x="2937.75" y="-254.7" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">(println! lines)
|
||||
</text>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5" d="M2974.25,-293C2974.25,-293 2901.25,-293 2901.25,-293 2895.25,-293 2889.25,-287 2889.25,-281 2889.25,-281 2889.25,-255 2889.25,-255 2889.25,-249 2895.25,-243 2901.25,-243 2901.25,-243 2974.25,-243 2974.25,-243 2980.25,-243 2986.25,-249 2986.25,-255 2986.25,-255 2986.25,-281 2986.25,-281 2986.25,-287 2980.25,-293 2974.25,-293"/>
|
||||
<text text-anchor="middle" x="2937.75" y="-275.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">stdout</text>
|
||||
<text text-anchor="middle" x="2937.75" y="-254.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">(println! lines)</text>
|
||||
</g>
|
||||
<!-- demo_report->stdout -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>demo_report->stdout</title>
|
||||
<path fill="none" stroke="#607d8b" stroke-width="1.2"
|
||||
d="M2822.7,-268C2841.33,-268 2860.33,-268 2877.35,-268"/>
|
||||
<polygon fill="#607d8b" stroke="#607d8b" stroke-width="1.2"
|
||||
points="2876.96,-271.5 2886.96,-268 2876.96,-264.5 2876.96,-271.5"/>
|
||||
<path fill="none" stroke="#607d8b" stroke-width="1.2" d="M2822.7,-268C2841.33,-268 2860.33,-268 2877.35,-268"/>
|
||||
<polygon fill="#607d8b" stroke="#607d8b" stroke-width="1.2" points="2876.96,-271.5 2886.96,-268 2876.96,-264.5 2876.96,-271.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 20 KiB |
@ -1,166 +0,0 @@
|
||||
{
|
||||
"_scenario": "three-atom-chain",
|
||||
"facts": {
|
||||
"edge": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
},
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "edge:1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "node:3"
|
||||
},
|
||||
{
|
||||
"str": "edge:2"
|
||||
}
|
||||
]
|
||||
],
|
||||
"node": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:3"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"query": {
|
||||
"nodes": [
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "a"
|
||||
},
|
||||
{
|
||||
"var": "b"
|
||||
},
|
||||
{
|
||||
"var": "_w0_2"
|
||||
}
|
||||
],
|
||||
"table": "edge"
|
||||
}
|
||||
},
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "b"
|
||||
},
|
||||
{
|
||||
"var": "c"
|
||||
},
|
||||
{
|
||||
"var": "_w1_2"
|
||||
}
|
||||
],
|
||||
"table": "edge"
|
||||
}
|
||||
},
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "a"
|
||||
}
|
||||
],
|
||||
"table": "node"
|
||||
}
|
||||
},
|
||||
"id": 3
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 1,
|
||||
"op": "left",
|
||||
"right": 3
|
||||
}
|
||||
},
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 2,
|
||||
"op": "left",
|
||||
"right": 4
|
||||
}
|
||||
},
|
||||
"id": 5
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 5,
|
||||
"op": "right",
|
||||
"right": 4
|
||||
}
|
||||
},
|
||||
"id": 6
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 6,
|
||||
"op": "right",
|
||||
"right": 3
|
||||
}
|
||||
},
|
||||
"id": 7
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 6,
|
||||
"op": "natural",
|
||||
"right": 7
|
||||
}
|
||||
},
|
||||
"id": 8
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 5,
|
||||
"op": "natural",
|
||||
"right": 8
|
||||
}
|
||||
},
|
||||
"id": 9
|
||||
}
|
||||
],
|
||||
"root": 9
|
||||
},
|
||||
"schema": {
|
||||
"edge": 3,
|
||||
"node": 1
|
||||
}
|
||||
}
|
||||
@ -1,344 +0,0 @@
|
||||
//! End-to-end runner that executes a `geolog-lang` conjunctive-query plan
|
||||
//! against this workspace's storage and `query-ops` operators.
|
||||
//!
|
||||
//! The upstream Haskell planner in `external/geolog/geolog-lang`
|
||||
//! (`Geolog.DB.Plan`) builds a Yannakakis-style join DAG over `QAtom`s. This
|
||||
//! crate accepts that DAG as JSON, materializes the input relations through
|
||||
//! the [`Storage`] trait, and walks the DAG using
|
||||
//! [`query_ops::atom::scan_atom`], [`query_ops::join::semijoin`], and
|
||||
//! [`query_ops::join::natural_join`]. The result is a binding
|
||||
//! [`Relation`](query_ops::relation::Relation) over the query's variables.
|
||||
//!
|
||||
//! The JSON IR mirrors `Geolog.DB.Plan.JoinPlan` and `Geolog.DB.InMemory.QAtom`
|
||||
//! without depending on the Haskell side at build time. A Haskell exporter
|
||||
//! that dumps `(schema, facts, JoinPlan)` to this shape is the planned
|
||||
//! follow-up that completes the round trip; the IR is the contract.
|
||||
//!
|
||||
//! Mapping from the Haskell planner:
|
||||
//!
|
||||
//! | `Geolog.DB.Plan` | this crate |
|
||||
//! |-----------------------------|-----------------------------------------------|
|
||||
//! | `PlanEvalAtom` | [`Action::Scan`] → `scan_atom` |
|
||||
//! | `PlanJoin LeftJoin a b` | [`Action::Join`] with [`JoinOp::Left`] → `semijoin(rel[a], rel[b])` |
|
||||
//! | `PlanJoin RightJoin a b` | [`Action::Join`] with [`JoinOp::Right`] → `semijoin(rel[b], rel[a])` |
|
||||
//! | `PlanJoin NaturalJoin a b` | [`Action::Join`] with [`JoinOp::Natural`] → `natural_join(rel[a], rel[b])` |
|
||||
//!
|
||||
//! The atom side covers `evalAtom` (`Geolog.DB.InMemory`): a [`Term::Var`]
|
||||
//! repeated across positions enforces equality, [`Term::Lit`] filters by
|
||||
//! constant, and distinct variables project in first-occurrence order.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use query_ops::atom::{AtomPattern, Term, scan_atom};
|
||||
use query_ops::join::{natural_join, semijoin};
|
||||
use query_ops::relation::Relation;
|
||||
use storage::value::Value;
|
||||
use storage::{MemoryStorage, Storage, StorageError, scan_as_table};
|
||||
|
||||
/// A single fixture: schema, ground facts, and a query plan to execute.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Plan {
|
||||
/// Relation name → arity (column count).
|
||||
pub schema: HashMap<String, usize>,
|
||||
/// Relation name → list of ground tuples to insert before execution.
|
||||
pub facts: HashMap<String, Vec<Vec<JsonValue>>>,
|
||||
/// The join DAG itself.
|
||||
pub query: Query,
|
||||
}
|
||||
|
||||
/// Mirrors `Geolog.DB.Plan.JoinPlan`: a set of nodes plus the id of the
|
||||
/// rooted result node.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Query {
|
||||
pub root: u32,
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
/// One node of the plan DAG. `id`s are dense within a `Query` but don't need
|
||||
/// to start at any particular value, mirroring the Haskell `PlanNodeId`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Node {
|
||||
pub id: u32,
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
/// What to compute at a node. Tagged externally so JSON reads as
|
||||
/// `{"action": {"scan": {...}}}` or `{"action": {"join": {...}}}`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Action {
|
||||
Scan(Atom),
|
||||
Join(Join),
|
||||
}
|
||||
|
||||
/// A flat atom pattern, one entry per column of the target relation.
|
||||
/// Matches the `toFlatArgs` view used by `Geolog.DB.InMemory.evalAtom`:
|
||||
/// `qaValues` positions are filled in directly, and the entity-id column
|
||||
/// (if any) appears at the last position. Wildcard positions in the
|
||||
/// Haskell `QAtom` (a `Map Int QVal` with a missing key) translate to a
|
||||
/// fresh, unique variable name on this side, which the operator binds but
|
||||
/// never joins against.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Atom {
|
||||
pub table: String,
|
||||
pub columns: Vec<JsonTerm>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum JsonTerm {
|
||||
Var(String),
|
||||
Lit(JsonValue),
|
||||
}
|
||||
|
||||
/// Wire-level value tag. Restricted to what `storage::value::Value` carries.
|
||||
/// Entity identities from the Haskell side (`ValEntity path id`) round-trip
|
||||
/// through `Str` for now using a `"path:id"` convention; that's a fixture
|
||||
/// concern, not a runner concern.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum JsonValue {
|
||||
Int(i64),
|
||||
Str(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Join {
|
||||
pub op: JoinOp,
|
||||
pub left: u32,
|
||||
pub right: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum JoinOp {
|
||||
/// `Geolog.DB.Plan.LeftJoin`: result is `left` rows whose shared columns
|
||||
/// appear in `right`. Lowered to `semijoin(left, right)`.
|
||||
Left,
|
||||
/// `Geolog.DB.Plan.RightJoin`: result is `right` rows whose shared
|
||||
/// columns appear in `left`. Lowered to `semijoin(right, left)`.
|
||||
Right,
|
||||
/// `Geolog.DB.Plan.NaturalJoin`. Lowered to `natural_join(left, right)`.
|
||||
Natural,
|
||||
}
|
||||
|
||||
/// Errors a runner can produce in addition to storage failures.
|
||||
#[derive(Debug)]
|
||||
pub enum RunError {
|
||||
/// A fact references a relation that isn't declared in `schema`.
|
||||
UnknownRelation(String),
|
||||
/// A node id appears in a `Join` action but no node with that id exists.
|
||||
MissingNode(u32),
|
||||
/// `Query.root` doesn't match any node in `nodes`.
|
||||
MissingRoot(u32),
|
||||
/// Two nodes share the same id.
|
||||
DuplicateNode(u32),
|
||||
/// A join node references its left or right side before that side has
|
||||
/// been computed: the DAG isn't actually topologically sorted by id, or
|
||||
/// it has a cycle.
|
||||
UnresolvedDependency { node: u32, depends_on: u32 },
|
||||
/// Storage layer rejected an operation.
|
||||
Storage(StorageError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RunError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UnknownRelation(name) => {
|
||||
write!(f, "facts reference relation {name:?} not in schema")
|
||||
}
|
||||
Self::MissingNode(id) => write!(f, "plan references missing node id {id}"),
|
||||
Self::MissingRoot(id) => write!(f, "plan root id {id} matches no node"),
|
||||
Self::DuplicateNode(id) => write!(f, "duplicate node id {id} in plan"),
|
||||
Self::UnresolvedDependency { node, depends_on } => write!(
|
||||
f,
|
||||
"node {node} depends on {depends_on}, which has not been computed yet"
|
||||
),
|
||||
Self::Storage(err) => write!(f, "storage error: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RunError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Storage(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorageError> for RunError {
|
||||
fn from(err: StorageError) -> Self {
|
||||
Self::Storage(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonValue> for Value {
|
||||
fn from(jv: JsonValue) -> Self {
|
||||
match jv {
|
||||
JsonValue::Int(n) => Self::Int(n),
|
||||
JsonValue::Str(s) => Self::Str(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonTerm> for Term {
|
||||
fn from(t: JsonTerm) -> Self {
|
||||
match t {
|
||||
JsonTerm::Var(name) => Self::Var(name),
|
||||
JsonTerm::Lit(value) => Self::Lit(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a [`Plan`] from a JSON string.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a [`serde_json::Error`] if the input isn't valid JSON in the
|
||||
/// expected shape.
|
||||
pub fn parse_plan(json: &str) -> Result<Plan, serde_json::Error> {
|
||||
serde_json::from_str(json)
|
||||
}
|
||||
|
||||
/// Load schema and facts from a [`Plan`] into a fresh [`MemoryStorage`].
|
||||
///
|
||||
/// All facts are inserted in a single transaction; commit is atomic so a
|
||||
/// failure on row N leaves the storage empty.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`RunError::UnknownRelation`] if facts mention a relation not
|
||||
/// declared in `schema`. Wraps storage failures (arity mismatch, transaction
|
||||
/// errors) in [`RunError::Storage`].
|
||||
pub fn load_into_memory(plan: &Plan) -> Result<MemoryStorage, RunError> {
|
||||
let mut storage = MemoryStorage::default();
|
||||
for (name, arity) in &plan.schema {
|
||||
storage.create_relation(name, *arity)?;
|
||||
}
|
||||
{
|
||||
let mut tx = storage.transaction()?;
|
||||
for (name, rows) in &plan.facts {
|
||||
if !plan.schema.contains_key(name) {
|
||||
return Err(RunError::UnknownRelation(name.clone()));
|
||||
}
|
||||
for row in rows {
|
||||
let cells: Vec<Value> = row.iter().cloned().map(Value::from).collect();
|
||||
tx.insert(name, cells)?;
|
||||
}
|
||||
}
|
||||
let _ = tx.commit()?;
|
||||
}
|
||||
Ok(storage)
|
||||
}
|
||||
|
||||
/// Execute a plan against a storage backend, returning the bindings
|
||||
/// [`Relation`] for the rooted plan node.
|
||||
///
|
||||
/// Nodes are executed in ascending `id` order. For a Yannakakis plan as
|
||||
/// emitted by `Geolog.DB.Plan` this is equivalent to a topological sort,
|
||||
/// since `insertJoin` only references node ids that have already been
|
||||
/// allocated. A non-monotone id ordering is rejected with
|
||||
/// [`RunError::UnresolvedDependency`].
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`RunError::DuplicateNode`] for repeated ids,
|
||||
/// [`RunError::MissingNode`] for join references to unknown ids,
|
||||
/// [`RunError::MissingRoot`] if `query.root` isn't present, and storage
|
||||
/// errors during the per-scan `scan_as_table` call.
|
||||
pub fn execute<S: Storage>(storage: &S, query: &Query) -> Result<Relation, RunError> {
|
||||
let mut seen_ids: std::collections::HashSet<u32> =
|
||||
std::collections::HashSet::with_capacity(query.nodes.len());
|
||||
for node in &query.nodes {
|
||||
if !seen_ids.insert(node.id) {
|
||||
return Err(RunError::DuplicateNode(node.id));
|
||||
}
|
||||
}
|
||||
if !seen_ids.contains(&query.root) {
|
||||
return Err(RunError::MissingRoot(query.root));
|
||||
}
|
||||
|
||||
let mut ordered: Vec<&Node> = query.nodes.iter().collect();
|
||||
ordered.sort_by_key(|n| n.id);
|
||||
|
||||
let mut results: HashMap<u32, Relation> = HashMap::with_capacity(ordered.len());
|
||||
for node in ordered {
|
||||
let computed = match &node.action {
|
||||
Action::Scan(atom) => {
|
||||
let table = scan_as_table(storage, &atom.table)?;
|
||||
let pattern = AtomPattern {
|
||||
columns: atom.columns.iter().cloned().map(Term::from).collect(),
|
||||
};
|
||||
scan_atom(&table, &pattern)
|
||||
}
|
||||
Action::Join(join) => {
|
||||
let left = require_dep(&results, &seen_ids, node.id, join.left)?;
|
||||
let right = require_dep(&results, &seen_ids, node.id, join.right)?;
|
||||
match join.op {
|
||||
JoinOp::Left => semijoin(left, right),
|
||||
JoinOp::Right => semijoin(right, left),
|
||||
JoinOp::Natural => natural_join(left, right),
|
||||
}
|
||||
}
|
||||
};
|
||||
results.insert(node.id, computed);
|
||||
}
|
||||
|
||||
results
|
||||
.remove(&query.root)
|
||||
.ok_or(RunError::MissingRoot(query.root))
|
||||
}
|
||||
|
||||
fn require_dep<'a>(
|
||||
results: &'a HashMap<u32, Relation>,
|
||||
seen: &std::collections::HashSet<u32>,
|
||||
node: u32,
|
||||
depends_on: u32,
|
||||
) -> Result<&'a Relation, RunError> {
|
||||
if let Some(rel) = results.get(&depends_on) {
|
||||
Ok(rel)
|
||||
} else if seen.contains(&depends_on) {
|
||||
Err(RunError::UnresolvedDependency { node, depends_on })
|
||||
} else {
|
||||
Err(RunError::MissingNode(depends_on))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience: parse JSON, load it into a fresh in-memory storage, and
|
||||
/// execute, returning the root binding relation.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a JSON parse error if the input is malformed, or a [`RunError`]
|
||||
/// for any later step.
|
||||
pub fn run_json(json: &str) -> Result<Relation, RunFromJsonError> {
|
||||
let plan = parse_plan(json).map_err(RunFromJsonError::Parse)?;
|
||||
let storage = load_into_memory(&plan).map_err(RunFromJsonError::Run)?;
|
||||
let bindings = execute(&storage, &plan.query).map_err(RunFromJsonError::Run)?;
|
||||
Ok(bindings)
|
||||
}
|
||||
|
||||
/// Combined error from [`run_json`].
|
||||
#[derive(Debug)]
|
||||
pub enum RunFromJsonError {
|
||||
Parse(serde_json::Error),
|
||||
Run(RunError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RunFromJsonError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Parse(err) => write!(f, "parse error: {err}"),
|
||||
Self::Run(err) => write!(f, "run error: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RunFromJsonError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Parse(err) => Some(err),
|
||||
Self::Run(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
//! `glog-run` CLI: read a JSON plan from a file (or stdin if `-`), execute
|
||||
//! it against a fresh in-memory store, and print the resulting binding
|
||||
//! relation as JSON on stdout.
|
||||
|
||||
use std::io::{self, Read};
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let Some(path) = args.next() else {
|
||||
eprintln!("usage: glog-run <plan.json | ->");
|
||||
return ExitCode::from(2);
|
||||
};
|
||||
|
||||
let input = match read_input(&path) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
eprintln!("failed to read {path}: {err}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
};
|
||||
|
||||
let relation = match glog_runner::run_json(&input) {
|
||||
Ok(r) => r,
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
};
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"columns": relation.columns,
|
||||
"rows": relation
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| row.iter().map(value_to_json).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>(),
|
||||
});
|
||||
println!("{payload}");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
fn read_input(path: &str) -> io::Result<String> {
|
||||
if path == "-" {
|
||||
let mut buf = String::new();
|
||||
io::stdin().read_to_string(&mut buf)?;
|
||||
Ok(buf)
|
||||
} else {
|
||||
std::fs::read_to_string(path)
|
||||
}
|
||||
}
|
||||
|
||||
fn value_to_json(value: &storage::value::Value) -> serde_json::Value {
|
||||
match value {
|
||||
storage::value::Value::Int(n) => serde_json::Value::Number((*n).into()),
|
||||
storage::value::Value::Str(s) => serde_json::Value::String(s.clone()),
|
||||
storage::value::Value::Id(id) => serde_json::Value::String(id.to_string()),
|
||||
}
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
//! End-to-end check: run the JSON fixture and verify the resulting bindings
|
||||
//! match the `DB.InMemoryTest` "matches evalConjunction on three-atom chain"
|
||||
//! case from `external/geolog/geolog-lang/test/DB/InMemoryTest.hs`.
|
||||
//!
|
||||
//! For `node = {e1, e2, e3}` and `edge = {(e1,e2,ee1), (e2,e3,ee2)}` the
|
||||
//! conjunction `node(a), edge(a, b, _), edge(b, c, _)` has exactly one
|
||||
//! solution: `(a=e1, b=e2, c=e3)`.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use glog_runner::run_json;
|
||||
use storage::value::Value;
|
||||
|
||||
fn fixture() -> &'static str {
|
||||
include_str!("../fixtures/three_atom_chain.json")
|
||||
}
|
||||
|
||||
fn ent(path: &str, id: u32) -> Value {
|
||||
Value::Str(format!("{path}:{id}"))
|
||||
}
|
||||
|
||||
fn project<'a>(
|
||||
columns: &'a [String],
|
||||
row: &'a [Value],
|
||||
keep: &'a [&'a str],
|
||||
) -> BTreeMap<&'a str, &'a Value> {
|
||||
keep.iter()
|
||||
.map(|name| {
|
||||
let pos = columns
|
||||
.iter()
|
||||
.position(|c| c == name)
|
||||
.expect("column missing");
|
||||
(*name, &row[pos])
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_atom_chain_matches_haskell_oracle() {
|
||||
let result = run_json(fixture()).expect("fixture should execute");
|
||||
|
||||
// The plan's root keeps every variable, including the per-atom wildcards
|
||||
// `_r1` and `_r2`. The oracle only asserts the (a, b, c) projection.
|
||||
let keep = ["a", "b", "c"];
|
||||
let mut projected: Vec<BTreeMap<&str, &Value>> = result
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| project(&result.columns, row, &keep))
|
||||
.collect();
|
||||
projected.sort_by_key(|m| format!("{m:?}"));
|
||||
|
||||
let e1 = ent("node", 1);
|
||||
let e2 = ent("node", 2);
|
||||
let e3 = ent("node", 3);
|
||||
let expected = vec![BTreeMap::from([("a", &e1), ("b", &e2), ("c", &e3)])];
|
||||
|
||||
assert_eq!(projected, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_columns_cover_a_b_c_plus_two_wildcards() {
|
||||
// The exporter emits unique wildcard variable names for the entity-id
|
||||
// column of each edge atom (e.g. `_w0_2`, `_w1_2`); their exact spelling
|
||||
// is an implementation detail of the exporter, so this test only checks
|
||||
// that the named variables are all present and that the total column
|
||||
// count is the three named ones plus two anonymous wildcards.
|
||||
let result = run_json(fixture()).expect("fixture should execute");
|
||||
let cols: std::collections::HashSet<&str> = result.columns.iter().map(String::as_str).collect();
|
||||
for expected in ["a", "b", "c"] {
|
||||
assert!(cols.contains(expected), "missing column {expected}");
|
||||
}
|
||||
assert_eq!(result.columns.len(), 5, "expected 3 named + 2 wildcards");
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "glog-runner"
|
||||
name = "plan-runner"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@ -9,11 +9,18 @@ rust-version.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
storage = { path = "../storage" }
|
||||
query-ops = { path = "../query-ops" }
|
||||
storage = { path = "../storage", features = [
|
||||
"lmdb",
|
||||
"redb",
|
||||
"fjall",
|
||||
"sqlite",
|
||||
"geomerge",
|
||||
] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tempfile = "3"
|
||||
|
||||
[[bin]]
|
||||
name = "glog-run"
|
||||
name = "plan-run"
|
||||
path = "src/main.rs"
|
||||
101
crates/plan-runner/README.md
Normal file
@ -0,0 +1,101 @@
|
||||
## Plan Runner
|
||||
|
||||
This crate implements an executor for (conjunctive) query plans.
|
||||
The implementation is a CLI tool.
|
||||
It reads a JSON plan (which currently is a DAG of scan and join nodes plus the input facts),
|
||||
walks the DAG using the operators from [`query-ops`](../query-ops),
|
||||
and prints the resulting relation as JSON to stdout.
|
||||
|
||||
### Pipeline
|
||||
|
||||
End-to-end, scenarios become runner output through three stages:
|
||||
|
||||
```text
|
||||
tools/exporter/examples/*.scenario.json
|
||||
└── (Haskell exporter; runs Geolog.DB.Plan.planConjunction
|
||||
and Geolog.DB.InMemory.evalConjunctionPlanned as a self-check)
|
||||
└── crates/plan-runner/fixtures/*.json (JSON IR; checked in)
|
||||
└── (plan-runner; this crate)
|
||||
└── stdout JSON, with row-for-row oracle check
|
||||
```
|
||||
|
||||
The exporter (`tools/exporter`) is the only producer of runner IR today;
|
||||
it's where atoms are planned and rejected if they don't fit the supported subset.
|
||||
Fixtures are regenerated with `make export-fixtures`, and the full loop is `make examples`.
|
||||
|
||||
What happens inside the runner once a JSON plan arrives:
|
||||
|
||||
<div align="center">
|
||||
<picture>
|
||||
<img alt="Workflow" src="docs/diagrams/workflow.svg" height="90%" width="90%">
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
### Storage Backends
|
||||
|
||||
The CLI takes a `--backend` flag.
|
||||
The `memory` backend is the pure in-memory path;
|
||||
every other backend routes facts through the [`Storage`](../storage) trait
|
||||
via `build_tables_via_storage`, then scans tables back out before executing.
|
||||
|
||||
| Backend | Storage | Location |
|
||||
|------------------|-------------------|-----------------------|
|
||||
| `memory` | none | n/a |
|
||||
| `memory-storage` | `MemoryStorage` | in-process |
|
||||
| `lmdb` | `LmdbStorage` | fresh tempdir per run |
|
||||
| `redb` | `RedbStorage` | fresh tempdir per run |
|
||||
| `fjall` | `FjallStorage` | fresh tempdir per run |
|
||||
| `sqlite` | `SqliteStorage` | fresh tempdir per run |
|
||||
| `geomerge` | `GeomergeStorage` | in-process |
|
||||
|
||||
### Execute a Query Plan
|
||||
|
||||
```sh
|
||||
# Run a plan with the default backend (no storage)
|
||||
cargo run -p plan-runner -- crates/plan-runner/fixtures/two_atom_join.json
|
||||
|
||||
# Run the same plan with every supported backend
|
||||
cargo run -p plan-runner -- --backend memory-storage crates/plan-runner/fixtures/two_atom_join.json
|
||||
cargo run -p plan-runner -- --backend lmdb crates/plan-runner/fixtures/two_atom_join.json
|
||||
cargo run -p plan-runner -- --backend redb crates/plan-runner/fixtures/two_atom_join.json
|
||||
cargo run -p plan-runner -- --backend fjall crates/plan-runner/fixtures/two_atom_join.json
|
||||
cargo run -p plan-runner -- --backend sqlite crates/plan-runner/fixtures/two_atom_join.json
|
||||
cargo run -p plan-runner -- --backend geomerge crates/plan-runner/fixtures/two_atom_join.json
|
||||
```
|
||||
|
||||
A sample run:
|
||||
|
||||
```sh
|
||||
$ plan-run crates/plan-runner/fixtures/two_atom_join.json
|
||||
{"columns":["a","b","_w0_2"],"rows":[["node:1","node:2","edge:1"],["node:2","node:1","edge:2"]]}
|
||||
```
|
||||
|
||||
The `_w<atomIdx>_<pos>` columns are wildcards the exporter named so the runner can bind them.
|
||||
The scenario's `expected_bindings` block names only the variables the test cares about,
|
||||
and `verify` projects the runner output to that subset before comparing as a multiset.
|
||||
|
||||
### Run the Tests
|
||||
|
||||
```sh
|
||||
cargo test -p plan-runner
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- **IR contract.**
|
||||
The runner is backend-agnostic and frontend-agnostic.
|
||||
It consumes JSON in the shape documented in `src/lib.rs` and produces a binding relation.
|
||||
Anything that emits the same JSON can drive it.
|
||||
- **No optimizer.**
|
||||
Plans are executed as written.
|
||||
Node ordering, join shape, and antijoin scheduling are all the producer's responsibility.
|
||||
This crate's job ends at faithful execution of the IR.
|
||||
- **Wildcard columns survive.**
|
||||
`scan_atom` keeps every distinct variable that appears in the pattern,
|
||||
including the exporter's synthetic `_w<atomIdx>_<pos>` names.
|
||||
The runner does not project them out;
|
||||
oracle verification handles that on the comparison side.
|
||||
- **Bulk, not streaming.**
|
||||
Each node materializes its full output as a `Relation`.
|
||||
This matches `query-ops`' execution model;
|
||||
it's not designed for incremental or maintained-view workloads.
|
||||
14
crates/plan-runner/docs/diagrams/make_figures.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# You need to have Graphviz installed to run this script
|
||||
# On Debian-based OSes, you can install it using: sudo apt-get install graphviz
|
||||
|
||||
# Directory containing .dot files. Defaults to the script's own directory so the
|
||||
# script works regardless of the caller's working directory.
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
ASSET_DIR=${1:-"${SCRIPT_DIR}"}
|
||||
|
||||
# Make figures from .dot files
|
||||
for f in "${ASSET_DIR}"/*.dot; do
|
||||
dot -Tsvg "$f" -o "${f%.dot}.svg"
|
||||
done
|
||||
136
crates/plan-runner/docs/diagrams/workflow.dot
Normal file
@ -0,0 +1,136 @@
|
||||
digraph PlanRunnerWorkflow {
|
||||
fontname = "Helvetica,Arial,sans-serif"
|
||||
layout = dot
|
||||
rankdir = LR
|
||||
ranksep = 0.9;
|
||||
nodesep = 0.7;
|
||||
splines = true;
|
||||
compound = true;
|
||||
bgcolor = "white"
|
||||
|
||||
node [
|
||||
fontname = "Helvetica,Arial,sans-serif",
|
||||
shape = box,
|
||||
style = "filled,rounded",
|
||||
color = "#555555",
|
||||
fillcolor = "white",
|
||||
penwidth = 1.5
|
||||
]
|
||||
edge [
|
||||
fontname = "Helvetica,Arial,sans-serif",
|
||||
color = "#333333",
|
||||
fontsize = 9,
|
||||
fontcolor = "#555555",
|
||||
labeldistance = 2.0,
|
||||
penwidth = 1.2
|
||||
]
|
||||
|
||||
subgraph cluster_input {
|
||||
label = "Input"
|
||||
style = "dashed"
|
||||
color = "#888888"
|
||||
fontcolor = "#555555"
|
||||
margin = 18
|
||||
json_plan [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>JSON Plan</b></td></tr>
|
||||
<tr><td align="left" balign="left">• schema: name -> arity</td></tr>
|
||||
<tr><td align="left" balign="left">• facts: name -> rows</td></tr>
|
||||
<tr><td align="left" balign="left">• query: { root, nodes }</td></tr>
|
||||
<tr><td align="left" balign="left">• expected_bindings (optional oracle)</td></tr>
|
||||
</table>>, fillcolor = "#E8F4FD", color = "#2196F3"]
|
||||
}
|
||||
|
||||
subgraph cluster_parse {
|
||||
label = "Parse"
|
||||
style = "dashed"
|
||||
color = "#9C27B0"
|
||||
fontcolor = "#7B1FA2"
|
||||
margin = 14
|
||||
parse_plan [label = "parse_plan(json)\n-> Plan", fillcolor = "#F3E5F5", color = "#9C27B0"]
|
||||
}
|
||||
|
||||
subgraph cluster_load {
|
||||
label = "Load Tables (--backend selects the path)"
|
||||
style = "dashed"
|
||||
color = "#4CAF50"
|
||||
fontcolor = "#388E3C"
|
||||
margin = 14
|
||||
build_pure [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>build_tables(plan)</b></td></tr>
|
||||
<tr><td align="left" balign="left">--backend memory</td></tr>
|
||||
<tr><td align="left" balign="left">direct from plan.facts</td></tr>
|
||||
</table>>, fillcolor = "#E8F5E9", color = "#4CAF50"]
|
||||
build_storage [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>build_tables_via_storage<S: Storage></b></td></tr>
|
||||
<tr><td align="left" balign="left">--backend memory-storage</td></tr>
|
||||
<tr><td align="left" balign="left">--backend lmdb / redb / fjall</td></tr>
|
||||
<tr><td align="left" balign="left">--backend sqlite / geomerge</td></tr>
|
||||
<tr><td align="left" balign="left">create_relation → tx.insert → scan_as_table</td></tr>
|
||||
</table>>, fillcolor = "#E8F5E9", color = "#4CAF50"]
|
||||
tables_map [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>HashMap<String, Table></b></td></tr>
|
||||
<tr><td align="left" balign="left">positional rows per relation</td></tr>
|
||||
</table>>, fillcolor = "#E8F4FD", color = "#2196F3"]
|
||||
}
|
||||
|
||||
subgraph cluster_execute {
|
||||
label = "Execute (walk node DAG in id order)"
|
||||
style = "dashed"
|
||||
color = "#FF9800"
|
||||
fontcolor = "#F57C00"
|
||||
margin = 14
|
||||
execute_node [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>execute(tables, query)</b></td></tr>
|
||||
<tr><td align="left" balign="left">Action::Scan → scan_atom</td></tr>
|
||||
<tr><td align="left" balign="left">Action::Join Left → semijoin(l, r)</td></tr>
|
||||
<tr><td align="left" balign="left">Action::Join Right → semijoin(r, l)</td></tr>
|
||||
<tr><td align="left" balign="left">Action::Join Natural → natural_join(l, r)</td></tr>
|
||||
<tr><td align="left" balign="left">cache per-node Relation; return root</td></tr>
|
||||
</table>>, fillcolor = "#FFF3E0", color = "#FF9800"]
|
||||
relation_out [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>Relation</b></td></tr>
|
||||
<tr><td align="left" balign="left">columns: variables + wildcards</td></tr>
|
||||
<tr><td align="left" balign="left">rows: bindings</td></tr>
|
||||
</table>>, fillcolor = "#FFF3E0", color = "#FF9800"]
|
||||
}
|
||||
|
||||
subgraph cluster_verify {
|
||||
label = "Verify (when expected_bindings is present)"
|
||||
style = "dashed"
|
||||
color = "#9C27B0"
|
||||
fontcolor = "#7B1FA2"
|
||||
margin = 14
|
||||
verify_node [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>verify(plan, relation)</b></td></tr>
|
||||
<tr><td align="left" balign="left">project to expected.columns</td></tr>
|
||||
<tr><td align="left" balign="left">multiset compare against expected.rows</td></tr>
|
||||
</table>>, fillcolor = "#F3E5F5", color = "#9C27B0"]
|
||||
}
|
||||
|
||||
subgraph cluster_output {
|
||||
label = "Output"
|
||||
style = "dashed"
|
||||
color = "#888888"
|
||||
fontcolor = "#555555"
|
||||
margin = 18
|
||||
stdout_json [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>stdout JSON</b></td></tr>
|
||||
<tr><td align="left" balign="left">{ columns, rows }</td></tr>
|
||||
</table>>, fillcolor = "#ECEFF1", color = "#607D8B"]
|
||||
oracle_pass [label = "Ok(true) / VerifyError\n(used by tests/examples.rs)", fillcolor = "#ECEFF1", color = "#607D8B"]
|
||||
}
|
||||
|
||||
// Pipeline edges
|
||||
json_plan -> parse_plan [color = "#2196F3"]
|
||||
parse_plan -> build_pure [label = "Backend::Memory", color = "#9C27B0"]
|
||||
parse_plan -> build_storage [label = "Backend::*Storage", color = "#9C27B0"]
|
||||
build_pure -> tables_map [color = "#4CAF50"]
|
||||
build_storage -> tables_map [color = "#4CAF50"]
|
||||
tables_map -> execute_node [color = "#2196F3"]
|
||||
parse_plan -> execute_node [style = "dashed", label = "plan.query", color = "#9C27B0"]
|
||||
execute_node -> relation_out [color = "#FF9800"]
|
||||
relation_out -> stdout_json [color = "#607D8B"]
|
||||
relation_out -> verify_node [style = "dashed", color = "#FF9800"]
|
||||
parse_plan -> verify_node [style = "dashed", label = "plan.expected_bindings", color = "#9C27B0"]
|
||||
verify_node -> oracle_pass [color = "#9C27B0"]
|
||||
}
|
||||
202
crates/plan-runner/docs/diagrams/workflow.svg
Normal file
@ -0,0 +1,202 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 12.2.1 (0)
|
||||
-->
|
||||
<!-- Title: PlanRunnerWorkflow Pages: 1 -->
|
||||
<svg width="2495pt" height="593pt"
|
||||
viewBox="0.00 0.00 2495.25 593.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 589)">
|
||||
<title>PlanRunnerWorkflow</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-589 2491.25,-589 2491.25,4 -4,4"/>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_input</title>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2" points="8,-126 8,-343 290,-343 290,-126 8,-126"/>
|
||||
<text text-anchor="middle" x="149" y="-325.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#555555">Input</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_parse</title>
|
||||
<polygon fill="white" stroke="#9c27b0" stroke-dasharray="5,2" points="325,-181 325,-288 469.5,-288 469.5,-181 325,-181"/>
|
||||
<text text-anchor="middle" x="397.25" y="-270.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#7b1fa2">Parse</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_load</title>
|
||||
<polygon fill="white" stroke="#4caf50" stroke-dasharray="5,2" points="584,-224 584,-577 1197.75,-577 1197.75,-224 584,-224"/>
|
||||
<text text-anchor="middle" x="890.88" y="-559.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#388e3c">Load Tables  (--backend selects the path)</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_execute</title>
|
||||
<polygon fill="white" stroke="#ff9800" stroke-dasharray="5,2" points="1333.25,-122 1333.25,-361 1893.25,-361 1893.25,-122 1333.25,-122"/>
|
||||
<text text-anchor="middle" x="1613.25" y="-343.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#f57c00">Execute  (walk node DAG in id order)</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_verify</title>
|
||||
<polygon fill="white" stroke="#9c27b0" stroke-dasharray="5,2" points="1932.25,-8 1932.25,-159 2226.5,-159 2226.5,-8 1932.25,-8"/>
|
||||
<text text-anchor="middle" x="2079.38" y="-141.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#7b1fa2">Verify  (when expected_bindings is present)</text>
|
||||
</g>
|
||||
<g id="clust6" class="cluster">
|
||||
<title>cluster_output</title>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2" points="2261.5,-37 2261.5,-268 2479.25,-268 2479.25,-37 2261.5,-37"/>
|
||||
<text text-anchor="middle" x="2370.38" y="-250.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#555555">Output</text>
|
||||
</g>
|
||||
<!-- json_plan -->
|
||||
<g id="node1" class="node">
|
||||
<title>json_plan</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M260,-296.12C260,-296.12 38,-296.12 38,-296.12 32,-296.12 26,-290.12 26,-284.12 26,-284.12 26,-155.88 26,-155.88 26,-149.88 32,-143.88 38,-143.88 38,-143.88 260,-143.88 260,-143.88 266,-143.88 272,-149.88 272,-155.88 272,-155.88 272,-284.12 272,-284.12 272,-290.12 266,-296.12 260,-296.12"/>
|
||||
<text text-anchor="start" x="114.12" y="-275.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">JSON Plan</text>
|
||||
<text text-anchor="start" x="38" y="-246.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• schema: name -> arity</text>
|
||||
<text text-anchor="start" x="38" y="-217.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• facts: name -> rows</text>
|
||||
<text text-anchor="start" x="38" y="-188.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• query: { root, nodes }</text>
|
||||
<text text-anchor="start" x="38" y="-159.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• expected_bindings (optional oracle)</text>
|
||||
</g>
|
||||
<!-- parse_plan -->
|
||||
<g id="node2" class="node">
|
||||
<title>parse_plan</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M443.5,-245C443.5,-245 351,-245 351,-245 345,-245 339,-239 339,-233 339,-233 339,-207 339,-207 339,-201 345,-195 351,-195 351,-195 443.5,-195 443.5,-195 449.5,-195 455.5,-201 455.5,-207 455.5,-207 455.5,-233 455.5,-233 455.5,-239 449.5,-245 443.5,-245"/>
|
||||
<text text-anchor="middle" x="397.25" y="-227.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">parse_plan(json)</text>
|
||||
<text text-anchor="middle" x="397.25" y="-206.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">-> Plan</text>
|
||||
</g>
|
||||
<!-- json_plan->parse_plan -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>json_plan->parse_plan</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" d="M272.4,-220C291.23,-220 310.09,-220 327.2,-220"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="326.93,-223.5 336.93,-220 326.93,-216.5 326.93,-223.5"/>
|
||||
</g>
|
||||
<!-- build_pure -->
|
||||
<g id="node3" class="node">
|
||||
<title>build_pure</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M806.88,-534.12C806.88,-534.12 680.88,-534.12 680.88,-534.12 674.88,-534.12 668.88,-528.12 668.88,-522.12 668.88,-522.12 668.88,-451.88 668.88,-451.88 668.88,-445.88 674.88,-439.88 680.88,-439.88 680.88,-439.88 806.88,-439.88 806.88,-439.88 812.88,-439.88 818.88,-445.88 818.88,-451.88 818.88,-451.88 818.88,-522.12 818.88,-522.12 818.88,-528.12 812.88,-534.12 806.88,-534.12"/>
|
||||
<text text-anchor="start" x="686.12" y="-513.83" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">build_tables(plan)</text>
|
||||
<text text-anchor="start" x="680.88" y="-484.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">--backend memory</text>
|
||||
<text text-anchor="start" x="680.88" y="-455.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">direct from plan.facts</text>
|
||||
</g>
|
||||
<!-- parse_plan->build_pure -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>parse_plan->build_pure</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M417.17,-245.45C448.23,-285.81 513.67,-365.05 584,-415 606.47,-430.96 633.04,-444.67 657.95,-455.71"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="656.28,-458.8 666.85,-459.56 659.06,-452.38 656.28,-458.8"/>
|
||||
<text text-anchor="middle" x="526.75" y="-402.33" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Backend::Memory</text>
|
||||
</g>
|
||||
<!-- build_storage -->
|
||||
<g id="node4" class="node">
|
||||
<title>build_storage</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M877.75,-390.12C877.75,-390.12 610,-390.12 610,-390.12 604,-390.12 598,-384.12 598,-378.12 598,-378.12 598,-249.88 598,-249.88 598,-243.88 604,-237.88 610,-237.88 610,-237.88 877.75,-237.88 877.75,-237.88 883.75,-237.88 889.75,-243.88 889.75,-249.88 889.75,-249.88 889.75,-378.12 889.75,-378.12 889.75,-384.12 883.75,-390.12 877.75,-390.12"/>
|
||||
<text text-anchor="start" x="620.5" y="-369.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">build_tables_via_storage<S: Storage></text>
|
||||
<text text-anchor="start" x="610" y="-340.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">--backend memory-storage</text>
|
||||
<text text-anchor="start" x="610" y="-311.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">--backend lmdb / redb / fjall</text>
|
||||
<text text-anchor="start" x="610" y="-282.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">--backend sqlite / geomerge</text>
|
||||
<text text-anchor="start" x="610" y="-253.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">create_relation → tx.insert → scan_as_table</text>
|
||||
</g>
|
||||
<!-- parse_plan->build_storage -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>parse_plan->build_storage</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M455.93,-235.73C491.85,-245.53 539.94,-258.65 586.47,-271.34"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="585.3,-274.65 595.87,-273.9 587.14,-267.89 585.3,-274.65"/>
|
||||
<text text-anchor="middle" x="526.75" y="-269.13" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Backend::*Storage</text>
|
||||
</g>
|
||||
<!-- execute_node -->
|
||||
<g id="node6" class="node">
|
||||
<title>execute_node</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M1591.75,-317.62C1591.75,-317.62 1359.25,-317.62 1359.25,-317.62 1353.25,-317.62 1347.25,-311.62 1347.25,-305.62 1347.25,-305.62 1347.25,-148.38 1347.25,-148.38 1347.25,-142.38 1353.25,-136.38 1359.25,-136.38 1359.25,-136.38 1591.75,-136.38 1591.75,-136.38 1597.75,-136.38 1603.75,-142.38 1603.75,-148.38 1603.75,-148.38 1603.75,-305.62 1603.75,-305.62 1603.75,-311.62 1597.75,-317.62 1591.75,-317.62"/>
|
||||
<text text-anchor="start" x="1403.88" y="-297.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">execute(tables, query)</text>
|
||||
<text text-anchor="start" x="1359.25" y="-268.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Action::Scan  → scan_atom</text>
|
||||
<text text-anchor="start" x="1359.25" y="-239.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Action::Join Left  → semijoin(l, r)</text>
|
||||
<text text-anchor="start" x="1359.25" y="-210.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Action::Join Right → semijoin(r, l)</text>
|
||||
<text text-anchor="start" x="1359.25" y="-181.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Action::Join Natural → natural_join(l, r)</text>
|
||||
<text text-anchor="start" x="1359.25" y="-152.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">cache per-node Relation; return root</text>
|
||||
</g>
|
||||
<!-- parse_plan->execute_node -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>parse_plan->execute_node</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" stroke-dasharray="5,2" d="M455.98,-217.47C492.65,-215.87 541.11,-213.78 584,-212 753.85,-204.96 796.32,-193.74 966.25,-198.5 1091.19,-202 1233.2,-210.33 1335.29,-217.09"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="1334.89,-220.57 1345.1,-217.74 1335.35,-213.58 1334.89,-220.57"/>
|
||||
<text text-anchor="middle" x="944.5" y="-203.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">plan.query</text>
|
||||
</g>
|
||||
<!-- verify_node -->
|
||||
<g id="node8" class="node">
|
||||
<title>verify_node</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M2200.5,-116.12C2200.5,-116.12 1958.25,-116.12 1958.25,-116.12 1952.25,-116.12 1946.25,-110.12 1946.25,-104.12 1946.25,-104.12 1946.25,-33.88 1946.25,-33.88 1946.25,-27.88 1952.25,-21.88 1958.25,-21.88 1958.25,-21.88 2200.5,-21.88 2200.5,-21.88 2206.5,-21.88 2212.5,-27.88 2212.5,-33.88 2212.5,-33.88 2212.5,-104.12 2212.5,-104.12 2212.5,-110.12 2206.5,-116.12 2200.5,-116.12"/>
|
||||
<text text-anchor="start" x="2014.88" y="-95.83" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">verify(plan, relation)</text>
|
||||
<text text-anchor="start" x="1958.25" y="-66.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">project to expected.columns</text>
|
||||
<text text-anchor="start" x="1958.25" y="-37.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">multiset compare against expected.rows</text>
|
||||
</g>
|
||||
<!-- parse_plan->verify_node -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>parse_plan->verify_node</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" stroke-dasharray="5,2" d="M428.8,-194.65C486.38,-149.53 616.55,-60 742.88,-60 742.88,-60 742.88,-60 1776,-60 1827.65,-60 1884.23,-61.33 1934.34,-62.96"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="1934.01,-66.45 1944.12,-63.29 1934.24,-59.46 1934.01,-66.45"/>
|
||||
<text text-anchor="middle" x="1265.5" y="-64.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">plan.expected_bindings</text>
|
||||
</g>
|
||||
<!-- tables_map -->
|
||||
<g id="node5" class="node">
|
||||
<title>tables_map</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M1171.75,-346.62C1171.75,-346.62 1011.25,-346.62 1011.25,-346.62 1005.25,-346.62 999.25,-340.62 999.25,-334.62 999.25,-334.62 999.25,-293.38 999.25,-293.38 999.25,-287.38 1005.25,-281.38 1011.25,-281.38 1011.25,-281.38 1171.75,-281.38 1171.75,-281.38 1177.75,-281.38 1183.75,-287.38 1183.75,-293.38 1183.75,-293.38 1183.75,-334.62 1183.75,-334.62 1183.75,-340.62 1177.75,-346.62 1171.75,-346.62"/>
|
||||
<text text-anchor="start" x="1012.38" y="-326.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">HashMap<String, Table></text>
|
||||
<text text-anchor="start" x="1011.25" y="-297.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">positional rows per relation</text>
|
||||
</g>
|
||||
<!-- build_pure->tables_map -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>build_pure->tables_map</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M819.36,-450.07C841.98,-438.83 866.89,-426.43 889.75,-415 931.06,-394.35 977.08,-371.2 1014.64,-352.28"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="1016.01,-355.5 1023.37,-347.88 1012.86,-349.25 1016.01,-355.5"/>
|
||||
</g>
|
||||
<!-- build_storage->tables_map -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>build_storage->tables_map</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M890.2,-314C922.86,-314 956.86,-314 987.38,-314"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="987.08,-317.5 997.08,-314 987.08,-310.5 987.08,-317.5"/>
|
||||
</g>
|
||||
<!-- tables_map->execute_node -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>tables_map->execute_node</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" d="M1184,-293.16C1229.35,-282.83 1285.08,-270.14 1335.56,-258.64"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="1336.18,-262.09 1345.15,-256.46 1334.62,-255.27 1336.18,-262.09"/>
|
||||
</g>
|
||||
<!-- relation_out -->
|
||||
<g id="node7" class="node">
|
||||
<title>relation_out</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M1867.25,-246.12C1867.25,-246.12 1682.75,-246.12 1682.75,-246.12 1676.75,-246.12 1670.75,-240.12 1670.75,-234.12 1670.75,-234.12 1670.75,-163.88 1670.75,-163.88 1670.75,-157.88 1676.75,-151.88 1682.75,-151.88 1682.75,-151.88 1867.25,-151.88 1867.25,-151.88 1873.25,-151.88 1879.25,-157.88 1879.25,-163.88 1879.25,-163.88 1879.25,-234.12 1879.25,-234.12 1879.25,-240.12 1873.25,-246.12 1867.25,-246.12"/>
|
||||
<text text-anchor="start" x="1748.38" y="-225.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Relation</text>
|
||||
<text text-anchor="start" x="1682.75" y="-196.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">columns: variables + wildcards</text>
|
||||
<text text-anchor="start" x="1682.75" y="-167.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">rows: bindings</text>
|
||||
</g>
|
||||
<!-- execute_node->relation_out -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>execute_node->relation_out</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" d="M1603.96,-215C1622.2,-213.29 1640.88,-211.53 1658.87,-209.84"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="1659.14,-213.33 1668.76,-208.9 1658.48,-206.36 1659.14,-213.33"/>
|
||||
</g>
|
||||
<!-- relation_out->verify_node -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>relation_out->verify_node</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" stroke-dasharray="5,2" d="M1879.64,-154.44C1904.58,-143.72 1931.45,-132.17 1957.11,-121.13"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="1958.17,-124.49 1965.98,-117.32 1955.41,-118.06 1958.17,-124.49"/>
|
||||
</g>
|
||||
<!-- stdout_json -->
|
||||
<g id="node9" class="node">
|
||||
<title>stdout_json</title>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5" d="M2422.12,-220.62C2422.12,-220.62 2318.62,-220.62 2318.62,-220.62 2312.62,-220.62 2306.62,-214.62 2306.62,-208.62 2306.62,-208.62 2306.62,-167.38 2306.62,-167.38 2306.62,-161.38 2312.62,-155.38 2318.62,-155.38 2318.62,-155.38 2422.12,-155.38 2422.12,-155.38 2428.12,-155.38 2434.12,-161.38 2434.12,-167.38 2434.12,-167.38 2434.12,-208.62 2434.12,-208.62 2434.12,-214.62 2428.12,-220.62 2422.12,-220.62"/>
|
||||
<text text-anchor="start" x="2329.12" y="-200.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">stdout JSON</text>
|
||||
<text text-anchor="start" x="2318.62" y="-171.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">{ columns, rows }</text>
|
||||
</g>
|
||||
<!-- relation_out->stdout_json -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>relation_out->stdout_json</title>
|
||||
<path fill="none" stroke="#607d8b" stroke-width="1.2" d="M1879.7,-197.08C1998.37,-194.88 2189.83,-191.33 2294.73,-189.38"/>
|
||||
<polygon fill="#607d8b" stroke="#607d8b" stroke-width="1.2" points="2294.71,-192.88 2304.64,-189.2 2294.58,-185.89 2294.71,-192.88"/>
|
||||
</g>
|
||||
<!-- oracle_pass -->
|
||||
<g id="node10" class="node">
|
||||
<title>oracle_pass</title>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5" d="M2449.25,-105C2449.25,-105 2291.5,-105 2291.5,-105 2285.5,-105 2279.5,-99 2279.5,-93 2279.5,-93 2279.5,-67 2279.5,-67 2279.5,-61 2285.5,-55 2291.5,-55 2291.5,-55 2449.25,-55 2449.25,-55 2455.25,-55 2461.25,-61 2461.25,-67 2461.25,-67 2461.25,-93 2461.25,-93 2461.25,-99 2455.25,-105 2449.25,-105"/>
|
||||
<text text-anchor="middle" x="2370.38" y="-87.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Ok(true)  /  VerifyError</text>
|
||||
<text text-anchor="middle" x="2370.38" y="-66.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">(used by tests/examples.rs)</text>
|
||||
</g>
|
||||
<!-- verify_node->oracle_pass -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>verify_node->oracle_pass</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M2212.61,-74.03C2231.14,-74.74 2249.95,-75.45 2267.79,-76.13"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="2267.42,-79.62 2277.55,-76.5 2267.69,-72.63 2267.42,-79.62"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
114
crates/plan-runner/fixtures/cartesian.json
Normal file
@ -0,0 +1,114 @@
|
||||
{
|
||||
"_scenario": "cartesian",
|
||||
"expected_bindings": {
|
||||
"columns": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"str": "left:1"
|
||||
},
|
||||
{
|
||||
"str": "right:10"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "left:1"
|
||||
},
|
||||
{
|
||||
"str": "right:20"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "left:2"
|
||||
},
|
||||
{
|
||||
"str": "right:10"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "left:2"
|
||||
},
|
||||
{
|
||||
"str": "right:20"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"facts": {
|
||||
"left": [
|
||||
[
|
||||
{
|
||||
"str": "left:1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "left:2"
|
||||
}
|
||||
]
|
||||
],
|
||||
"right": [
|
||||
[
|
||||
{
|
||||
"str": "right:10"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "right:20"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"query": {
|
||||
"nodes": [
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "a"
|
||||
}
|
||||
],
|
||||
"table": "left"
|
||||
}
|
||||
},
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "b"
|
||||
}
|
||||
],
|
||||
"table": "right"
|
||||
}
|
||||
},
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 1,
|
||||
"op": "natural",
|
||||
"right": 2
|
||||
}
|
||||
},
|
||||
"id": 3
|
||||
}
|
||||
],
|
||||
"root": 3
|
||||
},
|
||||
"schema": {
|
||||
"left": 1,
|
||||
"right": 1
|
||||
}
|
||||
}
|
||||
84
crates/plan-runner/fixtures/self_loop.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"_scenario": "self-loop",
|
||||
"expected_bindings": {
|
||||
"columns": [
|
||||
"x"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:3"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"facts": {
|
||||
"edge": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
},
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "edge:1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "edge:2"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:3"
|
||||
},
|
||||
{
|
||||
"str": "node:3"
|
||||
},
|
||||
{
|
||||
"str": "edge:3"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"query": {
|
||||
"nodes": [
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "x"
|
||||
},
|
||||
{
|
||||
"var": "x"
|
||||
},
|
||||
{
|
||||
"var": "_w0_2"
|
||||
}
|
||||
],
|
||||
"table": "edge"
|
||||
}
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
],
|
||||
"root": 1
|
||||
},
|
||||
"schema": {
|
||||
"edge": 3
|
||||
}
|
||||
}
|
||||
186
crates/plan-runner/fixtures/three_atom_chain.json
Normal file
@ -0,0 +1,186 @@
|
||||
{
|
||||
"_scenario": "three-atom-chain",
|
||||
"expected_bindings": {
|
||||
"columns": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
},
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "node:3"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"facts": {
|
||||
"edge": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
},
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "edge:1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "node:3"
|
||||
},
|
||||
{
|
||||
"str": "edge:2"
|
||||
}
|
||||
]
|
||||
],
|
||||
"node": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:3"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"query": {
|
||||
"nodes": [
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "a"
|
||||
},
|
||||
{
|
||||
"var": "b"
|
||||
},
|
||||
{
|
||||
"var": "_w0_2"
|
||||
}
|
||||
],
|
||||
"table": "edge"
|
||||
}
|
||||
},
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "b"
|
||||
},
|
||||
{
|
||||
"var": "c"
|
||||
},
|
||||
{
|
||||
"var": "_w1_2"
|
||||
}
|
||||
],
|
||||
"table": "edge"
|
||||
}
|
||||
},
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "a"
|
||||
}
|
||||
],
|
||||
"table": "node"
|
||||
}
|
||||
},
|
||||
"id": 3
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 1,
|
||||
"op": "left",
|
||||
"right": 3
|
||||
}
|
||||
},
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 2,
|
||||
"op": "left",
|
||||
"right": 4
|
||||
}
|
||||
},
|
||||
"id": 5
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 5,
|
||||
"op": "right",
|
||||
"right": 4
|
||||
}
|
||||
},
|
||||
"id": 6
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 6,
|
||||
"op": "right",
|
||||
"right": 3
|
||||
}
|
||||
},
|
||||
"id": 7
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 6,
|
||||
"op": "natural",
|
||||
"right": 7
|
||||
}
|
||||
},
|
||||
"id": 8
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 5,
|
||||
"op": "natural",
|
||||
"right": 8
|
||||
}
|
||||
},
|
||||
"id": 9
|
||||
}
|
||||
],
|
||||
"root": 9
|
||||
},
|
||||
"schema": {
|
||||
"edge": 3,
|
||||
"node": 1
|
||||
}
|
||||
}
|
||||
136
crates/plan-runner/fixtures/two_atom_join.json
Normal file
@ -0,0 +1,136 @@
|
||||
{
|
||||
"_scenario": "two-atom-join",
|
||||
"expected_bindings": {
|
||||
"columns": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
},
|
||||
{
|
||||
"str": "node:2"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "node:1"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"facts": {
|
||||
"edge": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
},
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "edge:1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
},
|
||||
{
|
||||
"str": "node:1"
|
||||
},
|
||||
{
|
||||
"str": "edge:2"
|
||||
}
|
||||
]
|
||||
],
|
||||
"node": [
|
||||
[
|
||||
{
|
||||
"str": "node:1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"str": "node:2"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"query": {
|
||||
"nodes": [
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "a"
|
||||
},
|
||||
{
|
||||
"var": "b"
|
||||
},
|
||||
{
|
||||
"var": "_w0_2"
|
||||
}
|
||||
],
|
||||
"table": "edge"
|
||||
}
|
||||
},
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"scan": {
|
||||
"columns": [
|
||||
{
|
||||
"var": "a"
|
||||
}
|
||||
],
|
||||
"table": "node"
|
||||
}
|
||||
},
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 1,
|
||||
"op": "left",
|
||||
"right": 2
|
||||
}
|
||||
},
|
||||
"id": 3
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 3,
|
||||
"op": "right",
|
||||
"right": 2
|
||||
}
|
||||
},
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"action": {
|
||||
"join": {
|
||||
"left": 3,
|
||||
"op": "natural",
|
||||
"right": 4
|
||||
}
|
||||
},
|
||||
"id": 5
|
||||
}
|
||||
],
|
||||
"root": 5
|
||||
},
|
||||
"schema": {
|
||||
"edge": 3,
|
||||
"node": 1
|
||||
}
|
||||
}
|
||||
540
crates/plan-runner/src/lib.rs
Normal file
@ -0,0 +1,540 @@
|
||||
//! Snapshot executor for conjunctive-query plans.
|
||||
//!
|
||||
//! Takes a structural plan (a DAG of `Scan` and `Join` nodes), the input
|
||||
//! tables it scans, and walks the DAG via [`query_ops::atom::scan_atom`],
|
||||
//! [`query_ops::join::semijoin`], and [`query_ops::join::natural_join`].
|
||||
//! The result is a binding [`Relation`](query_ops::relation::Relation) over
|
||||
//! the query's variables.
|
||||
//!
|
||||
//! The runner is intentionally backend-agnostic: it depends only on
|
||||
//! `query-ops`, and the planner that emits the JSON IR is decoupled from
|
||||
//! the storage backend that produced the facts. To execute a plan against
|
||||
//! a [`Storage`](storage::Storage) backend, materialize each input table
|
||||
//! with [`storage::scan_as_table`] and call [`execute`] with the resulting
|
||||
//! map. The in-tree `tests/storage_roundtrip.rs` is the canonical example.
|
||||
//!
|
||||
//! The JSON IR mirrors `Geolog.DB.Plan.PlanGraph` and
|
||||
//! `Geolog.DB.InMemory.QAtom` from the `external/geolog` submodule, but the
|
||||
//! shape is the contract: any frontend that emits this JSON can use the
|
||||
//! runner.
|
||||
//!
|
||||
//! Operator mapping from the Haskell planner:
|
||||
//!
|
||||
//! | `Geolog.DB.Plan` | this crate |
|
||||
//! |-----------------------------|-----------------------------------------------|
|
||||
//! | `PlanEvalAtom` | [`Action::Scan`] → `scan_atom` |
|
||||
//! | `PlanJoin LeftJoin a b` | [`Action::Join`] with [`JoinOp::Left`] → `semijoin(rel[a], rel[b])` |
|
||||
//! | `PlanJoin RightJoin a b` | [`Action::Join`] with [`JoinOp::Right`] → `semijoin(rel[b], rel[a])` |
|
||||
//! | `PlanJoin NaturalJoin a b` | [`Action::Join`] with [`JoinOp::Natural`] → `natural_join(rel[a], rel[b])` |
|
||||
//!
|
||||
//! The atom side covers `evalAtom` (`Geolog.DB.InMemory`): a [`Term::Var`]
|
||||
//! repeated across positions enforces equality, [`Term::Lit`] filters by
|
||||
//! constant, and distinct variables project in first-occurrence order.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use query_ops::atom::{AtomPattern, Term, scan_atom};
|
||||
use query_ops::join::{natural_join, semijoin};
|
||||
use query_ops::relation::Relation;
|
||||
use storage::table::Table;
|
||||
use storage::value::Value;
|
||||
use storage::{Storage, StorageError, scan_as_table};
|
||||
|
||||
/// A single fixture: schema, ground facts, and a query plan to execute.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Plan {
|
||||
/// Relation name → arity (column count).
|
||||
pub schema: HashMap<String, usize>,
|
||||
/// Relation name → list of ground tuples to insert before execution.
|
||||
pub facts: HashMap<String, Vec<Vec<JsonValue>>>,
|
||||
/// The join DAG itself.
|
||||
pub query: Query,
|
||||
/// Optional oracle: if present, [`verify`] cross-checks an executed
|
||||
/// [`Relation`] against this projection. The exporter lifts the
|
||||
/// scenario's `expected_bindings` block into this field.
|
||||
#[serde(default)]
|
||||
pub expected_bindings: Option<ExpectedBindings>,
|
||||
}
|
||||
|
||||
/// Expected query result, projected to a named subset of variables. The
|
||||
/// columns named here must all appear in the runner's output; any extra
|
||||
/// columns (typically per-atom wildcards) are ignored.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ExpectedBindings {
|
||||
pub columns: Vec<String>,
|
||||
pub rows: Vec<Vec<JsonValue>>,
|
||||
}
|
||||
|
||||
/// Mirrors `Geolog.DB.Plan.PlanGraph`: a set of nodes plus the id of the
|
||||
/// rooted result node (the last node in topological order).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Query {
|
||||
pub root: u32,
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
/// One node of the plan DAG. `id`s don't need to start at any particular
|
||||
/// value, mirroring the Haskell `PlanNodeId`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Node {
|
||||
pub id: u32,
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
/// What to compute at a node. Tagged externally so JSON reads as
|
||||
/// `{"action": {"scan": {...}}}` or `{"action": {"join": {...}}}`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Action {
|
||||
Scan(Atom),
|
||||
Join(Join),
|
||||
}
|
||||
|
||||
/// A flat atom pattern, one entry per column of the target relation.
|
||||
/// Matches the `toFlatArgs` view used by `Geolog.DB.InMemory.evalAtom`:
|
||||
/// `qaValues` positions are filled in directly, and the entity-id column
|
||||
/// (if any) appears at the last position. Wildcard positions in the
|
||||
/// Haskell `QAtom` (a `Map Int QVal` with a missing key) translate to a
|
||||
/// fresh, unique variable name on this side, which the operator binds but
|
||||
/// never joins against.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Atom {
|
||||
pub table: String,
|
||||
pub columns: Vec<JsonTerm>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum JsonTerm {
|
||||
Var(String),
|
||||
Lit(JsonValue),
|
||||
}
|
||||
|
||||
/// Wire-level value tag. Restricted to what
|
||||
/// [`storage::value::Value`](storage::value::Value) carries. Entity identities from
|
||||
/// the Haskell side (`ValEntity path id`) round-trip through `Str` using a
|
||||
/// `"path:id"` convention; that's a fixture concern, not a runner concern.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum JsonValue {
|
||||
Int(i64),
|
||||
Str(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Join {
|
||||
pub op: JoinOp,
|
||||
pub left: u32,
|
||||
pub right: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum JoinOp {
|
||||
/// `Geolog.DB.Plan.LeftJoin`: result is `left` rows whose shared columns
|
||||
/// appear in `right`. Lowered to `semijoin(left, right)`.
|
||||
Left,
|
||||
/// `Geolog.DB.Plan.RightJoin`: result is `right` rows whose shared
|
||||
/// columns appear in `left`. Lowered to `semijoin(right, left)`.
|
||||
Right,
|
||||
/// `Geolog.DB.Plan.NaturalJoin`. Lowered to `natural_join(left, right)`.
|
||||
Natural,
|
||||
}
|
||||
|
||||
/// Errors produced by [`verify`] when actual bindings don't match the
|
||||
/// scenario's `expected_bindings` projection.
|
||||
#[derive(Debug)]
|
||||
pub enum VerifyError {
|
||||
/// An expected column wasn't produced by the plan.
|
||||
MissingColumn(String),
|
||||
/// An expected row's width didn't match the column count.
|
||||
ExpectedRowArity { expected: usize, got: usize },
|
||||
/// The expected and actual rows (after projection) differ as multisets.
|
||||
BindingsMismatch {
|
||||
expected: Vec<Vec<Value>>,
|
||||
actual: Vec<Vec<Value>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VerifyError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MissingColumn(name) => {
|
||||
write!(f, "expected column {name:?} not in plan output")
|
||||
}
|
||||
Self::ExpectedRowArity { expected, got } => write!(
|
||||
f,
|
||||
"expected row has {got} cells but columns has {expected} entries"
|
||||
),
|
||||
Self::BindingsMismatch { expected, actual } => write!(
|
||||
f,
|
||||
"bindings mismatch:\n expected: {expected:?}\n actual: {actual:?}"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for VerifyError {}
|
||||
|
||||
/// Cross-check an executed [`Relation`] against a [`Plan`]'s
|
||||
/// `expected_bindings`. Projects `actual` to the expected columns (so the
|
||||
/// runner is free to surface wildcard columns the oracle doesn't name) and
|
||||
/// compares as a multiset.
|
||||
///
|
||||
/// Returns `Ok(true)` if the plan carried an oracle and it matched,
|
||||
/// `Ok(false)` if there was no oracle (caller decides whether that's an
|
||||
/// error). Returns [`VerifyError`] on mismatch.
|
||||
///
|
||||
/// # Errors
|
||||
/// See [`VerifyError`].
|
||||
pub fn verify(plan: &Plan, actual: &Relation) -> Result<bool, VerifyError> {
|
||||
let Some(expected) = &plan.expected_bindings else {
|
||||
return Ok(false);
|
||||
};
|
||||
let mut projection: Vec<usize> = Vec::with_capacity(expected.columns.len());
|
||||
for col in &expected.columns {
|
||||
let idx = actual
|
||||
.columns
|
||||
.iter()
|
||||
.position(|c| c == col)
|
||||
.ok_or_else(|| VerifyError::MissingColumn(col.clone()))?;
|
||||
projection.push(idx);
|
||||
}
|
||||
let mut actual_proj: Vec<Vec<Value>> = actual
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| projection.iter().map(|&i| row[i].clone()).collect())
|
||||
.collect();
|
||||
let mut expected_proj: Vec<Vec<Value>> = Vec::with_capacity(expected.rows.len());
|
||||
for row in &expected.rows {
|
||||
if row.len() != expected.columns.len() {
|
||||
return Err(VerifyError::ExpectedRowArity {
|
||||
expected: expected.columns.len(),
|
||||
got: row.len(),
|
||||
});
|
||||
}
|
||||
expected_proj.push(row.iter().cloned().map(Value::from).collect());
|
||||
}
|
||||
// Value is not Ord; use Debug-derived sort keys to compare as a multiset.
|
||||
let key = |row: &[Value]| -> String { format!("{row:?}") };
|
||||
actual_proj.sort_by_key(|r| key(r));
|
||||
expected_proj.sort_by_key(|r| key(r));
|
||||
if actual_proj == expected_proj {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(VerifyError::BindingsMismatch {
|
||||
expected: expected_proj,
|
||||
actual: actual_proj,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors a runner can produce during plan validation and execution.
|
||||
#[derive(Debug)]
|
||||
pub enum RunError {
|
||||
/// A fact or scan references a relation that isn't declared in `schema`.
|
||||
UnknownRelation(String),
|
||||
/// A scan refers to a table that wasn't supplied in the input map.
|
||||
MissingTable(String),
|
||||
/// A fact row's length doesn't match the schema's declared arity.
|
||||
ArityMismatch {
|
||||
relation: String,
|
||||
expected: usize,
|
||||
got: usize,
|
||||
},
|
||||
/// A scan's atom pattern doesn't match the table's arity.
|
||||
PatternArityMismatch {
|
||||
table: String,
|
||||
table_arity: usize,
|
||||
pattern_arity: usize,
|
||||
},
|
||||
/// A join node references a node id that doesn't exist.
|
||||
MissingNode(u32),
|
||||
/// `Query.root` doesn't match any node in `nodes`.
|
||||
MissingRoot(u32),
|
||||
/// Two nodes share the same id.
|
||||
DuplicateNode(u32),
|
||||
/// A join node references its left or right side before that side has
|
||||
/// been computed: the DAG isn't actually topologically sorted by id, or
|
||||
/// it has a cycle.
|
||||
UnresolvedDependency { node: u32, depends_on: u32 },
|
||||
/// A [`Storage`] backend used to materialize tables returned an error.
|
||||
Storage(StorageError),
|
||||
}
|
||||
|
||||
impl From<StorageError> for RunError {
|
||||
fn from(err: StorageError) -> Self {
|
||||
Self::Storage(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RunError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UnknownRelation(name) => {
|
||||
write!(f, "facts reference relation {name:?} not in schema")
|
||||
}
|
||||
Self::MissingTable(name) => write!(f, "scan references missing table {name:?}"),
|
||||
Self::ArityMismatch {
|
||||
relation,
|
||||
expected,
|
||||
got,
|
||||
} => write!(
|
||||
f,
|
||||
"relation {relation:?}: row arity {got} differs from schema arity {expected}"
|
||||
),
|
||||
Self::PatternArityMismatch {
|
||||
table,
|
||||
table_arity,
|
||||
pattern_arity,
|
||||
} => write!(
|
||||
f,
|
||||
"scan of {table:?}: pattern has {pattern_arity} columns, table has {table_arity}"
|
||||
),
|
||||
Self::MissingNode(id) => write!(f, "plan references missing node id {id}"),
|
||||
Self::MissingRoot(id) => write!(f, "plan root id {id} matches no node"),
|
||||
Self::DuplicateNode(id) => write!(f, "duplicate node id {id} in plan"),
|
||||
Self::UnresolvedDependency { node, depends_on } => write!(
|
||||
f,
|
||||
"node {node} depends on {depends_on}, which has not been computed yet"
|
||||
),
|
||||
Self::Storage(err) => write!(f, "storage backend error: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RunError {}
|
||||
|
||||
impl From<JsonValue> for Value {
|
||||
fn from(jv: JsonValue) -> Self {
|
||||
match jv {
|
||||
JsonValue::Int(n) => Self::Int(n),
|
||||
JsonValue::Str(s) => Self::Str(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonTerm> for Term {
|
||||
fn from(t: JsonTerm) -> Self {
|
||||
match t {
|
||||
JsonTerm::Var(name) => Self::Var(name),
|
||||
JsonTerm::Lit(value) => Self::Lit(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a [`Plan`] from a JSON string.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a [`serde_json::Error`] if the input isn't valid JSON in the
|
||||
/// expected shape.
|
||||
pub fn parse_plan(json: &str) -> Result<Plan, serde_json::Error> {
|
||||
serde_json::from_str(json)
|
||||
}
|
||||
|
||||
/// Build the input [`Table`] for each relation declared in a [`Plan`]'s
|
||||
/// schema, populating rows from the plan's `facts` map. Relations with no
|
||||
/// facts get an empty table at the declared arity.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`RunError::UnknownRelation`] if `facts` mentions a relation
|
||||
/// not in `schema`, or [`RunError::ArityMismatch`] if a row's width doesn't
|
||||
/// match the declared arity.
|
||||
pub fn build_tables(plan: &Plan) -> Result<HashMap<String, Table>, RunError> {
|
||||
let mut tables: HashMap<String, Table> = plan
|
||||
.schema
|
||||
.iter()
|
||||
.map(|(name, arity)| (name.clone(), Table::new(*arity)))
|
||||
.collect();
|
||||
for (name, rows) in &plan.facts {
|
||||
let Some(table) = tables.get_mut(name) else {
|
||||
return Err(RunError::UnknownRelation(name.clone()));
|
||||
};
|
||||
for row in rows {
|
||||
if row.len() != table.arity {
|
||||
return Err(RunError::ArityMismatch {
|
||||
relation: name.clone(),
|
||||
expected: table.arity,
|
||||
got: row.len(),
|
||||
});
|
||||
}
|
||||
let cells: Vec<Value> = row.iter().cloned().map(Value::from).collect();
|
||||
table.push(cells);
|
||||
}
|
||||
}
|
||||
Ok(tables)
|
||||
}
|
||||
|
||||
/// Populate a [`Storage`] backend from a [`Plan`]'s schema and facts, then
|
||||
/// materialize each declared relation back into an in-memory [`Table`] via
|
||||
/// [`scan_as_table`]. The returned map is the same shape [`execute`]
|
||||
/// consumes, so this is the storage-backed analogue of [`build_tables`].
|
||||
///
|
||||
/// Adding a new backend means constructing a different `S` at the call
|
||||
/// site; the body here doesn't need to change.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`RunError::UnknownRelation`] or [`RunError::ArityMismatch`] on
|
||||
/// the same conditions as [`build_tables`], or [`RunError::Storage`] when
|
||||
/// the backend itself rejects an operation.
|
||||
pub fn build_tables_via_storage<S: Storage>(
|
||||
plan: &Plan,
|
||||
storage: &mut S,
|
||||
) -> Result<HashMap<String, Table>, RunError> {
|
||||
for (name, arity) in &plan.schema {
|
||||
storage.create_relation(name, *arity)?;
|
||||
}
|
||||
{
|
||||
let mut tx = storage.transaction()?;
|
||||
for (name, rows) in &plan.facts {
|
||||
let Some(&arity) = plan.schema.get(name) else {
|
||||
return Err(RunError::UnknownRelation(name.clone()));
|
||||
};
|
||||
for row in rows {
|
||||
if row.len() != arity {
|
||||
return Err(RunError::ArityMismatch {
|
||||
relation: name.clone(),
|
||||
expected: arity,
|
||||
got: row.len(),
|
||||
});
|
||||
}
|
||||
let cells: Vec<Value> = row.iter().cloned().map(Value::from).collect();
|
||||
tx.insert(name, cells)?;
|
||||
}
|
||||
}
|
||||
tx.commit()?;
|
||||
}
|
||||
let mut tables: HashMap<String, Table> = HashMap::with_capacity(plan.schema.len());
|
||||
for name in plan.schema.keys() {
|
||||
let table = scan_as_table(storage as &dyn Storage, name)?;
|
||||
tables.insert(name.clone(), table);
|
||||
}
|
||||
Ok(tables)
|
||||
}
|
||||
|
||||
/// Execute a query DAG against the supplied input tables, returning the
|
||||
/// bindings [`Relation`] for the rooted plan node.
|
||||
///
|
||||
/// Nodes are executed in ascending `id` order. For a Yannakakis plan as
|
||||
/// emitted by `Geolog.DB.Plan` this is equivalent to a topological sort,
|
||||
/// since `insertJoin` only references node ids that have already been
|
||||
/// allocated. A non-monotone id ordering is rejected with
|
||||
/// [`RunError::UnresolvedDependency`].
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`RunError::DuplicateNode`] for repeated ids,
|
||||
/// [`RunError::MissingNode`] for join references to unknown ids,
|
||||
/// [`RunError::MissingRoot`] if `query.root` isn't present,
|
||||
/// [`RunError::MissingTable`] if a scan references a table not in the map,
|
||||
/// or [`RunError::PatternArityMismatch`] if a scan's pattern doesn't match
|
||||
/// the table's arity.
|
||||
pub fn execute<S: std::hash::BuildHasher>(
|
||||
tables: &HashMap<String, Table, S>,
|
||||
query: &Query,
|
||||
) -> Result<Relation, RunError> {
|
||||
let mut seen_ids: std::collections::HashSet<u32> =
|
||||
std::collections::HashSet::with_capacity(query.nodes.len());
|
||||
for node in &query.nodes {
|
||||
if !seen_ids.insert(node.id) {
|
||||
return Err(RunError::DuplicateNode(node.id));
|
||||
}
|
||||
}
|
||||
if !seen_ids.contains(&query.root) {
|
||||
return Err(RunError::MissingRoot(query.root));
|
||||
}
|
||||
|
||||
let mut ordered: Vec<&Node> = query.nodes.iter().collect();
|
||||
ordered.sort_by_key(|n| n.id);
|
||||
|
||||
let mut results: HashMap<u32, Relation> = HashMap::with_capacity(ordered.len());
|
||||
for node in ordered {
|
||||
let computed = match &node.action {
|
||||
Action::Scan(atom) => {
|
||||
let table = tables
|
||||
.get(&atom.table)
|
||||
.ok_or_else(|| RunError::MissingTable(atom.table.clone()))?;
|
||||
if atom.columns.len() != table.arity {
|
||||
return Err(RunError::PatternArityMismatch {
|
||||
table: atom.table.clone(),
|
||||
table_arity: table.arity,
|
||||
pattern_arity: atom.columns.len(),
|
||||
});
|
||||
}
|
||||
let pattern = AtomPattern {
|
||||
columns: atom.columns.iter().cloned().map(Term::from).collect(),
|
||||
};
|
||||
scan_atom(table, &pattern)
|
||||
}
|
||||
Action::Join(join) => {
|
||||
let left = require_dep(&results, &seen_ids, node.id, join.left)?;
|
||||
let right = require_dep(&results, &seen_ids, node.id, join.right)?;
|
||||
match join.op {
|
||||
JoinOp::Left => semijoin(left, right),
|
||||
JoinOp::Right => semijoin(right, left),
|
||||
JoinOp::Natural => natural_join(left, right),
|
||||
}
|
||||
}
|
||||
};
|
||||
results.insert(node.id, computed);
|
||||
}
|
||||
|
||||
results
|
||||
.remove(&query.root)
|
||||
.ok_or(RunError::MissingRoot(query.root))
|
||||
}
|
||||
|
||||
fn require_dep<'a>(
|
||||
results: &'a HashMap<u32, Relation>,
|
||||
seen: &std::collections::HashSet<u32>,
|
||||
node: u32,
|
||||
depends_on: u32,
|
||||
) -> Result<&'a Relation, RunError> {
|
||||
if let Some(rel) = results.get(&depends_on) {
|
||||
Ok(rel)
|
||||
} else if seen.contains(&depends_on) {
|
||||
Err(RunError::UnresolvedDependency { node, depends_on })
|
||||
} else {
|
||||
Err(RunError::MissingNode(depends_on))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience: parse JSON, build tables from the embedded facts, and
|
||||
/// execute, returning the root binding relation. Equivalent to
|
||||
/// `parse_plan` + [`build_tables`] + [`execute`].
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a JSON parse error if the input is malformed, or a [`RunError`]
|
||||
/// for any later step.
|
||||
pub fn run_json(json: &str) -> Result<Relation, RunFromJsonError> {
|
||||
let plan = parse_plan(json).map_err(RunFromJsonError::Parse)?;
|
||||
let tables = build_tables(&plan).map_err(RunFromJsonError::Run)?;
|
||||
let bindings = execute(&tables, &plan.query).map_err(RunFromJsonError::Run)?;
|
||||
Ok(bindings)
|
||||
}
|
||||
|
||||
/// Combined error from [`run_json`].
|
||||
#[derive(Debug)]
|
||||
pub enum RunFromJsonError {
|
||||
Parse(serde_json::Error),
|
||||
Run(RunError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RunFromJsonError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Parse(err) => write!(f, "parse error: {err}"),
|
||||
Self::Run(err) => write!(f, "run error: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RunFromJsonError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Parse(err) => Some(err),
|
||||
Self::Run(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
225
crates/plan-runner/src/main.rs
Normal file
@ -0,0 +1,225 @@
|
||||
//! `plan-run` CLI: read a JSON plan from a file (or stdin if `-`), execute
|
||||
//! it against the chosen backend, and print the resulting binding relation
|
||||
//! as JSON on stdout.
|
||||
//!
|
||||
//! Backends:
|
||||
//!
|
||||
//! - `memory` (default): build tables straight from the plan's `facts`
|
||||
//! block, no `Storage` trait involved. Pure in-memory path.
|
||||
//! - `memory-storage`: load the same facts through `storage::MemoryStorage`
|
||||
//! via the `Storage` trait, then materialize tables back out with
|
||||
//! `scan_as_table` before executing.
|
||||
//! - `lmdb`, `redb`, `fjall`, `sqlite`: file-backed `Storage` adapters.
|
||||
//! Each invocation creates a fresh tempdir for the store and drops it on
|
||||
//! exit; the runner is one-shot, so persistent paths aren't needed.
|
||||
//! - `geomerge`: CRDT-backed adapter. Constructed in-memory; alpha-status
|
||||
//! upstream.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::{self, Read};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use plan_runner::{JsonValue, Plan, build_tables, build_tables_via_storage, execute, parse_plan};
|
||||
use storage::MemoryStorage;
|
||||
use storage::adapters::fjall::FjallStorage;
|
||||
use storage::adapters::geomerge::{ColumnKind, GeomergeStorage};
|
||||
use storage::adapters::lmdb::LmdbStorage;
|
||||
use storage::adapters::redb::RedbStorage;
|
||||
use storage::adapters::sqlite::SqliteStorage;
|
||||
use storage::table::Table;
|
||||
use storage::value::Value;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Backend {
|
||||
Memory,
|
||||
MemoryStorage,
|
||||
Lmdb,
|
||||
Redb,
|
||||
Fjall,
|
||||
Sqlite,
|
||||
Geomerge,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
fn parse(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"memory" => Some(Self::Memory),
|
||||
"memory-storage" => Some(Self::MemoryStorage),
|
||||
"lmdb" => Some(Self::Lmdb),
|
||||
"redb" => Some(Self::Redb),
|
||||
"fjall" => Some(Self::Fjall),
|
||||
"sqlite" => Some(Self::Sqlite),
|
||||
"geomerge" => Some(Self::Geomerge),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BACKENDS_HELP: &str = "memory|memory-storage|lmdb|redb|fjall|sqlite|geomerge";
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let mut backend = Backend::Memory;
|
||||
let mut input_path: Option<String> = None;
|
||||
let mut args = std::env::args().skip(1);
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--backend" => {
|
||||
let Some(value) = args.next() else {
|
||||
eprintln!("--backend requires a value ({BACKENDS_HELP})");
|
||||
return ExitCode::from(2);
|
||||
};
|
||||
let Some(parsed) = Backend::parse(&value) else {
|
||||
eprintln!("unknown backend {value:?} (try {BACKENDS_HELP})");
|
||||
return ExitCode::from(2);
|
||||
};
|
||||
backend = parsed;
|
||||
}
|
||||
other if input_path.is_none() => input_path = Some(other.to_string()),
|
||||
other => {
|
||||
eprintln!("unexpected argument: {other}");
|
||||
return ExitCode::from(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(path) = input_path else {
|
||||
eprintln!("usage: plan-run [--backend {BACKENDS_HELP}] <plan.json | ->");
|
||||
return ExitCode::from(2);
|
||||
};
|
||||
|
||||
let input = match read_input(&path) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
eprintln!("failed to read {path}: {err}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
};
|
||||
|
||||
let plan = match parse_plan(&input) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
eprintln!("parse error: {err}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
};
|
||||
|
||||
let tables = match build_tables_for(&plan, backend) {
|
||||
Ok(t) => t,
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
};
|
||||
|
||||
let relation = match execute(&tables, &plan.query) {
|
||||
Ok(r) => r,
|
||||
Err(err) => {
|
||||
eprintln!("execute error: {err}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
};
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"columns": relation.columns,
|
||||
"rows": relation
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| row.iter().map(value_to_json).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>(),
|
||||
});
|
||||
println!("{payload}");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
/// Build the input tables for `plan` using `backend`. Path-based adapters
|
||||
/// allocate a fresh tempdir; it drops at the end of this function, which is
|
||||
/// safe because `build_tables_via_storage` fully materializes the tables
|
||||
/// into owned `Vec<Value>` before returning.
|
||||
fn build_tables_for(plan: &Plan, backend: Backend) -> Result<HashMap<String, Table>, String> {
|
||||
match backend {
|
||||
Backend::Memory => build_tables(plan).map_err(|e| format!("build error: {e}")),
|
||||
Backend::MemoryStorage => {
|
||||
let mut storage = MemoryStorage::default();
|
||||
build_tables_via_storage(plan, &mut storage)
|
||||
.map_err(|e| format!("build error (memory-storage): {e}"))
|
||||
}
|
||||
Backend::Lmdb => {
|
||||
let dir = TempDir::new().map_err(|e| format!("tempdir: {e}"))?;
|
||||
let mut storage = LmdbStorage::open(dir.path())
|
||||
.map_err(|e| format!("failed to open lmdb backend: {e}"))?;
|
||||
build_tables_via_storage(plan, &mut storage)
|
||||
.map_err(|e| format!("build error (lmdb): {e}"))
|
||||
}
|
||||
Backend::Redb => {
|
||||
let dir = TempDir::new().map_err(|e| format!("tempdir: {e}"))?;
|
||||
let mut storage = RedbStorage::open(dir.path().join("data.redb"))
|
||||
.map_err(|e| format!("failed to open redb backend: {e}"))?;
|
||||
build_tables_via_storage(plan, &mut storage)
|
||||
.map_err(|e| format!("build error (redb): {e}"))
|
||||
}
|
||||
Backend::Fjall => {
|
||||
let dir = TempDir::new().map_err(|e| format!("tempdir: {e}"))?;
|
||||
let mut storage = FjallStorage::open(dir.path())
|
||||
.map_err(|e| format!("failed to open fjall backend: {e}"))?;
|
||||
build_tables_via_storage(plan, &mut storage)
|
||||
.map_err(|e| format!("build error (fjall): {e}"))
|
||||
}
|
||||
Backend::Sqlite => {
|
||||
let dir = TempDir::new().map_err(|e| format!("tempdir: {e}"))?;
|
||||
let mut storage = SqliteStorage::open(dir.path().join("data.sqlite"))
|
||||
.map_err(|e| format!("failed to open sqlite backend: {e}"))?;
|
||||
build_tables_via_storage(plan, &mut storage)
|
||||
.map_err(|e| format!("build error (sqlite): {e}"))
|
||||
}
|
||||
Backend::Geomerge => {
|
||||
let relations = plan
|
||||
.schema
|
||||
.iter()
|
||||
.map(|(name, &arity)| (name.clone(), infer_column_kinds(plan, name, arity)));
|
||||
let mut storage = GeomergeStorage::with_relations(relations)
|
||||
.map_err(|e| format!("failed to open geomerge backend: {e}"))?;
|
||||
build_tables_via_storage(plan, &mut storage)
|
||||
.map_err(|e| format!("build error (geomerge): {e}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Best-effort column type inference for `geomerge`'s synthesized theory.
|
||||
/// The runner IR carries only arity, so we peek at the first fact row of
|
||||
/// the relation. Columns without a sample default to `String`, which
|
||||
/// matches every checked-in fixture (entity identities are encoded as
|
||||
/// strings by the exporter).
|
||||
fn infer_column_kinds(plan: &Plan, name: &str, arity: usize) -> Vec<ColumnKind> {
|
||||
let mut kinds = vec![ColumnKind::String; arity];
|
||||
let Some(rows) = plan.facts.get(name) else {
|
||||
return kinds;
|
||||
};
|
||||
let Some(first) = rows.first() else {
|
||||
return kinds;
|
||||
};
|
||||
for (i, cell) in first.iter().take(arity).enumerate() {
|
||||
kinds[i] = match cell {
|
||||
JsonValue::Int(_) => ColumnKind::Int,
|
||||
JsonValue::Str(_) => ColumnKind::String,
|
||||
};
|
||||
}
|
||||
kinds
|
||||
}
|
||||
|
||||
fn read_input(path: &str) -> io::Result<String> {
|
||||
if path == "-" {
|
||||
let mut buf = String::new();
|
||||
io::stdin().read_to_string(&mut buf)?;
|
||||
Ok(buf)
|
||||
} else {
|
||||
std::fs::read_to_string(path)
|
||||
}
|
||||
}
|
||||
|
||||
fn value_to_json(value: &Value) -> serde_json::Value {
|
||||
match value {
|
||||
Value::Int(n) => serde_json::Value::Number((*n).into()),
|
||||
Value::Str(s) => serde_json::Value::String(s.clone()),
|
||||
Value::Id(id) => serde_json::Value::String(id.to_string()),
|
||||
}
|
||||
}
|
||||
77
crates/plan-runner/tests/examples.rs
Normal file
@ -0,0 +1,77 @@
|
||||
//! Walks every JSON fixture under `crates/plan-runner/fixtures/` and
|
||||
//! verifies it against the `expected_bindings` the exporter lifted from
|
||||
//! the matching `tools/exporter/examples/*.scenario.json`. A fixture without an oracle
|
||||
//! is reported as a failure (every checked-in fixture is expected to
|
||||
//! carry one).
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use plan_runner::{parse_plan, run_json, verify};
|
||||
|
||||
fn fixtures_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures")
|
||||
}
|
||||
|
||||
fn collect_fixtures() -> BTreeMap<String, String> {
|
||||
let mut out = BTreeMap::new();
|
||||
for entry in fs::read_dir(fixtures_dir()).expect("read fixtures/") {
|
||||
let path = entry.expect("dir entry").path();
|
||||
if path.extension().and_then(|e| e.to_str()) != Some("json") {
|
||||
continue;
|
||||
}
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.expect("ascii fixture name")
|
||||
.to_string();
|
||||
let contents = fs::read_to_string(&path).expect("read fixture");
|
||||
out.insert(name, contents);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_fixture_runs_and_matches_its_oracle() {
|
||||
let fixtures = collect_fixtures();
|
||||
assert!(
|
||||
!fixtures.is_empty(),
|
||||
"no fixtures found in {}",
|
||||
fixtures_dir().display()
|
||||
);
|
||||
|
||||
let mut failures: Vec<String> = Vec::new();
|
||||
for (name, json) in &fixtures {
|
||||
let plan = match parse_plan(json) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
failures.push(format!("{name}: parse error: {err}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if plan.expected_bindings.is_none() {
|
||||
failures.push(format!("{name}: fixture has no expected_bindings"));
|
||||
continue;
|
||||
}
|
||||
let relation = match run_json(json) {
|
||||
Ok(r) => r,
|
||||
Err(err) => {
|
||||
failures.push(format!("{name}: run error: {err}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match verify(&plan, &relation) {
|
||||
Ok(true) => {}
|
||||
Ok(false) => failures.push(format!("{name}: verify returned no-oracle unexpectedly")),
|
||||
Err(err) => failures.push(format!("{name}: {err}")),
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"{} fixture(s) failed:\n {}",
|
||||
failures.len(),
|
||||
failures.join("\n ")
|
||||
);
|
||||
}
|
||||
52
crates/plan-runner/tests/storage_roundtrip.rs
Normal file
@ -0,0 +1,52 @@
|
||||
//! Cross-checks the two paths [`plan-runner`] exposes for materializing
|
||||
//! input tables: the pure [`build_tables`] path and the [`Storage`]-routed
|
||||
//! [`build_tables_via_storage`] path. Same fixture, same plan, must agree
|
||||
//! row-for-row.
|
||||
//!
|
||||
//! This is the visible proof of the layer boundary: any new `Storage`
|
||||
//! backend (LMDB, fjall, geomerge) keeps this test honest by re-running it
|
||||
//! with a different `S`.
|
||||
|
||||
use plan_runner::{build_tables, build_tables_via_storage, execute, parse_plan, run_json};
|
||||
use storage::MemoryStorage;
|
||||
use storage::value::Value;
|
||||
|
||||
const FIXTURE: &str = include_str!("../fixtures/three_atom_chain.json");
|
||||
|
||||
#[test]
|
||||
fn storage_backed_execution_matches_pure_path() {
|
||||
let plan = parse_plan(FIXTURE).expect("parse plan");
|
||||
|
||||
let pure_tables = build_tables(&plan).expect("build_tables");
|
||||
let pure = execute(&pure_tables, &plan.query).expect("pure execute");
|
||||
|
||||
let mut storage = MemoryStorage::default();
|
||||
let storage_tables =
|
||||
build_tables_via_storage(&plan, &mut storage).expect("build_tables_via_storage");
|
||||
let via_storage = execute(&storage_tables, &plan.query).expect("storage execute");
|
||||
|
||||
assert_eq!(pure.columns, via_storage.columns);
|
||||
// Scan order between MemoryStorage and the direct-from-JSON path isn't
|
||||
// required to match; compare rows as a multiset. `Value` is not `Ord`
|
||||
// (it carries `RowId` and `String`), so use a Debug-derived sort key.
|
||||
assert_eq!(sorted_rows(&pure.rows), sorted_rows(&via_storage.rows));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_backed_execution_matches_run_json_oracle() {
|
||||
let plan = parse_plan(FIXTURE).expect("parse plan");
|
||||
let oracle = run_json(FIXTURE).expect("run_json");
|
||||
|
||||
let mut storage = MemoryStorage::default();
|
||||
let tables = build_tables_via_storage(&plan, &mut storage).expect("build_tables_via_storage");
|
||||
let via_storage = execute(&tables, &plan.query).expect("storage execute");
|
||||
|
||||
assert_eq!(oracle.columns, via_storage.columns);
|
||||
assert_eq!(sorted_rows(&oracle.rows), sorted_rows(&via_storage.rows));
|
||||
}
|
||||
|
||||
fn sorted_rows(rows: &[Vec<Value>]) -> Vec<String> {
|
||||
let mut keys: Vec<String> = rows.iter().map(|r| format!("{r:?}")).collect();
|
||||
keys.sort();
|
||||
keys
|
||||
}
|
||||
@ -121,7 +121,7 @@ How it works (logically):
|
||||
|
||||
<div align="center">
|
||||
<picture>
|
||||
<img alt="Types" src="docs/diagrams/workflow.svg" height="90%" width="90%%">
|
||||
<img alt="Workflow" src="docs/diagrams/workflow.svg" height="90%" width="90%">
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ penwidth = 1.2
|
||||
]
|
||||
|
||||
table_node [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>Table</b> (struct)</td></tr>
|
||||
<tr><td align="center"><b>Table</b> (struct, from storage)</td></tr>
|
||||
<tr><td align="left" balign="left">arity: usize</td></tr>
|
||||
<tr><td align="left" balign="left">rows: Vec<Vec<Value>></td></tr>
|
||||
</table>>, fillcolor = "#E8F4FD", color = "#2196F3"]
|
||||
@ -47,7 +47,7 @@ term_node [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding
|
||||
</table>>, fillcolor = "#F3E5F5", color = "#9C27B0"]
|
||||
|
||||
value_node [label = <<table border="0" cellborder="0" cellspacing="0" cellpadding="4">
|
||||
<tr><td align="center"><b>Value</b> (enum)</td></tr>
|
||||
<tr><td align="center"><b>Value</b> (enum, from storage)</td></tr>
|
||||
<tr><td align="left" balign="left">Int(i64)</td></tr>
|
||||
<tr><td align="left" balign="left">Str(String)</td></tr>
|
||||
<tr><td align="left" balign="left">Id(RowId)</td></tr>
|
||||
|
||||
@ -4,144 +4,83 @@
|
||||
<!-- Generated by graphviz version 12.2.1 (0)
|
||||
-->
|
||||
<!-- Title: QueryOpsTypes Pages: 1 -->
|
||||
<svg width="584pt" height="420pt"
|
||||
viewBox="0.00 0.00 583.50 420.00" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="604pt" height="420pt"
|
||||
viewBox="0.00 0.00 603.62 420.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 416)">
|
||||
<title>QueryOpsTypes</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-416 579.5,-416 579.5,4 -4,4"/>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-416 599.62,-416 599.62,4 -4,4"/>
|
||||
<!-- table_node -->
|
||||
<g id="node1" class="node">
|
||||
<title>table_node</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M159.75,-282.5C159.75,-282.5 12,-282.5 12,-282.5 6,-282.5 0,-276.5 0,-270.5 0,-270.5 0,-199.5 0,-199.5 0,-193.5 6,-187.5 12,-187.5 12,-187.5 159.75,-187.5 159.75,-187.5 165.75,-187.5 171.75,-193.5 171.75,-199.5 171.75,-199.5 171.75,-270.5 171.75,-270.5 171.75,-276.5 165.75,-282.5 159.75,-282.5"/>
|
||||
<text text-anchor="start" x="43.88" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Table
|
||||
</text>
|
||||
<text text-anchor="start" x="78.38" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (struct)
|
||||
</text>
|
||||
<text text-anchor="start" x="12" y="-232.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
arity: usize
|
||||
</text>
|
||||
<text text-anchor="start" x="12" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">rows:
|
||||
Vec<Vec<Value>>
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M180,-282.5C180,-282.5 12,-282.5 12,-282.5 6,-282.5 0,-276.5 0,-270.5 0,-270.5 0,-199.5 0,-199.5 0,-193.5 6,-187.5 12,-187.5 12,-187.5 180,-187.5 180,-187.5 186,-187.5 192,-193.5 192,-199.5 192,-199.5 192,-270.5 192,-270.5 192,-276.5 186,-282.5 180,-282.5"/>
|
||||
<text text-anchor="start" x="12" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Table</text>
|
||||
<text text-anchor="start" x="46.5" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (struct, from storage)</text>
|
||||
<text text-anchor="start" x="12" y="-232.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">arity: usize</text>
|
||||
<text text-anchor="start" x="12" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">rows: Vec<Vec<Value>></text>
|
||||
</g>
|
||||
<!-- value_node -->
|
||||
<g id="node5" class="node">
|
||||
<title>value_node</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M351.38,-124C351.38,-124 264.38,-124 264.38,-124 258.38,-124 252.38,-118 252.38,-112 252.38,-112 252.38,-12 252.38,-12 252.38,-6 258.38,0 264.38,0 264.38,0 351.38,0 351.38,0 357.38,0 363.38,-6 363.38,-12 363.38,-12 363.38,-112 363.38,-112 363.38,-118 357.38,-124 351.38,-124"/>
|
||||
<text text-anchor="start" x="264.38" y="-103.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Value
|
||||
</text>
|
||||
<text text-anchor="start" x="300.38" y="-103.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (enum)
|
||||
</text>
|
||||
<text text-anchor="start" x="264.38" y="-73.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Int(i64)
|
||||
</text>
|
||||
<text text-anchor="start" x="264.38" y="-44.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Str(String)
|
||||
</text>
|
||||
<text text-anchor="start" x="264.38" y="-15.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Id(RowId)
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M413.5,-124C413.5,-124 242.5,-124 242.5,-124 236.5,-124 230.5,-118 230.5,-112 230.5,-112 230.5,-12 230.5,-12 230.5,-6 236.5,0 242.5,0 242.5,0 413.5,0 413.5,0 419.5,0 425.5,-6 425.5,-12 425.5,-12 425.5,-112 425.5,-112 425.5,-118 419.5,-124 413.5,-124"/>
|
||||
<text text-anchor="start" x="242.5" y="-103.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Value</text>
|
||||
<text text-anchor="start" x="278.5" y="-103.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (enum, from storage)</text>
|
||||
<text text-anchor="start" x="242.5" y="-73.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Int(i64)</text>
|
||||
<text text-anchor="start" x="242.5" y="-44.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Str(String)</text>
|
||||
<text text-anchor="start" x="242.5" y="-15.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Id(RowId)</text>
|
||||
</g>
|
||||
<!-- table_node->value_node -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>table_node->value_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M146.83,-187.05C176.61,-164.11 212.47,-136.49 242.78,-113.14"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="244.77,-116.02 250.56,-107.15 240.5,-110.48 244.77,-116.02"/>
|
||||
<text text-anchor="middle" x="227.34" y="-153.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Vec<Vec<Value>>
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M159.7,-187.05C183.12,-169.79 210.13,-149.88 235.56,-131.13"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="237.42,-134.11 243.39,-125.36 233.27,-128.48 237.42,-134.11"/>
|
||||
<text text-anchor="middle" x="242.17" y="-153.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Vec<Vec<Value>></text>
|
||||
</g>
|
||||
<!-- relation_node -->
|
||||
<g id="node2" class="node">
|
||||
<title>relation_node</title>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5"
|
||||
d="M381.75,-282.5C381.75,-282.5 234,-282.5 234,-282.5 228,-282.5 222,-276.5 222,-270.5 222,-270.5 222,-199.5 222,-199.5 222,-193.5 228,-187.5 234,-187.5 234,-187.5 381.75,-187.5 381.75,-187.5 387.75,-187.5 393.75,-193.5 393.75,-199.5 393.75,-199.5 393.75,-270.5 393.75,-270.5 393.75,-276.5 387.75,-282.5 381.75,-282.5"/>
|
||||
<text text-anchor="start" x="256.5" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Relation
|
||||
</text>
|
||||
<text text-anchor="start" x="309.75" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (struct)
|
||||
</text>
|
||||
<text text-anchor="start" x="234" y="-232.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
columns: Vec<String>
|
||||
</text>
|
||||
<text text-anchor="start" x="234" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
rows: Vec<Vec<Value>>
|
||||
</text>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5" d="M401.88,-282.5C401.88,-282.5 254.12,-282.5 254.12,-282.5 248.12,-282.5 242.12,-276.5 242.12,-270.5 242.12,-270.5 242.12,-199.5 242.12,-199.5 242.12,-193.5 248.12,-187.5 254.12,-187.5 254.12,-187.5 401.88,-187.5 401.88,-187.5 407.88,-187.5 413.88,-193.5 413.88,-199.5 413.88,-199.5 413.88,-270.5 413.88,-270.5 413.88,-276.5 407.88,-282.5 401.88,-282.5"/>
|
||||
<text text-anchor="start" x="276.62" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Relation</text>
|
||||
<text text-anchor="start" x="329.88" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (struct)</text>
|
||||
<text text-anchor="start" x="254.12" y="-232.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">columns: Vec<String></text>
|
||||
<text text-anchor="start" x="254.12" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">rows: Vec<Vec<Value>></text>
|
||||
</g>
|
||||
<!-- relation_node->value_node -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>relation_node->value_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M307.88,-187.27C307.88,-171.37 307.88,-153.21 307.88,-135.76"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="311.38,-136.25 307.88,-126.25 304.38,-136.25 311.38,-136.25"/>
|
||||
<text text-anchor="middle" x="345" y="-153.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Vec<Vec<Value>>
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M328,-187.27C328,-171.37 328,-153.21 328,-135.76"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="331.5,-136.25 328,-126.25 324.5,-136.25 331.5,-136.25"/>
|
||||
<text text-anchor="middle" x="365.12" y="-153.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Vec<Vec<Value>></text>
|
||||
</g>
|
||||
<!-- atom_pattern_node -->
|
||||
<g id="node3" class="node">
|
||||
<title>atom_pattern_node</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M563.5,-412C563.5,-412 432.25,-412 432.25,-412 426.25,-412 420.25,-406 420.25,-400 420.25,-400 420.25,-358 420.25,-358 420.25,-352 426.25,-346 432.25,-346 432.25,-346 563.5,-346 563.5,-346 569.5,-346 575.5,-352 575.5,-358 575.5,-358 575.5,-400 575.5,-400 575.5,-406 569.5,-412 563.5,-412"/>
|
||||
<text text-anchor="start" x="432.25" y="-391.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">AtomPattern
|
||||
</text>
|
||||
<text text-anchor="start" x="514" y="-391.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (struct)
|
||||
</text>
|
||||
<text text-anchor="start" x="432.25" y="-361.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
columns: Vec<Term>
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M583.62,-412C583.62,-412 452.38,-412 452.38,-412 446.38,-412 440.38,-406 440.38,-400 440.38,-400 440.38,-358 440.38,-358 440.38,-352 446.38,-346 452.38,-346 452.38,-346 583.62,-346 583.62,-346 589.62,-346 595.62,-352 595.62,-358 595.62,-358 595.62,-400 595.62,-400 595.62,-406 589.62,-412 583.62,-412"/>
|
||||
<text text-anchor="start" x="452.38" y="-391.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">AtomPattern</text>
|
||||
<text text-anchor="start" x="534.12" y="-391.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (struct)</text>
|
||||
<text text-anchor="start" x="452.38" y="-361.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">columns: Vec<Term></text>
|
||||
</g>
|
||||
<!-- term_node -->
|
||||
<g id="node4" class="node">
|
||||
<title>term_node</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M539.88,-282.5C539.88,-282.5 455.88,-282.5 455.88,-282.5 449.88,-282.5 443.88,-276.5 443.88,-270.5 443.88,-270.5 443.88,-199.5 443.88,-199.5 443.88,-193.5 449.88,-187.5 455.88,-187.5 455.88,-187.5 539.88,-187.5 539.88,-187.5 545.88,-187.5 551.88,-193.5 551.88,-199.5 551.88,-199.5 551.88,-270.5 551.88,-270.5 551.88,-276.5 545.88,-282.5 539.88,-282.5"/>
|
||||
<text text-anchor="start" x="455.88" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Term
|
||||
</text>
|
||||
<text text-anchor="start" x="488.88" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (enum)
|
||||
</text>
|
||||
<text text-anchor="start" x="455.88" y="-232.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Var(String)
|
||||
</text>
|
||||
<text text-anchor="start" x="455.88" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Lit(Value)
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M560,-282.5C560,-282.5 476,-282.5 476,-282.5 470,-282.5 464,-276.5 464,-270.5 464,-270.5 464,-199.5 464,-199.5 464,-193.5 470,-187.5 476,-187.5 476,-187.5 560,-187.5 560,-187.5 566,-187.5 572,-193.5 572,-199.5 572,-199.5 572,-270.5 572,-270.5 572,-276.5 566,-282.5 560,-282.5"/>
|
||||
<text text-anchor="start" x="476" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Term</text>
|
||||
<text text-anchor="start" x="509" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (enum)</text>
|
||||
<text text-anchor="start" x="476" y="-232.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Var(String)</text>
|
||||
<text text-anchor="start" x="476" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Lit(Value)</text>
|
||||
</g>
|
||||
<!-- atom_pattern_node->term_node -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>atom_pattern_node->term_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M497.88,-345.78C497.88,-330.61 497.88,-312.04 497.88,-294.52"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="501.38,-294.73 497.88,-284.73 494.38,-294.73 501.38,-294.73"/>
|
||||
<text text-anchor="middle" x="520.75" y="-312.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Vec<Term>
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M518,-345.78C518,-330.61 518,-312.04 518,-294.52"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="521.5,-294.73 518,-284.73 514.5,-294.73 521.5,-294.73"/>
|
||||
<text text-anchor="middle" x="540.88" y="-312.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Vec<Term></text>
|
||||
</g>
|
||||
<!-- term_node->value_node -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>term_node->value_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M445.71,-187.05C423.01,-166.62 396.18,-142.47 372.26,-120.94"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="374.71,-118.44 364.94,-114.35 370.03,-123.65 374.71,-118.44"/>
|
||||
<text text-anchor="middle" x="433.75" y="-153.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Lit(Value)
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M465.83,-187.05C446.99,-170.09 425.31,-150.58 404.8,-132.12"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="407.22,-129.59 397.44,-125.5 402.53,-134.79 407.22,-129.59"/>
|
||||
<text text-anchor="middle" x="453.88" y="-153.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Lit(Value)</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 7.3 KiB |
@ -5,253 +5,155 @@
|
||||
-->
|
||||
<!-- Title: QueryOpsHandPlan Pages: 1 -->
|
||||
<svg width="1482pt" height="471pt"
|
||||
viewBox="0.00 0.00 1481.75 471.00" xmlns="http://www.w3.org/2000/svg">
|
||||
viewBox="0.00 0.00 1481.75 471.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 467)">
|
||||
<title>QueryOpsHandPlan</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-467 1477.75,-467 1477.75,4 -4,4"/>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_inputs</title>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2"
|
||||
points="8,-8 8,-455 198.5,-455 198.5,-8 8,-8"/>
|
||||
<text text-anchor="middle" x="103.25" y="-437.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#555555">Inputs (positional tables)
|
||||
</text>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2" points="8,-8 8,-455 198.5,-455 198.5,-8 8,-8"/>
|
||||
<text text-anchor="middle" x="103.25" y="-437.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#555555">Inputs (positional tables)</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_atoms</title>
|
||||
<polygon fill="white" stroke="#9c27b0" stroke-dasharray="5,2"
|
||||
points="233.5,-12 233.5,-451 609.5,-451 609.5,-12 233.5,-12"/>
|
||||
<text text-anchor="middle" x="421.5" y="-433.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#7b1fa2">Atom Scans  (scan_atom: Table × AtomPattern → Relation)
|
||||
</text>
|
||||
<polygon fill="white" stroke="#9c27b0" stroke-dasharray="5,2" points="233.5,-12 233.5,-451 609.5,-451 609.5,-12 233.5,-12"/>
|
||||
<text text-anchor="middle" x="421.5" y="-433.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#7b1fa2">Atom Scans  (scan_atom: Table × AtomPattern → Relation)</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_joins</title>
|
||||
<polygon fill="white" stroke="#4caf50" stroke-dasharray="5,2"
|
||||
points="665.5,-141 665.5,-322 1106,-322 1106,-141 665.5,-141"/>
|
||||
<text text-anchor="middle" x="885.75" y="-304.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#388e3c">Joins  (shared cols = matching column names)
|
||||
</text>
|
||||
<polygon fill="white" stroke="#4caf50" stroke-dasharray="5,2" points="665.5,-141 665.5,-322 1106,-322 1106,-141 665.5,-141"/>
|
||||
<text text-anchor="middle" x="885.75" y="-304.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#388e3c">Joins  (shared cols = matching column names)</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_output</title>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2"
|
||||
points="1141,-152 1141,-311 1465.75,-311 1465.75,-152 1141,-152"/>
|
||||
<text text-anchor="middle" x="1303.38" y="-293.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#555555">Output (binding relation)
|
||||
</text>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2" points="1141,-152 1141,-311 1465.75,-311 1465.75,-152 1141,-152"/>
|
||||
<text text-anchor="middle" x="1303.38" y="-293.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#555555">Output (binding relation)</text>
|
||||
</g>
|
||||
<!-- author_table -->
|
||||
<g id="node1" class="node">
|
||||
<title>author_table</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M165.88,-408.12C165.88,-408.12 40.62,-408.12 40.62,-408.12 34.62,-408.12 28.62,-402.12 28.62,-396.12 28.62,-396.12 28.62,-325.88 28.62,-325.88 28.62,-319.88 34.62,-313.88 40.62,-313.88 40.62,-313.88 165.88,-313.88 165.88,-313.88 171.88,-313.88 177.88,-319.88 177.88,-325.88 177.88,-325.88 177.88,-396.12 177.88,-396.12 177.88,-402.12 171.88,-408.12 165.88,-408.12"/>
|
||||
<text text-anchor="start" x="60.88" y="-387.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Table: author
|
||||
</text>
|
||||
<text text-anchor="start" x="40.62" y="-358.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
arity 2
|
||||
</text>
|
||||
<text text-anchor="start" x="40.62" y="-329.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
rows: (name, book)
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M165.88,-408.12C165.88,-408.12 40.62,-408.12 40.62,-408.12 34.62,-408.12 28.62,-402.12 28.62,-396.12 28.62,-396.12 28.62,-325.88 28.62,-325.88 28.62,-319.88 34.62,-313.88 40.62,-313.88 40.62,-313.88 165.88,-313.88 165.88,-313.88 171.88,-313.88 177.88,-319.88 177.88,-325.88 177.88,-325.88 177.88,-396.12 177.88,-396.12 177.88,-402.12 171.88,-408.12 165.88,-408.12"/>
|
||||
<text text-anchor="start" x="60.88" y="-387.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Table: author</text>
|
||||
<text text-anchor="start" x="40.62" y="-358.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• arity 2</text>
|
||||
<text text-anchor="start" x="40.62" y="-329.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• rows: (name, book)</text>
|
||||
</g>
|
||||
<!-- author_rel -->
|
||||
<g id="node4" class="node">
|
||||
<title>author_rel</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M509.12,-408.12C509.12,-408.12 332.88,-408.12 332.88,-408.12 326.88,-408.12 320.88,-402.12 320.88,-396.12 320.88,-396.12 320.88,-325.88 320.88,-325.88 320.88,-319.88 326.88,-313.88 332.88,-313.88 332.88,-313.88 509.12,-313.88 509.12,-313.88 515.12,-313.88 521.12,-319.88 521.12,-325.88 521.12,-325.88 521.12,-396.12 521.12,-396.12 521.12,-402.12 515.12,-408.12 509.12,-408.12"/>
|
||||
<text text-anchor="start" x="388" y="-387.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">author_rel
|
||||
</text>
|
||||
<text text-anchor="start" x="332.88" y="-358.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
pattern: [Var name, Var book]
|
||||
</text>
|
||||
<text text-anchor="start" x="332.88" y="-329.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
cols: [name, book]
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M509.12,-408.12C509.12,-408.12 332.88,-408.12 332.88,-408.12 326.88,-408.12 320.88,-402.12 320.88,-396.12 320.88,-396.12 320.88,-325.88 320.88,-325.88 320.88,-319.88 326.88,-313.88 332.88,-313.88 332.88,-313.88 509.12,-313.88 509.12,-313.88 515.12,-313.88 521.12,-319.88 521.12,-325.88 521.12,-325.88 521.12,-396.12 521.12,-396.12 521.12,-402.12 515.12,-408.12 509.12,-408.12"/>
|
||||
<text text-anchor="start" x="388" y="-387.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">author_rel</text>
|
||||
<text text-anchor="start" x="332.88" y="-358.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">pattern: [Var name, Var book]</text>
|
||||
<text text-anchor="start" x="332.88" y="-329.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">cols: [name, book]</text>
|
||||
</g>
|
||||
<!-- author_table->author_rel -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>author_table->author_rel</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" d="M178.28,-361C217.1,-361 265.45,-361 308.68,-361"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2"
|
||||
points="308.62,-364.5 318.62,-361 308.62,-357.5 308.62,-364.5"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="308.62,-364.5 318.62,-361 308.62,-357.5 308.62,-364.5"/>
|
||||
</g>
|
||||
<!-- bestseller_table -->
|
||||
<g id="node2" class="node">
|
||||
<title>bestseller_table</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M156.12,-264.12C156.12,-264.12 50.38,-264.12 50.38,-264.12 44.38,-264.12 38.38,-258.12 38.38,-252.12 38.38,-252.12 38.38,-181.88 38.38,-181.88 38.38,-175.88 44.38,-169.88 50.38,-169.88 50.38,-169.88 156.12,-169.88 156.12,-169.88 162.12,-169.88 168.12,-175.88 168.12,-181.88 168.12,-181.88 168.12,-252.12 168.12,-252.12 168.12,-258.12 162.12,-264.12 156.12,-264.12"/>
|
||||
<text text-anchor="start" x="50.38" y="-243.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Table: bestseller
|
||||
</text>
|
||||
<text text-anchor="start" x="50.38" y="-214.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
arity 1
|
||||
</text>
|
||||
<text text-anchor="start" x="50.38" y="-185.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
rows: (book)
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M156.12,-264.12C156.12,-264.12 50.38,-264.12 50.38,-264.12 44.38,-264.12 38.38,-258.12 38.38,-252.12 38.38,-252.12 38.38,-181.88 38.38,-181.88 38.38,-175.88 44.38,-169.88 50.38,-169.88 50.38,-169.88 156.12,-169.88 156.12,-169.88 162.12,-169.88 168.12,-175.88 168.12,-181.88 168.12,-181.88 168.12,-252.12 168.12,-252.12 168.12,-258.12 162.12,-264.12 156.12,-264.12"/>
|
||||
<text text-anchor="start" x="50.38" y="-243.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Table: bestseller</text>
|
||||
<text text-anchor="start" x="50.38" y="-214.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• arity 1</text>
|
||||
<text text-anchor="start" x="50.38" y="-185.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• rows: (book)</text>
|
||||
</g>
|
||||
<!-- bestseller_rel -->
|
||||
<g id="node5" class="node">
|
||||
<title>bestseller_rel</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M476.12,-264.12C476.12,-264.12 365.88,-264.12 365.88,-264.12 359.88,-264.12 353.88,-258.12 353.88,-252.12 353.88,-252.12 353.88,-181.88 353.88,-181.88 353.88,-175.88 359.88,-169.88 365.88,-169.88 365.88,-169.88 476.12,-169.88 476.12,-169.88 482.12,-169.88 488.12,-175.88 488.12,-181.88 488.12,-181.88 488.12,-252.12 488.12,-252.12 488.12,-258.12 482.12,-264.12 476.12,-264.12"/>
|
||||
<text text-anchor="start" x="377.5" y="-243.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">bestseller_rel
|
||||
</text>
|
||||
<text text-anchor="start" x="365.88" y="-214.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
pattern: [Var book]
|
||||
</text>
|
||||
<text text-anchor="start" x="365.88" y="-185.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
cols: [book]
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M476.12,-264.12C476.12,-264.12 365.88,-264.12 365.88,-264.12 359.88,-264.12 353.88,-258.12 353.88,-252.12 353.88,-252.12 353.88,-181.88 353.88,-181.88 353.88,-175.88 359.88,-169.88 365.88,-169.88 365.88,-169.88 476.12,-169.88 476.12,-169.88 482.12,-169.88 488.12,-175.88 488.12,-181.88 488.12,-181.88 488.12,-252.12 488.12,-252.12 488.12,-258.12 482.12,-264.12 476.12,-264.12"/>
|
||||
<text text-anchor="start" x="377.5" y="-243.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">bestseller_rel</text>
|
||||
<text text-anchor="start" x="365.88" y="-214.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">pattern: [Var book]</text>
|
||||
<text text-anchor="start" x="365.88" y="-185.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">cols: [book]</text>
|
||||
</g>
|
||||
<!-- bestseller_table->bestseller_rel -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>bestseller_table->bestseller_rel</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" d="M168.53,-217C218.65,-217 288.47,-217 341.83,-217"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2"
|
||||
points="341.82,-220.5 351.82,-217 341.82,-213.5 341.82,-220.5"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="341.82,-220.5 351.82,-217 341.82,-213.5 341.82,-220.5"/>
|
||||
</g>
|
||||
<!-- price_table -->
|
||||
<g id="node3" class="node">
|
||||
<title>price_table</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M168.5,-120.12C168.5,-120.12 38,-120.12 38,-120.12 32,-120.12 26,-114.12 26,-108.12 26,-108.12 26,-37.88 26,-37.88 26,-31.88 32,-25.88 38,-25.88 38,-25.88 168.5,-25.88 168.5,-25.88 174.5,-25.88 180.5,-31.88 180.5,-37.88 180.5,-37.88 180.5,-108.12 180.5,-108.12 180.5,-114.12 174.5,-120.12 168.5,-120.12"/>
|
||||
<text text-anchor="start" x="65.75" y="-99.83" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Table: price
|
||||
</text>
|
||||
<text text-anchor="start" x="38" y="-70.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
arity 2
|
||||
</text>
|
||||
<text text-anchor="start" x="38" y="-41.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
rows: (book, dollars)
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M168.5,-120.12C168.5,-120.12 38,-120.12 38,-120.12 32,-120.12 26,-114.12 26,-108.12 26,-108.12 26,-37.88 26,-37.88 26,-31.88 32,-25.88 38,-25.88 38,-25.88 168.5,-25.88 168.5,-25.88 174.5,-25.88 180.5,-31.88 180.5,-37.88 180.5,-37.88 180.5,-108.12 180.5,-108.12 180.5,-114.12 174.5,-120.12 168.5,-120.12"/>
|
||||
<text text-anchor="start" x="65.75" y="-99.83" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Table: price</text>
|
||||
<text text-anchor="start" x="38" y="-70.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• arity 2</text>
|
||||
<text text-anchor="start" x="38" y="-41.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• rows: (book, dollars)</text>
|
||||
</g>
|
||||
<!-- price_rel -->
|
||||
<g id="node6" class="node">
|
||||
<title>price_rel</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M511.75,-120.12C511.75,-120.12 330.25,-120.12 330.25,-120.12 324.25,-120.12 318.25,-114.12 318.25,-108.12 318.25,-108.12 318.25,-37.88 318.25,-37.88 318.25,-31.88 324.25,-25.88 330.25,-25.88 330.25,-25.88 511.75,-25.88 511.75,-25.88 517.75,-25.88 523.75,-31.88 523.75,-37.88 523.75,-37.88 523.75,-108.12 523.75,-108.12 523.75,-114.12 517.75,-120.12 511.75,-120.12"/>
|
||||
<text text-anchor="start" x="392.88" y="-99.83" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">price_rel
|
||||
</text>
|
||||
<text text-anchor="start" x="330.25" y="-70.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
pattern: [Var book, Var dollars]
|
||||
</text>
|
||||
<text text-anchor="start" x="330.25" y="-41.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
cols: [book, dollars]
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M511.75,-120.12C511.75,-120.12 330.25,-120.12 330.25,-120.12 324.25,-120.12 318.25,-114.12 318.25,-108.12 318.25,-108.12 318.25,-37.88 318.25,-37.88 318.25,-31.88 324.25,-25.88 330.25,-25.88 330.25,-25.88 511.75,-25.88 511.75,-25.88 517.75,-25.88 523.75,-31.88 523.75,-37.88 523.75,-37.88 523.75,-108.12 523.75,-108.12 523.75,-114.12 517.75,-120.12 511.75,-120.12"/>
|
||||
<text text-anchor="start" x="392.88" y="-99.83" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">price_rel</text>
|
||||
<text text-anchor="start" x="330.25" y="-70.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">pattern: [Var book, Var dollars]</text>
|
||||
<text text-anchor="start" x="330.25" y="-41.58" font-family="Helvetica,Arial,sans-serif" font-size="14.00">cols: [book, dollars]</text>
|
||||
</g>
|
||||
<!-- price_table->price_rel -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>price_table->price_rel</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" d="M180.68,-73C218.39,-73 264.62,-73 306.37,-73"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2"
|
||||
points="306.2,-76.5 316.2,-73 306.2,-69.5 306.2,-76.5"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="306.2,-76.5 316.2,-73 306.2,-69.5 306.2,-76.5"/>
|
||||
</g>
|
||||
<!-- semijoin_step -->
|
||||
<g id="node7" class="node">
|
||||
<title>semijoin_step</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M819.75,-278.62C819.75,-278.62 691.5,-278.62 691.5,-278.62 685.5,-278.62 679.5,-272.62 679.5,-266.62 679.5,-266.62 679.5,-167.38 679.5,-167.38 679.5,-161.38 685.5,-155.38 691.5,-155.38 691.5,-155.38 819.75,-155.38 819.75,-155.38 825.75,-155.38 831.75,-161.38 831.75,-167.38 831.75,-167.38 831.75,-266.62 831.75,-266.62 831.75,-272.62 825.75,-278.62 819.75,-278.62"/>
|
||||
<text text-anchor="start" x="727.88" y="-258.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">semijoin
|
||||
</text>
|
||||
<text text-anchor="start" x="691.5" y="-229.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
authors of bestsellers
|
||||
</text>
|
||||
<text text-anchor="start" x="691.5" y="-200.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
shared: book
|
||||
</text>
|
||||
<text text-anchor="start" x="691.5" y="-171.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
cols: [name, book]
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M819.75,-278.62C819.75,-278.62 691.5,-278.62 691.5,-278.62 685.5,-278.62 679.5,-272.62 679.5,-266.62 679.5,-266.62 679.5,-167.38 679.5,-167.38 679.5,-161.38 685.5,-155.38 691.5,-155.38 691.5,-155.38 819.75,-155.38 819.75,-155.38 825.75,-155.38 831.75,-161.38 831.75,-167.38 831.75,-167.38 831.75,-266.62 831.75,-266.62 831.75,-272.62 825.75,-278.62 819.75,-278.62"/>
|
||||
<text text-anchor="start" x="727.88" y="-258.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">semijoin</text>
|
||||
<text text-anchor="start" x="691.5" y="-229.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">authors of bestsellers</text>
|
||||
<text text-anchor="start" x="691.5" y="-200.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">shared: book</text>
|
||||
<text text-anchor="start" x="691.5" y="-171.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">cols: [name, book]</text>
|
||||
</g>
|
||||
<!-- author_rel->semijoin_step -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>author_rel->semijoin_step</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2"
|
||||
d="M521.48,-324.79C550.11,-313.83 581.24,-301.4 609.5,-289 628.84,-280.51 649.32,-270.81 668.61,-261.33"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="670.15,-264.48 677.56,-256.91 667.04,-258.2 670.15,-264.48"/>
|
||||
<text text-anchor="middle" x="637.5" y="-284.9" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">left
|
||||
</text>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M521.48,-324.79C550.11,-313.83 581.24,-301.4 609.5,-289 628.84,-280.51 649.32,-270.81 668.61,-261.33"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="670.15,-264.48 677.56,-256.91 667.04,-258.2 670.15,-264.48"/>
|
||||
<text text-anchor="middle" x="637.5" y="-284.9" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">left</text>
|
||||
</g>
|
||||
<!-- bestseller_rel->semijoin_step -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>bestseller_rel->semijoin_step</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M488.51,-217C539.93,-217 611.54,-217 667.53,-217"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="667.41,-220.5 677.41,-217 667.41,-213.5 667.41,-220.5"/>
|
||||
<text text-anchor="middle" x="637.5" y="-221.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">right
|
||||
</text>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="667.41,-220.5 677.41,-217 667.41,-213.5 667.41,-220.5"/>
|
||||
<text text-anchor="middle" x="637.5" y="-221.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">right</text>
|
||||
</g>
|
||||
<!-- natural_join_step -->
|
||||
<g id="node8" class="node">
|
||||
<title>natural_join_step</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M1080,-278.62C1080,-278.62 922.5,-278.62 922.5,-278.62 916.5,-278.62 910.5,-272.62 910.5,-266.62 910.5,-266.62 910.5,-167.38 910.5,-167.38 910.5,-161.38 916.5,-155.38 922.5,-155.38 922.5,-155.38 1080,-155.38 1080,-155.38 1086,-155.38 1092,-161.38 1092,-167.38 1092,-167.38 1092,-266.62 1092,-266.62 1092,-272.62 1086,-278.62 1080,-278.62"/>
|
||||
<text text-anchor="start" x="963" y="-258.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">natural_join
|
||||
</text>
|
||||
<text text-anchor="start" x="922.5" y="-229.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
attach each book's price
|
||||
</text>
|
||||
<text text-anchor="start" x="922.5" y="-200.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
shared: book
|
||||
</text>
|
||||
<text text-anchor="start" x="922.5" y="-171.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
cols: [name, book, dollars]
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M1080,-278.62C1080,-278.62 922.5,-278.62 922.5,-278.62 916.5,-278.62 910.5,-272.62 910.5,-266.62 910.5,-266.62 910.5,-167.38 910.5,-167.38 910.5,-161.38 916.5,-155.38 922.5,-155.38 922.5,-155.38 1080,-155.38 1080,-155.38 1086,-155.38 1092,-161.38 1092,-167.38 1092,-167.38 1092,-266.62 1092,-266.62 1092,-272.62 1086,-278.62 1080,-278.62"/>
|
||||
<text text-anchor="start" x="963" y="-258.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">natural_join</text>
|
||||
<text text-anchor="start" x="922.5" y="-229.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">attach each book's price</text>
|
||||
<text text-anchor="start" x="922.5" y="-200.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">shared: book</text>
|
||||
<text text-anchor="start" x="922.5" y="-171.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">cols: [name, book, dollars]</text>
|
||||
</g>
|
||||
<!-- price_rel->natural_join_step -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>price_rel->natural_join_step</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2"
|
||||
d="M523.91,-71.78C608.41,-73.58 730.63,-82.79 831.75,-116.5 855.71,-124.49 879.92,-136.28 902.24,-149"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="900.38,-151.97 910.78,-153.98 903.91,-145.92 900.38,-151.97"/>
|
||||
<text text-anchor="middle" x="755.62" y="-121.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">right
|
||||
</text>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M523.91,-71.78C608.41,-73.58 730.63,-82.79 831.75,-116.5 855.71,-124.49 879.92,-136.28 902.24,-149"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="900.38,-151.97 910.78,-153.98 903.91,-145.92 900.38,-151.97"/>
|
||||
<text text-anchor="middle" x="755.62" y="-121.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">right</text>
|
||||
</g>
|
||||
<!-- semijoin_step->natural_join_step -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>semijoin_step->natural_join_step</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M832.04,-217C853.1,-217 876.34,-217 898.65,-217"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="898.4,-220.5 908.4,-217 898.4,-213.5 898.4,-220.5"/>
|
||||
<text text-anchor="middle" x="871.12" y="-221.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">left
|
||||
</text>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="898.4,-220.5 908.4,-217 898.4,-213.5 898.4,-220.5"/>
|
||||
<text text-anchor="middle" x="871.12" y="-221.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">left</text>
|
||||
</g>
|
||||
<!-- result -->
|
||||
<g id="node9" class="node">
|
||||
<title>result</title>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5"
|
||||
d="M1435.75,-264.12C1435.75,-264.12 1171,-264.12 1171,-264.12 1165,-264.12 1159,-258.12 1159,-252.12 1159,-252.12 1159,-181.88 1159,-181.88 1159,-175.88 1165,-169.88 1171,-169.88 1171,-169.88 1435.75,-169.88 1435.75,-169.88 1441.75,-169.88 1447.75,-175.88 1447.75,-181.88 1447.75,-181.88 1447.75,-252.12 1447.75,-252.12 1447.75,-258.12 1441.75,-264.12 1435.75,-264.12"/>
|
||||
<text text-anchor="start" x="1277.5" y="-243.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Q result
|
||||
</text>
|
||||
<text text-anchor="start" x="1171" y="-214.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
authors of bestsellers with each book's price
|
||||
</text>
|
||||
<text text-anchor="start" x="1171" y="-185.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
cols: [name, book, dollars]
|
||||
</text>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5" d="M1435.75,-264.12C1435.75,-264.12 1171,-264.12 1171,-264.12 1165,-264.12 1159,-258.12 1159,-252.12 1159,-252.12 1159,-181.88 1159,-181.88 1159,-175.88 1165,-169.88 1171,-169.88 1171,-169.88 1435.75,-169.88 1435.75,-169.88 1441.75,-169.88 1447.75,-175.88 1447.75,-181.88 1447.75,-181.88 1447.75,-252.12 1447.75,-252.12 1447.75,-258.12 1441.75,-264.12 1435.75,-264.12"/>
|
||||
<text text-anchor="start" x="1277.5" y="-243.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Q result</text>
|
||||
<text text-anchor="start" x="1171" y="-214.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">authors of bestsellers with each book's price</text>
|
||||
<text text-anchor="start" x="1171" y="-185.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">cols: [name, book, dollars]</text>
|
||||
</g>
|
||||
<!-- natural_join_step->result -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>natural_join_step->result</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2"
|
||||
d="M1092.3,-217C1109.6,-217 1128.17,-217 1146.86,-217"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="1146.69,-220.5 1156.69,-217 1146.69,-213.5 1146.69,-220.5"/>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M1092.3,-217C1109.6,-217 1128.17,-217 1146.86,-217"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="1146.69,-220.5 1156.69,-217 1146.69,-213.5 1146.69,-220.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 14 KiB |
@ -2,9 +2,9 @@
|
||||
//!
|
||||
//! Three operators are in scope:
|
||||
//!
|
||||
//! - [`atom::scan_atom`] scans a [`Table`](storage::table::Table) under
|
||||
//! an [`atom::AtomPattern`], filtering for repeated-variable equality and
|
||||
//! literal equality, and outputs a binding [`relation::Relation`].
|
||||
//! - [`atom::scan_atom`] scans a [`Table`] under an [`atom::AtomPattern`],
|
||||
//! filtering for repeated-variable equality and literal equality, and
|
||||
//! outputs a binding [`relation::Relation`].
|
||||
//! - [`join::semijoin`] keeps rows of one relation whose shared-column values
|
||||
//! appear in another.
|
||||
//! - [`join::natural_join`] combines rows that agree on shared columns,
|
||||
@ -14,10 +14,8 @@
|
||||
//! is just an expression like
|
||||
//! `natural_join(&semijoin(&a, &b), &scan_atom(&t, &p))`.
|
||||
//!
|
||||
//! Foundational types [`Value`](storage::value::Value) and
|
||||
//! [`Table`](storage::table::Table) live in `storage`, the
|
||||
//! storage-layer crate this crate is built on; storage backends produce
|
||||
//! `Table`s that operators here consume.
|
||||
//! `Value` and `Table` live in the `storage` crate; consumers that build
|
||||
//! inputs depend on `storage` directly.
|
||||
|
||||
pub mod atom;
|
||||
pub mod join;
|
||||
|
||||
@ -8,7 +8,7 @@ This crates helps with decoupling the query execution logic from the underlying
|
||||
### Public API
|
||||
|
||||
| Item | Kind | Description |
|
||||
|--------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|--------------------------------------------------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `Storage` | trait | Backend-agnostic interface for storing and retrieving rows. Required methods: `create_relation`, `arity`, `scan_iter`, and `transaction`. The rest (`scan`, `scan_where`, `insert`, `delete`) have default implementations. |
|
||||
| `Transaction` | trait | Atomic batch of inserts and deletes against a `Storage`. `insert` returns a pending `RowId`; `commit` consumes the boxed transaction and returns a `CommittedTx`; dropping without committing rolls back. |
|
||||
| `CommittedTx` | struct | Result of a successful `Transaction::commit`. Resolves pending `RowId`s returned during the transaction to their post-commit form via `resolve`. Empty for KV adapters where pending equals real; populated for `geomerge`. |
|
||||
@ -24,7 +24,8 @@ This crates helps with decoupling the query execution logic from the underlying
|
||||
| `adapters::redb::RedbStorage` | struct (feat) | Single-file B-tree backed `Storage`, behind the `redb` feature. Wraps `redb::WriteTransaction` for native atomic commits. |
|
||||
| `adapters::fjall::FjallStorage` | struct (feat) | LSM-tree backed `Storage`, behind the `fjall` feature. Each relation gets a partition; transactions buffer inserts and apply them on commit. |
|
||||
| `adapters::lmdb::LmdbStorage` | struct (feat) | mmap'd B-tree backed `Storage`, behind the `lmdb` feature. Wraps `heed`'s `RwTxn` for native atomic commits. |
|
||||
| `adapters::geomerge::GeomergeStorage` | struct (feat) | CRDT-backed `Storage` over the workspace's `geomerge` crate, behind the `geomerge` feature. Wraps `geomerge::Transaction` and resolves pending row IDs via `CommittedTx`. Deletion is not supported (append-only log). |
|
||||
| `adapters::geomerge::GeomergeStorage` | struct (feat) | CRDT-backed `Storage` over the workspace's `geomerge` crate, behind the `geomerge` feature. Wraps `geomerge::Transaction` and resolves pending row IDs via `CommittedTx`. Deletion is not supported (append-only log). Construct with `from_theory`, `from_store`, or `with_relations` (synthesizes a theory from `(name, Vec<ColumnKind>)` for callers that lack a typed schema). |
|
||||
| `adapters::geomerge::ColumnKind` | enum (feat) | Primitive column type fed to `GeomergeStorage::with_relations`: `Int` maps to geomerge `PrimInt`, `String` maps to `PrimString`. Exists so callers can synthesize a theory without depending on `geolog-lang::ir` directly. |
|
||||
|
||||
Data types and their relationships:
|
||||
|
||||
@ -99,7 +100,8 @@ cargo test -p storage --all-features
|
||||
iterators.
|
||||
- **Atomic transactions.**
|
||||
For storage backends with write transactions support (LMDB, Redb, SQLite, and geomerge) we use their transaction API directly.
|
||||
Adapters without native transaction support (MemoryStorage and Fjall) implement `Transaction` with an internal buffer of pending operations that are applied on `commit`.
|
||||
Adapters without native transaction support (MemoryStorage and Fjall) implement `Transaction` with an internal buffer of pending operations that are
|
||||
applied on `commit`.
|
||||
Note that dropping a transaction without calling `commit` rolls back any pending operations.
|
||||
- **Deletion support.**
|
||||
Most adapters implement `delete`.
|
||||
|
||||
@ -5,370 +5,196 @@
|
||||
-->
|
||||
<!-- Title: StorageTypes Pages: 1 -->
|
||||
<svg width="836pt" height="1114pt"
|
||||
viewBox="0.00 0.00 835.88 1114.00" xmlns="http://www.w3.org/2000/svg">
|
||||
viewBox="0.00 0.00 835.88 1114.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1110)">
|
||||
<title>StorageTypes</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-1110 831.88,-1110 831.88,4 -4,4"/>
|
||||
<!-- storage_node -->
|
||||
<g id="node1" class="node">
|
||||
<title>storage_node</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M439.38,-802.5C439.38,-802.5 180.63,-802.5 180.63,-802.5 174.63,-802.5 168.63,-796.5 168.63,-790.5 168.63,-790.5 168.63,-545.5 168.63,-545.5 168.63,-539.5 174.63,-533.5 180.63,-533.5 180.63,-533.5 439.38,-533.5 439.38,-533.5 445.38,-533.5 451.38,-539.5 451.38,-545.5 451.38,-545.5 451.38,-790.5 451.38,-790.5 451.38,-796.5 445.38,-802.5 439.38,-802.5"/>
|
||||
<text text-anchor="start" x="265.01" y="-782.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Storage
|
||||
</text>
|
||||
<text text-anchor="start" x="316.01" y="-782.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (trait)
|
||||
</text>
|
||||
<text text-anchor="start" x="180.63" y="-752.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
create_relation(name, arity)
|
||||
</text>
|
||||
<text text-anchor="start" x="180.63" y="-723.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
arity(name)
|
||||
</text>
|
||||
<text text-anchor="start" x="180.63" y="-694.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
scan_iter(name) -> RowStream
|
||||
</text>
|
||||
<text text-anchor="start" x="180.63" y="-665.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
scan(name) -> Vec<(RowId, Vec<Value>)>
|
||||
</text>
|
||||
<text text-anchor="start" x="180.63" y="-636.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
scan_where(name, col, value)
|
||||
</text>
|
||||
<text text-anchor="start" x="180.63" y="-607.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
transaction() -> Box<dyn Transaction>
|
||||
</text>
|
||||
<text text-anchor="start" x="180.63" y="-578.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
insert(name, row) -> RowId
|
||||
</text>
|
||||
<text text-anchor="start" x="180.63" y="-549.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
delete(name, id)
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M439.38,-802.5C439.38,-802.5 180.63,-802.5 180.63,-802.5 174.63,-802.5 168.63,-796.5 168.63,-790.5 168.63,-790.5 168.63,-545.5 168.63,-545.5 168.63,-539.5 174.63,-533.5 180.63,-533.5 180.63,-533.5 439.38,-533.5 439.38,-533.5 445.38,-533.5 451.38,-539.5 451.38,-545.5 451.38,-545.5 451.38,-790.5 451.38,-790.5 451.38,-796.5 445.38,-802.5 439.38,-802.5"/>
|
||||
<text text-anchor="start" x="265.01" y="-782.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Storage</text>
|
||||
<text text-anchor="start" x="316.01" y="-782.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (trait)</text>
|
||||
<text text-anchor="start" x="180.63" y="-752.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">create_relation(name, arity)</text>
|
||||
<text text-anchor="start" x="180.63" y="-723.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">arity(name)</text>
|
||||
<text text-anchor="start" x="180.63" y="-694.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">scan_iter(name) -> RowStream</text>
|
||||
<text text-anchor="start" x="180.63" y="-665.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">scan(name) -> Vec<(RowId, Vec<Value>)></text>
|
||||
<text text-anchor="start" x="180.63" y="-636.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">scan_where(name, col, value)</text>
|
||||
<text text-anchor="start" x="180.63" y="-607.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">transaction() -> Box<dyn Transaction></text>
|
||||
<text text-anchor="start" x="180.63" y="-578.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">insert(name, row) -> RowId</text>
|
||||
<text text-anchor="start" x="180.63" y="-549.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">delete(name, id)</text>
|
||||
</g>
|
||||
<!-- transaction_node -->
|
||||
<g id="node2" class="node">
|
||||
<title>transaction_node</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M245.51,-470C245.51,-470 80.51,-470 80.51,-470 74.51,-470 68.51,-464 68.51,-458 68.51,-458 68.51,-358 68.51,-358 68.51,-352 74.51,-346 80.51,-346 80.51,-346 245.51,-346 245.51,-346 251.51,-346 257.51,-352 257.51,-358 257.51,-358 257.51,-458 257.51,-458 257.51,-464 251.51,-470 245.51,-470"/>
|
||||
<text text-anchor="start" x="105.63" y="-449.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Transaction
|
||||
</text>
|
||||
<text text-anchor="start" x="181.38" y="-449.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (trait)
|
||||
</text>
|
||||
<text text-anchor="start" x="80.51" y="-419.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
insert(name, row) -> RowId
|
||||
</text>
|
||||
<text text-anchor="start" x="80.51" y="-390.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
delete(name, id)
|
||||
</text>
|
||||
<text text-anchor="start" x="80.51" y="-361.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
commit() -> CommittedTx
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M245.51,-470C245.51,-470 80.51,-470 80.51,-470 74.51,-470 68.51,-464 68.51,-458 68.51,-458 68.51,-358 68.51,-358 68.51,-352 74.51,-346 80.51,-346 80.51,-346 245.51,-346 245.51,-346 251.51,-346 257.51,-352 257.51,-358 257.51,-358 257.51,-458 257.51,-458 257.51,-464 251.51,-470 245.51,-470"/>
|
||||
<text text-anchor="start" x="105.63" y="-449.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Transaction</text>
|
||||
<text text-anchor="start" x="181.38" y="-449.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (trait)</text>
|
||||
<text text-anchor="start" x="80.51" y="-419.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">insert(name, row) -> RowId</text>
|
||||
<text text-anchor="start" x="80.51" y="-390.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">delete(name, id)</text>
|
||||
<text text-anchor="start" x="80.51" y="-361.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">commit() -> CommittedTx</text>
|
||||
</g>
|
||||
<!-- storage_node->transaction_node -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>storage_node->transaction_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M233.71,-533.09C223.34,-514.9 213.06,-496.84 203.67,-480.36"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="206.83,-478.85 198.84,-471.9 200.75,-482.32 206.83,-478.85"/>
|
||||
<text text-anchor="middle" x="257.64" y="-499.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">transaction() yields
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M233.71,-533.09C223.34,-514.9 213.06,-496.84 203.67,-480.36"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="206.83,-478.85 198.84,-471.9 200.75,-482.32 206.83,-478.85"/>
|
||||
<text text-anchor="middle" x="257.64" y="-499.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">transaction() yields</text>
|
||||
</g>
|
||||
<!-- row_stream_node -->
|
||||
<g id="node4" class="node">
|
||||
<title>row_stream_node</title>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5"
|
||||
d="M594.26,-470C594.26,-470 319.76,-470 319.76,-470 313.76,-470 307.76,-464 307.76,-458 307.76,-458 307.76,-358 307.76,-358 307.76,-352 313.76,-346 319.76,-346 319.76,-346 594.26,-346 594.26,-346 600.26,-346 606.26,-352 606.26,-358 606.26,-358 606.26,-458 606.26,-458 606.26,-464 600.26,-470 594.26,-470"/>
|
||||
<text text-anchor="start" x="368.88" y="-449.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">RowStream<'a>
|
||||
</text>
|
||||
<text text-anchor="start" x="471.63" y="-449.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (type alias)
|
||||
</text>
|
||||
<text text-anchor="start" x="319.76" y="-419.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Box<dyn Iterator<Item =
|
||||
</text>
|
||||
<text text-anchor="start" x="319.76" y="-390.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 Result<(RowId, Vec<Value>), StorageError>
|
||||
</text>
|
||||
<text text-anchor="start" x="319.76" y="-361.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
> + 'a>
|
||||
</text>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5" d="M594.26,-470C594.26,-470 319.76,-470 319.76,-470 313.76,-470 307.76,-464 307.76,-458 307.76,-458 307.76,-358 307.76,-358 307.76,-352 313.76,-346 319.76,-346 319.76,-346 594.26,-346 594.26,-346 600.26,-346 606.26,-352 606.26,-358 606.26,-358 606.26,-458 606.26,-458 606.26,-464 600.26,-470 594.26,-470"/>
|
||||
<text text-anchor="start" x="368.88" y="-449.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">RowStream<'a></text>
|
||||
<text text-anchor="start" x="471.63" y="-449.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (type alias)</text>
|
||||
<text text-anchor="start" x="319.76" y="-419.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Box<dyn Iterator<Item =</text>
|
||||
<text text-anchor="start" x="319.76" y="-390.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  Result<(RowId, Vec<Value>), StorageError></text>
|
||||
<text text-anchor="start" x="319.76" y="-361.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">> + 'a></text>
|
||||
</g>
|
||||
<!-- storage_node->row_stream_node -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>storage_node->row_stream_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M386.3,-533.09C396.67,-514.9 406.96,-496.84 416.34,-480.36"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="419.26,-482.32 421.17,-471.9 413.18,-478.85 419.26,-482.32"/>
|
||||
<text text-anchor="middle" x="437.14" y="-499.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">scan_iter yields
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M386.3,-533.09C396.67,-514.9 406.96,-496.84 416.34,-480.36"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="419.26,-482.32 421.17,-471.9 413.18,-478.85 419.26,-482.32"/>
|
||||
<text text-anchor="middle" x="437.14" y="-499.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">scan_iter yields</text>
|
||||
</g>
|
||||
<!-- committed_tx_node -->
|
||||
<g id="node3" class="node">
|
||||
<title>committed_tx_node</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M344.88,-268C344.88,-268 113.13,-268 113.13,-268 107.13,-268 101.13,-262 101.13,-256 101.13,-256 101.13,-185 101.13,-185 101.13,-179 107.13,-173 113.13,-173 113.13,-173 344.88,-173 344.88,-173 350.88,-173 356.88,-179 356.88,-185 356.88,-185 356.88,-256 356.88,-256 356.88,-262 350.88,-268 344.88,-268"/>
|
||||
<text text-anchor="start" x="160.38" y="-247.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">CommittedTx
|
||||
</text>
|
||||
<text text-anchor="start" x="248.13" y="-247.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (struct)
|
||||
</text>
|
||||
<text text-anchor="start" x="113.13" y="-217.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
resolutions: HashMap<RowId, RowId>
|
||||
</text>
|
||||
<text text-anchor="start" x="113.13" y="-188.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
resolve(pending) -> RowId
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M344.88,-268C344.88,-268 113.13,-268 113.13,-268 107.13,-268 101.13,-262 101.13,-256 101.13,-256 101.13,-185 101.13,-185 101.13,-179 107.13,-173 113.13,-173 113.13,-173 344.88,-173 344.88,-173 350.88,-173 356.88,-179 356.88,-185 356.88,-185 356.88,-256 356.88,-256 356.88,-262 350.88,-268 344.88,-268"/>
|
||||
<text text-anchor="start" x="160.38" y="-247.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">CommittedTx</text>
|
||||
<text text-anchor="start" x="248.13" y="-247.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (struct)</text>
|
||||
<text text-anchor="start" x="113.13" y="-217.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">resolutions: HashMap<RowId, RowId></text>
|
||||
<text text-anchor="start" x="113.13" y="-188.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">resolve(pending) -> RowId</text>
|
||||
</g>
|
||||
<!-- transaction_node->committed_tx_node -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>transaction_node->committed_tx_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M184.8,-345.75C192.41,-324.37 200.91,-300.45 208.42,-279.36"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="211.69,-280.6 211.75,-270 205.1,-278.25 211.69,-280.6"/>
|
||||
<text text-anchor="middle" x="228.73" y="-312.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">commit() yields
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M184.8,-345.75C192.41,-324.37 200.91,-300.45 208.42,-279.36"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="211.69,-280.6 211.75,-270 205.1,-278.25 211.69,-280.6"/>
|
||||
<text text-anchor="middle" x="228.73" y="-312.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">commit() yields</text>
|
||||
</g>
|
||||
<!-- row_id_node -->
|
||||
<g id="node5" class="node">
|
||||
<title>row_id_node</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M501.63,-95C501.63,-95 298.38,-95 298.38,-95 292.38,-95 286.38,-89 286.38,-83 286.38,-83 286.38,-12 286.38,-12 286.38,-6 292.38,0 298.38,0 298.38,0 501.63,0 501.63,0 507.63,0 513.63,-6 513.63,-12 513.63,-12 513.63,-83 513.63,-83 513.63,-89 507.63,-95 501.63,-95"/>
|
||||
<text text-anchor="start" x="354.63" y="-74.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">RowId
|
||||
</text>
|
||||
<text text-anchor="start" x="395.88" y="-74.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (struct)
|
||||
</text>
|
||||
<text text-anchor="start" x="298.38" y="-44.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
SmallVec<[u8; 36]> (opaque)
|
||||
</text>
|
||||
<text text-anchor="start" x="298.38" y="-15.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
new(bytes), as_bytes(), from(u64)
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M501.63,-95C501.63,-95 298.38,-95 298.38,-95 292.38,-95 286.38,-89 286.38,-83 286.38,-83 286.38,-12 286.38,-12 286.38,-6 292.38,0 298.38,0 298.38,0 501.63,0 501.63,0 507.63,0 513.63,-6 513.63,-12 513.63,-12 513.63,-83 513.63,-83 513.63,-89 507.63,-95 501.63,-95"/>
|
||||
<text text-anchor="start" x="354.63" y="-74.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">RowId</text>
|
||||
<text text-anchor="start" x="395.88" y="-74.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (struct)</text>
|
||||
<text text-anchor="start" x="298.38" y="-44.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">SmallVec<[u8; 36]> (opaque)</text>
|
||||
<text text-anchor="start" x="298.38" y="-15.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">new(bytes), as_bytes(), from(u64)</text>
|
||||
</g>
|
||||
<!-- transaction_node->row_id_node -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>transaction_node->row_id_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M69.82,-345.56C50.18,-327.75 32.13,-306.52 21.26,-282.5 -1.47,-232.29 -11.84,-202.57 21.26,-158.5 52.05,-117.48 176.52,-86.8 274.48,-68.4"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="275,-71.87 284.19,-66.61 273.72,-64.98 275,-71.87"/>
|
||||
<text text-anchor="middle" x="48.63" y="-218.7" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">insert() yields
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M69.82,-345.56C50.18,-327.75 32.13,-306.52 21.26,-282.5 -1.47,-232.29 -11.84,-202.57 21.26,-158.5 52.05,-117.48 176.52,-86.8 274.48,-68.4"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="275,-71.87 284.19,-66.61 273.72,-64.98 275,-71.87"/>
|
||||
<text text-anchor="middle" x="48.63" y="-218.7" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">insert() yields</text>
|
||||
</g>
|
||||
<!-- committed_tx_node->row_id_node -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>committed_tx_node->row_id_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M275.73,-172.77C297.13,-151.37 322.65,-125.86 344.97,-103.53"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="347.16,-106.3 351.75,-96.75 342.21,-101.35 347.16,-106.3"/>
|
||||
<text text-anchor="middle" x="355.87" y="-124.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">resolve() yields
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M275.73,-172.77C297.13,-151.37 322.65,-125.86 344.97,-103.53"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="347.16,-106.3 351.75,-96.75 342.21,-101.35 347.16,-106.3"/>
|
||||
<text text-anchor="middle" x="355.87" y="-124.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">resolve() yields</text>
|
||||
</g>
|
||||
<!-- row_stream_node->row_id_node -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>row_stream_node->row_id_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M447.27,-345.78C436.64,-278.9 419.73,-172.56 409.28,-106.84"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="412.78,-106.58 407.76,-97.25 405.87,-107.68 412.78,-106.58"/>
|
||||
<text text-anchor="middle" x="471.44" y="-218.7" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Item = (RowId, _)
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M447.27,-345.78C436.64,-278.9 419.73,-172.56 409.28,-106.84"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="412.78,-106.58 407.76,-97.25 405.87,-107.68 412.78,-106.58"/>
|
||||
<text text-anchor="middle" x="471.44" y="-218.7" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Item = (RowId, _)</text>
|
||||
</g>
|
||||
<!-- value_node -->
|
||||
<g id="node6" class="node">
|
||||
<title>value_node</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M675.51,-282.5C675.51,-282.5 588.51,-282.5 588.51,-282.5 582.51,-282.5 576.51,-276.5 576.51,-270.5 576.51,-270.5 576.51,-170.5 576.51,-170.5 576.51,-164.5 582.51,-158.5 588.51,-158.5 588.51,-158.5 675.51,-158.5 675.51,-158.5 681.51,-158.5 687.51,-164.5 687.51,-170.5 687.51,-170.5 687.51,-270.5 687.51,-270.5 687.51,-276.5 681.51,-282.5 675.51,-282.5"/>
|
||||
<text text-anchor="start" x="588.51" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Value
|
||||
</text>
|
||||
<text text-anchor="start" x="624.51" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (enum)
|
||||
</text>
|
||||
<text text-anchor="start" x="588.51" y="-232.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Int(i64)
|
||||
</text>
|
||||
<text text-anchor="start" x="588.51" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Str(String)
|
||||
</text>
|
||||
<text text-anchor="start" x="588.51" y="-174.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Id(RowId)
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M675.51,-282.5C675.51,-282.5 588.51,-282.5 588.51,-282.5 582.51,-282.5 576.51,-276.5 576.51,-270.5 576.51,-270.5 576.51,-170.5 576.51,-170.5 576.51,-164.5 582.51,-158.5 588.51,-158.5 588.51,-158.5 675.51,-158.5 675.51,-158.5 681.51,-158.5 687.51,-164.5 687.51,-170.5 687.51,-170.5 687.51,-270.5 687.51,-270.5 687.51,-276.5 681.51,-282.5 675.51,-282.5"/>
|
||||
<text text-anchor="start" x="588.51" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Value</text>
|
||||
<text text-anchor="start" x="624.51" y="-262.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (enum)</text>
|
||||
<text text-anchor="start" x="588.51" y="-232.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Int(i64)</text>
|
||||
<text text-anchor="start" x="588.51" y="-203.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Str(String)</text>
|
||||
<text text-anchor="start" x="588.51" y="-174.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Id(RowId)</text>
|
||||
</g>
|
||||
<!-- row_stream_node->value_node -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>row_stream_node->value_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M514.79,-345.75C531.89,-327.63 550.7,-307.69 568.15,-289.18"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="570.5,-291.79 574.82,-282.12 565.41,-286.99 570.5,-291.79"/>
|
||||
<text text-anchor="middle" x="595.5" y="-312.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Item = (_, Vec<Value>)
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M514.79,-345.75C531.89,-327.63 550.7,-307.69 568.15,-289.18"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="570.5,-291.79 574.82,-282.12 565.41,-286.99 570.5,-291.79"/>
|
||||
<text text-anchor="middle" x="595.5" y="-312.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Item = (_, Vec<Value>)</text>
|
||||
</g>
|
||||
<!-- value_node->row_id_node -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>value_node->row_id_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M576.14,-178.32C545.13,-155.47 506.2,-126.78 472.83,-102.18"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="475.17,-99.55 465.04,-96.44 471.01,-105.19 475.17,-99.55"/>
|
||||
<text text-anchor="middle" x="528.92" y="-124.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Id(RowId)
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M576.14,-178.32C545.13,-155.47 506.2,-126.78 472.83,-102.18"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="475.17,-99.55 465.04,-96.44 471.01,-105.19 475.17,-99.55"/>
|
||||
<text text-anchor="middle" x="528.92" y="-124.95" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Id(RowId)</text>
|
||||
</g>
|
||||
<!-- table_node -->
|
||||
<g id="node7" class="node">
|
||||
<title>table_node</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M815.88,-455.5C815.88,-455.5 668.13,-455.5 668.13,-455.5 662.13,-455.5 656.13,-449.5 656.13,-443.5 656.13,-443.5 656.13,-372.5 656.13,-372.5 656.13,-366.5 662.13,-360.5 668.13,-360.5 668.13,-360.5 815.88,-360.5 815.88,-360.5 821.88,-360.5 827.88,-366.5 827.88,-372.5 827.88,-372.5 827.88,-443.5 827.88,-443.5 827.88,-449.5 821.88,-455.5 815.88,-455.5"/>
|
||||
<text text-anchor="start" x="700.01" y="-435.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Table
|
||||
</text>
|
||||
<text text-anchor="start" x="734.51" y="-435.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (struct)
|
||||
</text>
|
||||
<text text-anchor="start" x="668.13" y="-405.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
arity: usize
|
||||
</text>
|
||||
<text text-anchor="start" x="668.13" y="-376.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
rows: Vec<Vec<Value>>
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M815.88,-455.5C815.88,-455.5 668.13,-455.5 668.13,-455.5 662.13,-455.5 656.13,-449.5 656.13,-443.5 656.13,-443.5 656.13,-372.5 656.13,-372.5 656.13,-366.5 662.13,-360.5 668.13,-360.5 668.13,-360.5 815.88,-360.5 815.88,-360.5 821.88,-360.5 827.88,-366.5 827.88,-372.5 827.88,-372.5 827.88,-443.5 827.88,-443.5 827.88,-449.5 821.88,-455.5 815.88,-455.5"/>
|
||||
<text text-anchor="start" x="700.01" y="-435.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Table</text>
|
||||
<text text-anchor="start" x="734.51" y="-435.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (struct)</text>
|
||||
<text text-anchor="start" x="668.13" y="-405.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">arity: usize</text>
|
||||
<text text-anchor="start" x="668.13" y="-376.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">rows: Vec<Vec<Value>></text>
|
||||
</g>
|
||||
<!-- table_node->value_node -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>table_node->value_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M714.25,-360.19C702.11,-339.72 687.59,-315.24 674.17,-292.6"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="677.32,-291.06 669.21,-284.24 671.3,-294.63 677.32,-291.06"/>
|
||||
<text text-anchor="middle" x="727.43" y="-312.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Vec<Vec<Value>>
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M714.25,-360.19C702.11,-339.72 687.59,-315.24 674.17,-292.6"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="677.32,-291.06 669.21,-284.24 671.3,-294.63 677.32,-291.06"/>
|
||||
<text text-anchor="middle" x="727.43" y="-312.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Vec<Vec<Value>></text>
|
||||
</g>
|
||||
<!-- storage_error_node -->
|
||||
<g id="node8" class="node">
|
||||
<title>storage_error_node</title>
|
||||
<path fill="#fbe9e7" stroke="#e64a19" stroke-width="1.5"
|
||||
d="M718.76,-1106C718.76,-1106 531.26,-1106 531.26,-1106 525.26,-1106 519.26,-1100 519.26,-1094 519.26,-1094 519.26,-878 519.26,-878 519.26,-872 525.26,-866 531.26,-866 531.26,-866 718.76,-866 718.76,-866 724.76,-866 730.76,-872 730.76,-878 730.76,-878 730.76,-1094 730.76,-1094 730.76,-1100 724.76,-1106 718.76,-1106"/>
|
||||
<text text-anchor="start" x="557.51" y="-1085.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">StorageError
|
||||
</text>
|
||||
<text text-anchor="start" x="641.51" y="-1085.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (enum)
|
||||
</text>
|
||||
<text text-anchor="start" x="531.26" y="-1055.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
RelationNotFound(String)
|
||||
</text>
|
||||
<text text-anchor="start" x="531.26" y="-1026.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
RelationExists(String)
|
||||
</text>
|
||||
<text text-anchor="start" x="531.26" y="-997.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
ArityMismatch { expected, got }
|
||||
</text>
|
||||
<text text-anchor="start" x="531.26" y="-968.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Validation(String)
|
||||
</text>
|
||||
<text text-anchor="start" x="531.26" y="-939.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Decode(CodecError)
|
||||
</text>
|
||||
<text text-anchor="start" x="531.26" y="-910.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Unsupported(&'static str)
|
||||
</text>
|
||||
<text text-anchor="start" x="531.26" y="-881.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
Backend(Box<dyn Error>)
|
||||
</text>
|
||||
<path fill="#fbe9e7" stroke="#e64a19" stroke-width="1.5" d="M718.76,-1106C718.76,-1106 531.26,-1106 531.26,-1106 525.26,-1106 519.26,-1100 519.26,-1094 519.26,-1094 519.26,-878 519.26,-878 519.26,-872 525.26,-866 531.26,-866 531.26,-866 718.76,-866 718.76,-866 724.76,-866 730.76,-872 730.76,-878 730.76,-878 730.76,-1094 730.76,-1094 730.76,-1100 724.76,-1106 718.76,-1106"/>
|
||||
<text text-anchor="start" x="557.51" y="-1085.7" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">StorageError</text>
|
||||
<text text-anchor="start" x="641.51" y="-1085.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (enum)</text>
|
||||
<text text-anchor="start" x="531.26" y="-1055.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">RelationNotFound(String)</text>
|
||||
<text text-anchor="start" x="531.26" y="-1026.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">RelationExists(String)</text>
|
||||
<text text-anchor="start" x="531.26" y="-997.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">ArityMismatch { expected, got }</text>
|
||||
<text text-anchor="start" x="531.26" y="-968.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Validation(String)</text>
|
||||
<text text-anchor="start" x="531.26" y="-939.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Decode(CodecError)</text>
|
||||
<text text-anchor="start" x="531.26" y="-910.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Unsupported(&'static str)</text>
|
||||
<text text-anchor="start" x="531.26" y="-881.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">Backend(Box<dyn Error>)</text>
|
||||
</g>
|
||||
<!-- codec_error_node -->
|
||||
<g id="node9" class="node">
|
||||
<title>codec_error_node</title>
|
||||
<path fill="#fbe9e7" stroke="#e64a19" stroke-width="1.5"
|
||||
d="M736.76,-744.5C736.76,-744.5 513.26,-744.5 513.26,-744.5 507.26,-744.5 501.26,-738.5 501.26,-732.5 501.26,-732.5 501.26,-603.5 501.26,-603.5 501.26,-597.5 507.26,-591.5 513.26,-591.5 513.26,-591.5 736.76,-591.5 736.76,-591.5 742.76,-591.5 748.76,-597.5 748.76,-603.5 748.76,-603.5 748.76,-732.5 748.76,-732.5 748.76,-738.5 742.76,-744.5 736.76,-744.5"/>
|
||||
<text text-anchor="start" x="562.38" y="-724.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">CodecError
|
||||
</text>
|
||||
<text text-anchor="start" x="636.63" y="-724.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (enum)
|
||||
</text>
|
||||
<text text-anchor="start" x="513.26" y="-694.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
UnexpectedEof
|
||||
</text>
|
||||
<text text-anchor="start" x="513.26" y="-665.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
UnknownTag(u8)
|
||||
</text>
|
||||
<text text-anchor="start" x="513.26" y="-636.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
LengthOverrun { declared, available }
|
||||
</text>
|
||||
<text text-anchor="start" x="513.26" y="-607.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
InvalidUtf8
|
||||
</text>
|
||||
<path fill="#fbe9e7" stroke="#e64a19" stroke-width="1.5" d="M736.76,-744.5C736.76,-744.5 513.26,-744.5 513.26,-744.5 507.26,-744.5 501.26,-738.5 501.26,-732.5 501.26,-732.5 501.26,-603.5 501.26,-603.5 501.26,-597.5 507.26,-591.5 513.26,-591.5 513.26,-591.5 736.76,-591.5 736.76,-591.5 742.76,-591.5 748.76,-597.5 748.76,-603.5 748.76,-603.5 748.76,-732.5 748.76,-732.5 748.76,-738.5 742.76,-744.5 736.76,-744.5"/>
|
||||
<text text-anchor="start" x="562.38" y="-724.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">CodecError</text>
|
||||
<text text-anchor="start" x="636.63" y="-724.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (enum)</text>
|
||||
<text text-anchor="start" x="513.26" y="-694.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">UnexpectedEof</text>
|
||||
<text text-anchor="start" x="513.26" y="-665.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">UnknownTag(u8)</text>
|
||||
<text text-anchor="start" x="513.26" y="-636.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">LengthOverrun { declared, available }</text>
|
||||
<text text-anchor="start" x="513.26" y="-607.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">InvalidUtf8</text>
|
||||
</g>
|
||||
<!-- storage_error_node->codec_error_node -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>storage_error_node->codec_error_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2"
|
||||
d="M625.01,-865.53C625.01,-829.26 625.01,-790.11 625.01,-756.36"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="628.51,-756.48 625.01,-746.48 621.51,-756.48 628.51,-756.48"/>
|
||||
<text text-anchor="middle" x="667.76" y="-832.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">Decode(CodecError)
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" d="M625.01,-865.53C625.01,-829.26 625.01,-790.11 625.01,-756.36"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="628.51,-756.48 625.01,-746.48 621.51,-756.48 628.51,-756.48"/>
|
||||
<text text-anchor="middle" x="667.76" y="-832.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">Decode(CodecError)</text>
|
||||
</g>
|
||||
<!-- adapters_node -->
|
||||
<g id="node10" class="node">
|
||||
<title>adapters_node</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M418.38,-1091.5C418.38,-1091.5 201.63,-1091.5 201.63,-1091.5 195.63,-1091.5 189.63,-1085.5 189.63,-1079.5 189.63,-1079.5 189.63,-892.5 189.63,-892.5 189.63,-886.5 195.63,-880.5 201.63,-880.5 201.63,-880.5 418.38,-880.5 418.38,-880.5 424.38,-880.5 430.38,-886.5 430.38,-892.5 430.38,-892.5 430.38,-1079.5 430.38,-1079.5 430.38,-1085.5 424.38,-1091.5 418.38,-1091.5"/>
|
||||
<text text-anchor="start" x="234.26" y="-1071.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Adapters
|
||||
</text>
|
||||
<text text-anchor="start" x="292.76" y="-1071.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
 (impl Storage)
|
||||
</text>
|
||||
<text text-anchor="start" x="201.63" y="-1041.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
MemoryStorage
|
||||
</text>
|
||||
<text text-anchor="start" x="201.63" y="-1012.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
SqliteStorage  (feat sqlite)
|
||||
</text>
|
||||
<text text-anchor="start" x="201.63" y="-983.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
RedbStorage  (feat redb)
|
||||
</text>
|
||||
<text text-anchor="start" x="201.63" y="-954.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
FjallStorage  (feat fjall)
|
||||
</text>
|
||||
<text text-anchor="start" x="201.63" y="-925.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
LmdbStorage  (feat lmdb)
|
||||
</text>
|
||||
<text text-anchor="start" x="201.63" y="-896.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
GeomergeStorage  (feat geomerge)
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M418.38,-1091.5C418.38,-1091.5 201.63,-1091.5 201.63,-1091.5 195.63,-1091.5 189.63,-1085.5 189.63,-1079.5 189.63,-1079.5 189.63,-892.5 189.63,-892.5 189.63,-886.5 195.63,-880.5 201.63,-880.5 201.63,-880.5 418.38,-880.5 418.38,-880.5 424.38,-880.5 430.38,-886.5 430.38,-892.5 430.38,-892.5 430.38,-1079.5 430.38,-1079.5 430.38,-1085.5 424.38,-1091.5 418.38,-1091.5"/>
|
||||
<text text-anchor="start" x="234.26" y="-1071.2" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Adapters</text>
|
||||
<text text-anchor="start" x="292.76" y="-1071.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  (impl Storage)</text>
|
||||
<text text-anchor="start" x="201.63" y="-1041.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">MemoryStorage</text>
|
||||
<text text-anchor="start" x="201.63" y="-1012.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">SqliteStorage  (feat sqlite)</text>
|
||||
<text text-anchor="start" x="201.63" y="-983.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">RedbStorage  (feat redb)</text>
|
||||
<text text-anchor="start" x="201.63" y="-954.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">FjallStorage  (feat fjall)</text>
|
||||
<text text-anchor="start" x="201.63" y="-925.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">LmdbStorage  (feat lmdb)</text>
|
||||
<text text-anchor="start" x="201.63" y="-896.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">GeomergeStorage  (feat geomerge)</text>
|
||||
</g>
|
||||
<!-- adapters_node->storage_node -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>adapters_node->storage_node</title>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M310.01,-880.22C310.01,-859.1 310.01,-836.56 310.01,-814.34"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2"
|
||||
points="313.51,-814.58 310.01,-804.58 306.51,-814.58 313.51,-814.58"/>
|
||||
<text text-anchor="middle" x="318.63" y="-832.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">impl
|
||||
</text>
|
||||
<path fill="none" stroke="#333333" stroke-width="1.2" stroke-dasharray="5,2" d="M310.01,-880.22C310.01,-859.1 310.01,-836.56 310.01,-814.34"/>
|
||||
<polygon fill="#333333" stroke="#333333" stroke-width="1.2" points="313.51,-814.58 310.01,-804.58 306.51,-814.58 313.51,-814.58"/>
|
||||
<text text-anchor="middle" x="318.63" y="-832.45" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">impl</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 19 KiB |
@ -5,355 +5,222 @@
|
||||
-->
|
||||
<!-- Title: StorageWorkflow Pages: 1 -->
|
||||
<svg width="2196pt" height="573pt"
|
||||
viewBox="0.00 0.00 2195.75 573.00" xmlns="http://www.w3.org/2000/svg">
|
||||
viewBox="0.00 0.00 2195.75 573.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 569)">
|
||||
<title>StorageWorkflow</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-569 2191.75,-569 2191.75,4 -4,4"/>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_inputs</title>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2"
|
||||
points="23.5,-254 23.5,-557 211.75,-557 211.75,-254 23.5,-254"/>
|
||||
<text text-anchor="middle" x="117.62" y="-539.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#555555">Inputs
|
||||
</text>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2" points="23.5,-254 23.5,-557 211.75,-557 211.75,-254 23.5,-254"/>
|
||||
<text text-anchor="middle" x="117.62" y="-539.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#555555">Inputs</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_setup</title>
|
||||
<polygon fill="white" stroke="#9c27b0" stroke-dasharray="5,2"
|
||||
points="8,-65 8,-246 524.75,-246 524.75,-65 8,-65"/>
|
||||
<text text-anchor="middle" x="266.38" y="-228.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#7b1fa2">Setup  (open backend, declare relations)
|
||||
</text>
|
||||
<polygon fill="white" stroke="#9c27b0" stroke-dasharray="5,2" points="8,-65 8,-246 524.75,-246 524.75,-65 8,-65"/>
|
||||
<text text-anchor="middle" x="266.38" y="-228.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#7b1fa2">Setup  (open backend, declare relations)</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_write</title>
|
||||
<polygon fill="white" stroke="#4caf50" stroke-dasharray="5,2"
|
||||
points="563.75,-323 563.75,-532 1882.5,-532 1882.5,-323 563.75,-323"/>
|
||||
<text text-anchor="middle" x="1223.12" y="-514.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#388e3c">Write  (atomic batch via Transaction)
|
||||
</text>
|
||||
<polygon fill="white" stroke="#4caf50" stroke-dasharray="5,2" points="563.75,-323 563.75,-532 1882.5,-532 1882.5,-323 563.75,-323"/>
|
||||
<text text-anchor="middle" x="1223.12" y="-514.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#388e3c">Write  (atomic batch via Transaction)</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_read</title>
|
||||
<polygon fill="white" stroke="#ff9800" stroke-dasharray="5,2"
|
||||
points="1568.25,-8 1568.25,-315 1841.75,-315 1841.75,-8 1568.25,-8"/>
|
||||
<text text-anchor="middle" x="1705" y="-297.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#f57c00">Read
|
||||
</text>
|
||||
<polygon fill="white" stroke="#ff9800" stroke-dasharray="5,2" points="1568.25,-8 1568.25,-315 1841.75,-315 1841.75,-8 1568.25,-8"/>
|
||||
<text text-anchor="middle" x="1705" y="-297.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#f57c00">Read</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_output</title>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2"
|
||||
points="1965.25,-117 1965.25,-306 2179.75,-306 2179.75,-117 1965.25,-117"/>
|
||||
<text text-anchor="middle" x="2072.5" y="-288.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00"
|
||||
fill="#555555">Output
|
||||
</text>
|
||||
<polygon fill="white" stroke="#888888" stroke-dasharray="5,2" points="1965.25,-117 1965.25,-306 2179.75,-306 2179.75,-117 1965.25,-117"/>
|
||||
<text text-anchor="middle" x="2072.5" y="-288.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00" fill="#555555">Output</text>
|
||||
</g>
|
||||
<!-- schema -->
|
||||
<g id="node1" class="node">
|
||||
<title>schema</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M181.75,-366.12C181.75,-366.12 53.5,-366.12 53.5,-366.12 47.5,-366.12 41.5,-360.12 41.5,-354.12 41.5,-354.12 41.5,-283.88 41.5,-283.88 41.5,-277.88 47.5,-271.88 53.5,-271.88 53.5,-271.88 181.75,-271.88 181.75,-271.88 187.75,-271.88 193.75,-277.88 193.75,-283.88 193.75,-283.88 193.75,-354.12 193.75,-354.12 193.75,-360.12 187.75,-366.12 181.75,-366.12"/>
|
||||
<text text-anchor="start" x="91.38" y="-345.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Schema
|
||||
</text>
|
||||
<text text-anchor="start" x="53.5" y="-316.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
relation name
|
||||
</text>
|
||||
<text text-anchor="start" x="53.5" y="-287.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
arity (column count)
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M181.75,-366.12C181.75,-366.12 53.5,-366.12 53.5,-366.12 47.5,-366.12 41.5,-360.12 41.5,-354.12 41.5,-354.12 41.5,-283.88 41.5,-283.88 41.5,-277.88 47.5,-271.88 53.5,-271.88 53.5,-271.88 181.75,-271.88 181.75,-271.88 187.75,-271.88 193.75,-277.88 193.75,-283.88 193.75,-283.88 193.75,-354.12 193.75,-354.12 193.75,-360.12 187.75,-366.12 181.75,-366.12"/>
|
||||
<text text-anchor="start" x="91.38" y="-345.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Schema</text>
|
||||
<text text-anchor="start" x="53.5" y="-316.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• relation name</text>
|
||||
<text text-anchor="start" x="53.5" y="-287.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• arity (column count)</text>
|
||||
</g>
|
||||
<!-- create_relation -->
|
||||
<g id="node4" class="node">
|
||||
<title>create_relation</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M498.75,-184C498.75,-184 292.25,-184 292.25,-184 286.25,-184 280.25,-178 280.25,-172 280.25,-172 280.25,-160 280.25,-160 280.25,-154 286.25,-148 292.25,-148 292.25,-148 498.75,-148 498.75,-148 504.75,-148 510.75,-154 510.75,-160 510.75,-160 510.75,-172 510.75,-172 510.75,-178 504.75,-184 498.75,-184"/>
|
||||
<text text-anchor="middle" x="395.5" y="-163.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
storage.create_relation(name, arity)
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M498.75,-184C498.75,-184 292.25,-184 292.25,-184 286.25,-184 280.25,-178 280.25,-172 280.25,-172 280.25,-160 280.25,-160 280.25,-154 286.25,-148 292.25,-148 292.25,-148 498.75,-148 498.75,-148 504.75,-148 510.75,-154 510.75,-160 510.75,-160 510.75,-172 510.75,-172 510.75,-178 504.75,-184 498.75,-184"/>
|
||||
<text text-anchor="middle" x="395.5" y="-163.2" font-family="Helvetica,Arial,sans-serif" font-size="14.00">storage.create_relation(name, arity)</text>
|
||||
</g>
|
||||
<!-- schema->create_relation -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>schema->create_relation</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2"
|
||||
d="M194.01,-277.19C244.45,-249.22 309.06,-213.38 351.1,-190.07"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2"
|
||||
points="352.64,-193.22 359.69,-185.31 349.24,-187.1 352.64,-193.22"/>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" d="M194.01,-277.19C244.45,-249.22 309.06,-213.38 351.1,-190.07"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="352.64,-193.22 359.69,-185.31 349.24,-187.1 352.64,-193.22"/>
|
||||
</g>
|
||||
<!-- row_data -->
|
||||
<g id="node2" class="node">
|
||||
<title>row_data</title>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5"
|
||||
d="M178.75,-510.12C178.75,-510.12 56.5,-510.12 56.5,-510.12 50.5,-510.12 44.5,-504.12 44.5,-498.12 44.5,-498.12 44.5,-427.88 44.5,-427.88 44.5,-421.88 50.5,-415.88 56.5,-415.88 56.5,-415.88 178.75,-415.88 178.75,-415.88 184.75,-415.88 190.75,-421.88 190.75,-427.88 190.75,-427.88 190.75,-498.12 190.75,-498.12 190.75,-504.12 184.75,-510.12 178.75,-510.12"/>
|
||||
<text text-anchor="start" x="86.5" y="-489.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Row Data
|
||||
</text>
|
||||
<text text-anchor="start" x="56.5" y="-460.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
Vec<Value>
|
||||
</text>
|
||||
<text text-anchor="start" x="56.5" y="-431.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">•
|
||||
Int / Str / Id(RowId)
|
||||
</text>
|
||||
<path fill="#e8f4fd" stroke="#2196f3" stroke-width="1.5" d="M178.75,-510.12C178.75,-510.12 56.5,-510.12 56.5,-510.12 50.5,-510.12 44.5,-504.12 44.5,-498.12 44.5,-498.12 44.5,-427.88 44.5,-427.88 44.5,-421.88 50.5,-415.88 56.5,-415.88 56.5,-415.88 178.75,-415.88 178.75,-415.88 184.75,-415.88 190.75,-421.88 190.75,-427.88 190.75,-427.88 190.75,-498.12 190.75,-498.12 190.75,-504.12 184.75,-510.12 178.75,-510.12"/>
|
||||
<text text-anchor="start" x="86.5" y="-489.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Row Data</text>
|
||||
<text text-anchor="start" x="56.5" y="-460.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• Vec<Value></text>
|
||||
<text text-anchor="start" x="56.5" y="-431.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• Int / Str / Id(RowId)</text>
|
||||
</g>
|
||||
<!-- tx_ops -->
|
||||
<g id="node6" class="node">
|
||||
<title>tx_ops</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M1020.75,-474.62C1020.75,-474.62 824.25,-474.62 824.25,-474.62 818.25,-474.62 812.25,-468.62 812.25,-462.62 812.25,-462.62 812.25,-363.38 812.25,-363.38 812.25,-357.38 818.25,-351.38 824.25,-351.38 824.25,-351.38 1020.75,-351.38 1020.75,-351.38 1026.75,-351.38 1032.75,-357.38 1032.75,-363.38 1032.75,-363.38 1032.75,-462.62 1032.75,-462.62 1032.75,-468.62 1026.75,-474.62 1020.75,-474.62"/>
|
||||
<text text-anchor="start" x="862.88" y="-454.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">tx.insert / tx.delete
|
||||
</text>
|
||||
<text text-anchor="start" x="824.25" y="-425.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
• insert yields pending RowId
|
||||
</text>
|
||||
<text text-anchor="start" x="824.25" y="-396.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
• pending RowIds reused as FKs
|
||||
</text>
|
||||
<text text-anchor="start" x="824.25" y="-367.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
• delete by RowId
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M1020.75,-474.62C1020.75,-474.62 824.25,-474.62 824.25,-474.62 818.25,-474.62 812.25,-468.62 812.25,-462.62 812.25,-462.62 812.25,-363.38 812.25,-363.38 812.25,-357.38 818.25,-351.38 824.25,-351.38 824.25,-351.38 1020.75,-351.38 1020.75,-351.38 1026.75,-351.38 1032.75,-357.38 1032.75,-363.38 1032.75,-363.38 1032.75,-462.62 1032.75,-462.62 1032.75,-468.62 1026.75,-474.62 1020.75,-474.62"/>
|
||||
<text text-anchor="start" x="862.88" y="-454.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">tx.insert / tx.delete</text>
|
||||
<text text-anchor="start" x="824.25" y="-425.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• insert yields pending RowId</text>
|
||||
<text text-anchor="start" x="824.25" y="-396.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• pending RowIds reused as FKs</text>
|
||||
<text text-anchor="start" x="824.25" y="-367.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• delete by RowId</text>
|
||||
</g>
|
||||
<!-- row_data->tx_ops -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>row_data->tx_ops</title>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M190.94,-458.5C328.87,-449.91 630.86,-431.1 800.21,-420.55"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2"
|
||||
points="800.34,-424.05 810.11,-419.94 799.91,-417.07 800.34,-424.05"/>
|
||||
<path fill="none" stroke="#2196f3" stroke-width="1.2" stroke-dasharray="5,2" d="M190.94,-458.5C328.87,-449.91 630.86,-431.1 800.21,-420.55"/>
|
||||
<polygon fill="#2196f3" stroke="#2196f3" stroke-width="1.2" points="800.34,-424.05 810.11,-419.94 799.91,-417.07 800.34,-424.05"/>
|
||||
</g>
|
||||
<!-- open_backend -->
|
||||
<g id="node3" class="node">
|
||||
<title>open_backend</title>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5"
|
||||
d="M201.25,-202.62C201.25,-202.62 34,-202.62 34,-202.62 28,-202.62 22,-196.62 22,-190.62 22,-190.62 22,-91.38 22,-91.38 22,-85.38 28,-79.38 34,-79.38 34,-79.38 201.25,-79.38 201.25,-79.38 207.25,-79.38 213.25,-85.38 213.25,-91.38 213.25,-91.38 213.25,-190.62 213.25,-190.62 213.25,-196.62 207.25,-202.62 201.25,-202.62"/>
|
||||
<text text-anchor="start" x="70" y="-182.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Open Backend
|
||||
</text>
|
||||
<text text-anchor="start" x="34" y="-153.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
MemoryStorage::new() /
|
||||
</text>
|
||||
<text text-anchor="start" x="34" y="-124.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
SqliteStorage::open(path) /
|
||||
</text>
|
||||
<text text-anchor="start" x="34" y="-95.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
FjallStorage::open(path) / ...
|
||||
</text>
|
||||
<path fill="#f3e5f5" stroke="#9c27b0" stroke-width="1.5" d="M201.25,-202.62C201.25,-202.62 34,-202.62 34,-202.62 28,-202.62 22,-196.62 22,-190.62 22,-190.62 22,-91.38 22,-91.38 22,-85.38 28,-79.38 34,-79.38 34,-79.38 201.25,-79.38 201.25,-79.38 207.25,-79.38 213.25,-85.38 213.25,-91.38 213.25,-91.38 213.25,-190.62 213.25,-190.62 213.25,-196.62 207.25,-202.62 201.25,-202.62"/>
|
||||
<text text-anchor="start" x="70" y="-182.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Open Backend</text>
|
||||
<text text-anchor="start" x="34" y="-153.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">MemoryStorage::new() /</text>
|
||||
<text text-anchor="start" x="34" y="-124.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">SqliteStorage::open(path) /</text>
|
||||
<text text-anchor="start" x="34" y="-95.08" font-family="Helvetica,Arial,sans-serif" font-size="14.00">FjallStorage::open(path) / ...</text>
|
||||
</g>
|
||||
<!-- open_backend->create_relation -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>open_backend->create_relation</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2"
|
||||
d="M213.57,-149.6C231.17,-151.2 249.8,-152.89 268.16,-154.55"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="267.78,-158.03 278.05,-155.45 268.41,-151.06 267.78,-158.03"/>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" d="M213.57,-149.6C231.17,-151.2 249.8,-152.89 268.16,-154.55"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="267.78,-158.03 278.05,-155.45 268.41,-151.06 267.78,-158.03"/>
|
||||
</g>
|
||||
<!-- begin_tx -->
|
||||
<g id="node5" class="node">
|
||||
<title>begin_tx</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M733.25,-387C733.25,-387 589.75,-387 589.75,-387 583.75,-387 577.75,-381 577.75,-375 577.75,-375 577.75,-349 577.75,-349 577.75,-343 583.75,-337 589.75,-337 589.75,-337 733.25,-337 733.25,-337 739.25,-337 745.25,-343 745.25,-349 745.25,-349 745.25,-375 745.25,-375 745.25,-381 739.25,-387 733.25,-387"/>
|
||||
<text text-anchor="middle" x="661.5" y="-369.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
storage.transaction()
|
||||
</text>
|
||||
<text text-anchor="middle" x="661.5" y="-348.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
-> Box<dyn Transaction>
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M733.25,-387C733.25,-387 589.75,-387 589.75,-387 583.75,-387 577.75,-381 577.75,-375 577.75,-375 577.75,-349 577.75,-349 577.75,-343 583.75,-337 589.75,-337 589.75,-337 733.25,-337 733.25,-337 739.25,-337 745.25,-343 745.25,-349 745.25,-349 745.25,-375 745.25,-375 745.25,-381 739.25,-387 733.25,-387"/>
|
||||
<text text-anchor="middle" x="661.5" y="-369.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">storage.transaction()</text>
|
||||
<text text-anchor="middle" x="661.5" y="-348.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">-> Box<dyn Transaction></text>
|
||||
</g>
|
||||
<!-- create_relation->begin_tx -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>create_relation->begin_tx</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2"
|
||||
d="M416.8,-184.23C444.61,-208.87 496.48,-253.65 543.75,-288 564.01,-302.72 587.16,-317.74 607.47,-330.37"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="605.6,-333.32 615.94,-335.59 609.27,-327.37 605.6,-333.32"/>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M416.8,-184.23C444.61,-208.87 496.48,-253.65 543.75,-288 564.01,-302.72 587.16,-317.74 607.47,-330.37"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="605.6,-333.32 615.94,-335.59 609.27,-327.37 605.6,-333.32"/>
|
||||
</g>
|
||||
<!-- scan_iter -->
|
||||
<g id="node9" class="node">
|
||||
<title>scan_iter</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M1774.5,-272C1774.5,-272 1635.5,-272 1635.5,-272 1629.5,-272 1623.5,-266 1623.5,-260 1623.5,-260 1623.5,-234 1623.5,-234 1623.5,-228 1629.5,-222 1635.5,-222 1635.5,-222 1774.5,-222 1774.5,-222 1780.5,-222 1786.5,-228 1786.5,-234 1786.5,-234 1786.5,-260 1786.5,-260 1786.5,-266 1780.5,-272 1774.5,-272"/>
|
||||
<text text-anchor="middle" x="1705" y="-254.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
storage.scan_iter(name)
|
||||
</text>
|
||||
<text text-anchor="middle" x="1705" y="-233.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
-> RowStream
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M1774.5,-272C1774.5,-272 1635.5,-272 1635.5,-272 1629.5,-272 1623.5,-266 1623.5,-260 1623.5,-260 1623.5,-234 1623.5,-234 1623.5,-228 1629.5,-222 1635.5,-222 1635.5,-222 1774.5,-222 1774.5,-222 1780.5,-222 1786.5,-228 1786.5,-234 1786.5,-234 1786.5,-260 1786.5,-260 1786.5,-266 1780.5,-272 1774.5,-272"/>
|
||||
<text text-anchor="middle" x="1705" y="-254.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">storage.scan_iter(name)</text>
|
||||
<text text-anchor="middle" x="1705" y="-233.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">-> RowStream</text>
|
||||
</g>
|
||||
<!-- create_relation->scan_iter -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>create_relation->scan_iter</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M430.83,-184.43C480.17,-209.16 574.65,-250 660.5,-250 660.5,-250 660.5,-250 1261.62,-250 1382.54,-250 1522.03,-248.87 1611.53,-247.99"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="1611.5,-251.49 1621.46,-247.89 1611.43,-244.49 1611.5,-251.49"/>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" stroke-dasharray="5,2" d="M430.83,-184.43C480.17,-209.16 574.65,-250 660.5,-250 660.5,-250 660.5,-250 1261.62,-250 1382.54,-250 1522.03,-248.87 1611.53,-247.99"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="1611.5,-251.49 1621.46,-247.89 1611.43,-244.49 1611.5,-251.49"/>
|
||||
</g>
|
||||
<!-- scan_where -->
|
||||
<g id="node10" class="node">
|
||||
<title>scan_where</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M1815.75,-72C1815.75,-72 1594.25,-72 1594.25,-72 1588.25,-72 1582.25,-66 1582.25,-60 1582.25,-60 1582.25,-34 1582.25,-34 1582.25,-28 1588.25,-22 1594.25,-22 1594.25,-22 1815.75,-22 1815.75,-22 1821.75,-22 1827.75,-28 1827.75,-34 1827.75,-34 1827.75,-60 1827.75,-60 1827.75,-66 1821.75,-72 1815.75,-72"/>
|
||||
<text text-anchor="middle" x="1705" y="-54.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
storage.scan_where(name, col, value)
|
||||
</text>
|
||||
<text text-anchor="middle" x="1705" y="-33.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
-> RowStream  (filtered)
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M1815.75,-72C1815.75,-72 1594.25,-72 1594.25,-72 1588.25,-72 1582.25,-66 1582.25,-60 1582.25,-60 1582.25,-34 1582.25,-34 1582.25,-28 1588.25,-22 1594.25,-22 1594.25,-22 1815.75,-22 1815.75,-22 1821.75,-22 1827.75,-28 1827.75,-34 1827.75,-34 1827.75,-60 1827.75,-60 1827.75,-66 1821.75,-72 1815.75,-72"/>
|
||||
<text text-anchor="middle" x="1705" y="-54.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">storage.scan_where(name, col, value)</text>
|
||||
<text text-anchor="middle" x="1705" y="-33.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">-> RowStream  (filtered)</text>
|
||||
</g>
|
||||
<!-- create_relation->scan_where -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>create_relation->scan_where</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M419.28,-147.51C463.15,-113.85 563.58,-46 660.5,-46 660.5,-46 660.5,-46 1261.62,-46 1365.26,-46 1482.56,-46.28 1570.31,-46.54"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="1570.12,-50.04 1580.13,-46.57 1570.15,-43.04 1570.12,-50.04"/>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" stroke-dasharray="5,2" d="M419.28,-147.51C463.15,-113.85 563.58,-46 660.5,-46 660.5,-46 660.5,-46 1261.62,-46 1365.26,-46 1482.56,-46.28 1570.31,-46.54"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="1570.12,-50.04 1580.13,-46.57 1570.15,-43.04 1570.12,-50.04"/>
|
||||
</g>
|
||||
<!-- scan_full -->
|
||||
<g id="node11" class="node">
|
||||
<title>scan_full</title>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5"
|
||||
d="M1792.5,-172C1792.5,-172 1617.5,-172 1617.5,-172 1611.5,-172 1605.5,-166 1605.5,-160 1605.5,-160 1605.5,-134 1605.5,-134 1605.5,-128 1611.5,-122 1617.5,-122 1617.5,-122 1792.5,-122 1792.5,-122 1798.5,-122 1804.5,-128 1804.5,-134 1804.5,-134 1804.5,-160 1804.5,-160 1804.5,-166 1798.5,-172 1792.5,-172"/>
|
||||
<text text-anchor="middle" x="1705" y="-154.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
storage.scan(name)
|
||||
</text>
|
||||
<text text-anchor="middle" x="1705" y="-133.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
-> Vec<(RowId, Vec<Value>)>
|
||||
</text>
|
||||
<path fill="#fff3e0" stroke="#ff9800" stroke-width="1.5" d="M1792.5,-172C1792.5,-172 1617.5,-172 1617.5,-172 1611.5,-172 1605.5,-166 1605.5,-160 1605.5,-160 1605.5,-134 1605.5,-134 1605.5,-128 1611.5,-122 1617.5,-122 1617.5,-122 1792.5,-122 1792.5,-122 1798.5,-122 1804.5,-128 1804.5,-134 1804.5,-134 1804.5,-160 1804.5,-160 1804.5,-166 1798.5,-172 1792.5,-172"/>
|
||||
<text text-anchor="middle" x="1705" y="-154.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">storage.scan(name)</text>
|
||||
<text text-anchor="middle" x="1705" y="-133.7" font-family="Helvetica,Arial,sans-serif" font-size="14.00">-> Vec<(RowId, Vec<Value>)></text>
|
||||
</g>
|
||||
<!-- create_relation->scan_full -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>create_relation->scan_full</title>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M511.06,-154.78C557.41,-151.13 611.46,-148 660.5,-148 660.5,-148 660.5,-148 1261.62,-148 1374.63,-148 1503.88,-147.67 1593.36,-147.39"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2"
|
||||
points="1593.37,-150.89 1603.35,-147.36 1593.34,-143.89 1593.37,-150.89"/>
|
||||
<path fill="none" stroke="#9c27b0" stroke-width="1.2" stroke-dasharray="5,2" d="M511.06,-154.78C557.41,-151.13 611.46,-148 660.5,-148 660.5,-148 660.5,-148 1261.62,-148 1374.63,-148 1503.88,-147.67 1593.36,-147.39"/>
|
||||
<polygon fill="#9c27b0" stroke="#9c27b0" stroke-width="1.2" points="1593.37,-150.89 1603.35,-147.36 1593.34,-143.89 1593.37,-150.89"/>
|
||||
</g>
|
||||
<!-- begin_tx->tx_ops -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>begin_tx->tx_ops</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2"
|
||||
d="M745.52,-378.35C763.04,-381.8 781.87,-385.51 800.47,-389.17"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="799.73,-392.59 810.22,-391.09 801.09,-385.72 799.73,-392.59"/>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M745.52,-378.35C763.04,-381.8 781.87,-385.51 800.47,-389.17"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="799.73,-392.59 810.22,-391.09 801.09,-385.72 799.73,-392.59"/>
|
||||
</g>
|
||||
<!-- commit -->
|
||||
<g id="node7" class="node">
|
||||
<title>commit</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M1409.5,-489.12C1409.5,-489.12 1111.75,-489.12 1111.75,-489.12 1105.75,-489.12 1099.75,-483.12 1099.75,-477.12 1099.75,-477.12 1099.75,-348.88 1099.75,-348.88 1099.75,-342.88 1105.75,-336.88 1111.75,-336.88 1111.75,-336.88 1409.5,-336.88 1409.5,-336.88 1415.5,-336.88 1421.5,-342.88 1421.5,-348.88 1421.5,-348.88 1421.5,-477.12 1421.5,-477.12 1421.5,-483.12 1415.5,-489.12 1409.5,-489.12"/>
|
||||
<text text-anchor="start" x="1223.5" y="-468.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">tx.commit()
|
||||
</text>
|
||||
<text text-anchor="start" x="1111.75" y="-439.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• native commit (LMDB, redb, SQLite, geomerge)
|
||||
</text>
|
||||
<text text-anchor="start" x="1111.75" y="-410.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• buffered apply (memory, fjall)
|
||||
</text>
|
||||
<text text-anchor="start" x="1111.75" y="-381.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• law validation (geomerge)
|
||||
</text>
|
||||
<text text-anchor="start" x="1111.75" y="-352.57" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• yields CommittedTx
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M1409.5,-489.12C1409.5,-489.12 1111.75,-489.12 1111.75,-489.12 1105.75,-489.12 1099.75,-483.12 1099.75,-477.12 1099.75,-477.12 1099.75,-348.88 1099.75,-348.88 1099.75,-342.88 1105.75,-336.88 1111.75,-336.88 1111.75,-336.88 1409.5,-336.88 1409.5,-336.88 1415.5,-336.88 1421.5,-342.88 1421.5,-348.88 1421.5,-348.88 1421.5,-477.12 1421.5,-477.12 1421.5,-483.12 1415.5,-489.12 1409.5,-489.12"/>
|
||||
<text text-anchor="start" x="1223.5" y="-468.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">tx.commit()</text>
|
||||
<text text-anchor="start" x="1111.75" y="-439.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• native commit (LMDB, redb, SQLite, geomerge)</text>
|
||||
<text text-anchor="start" x="1111.75" y="-410.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• buffered apply (memory, fjall)</text>
|
||||
<text text-anchor="start" x="1111.75" y="-381.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• law validation (geomerge)</text>
|
||||
<text text-anchor="start" x="1111.75" y="-352.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• yields CommittedTx</text>
|
||||
</g>
|
||||
<!-- tx_ops->commit -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>tx_ops->commit</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2"
|
||||
d="M1033.09,-413C1050.7,-413 1069.29,-413 1087.94,-413"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="1087.74,-416.5 1097.74,-413 1087.74,-409.5 1087.74,-416.5"/>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M1033.09,-413C1050.7,-413 1069.29,-413 1087.94,-413"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="1087.74,-416.5 1097.74,-413 1087.74,-409.5 1087.74,-416.5"/>
|
||||
</g>
|
||||
<!-- resolve_ids -->
|
||||
<g id="node8" class="node">
|
||||
<title>resolve_ids</title>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5"
|
||||
d="M1856.5,-438.12C1856.5,-438.12 1553.5,-438.12 1553.5,-438.12 1547.5,-438.12 1541.5,-432.12 1541.5,-426.12 1541.5,-426.12 1541.5,-355.88 1541.5,-355.88 1541.5,-349.88 1547.5,-343.88 1553.5,-343.88 1553.5,-343.88 1856.5,-343.88 1856.5,-343.88 1862.5,-343.88 1868.5,-349.88 1868.5,-355.88 1868.5,-355.88 1868.5,-426.12 1868.5,-426.12 1868.5,-432.12 1862.5,-438.12 1856.5,-438.12"/>
|
||||
<text text-anchor="start" x="1633" y="-417.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">CommittedTx::resolve
|
||||
</text>
|
||||
<text text-anchor="start" x="1553.5" y="-388.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
• KV: pending == real
|
||||
</text>
|
||||
<text text-anchor="start" x="1553.5" y="-359.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">
|
||||
• geomerge: pending counter → (commit, counter)
|
||||
</text>
|
||||
<path fill="#e8f5e9" stroke="#4caf50" stroke-width="1.5" d="M1856.5,-438.12C1856.5,-438.12 1553.5,-438.12 1553.5,-438.12 1547.5,-438.12 1541.5,-432.12 1541.5,-426.12 1541.5,-426.12 1541.5,-355.88 1541.5,-355.88 1541.5,-349.88 1547.5,-343.88 1553.5,-343.88 1553.5,-343.88 1856.5,-343.88 1856.5,-343.88 1862.5,-343.88 1868.5,-349.88 1868.5,-355.88 1868.5,-355.88 1868.5,-426.12 1868.5,-426.12 1868.5,-432.12 1862.5,-438.12 1856.5,-438.12"/>
|
||||
<text text-anchor="start" x="1633" y="-417.82" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">CommittedTx::resolve</text>
|
||||
<text text-anchor="start" x="1553.5" y="-388.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• KV: pending == real</text>
|
||||
<text text-anchor="start" x="1553.5" y="-359.57" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• geomerge: pending counter → (commit, counter)</text>
|
||||
</g>
|
||||
<!-- commit->resolve_ids -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>commit->resolve_ids</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2"
|
||||
d="M1421.96,-405.03C1456.99,-403.28 1494.23,-401.43 1529.77,-399.66"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="1529.61,-403.18 1539.43,-399.18 1529.27,-396.19 1529.61,-403.18"/>
|
||||
<text text-anchor="middle" x="1481.5" y="-408.27" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">CommittedTx
|
||||
</text>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" d="M1421.96,-405.03C1456.99,-403.28 1494.23,-401.43 1529.77,-399.66"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="1529.61,-403.18 1539.43,-399.18 1529.27,-396.19 1529.61,-403.18"/>
|
||||
<text text-anchor="middle" x="1481.5" y="-408.27" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">CommittedTx</text>
|
||||
</g>
|
||||
<!-- commit->scan_iter -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>commit->scan_iter</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M1421.96,-352.84C1491.15,-326.87 1568.97,-297.67 1625.3,-276.53"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="1626.44,-279.84 1634.58,-273.05 1623.98,-273.29 1626.44,-279.84"/>
|
||||
<text text-anchor="middle" x="1481.5" y="-344.89" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">after commit
|
||||
</text>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" stroke-dasharray="5,2" d="M1421.96,-352.84C1491.15,-326.87 1568.97,-297.67 1625.3,-276.53"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="1626.44,-279.84 1634.58,-273.05 1623.98,-273.29 1626.44,-279.84"/>
|
||||
<text text-anchor="middle" x="1481.5" y="-344.89" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">after commit</text>
|
||||
</g>
|
||||
<!-- rows_out -->
|
||||
<g id="node12" class="node">
|
||||
<title>rows_out</title>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5"
|
||||
d="M2149.75,-258.62C2149.75,-258.62 1995.25,-258.62 1995.25,-258.62 1989.25,-258.62 1983.25,-252.62 1983.25,-246.62 1983.25,-246.62 1983.25,-147.38 1983.25,-147.38 1983.25,-141.38 1989.25,-135.38 1995.25,-135.38 1995.25,-135.38 2149.75,-135.38 2149.75,-135.38 2155.75,-135.38 2161.75,-141.38 2161.75,-147.38 2161.75,-147.38 2161.75,-246.62 2161.75,-246.62 2161.75,-252.62 2155.75,-258.62 2149.75,-258.62"/>
|
||||
<text text-anchor="start" x="2054.5" y="-238.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold"
|
||||
font-size="14.00">Rows
|
||||
</text>
|
||||
<text text-anchor="start" x="1995.25" y="-209.07" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• (RowId, Vec<Value>)
|
||||
</text>
|
||||
<text text-anchor="start" x="1995.25" y="-180.07" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00">• consumed by query-ops
|
||||
</text>
|
||||
<text text-anchor="start" x="1995.25" y="-151.07" font-family="Helvetica,Arial,sans-serif"
|
||||
font-size="14.00"> via scan_as_table
|
||||
</text>
|
||||
<path fill="#eceff1" stroke="#607d8b" stroke-width="1.5" d="M2149.75,-258.62C2149.75,-258.62 1995.25,-258.62 1995.25,-258.62 1989.25,-258.62 1983.25,-252.62 1983.25,-246.62 1983.25,-246.62 1983.25,-147.38 1983.25,-147.38 1983.25,-141.38 1989.25,-135.38 1995.25,-135.38 1995.25,-135.38 2149.75,-135.38 2149.75,-135.38 2155.75,-135.38 2161.75,-141.38 2161.75,-147.38 2161.75,-147.38 2161.75,-246.62 2161.75,-246.62 2161.75,-252.62 2155.75,-258.62 2149.75,-258.62"/>
|
||||
<text text-anchor="start" x="2054.5" y="-238.32" font-family="Helvetica,Arial,sans-serif" font-weight="bold" font-size="14.00">Rows</text>
|
||||
<text text-anchor="start" x="1995.25" y="-209.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• (RowId, Vec<Value>)</text>
|
||||
<text text-anchor="start" x="1995.25" y="-180.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">• consumed by query-ops</text>
|
||||
<text text-anchor="start" x="1995.25" y="-151.07" font-family="Helvetica,Arial,sans-serif" font-size="14.00">  via scan_as_table</text>
|
||||
</g>
|
||||
<!-- resolve_ids->rows_out -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>resolve_ids->rows_out</title>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" stroke-dasharray="5,2"
|
||||
d="M1847.09,-343.54C1859.24,-338.34 1871.2,-332.82 1882.5,-327 1916.36,-309.58 1951.29,-287.04 1981.77,-265.63"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2"
|
||||
points="1983.46,-268.72 1989.6,-260.08 1979.41,-263.01 1983.46,-268.72"/>
|
||||
<text text-anchor="middle" x="1925.88" y="-320.11" font-family="Helvetica,Arial,sans-serif" font-size="9.00"
|
||||
fill="#555555">real RowIds
|
||||
</text>
|
||||
<path fill="none" stroke="#4caf50" stroke-width="1.2" stroke-dasharray="5,2" d="M1847.09,-343.54C1859.24,-338.34 1871.2,-332.82 1882.5,-327 1916.36,-309.58 1951.29,-287.04 1981.77,-265.63"/>
|
||||
<polygon fill="#4caf50" stroke="#4caf50" stroke-width="1.2" points="1983.46,-268.72 1989.6,-260.08 1979.41,-263.01 1983.46,-268.72"/>
|
||||
<text text-anchor="middle" x="1925.88" y="-320.11" font-family="Helvetica,Arial,sans-serif" font-size="9.00" fill="#555555">real RowIds</text>
|
||||
</g>
|
||||
<!-- scan_iter->rows_out -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>scan_iter->rows_out</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2"
|
||||
d="M1786.66,-235.97C1841.16,-228.51 1913.48,-218.62 1971.62,-210.66"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2"
|
||||
points="1971.81,-214.17 1981.25,-209.35 1970.86,-207.23 1971.81,-214.17"/>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" d="M1786.66,-235.97C1841.16,-228.51 1913.48,-218.62 1971.62,-210.66"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="1971.81,-214.17 1981.25,-209.35 1970.86,-207.23 1971.81,-214.17"/>
|
||||
</g>
|
||||
<!-- scan_where->rows_out -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>scan_where->rows_out</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2"
|
||||
d="M1809.89,-72.49C1834.05,-79.5 1859.44,-87.75 1882.5,-97 1912.57,-109.06 1944.11,-124.52 1972.62,-139.66"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2"
|
||||
points="1970.91,-142.71 1981.37,-144.36 1974.22,-136.55 1970.91,-142.71"/>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" d="M1809.89,-72.49C1834.05,-79.5 1859.44,-87.75 1882.5,-97 1912.57,-109.06 1944.11,-124.52 1972.62,-139.66"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="1970.91,-142.71 1981.37,-144.36 1974.22,-136.55 1970.91,-142.71"/>
|
||||
</g>
|
||||
<!-- scan_full->rows_out -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>scan_full->rows_out</title>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2"
|
||||
d="M1804.91,-160.53C1856.5,-167.59 1919.48,-176.2 1971.27,-183.29"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2"
|
||||
points="1970.63,-186.73 1981.02,-184.62 1971.58,-179.8 1970.63,-186.73"/>
|
||||
<path fill="none" stroke="#ff9800" stroke-width="1.2" d="M1804.91,-160.53C1856.5,-167.59 1919.48,-176.2 1971.27,-183.29"/>
|
||||
<polygon fill="#ff9800" stroke="#ff9800" stroke-width="1.2" points="1970.63,-186.73 1981.02,-184.62 1971.58,-179.8 1970.63,-186.73"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 19 KiB |
@ -93,6 +93,17 @@ fn decode_pending_row_id(bytes: &[u8]) -> Result<TempRowId, StorageError> {
|
||||
}
|
||||
|
||||
/// Geomerge-backed [`Storage`] implementation.
|
||||
/// Primitive column type used by [`GeomergeStorage::with_relations`] to
|
||||
/// synthesize a theory from an untyped `(name, arity)` schema. Geomerge
|
||||
/// supports `PrimInt`, `PrimString`, and entity types; only the two
|
||||
/// primitives are exposed here, since callers using this constructor by
|
||||
/// definition don't carry entity-target information.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ColumnKind {
|
||||
Int,
|
||||
String,
|
||||
}
|
||||
|
||||
pub struct GeomergeStorage {
|
||||
store: Store,
|
||||
declared: HashSet<String>,
|
||||
@ -138,6 +149,52 @@ impl GeomergeStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a store with a theory synthesized from a flat list of
|
||||
/// `(relation_name, column_kinds)`. Each `ColumnKind` is mapped to the
|
||||
/// matching `PrimType`. No entity columns and no laws are declared.
|
||||
///
|
||||
/// This is the convenience constructor for callers (e.g., the
|
||||
/// `plan-runner` CLI) whose schema only carries arity plus a column-by-
|
||||
/// column primitive-type guess taken from a sample row. It exists so
|
||||
/// those callers don't have to depend on `geolog-lang::ir` directly.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`StorageError::Backend`] if geomerge rejects the synthesized
|
||||
/// theory.
|
||||
pub fn with_relations<I, S>(relations: I) -> Result<Self, StorageError>
|
||||
where
|
||||
I: IntoIterator<Item = (S, Vec<ColumnKind>)>,
|
||||
S: Into<String>,
|
||||
{
|
||||
let tables: Vec<ir::TableEntry> = relations
|
||||
.into_iter()
|
||||
.map(|(name, kinds)| {
|
||||
let columns = kinds
|
||||
.into_iter()
|
||||
.map(|k| ir::ColType::PrimType {
|
||||
prim: match k {
|
||||
ColumnKind::Int => ir::PrimType::PrimInt,
|
||||
ColumnKind::String => ir::PrimType::PrimString,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
let name: String = name.into();
|
||||
ir::TableEntry {
|
||||
path: name.into(),
|
||||
table: ir::Schema {
|
||||
columns,
|
||||
primary_key: None,
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let theory = ir::FlatTheory {
|
||||
tables,
|
||||
laws: Vec::new(),
|
||||
};
|
||||
Self::from_theory(theory)
|
||||
}
|
||||
|
||||
/// Borrow the underlying geomerge store (for backend-specific operations
|
||||
/// like persistence, dump, or law inspection that aren't on the trait).
|
||||
#[must_use]
|
||||
|
||||
@ -154,12 +154,12 @@ impl Transaction for LmdbTx<'_> {
|
||||
let Some(wtxn) = self.wtxn.as_ref() else {
|
||||
unreachable!("transaction was already committed")
|
||||
};
|
||||
let raw = self
|
||||
let encoded = self
|
||||
.meta
|
||||
.get(wtxn, name.as_bytes())
|
||||
.map_err(backend)?
|
||||
.ok_or_else(|| StorageError::RelationNotFound(name.to_string()))?;
|
||||
let entry = decode_meta(raw)?;
|
||||
let entry = decode_meta(encoded)?;
|
||||
self.next_ids.insert(name.to_string(), entry);
|
||||
entry
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//! SQLite adapter via the `rusqlite` crate (bundled libsqlite3).
|
||||
//! `SQLite` adapter via the `rusqlite` crate (bundled libsqlite3).
|
||||
//!
|
||||
//! Storage layout:
|
||||
//!
|
||||
@ -35,13 +35,13 @@ CREATE TABLE IF NOT EXISTS __rows (
|
||||
);
|
||||
";
|
||||
|
||||
/// SQLite-backed [`Storage`] implementation.
|
||||
/// `SQLite`-backed [`Storage`] implementation.
|
||||
pub struct SqliteStorage {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl SqliteStorage {
|
||||
/// Open or create a SQLite database at `path`. Pass `":memory:"` for
|
||||
/// Open or create a `SQLite` database at `path`. Pass `":memory:"` for
|
||||
/// an in-process database (useful in tests).
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
# Geolog Branch Focus
|
||||
|
||||
Generated from the local `tmp/geolog` repository. Focus is inferred from branch names, tip commit messages, commits unique relative to `origin/main`,
|
||||
and touched files. Branches are sorted by latest tip commit date, newest first. Git does not store branch creator metadata in refs, so
|
||||
`First Unique Commit Author` is the author of the oldest commit unique to that branch relative to `origin/main`.
|
||||
|
||||
| Branch | Latest Update | Last Change Author | First Unique Commit Author | Unique Commits Versus `origin/main` | Focus |
|
||||
|---------------------------|---------------|--------------------|----------------------------|------------------------------------:|----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `may-spring-cleaning` | 2026-05-21 | James Deikun | Owen Lynch | 4 | Core cleanup around conversion checking, evaluation parameters, readback, value syntax, and printing. |
|
||||
| `wasm-diagrams-demo` | 2026-05-21 | George Thomas | Owen Lynch | 136 | Wasm diagrams demo with interactive query-plan diagrams, Graphviz and diagrams vendoring, web layout fixes, and live frontend work. |
|
||||
| `query-plan-ir-draft-1` | 2026-05-21 | Patrick Aldis | Owen Lynch | 81 | Query plan IR draft with evaluation passing tests, diagram rendering, join orientation visualization, and REPL query diagrams. |
|
||||
| `main` | 2026-05-21 | Owen Lynch | Unknown, no unique commits | 0 | Current mainline. Tip is a merge of `manual-deploy-fixup`. |
|
||||
| `reports-2026-05-19` | 2026-05-19 | George Thomas | Owen Lynch | 8 | Team report files through May 19, 2026. |
|
||||
| `query-plan-ir` | 2026-05-18 | Patrick Aldis | Owen Lynch | 64 | Query planning IR on top of the in-memory and IR integration branch, including register information and plan rendering. |
|
||||
| `wasm` | 2026-05-15 | George Thomas | Owen Lynch | 69 | Wasm and web frontend work, including JSON output, Geolog Wasm library, Nix/Haskell build updates, and diagrams dependencies. |
|
||||
| `post-cale-in-memory-ir2` | 2026-05-13 | Patrick Aldis | Owen Lynch | 60 | Integration branch combining in-memory and REPL cleanup with `ir-draft2`. |
|
||||
| `wasm-dist` | 2026-05-12 | George Thomas | Owen Lynch | 68 | Wasm distribution branch for built assets and frontend output, based on the Wasm and in-memory work. |
|
||||
| `reports-2026-05-12` | 2026-05-12 | felix | Owen Lynch | 7 | Team report files for late April and May 12, 2026. |
|
||||
| `felix-db-2` | 2026-05-12 | felix | felix | 8 | Experimental Datalog database backend work, including in-memory and Orville SQL APIs, `applyRule`, and chase tests. |
|
||||
| `energy` | 2026-05-05 | James Deikun | James Deikun | 3 | Energy or potential semantics in core operations, elaboration, notation, pretty printing, and related golden tests. |
|
||||
| `ir-draft2-bands` | 2026-05-01 | felix | Owen Lynch | 21 | `ir-draft2` plus a band example and golden output showing lowering behavior. |
|
||||
| `hexane` | 2026-05-01 | George Thomas | George Thomas | 9 | Hexane integration, Haskell FFI and Rust tooling, Geomerge Haskell wrapper, JSON tests, and IR cherry-picks. |
|
||||
| `in-memory-devibed` | 2026-04-30 | Patrick Aldis | Owen Lynch | 57 | Cleanup of in-memory and REPL code, structured REPL errors, command and state modules, comments, and property tests. |
|
||||
| `lowering-via-eval-quote` | 2026-04-29 | Owen Lynch | Owen Lynch | 7 | Alternative lowering approach via eval and quote, with SSA and graded-rig tests. |
|
||||
| `in-memory` | 2026-04-28 | Patrick Aldis | Owen Lynch | 48 | In-memory database execution, DB plan modules, IR lowering, REPL support, and formatting cleanup. |
|
||||
| `lower-to-js` | 2026-04-26 | Owen Lynch | Owen Lynch | 8 | TypeScript and JavaScript runtime lowering experiments, including graph-of-graphs compilation and relation validators. |
|
||||
| `george/wip` | 2026-04-20 | Cale Gibbard | Owen Lynch | 96 | Broad work-in-progress branch covering Nix flakes, Reflex, examples, data partitioning, diagnostics, FNotation, and Geolog language changes. |
|
||||
| `latex-dates` | 2026-04-20 | George Thomas | George Thomas | 2 | Nix shell and Shake fixes for LaTeX date generation in documentation builds. |
|
||||
| `config-default` | 2026-04-17 | Shuntian Liu | Unknown, no unique commits | 0 | Configuration default fix, likely already merged or superseded by `main`. |
|
||||
| `geomerge` | 2026-04-17 | Shuntian Liu | Unknown, no unique commits | 0 | Geomerge import or merge branch, likely already merged or superseded by `main`. |
|
||||
| `warmup-ci` | 2026-04-17 | Owen Lynch | Owen Lynch | 8 | CI warmup work, including Cabal update steps and a freeze file for Shake. |
|
||||
| `mcp` | 2026-04-09 | George Thomas | Owen Lynch | 56 | MCP server or tool experiment with REPL-like functionality, LaTeX source-reading tools, transcripts, and in-memory dependencies. |
|
||||
| `ir-draft2` | 2026-04-09 | James Deikun | Owen Lynch | 20 | Second IR draft with equality-type support and updates across core, elaborator, IR, lowering, diagnostics, and tests. |
|
||||
| `golden-tests-fix` | 2026-04-09 | Patrick Aldis | Patrick Aldis | 2 | Golden-test path fix so `tasty-golden` looks in the `test` directory for FNotation and Geolog. |
|
||||
| `in-memory-demo` | 2026-04-07 | Felix Dilke | Owen Lynch | 38 | In-memory demo and REPL workflow, including `:schema`, dotted table names for insert and query, and REPL test scripts. |
|
||||
| `ir-draft1-clean` | 2026-04-07 | Cale Gibbard | Owen Lynch | 22 | Cleaned and rebased version of `ir-draft1`, including explicit lowering context and local environment handling. |
|
||||
| `lsp-demo` | 2026-04-02 | Patrick Aldis | Patrick Aldis | 2 | Demo-only LSP behavior change to hide fatal errors. |
|
||||
| `ir-draft1` | 2026-04-01 | James Deikun | Owen Lynch | 16 | First IR lowering draft, with support for columns, totality-rule cleanup, core lowering, and golden tests. |
|
||||
| `lsp` | 2026-03-31 | Patrick Aldis | Patrick Aldis | 28 | Geolog LSP development, diagnostics integration, analyzed buffer result types, and `lsp-test` coverage. |
|
||||
| `benchmarks` | 2026-03-31 | felix | Unknown, no unique commits | 0 | Benchmark work that appears already merged or superseded by `main`. |
|
||||
| `why-geolog` | 2026-03-31 | Martin Kleppmann | Unknown, no unique commits | 0 | Why-Geolog documentation or positioning notes, likely already merged or superseded by `main`. |
|
||||
| `fnotation-release` | 2026-03-30 | Owen Lynch | Unknown, no unique commits | 0 | Documentation for an FNotation release, likely already merged or superseded by `main`. |
|
||||
| `repl` | 2026-03-27 | George Thomas | George Thomas | 18 | REPL usability changes, including command renames, completions, declaration display, and load-to-source naming. |
|
||||
| `reporting-refactor` | 2026-03-27 | Patrick Aldis | Patrick Aldis | 4 | Reporter abstraction cleanup, including contrafunctor support and lifted `reportIO`. |
|
||||
| `parser-exceptions` | 2026-03-25 | Patrick Aldis | Patrick Aldis | 8 | Parser, lexer, and elaborator refactor to use `ExceptT` and pure reporting. |
|
||||
| `fb3-fail` | 2026-03-24 | felix | Patrick Aldis | 6 | Failure-case fixtures and Nix setup around `fb3` parser and elaborator golden tests. |
|
||||
| `edits-from-london` | 2026-03-24 | Owen Lynch | Owen Lynch | 2 | London meeting edits across core syntax, operations, elaboration, lexer, pretty printing, and linear-order tests. |
|
||||
| `fix-off-by-one` | 2026-03-20 | Patrick Aldis | Patrick Aldis | 2 | Diagnostic source-position fix for an off-by-one error in `lineOf`. |
|
||||
| `felix-db-2-cale-edits` | 2026-03-11 | Felix Dilke | Felix Dilke | 2 | Incomplete Cale edits making `InMDB` monadic via state in `felix-db`. |
|
||||
| `cg/query-planning` | 2026-03-10 | Cale Gibbard | Unknown, no unique commits | 0 | Query planning work that appears already merged or superseded by `main`. |
|
||||
| `ir-design-2` | 2026-03-06 | James Deikun | Unknown, no unique commits | 0 | Follow-up IR design document cleanup, likely already merged or superseded by `main`. |
|
||||
| `observational` | 2026-02-24 | Owen Lynch | Owen Lynch | 16 | Observational checking and diagnostics refactor, including lexer, parser, elaborator diagnostics, and related documentation. |
|
||||
| `ir-design-1` | 2026-02-23 | James Deikun | Unknown, no unique commits | 0 | IR vision-document draft, likely already merged or superseded by `main`. |
|
||||
| `haddock-ci` | 2026-01-15 | Owen Lynch | Unknown, no unique commits | 0 | Haddock CI experiment, likely already merged or superseded by `main`. |
|
||||
| `notation-refactor` | 2025-12-24 | Owen Lynch | Unknown, no unique commits | 0 | Notation refactor and output-name updates, likely already merged or superseded by `main`. |
|
||||
| `tests` | 2025-12-19 | Owen Lynch | Owen Lynch | 2 | Prospectus documentation change clarifying theorem expectations. |
|
||||
|
||||
## Change Log
|
||||
|
||||
- 2026-05-22: Updated branch table with latest update dates, last-change authors, first unique commit authors, unique commit counts, and inferred focus notes.
|
||||
@ -1,222 +0,0 @@
|
||||
# Geolog Lang Findings
|
||||
|
||||
Source inspected: local reference checkout at `tmp/geolog/geolog-lang`.
|
||||
|
||||
## Summary
|
||||
|
||||
`geolog-lang` is a Haskell prototype for a Geolog source language. It parses `.glog` theory files, elaborates them into a typed core language, lowers a selected `Main` theory into a flat relational representation, and includes an in-memory relation engine with law checking and a conjunctive-query planner.
|
||||
|
||||
For this repository, the useful reference is not the Haskell implementation itself. The useful reference is the shape of the pipeline:
|
||||
|
||||
```text
|
||||
Geolog theory
|
||||
-> typed core elaboration
|
||||
-> flat relational theory
|
||||
-> tables, generated laws, and validation queries
|
||||
-> in-memory execution and query planning experiments
|
||||
```
|
||||
|
||||
## Package Shape
|
||||
|
||||
The package exposes modules for:
|
||||
|
||||
- core syntax, values, evaluation, quoting, and equality checking
|
||||
- notation, lexer, parser configuration, and diagnostics
|
||||
- elaboration from parsed notation into typed core entries
|
||||
- lowering from the typed `Main` theory into a flat relational theory
|
||||
- relational IR for tables, atoms, propositions, and laws
|
||||
- an in-memory database and a conjunctive-query planner
|
||||
|
||||
The test suite combines golden tests for `.glog` elaboration and lowering with unit and property tests for the in-memory database and planner.
|
||||
|
||||
## Source Language Shape
|
||||
|
||||
The examples use declarations such as:
|
||||
|
||||
```text
|
||||
theory Main := sig
|
||||
Node : Set
|
||||
Edge : Node -> Node -> Set
|
||||
end
|
||||
```
|
||||
|
||||
The notation supports `theory`, `def`, `set`, `let`, `open`, `import`, `Set`, `Prop`, `Int`, `String`, `Inductive`, `pure`, `init`, `->`, `*->`, and `=>`.
|
||||
|
||||
The source language is closer to a dependent typed theory language than to ordinary Datalog. Relations, functions, records, equality, and inductive constructions are represented in the source and then selectively lowered.
|
||||
|
||||
## Lowered Relational IR
|
||||
|
||||
The lowered IR has these main concepts:
|
||||
|
||||
- `Path`: dotted table or law names represented as path components
|
||||
- `Table`: column types and an optional primary key
|
||||
- `Atom`: table reference, optional row identity term, and positional value terms
|
||||
- `Prop`: atom, equality, conjunction, or disjunction
|
||||
- `Law`: universally quantified variables, an antecedent proposition, and a consequent proposition
|
||||
- `FlatTheory`: maps of tables and laws
|
||||
|
||||
For the graph example above, lowering produces a `Node` table, an `Edge` table, and an `Edge.foreignKeys` law. The law says that every edge row implies the existence of both endpoint node rows.
|
||||
|
||||
This is directly relevant to Geomerge-style validation experiments. A high-level declaration can compile into maintained violation checks by turning generated laws into queries:
|
||||
|
||||
```text
|
||||
required_consequent(x) :- antecedent(x).
|
||||
violation(x) :- required_consequent(x), not consequent(x).
|
||||
```
|
||||
|
||||
## Generated Law Patterns
|
||||
|
||||
Two generated law shapes matter for this playground:
|
||||
|
||||
- foreign-key laws: rows in one table require referenced rows in another table
|
||||
- totality laws: functional relations require an output for each valid input
|
||||
|
||||
Foreign-key laws are generated from dependent arguments that reference prior sets or relations. Totality laws are generated for functional table shapes.
|
||||
|
||||
The current lowering appears intentionally narrow. Unsupported shapes are reported or panic in prototype code rather than being treated as complete semantics. That is useful for this repo because early adapters should also make supported and unsupported cases explicit.
|
||||
|
||||
## In-Memory Execution
|
||||
|
||||
The in-memory database stores:
|
||||
|
||||
- ground values: integers, text, and entity identities
|
||||
- relations: set-valued tuples with secondary indexes by column and value
|
||||
- bindings: rows of variable assignments
|
||||
|
||||
Query evaluation supports atoms and conjunctions. Conjunctions are evaluated with natural join. Law checking evaluates a law antecedent to produce witness bindings, then checks the consequent for each witness. Violations carry the law path and witness values.
|
||||
|
||||
This gives a simple snapshot oracle shape for validation experiments:
|
||||
|
||||
```text
|
||||
facts + generated law
|
||||
-> antecedent witnesses
|
||||
-> consequent checks
|
||||
-> violation rows
|
||||
```
|
||||
|
||||
## Storage Engine
|
||||
|
||||
`geolog-lang` uses a custom in-memory storage engine, not SQLite, Postgres, or another external database. The storage module is `Geolog.DB.InMemory`.
|
||||
|
||||
The core storage structures are:
|
||||
|
||||
- `DB`: schema, relations, and next entity IDs
|
||||
- `Relation`: fixed-arity tuple set plus a secondary index
|
||||
- `Val`: stored values, including integers, text, and entity identities
|
||||
- `Bindings`: query result rows as variable-to-value maps
|
||||
|
||||
The relation storage shape is:
|
||||
|
||||
```text
|
||||
Relation {
|
||||
arity: tuple width,
|
||||
members: set of tuples,
|
||||
index: map from (column position, value) to matching tuples
|
||||
}
|
||||
```
|
||||
|
||||
Inserts add a tuple to the `members` set and update the secondary index for every `(column, value)` pair in the tuple. Atom evaluation uses literal terms to probe those secondary indexes, intersects candidate tuple sets when multiple literal constraints are present, and then produces variable bindings.
|
||||
|
||||
Conjunction evaluation has two paths:
|
||||
|
||||
- naive evaluation: evaluate each atom, then natural-join the resulting bindings
|
||||
- planned evaluation: use `Geolog.DB.Plan` to build a semijoin-based join plan, then evaluate that plan against the same in-memory relations
|
||||
|
||||
The checked insert path inserts into a temporary updated database and checks generated laws such as `foreignKeys`. If the law check finds violations, the updated database is not returned. This gives rollback-like behavior at the API boundary, but it is not a transactional storage engine with durable logs or multi-step rollback.
|
||||
|
||||
## Storage API
|
||||
|
||||
`geolog-lang` does not define a storage abstraction or backend trait. It directly uses the concrete in-memory API in `Geolog.DB.InMemory`.
|
||||
|
||||
The minimal storage API it needs is:
|
||||
|
||||
```text
|
||||
schema registration:
|
||||
fromTheory :: FlatTheory -> DB
|
||||
|
||||
entity allocation:
|
||||
freshEntity :: Path -> DB -> (Val, DB)
|
||||
|
||||
writes:
|
||||
insertRow :: Path -> [Val] -> DB -> DB
|
||||
checkedInsertRow :: FlatTheory -> Path -> [Val] -> DB -> Either InsertError DB
|
||||
|
||||
reads:
|
||||
evalAtom :: DB -> QAtom -> Either DBError Bindings
|
||||
evalConjunction :: DB -> [QAtom] -> Either DBError Bindings
|
||||
|
||||
validation:
|
||||
checkLaw :: DB -> Path -> Law -> Either DBError [LawViolation]
|
||||
```
|
||||
|
||||
The planner also depends on planned conjunction evaluation:
|
||||
|
||||
```text
|
||||
planConjunction :: [QAtom] -> JoinPlan
|
||||
evalRootedPlan :: DB -> JoinPlan -> Either DBError Bindings
|
||||
evalConjunctionPlanned :: DB -> [QAtom] -> Either DBError Bindings
|
||||
```
|
||||
|
||||
For this repository, an adapter-facing storage interface should probably provide:
|
||||
|
||||
1. flat schema registration from lowered tables,
|
||||
2. fixed-arity tuple insertion into named relations,
|
||||
3. stable entity ID allocation per table path,
|
||||
4. tuple lookup by table and positional constraints,
|
||||
5. variable binding output for query atoms,
|
||||
6. natural join and semijoin support, or enough scans and index probes for the planner to implement them,
|
||||
7. generated-law checking with structured violations, and
|
||||
8. checked insert behavior that rejects or rolls back invalid writes.
|
||||
|
||||
This API is snapshot-oriented. It is not yet a durable transaction API, an incremental DBSP API, or a general SQL-like storage layer.
|
||||
|
||||
## Query Planning
|
||||
|
||||
The planner targets conjunctive queries over the in-memory database. It:
|
||||
|
||||
1. evaluates each atom independently,
|
||||
2. builds an atom intersection graph weighted by shared variables,
|
||||
3. computes a maximum spanning forest,
|
||||
4. performs bottom-up and top-down semijoin reduction, and
|
||||
5. performs full joins along the forest.
|
||||
|
||||
The tests compare planned conjunction evaluation against naive conjunction evaluation on generated graph workloads. This is a useful test pattern for this repository: every planned execution path should have a simple snapshot or naive oracle while the planner is still experimental.
|
||||
|
||||
## Adapter Takeaways
|
||||
|
||||
The practical adapter direction is:
|
||||
|
||||
1. Model a small `FlatTheory`-like IR in Rust before implementing a full Geolog frontend.
|
||||
2. Start with generated foreign-key and totality laws because they have clear violation semantics.
|
||||
3. Represent violation outputs with law identity, violation kind, relation or consequent identity, and bound variable values.
|
||||
4. Keep source elaboration, relational lowering, planning, and execution as separate modules.
|
||||
5. Add textual plan output early so planner behavior is inspectable.
|
||||
6. Use a naive snapshot evaluator as the oracle for planned or maintained execution.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- How much of Geolog source syntax should this repo parse directly versus using hand-built fixtures first?
|
||||
- Should equality in consequents be supported before disjunction, existential witnesses, or recursive laws?
|
||||
- Should totality validation use explicit missing-output violations or a generated required-output relation?
|
||||
- How should entity identities map onto this repo's storage transaction and rollback model?
|
||||
- Which law subset is enough to test Geomerge-style maintained violation relations without building a full chase engine?
|
||||
|
||||
## Next Experiment Shape
|
||||
|
||||
A small vertical slice for this repository could be:
|
||||
|
||||
```text
|
||||
input:
|
||||
table node(id)
|
||||
table edge(src, dst, id)
|
||||
law edge_foreign_keys:
|
||||
edge(src, dst, id) -> node(src) and node(dst)
|
||||
|
||||
snapshot output:
|
||||
violation rows for missing src or dst nodes
|
||||
|
||||
planned output:
|
||||
textual antijoin plan for edge against node(src) and node(dst)
|
||||
```
|
||||
|
||||
That would test catalog construction, generated-law representation, antijoin planning, deterministic violation rows, and a snapshot oracle without requiring the rest of Geolog.
|
||||
@ -1,135 +0,0 @@
|
||||
## Cozo and LMDB Findings
|
||||
|
||||
Sources inspected: the Cozo source tree at `github.com/cozodb/cozo`, the LMDB source tree at `github.com/LMDB/lmdb`, and the `heed` Rust binding at `github.com/meilisearch/heed`.
|
||||
File paths in this note are relative to the root of the named project's source tree.
|
||||
The aim was to understand how a working Datalog engine (Cozo) implements joins and what a low-level key-value substrate (LMDB) provides that makes those joins cheap.
|
||||
This note summarizes the design lessons and the practical implications for the `query-ops` crate in this playground.
|
||||
|
||||
### Summary
|
||||
|
||||
Cozo is an embedded Datalog database written in Rust.
|
||||
It does not have a separate semijoin operator.
|
||||
Instead, it has one inner-join operator that picks between two strategies based on how each relation is stored: an index-nested-loop strategy that uses ordered range scans over the substrate, and a fallback that materializes one side into a sorted vector and probes it.
|
||||
Semijoin behavior, when needed, emerges from a separate rewrite step called the magic-sets transformation, which converts semijoin-shaped pruning into regular inner joins against derived relations.
|
||||
|
||||
LMDB is a memory-mapped, ordered key-value store with a B+ tree on disk.
|
||||
It exposes a small set of cursor primitives that support prefix iteration, range iteration, and exact-key lookup.
|
||||
These primitives are exactly what an index-nested-loop join needs: seek to a key prefix, then iterate forward while the prefix matches.
|
||||
|
||||
The combined lesson is that a good join does not require a clever operator.
|
||||
It requires the relation to be stored with the join columns at the front of the key, so that the substrate's ordered iteration can do the join itself.
|
||||
|
||||
### Cozo
|
||||
|
||||
#### What It Is
|
||||
|
||||
Cozo is a Datalog database with multiple swappable storage backends, including an in-memory store, SQLite, RocksDB, sled, and TiKV.
|
||||
The execution engine speaks a single narrow storage trait whose surface is essentially `get`, `put`, `range_iter`, and `prefix_iter` over byte keys.
|
||||
Each backend implements that trait.
|
||||
The trait definition lives at `cozo-core/src/storage/mod.rs` in the Cozo source tree.
|
||||
|
||||
#### Join Behavior
|
||||
|
||||
The relational algebra at `cozo-core/src/query/ra.rs` in the Cozo source tree defines a single join operator named `InnerJoin`.
|
||||
At execution time it chooses between two strategies based on a check called `join_is_prefix`:
|
||||
|
||||
- prefix join: for each tuple from the left side, the engine builds a byte prefix from the join columns and calls `prefix_iter` on the right relation.
|
||||
The substrate yields all matching tuples in key order.
|
||||
No hash table is built.
|
||||
This path is taken whenever the right side's join columns are stored as the prefix of its key.
|
||||
- materialized join: used when the join columns are not a key prefix.
|
||||
The right side is read fully into a sorted, deduplicated vector, reordered so the join columns come first, then walked with a `starts_with(prefix)` check.
|
||||
This is the build-and-probe family, but with a sorted vector instead of a hash map.
|
||||
|
||||
The choice is made entirely on whether the join columns sit at the front of the stored key.
|
||||
|
||||
#### No Semijoin Operator
|
||||
|
||||
A search of the Cozo source for `semijoin` or `semi_join` returns nothing.
|
||||
Semijoin behavior comes from the magic-sets transformation at `cozo-core/src/query/magic.rs` in the Cozo source tree.
|
||||
This pass rewrites each rule so that body atoms get joined against an auxiliary "magic" relation whose contents encode the binding patterns supplied by the rule's callers.
|
||||
The net effect is the same as semijoining body atoms against caller-supplied filters, but the implementation is a logical rewrite, not a runtime operator.
|
||||
|
||||
#### No Auto-Maintained Secondary Indexes
|
||||
|
||||
Cozo does not maintain secondary indexes automatically.
|
||||
If you want to query a relation by a column order different from how it was declared, you declare a second relation with the columns reordered and keep its contents synchronized at insert time.
|
||||
A covering index is just another stored relation.
|
||||
The decision of which column order to store comes from how you expect to query the data, not from the engine.
|
||||
|
||||
### LMDB
|
||||
|
||||
#### What It Is
|
||||
|
||||
LMDB is a single-file, memory-mapped, ordered key-value store.
|
||||
It uses a B+ tree on disk and exposes reads as zero-copy byte slices that point directly into the mmap.
|
||||
It supports a single writer at a time and many concurrent readers, and it uses shadow paging for MVCC, which means commits are atomic without a write-ahead log.
|
||||
|
||||
#### Cursor Primitives
|
||||
|
||||
A cursor in LMDB is a position inside the B+ tree.
|
||||
The full set of cursor operations is defined by the `MDB_cursor_op` enum in `libraries/liblmdb/lmdb.h` in the LMDB source tree.
|
||||
The operations relevant to join work are:
|
||||
|
||||
- `MDB_SET_RANGE`: position at the first key greater than or equal to a given key.
|
||||
This is the seek primitive that makes prefix scans possible.
|
||||
- `MDB_NEXT`: advance one step forward in key order.
|
||||
Combined with `MDB_SET_RANGE` and a per-step prefix check, this gives you ordered range iteration.
|
||||
- `MDB_SET` and `MDB_SET_KEY`: exact-key positioning, used for point lookups.
|
||||
- `MDB_FIRST` and `MDB_LAST`: positional endpoints.
|
||||
|
||||
For databases opened with the `MDB_DUPSORT` flag, one key can carry multiple sorted values, and additional operations apply: `MDB_GET_BOTH`, `MDB_NEXT_DUP`, `MDB_FIRST_DUP`.
|
||||
This is useful when a relation is encoded as "key = join columns, duplicate values = remaining columns": the set of duplicates is itself a secondary index over the join key.
|
||||
|
||||
#### Rust Binding
|
||||
|
||||
`heed` is the idiomatic Rust binding for LMDB.
|
||||
It wraps the cursor operations as `RoCursor` and `RwCursor` and returns key and value byte slices tied to the transaction lifetime, so reads remain zero-copy.
|
||||
Meilisearch uses `heed` in production, so the binding is well exercised.
|
||||
|
||||
### LMDB Versus RocksDB
|
||||
|
||||
Both LMDB and RocksDB are ordered key-value stores with prefix and range scans, but their internal designs lead to different operational profiles.
|
||||
|
||||
LMDB highlights:
|
||||
|
||||
- B+ tree on disk, memory mapped
|
||||
- Single writer at a time, many concurrent readers
|
||||
- Zero-copy reads from the mmap
|
||||
- Append-only on-disk format; deletes leave reclaimable free pages
|
||||
- File size grows up to a configured `mapsize`
|
||||
- No background compaction
|
||||
- Manual reclaim with `mdb_copy --compact`
|
||||
|
||||
RocksDB highlights:
|
||||
|
||||
- Log-structured merge tree
|
||||
- Multiple concurrent writers
|
||||
- Background compaction
|
||||
- Higher write throughput at the cost of write amplification
|
||||
- Reads may traverse multiple levels with bloom-filter checks
|
||||
- Engine manages its own disk layout
|
||||
|
||||
For a read-heavy prototype with batch inserts, LMDB is the closer fit: predictable read costs, cheap range scans, and zero-copy probes.
|
||||
RocksDB earns its overhead when sustained write throughput is the bottleneck.
|
||||
|
||||
### Practical Implications
|
||||
|
||||
The current `query-ops` crate works on in-memory `Vec<Row>` values and will implement semijoin and natural join with a transient hash on one side.
|
||||
The Cozo design suggests a clear upgrade path once a real substrate is added.
|
||||
|
||||
Short term: keep the in-memory operator and build a transient hash on the smaller side.
|
||||
This is correct, easy to test, and easy to reason about.
|
||||
|
||||
Medium term: when relations move into a substrate like LMDB, encode each relation so that the join columns sit at the prefix of the key, or use a `DUPSORT` database where the duplicate values carry the remaining columns.
|
||||
At that point the join operator becomes a cursor pattern (`MDB_SET_RANGE` followed by `MDB_NEXT` while the prefix matches), and the separate hash-building step disappears.
|
||||
|
||||
Index discipline: if a relation needs to be joined two different ways, store it twice with different prefix orders.
|
||||
There is no clever-indexing shortcut in either Cozo or LMDB, and trying to invent one is unlikely to be worth the cost.
|
||||
|
||||
The takeaway is that the operator surface in `query-ops` is fine for an in-memory prototype, but the substrate decision is the load-bearing one for performance.
|
||||
We do not need to design around it now, but the natural successor to the current operators is a key-encoding discipline rather than a more elaborate operator implementation.
|
||||
|
||||
### Changelog
|
||||
|
||||
- **June 2, 2026** -- The first version of this document was made.
|
||||
75
tools/exporter/README.md
Normal file
@ -0,0 +1,75 @@
|
||||
## 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.
|
||||
|
||||
```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.
|
||||
@ -7,7 +7,7 @@
|
||||
-- against.
|
||||
|
||||
packages:
|
||||
glog-exporter.cabal
|
||||
plan-exporter.cabal
|
||||
../../external/geolog/geolog-lang/geolog-lang.cabal
|
||||
../../external/geolog/data-partition/data-partition.cabal
|
||||
../../external/geolog/diagnostician/diagnostician.cabal
|
||||
|
||||
140
tools/exporter/examples/cartesian.scenario.json
Normal file
@ -0,0 +1,140 @@
|
||||
{
|
||||
"name": "cartesian",
|
||||
"_description": "Two disjoint atoms over different tables. Exercises the 'stray' branch of planConjunction's spanning forest (no shared variables = no edge in the intersection graph) and the linear chain of natural-joins that fullJoinForest emits over disconnected components.",
|
||||
"schema": {
|
||||
"left": {
|
||||
"columns": [
|
||||
{
|
||||
"entity": "left"
|
||||
}
|
||||
]
|
||||
},
|
||||
"right": {
|
||||
"columns": [
|
||||
{
|
||||
"entity": "right"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"facts": {
|
||||
"left": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"left",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"left",
|
||||
2
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"right": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"right",
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"right",
|
||||
20
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"atoms": [
|
||||
{
|
||||
"table": "left",
|
||||
"values": {
|
||||
"0": {
|
||||
"var": "a"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "right",
|
||||
"values": {
|
||||
"0": {
|
||||
"var": "b"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"expected_bindings": {
|
||||
"columns": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"left",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"right",
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"left",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"right",
|
||||
20
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"left",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"right",
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"left",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"right",
|
||||
20
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
119
tools/exporter/examples/self_loop.scenario.json
Normal file
@ -0,0 +1,119 @@
|
||||
{
|
||||
"name": "self-loop",
|
||||
"_description": "Single-atom query with a repeated variable across two columns: edge(x, x, _). Exercises evalAtom's equality-enforcement path; the planner emits one PlanEvalAtom node and no joins.",
|
||||
"schema": {
|
||||
"edge": {
|
||||
"columns": [
|
||||
{
|
||||
"entity": "node"
|
||||
},
|
||||
{
|
||||
"entity": "node"
|
||||
},
|
||||
{
|
||||
"entity": "edge"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"facts": {
|
||||
"edge": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"edge",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"edge",
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"edge",
|
||||
3
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"atoms": [
|
||||
{
|
||||
"table": "edge",
|
||||
"values": {
|
||||
"0": {
|
||||
"var": "x"
|
||||
},
|
||||
"1": {
|
||||
"var": "x"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"expected_bindings": {
|
||||
"columns": [
|
||||
"x"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
3
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
156
tools/exporter/examples/three_atom_chain.scenario.json
Normal file
@ -0,0 +1,156 @@
|
||||
{
|
||||
"name": "three-atom-chain",
|
||||
"schema": {
|
||||
"node": {
|
||||
"columns": [
|
||||
{
|
||||
"entity": "node"
|
||||
}
|
||||
]
|
||||
},
|
||||
"edge": {
|
||||
"columns": [
|
||||
{
|
||||
"entity": "node"
|
||||
},
|
||||
{
|
||||
"entity": "node"
|
||||
},
|
||||
{
|
||||
"entity": "edge"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"facts": {
|
||||
"node": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
3
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"edge": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"edge",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"edge",
|
||||
2
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"atoms": [
|
||||
{
|
||||
"table": "node",
|
||||
"values": {
|
||||
"0": {
|
||||
"var": "a"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "edge",
|
||||
"values": {
|
||||
"0": {
|
||||
"var": "a"
|
||||
},
|
||||
"1": {
|
||||
"var": "b"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "edge",
|
||||
"values": {
|
||||
"0": {
|
||||
"var": "b"
|
||||
},
|
||||
"1": {
|
||||
"var": "c"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"expected_bindings": {
|
||||
"columns": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
3
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
144
tools/exporter/examples/two_atom_join.scenario.json
Normal file
@ -0,0 +1,144 @@
|
||||
{
|
||||
"name": "two-atom-join",
|
||||
"schema": {
|
||||
"node": {
|
||||
"columns": [
|
||||
{
|
||||
"entity": "node"
|
||||
}
|
||||
]
|
||||
},
|
||||
"edge": {
|
||||
"columns": [
|
||||
{
|
||||
"entity": "node"
|
||||
},
|
||||
{
|
||||
"entity": "node"
|
||||
},
|
||||
{
|
||||
"entity": "edge"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"facts": {
|
||||
"node": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"edge": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"edge",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"edge",
|
||||
2
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"atoms": [
|
||||
{
|
||||
"table": "node",
|
||||
"values": {
|
||||
"0": {
|
||||
"var": "a"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"table": "edge",
|
||||
"values": {
|
||||
"0": {
|
||||
"var": "a"
|
||||
},
|
||||
"1": {
|
||||
"var": "b"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"expected_bindings": {
|
||||
"columns": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"entity": [
|
||||
"node",
|
||||
1
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,20 @@
|
||||
cabal-version: 3.4
|
||||
name: glog-exporter
|
||||
name: plan-exporter
|
||||
version: 0.1.0.0
|
||||
license: MIT OR Apache-2.0
|
||||
author: storage-engine-playground
|
||||
synopsis: Export geolog-lang join plans as JSON for the Rust runner.
|
||||
synopsis: Export conjunctive-query plans as JSON for the Rust plan-runner.
|
||||
description:
|
||||
Builds a FlatTheory + facts + a list of QAtoms for a named scenario,
|
||||
runs Geolog.DB.Plan.planConjunction, and emits a JSON document that
|
||||
crates/glog-runner consumes. This allows the playground use query-ops and
|
||||
storage end-to-end with a real Yannakakis plan produced by the geolog
|
||||
frontend, not a hand-written fixture.
|
||||
Reads a scenario (FlatTheory + facts + a list of QAtoms) from JSON,
|
||||
runs Geolog.DB.Plan.planConjunction, and emits a plan IR JSON document
|
||||
that crates/plan-runner consumes. The IR is the contract between the
|
||||
Haskell frontend and the Rust executor; this tool is currently the only
|
||||
producer, but any frontend that emits the same JSON shape can drive the
|
||||
runner.
|
||||
|
||||
build-type: Simple
|
||||
|
||||
executable glog-export
|
||||
executable plan-export
|
||||
main-is: Main.hs
|
||||
hs-source-dirs: src
|
||||
default-language: GHC2024
|
||||
@ -32,5 +33,6 @@ executable glog-export
|
||||
, base
|
||||
, bytestring
|
||||
, containers
|
||||
, fnotation
|
||||
, geolog-lang
|
||||
, text
|
||||
@ -1,31 +1,41 @@
|
||||
-- | Exports a geolog-lang join plan as JSON for the Rust runner in
|
||||
-- @crates/glog-runner@.
|
||||
-- | Reads a @.scenario.json@ example, plans its conjunction with
|
||||
-- @Geolog.DB.Plan.planConjunction@, and writes a runner-IR JSON plan that
|
||||
-- @crates\/plan-runner@ consumes.
|
||||
--
|
||||
-- Invocation:
|
||||
--
|
||||
-- @
|
||||
-- cabal run glog-export -- <scenario> > plan.json
|
||||
-- cabal run plan-export -- <scenario.json>
|
||||
-- @
|
||||
--
|
||||
-- Available scenarios: @three-atom-chain@.
|
||||
-- The scenario format is documented in @examples\/README@ or by example
|
||||
-- (@examples\/*.scenario.json@); the output shape is documented in
|
||||
-- @crates\/plan-runner\/src\/lib.rs@.
|
||||
--
|
||||
-- The output shape is documented in @crates\/glog-runner\/src\/lib.rs@.
|
||||
-- This program is the canonical producer: any change to the IR should
|
||||
-- start here, with the Rust runner updated to match.
|
||||
-- The exporter is also a self-check: before emitting, it runs the planned
|
||||
-- query through @evalConjunctionPlanned@ and verifies the bindings match
|
||||
-- the scenario's @expected_bindings@. A mismatched scenario fails loudly
|
||||
-- here rather than handing a bad fixture to the Rust runner.
|
||||
module Main (main) where
|
||||
|
||||
import Algebra.Graph qualified as AG
|
||||
import Data.Aeson ((.=))
|
||||
import Control.Monad (unless)
|
||||
import Data.Aeson ((.!=), (.:), (.:?), (.=))
|
||||
import Data.Aeson qualified as Aeson
|
||||
import Data.Aeson.Encode.Pretty qualified as AesonPretty
|
||||
import Data.Aeson.Key qualified as Key
|
||||
import Data.Aeson.KeyMap qualified as KM
|
||||
import Data.Aeson.Types (Parser)
|
||||
import Data.ByteString.Lazy.Char8 qualified as LBS8
|
||||
import Data.Foldable (toList)
|
||||
import Data.List (sortOn)
|
||||
import Data.Map.Strict (Map)
|
||||
import Data.Map.Strict qualified as Map
|
||||
import Data.Set qualified as Set
|
||||
import Data.Text (Text)
|
||||
import Data.Text qualified as T
|
||||
import Data.String (fromString)
|
||||
import FNotation.Names (Name)
|
||||
import Geolog.DB.InMemory
|
||||
import Geolog.DB.Plan
|
||||
import Geolog.IR qualified as IR
|
||||
@ -33,74 +43,142 @@ import System.Environment (getArgs)
|
||||
import System.Exit (die)
|
||||
import System.IO (hPutStrLn, stderr)
|
||||
|
||||
-- * Scenario plumbing
|
||||
-- * Scenario file format
|
||||
--
|
||||
-- A scenario fixes a schema, a set of ground facts, and a conjunction of
|
||||
-- query atoms. The exporter is intentionally code-driven (not @.glog@
|
||||
-- driven): @.glog@ files declare theories, not queries, so the query
|
||||
-- side has to live in Haskell either way.
|
||||
-- Mirrors @Geolog.IR.FlatTheory@ + @[(Path, [Val])]@ + @[QAtom]@. The
|
||||
-- 'Expected' block is optional but, when present, the exporter cross-
|
||||
-- checks it against the planner's own evaluation before emitting.
|
||||
|
||||
data Scenario = Scenario
|
||||
{ scName :: String
|
||||
, scTheory :: IR.FlatTheory
|
||||
{ scName :: Text
|
||||
, scSchema :: Map IR.Path SchemaEntry
|
||||
, scFacts :: [(IR.Path, [Val])]
|
||||
, scAtoms :: [QAtom]
|
||||
, scExpected :: Maybe Expected
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
-- * three-atom-chain
|
||||
--
|
||||
-- Mirrors @DB.InMemoryTest@ "matches evalConjunction on three-atom chain".
|
||||
-- node = {e1, e2, e3}, edge = {(e1,e2,ee1), (e2,e3,ee2)}.
|
||||
-- Conjunction: node(a), edge(a, b, _), edge(b, c, _).
|
||||
data SchemaEntry = SchemaEntry
|
||||
{ seColumns :: [IR.ColType]
|
||||
, sePrimaryKey :: Maybe [Int]
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
nodePath, edgePath :: IR.Path
|
||||
nodePath = ["node"]
|
||||
edgePath = ["edge"]
|
||||
data Expected = Expected
|
||||
{ exColumns :: [Text]
|
||||
, exRows :: [[Val]]
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
threeAtomChain :: Scenario
|
||||
threeAtomChain =
|
||||
Scenario
|
||||
{ scName = "three-atom-chain"
|
||||
, scTheory =
|
||||
IR.FlatTheory
|
||||
{ tables =
|
||||
-- ** JSON parsers
|
||||
|
||||
parsePath :: Aeson.Value -> Parser IR.Path
|
||||
parsePath = Aeson.withText "path" \t -> pure [nameFromText t]
|
||||
|
||||
-- | Build a single-segment 'Name' from text. Multi-segment names (which
|
||||
-- would carry a non-empty 'init' field) aren't needed by any current
|
||||
-- example; if a scenario wants @"a/b"@-style paths, extend this helper.
|
||||
nameFromText :: Text -> Name
|
||||
nameFromText = fromString . T.unpack
|
||||
|
||||
instance Aeson.FromJSON SchemaEntry where
|
||||
parseJSON = Aeson.withObject "SchemaEntry" \o ->
|
||||
SchemaEntry <$> o .: "columns" <*> o .:? "primaryKey"
|
||||
|
||||
instance Aeson.FromJSON IR.ColType where
|
||||
parseJSON = Aeson.withObject "ColType" \o -> do
|
||||
case KM.toList o of
|
||||
[("entity", v)] -> IR.EntityType <$> parsePath v
|
||||
[("prim", v)] -> IR.PrimType <$> parsePrim v
|
||||
_ -> fail "ColType: expected {\"entity\": <path>} or {\"prim\": \"int\"|\"string\"}"
|
||||
|
||||
parsePrim :: Aeson.Value -> Parser IR.PrimType
|
||||
parsePrim = Aeson.withText "prim type" \case
|
||||
"int" -> pure IR.PrimInt
|
||||
"string" -> pure IR.PrimString
|
||||
other -> fail ("unknown primitive type: " <> T.unpack other)
|
||||
|
||||
parseVal :: Aeson.Value -> Parser Val
|
||||
parseVal = Aeson.withObject "Val" \o ->
|
||||
case KM.toList o of
|
||||
[("int", v)] -> ValInt <$> Aeson.parseJSON v
|
||||
[("str", v)] -> ValText <$> Aeson.parseJSON v
|
||||
[("entity", v)] -> parseEntity v
|
||||
_ -> fail "Val: expected {\"int\": ..} | {\"str\": ..} | {\"entity\": [<path>, <id>]}"
|
||||
where
|
||||
parseEntity = Aeson.withArray "entity" \arr -> case toList arr of
|
||||
[pv, nv] -> do
|
||||
p <- parsePath pv
|
||||
n <- Aeson.parseJSON nv
|
||||
pure (ValEntity p n)
|
||||
_ -> fail "entity: expected [<path>, <id>]"
|
||||
|
||||
parseQVal :: Aeson.Value -> Parser QVal
|
||||
parseQVal = Aeson.withObject "QVal" \o ->
|
||||
case KM.toList o of
|
||||
[("var", v)] -> QVar . Var <$> Aeson.parseJSON v
|
||||
[("lit", v)] -> QLit <$> parseVal v
|
||||
_ -> fail "QVal: expected {\"var\": \"name\"} or {\"lit\": <value>}"
|
||||
|
||||
parseAtom :: Aeson.Value -> Parser QAtom
|
||||
parseAtom = Aeson.withObject "QAtom" \o -> do
|
||||
qaTable <- o .: "table" >>= parsePath
|
||||
qaRowId <- o .:? "rowId" >>= traverse parseQVal
|
||||
values <- o .: "values" :: Parser (Map Text Aeson.Value)
|
||||
qaValues <-
|
||||
Map.fromList
|
||||
[ (nodePath, IR.Table {columns = [IR.EntityType nodePath], primaryKey = Nothing})
|
||||
, (edgePath, IR.Table {columns = [IR.EntityType nodePath, IR.EntityType nodePath, IR.EntityType edgePath], primaryKey = Nothing})
|
||||
]
|
||||
<$> traverse
|
||||
( \(k, v) -> case reads (T.unpack k) of
|
||||
[(i, "")] -> (i,) <$> parseQVal v
|
||||
_ -> fail ("non-integer key in atom values: " <> T.unpack k)
|
||||
)
|
||||
(Map.toList values)
|
||||
pure QAtom {qaTable, qaRowId, qaValues}
|
||||
|
||||
parseExpected :: Aeson.Value -> Parser Expected
|
||||
parseExpected = Aeson.withObject "Expected" \o -> do
|
||||
exColumns <- o .: "columns"
|
||||
rawRows <- o .: "rows" :: Parser [[Aeson.Value]]
|
||||
exRows <- traverse (traverse parseVal) rawRows
|
||||
pure Expected {exColumns, exRows}
|
||||
|
||||
instance Aeson.FromJSON Scenario where
|
||||
parseJSON = Aeson.withObject "Scenario" \o -> do
|
||||
scName <- o .:? "name" .!= "unnamed"
|
||||
rawSchema <- o .: "schema" :: Parser (Map Text SchemaEntry)
|
||||
let scSchema = Map.fromList [([nameFromText k], v) | (k, v) <- Map.toList rawSchema]
|
||||
rawFacts <- o .:? "facts" .!= mempty :: Parser (Map Text [[Aeson.Value]])
|
||||
scFacts <-
|
||||
concat
|
||||
<$> traverse
|
||||
( \(name, rows) -> do
|
||||
let path = [nameFromText name]
|
||||
parsedRows <- traverse (traverse parseVal) rows
|
||||
pure [(path, row) | row <- parsedRows]
|
||||
)
|
||||
(Map.toList rawFacts)
|
||||
rawAtoms <- o .: "atoms" :: Parser [Aeson.Value]
|
||||
scAtoms <- traverse parseAtom rawAtoms
|
||||
scExpected <- o .:? "expected_bindings" >>= traverse parseExpected
|
||||
pure Scenario {scName, scSchema, scFacts, scAtoms, scExpected}
|
||||
|
||||
-- * Scenario → FlatTheory + DB + atoms
|
||||
|
||||
toFlatTheory :: Scenario -> IR.FlatTheory
|
||||
toFlatTheory sc =
|
||||
IR.FlatTheory
|
||||
{ tables = Map.map (\e -> IR.Table {columns = seColumns e, primaryKey = sePrimaryKey e}) sc.scSchema
|
||||
, laws = Map.empty
|
||||
}
|
||||
, scFacts =
|
||||
[ (nodePath, [ValEntity nodePath 1])
|
||||
, (nodePath, [ValEntity nodePath 2])
|
||||
, (nodePath, [ValEntity nodePath 3])
|
||||
, (edgePath, [ValEntity nodePath 1, ValEntity nodePath 2, ValEntity edgePath 1])
|
||||
, (edgePath, [ValEntity nodePath 2, ValEntity nodePath 3, ValEntity edgePath 2])
|
||||
]
|
||||
, scAtoms =
|
||||
[ QAtom {qaTable = nodePath, qaRowId = Nothing, qaValues = Map.singleton 0 (QVar (Var "a"))}
|
||||
, QAtom {qaTable = edgePath, qaRowId = Nothing, qaValues = Map.fromList [(0, QVar (Var "a")), (1, QVar (Var "b"))]}
|
||||
, QAtom {qaTable = edgePath, qaRowId = Nothing, qaValues = Map.fromList [(0, QVar (Var "b")), (1, QVar (Var "c"))]}
|
||||
]
|
||||
}
|
||||
|
||||
scenarios :: [Scenario]
|
||||
scenarios = [threeAtomChain]
|
||||
populateDB :: Scenario -> DB
|
||||
populateDB sc = foldl (\d (p, row) -> insertRow p row d) (fromTheory (toFlatTheory sc)) sc.scFacts
|
||||
|
||||
-- * JSON encoding
|
||||
-- * JSON encoding for the plan-runner IR
|
||||
--
|
||||
-- The shape mirrors the IR in @crates/glog-runner/src/lib.rs@:
|
||||
--
|
||||
-- > {
|
||||
-- > "schema": {<name>: <arity>, ...},
|
||||
-- > "facts": {<name>: [[<value>, ...], ...], ...},
|
||||
-- > "query": {"root": <id>, "nodes": [{"id": <id>, "action": <action>}, ...]}
|
||||
-- > }
|
||||
-- The shape is the same one we settled on earlier; see
|
||||
-- @crates/plan-runner/src/lib.rs@.
|
||||
|
||||
-- | Render a 'Geolog.IR.Path' (a list of 'FNotation.Names.Name') as a flat
|
||||
-- string for use as a relation name on the Rust side. Each 'Name' is
|
||||
-- already shown with @\/@ between its own init segments and last, so we
|
||||
-- reuse 'show' and join Names with @\/@ too.
|
||||
pathText :: IR.Path -> Text
|
||||
pathText = T.intercalate "/" . map (T.pack . show)
|
||||
|
||||
@ -119,10 +197,6 @@ encodeTerm = \case
|
||||
QVar (Var name) -> Aeson.object ["var" .= name]
|
||||
QLit v -> Aeson.object ["lit" .= encodeValue v]
|
||||
|
||||
-- | Flatten an atom into one term per stored column, mirroring
|
||||
-- @Geolog.DB.InMemory.toFlatArgs@: @qaValues@ keys map to positions
|
||||
-- @0..n-2@, @qaRowId@ (if present) maps to position @n-1@, and any
|
||||
-- missing positions become wildcard variables with locally-unique names.
|
||||
flattenAtom :: Int -> Int -> QAtom -> [Aeson.Value]
|
||||
flattenAtom atomIdx arity qa =
|
||||
[ encodeTerm (Map.findWithDefault (wildcard atomIdx pos) pos merged)
|
||||
@ -145,9 +219,6 @@ encodeAtom tables atomIdx qa =
|
||||
Just t -> length t.columns
|
||||
Nothing -> error ("encodeAtom: unknown table " <> show qa.qaTable)
|
||||
|
||||
-- | Stable atom indexing keyed by atom identity, so the wildcard names in
|
||||
-- @flattenAtom@ are deterministic across runs even if the planner's node
|
||||
-- ordering changes.
|
||||
atomIndex :: [QAtom] -> Map QAtom Int
|
||||
atomIndex atoms = Map.fromList (zip (Set.toList (Set.fromList atoms)) [0 ..])
|
||||
|
||||
@ -176,9 +247,6 @@ encodeNode tables idx n =
|
||||
]
|
||||
]
|
||||
|
||||
-- | Render a 'PlanGraph' as the JSON the runner consumes. Empty graphs
|
||||
-- produce @{"root": 0, "nodes": []}@, which the runner treats as a
|
||||
-- well-formed but empty query.
|
||||
encodeQuery :: Map IR.Path IR.Table -> Map QAtom Int -> PlanGraph -> Aeson.Value
|
||||
encodeQuery tables idx (PlanGraph g)
|
||||
| null nodes =
|
||||
@ -192,24 +260,30 @@ encodeQuery tables idx (PlanGraph g)
|
||||
nodes = sortOn (.graphId.unPlanNodeId) (AG.vertexList g)
|
||||
rootId = case graphRoot (PlanGraph g) of
|
||||
Just (PlanNodeId i) -> i
|
||||
-- Non-empty graph with no topological root means a cycle, which
|
||||
-- planConjunction never produces. Fall back to the last id rather
|
||||
-- than crashing so a bug here is still inspectable.
|
||||
Nothing -> (.graphId.unPlanNodeId) (last nodes)
|
||||
|
||||
encodeExpected :: Expected -> Aeson.Value
|
||||
encodeExpected ex =
|
||||
Aeson.object
|
||||
[ "columns" .= exColumns ex
|
||||
, "rows" .= map (map encodeValue) (exRows ex)
|
||||
]
|
||||
|
||||
encodePlan :: Scenario -> Aeson.Value
|
||||
encodePlan sc =
|
||||
Aeson.object
|
||||
[ "_scenario" .= sc.scName
|
||||
, "schema" .= Aeson.object
|
||||
[pathKey p .= length t.columns | (p, t) <- Map.toList sc.scTheory.tables]
|
||||
, "facts" .= Aeson.object
|
||||
[pathKey p .= map (map encodeValue) rows | (p, rows) <- groupedFacts sc.scFacts]
|
||||
, "query" .= encodeQuery sc.scTheory.tables (atomIndex sc.scAtoms) (planConjunction sc.scAtoms)
|
||||
( [ "_scenario" .= scName sc
|
||||
, "schema" .= Aeson.object [pathKey p .= length (seColumns t) | (p, t) <- Map.toList sc.scSchema]
|
||||
, "facts"
|
||||
.= Aeson.object
|
||||
[ pathKey p .= map (map encodeValue) rows
|
||||
| (p, rows) <- groupedFacts sc.scFacts
|
||||
]
|
||||
, "query" .= encodeQuery (toFlatTheory sc).tables (atomIndex sc.scAtoms) (planConjunction sc.scAtoms)
|
||||
]
|
||||
++ maybe [] (\e -> ["expected_bindings" .= encodeExpected e]) sc.scExpected
|
||||
)
|
||||
|
||||
-- | Group facts by table while preserving table-first-seen order and
|
||||
-- per-table insertion order.
|
||||
groupedFacts :: [(IR.Path, [Val])] -> [(IR.Path, [[Val]])]
|
||||
groupedFacts = go []
|
||||
where
|
||||
@ -222,17 +296,45 @@ groupedFacts = go []
|
||||
|
||||
-- * Self-check
|
||||
--
|
||||
-- Run the planner's @evalConjunctionPlanned@ against the scenario's DB
|
||||
-- to confirm the plan we're about to emit is well-formed and produces
|
||||
-- non-error output. Catches malformed scenarios before they hand a bad
|
||||
-- plan to the Rust runner.
|
||||
-- Cross-check the planned bindings against any user-supplied
|
||||
-- 'expected_bindings'. Detects two classes of bug before they reach the
|
||||
-- Rust side: a scenario whose 'expected' is wrong, and a planner output
|
||||
-- that disagrees with 'evalConjunction'.
|
||||
|
||||
selfCheck :: Scenario -> IO ()
|
||||
selfCheck sc = do
|
||||
let db = foldl (\d (p, row) -> insertRow p row d) (fromTheory sc.scTheory) sc.scFacts
|
||||
let db = populateDB sc
|
||||
case evalConjunctionPlanned db sc.scAtoms of
|
||||
Left err -> die ("self-check failed for " <> sc.scName <> ": " <> show err)
|
||||
Right _ -> pure ()
|
||||
Left err -> die ("self-check failed for " <> T.unpack sc.scName <> ": " <> show err)
|
||||
Right actual -> case sc.scExpected of
|
||||
Nothing -> pure ()
|
||||
Just expected -> verifyAgainstExpected sc.scName expected actual
|
||||
|
||||
verifyAgainstExpected :: Text -> Expected -> Bindings -> IO ()
|
||||
verifyAgainstExpected name expected actual = do
|
||||
let actualCols = actual.cols
|
||||
expectedCols = Set.fromList (map Var (exColumns expected))
|
||||
unless (Set.isSubsetOf expectedCols actualCols) $
|
||||
die $
|
||||
"self-check failed for "
|
||||
<> T.unpack name
|
||||
<> ": expected_bindings names columns not produced by the plan: "
|
||||
<> show (Set.difference expectedCols actualCols)
|
||||
let projectedActual = Set.map (`projectOn` exColumns expected) actual.table
|
||||
expectedProjected = Set.fromList (map (zip (exColumns expected)) (exRows expected))
|
||||
expectedSet = Set.map (Map.fromList . map (\(v, x) -> (Var v, x))) expectedProjected
|
||||
unless (projectedActual == expectedSet) $
|
||||
die $
|
||||
"self-check failed for "
|
||||
<> T.unpack name
|
||||
<> ":\n expected: "
|
||||
<> show expectedSet
|
||||
<> "\n actual: "
|
||||
<> show projectedActual
|
||||
|
||||
projectOn :: Map Var Val -> [Text] -> Map Var Val
|
||||
projectOn row keys =
|
||||
Map.fromList [(Var k, v) | k <- keys, Just v <- [Map.lookup (Var k) row]]
|
||||
|
||||
-- * Entry point
|
||||
|
||||
@ -240,13 +342,13 @@ main :: IO ()
|
||||
main = do
|
||||
args <- getArgs
|
||||
case args of
|
||||
[name] -> case lookup name [(s.scName, s) | s <- scenarios] of
|
||||
Just sc -> do
|
||||
[path] -> do
|
||||
raw <- LBS8.readFile path
|
||||
sc <- case Aeson.eitherDecode raw of
|
||||
Left err -> die ("failed to parse " <> path <> ": " <> err)
|
||||
Right sc -> pure sc
|
||||
selfCheck sc
|
||||
LBS8.putStrLn (AesonPretty.encodePretty (encodePlan sc))
|
||||
Nothing ->
|
||||
die ("unknown scenario: " <> name <> "\navailable: " <> unwords (map (.scName) scenarios))
|
||||
_ -> do
|
||||
hPutStrLn stderr "usage: glog-export <scenario>"
|
||||
hPutStrLn stderr ("scenarios: " <> unwords (map (.scName) scenarios))
|
||||
hPutStrLn stderr "usage: plan-export <scenario.json>"
|
||||
die ""
|
||||
|
||||