diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab0fa1cb..fc582d63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,68 @@ jobs: - name: Check formatting run: cargo fmt --package pg_durable -- --check + generated-matrix: + # Phase 2 + Phase 4 + Phase 3 (#232): the combinator-nesting matrix + # generator, the Phase 4 metamorphic-relations registry, and the Phase 3 + # model-level proptest properties all live in one standalone std-only crate, + # so their lint, determinism, unit, and property gates need no PostgreSQL + # build and run fast as their own blocking gate. The live/quarantine SQL + # (incl. the meta-*.sql relations) is exercised inside the `test` job, which + # already has the extension installed. + name: Generated Matrix (lint + determinism + unit + proptest) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # std-only stable crate — uses the runner's pre-installed stable toolchain + # (no nightly needed, unlike the extension's nightly-rustfmt format job). + + # Lints the whole generator crate INCLUDING the #[cfg(test)] Phase 3 + # proptest module (--all-targets), so prop.rs stays clippy-clean alongside + # the generation binary. + - name: Generator clippy (-D warnings, lints the proptest module) + run: cargo clippy --manifest-path tests/e2e/generated/generator/Cargo.toml --all-targets -- -D warnings + + # `cargo test` runs the std unit tests AND the Phase 3 proptest properties. + # proptest ALWAYS replays the committed proptest-regressions/ corpus first + # (the deterministic regression guard for every past counterexample), then + # explores PROPTEST_CASES fresh random trees per property within the PR + # time budget. The nightly step below widens that budget for deeper search. + - name: Generator unit + property tests (classifier, metamorphic, proptest) + env: + PROPTEST_CASES: 256 + run: cargo test --manifest-path tests/e2e/generated/generator/Cargo.toml + + # Regenerates the manifests in memory and diffs them against the committed + # tests/e2e/generated/{manifest,meta-manifest}.json. Fails if generation + # drifted or a committed golden is stale — keeping generation deterministic. + # (proptest is a dev-dependency only, so it never perturbs these goldens.) + - name: Check manifest determinism + run: cargo run --manifest-path tests/e2e/generated/generator/Cargo.toml -- --check + + # Nightly / on-demand deep proptest exploration: a far larger case budget + # and a fresh random seed each run, to hunt for counterexamples the 256-case + # PR gate misses. Non-blocking (mirrors the quarantine nightly) — any NEW + # minimal counterexample is printed AND written to proptest-regressions/; + # commit that seed to turn it into a blocking regression guard. + - name: Deep proptest exploration (non-blocking, nightly) + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + continue-on-error: true + env: + PROPTEST_CASES: 8192 + run: cargo test --manifest-path tests/e2e/generated/generator/Cargo.toml + + - name: Surface nightly proptest counterexamples + if: always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') + run: | + corpus=tests/e2e/generated/generator/proptest-regressions/prop.txt + if [ -f "$corpus" ] && grep -qE '^cc ' "$corpus"; then + echo "::warning::Phase 3 proptest corpus has saved counterexample seed(s) — review and commit any new ones:" + grep -E '^cc ' "$corpus" + else + echo "No proptest counterexample seeds present — nightly found nothing beyond the committed corpus." + fi + prepare: name: Prepare Matrix runs-on: ubuntu-latest @@ -83,9 +145,13 @@ jobs: test: name: Clippy & Tests (PG${{ matrix.pg_version }}) runs-on: ubuntu-latest - needs: [azure_example_smoke, format, prepare] + needs: [azure_example_smoke, format, generated-matrix, prepare] permissions: contents: read + # Generous bound: this job builds the extension and runs the full E2E suite + # (incl. the live generated matrix) plus upgrade tests. Caps a hung backend + # worker / stuck instance instead of burning the 6h GitHub default. + timeout-minutes: 45 strategy: fail-fast: false matrix: @@ -195,9 +261,60 @@ jobs: - name: Run unit tests run: cargo pgrx test pg${{ matrix.pg_version }} --features http-allow-test-domains - - name: Run E2E tests (default-build phases) + # Phase 2 + Phase 4 (#232): render the combinator matrix and the + # metamorphic relations into tests/e2e/generated/{sql,quarantine}/*.sql + # before the E2E run. The crate is std-only and fast; --include-generated + # below runs the live (sql/) set, which holds both gen-*.sql and meta-*.sql. + - name: Generate Phase 2 + Phase 4 matrix + run: cargo run --manifest-path tests/e2e/generated/generator/Cargo.toml + + - name: Run E2E tests (default-build phases + generated matrix) id: e2e_default_build_phases - run: ./scripts/test-e2e-local.sh --clean --default-build-phases --pg-version ${{ matrix.pg_version }} + run: ./scripts/test-e2e-local.sh --clean --default-build-phases --include-generated --pg-version ${{ matrix.pg_version }} + + # Quarantined shapes (xfail) reproduce known, filed loop-nesting bugs + # (#227/#230/#233): each asserts the *correct* behavior, so it fails today. + # Non-blocking and gated to nightly/manual so PRs stay fast — its purpose is + # to keep those bugs continuously documented, not to gate merges. + # The run collects the base tests/e2e/sql suite plus quarantine/ (NOT the + # live generated sql/ set), so a `gen-* ... PASS` line can only be a + # quarantined shape that unexpectedly passed. + - name: Run quarantined matrix (non-blocking, nightly) + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + continue-on-error: true + run: | + ./scripts/test-e2e-local.sh --clean --include-generated-quarantine --pg-version ${{ matrix.pg_version }} \ + 2>&1 | tee quarantine-pg${{ matrix.pg_version }}.log + + # A quarantined shape asserts the CORRECT behavior for an open bug, so it is + # expected to FAIL. If one unexpectedly PASSES, the underlying loop-nesting + # bug (#227/#230/#233) may be fixed — surface it loudly so the shape can be + # promoted out of quarantine/ into the blocking sql/ set. Never fails the job. + - name: Flag unexpectedly-passing quarantined shapes + if: always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') + run: | + log="quarantine-pg${{ matrix.pg_version }}.log" + if [ ! -f "$log" ]; then + echo "No quarantine log ($log); nothing to inspect." + exit 0 + fi + # Strip ANSI colour codes, then count quarantined (gen-*) PASS lines. + stripped=$(sed -r 's/\x1b\[[0-9;]*m//g' "$log") + passes=$(printf '%s\n' "$stripped" | grep -cE 'gen-[0-9]+ +\.\.\. +PASS$' || true) + if [ "$passes" -gt 0 ]; then + msg="$passes quarantined shape(s) unexpectedly PASSED on PG${{ matrix.pg_version }} — a known loop-nesting bug may be fixed; review for promotion out of quarantine/." + echo "::warning::$msg" + { + echo "### ⚠️ Quarantine watch (PG${{ matrix.pg_version }})" + echo "$msg" + echo "" + echo '```' + printf '%s\n' "$stripped" | grep -E 'gen-[0-9]+ +\.\.\. +PASS$' || true + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + else + echo "All quarantined shapes failed as expected (0 unexpected passes)." + fi - name: Run upgrade tests id: upgrade_tests diff --git a/.gitignore b/.gitignore index b3f4c9cf..9c96fe8d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,11 @@ results/ tmp_check/ tmp_check_iso/ log/ + +# Phase 2 + Phase 4 generated matrix tests — regenerated by the generator on demand. +# The golden manifest.json + meta-manifest.json ARE committed; the per-shape and +# per-relation .sql files (gen-*.sql, meta-*.sql) are not. +/tests/e2e/generated/sql/ +/tests/e2e/generated/quarantine/ +# Generator crate build output (nested crate, not caught by /target/). +/tests/e2e/generated/generator/target/ diff --git a/Cargo.lock b/Cargo.lock index 55e3e3d7..e427d017 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1892,7 +1892,7 @@ dependencies = [ [[package]] name = "pg_durable" -version = "0.2.3" +version = "0.2.4" dependencies = [ "bigdecimal", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 45a400ad..b783f4e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pg_durable" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "PostgreSQL" repository = "https://github.com/microsoft/pg_durable" diff --git a/Makefile b/Makefile index d77f3389..c87816e5 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ PG_VERSION ?= pg17 ACR_REGISTRY ?= myregistry.azurecr.io ACR_IMAGE ?= pg_durable -.PHONY: build test test-unit test-e2e test-regress pg-clean docker-build docker-push pg-install +.PHONY: build test test-unit test-e2e test-regress pg-clean docker-build docker-push pg-install generate-matrix proptest # Default target all: build @@ -29,6 +29,20 @@ test-unit: test-e2e: ./scripts/test.sh --e2e +# Generate the Phase 2 combinator-nesting + Phase 4 metamorphic E2E matrix. +# Writes tests/e2e/generated/sql/*.sql (gitignored: gen-*.sql + meta-*.sql) and +# refreshes manifest.json + meta-manifest.json. +generate-matrix: + cargo run --manifest-path tests/e2e/generated/generator/Cargo.toml + +# Phase 3 (#232): run the model-level proptest properties (random labeled-leaf +# trees over the Phase 4 reference interpreter + renderer, with shrinking). The +# committed proptest-regressions/ corpus replays first; PROPTEST_CASES sets the +# fresh-exploration budget (override for a deeper local hunt, e.g. +# `PROPTEST_CASES=8192 make proptest`). +proptest: + PROPTEST_CASES=$${PROPTEST_CASES:-1024} cargo test --manifest-path tests/e2e/generated/generator/Cargo.toml + # Build Docker image docker-build: docker build --platform linux/amd64 -t pg_durable:latest . @@ -70,6 +84,8 @@ help: @echo " test - Run all tests (unit + E2E)" @echo " test-unit - Run pgrx unit tests only" @echo " test-e2e - Run E2E tests only (Docker)" + @echo " generate-matrix - Generate the Phase 2 + Phase 4 generated E2E matrix" + @echo " proptest - Run the Phase 3 model-level proptest properties (shrinking)" @echo " test-regress - Run pg_regress tests (resets and starts PostgreSQL)" @echo " installcheck - Run pg_regress tests (requires PostgreSQL running, via PGXS)" @echo " docker-build - Build Docker image" diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 5a86fade..932b4e2f 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -186,6 +186,7 @@ df.sql('SELECT 1') ~> df.sql('SELECT 2') | `df.status(id)` | Get status | `df.status('a1b2c3d4')` | | `df.result(id)` | Get result | `df.result('a1b2c3d4')` | | `df.explain(input)` | Visualize graph | `df.explain('a1b2c3d4')` | +| `df.assert_structural_invariants(id, fail_on_violation)` | Validate a completed instance's structure (`STRICT`: a NULL arg returns no rows, so pass a non-NULL boolean) | `df.assert_structural_invariants('a1b2c3d4', true)` | | `df.setvar(name, value)` | Set durable function variable | `df.setvar('api_url', 'https://...')` | | `df.getvar(name)` | Get durable function variable | `df.getvar('api_url')` | | `df.unsetvar(name)` | Remove durable function variable | `df.unsetvar('api_url')` | @@ -1504,6 +1505,47 @@ SELECT started_at, last_seen_at, The background worker updates `last_seen_at` every ~5 seconds as part of its normal operation. +### Validating Structural Invariants + +`df.assert_structural_invariants(instance_id, fail_on_violation => false)` checks a +**completed** instance's node graph against pg_durable's operational-semantics contract +(see `docs/dsl-semantics.md`). It is a read-only diagnostic — handy in tests and CI to catch +regressions in branching, parallelism, and reachability. + +```sql +-- Inspect every invariant (one row each; passed = true when it holds) +SELECT * FROM df.assert_structural_invariants('a1b2c3d4'); + +-- Assertion form: raises an error listing any violations (one-line test check) +SELECT * FROM df.assert_structural_invariants('a1b2c3d4', true); +``` + +**Columns:** `invariant`, `passed`, `node_id`, `detail`. When an invariant holds you get a +single `passed = true` row for it; when it is violated you get one `passed = false` row per +offending node, with `detail` explaining why. + +**Invariants checked** (all derived purely from the `df.nodes` snapshot): + +- `every_reachable_node_completed` — every node on a taken path is `completed`/`failed`. +- `join_all_branches_completed` — every branch of a `JOIN` completed. +- `race_at_least_one_branch_completed` — a resolved `RACE` has a completed branch. +- `untaken_if_branch_pending` — the untaken `IF` branch stays `pending`. +- `join_branch_result_name_disjoint` — parallel `JOIN` branches don't bind the same result name. +- `query_json_well_formed` — internal query JSON (IF/LOOP condition wiring) parses. + +**Caveats:** + +- **Terminal only.** It evaluates a *snapshot*, so it skips instances that are still running + (returning a single `passed = true` `instance_terminal` row). Run it after the instance + reaches `completed`/`failed`/`cancelled` — e.g. after `df.wait_for_completion()`. +- **Relaxed inside loops.** Because `df.nodes` is current state with no per-iteration history, + completeness checks are deliberately relaxed for nodes inside a `LOOP` body to avoid false + positives (a `break` can abandon an in-flight sibling; an `IF` can leave both branches + `completed` across iterations). It never reports a false violation, but catches fewer issues + inside loop bodies. It cannot check execution/iteration counts at all. +- **RLS-scoped.** It only sees instances visible to the calling role; an unknown or + not-visible id returns a `passed = false` `instance_found` row. + --- ## User Isolation & Privileges diff --git a/docs/dsl-semantics.md b/docs/dsl-semantics.md new file mode 100644 index 00000000..54358ce5 --- /dev/null +++ b/docs/dsl-semantics.md @@ -0,0 +1,310 @@ +# pg_durable DSL — Operational Semantics + +**Status:** normative contract. This document defines the *intended* runtime behaviour +of every DSL combinator, especially under nesting. It is the acceptance contract for +the structural-invariant oracle (issue #232 Phase 1) and the reference interpreter +(Phase 5, `tests/e2e/generated/generator/src/refinterp.rs` — §4 Seq, §5 If, §6 Loop, +and §7 Join/Race below are its happens-before rules verbatim; the generated live +`.sql` matrix asserts each of those `≺` edges against the real runtime as +`earlier.wall_clock < later.wall_clock`), and it states explicitly +the nesting contracts that bugs +[#227](https://github.com/microsoft/pg_durable/issues/227) and +[#230](https://github.com/microsoft/pg_durable/issues/230) violate. + +For surface syntax/precedence see [grammar.md](grammar.md). This document is about +*meaning*, not parsing. + +--- + +## 1. Execution model + +A `df.start(expr, label)` call parses `expr` into a **function graph**: an immutable +tree of nodes persisted in `df.nodes`, rooted at `df.instances.root_node`. A background +worker then executes the tree to completion using the [duroxide](https://github.com/microsoft/duroxide) +durable runtime (`src/orchestrations/execute_function_graph.rs`). + +Execution threads two environments through the tree: + +| Environment | Symbol | Mutability | Flow | +|-------------|--------|------------|------| +| **Variables** | `V` | **immutable** for the life of the instance; captured from `df.setvar` at `df.start` time | passed *into* every node and sub-orchestration unchanged; never written back | +| **Named results** | `R` | **mutable**, forward-flowing | a node bound with `|=>`/`df.as` writes `R[name] := result`; later nodes read `$name` | + +Substitution happens **at execution time**: `{name}` reads `V`, `$name` reads `R`, +plus system vars (`$df.instance_id`, etc.). A read of `$name` before the producing node +has run is an error. + +### Node lifecycle + +Every node row carries exactly one status from this set (CHECK constraint, `src/lib.rs:251`): + +``` +pending → running → completed + ↘ failed +``` + +- `pending` — created but not yet started (**includes nodes that are never reached**: + the untaken `IF` branch, a `RACE` loser, the body of a loop that breaks before + re-entering). There is **no** `skipped`/`cancelled` *node* status. +- A `df.nodes` row is **current state**, written in place by the `update-node-status` + activity (`src/activities/update_node_status.rs`). It is **not** an append-only trace. + On loop re-entry (`continue_as_new`) the body's nodes are re-executed and each node row is + **overwritten in place as that node runs again** — there is **no blanket reset** of the body + to `running`. A node an earlier iteration set `completed` therefore keeps that (stale) status + until a later iteration overwrites it; if a later iteration takes a different path the node is + never revisited and the stale value persists. To count executions/iterations you must use + duroxide execution history (`df.instance_executions`), not `df.nodes`. +- A node that raised `Break` is recorded **`completed`** (carrying the break value as its + `result`), not `failed` — including the compound nodes the break unwinds through. Only + `Failure` marks a node `failed`. `result` is non-null only when status is + `completed`/`failed` (`nodes_result_status_chk`). + +Only the **instance** (`df.instances.status`) may end as `cancelled`. + +### Control signals + +A node evaluation yields either a **value** (`String`, the JSON/SQL result), or one of +two errors (`NodeError`, `execute_function_graph.rs:43`): + +- **`Failure(msg)`** — a real error. Propagates up, marks nodes `failed`, fails the instance. +- **`Break(value)`** — *control flow*, not an error. Unwinds (via `?`) to the nearest + enclosing `LOOP`, which catches it and exits normally with `value`. A `Break` with no + enclosing loop reaches the root and fails the instance. + +--- + +## 2. Notation + +We write a small-step judgement + +``` +⟨n, R, V⟩ ⇓ r ⊣ R' +``` + +read: *node `n`, under results `R` and vars `V`, evaluates to result `r` and produces +updated results `R'`.* Effects on the durable store (status writes, activity +side-effects) are noted in prose. `R` is threaded left-to-right unless stated otherwise. +`bind(n, r, R)` means: if `n` has a `result_name = name`, return `R[name := r]`, else `R`. + +--- + +## 3. Leaf nodes + +Leaves perform one durable activity, timer, or wait. Among leaves, **only `SQL`, `HTTP`, +and `SIGNAL` bind** a `result_name`/`|=>`; `SLEEP` and `WAIT_SCHEDULE` currently **ignore** +`result_name` (their handlers have no bind step), so they never write `R`. + +### SQL — `df.sql(q)`, bare `'...'` +``` +⟨SQL q, R, V⟩ ⇓ r ⊣ bind(SQL, r, R) + where r = execute-sql( substitute(q, R, V) ) +``` +Runs the substituted query as one durable activity. `r` is a JSON envelope +(`{row_count, rows, ...}`). Side-effecting SQL runs **exactly once** per node execution +(duroxide replays the recorded result, never the query). + +### HTTP — `df.http(url, method, body, headers, timeout)` +Like SQL: substitutes `url`/`body`/`headers` from `R`,`V`, performs one HTTP activity, +binds result. Result is the response envelope. + +### SLEEP — `df.sleep(secs)` +Durable timer of `secs`. Result is `{"slept":true,"seconds":N}`. Never writes `R`: a +`result_name` on a `SLEEP` is silently ignored (handler has no bind step). + +### WAIT_SCHEDULE — `df.wait_for_schedule(cron)` +Durable timer of a **fixed `wait_seconds` pre-computed at `df.start` (DSL) time** from the +cron expression — it is *not* recomputed against the clock at run time, so under a loop +each iteration waits the same stored delay. Result is `{"scheduled":true}`. Also ignores +`result_name` (no bind step). + +### SIGNAL — `df.wait_for_signal(name, timeout?)` +Waits for an external signal raised by `df.signal(instance_id, name, data)`. With +`timeout`, races the wait against a timer (`select2`). Result: +`{signal_name, timed_out, data}`; on timeout `timed_out=true`. Binds `result_name`. + +--- + +## 4. Sequencing — `THEN` (`~>`, `df.seq`) + +``` +⟨THEN a b, R, V⟩ ⇓ r_b ⊣ bind(THEN, r_b, R₂) + where ⟨a, R, V⟩ ⇓ _ ⊣ R₁ + ⟨b, R₁, V⟩ ⇓ r_b ⊣ R₂ +``` + +- `a` runs to completion first; **its `R` updates are visible to `b`** (this is how + `|=>` then `$name` works). +- The sequence's result is **`b`'s** result; `a`'s value is discarded (but its named + result, if any, persists in `R`). A `result_name` on the `THEN` itself + (`df.as(seq, name)`) binds `b`'s result. +- If `a` raises `Break`/`Failure`, `b` **does not run** and the signal propagates. +- `THEN` is right-result associative: `a ~> b ~> c` ≡ `(a ~> b) ~> c` and yields `c`'s + result with `R` accumulated across all three. + +--- + +## 5. Conditional — `IF` (`?> !>`, `df.if`, `df.if_rows`) + +``` +⟨IF cond then else, R, V⟩ ⇓ r ⊣ bind(IF, r, R') + where b = eval-cond(cond, R, V) + ⟨ (b ? then : else), R, V⟩ ⇓ r ⊣ R' +``` + +- **Exactly one** branch is evaluated. The other branch's subtree is **never reached** + and its node rows stay `pending`. +- `df.if`: `cond` is a SQL/condition node; truthiness via `evaluate_condition` + (boolean `true`, non-zero, non-empty first column). +- `df.if_rows`: `cond` names a prior SQL result; true iff its `row_count > 0`. +- Result is the taken branch's result; bound to `result_name` if present. +- A reachability oracle must read the recorded condition result to decide which branch + *should* be non-`pending`; it cannot assume both children run. + +--- + +## 6. Loop — `LOOP` (`@>`, `df.loop`) + +A `LOOP` has a **body** and an optional **while-condition** (stored as `condition_node` +inside the node's `query` JSON). It is a **do-while**: the body runs, *then* the condition +is checked. + +``` + ⟨body, R, V⟩ ⇓ r ⊣ R₁ ; one iteration +LOOP step: if body raised Break(v): exit LOOP with v + elif condition present and eval-cond(false): exit LOOP with r + else: continue_as_new(V) ; re-enter from a fresh execution +``` + +- **`continue_as_new`** ends the current duroxide execution and starts the next one, + carrying **`V` only** (`FunctionInput { instance_id, label, vars }`, + `execute_function_graph.rs:626`). The graph is reloaded and execution restarts at + `root_node`. Named results `R` are **not** carried across iterations. +- An infinite loop (`@>`, no condition, no break) never reaches a terminal status by + itself; it is exited by `df.break` or by losing a `RACE`. +- Each iteration is rate-limited to a minimum wall-time (`LOOP_MIN_ITER_DURATION = 1s`, + `:527`) so a tight loop cannot spin the worker. +- The body result `r` (or break value `v`) is the loop's result and may be bound. + +### `BREAK` — `df.break(value?)` +``` +⟨BREAK value, R, V⟩ ⇓ ⊥ ⊣ — ; raises Break(value) +``` +`value` is a literal JSON string (no SQL is run). It unwinds to the nearest enclosing +`LOOP`. **Outside any loop it is a `Failure`** (uncaught break fails the instance). +A `BREAK` raised inside a `JOIN`/`RACE` branch is re-raised across the sub-orchestration +boundary (`parse_subtree_envelope`, `:783`) so it still reaches the enclosing loop. + +--- + +## 7. Parallel — `JOIN` / `RACE` + +Both run each operand as a **sub-orchestration** (`execute_subtree`) scheduled with +`ctx.schedule_sub_orchestration` and receiving a clone of `R` and the same `V`. Branches +do not see each other's `R` writes while running; results merge back as described. + +### JOIN (`&`, `df.join`, `df.join3`) — wait for all +``` +⟨JOIN b₁..bₖ, R, V⟩ ⇓ [r₁..rₖ] ⊣ bind(JOIN, [r₁..rₖ], R ⊕ ΔR₁ ⊕ … ⊕ ΔRₖ) +``` +- All `k` branches run concurrently; `JOIN` completes only when **every** branch + completes. (`join3`/N-ary store extra operands in `query.extra_nodes`.) +- Each branch's named-result delta is merged back into the parent, **in branch order** + (later branch wins on a key collision — so two branches should not bind the same name). +- If **any** branch raises `Failure`, the JOIN fails. If any branch raises `Break`, it + unwinds to the enclosing loop. +- Result is the JSON array of branch results, bindable via `|=>`. + +### RACE (`|`, `df.race`) — first to complete wins +``` +⟨RACE b₁ b₂, R, V⟩ ⇓ r_w ⊣ bind(RACE, r_w, R ⊕ ΔR_w) +``` +- Both branches start; the **first to complete** is the winner `w`. Only the **winner's** + result and named-result delta are kept; the loser is **abandoned** (not awaited) and + makes no further progress (see C5 for the precise guarantee and its caveats). +- A winning `Break`/`Failure` propagates. Result is the winner's result string. +- Canonical loop-escape pattern: `(@> body) | df.wait_for_signal('shutdown')`. + +--- + +## 8. Nesting contracts (normative) + +These are the properties the oracle and reference interpreter enforce. **C1 and C2 are +currently violated** by open bugs (§9). + +- **C1 — Loop-body locality.** A `LOOP` iteration re-executes **only the loop's own + subgraph**. Any node *outside* the loop (a prefix `a ~> (@> body)`, or a sibling) + executes **exactly once per instance**, never once per iteration. Side-effecting + prefix SQL must not re-run when the body continues. +- **C2 — Per-iteration sub-orchestration identity.** A `JOIN`/`RACE` nested inside a + `LOOP` must derive **distinct** sub-orchestration identities on each iteration, so + child instances from iteration *i* never collide with iteration *i+1* after + `continue_as_new`. +- **C3 — Break scope.** `Break` is caught by exactly the nearest enclosing `LOOP`; + uncaught `Break` is a failure. `Break` is never observable as a normal result. +- **C4 — Result-name discipline.** Within a sequential path a name is **written before + read**. Parallel branches of one `JOIN` must not bind the same name (merge order would + otherwise decide the winner). A `RACE` only publishes the winner's bindings. +- **C5 — Race-loser abandonment.** When the `RACE` resolves, the losing branch is + abandoned and makes **no further progress** — verified by `23_signal_in_race.sql`, where + the loser's post-`sleep` SQL never runs. **Caveats (do not over-assert):** a loser node + that *already* completed before resolution stays `completed`, and a photo-finish can + complete *both* branch roots, so "exactly one branch completed" is **not** a sound + structural invariant. The robust property is: ≥1 branch root completes, the `RACE` + result equals a completed branch's result, and nodes still `pending`/`running` at + resolution never later reach a terminal state. +- **C6 — Vars immutability.** `V` is fixed for the instance; no node mutates it. Sub- + orchestrations receive `V` unchanged and cannot write it back. +- **C7 — Reachability.** A node is `completed`/`failed` only if it is on a **taken** + path: the taken `IF` branch, all `JOIN` branches, the winning `RACE` branch, and the + body of a loop that ran. Unreached nodes remain `pending`. + +### Invariants implied (Phase 1 oracle) + +| Invariant | Source contract | Checkable from `df.nodes` alone? | +|-----------|-----------------|----------------------------------| +| `every_reachable_node_completed` | C7 | ✅ | +| `join_all_branches_completed` | JOIN, C7 | ✅ | +| `race_at_least_one_branch_completed` | C5 | ✅ | +| `race_loser_no_late_completion` | C5 | ⚠️ temporal (post-resolution) — needs an event log, not a snapshot | +| `untaken_if_branch_pending` | IF, C7 | ✅ | +| `result_name_written_before_read` | C4 | ✅ (static, from tree) | +| `single_execution_outside_loop` | **C1** | ❌ needs an execution count → `df.node_events` log or duroxide history | +| `loop_body_iteration_count_matches` | **C2** | ❌ needs an iteration count → duroxide history | + +The reachability walk must follow **query-embedded children** (loop `condition_node`, +`join3` `extra_nodes`), not just `left_node`/`right_node` — reuse the walk in +`src/explain.rs` (`collect_nodes:256`). + +**Loop-body soundness limitation.** Because a `df.nodes` row is current state with no +blanket reset on `continue_as_new` (§1), the strict completeness rules are **unsound for +nodes inside a loop body** and the snapshot oracle deliberately *relaxes* (scopes), rather +than enforces, them there: + +- `join_all_branches_completed` — a `break` can abandon an in-flight sibling under a + `completed` JOIN, so for a JOIN under a loop a non-`failed` (running/pending) sibling is + accepted; only a `failed` or missing branch is still flagged. +- `untaken_if_branch_pending` — an `IF` that takes different branches across iterations ends + with **both** branches stale-`completed`, so the "exactly one branch taken / untaken subtree + pending" rules are skipped for an `IF` under a loop (its `condition_node` is still required + to be completed). +- `every_reachable_node_completed` — under a loop the walk descends only into a JOIN's + *completed* branches. + +The loop's **own** `condition_node` is a child of the `LOOP` node (not of the body) and stays +strict. Nodes outside every loop are unaffected. These relaxations cost some bug-catching power +inside loops but guarantee **no false positives** on a quiesced, terminal instance. + +--- + +## 9. Known deviations (open bugs) + +- **#227 — prefix re-runs once per iteration (violates C1).** When the `LOOP` is not the + root, `continue_as_new` restarts execution at `root_node`, so any prefix before the + loop runs again every iteration. Intended: prefix runs once. +- **#230 — JOIN/RACE-in-loop stalls on iteration ≥2 (violates C2).** `continue_as_new` + resets duroxide's sub-orchestration id counter, so the iteration-2 child id collides + with iteration-1's, deduplicating to the old (already-finished) result and stalling. + Intended: per-iteration unique child identity. + +Tests written against this document should treat C1/C2 as the **expected** behaviour +(i.e. they should fail until the bugs are fixed). diff --git a/docs/upgrade-testing.md b/docs/upgrade-testing.md index 51059edd..48177f7d 100644 --- a/docs/upgrade-testing.md +++ b/docs/upgrade-testing.md @@ -203,6 +203,15 @@ gate, so they never need to be added to the exclude list. Each schema-changing PR should add a section here documenting what changed, what the upgrade script handles, and any backward compatibility considerations. +### v0.2.3 → v0.2.4 + +#### Add df.assert_structural_invariants() structural-invariant oracle (#232, Phase 1) +- **DDL change (df schema):** Adds `df.assert_structural_invariants(instance_id text, fail_on_violation boolean DEFAULT false)`, a `STRICT` C function returning `TABLE(invariant text, passed bool, node_id text, detail text)`. It is a read-only, RLS-scoped snapshot oracle that validates a terminal instance's `df.nodes` against the operational-semantics contract (`docs/dsl-semantics.md`). Fresh 0.2.4 installs create it via pgrx (`src/invariants.rs`); the upgrade script `sql/pg_durable--0.2.3--0.2.4.sql` creates the identical function so the upgraded catalog matches a fresh one. +- **DDL change (grant_usage):** The upgrade script re-emits `CREATE OR REPLACE FUNCTION df.grant_usage(...)` with `df.assert_structural_invariants(text, boolean)` added to its `func_sigs` list. Without this, a role granted access via `df.grant_usage()` on an upgraded install would not receive `EXECUTE` on the new function (same pattern used when `df.explain()` was added in v0.2.0). +- **Scenario A considerations:** The Scenario A equivalence contract compares function signatures (`pg_get_function_arguments`/`pg_get_function_result`) and routine grants, not function bodies. The hand-written `CREATE FUNCTION` block is copied verbatim from the pgrx-emitted DDL, so the signature matches. The new function's `proacl` is `NULL` (default ACL) on both the fresh-install and upgrade paths — `df.grant_usage()` only mutates a role's grants when explicitly called — so the `grant_routine` snapshot matches. The `grant_usage` body differs (new func_sig) but bodies are excluded from the diff. +- **Scenario B1 considerations:** No `.so` backward-compat risk. The oracle reads only long-present `df.nodes` / `df.instances` columns (`status`, `root_node`, `node_type`, `query`, `left_node`, `right_node`, `result_name`, `result`) and performs no runtime schema detection, so it behaves identically against any previous schema once the function exists. +- **Scenario B2 considerations:** No data migration. The upgrade only adds one read-only `df` function and refreshes the `grant_usage` body. + ### v0.2.2 → v0.2.3 #### Rename duroxide provider schema to `_duroxide` for fresh installs diff --git a/scripts/test-e2e-local.sh b/scripts/test-e2e-local.sh index 6b51b0d2..dee6ab8f 100755 --- a/scripts/test-e2e-local.sh +++ b/scripts/test-e2e-local.sh @@ -11,6 +11,8 @@ # --keep Leave PostgreSQL running after tests for investigation # --clean Start with a fresh database cluster # --verbose, -v Show NOTICE messages and full test output +# --include-generated Also run the Phase 2 generated matrix (tests/e2e/generated/sql) +# --include-generated-quarantine Also run the quarantined matrix (known-failing; tests/e2e/generated/quarantine) # --pg-version VER PostgreSQL major version to use (default: 17) # --default-build-phases Run all phases that share the standard build artifact # --http-disabled Run only the HTTP-disabled (no http Cargo feature) phase @@ -35,10 +37,14 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" SQL_DIR="$PROJECT_DIR/tests/e2e/sql" +GENERATED_SQL_DIR="$PROJECT_DIR/tests/e2e/generated/sql" +QUARANTINE_SQL_DIR="$PROJECT_DIR/tests/e2e/generated/quarantine" KEEP_RUNNING=false CLEAN_START=false VERBOSE=false +INCLUDE_GENERATED=false +INCLUDE_QUARANTINE=false TEST_FILTER="" REPEAT_COUNT=1 PG_VERSION="17" @@ -196,6 +202,14 @@ while [[ $# -gt 0 ]]; do VERBOSE=true shift ;; + --include-generated) + INCLUDE_GENERATED=true + shift + ;; + --include-generated-quarantine) + INCLUDE_QUARANTINE=true + shift + ;; --pg-version) if [ $# -lt 2 ] || ! [[ "$2" =~ ^[0-9]+$ ]]; then echo "Error: --pg-version requires a numeric argument" @@ -438,6 +452,14 @@ configure_phase() { # Clear stale ALTER SYSTEM overrides from prior phases/runs. : > "$DATA_DIR/postgresql.auto.conf" set_conf_line "port" "$PG_PORT" + # Pin the socket dir to this cluster's own data dir (writable, and isolated + # from other processes). A fresh `initdb` (via --clean) must not inherit a + # packager default like /var/run/postgresql that the test user cannot write + # to, and a per-cluster dir avoids colliding on a world-writable /tmp socket + # with concurrent runs. Clients connect over TCP (-h localhost) regardless, + # and $DATA_DIR lives under $HOME/.pgrx so the socket path stays well within + # the ~107-char sun_path limit. + set_conf_line "unix_socket_directories" "'$DATA_DIR'" clear_connlimit_gucs case "$phase" in @@ -567,6 +589,37 @@ prepare_phase() { esac } +# Append every *.sql in $1 to MATCHED_TESTS, honoring TEST_FILTER and skipping +# non-files. $2 is the CLI flag that requested the dir and $3 a human label; +# both appear only in the "nothing generated yet" error, which points the user +# at the generator command. Errors out if the flag was set but the dir is empty. +collect_sql_from_dir() { + local dir="$1" + local flag="$2" + local label="$3" + local test_file + local test_name + + if ! compgen -G "$dir/*.sql" >/dev/null; then + echo "Error: $flag was set but no $label tests exist in" + echo " $dir" + echo "Generate them first:" + echo " cargo run --manifest-path tests/e2e/generated/generator/Cargo.toml" + exit 1 + fi + + for test_file in "$dir"/*.sql; do + [ -f "$test_file" ] || continue + test_name=$(basename "$test_file" .sql) + + if [ -n "$TEST_FILTER" ] && [[ "$test_name" != *"$TEST_FILTER"* ]]; then + continue + fi + + MATCHED_TESTS+=("$test_file") + done +} + collect_matched_tests() { local test_file local test_name @@ -588,6 +641,14 @@ collect_matched_tests() { MATCHED_TESTS+=("$test_file") done + if [ "$INCLUDE_GENERATED" = true ]; then + collect_sql_from_dir "$GENERATED_SQL_DIR" "--include-generated" "generated" + fi + + if [ "$INCLUDE_QUARANTINE" = true ]; then + collect_sql_from_dir "$QUARANTINE_SQL_DIR" "--include-generated-quarantine" "quarantined" + fi + if [ "${#MATCHED_TESTS[@]}" -eq 0 ]; then echo "Error: no E2E tests matched the current selection" exit 1 diff --git a/sql/pg_durable--0.2.3--0.2.4.sql b/sql/pg_durable--0.2.3--0.2.4.sql new file mode 100644 index 00000000..2db3f27e --- /dev/null +++ b/sql/pg_durable--0.2.3--0.2.4.sql @@ -0,0 +1,151 @@ +-- Copyright (c) Microsoft Corporation. +-- Licensed under the PostgreSQL License. + +-- pg_durable upgrade: 0.2.3 → 0.2.4 +-- +-- Adds df.assert_structural_invariants(instance_id, fail_on_violation), a sound +-- post-run snapshot oracle that validates a terminal instance's df.nodes against +-- the operational-semantics contract (docs/dsl-semantics.md). It returns one row +-- per invariant when all hold, or one row per offending node when violated, and +-- can raise on violation (fail_on_violation => true) so tests can assert in one +-- line. Read-only and RLS-scoped — it only inspects instances visible to the +-- caller. Backward-compatible: it reads only long-present df.nodes / df.instances +-- columns and needs no runtime schema detection. +-- +-- The CREATE FUNCTION block below mirrors exactly what a fresh 0.2.4 install +-- generates (pgrx-emitted DDL), so the upgraded catalog matches a fresh one. +-- +-- df.grant_usage() is re-emitted (CREATE OR REPLACE) with the new function added +-- to its func_sigs list, so that delegated roles granted via df.grant_usage() +-- on an upgraded install also receive EXECUTE on the new diagnostic function. + +-- pgspot's SQL security gate (scripts/pgspot-gate.sh) trusts a schema named in a +-- function's SET search_path only when that schema is created in the same script +-- (pgspot state.py is_secure_searchpath). df already exists on any install being +-- upgraded, so this IF NOT EXISTS is a harmless runtime no-op; it is present so the +-- df.grant_usage() re-emit below -- which relies on SET search_path = pg_catalog, +-- df, pg_temp -- is recognized as secure by the gate, exactly as the fresh-install +-- SQL achieves it (pgrx emits CREATE SCHEMA IF NOT EXISTS df there). The resulting +-- PS010 finding for df is on the gate's allowlist. +CREATE SCHEMA IF NOT EXISTS df; + +/* */ +-- pg_durable::invariants::assert_structural_invariants +CREATE FUNCTION df."assert_structural_invariants"( +"instance_id" TEXT, /* &str */ +"fail_on_violation" bool DEFAULT false /* bool */ +) RETURNS TABLE ( +"invariant" TEXT, /* alloc::string::String */ +"passed" bool, /* bool */ +"node_id" TEXT, /* core::option::Option */ +"detail" TEXT /* core::option::Option */ +) +STRICT +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'assert_structural_invariants_wrapper'; +/* */ + +-- Re-emit df.grant_usage() so its func_sigs list includes the new +-- df.assert_structural_invariants(text, boolean) function. Without this, a role +-- granted access via df.grant_usage() on an upgraded install would not receive +-- EXECUTE on the new function. The body is identical to the fresh-install +-- definition in src/lib.rs aside from the added func_sig. +CREATE OR REPLACE FUNCTION df.grant_usage( + p_role TEXT, + include_http boolean DEFAULT false, + with_grant boolean DEFAULT false +) +RETURNS VOID +LANGUAGE plpgsql +SET search_path = pg_catalog, df, pg_temp +AS $fn$ +DECLARE + grant_opt TEXT := ''; + func_sig TEXT; + -- Explicit list of df.* functions to grant. Sensitive functions + -- (df.http, df.grant_usage, df.revoke_usage) are excluded from this + -- list and granted conditionally below. + func_sigs TEXT[] := ARRAY[ + -- DSL functions + 'df.sql(text)', + 'df.seq(text, text)', + 'df.as(text, text)', + 'df.sleep(bigint)', + 'df.wait_for_schedule(text)', + 'df.loop(text, text)', + 'df.break(text)', + 'df.if(text, text, text)', + 'df.if_rows(text, text, text)', + 'df.join(text, text)', + 'df.join3(text, text, text)', + 'df.race(text, text)', + 'df.wait_for_signal(text, integer)', + 'df.signal(text, text, text)', + 'df.start(text, text, text)', + 'df.setvar(text, text)', + 'df.getvar(text)', + 'df.unsetvar(text)', + 'df.clearvars()', + -- Monitoring functions + 'df.status(text)', + 'df.result(text)', + 'df.cancel(text, text)', + 'df.wait_for_completion(text, integer)', + 'df.run(text)', + 'df.list_instances(text, integer)', + 'df.instance_info(text)', + 'df.instance_nodes(text, integer)', + 'df.instance_executions(text, integer)', + 'df.metrics()', + -- Internal helpers (operators, version, etc.) + 'df.as_op(text, text)', + 'df.if_then_op(text, text)', + 'df.if_else_op(text, text)', + 'df.ensure_durofut(text)', + 'df.loop_prefix_op(text)', + 'df.version()', + 'df.debug_connection()', + 'df.explain(text)', + 'df.assert_structural_invariants(text, boolean)', + 'df.target_database()' + ]; +BEGIN + -- Validate the role exists + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = p_role) THEN + RAISE EXCEPTION 'role "%" does not exist', p_role; + END IF; + + IF with_grant THEN + grant_opt := ' WITH GRANT OPTION'; + END IF; + + -- Schema access + EXECUTE format('GRANT USAGE ON SCHEMA df TO %I', p_role) || grant_opt; + + -- Grant EXECUTE on each standard function explicitly. + FOREACH func_sig IN ARRAY func_sigs LOOP + EXECUTE format('GRANT EXECUTE ON FUNCTION %s TO %I', func_sig, p_role) || grant_opt; + END LOOP; + + -- df.http() — opt-in because it makes outbound network requests. + IF include_http THEN + EXECUTE format('GRANT EXECUTE ON FUNCTION df.http(text, text, text, jsonb, integer) TO %I', p_role) || grant_opt; + END IF; + + -- Admin helpers — only for delegated administrators. + IF with_grant THEN + EXECUTE format('GRANT EXECUTE ON FUNCTION df.grant_usage(text, boolean, boolean) TO %I', p_role) || grant_opt; + EXECUTE format('GRANT EXECUTE ON FUNCTION df.revoke_usage(text) TO %I', p_role) || grant_opt; + END IF; + + -- Table privileges + EXECUTE format('GRANT SELECT ON df.instances TO %I', p_role) || grant_opt; + EXECUTE format('GRANT UPDATE (status, updated_at) ON df.instances TO %I', p_role) || grant_opt; + EXECUTE format('GRANT SELECT ON df.nodes TO %I', p_role) || grant_opt; + EXECUTE format('GRANT INSERT (id, label, root_node, submitted_by, database) ON df.instances TO %I', p_role) || grant_opt; + EXECUTE format('GRANT INSERT (id, instance_id, node_type, query, result_name, left_node, right_node, submitted_by, database) ON df.nodes TO %I', p_role) || grant_opt; + EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON df.vars TO %I', p_role) || grant_opt; + + RAISE NOTICE 'pg_durable: granted df usage privileges to "%"', p_role; +END; +$fn$; diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 00000000..42ddfc80 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! Shared, read-only accessors for the `df.nodes` table. +//! +//! [`NodeSnapshot`] is the canonical projection of the seven *base* columns of +//! `df.nodes`. It is the single source of truth for the positional SPI read of +//! those columns. Other modules issue intentionally *wider* projections on top +//! of the same row shape: +//! +//! * `src/explain.rs` adds `result::text` (column 8) for tree rendering. +//! * `src/monitoring.rs` adds `result::text` and `updated_at` (columns 8–9). +//! +//! When the `df.nodes` column order changes, update the `SELECT` here and those +//! two sites together — the positional `row.get(N)` reads are not checked by the +//! compiler. + +use pgrx::prelude::*; + +/// Hard cap on the number of `df.nodes` rows loaded for a single instance. +/// +/// A durable function graph is normally tens of nodes; this bound exists only +/// to keep a pathologically large (or maliciously constructed) instance from +/// forcing the backend to materialize an unbounded result set into memory. +pub const MAX_NODES_PER_INSTANCE: usize = 50_000; + +/// The seven base columns of a `df.nodes` row, as read by the oracle. +#[derive(Debug, Clone)] +pub struct NodeSnapshot { + pub id: String, + pub node_type: String, + pub query: Option, + pub result_name: Option, + pub left_node: Option, + pub right_node: Option, + pub status: String, +} + +/// Load up to `limit` node snapshots for one instance (RLS-scoped). +/// +/// Returns `None` when the instance has *more* than `limit` nodes, signalling +/// the caller to treat the instance as too large to evaluate rather than +/// silently operating on a truncated snapshot. +pub fn load_node_snapshots(instance_id: &str, limit: usize) -> Option> { + Spi::connect(|client| { + // Fetch one more than the cap so we can detect (not silently truncate) + // an over-limit instance. + let sql = format!( + "SELECT id, node_type, query, result_name, left_node, right_node, status \ + FROM df.nodes WHERE instance_id = $1 LIMIT {}", + limit + 1 + ); + let mut out: Vec = Vec::new(); + if let Ok(table) = client.select(&sql, None, &[instance_id.into()]) { + for row in table { + let id: String = match row.get::(1) { + Ok(Some(v)) => v, + _ => continue, + }; + out.push(NodeSnapshot { + id, + node_type: row.get(2).ok().flatten().unwrap_or_default(), + query: row.get(3).ok().flatten(), + result_name: row.get(4).ok().flatten(), + left_node: row.get(5).ok().flatten(), + right_node: row.get(6).ok().flatten(), + status: row.get::(7).ok().flatten().unwrap_or_default(), + }); + if out.len() > limit { + return None; + } + } + } + Some(out) + }) +} diff --git a/src/explain.rs b/src/explain.rs index 7c5dab90..c9558622 100644 --- a/src/explain.rs +++ b/src/explain.rs @@ -299,6 +299,11 @@ fn collect_nodes( } /// Load nodes from a table into a HashMap +/// +/// Projection note: columns 1–7 match [`crate::db::NodeSnapshot`] (the canonical +/// base projection); this loader intentionally adds `result::text` (column 8). +/// Keep the positional reads in sync with `crate::db::load_node_snapshots` when +/// the `df.nodes` column order changes. fn load_nodes_from_table(table: &str, instance_id: Option<&str>) -> HashMap { // Note: table name is always a hardcoded value ("df.nodes") from internal callers, // so it is safe to interpolate. Only instance_id is parameterized. diff --git a/src/invariants.rs b/src/invariants.rs new file mode 100644 index 00000000..0b76f9d1 --- /dev/null +++ b/src/invariants.rs @@ -0,0 +1,1694 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! Structural-invariant oracle for durable function instances. +//! +//! `df.assert_structural_invariants(instance_id)` post-run validates the +//! persisted DSL execution snapshot (the rows in `df.nodes` for one instance) +//! against the operational-semantics contract documented in +//! `docs/dsl-semantics.md`. +//! +//! It is a **sound snapshot oracle**: every violation it reports is a genuine +//! contract breach. It intentionally does *not* attempt invariants that require +//! execution or iteration **counts** (e.g. issue #227 "single execution outside +//! a loop", issue #230 "loop body iteration count"), because `df.nodes` stores +//! the *current state* of each node (updated in place by the +//! `update_node_status` activity), not an append-only trace. On +//! `continue_as_new` a loop body's node rows are reset and overwritten, so the +//! snapshot cannot reveal how many times a node executed. Those count-based +//! invariants need an event log and are tracked as follow-up work. +//! +//! The checker is split into a pure [`evaluate_invariants`] over an in-memory +//! node map (unit-testable, no SPI) and a thin `#[pg_extern]` wrapper +//! ([`assert_structural_invariants`]) that loads the snapshot via SPI. +//! +//! ## Persisted child encoding +//! +//! A node's children come from two places, mirroring how the executor reads +//! them (`src/orchestrations/execute_function_graph.rs`): +//! * the structural columns `left_node` / `right_node`, and +//! * string id references embedded in the `query` JSON — `condition_node` +//! (`df.if`, `df.loop` while-condition) and `extra_nodes` (`df.join3`). +//! +//! Note `df.if_rows` produces an IF with **no** `condition_node` (it tests a +//! prior named result in memory), so `condition_node` is always optional. + +use pgrx::prelude::*; +use std::collections::{HashMap, HashSet}; + +// Invariant names (stable identifiers returned in the `invariant` column). +const INV_REACHABLE: &str = "every_reachable_node_completed"; +const INV_JOIN_BRANCHES: &str = "join_all_branches_completed"; +const INV_UNTAKEN_IF: &str = "untaken_if_branch_pending"; +const INV_RACE_BRANCH: &str = "race_at_least_one_branch_completed"; +const INV_JOIN_NAMES: &str = "join_branch_result_name_disjoint"; +const INV_QUERY_JSON: &str = "query_json_well_formed"; + +const STATUS_COMPLETED: &str = "completed"; +const STATUS_PENDING: &str = "pending"; + +fn is_completed(status: &str) -> bool { + status.eq_ignore_ascii_case(STATUS_COMPLETED) +} + +fn is_pending(status: &str) -> bool { + status.eq_ignore_ascii_case(STATUS_PENDING) +} + +/// A terminal (quiesced) instance status — the oracle's snapshot precondition. +/// On a non-terminal instance the snapshot is mid-flight and a completed JOIN +/// may legitimately have branches still running, so the oracle declines to run. +fn is_terminal_instance(status: &str) -> bool { + status.eq_ignore_ascii_case(STATUS_COMPLETED) + || status.eq_ignore_ascii_case("failed") + || status.eq_ignore_ascii_case("cancelled") +} + +/// Minimal projection of a `df.nodes` row used by the oracle. +#[derive(Debug, Clone)] +struct OracleNode { + id: String, + node_type: String, + query: Option, + result_name: Option, + left_node: Option, + right_node: Option, + status: String, +} + +impl OracleNode { + fn is_type(&self, ty: &str) -> bool { + self.node_type.eq_ignore_ascii_case(ty) + } + + /// Parse the `query` JSON once (None if absent or not an object). + fn config(&self) -> Option { + let q = self.query.as_ref()?; + serde_json::from_str::(q).ok() + } + + /// `condition_node` id embedded in `query` (IF via `df.if`, while-LOOP). + /// Absent for `df.if_rows` and for plain infinite loops. + fn condition_node_id(&self) -> Option { + self.config()? + .get("condition_node") + .and_then(|v| v.as_str()) + .map(str::to_string) + } + + /// `extra_nodes` ids embedded in `query` (e.g. the 3rd branch of `df.join3`). + fn extra_node_ids(&self) -> Vec { + let Some(cfg) = self.config() else { + return Vec::new(); + }; + cfg.get("extra_nodes") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|e| e.as_str().map(str::to_string)) + .collect() + }) + .unwrap_or_default() + } + + /// All child node ids: structural (`left`/`right`) plus query-embedded + /// (`condition_node`, `extra_nodes`). + fn child_ids(&self) -> Vec { + let mut ids = Vec::new(); + if let Some(l) = &self.left_node { + ids.push(l.clone()); + } + if let Some(r) = &self.right_node { + ids.push(r.clone()); + } + if let Some(c) = self.condition_node_id() { + ids.push(c); + } + ids.extend(self.extra_node_ids()); + ids + } + + /// Branch roots only: `left`/`right` plus `extra_nodes` (used by JOIN/RACE). + /// Excludes `condition_node`, which is not a parallel branch. + fn branch_ids(&self) -> Vec { + let mut ids = Vec::new(); + if let Some(l) = &self.left_node { + ids.push(l.clone()); + } + if let Some(r) = &self.right_node { + ids.push(r.clone()); + } + ids.extend(self.extra_node_ids()); + ids + } + + /// Node types whose `query` column holds JSON *configuration* (not raw SQL): + /// IF/LOOP embed `condition_node`, JOIN embeds `extra_nodes`. For these a + /// present-but-unparseable `query` silently drops children, so it must be + /// reported rather than swallowed by [`config`](Self::config)'s `.ok()`. + fn uses_json_query(&self) -> bool { + self.is_type("IF") || self.is_type("LOOP") || self.is_type("JOIN") + } + + /// True when `query` is present and non-blank but is not a JSON object. + fn has_malformed_json_query(&self) -> bool { + match self.query.as_deref() { + Some(q) if !q.trim().is_empty() => { + !matches!(serde_json::from_str::(q), Ok(v) if v.is_object()) + } + _ => false, + } + } +} + +/// A single result row of the oracle. +#[derive(Debug, Clone, PartialEq, Eq)] +struct CheckRow { + invariant: String, + passed: bool, + node_id: Option, + detail: Option, +} + +/// Push either a single `passed` row (no violations) or one row per violation. +/// Violations are sorted for deterministic output. +fn finalize(invariant: &str, mut violations: Vec<(String, String)>, rows: &mut Vec) { + if violations.is_empty() { + rows.push(CheckRow { + invariant: invariant.to_string(), + passed: true, + node_id: None, + detail: None, + }); + return; + } + violations.sort(); + for (node_id, detail) in violations { + rows.push(CheckRow { + invariant: invariant.to_string(), + passed: false, + node_id: Some(node_id), + detail: Some(detail), + }); + } +} + +/// Record an invariant that was not evaluated (reported as passed with a reason). +fn skip(invariant: &str, reason: String, rows: &mut Vec) { + rows.push(CheckRow { + invariant: invariant.to_string(), + passed: true, + node_id: None, + detail: Some(format!("skipped: {reason}")), + }); +} + +/// Collect a node id and its full subtree (cycle-safe via the visited set). +fn collect_subtree(root: &str, nodes: &HashMap, out: &mut HashSet) { + let mut stack = vec![root.to_string()]; + while let Some(id) = stack.pop() { + if !out.insert(id.clone()) { + continue; + } + if let Some(node) = nodes.get(&id) { + for child in node.child_ids() { + stack.push(child); + } + } + } +} + +/// Sorted node ids — gives deterministic iteration over the snapshot. +fn sorted_ids(nodes: &HashMap) -> Vec { + let mut ids: Vec = nodes.keys().cloned().collect(); + ids.sort(); + ids +} + +/// Ids of every node that lives *inside* a LOOP body (the union of each LOOP's +/// `left_node` subtree). +/// +/// Inside a loop body the executor re-runs nodes via duroxide `continue_as_new`, +/// overwriting per-node status lazily *without* a blanket reset (see +/// `execute_loop_node` in `src/orchestrations/execute_function_graph.rs`). Two +/// consequences make the strict completeness rules unsound for these nodes: +/// * an IF that takes different branches across iterations ends with *both* +/// branches recorded `completed` (stale), and +/// * a `break` unwinds an in-flight JOIN/RACE, leaving a sibling branch +/// non-completed even though the JOIN itself is recorded `completed`. +/// +/// The reachability / JOIN / IF checks therefore relax (scope), not skip, for +/// nodes in this set. The loop's own `condition_node` is a child of the LOOP +/// node — not of the body — so it is intentionally excluded and stays strict. +fn nodes_under_loop(nodes: &HashMap) -> HashSet { + let mut under = HashSet::new(); + for node in nodes.values() { + if !node.is_type("LOOP") { + continue; + } + if let Some(body) = &node.left_node { + collect_subtree(body, nodes, &mut under); + } + } + under +} + +/// Invariant 1: in a *completed* instance, every node reachable along the taken +/// execution path is `completed`. +/// +/// The reached set is computed top-down honouring each combinator's recorded +/// outcome, so legitimately-pending nodes (the untaken IF branch, an abandoned +/// RACE loser, a loop's while-condition on a break-exit) are never required to +/// be completed: +/// * THEN → both children run. +/// * JOIN → all branches run. +/// * IF → the `condition_node` (if present) plus the taken (non-pending) branch. +/// * RACE → only the completed (winner) branch(es). +/// * LOOP → the body; the while-condition only if it itself completed. +/// * leaves (SQL/SLEEP/WAIT_SCHEDULE/BREAK/HTTP/SIGNAL) → no children. +/// +/// Loop-scoping: for a JOIN *inside* a loop body a `break` can abandon a sibling +/// branch, so only its completed branches are treated as on-path (see +/// [`nodes_under_loop`]). +/// +/// Only evaluated for completed instances: a failed/cancelled instance +/// legitimately leaves nodes off the path pending. +fn check_reachable_completed( + nodes: &HashMap, + root_id: &str, + instance_status: &str, + under_loop: &HashSet, + rows: &mut Vec, +) { + if !is_completed(instance_status) { + skip( + INV_REACHABLE, + format!( + "instance status is '{instance_status}'; reachability completeness is only checked for completed instances" + ), + rows, + ); + return; + } + + let mut violations: Vec<(String, String)> = Vec::new(); + let mut seen: HashSet = HashSet::new(); + let mut stack = vec![root_id.to_string()]; + + while let Some(id) = stack.pop() { + if !seen.insert(id.clone()) { + continue; + } + let Some(node) = nodes.get(&id) else { + violations.push(( + id.clone(), + "reachable node id not found in df.nodes".to_string(), + )); + continue; + }; + if !is_completed(&node.status) { + violations.push(( + id.clone(), + format!( + "reachable {} node is '{}', expected completed", + node.node_type, node.status + ), + )); + // Do not descend through a node that is not completed: its children's + // expected state is ambiguous, and descending would cascade spurious + // violations. + continue; + } + push_reached_children(node, nodes, under_loop, &mut stack); + } + + finalize(INV_REACHABLE, violations, rows); +} + +fn push_reached_children( + node: &OracleNode, + nodes: &HashMap, + under_loop: &HashSet, + stack: &mut Vec, +) { + // Push a child id when `predicate` holds. When the id is absent from the + // snapshot, push it anyway iff `report_absent` so the walk surfaces the + // dangling reference ("not found in df.nodes"); for legitimately-absent + // references (an infinite loop's missing while-condition) keep it silent. + let push_if = |stack: &mut Vec, + id: &str, + report_absent: bool, + predicate: &dyn Fn(&OracleNode) -> bool| { + match nodes.get(id) { + Some(child) => { + if predicate(child) { + stack.push(id.to_string()); + } + } + None => { + if report_absent { + stack.push(id.to_string()); + } + } + } + }; + + match node.node_type.to_ascii_uppercase().as_str() { + "THEN" => { + if let Some(l) = &node.left_node { + stack.push(l.clone()); + } + if let Some(r) = &node.right_node { + stack.push(r.clone()); + } + } + "JOIN" => { + if under_loop.contains(&node.id) { + // Inside a loop body a `break` can abandon a sibling branch, + // leaving it non-completed under a `completed` JOIN. Descend only + // into completed branches; the JOIN-branches check applies the + // matching relaxed completeness. + for branch in node.branch_ids() { + push_if(stack, &branch, false, &|c| is_completed(&c.status)); + } + } else { + // Outside any loop all branches are on the path; push them all so + // a missing branch id is surfaced as a dangling reference. + for branch in node.branch_ids() { + stack.push(branch); + } + } + } + "IF" => { + // The condition node (df.if) always runs; a dangling reference is + // worth surfacing. + if let Some(c) = node.condition_node_id() { + stack.push(c); + } + // Descend into the taken (non-pending) branch(es) only; a taken but + // absent branch is a dangling reference worth surfacing. + if let Some(l) = &node.left_node { + push_if(stack, l, true, &|c| !is_pending(&c.status)); + } + if let Some(r) = &node.right_node { + push_if(stack, r, true, &|c| !is_pending(&c.status)); + } + } + "RACE" => { + // Descend into completed (winner) branch(es); abandoned losers may be + // legitimately pending/running. A winner whose id is absent is a + // dangling reference worth surfacing. + for branch in node.branch_ids() { + push_if(stack, &branch, true, &|c| is_completed(&c.status)); + } + } + "LOOP" => { + // The body's last iteration completed on a completed loop. + if let Some(body) = &node.left_node { + stack.push(body.clone()); + } + // The while-condition ran (and is recorded completed) only on a + // condition-false exit, not on a break-exit. Require it only if it + // actually completed; a legitimately-absent condition (infinite loop) + // stays silent. + if let Some(c) = node.condition_node_id() { + push_if(stack, &c, false, &|c| is_completed(&c.status)); + } + } + // Leaves have no children. + _ => {} + } +} + +/// Invariant 2: a *completed* JOIN has every branch completed. +/// +/// Loop-scoping: for a JOIN inside a loop body a `break` can abandon a sibling +/// branch (left non-completed under a `completed` JOIN). There the rule relaxes +/// to "no branch *failed*, none missing" — a running/pending abandoned sibling +/// is accepted (see [`nodes_under_loop`]). A `completed` JOIN with zero branches +/// is always a violation: it cannot meaningfully complete. +fn check_join_branches_completed( + nodes: &HashMap, + under_loop: &HashSet, + rows: &mut Vec, +) { + let mut violations: Vec<(String, String)> = Vec::new(); + + for id in sorted_ids(nodes) { + let node = &nodes[&id]; + if !node.is_type("JOIN") || !is_completed(&node.status) { + continue; + } + let branches = node.branch_ids(); + if branches.is_empty() { + violations.push(( + node.id.clone(), + format!("JOIN {} completed but has no branches", node.id), + )); + continue; + } + let relaxed = under_loop.contains(&node.id); + for branch in branches { + match nodes.get(&branch) { + Some(child) if is_completed(&child.status) => {} + // Under a loop, a break-abandoned sibling is non-terminal + // (running/pending) and legitimate; only a genuine `failed` + // branch is still a violation there. + Some(child) if relaxed && !child.status.eq_ignore_ascii_case("failed") => {} + Some(child) => violations.push(( + branch.clone(), + format!( + "JOIN {} completed but branch {} is '{}'", + node.id, branch, child.status + ), + )), + None => violations.push(( + branch.clone(), + format!("JOIN {} branch {} missing from df.nodes", node.id, branch), + )), + } + } + } + + finalize(INV_JOIN_BRANCHES, violations, rows); +} + +/// Invariant 3: a *completed* IF takes exactly one branch; the untaken branch's +/// whole subtree stays pending; the `condition_node` (when present) completed. +/// +/// Gated on the IF being completed (not merely terminal): a failed IF may have +/// failed in its condition before any branch was selected, which legitimately +/// leaves both branches pending. +/// +/// Loop-scoping: for an IF inside a loop body the executor re-runs it across +/// iterations and overwrites branch status lazily, so the final snapshot can +/// show *both* branches `completed` (stale) and an untaken subtree non-pending. +/// The "exactly one branch taken" and "untaken subtree pending" rules are +/// unsound there and are skipped; the condition-node check still applies (see +/// [`nodes_under_loop`]). +fn check_untaken_if_branch_pending( + nodes: &HashMap, + under_loop: &HashSet, + rows: &mut Vec, +) { + let mut violations: Vec<(String, String)> = Vec::new(); + + for id in sorted_ids(nodes) { + let node = &nodes[&id]; + if !node.is_type("IF") || !is_completed(&node.status) { + continue; + } + + if !under_loop.contains(&node.id) { + let branches: Vec = [&node.left_node, &node.right_node] + .into_iter() + .flatten() + .cloned() + .collect(); + + let taken: Vec = branches + .iter() + .filter(|b| nodes.get(*b).is_some_and(|n| !is_pending(&n.status))) + .cloned() + .collect(); + + if taken.len() > 1 { + violations.push(( + node.id.clone(), + format!( + "IF {} took more than one branch (both non-pending)", + node.id + ), + )); + } else if taken.is_empty() { + violations.push(( + node.id.clone(), + format!( + "IF {} completed but neither branch was taken (both pending)", + node.id + ), + )); + } + + // Every untaken branch subtree must be entirely pending. + for branch in &branches { + if taken.contains(branch) { + continue; + } + let mut subtree = HashSet::new(); + collect_subtree(branch, nodes, &mut subtree); + for sid in &subtree { + if let Some(sn) = nodes.get(sid) { + if !is_pending(&sn.status) { + violations.push(( + sid.clone(), + format!( + "IF {} untaken branch node {} is '{}', expected pending", + node.id, sid, sn.status + ), + )); + } + } + } + } + } + + // The condition node (df.if) runs whenever the IF is reached — in or out + // of a loop. A present-but-non-completed or a missing condition node is a + // violation. + if let Some(cond) = node.condition_node_id() { + match nodes.get(&cond) { + Some(cn) if is_completed(&cn.status) => {} + Some(cn) => violations.push(( + cond.clone(), + format!( + "IF {} condition node {} is '{}', expected completed", + node.id, cond, cn.status + ), + )), + None => violations.push(( + cond.clone(), + format!( + "IF {} condition node {} missing from df.nodes", + node.id, cond + ), + )), + } + } + } + + finalize(INV_UNTAKEN_IF, violations, rows); +} + +/// Invariant 4: a *completed* RACE has at least one completed branch. +/// +/// This is the sound replacement for the issue's `race_loser_terminal` +/// ("exactly one branch completes"), which is **not** sound: a race may resolve +/// against a branch that was already completed, and near-simultaneous +/// completions ("photo finish") can leave more than one branch completed +/// (see `docs/dsl-semantics.md` §C5). +fn check_race_branch_completed(nodes: &HashMap, rows: &mut Vec) { + let mut violations: Vec<(String, String)> = Vec::new(); + + for id in sorted_ids(nodes) { + let node = &nodes[&id]; + if !node.is_type("RACE") || !is_completed(&node.status) { + continue; + } + let any_completed = node + .branch_ids() + .iter() + .any(|b| nodes.get(b).is_some_and(|n| is_completed(&n.status))); + if !any_completed { + violations.push(( + node.id.clone(), + format!("RACE {} completed but no branch is completed", node.id), + )); + } + } + + finalize(INV_RACE_BRANCH, violations, rows); +} + +/// Invariant 5 (static): the parallel branches of a JOIN must bind disjoint +/// `result_name`s. Two branches binding the same name race to overwrite it in +/// the merged variable map, so the merged value is non-deterministic +/// (`docs/dsl-semantics.md` §C4). Checked structurally, independent of run state. +/// +/// A JOIN that references the same branch id more than once (`left_node == +/// right_node`, or a duplicate `extra_nodes` entry) is itself malformed; that is +/// reported as a `duplicate_branch_id` violation and the names analysis is +/// skipped for that JOIN (visiting the same subtree twice would otherwise report +/// a spurious self-collision). +fn check_join_result_name_disjoint(nodes: &HashMap, rows: &mut Vec) { + let mut violations: Vec<(String, String)> = Vec::new(); + + for id in sorted_ids(nodes) { + let node = &nodes[&id]; + if !node.is_type("JOIN") { + continue; + } + + // Detect duplicate branch ids before the names analysis. + let branches = node.branch_ids(); + let mut unique: Vec = Vec::with_capacity(branches.len()); + let mut dup_reported: HashSet = HashSet::new(); + for b in &branches { + if unique.contains(b) { + if dup_reported.insert(b.clone()) { + violations.push(( + node.id.clone(), + format!( + "JOIN {} references the same branch id {} more than once", + node.id, b + ), + )); + } + } else { + unique.push(b.clone()); + } + } + if !dup_reported.is_empty() { + // Skip the names analysis for a malformed (duplicate-branch) JOIN. + continue; + } + + // name -> branch root that first bound it + let mut seen: HashMap = HashMap::new(); + for branch in unique { + let mut subtree = HashSet::new(); + collect_subtree(&branch, nodes, &mut subtree); + + let mut names_here: Vec = subtree + .iter() + .filter_map(|sid| nodes.get(sid).and_then(|n| n.result_name.clone())) + .collect(); + names_here.sort(); + names_here.dedup(); + + for name in names_here { + if let Some(prev_branch) = seen.get(&name) { + violations.push(( + node.id.clone(), + format!( + "JOIN {} binds result_name '{}' in multiple parallel branches ({} and {})", + node.id, name, prev_branch, branch + ), + )); + } else { + seen.insert(name, branch.clone()); + } + } + } + } + + finalize(INV_JOIN_NAMES, violations, rows); +} + +/// Evaluate every structural invariant over an in-memory snapshot. +/// +/// `instance_status` is the recorded `df.instances.status`, lowercased. Global +/// completeness is asserted only for completed instances; the local node-level +/// rules apply regardless of instance state. Rows are returned in invariant +/// order, violations sorted within each invariant. +fn evaluate_invariants( + nodes: &HashMap, + root_id: &str, + instance_status: &str, +) -> Vec { + let mut rows = Vec::new(); + let under_loop = nodes_under_loop(nodes); + check_query_json_well_formed(nodes, &mut rows); + check_reachable_completed(nodes, root_id, instance_status, &under_loop, &mut rows); + check_join_branches_completed(nodes, &under_loop, &mut rows); + check_untaken_if_branch_pending(nodes, &under_loop, &mut rows); + check_race_branch_completed(nodes, &mut rows); + check_join_result_name_disjoint(nodes, &mut rows); + rows +} + +/// Invariant 0 (static): a node whose `query` holds JSON configuration (IF/LOOP +/// `condition_node`, JOIN `extra_nodes`) must have a well-formed JSON *object*. +/// +/// [`OracleNode::config`] swallows parse errors with `.ok()`, so a malformed or +/// wrong-shape `query` would otherwise make the node look childless and pass the +/// branch checks by omission — unsound. This surfaces the corruption directly. +fn check_query_json_well_formed(nodes: &HashMap, rows: &mut Vec) { + let mut violations: Vec<(String, String)> = Vec::new(); + for id in sorted_ids(nodes) { + let node = &nodes[&id]; + if node.uses_json_query() && node.has_malformed_json_query() { + violations.push(( + node.id.clone(), + format!( + "{} node {} has a present but non-object/unparseable query JSON", + node.node_type, node.id + ), + )); + } + } + finalize(INV_QUERY_JSON, violations, rows); +} + +/// Load every `df.nodes` row for one instance into an id-keyed map (RLS-scoped). +/// +/// Delegates the positional SPI read to the canonical [`crate::db::NodeSnapshot`] +/// loader. Returns `None` when the instance has more than +/// [`crate::db::MAX_NODES_PER_INSTANCE`] nodes, so the caller can report +/// "instance too large" instead of evaluating a truncated snapshot (which could +/// manufacture a false "missing branch" violation). +fn load_oracle_nodes(instance_id: &str) -> Option> { + let snapshots = crate::db::load_node_snapshots(instance_id, crate::db::MAX_NODES_PER_INSTANCE)?; + let mut nodes = HashMap::with_capacity(snapshots.len()); + for s in snapshots { + nodes.insert( + s.id.clone(), + OracleNode { + id: s.id, + node_type: s.node_type, + query: s.query, + result_name: s.result_name, + left_node: s.left_node, + right_node: s.right_node, + status: s.status, + }, + ); + } + Some(nodes) +} + +// The pg_extern table-iterator type is repeated inline below because the +// `name!`/`TableIterator` macro expansion must appear in the function signature; +// keep it in sync with [`InvariantRow`]. +type InvariantRow = ( + name!(invariant, String), + name!(passed, bool), + name!(node_id, Option), + name!(detail, Option), +); + +/// Validate the structural invariants of a durable function instance against the +/// operational-semantics contract (`docs/dsl-semantics.md`). +/// +/// Returns one row per invariant when it holds, or one row per offending node +/// when it is violated. This is a sound *snapshot* oracle — it cannot catch +/// invariants that require execution/iteration counts (see the module docs). +/// +/// With `fail_on_violation => true` the function raises an error if any +/// invariant is violated, which makes it a one-line assertion for tests: +/// +/// ```sql +/// SELECT * FROM df.assert_structural_invariants('abc12345', true); +/// ``` +/// +/// The function is `STRICT`: a NULL argument returns the empty set instead of +/// running, so `df.assert_structural_invariants(id, NULL)` is a silent no-op +/// that never raises. Always pass a non-NULL boolean — the default is `false`. +#[pg_extern(schema = "df")] +pub fn assert_structural_invariants( + instance_id: &str, + fail_on_violation: default!(bool, "false"), +) -> TableIterator< + 'static, + // Inline form of [`InvariantRow`] — the `name!` macro must expand in the + // signature. Keep the two in sync. + ( + name!(invariant, String), + name!(passed, bool), + name!(node_id, Option), + name!(detail, Option), + ), +> { + // Ownership/existence check goes through RLS, so non-owned instances are + // invisible. Also fetch the root node and recorded status in one shot. + // `Some(1)` + `into_iter().next()` reads exactly the first row (a `for` + // loop that always returns would trip `clippy::never_loop`). + let info: Option<(Option, String)> = Spi::connect(|client| { + let sql = "SELECT root_node, status FROM df.instances WHERE id = $1"; + client + .select(sql, Some(1), &[instance_id.into()]) + .ok() + .and_then(|table| { + table.into_iter().next().map(|row| { + let root: Option = row.get(1).ok().flatten(); + let status: Option = row.get(2).ok().flatten(); + (root, status.unwrap_or_default()) + }) + }) + }); + + let (root_id, status) = match info { + Some((Some(root), status)) => (root, status), + Some((None, _)) => { + return finish( + vec![CheckRow { + invariant: "instance_has_root_node".to_string(), + passed: false, + node_id: Some(instance_id.to_string()), + detail: Some("instance exists but has no root_node".to_string()), + }], + fail_on_violation, + ); + } + None => { + return finish( + vec![CheckRow { + invariant: "instance_found".to_string(), + passed: false, + node_id: Some(instance_id.to_string()), + detail: Some( + "instance not found or not visible to the current user".to_string(), + ), + }], + fail_on_violation, + ); + } + }; + + // Snapshot soundness: a still-running instance has nodes legitimately in + // flight, and concurrent writes make a read racy. Evaluate only terminal + // instances; otherwise report a single (passed) skipped row. + if !is_terminal_instance(&status) { + return finish( + vec![CheckRow { + invariant: "instance_terminal".to_string(), + passed: true, + node_id: Some(instance_id.to_string()), + detail: Some(format!( + "skipped: instance status '{status}' is not terminal (still running)" + )), + }], + fail_on_violation, + ); + } + + let nodes = match load_oracle_nodes(instance_id) { + Some(nodes) => nodes, + None => { + return finish( + vec![CheckRow { + invariant: "instance_size".to_string(), + passed: false, + node_id: Some(instance_id.to_string()), + detail: Some(format!( + "instance has more than {} nodes; too large to evaluate", + crate::db::MAX_NODES_PER_INSTANCE + )), + }], + fail_on_violation, + ); + } + }; + let rows = evaluate_invariants(&nodes, &root_id, &status.to_ascii_lowercase()); + finish(rows, fail_on_violation) +} + +/// Convert check rows into the table iterator, optionally raising on violations. +fn finish(rows: Vec, fail_on_violation: bool) -> TableIterator<'static, InvariantRow> { + if fail_on_violation { + let violations: Vec<&CheckRow> = rows.iter().filter(|r| !r.passed).collect(); + if !violations.is_empty() { + let summary = violations + .iter() + .take(10) + .map(|r| { + format!( + "[{}] node={} {}", + r.invariant, + r.node_id.as_deref().unwrap_or("-"), + r.detail.as_deref().unwrap_or("") + ) + }) + .collect::>() + .join("; "); + pgrx::error!( + "df.assert_structural_invariants: {} violation(s): {summary}", + violations.len() + ); + } + } + + let tuples = rows + .into_iter() + .map(|r| (r.invariant, r.passed, r.node_id, r.detail)) + .collect::>(); + TableIterator::new(tuples) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn node( + id: &str, + ty: &str, + status: &str, + left: Option<&str>, + right: Option<&str>, + query: Option<&str>, + result_name: Option<&str>, + ) -> OracleNode { + OracleNode { + id: id.to_string(), + node_type: ty.to_string(), + query: query.map(str::to_string), + result_name: result_name.map(str::to_string), + left_node: left.map(str::to_string), + right_node: right.map(str::to_string), + status: status.to_string(), + } + } + + fn map(nodes: Vec) -> HashMap { + nodes.into_iter().map(|n| (n.id.clone(), n)).collect() + } + + /// Find the result row(s) for one invariant. + fn rows_for<'a>(rows: &'a [CheckRow], invariant: &str) -> Vec<&'a CheckRow> { + rows.iter().filter(|r| r.invariant == invariant).collect() + } + + fn passed(rows: &[CheckRow], invariant: &str) -> bool { + let r = rows_for(rows, invariant); + !r.is_empty() && r.iter().all(|x| x.passed) + } + + fn cond_if(cond: &str) -> Option { + Some(format!("{{\"condition_node\": \"{cond}\"}}")) + } + + // ----- child encoding ------------------------------------------------- + + #[test] + fn child_ids_include_condition_and_extra_nodes() { + let if_node = node( + "if1", + "IF", + "completed", + Some("then1"), + Some("else1"), + Some("{\"condition_node\": \"cond1\"}"), + None, + ); + let mut ids = if_node.child_ids(); + ids.sort(); + assert_eq!(ids, vec!["cond1", "else1", "then1"]); + + let join3 = node( + "j", + "JOIN", + "completed", + Some("a"), + Some("b"), + Some("{\"extra_nodes\": [\"c\"]}"), + None, + ); + let mut bids = join3.branch_ids(); + bids.sort(); + assert_eq!(bids, vec!["a", "b", "c"]); + } + + #[test] + fn if_rows_has_no_condition_node() { + let if_rows = node( + "if1", + "IF", + "completed", + Some("then1"), + Some("else1"), + Some("{\"condition_type\": \"result_has_rows\", \"result_name\": \"data\"}"), + None, + ); + assert_eq!(if_rows.condition_node_id(), None); + let mut ids = if_rows.child_ids(); + ids.sort(); + assert_eq!(ids, vec!["else1", "then1"]); + } + + // ----- every_reachable_node_completed -------------------------------- + + #[test] + fn reachable_skipped_for_non_completed_instance() { + let nodes = map(vec![node( + "r", + "SQL", + "running", + None, + None, + Some("SELECT 1"), + None, + )]); + let rows = evaluate_invariants(&nodes, "r", "running"); + let r = rows_for(&rows, INV_REACHABLE); + assert_eq!(r.len(), 1); + assert!(r[0].passed); + assert!(r[0].detail.as_ref().unwrap().contains("skipped")); + } + + #[test] + fn reachable_passes_for_completed_sequence() { + // THEN(completed) -> left SQL(completed), right SQL(completed) + let nodes = map(vec![ + node("t", "THEN", "completed", Some("a"), Some("b"), None, None), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "completed", None, None, Some("SELECT 2"), None), + ]); + let rows = evaluate_invariants(&nodes, "t", "completed"); + assert!(passed(&rows, INV_REACHABLE)); + } + + #[test] + fn reachable_flags_incomplete_node_on_path() { + let nodes = map(vec![ + node("t", "THEN", "completed", Some("a"), Some("b"), None, None), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "pending", None, None, Some("SELECT 2"), None), + ]); + let rows = evaluate_invariants(&nodes, "t", "completed"); + let r = rows_for(&rows, INV_REACHABLE); + assert!(r + .iter() + .any(|x| !x.passed && x.node_id.as_deref() == Some("b"))); + } + + #[test] + fn reachable_ignores_untaken_if_branch() { + // IF(completed) cond=c then=a(completed) else=b(pending subtree) + let nodes = map(vec![ + node( + "if", + "IF", + "completed", + Some("a"), + Some("b"), + cond_if("c").as_deref(), + None, + ), + node( + "c", + "SQL", + "completed", + None, + None, + Some("SELECT true"), + None, + ), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "pending", None, None, Some("SELECT 2"), None), + ]); + let rows = evaluate_invariants(&nodes, "if", "completed"); + assert!( + passed(&rows, INV_REACHABLE), + "untaken else branch must not be required completed" + ); + } + + #[test] + fn reachable_ignores_abandoned_race_loser() { + // RACE(completed): left winner completed, right loser still running. + let nodes = map(vec![ + node("rc", "RACE", "completed", Some("w"), Some("l"), None, None), + node("w", "SQL", "completed", None, None, Some("SELECT 1"), None), + node( + "l", + "SLEEP", + "running", + None, + None, + Some("{\"sleep_seconds\":60}"), + None, + ), + ]); + let rows = evaluate_invariants(&nodes, "rc", "completed"); + assert!(passed(&rows, INV_REACHABLE)); + } + + #[test] + fn reachable_loop_break_exit_does_not_require_condition() { + // LOOP(completed) body completed; while-condition present but pending + // (break-exit never evaluated it). Must still pass. + let nodes = map(vec![ + node( + "lp", + "LOOP", + "completed", + Some("body"), + None, + Some("{\"condition_node\": \"cond\"}"), + None, + ), + node( + "body", + "BREAK", + "completed", + None, + None, + Some("{\"break_value\": null}"), + None, + ), + node( + "cond", + "SQL", + "pending", + None, + None, + Some("SELECT false"), + None, + ), + ]); + let rows = evaluate_invariants(&nodes, "lp", "completed"); + assert!(passed(&rows, INV_REACHABLE)); + } + + // ----- join_all_branches_completed ----------------------------------- + + #[test] + fn join_branches_pass_and_fail() { + let ok = map(vec![ + node("j", "JOIN", "completed", Some("a"), Some("b"), None, None), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "completed", None, None, Some("SELECT 2"), None), + ]); + assert!(passed( + &evaluate_invariants(&ok, "j", "completed"), + INV_JOIN_BRANCHES + )); + + let bad = map(vec![ + node("j", "JOIN", "completed", Some("a"), Some("b"), None, None), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "running", None, None, Some("SELECT 2"), None), + ]); + let rows = evaluate_invariants(&bad, "j", "completed"); + let r = rows_for(&rows, INV_JOIN_BRANCHES); + assert!(r + .iter() + .any(|x| !x.passed && x.node_id.as_deref() == Some("b"))); + } + + // ----- untaken_if_branch_pending ------------------------------------- + + #[test] + fn untaken_if_pass() { + let nodes = map(vec![ + node( + "if", + "IF", + "completed", + Some("a"), + Some("b"), + Some("{\"condition_node\": \"c\"}"), + None, + ), + node( + "c", + "SQL", + "completed", + None, + None, + Some("SELECT true"), + None, + ), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "pending", None, None, Some("SELECT 2"), None), + ]); + assert!(passed( + &evaluate_invariants(&nodes, "if", "completed"), + INV_UNTAKEN_IF + )); + } + + #[test] + fn untaken_if_both_branches_taken_flagged() { + let nodes = map(vec![ + node( + "if", + "IF", + "completed", + Some("a"), + Some("b"), + Some("{\"condition_node\": \"c\"}"), + None, + ), + node( + "c", + "SQL", + "completed", + None, + None, + Some("SELECT true"), + None, + ), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "completed", None, None, Some("SELECT 2"), None), + ]); + let rows = evaluate_invariants(&nodes, "if", "completed"); + assert!(!passed(&rows, INV_UNTAKEN_IF)); + } + + #[test] + fn untaken_if_subtree_must_be_pending() { + // else branch root pending, but a node under it completed -> violation. + let nodes = map(vec![ + node( + "if", + "IF", + "completed", + Some("a"), + Some("bthen"), + Some("{\"condition_node\": \"c\"}"), + None, + ), + node( + "c", + "SQL", + "completed", + None, + None, + Some("SELECT true"), + None, + ), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node( + "bthen", + "THEN", + "pending", + Some("b1"), + Some("b2"), + None, + None, + ), + node("b1", "SQL", "completed", None, None, Some("SELECT 2"), None), + node("b2", "SQL", "pending", None, None, Some("SELECT 3"), None), + ]); + let rows = evaluate_invariants(&nodes, "if", "completed"); + let r = rows_for(&rows, INV_UNTAKEN_IF); + assert!(r + .iter() + .any(|x| !x.passed && x.node_id.as_deref() == Some("b1"))); + } + + // ----- race_at_least_one_branch_completed ---------------------------- + + #[test] + fn race_requires_a_completed_branch() { + let ok = map(vec![ + node("rc", "RACE", "completed", Some("w"), Some("l"), None, None), + node("w", "SQL", "completed", None, None, Some("SELECT 1"), None), + node( + "l", + "SLEEP", + "running", + None, + None, + Some("{\"sleep_seconds\":60}"), + None, + ), + ]); + assert!(passed( + &evaluate_invariants(&ok, "rc", "completed"), + INV_RACE_BRANCH + )); + + let bad = map(vec![ + node("rc", "RACE", "completed", Some("w"), Some("l"), None, None), + node("w", "SQL", "running", None, None, Some("SELECT 1"), None), + node( + "l", + "SLEEP", + "running", + None, + None, + Some("{\"sleep_seconds\":60}"), + None, + ), + ]); + assert!(!passed( + &evaluate_invariants(&bad, "rc", "completed"), + INV_RACE_BRANCH + )); + } + + // ----- join_branch_result_name_disjoint ------------------------------ + + #[test] + fn join_name_collision_flagged() { + let bad = map(vec![ + node("j", "JOIN", "completed", Some("a"), Some("b"), None, None), + node( + "a", + "SQL", + "completed", + None, + None, + Some("SELECT 1"), + Some("x"), + ), + node( + "b", + "SQL", + "completed", + None, + None, + Some("SELECT 2"), + Some("x"), + ), + ]); + assert!(!passed( + &evaluate_invariants(&bad, "j", "completed"), + INV_JOIN_NAMES + )); + + let ok = map(vec![ + node("j", "JOIN", "completed", Some("a"), Some("b"), None, None), + node( + "a", + "SQL", + "completed", + None, + None, + Some("SELECT 1"), + Some("x"), + ), + node( + "b", + "SQL", + "completed", + None, + None, + Some("SELECT 2"), + Some("y"), + ), + ]); + assert!(passed( + &evaluate_invariants(&ok, "j", "completed"), + INV_JOIN_NAMES + )); + } + + #[test] + fn all_invariants_present_in_output() { + let nodes = map(vec![node( + "r", + "SQL", + "completed", + None, + None, + Some("SELECT 1"), + None, + )]); + let rows = evaluate_invariants(&nodes, "r", "completed"); + for inv in [ + INV_REACHABLE, + INV_JOIN_BRANCHES, + INV_UNTAKEN_IF, + INV_RACE_BRANCH, + INV_JOIN_NAMES, + INV_QUERY_JSON, + ] { + assert!(!rows_for(&rows, inv).is_empty(), "missing invariant {inv}"); + } + } + + // ----- case-insensitive status (SF8) --------------------------------- + + #[test] + fn node_status_is_case_insensitive() { + // Mixed-case node statuses must be treated as completed. + let nodes = map(vec![ + node("t", "THEN", "COMPLETED", Some("a"), Some("b"), None, None), + node("a", "SQL", "Completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "completed", None, None, Some("SELECT 2"), None), + ]); + let rows = evaluate_invariants(&nodes, "t", "completed"); + assert!(passed(&rows, INV_REACHABLE)); + } + + // ----- missing IF condition node (MF1) ------------------------------- + + #[test] + fn missing_if_condition_node_flagged() { + // IF completed, condition_node referenced but absent from df.nodes. + let nodes = map(vec![ + node( + "if", + "IF", + "completed", + Some("a"), + Some("b"), + cond_if("cccc").as_deref(), + None, + ), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "pending", None, None, Some("SELECT 2"), None), + ]); + let rows = evaluate_invariants(&nodes, "if", "completed"); + let r = rows_for(&rows, INV_UNTAKEN_IF); + assert!( + r.iter() + .any(|x| !x.passed && x.node_id.as_deref() == Some("cccc")), + "missing IF condition node must be flagged" + ); + } + + // ----- break abandons JOIN sibling under a loop (MF3) ---------------- + + #[test] + fn join_under_loop_tolerates_break_abandoned_sibling() { + // LOOP body is a JOIN: one branch completed, the sibling running + // (abandoned by a break). Under the loop this is legitimate. + let nodes = map(vec![ + node( + "lp", + "LOOP", + "completed", + Some("j"), + None, + Some("{\"condition_node\": \"cond\"}"), + None, + ), + node("j", "JOIN", "completed", Some("a"), Some("b"), None, None), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node( + "b", + "SLEEP", + "running", + None, + None, + Some("{\"sleep_seconds\":60}"), + None, + ), + node( + "cond", + "SQL", + "pending", + None, + None, + Some("SELECT false"), + None, + ), + ]); + let rows = evaluate_invariants(&nodes, "lp", "completed"); + assert!( + passed(&rows, INV_JOIN_BRANCHES), + "break-abandoned sibling under a loop must not be a violation" + ); + assert!(passed(&rows, INV_REACHABLE)); + } + + #[test] + fn join_outside_loop_still_strict_about_running_sibling() { + // Same shape, but the JOIN is the root (not under a loop): strict. + let nodes = map(vec![ + node("j", "JOIN", "completed", Some("a"), Some("b"), None, None), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node( + "b", + "SLEEP", + "running", + None, + None, + Some("{\"sleep_seconds\":60}"), + None, + ), + ]); + let rows = evaluate_invariants(&nodes, "j", "completed"); + assert!(!passed(&rows, INV_JOIN_BRANCHES)); + } + + // ----- IF takes different branches across iterations (MF4) ----------- + + #[test] + fn if_under_loop_with_both_branches_completed_passes() { + // Inside a loop the IF ran both branches across iterations; both end + // `completed` (stale). The "exactly one branch" rule is skipped. + let nodes = map(vec![ + node( + "lp", + "LOOP", + "completed", + Some("if"), + None, + Some("{\"condition_node\": \"lcond\"}"), + None, + ), + node( + "if", + "IF", + "completed", + Some("a"), + Some("b"), + cond_if("ifc").as_deref(), + None, + ), + node( + "ifc", + "SQL", + "completed", + None, + None, + Some("SELECT true"), + None, + ), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "completed", None, None, Some("SELECT 2"), None), + node( + "lcond", + "SQL", + "completed", + None, + None, + Some("SELECT false"), + None, + ), + ]); + let rows = evaluate_invariants(&nodes, "lp", "completed"); + assert!( + passed(&rows, INV_UNTAKEN_IF), + "stale both-branches-completed under a loop must not be a violation" + ); + } + + // ----- malformed query JSON (SF1) ------------------------------------ + + #[test] + fn malformed_if_query_json_flagged() { + let nodes = map(vec![ + node( + "if", + "IF", + "completed", + Some("a"), + Some("b"), + Some("not json at all"), + None, + ), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + node("b", "SQL", "pending", None, None, Some("SELECT 2"), None), + ]); + let rows = evaluate_invariants(&nodes, "if", "completed"); + let r = rows_for(&rows, INV_QUERY_JSON); + assert!( + r.iter() + .any(|x| !x.passed && x.node_id.as_deref() == Some("if")), + "non-object IF query must be flagged" + ); + } + + #[test] + fn raw_sql_query_is_not_flagged_as_malformed_json() { + // SQL nodes carry raw SQL in `query`; they must not trip the JSON check. + let nodes = map(vec![node( + "s", + "SQL", + "completed", + None, + None, + Some("SELECT 1"), + None, + )]); + let rows = evaluate_invariants(&nodes, "s", "completed"); + assert!(passed(&rows, INV_QUERY_JSON)); + } + + // ----- duplicate branch id (SF2) ------------------------------------- + + #[test] + fn join_with_left_equals_right_flagged() { + let nodes = map(vec![ + node("j", "JOIN", "completed", Some("a"), Some("a"), None, None), + node( + "a", + "SQL", + "completed", + None, + None, + Some("SELECT 1"), + Some("x"), + ), + ]); + let rows = evaluate_invariants(&nodes, "j", "completed"); + let r = rows_for(&rows, INV_JOIN_NAMES); + assert!( + r.iter().any(|x| !x.passed + && x.detail + .as_deref() + .is_some_and(|d| d.contains("more than once"))), + "duplicate branch id must be flagged, not a spurious self-collision" + ); + } + + // ----- zero-branch JOIN (SF3) ---------------------------------------- + + #[test] + fn completed_join_with_no_branches_flagged() { + let nodes = map(vec![node("j", "JOIN", "completed", None, None, None, None)]); + let rows = evaluate_invariants(&nodes, "j", "completed"); + let r = rows_for(&rows, INV_JOIN_BRANCHES); + assert!(r + .iter() + .any(|x| !x.passed && x.node_id.as_deref() == Some("j"))); + } + + // ----- missing JOIN branch + 3-way name collision -------------------- + + #[test] + fn join_branch_missing_from_nodes_flagged() { + let nodes = map(vec![ + node("j", "JOIN", "completed", Some("a"), Some("b"), None, None), + node("a", "SQL", "completed", None, None, Some("SELECT 1"), None), + // branch "b" intentionally absent + ]); + let rows = evaluate_invariants(&nodes, "j", "completed"); + let r = rows_for(&rows, INV_JOIN_BRANCHES); + assert!(r + .iter() + .any(|x| !x.passed && x.node_id.as_deref() == Some("b"))); + } + + #[test] + fn join3_name_collision_via_extra_nodes() { + let nodes = map(vec![ + node( + "j", + "JOIN", + "completed", + Some("a"), + Some("b"), + Some("{\"extra_nodes\": [\"c\"]}"), + None, + ), + node( + "a", + "SQL", + "completed", + None, + None, + Some("SELECT 1"), + Some("x"), + ), + node( + "b", + "SQL", + "completed", + None, + None, + Some("SELECT 2"), + Some("y"), + ), + node( + "c", + "SQL", + "completed", + None, + None, + Some("SELECT 3"), + Some("x"), + ), + ]); + assert!(!passed( + &evaluate_invariants(&nodes, "j", "completed"), + INV_JOIN_NAMES + )); + } +} diff --git a/src/lib.rs b/src/lib.rs index 6dccf2d6..d0dea88b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,8 +32,10 @@ pub static ENABLE_SUPERUSER_INSTANCES: GucSetting = GucSetting::::ne // Module declarations pub mod activities; pub mod client; +pub mod db; pub mod dsl; pub mod explain; +pub mod invariants; pub mod monitoring; pub mod orchestrations; pub mod registry; @@ -413,6 +415,7 @@ DECLARE 'df.version()', 'df.debug_connection()', 'df.explain(text)', + 'df.assert_structural_invariants(text, boolean)', 'df.target_database()' ]; BEGIN diff --git a/src/monitoring.rs b/src/monitoring.rs index 97c583b7..58a81576 100644 --- a/src/monitoring.rs +++ b/src/monitoring.rs @@ -365,6 +365,11 @@ pub fn instance_nodes( let provider_schema = backend_duroxide_schema(); // Get node definitions from PostgreSQL (including status, result and updated_at) + // + // Projection note: columns 1–7 match `crate::db::NodeSnapshot` (the canonical + // base projection); this query intentionally adds `result::text` and + // `updated_at` (columns 8–9). Keep the positional reads in sync with + // `crate::db::load_node_snapshots` when the `df.nodes` column order changes. let node_defs: Vec<( String, String, diff --git a/tests/e2e/generated/.gitattributes b/tests/e2e/generated/.gitattributes new file mode 100644 index 00000000..dca42a38 --- /dev/null +++ b/tests/e2e/generated/.gitattributes @@ -0,0 +1,16 @@ +# Phase 2 + Phase 4 + Phase 3 (#232) generated-test artifacts. +# +# The committed golden manifests and the generator sources are normalized to LF +# in the index so a Windows-authored commit and a Linux CI run regenerate them +# byte-for-byte identically — keeping the `--check` determinism gate stable +# across platforms regardless of each developer's core.autocrlf setting. (The +# generator's `check_artifact` also normalizes CRLF on read, so this is +# defense-in-depth; it additionally keeps the committed goldens diff-clean.) +# +# The Phase 3 proptest failure corpus (proptest-regressions/*.txt) is likewise +# pinned to LF: proptest hashes each seed line, and a committed CRLF copy read +# back on Linux CI would not match, silently dropping the regression guard. +*.json text eol=lf +*.sql text eol=lf +generator/src/*.rs text eol=lf +generator/proptest-regressions/*.txt text eol=lf diff --git a/tests/e2e/generated/README.md b/tests/e2e/generated/README.md new file mode 100644 index 00000000..a577f3f5 --- /dev/null +++ b/tests/e2e/generated/README.md @@ -0,0 +1,499 @@ +# Phase 2 — combinator-nesting test matrix + +Part of the DSL automated-testing roadmap ([#232](https://github.com/microsoft/pg_durable/issues/232)). + +A deterministic generator enumerates every combinator-**nesting** shape up to a +bounded depth, renders each to a self-contained pg_durable DSL E2E test with +marker leaves, runs them live, and asserts both the Phase 1 structural-invariant +oracle (`df.assert_structural_invariants`) **and** generator-known per-path +execution counts. The goal is broad, automatic coverage of *how combinators +compose* — the corner of the grammar humans rarely test exhaustively by hand. + +## Layout + +| Path | Committed? | What it is | +|---|---|---| +| `generator/` | ✅ yes | The standalone generator crate (std-only Rust, no pgrx dep). | +| `manifest.json` | ✅ yes | Golden regression baseline: every shape's id, signature, class, reason, DSL, expected per-path counts, and (Phase 5) `order` — the reference interpreter's happens-before edges. The `--check` determinism guard diffs against this. | +| `meta-manifest.json` | ✅ yes | Golden baseline for the **Phase 4** metamorphic relations (see below): every relation's id, name, rationale, both DSL programs, and expected observable. The same `--check` guard diffs against this. | +| `README.md` | ✅ yes | This file. | +| `sql/` | 🚫 gitignored | Live (clean) E2E tests, regenerated on demand. Holds both the Phase 2 `gen-*.sql` matrix and the Phase 4 `meta-*.sql` relations. | +| `quarantine/` | 🚫 gitignored | Known-failing E2E tests for documented product bugs, regenerated on demand. | + +The `.sql` files are **derived artifacts** — they are never committed; CI +regenerates them from the generator crate. `manifest.json` is the only +generated output under version control, and it is what makes generation +auditable and deterministic. + +## Regenerating + +```bash +# default: --max-depth 2, combinators seq,if,loop,join,race, seeds included +make generate-matrix +# equivalently: +cargo run --manifest-path tests/e2e/generated/generator/Cargo.toml +``` + +This (re)writes `sql/*.sql` + `quarantine/*.sql` and refreshes `manifest.json`. + +Useful flags (`--help` for the full list): + +| Flag | Default | Purpose | +|---|---|---| +| `--max-depth N` | `2` | Maximum combinator-nesting depth. Depth-2 is the live CI budget. | +| `--combinators LIST` | `seq,if,loop,join,race` | Comma list; subset of `seq,if,loop,join,join3,race`. | +| `--full` | — | Shortcut for the full set including `join3`. | +| `--loop-iters K` | `2` | Iterations each generated loop runs (≥2 is what trips the loop bugs). | +| `--max-shapes N` | none | Cap (≥1) on the sorted shape list, applied **after** enumeration; bounds output size, not enumeration cost at high depth. | +| `--no-seeds` | — | Exclude the hand-written else/break seed shapes. | +| `--wait-timeout N` | `60` | Seconds each **live** test waits for completion. | +| `--quarantine-timeout N` | `10` | Seconds each **quarantined** test waits (they hang, so keep this short). | +| `--check` | — | Regenerate the manifest in memory and diff vs the committed copy. Non-zero exit on drift. | + +### Determinism + +Generation is a pure function of the CLI inputs — no clocks, no RNG, no +filesystem order. `cargo run … -- --check` regenerates the manifest and diffs it +against the committed `manifest.json`; any drift fails. Run it after touching the +generator and commit the refreshed `manifest.json` in the same change. + +## Each shape, end to end + +1. **Enumerate** — `shape.rs` builds the deduped set of nesting shapes for the + requested depth and combinator set, plus a few hand-written seed shapes that + exercise `else`/`break`. +2. **Render** — `render.rs` turns a shape into a DSL string. Every leaf is a + `df.sql` marker that `INSERT`s into a shared `df_gen_trace(shape_id, + node_path, iteration, wall_clock)` table. Each leaf knows its **node path** + (e.g. `r.t.e` = root → then → else) and the renderer computes the **expected + iteration count** for every path. `if` conditions are rendered to take a + deterministic branch; `race` is rendered so the fast branch deterministically + wins (slow branch sleeps); loops get a generated bounded-iteration condition. +3. **Isolate** — every trace row is tagged with the shape's unique `shape_id`, + and every per-path count query is `shape_id`-scoped. This keeps shapes from + contaminating each other's marker counts when the harness runs them in the + same database (it runs sequentially, but isolation makes a shape's assertions + independent of execution order and of earlier residue). +4. **Emit** — `emit.rs` writes a self-contained `.sql` test (truncate this + shape's trace → `df.start` → `wait_for_completion` → assert + `df.assert_structural_invariants` all pass → assert per-path counts match the + generator's expectation → assert no unexpected paths → **(Phase 5, live shapes + only) assert the causal order**: for every happens-before edge the reference + interpreter derived, `earlier.wall_clock < later.wall_clock` → `TEST PASSED`) + and a `manifest.json` entry (now including the `order` edge list). +5. **Run** — the E2E harness picks the files up (see below). + +### Node path scheme + +Every marker leaf carries a **node path** string (the `node_path` column in +`df_gen_trace`) built by walking from the root and appending one suffix per +combinator edge. The renderer computes the expected execution count per path; +the generated test asserts `COUNT(*)` per path matches. The suffixes +(`generator/src/render.rs`): + +| Suffix | Combinator | Meaning | +|---|---|---| +| `r` | — | Root of the shape (the starting path). | +| `.0` / `.1` | `seq` | First step / second step. | +| `.0` / `.1` | `join` | First branch / second branch. | +| `.0` / `.1` / `.2` | `join3` | First / second / third branch. | +| `.t` / `.e` | `if` | `then` branch / `else` branch. | +| `.c` | `loop` | Loop **counter** marker (drives the bounded termination condition). | +| `.b` | `loop` | Loop **body** subtree (executes once per iteration). | +| `.w` / `.l` | `race` | **W**inner (fast) branch / **l**oser (slow) branch. | +| `.0` | `break` | The break node's marker (runs each iteration until the bound). | + +Paths nest, so e.g. `r.t.b.0` = root → `if` then-branch → `loop` body → first +`seq` step. A losing `race` branch or an untaken `if` branch still gets a path, +but its expected count is `0` (the DSL node is emitted; it just never executes). + +## Running the matrix + +```bash +# Live (blocking) matrix — only tests/e2e/generated/sql/*.sql +./scripts/test-e2e-local.sh --include-generated + +# Known-failing (non-blocking) matrix — only tests/e2e/generated/quarantine/*.sql +./scripts/test-e2e-local.sh --include-generated-quarantine +``` + +`--include-generated` globs **only** `sql/`, so quarantined shapes never block a +clean run. Both flags require the files to exist first (`make generate-matrix`). + +## The loop bug, quarantine, and the xfail policy + +Generating the depth-2 matrix surfaced a family of **pre-existing product +defects** in how loops behave when nested inside other combinators. Rather than +delete those shapes (losing coverage) or let them turn CI red (blocking +unrelated work), the generator **classifies** each shape and **splits** the +output: + +- **live → `sql/`** — shapes expected to pass today. These are **blocking** in CI. +- **quarantine → `quarantine/`** — shapes that hit a known, filed product bug. + Their generated test asserts the **correct** expected behavior, so it *fails* + today. These run **non-blocking** (xfail) so the bug stays continuously + documented and measured without gating merges. + +At `--max-depth 2` the split is **128 live / 26 quarantined** (154 total). + +### Quarantine reasons (depth-2) + +| `reason` | Count | Shape family | Tracking | +|---|---|---|---| +| `loop-in-join` | 11 | a `loop` inside a `join`/`join3` branch | [#233](https://github.com/microsoft/pg_durable/issues/233) | +| `loop-in-race-winner` | 6 | a `loop` in a `race`'s winning branch | [#233](https://github.com/microsoft/pg_durable/issues/233) | +| `loop-in-seq-tail` | 6 | a `loop` after a sibling in a `seq` (not the first step) | [#227](https://github.com/microsoft/pg_durable/issues/227) | +| `loop-body-combinator` | 3 | a `loop` whose body is itself a combinator | [#230](https://github.com/microsoft/pg_durable/issues/230) | + +**Common root cause:** `continue_as_new` (used by the loop node on every +continuing iteration) restarts *whichever orchestration is currently executing, +from its root*. That is only correct when the loop **is** the root. Nested under +another combinator, it restarts the wrong host orchestration (or reuses a +completed child id), so the second iteration fails terminally or silently +re-runs siblings. See the issues above; a single host-aware fix likely resolves +all three. + +The classifier lives in `generator/src/shape.rs` (`is_problematic` / +`classify_for_quarantine`). Its output is pinned by the `EMPIRICAL_FAILS` table — +the 26 shape signatures observed to fail live at depth 2 — and the contract test +`is_problematic_matches_empirical_depth2_failset` asserts the classifier flags +*exactly* that set (the **128 live / 26 quarantined** split). So the split can't +silently drift: any classifier change that re-balances it fails that test until +`EMPIRICAL_FAILS` is updated in the same commit. + +### Promotion path (xfail → live) + +When a product bug is fixed: + +1. Re-run `make generate-matrix`. The now-correct shapes still classify as + quarantine (the classifier is intentionally conservative). +2. Confirm they pass: `./scripts/test-e2e-local.sh --include-generated-quarantine` + should report the affected shapes green. +3. Remove the corresponding arm from the classifier in `shape.rs` and drop those + signatures from `EMPIRICAL_FAILS`, updating the + `is_problematic_matches_empirical_depth2_failset` contract test in the same + commit, so those shapes generate into `sql/` and become **blocking**. +4. Regenerate, re-run `--check`, commit the refreshed `manifest.json`. + +## Phase 4 — metamorphic relations + +Part of the same roadmap ([#232](https://github.com/microsoft/pg_durable/issues/232)), +living in the **same generator crate** (`generator/src/meta.rs`). Where Phase 2 +asks *"does this one program execute the way we computed?"*, Phase 4 asks *"do +two programs the runtime is **supposed to treat as equivalent** actually produce +the same observable result?"* — a metamorphic relation. We build a pair +`(program_a, program_b)` plus an equivalence predicate, run **both**, and assert +they agree. + +### Labels, not paths + +Phase 2 tags each marker by its **structural** `node_path` (`r`, `r.t`, `r.b.0`, +…). That works when comparing a program against *itself*, but two +structurally-different-yet-equivalent programs have **different** paths for the +*same logical leaf* — `seq(a, seq(b,c))` vs `seq(seq(a,b), c)` put leaf `b` at +different paths. So Phase 4 tags markers by a **stable leaf label** (`a`, `b`, +`c`): the same logical leaf gets the same label in **both** sides of the pair. + +The **observable** of a run is the multiset `{label -> completed-count}` — the +`GROUP BY node_path` count over that run's trace rows. The **equivalence +predicate** is multiset equality. A leaf that never executes (an untaken `if` +branch, an abandoned `race` loser) writes no trace rows and so contributes `0` +automatically — e.g. `if(true, a, b) ≡ a` yields `{a:1}` on both sides. + +### The registry + +`registry()` returns the relations below; each asserts `observable(A) == +observable(B)`. All seven hold under both the *correct* and the *current* +runtime. + +| id | name | A | B | observable | +|---|---|---|---|---| +| `meta-0001` | seq-assoc | `seq(a, seq(b,c))` | `seq(seq(a,b), c)` | `{a:1, b:1, c:1}` | +| `meta-0002` | if-true | `if(T, a, b)` | `a` | `{a:1}` | +| `meta-0003` | if-false | `if(F, a, b)` | `b` | `{b:1}` | +| `meta-0004` | join-comm | `join(a, b)` | `join(b, a)` | `{a:1, b:1}` | +| `meta-0005` | race-winner | `race(a, sleep(N))` | `a` | `{a:1}` | +| `meta-0006` | do-while-once | `loop(a, COUNT(a) < 1)` | `a` | `{a:1}` | +| `meta-0007` | loop-break-once | `loop(seq(a, if(COUNT(a) >= 1, break)))` | `a` | `{a:1}` | + +### Each relation's test, end to end + +`meta.rs` renders one `sql/meta-NNNN.sql` per relation. It reuses Phase 2's exact +marker / `if` / `loop` / `race` / `break` DSL, so the leaf semantics are +identical. Both programs run in the **same** `df_gen_trace`, tagged +`meta-NNNN-a` / `meta-NNNN-b` for isolation. The test: + +1. `df.start` **both** programs, then `wait_for_completion` and assert **both** + reach `completed`. +2. Assert `df.assert_structural_invariants` passes for **both** instances (the + Phase 1 oracle — each side must be internally well-formed). +3. **Headline:** `observable(A) == observable(B)` via an `EXCEPT`-based multiset + symmetric difference over `(SELECT node_path, COUNT(*) … GROUP BY node_path)`; + assert the diff is empty. +4. **Backstop:** assert each side's per-label counts equal the generator-computed + expected multiset, plus a `NOT IN ()` guard that no **unexpected** + label appears. + +The backstop matters because the Phase 1 invariants are **pure-state** (they +never count executions), so the headline `A == B` check alone would pass if +*both* sides symmetrically **over-executed** (e.g. `{a:2}` on both). The backstop +pins each side to absolute ground truth and closes that blind spot. + +### Why loops don't pollute the observable + +A naive bounded loop needs a counter leaf, which would add a spurious label. So +the loop relations instead make the termination condition count an **existing +body leaf** (the anchor): `loop(a, COUNT(a) < 1)` runs the body once (loops are +do-while — body first, then condition — see `docs/dsl-semantics.md`), re-checks, +finds `COUNT(a) = 1`, and stops. No synthetic counter; the observable stays +exactly `{a:1}`. + +### Why `race(a, sleep(N))` is sound + +The race loser is a bare `df.sleep(N)` that contributes nothing: when the marker +wins, duroxide abandons the sleep the instant the winner completes (no added +latency). The oracle accepts this — its `race` invariant only requires **≥1** +completed branch and its reachability check ignores abandoned losers +(`src/invariants.rs`) — so the winner-only program validates cleanly. + +### Live-only, v1 + +Every relation holds under the **current** runtime because none nests a loop in a +non-root / `join` / `race` position — the [#227](https://github.com/microsoft/pg_durable/issues/227) +/ [#230](https://github.com/microsoft/pg_durable/issues/230) / +[#233](https://github.com/microsoft/pg_durable/issues/233) bug zone. So Phase 4 +needs no quarantine split: `meta-*.sql` are all **live / blocking**. A natural +future extension is **bug-seeding** metamorphic relations — e.g. a loop-at-root +program paired with an equivalent loop-nested-non-root one — which would *fail* +until those loop bugs are fixed; deferred until the relations above are confirmed +live in CI. + +### Running / regenerating + +`make generate-matrix` (or the bare `cargo run …`) writes `sql/meta-*.sql` +alongside `gen-*.sql` and refreshes `meta-manifest.json`. The live harness picks +them up with the **same** flag — no new flag needed: + +```bash +# runs gen-*.sql AND meta-*.sql (both live in sql/) +./scripts/test-e2e-local.sh --include-generated +``` + +`cargo run … -- --check` diffs **both** `manifest.json` and `meta-manifest.json`, +so metamorphic-relation drift fails CI exactly like Phase 2 shape drift. + +## Phase 3 — property-based testing (proptest) + +Phase 2 enumerates a *fixed, exhaustive* depth-2 matrix; Phase 4 pins *seven +hand-written* equivalences. Phase 3 generalizes both: a recursive +[`proptest`](https://proptest-rs.github.io/proptest/) `Strategy` +(`generator/src/prop.rs`) emits **thousands of random labeled-leaf trees per +run** and asserts algebraic + structural properties over them — and, when a +property fails, proptest **shrinks** the random tree down to a *minimal* +counterexample. + +### Why the model, not live PostgreSQL + +Phase 3's headline value-add over Phase 2 (per the issue) is **shrinking**, and +shrinking only works when the property executes **in-process**: proptest drives +the reduction loop by re-running the predicate on progressively smaller inputs. +A property that round-trips through a live `df.start()` cannot shrink. So the +properties run over the same pure reference model Phase 4 already trusts — the +`Meta` interpreter (`eval`/`observable`) and the renderer (`render_prog`) — which +is the std-only analogue of the issue's `FunctionGraph`. This is not a coverage +downgrade: exhaustive depth-2 **live** oracle coverage already exists (Phases +2+4), and because Phase 4's live ground-truth is *computed by `eval`*, every +property that hardens `eval` strengthens the live suite transitively. + +### What it checks + +`proptest! { … }` runs 12 properties over `Meta` trees (each shrinking-enabled, +`PROPTEST_CASES` random trees apiece); the same block also runs Phase 5's 6 +causal-order properties over `Shape` trees — see [Phase 5](#phase-5--reference-interpreter--causal-order-oracle): + +| # | Property | Catches | +|---|----------|---------| +| 1 | `eval` is deterministic | hidden state / ordering bugs | +| 2 | **differential**: `eval` vs an independent functional `ref_observable` over the whole random space | interpreter logic errors (anti-circular; a Phase-5 bridge) | +| 3–8 | metamorphic **algebra** on random subtrees: seq-assoc, join-comm, join-assoc, seq≡join multiset, if-true/false selection, race→winner | generalizes Phase 4's 7 hand cases to thousands | +| 9 | loop multiplier scales body counts (DoWhile `k` / LoopBreak `n`, 1..=4) | off-by-one / saturating-mul bugs | +| 10 | total completed-count is conserved across equivalent forms | silent over/under-execution | +| 11 | `render_prog` is deterministic | non-reproducible SQL | +| 12 | rendered SQL is **well-formed**: balanced parens (ignoring `$mk$`/`$c$` dollar-quoted spans), even dollar-quote counts, no leaked `df.start`, `df.race(`/`df.sleep(` and `df.loop(`/`df.break()` arity matches the tree, every label present | renderer corruption | + +Four helper unit tests (`mod helper_tests`) cross-check the *independent oracles* +themselves (`ref_observable`, `first_executing_label`, `node_counts`, the +paren-balancer) against hand cases, so a bug in the test scaffolding can't mask a +bug in the model. + +### The strategy + +`arb_meta()` is a `prop_recursive(4, 48, 3, …)` over weighted `prop_oneof!` +knobs (seq=3, join=2, if=2, race=1, dowhile=1, loopbreak=1) with labels from a +small alphabet (`a`–`e`). Loop anchors are chosen via `first_executing_label` +(the first leaf the body actually executes), so every generated loop terminates +and counts a real leaf — keeping the observable pure, exactly like Phase 4. The +strategy can emit `loop`-in-`join`/`race` shapes (the +[#227](https://github.com/microsoft/pg_durable/issues/227)/[#230](https://github.com/microsoft/pg_durable/issues/230)/[#233](https://github.com/microsoft/pg_durable/issues/233) +defect zone), so a future live harness inherits corpus coverage there. + +### Failure corpus + +When a property fails, proptest writes the minimal seed to +`generator/proptest-regressions/prop.txt` and **replays it first on every +subsequent run**. That file is committed (LF-normalized via `.gitattributes`) so +a counterexample becomes a permanent, shared regression guard. It is empty today +because no property has failed. + +### Isolation — goldens stay byte-identical + +`proptest` is a **`[dev-dependencies]`** entry only. The generation binary +(`cargo run` / `--check`) never links it, so adding it left `manifest.json` and +`meta-manifest.json` (7730 B) byte-for-byte unchanged, and `prop.rs` is +`#[cfg(test)] mod prop;` — it compiles only under `cargo test`. (Phase 5 Step 2 +later changed `manifest.json` deliberately by adding the `order` field — now +198914 B — but that is the *binary's* output, independent of the proptest dep.) + +### Running + +```bash +# committed corpus + 1024 fresh random trees per property (override the budget): +make proptest +PROPTEST_CASES=8192 make proptest + +# or directly — the default in-code budget is 256 cases: +cargo test --manifest-path tests/e2e/generated/generator/Cargo.toml +``` + +## Phase 5 — reference interpreter & causal-order oracle + +Phases 2 and 4 differential-test **counts** and **multisets** against the live +duroxide runtime (via `df_gen_trace`). Neither checks **order**: that two events +on the same path happened in iteration order, that a `seq` ran its left side +*before* its right, or that two `join` branches were genuinely *unordered*. +Phase 5 adds that missing dimension with a **reference interpreter** +(`generator/src/refinterp.rs`) — a synchronous, single-threaded tree-walker over +`Shape` that produces, per program, a **pomset** (partially-ordered multiset): + +- **events** — a deterministic linearization of `(node_path, iteration)` rows, + exactly the columns the live marker writes into `df_gen_trace`; and +- **edges** — the happens-before (`≺`) relation as forward index pairs + `(earlier, later)`. + +It implements the [`docs/dsl-semantics.md`](../../../docs/dsl-semantics.md) +ordering contract *directly* (§4 Seq: all-of-`a` ≺ all-of-`b`; §6 do-while loop: +body ≺ counter and iteration `i` ≺ `i+1`; §7 Join/Join3: branches **concurrent**, +no edge; §7 Race: winner-only; §5 If: taken branch only). Concurrent siblings get +**no** edge, so the relation is a DAG by construction (`all_edges_point_forward`). + +### A third, independent interpreter + +The renderer (`render`) computes per-path counts in **closed form** (arithmetic +`mult`). The interpreter computes them by **step-by-step simulation**. They are +written independently, so agreement is strong evidence both are correct — +`counts_match_render(shape, k)` projects the pomset to per-path counts and +asserts equality with `render(shape, k, …).expected` (filtered to the reachable +paths the interpreter emits). The headline unit test runs that differential over +the **entire depth-2 matrix** (`shapes_up_to(&ALL_COMBS, 2)` + seeds) × every +`k ∈ {1, 2, 3}`. This is a *third* interpreter alongside Phase 4's `eval` and +Phase 3's `ref_observable` — three implementations of the same semantics, each +guarding the others. + +### Causal-order properties (proptest) + +The same `proptest! { … }` block adds **6 properties over random `Shape` trees** +(`arb_shape()`, the node-path-tagged analogue of `arb_meta()`), each shrinking- +enabled: + +| # | Property | Catches | +|---|----------|---------| +| 13 | **differential**: interpreter counts == `render`'s closed-form `expected`, for every tree and every `k ∈ 1..=3` | divergence between simulation and arithmetic | +| 14 | interpreter is deterministic (identical events **and** edges) | hidden ordering / state bugs | +| 15 | every happens-before edge points strictly forward (`u < v`) | cyclic / self-contradictory order | +| 16 | per path, iterations are the dense range `1..=count` | gaps / duplicate iteration numbers | +| 17 | **causal-order law (Seq)**: `Seq(a,b)` preserves each child's edge set exactly and adds only forward `a ≺ b` cross edges — non-empty iff both sides execute | sequencing that drops, reorders, or fails to impose happens-before | +| 18 | **causal-order law (Join)**: `Join(a,b)`'s edge set is **exactly** `a`'s edges ∪ `b`'s (shifted) — no cross edge either way | spurious ordering between parallel branches | + +Properties 17–18 are the order-level analogues of Phase 4's count laws and are +Phase 5's unique value-add: 17 proves `seq` *introduces* happens-before, 18 proves +`join` *introduces none* — the two facts a live wall-clock assertion would rely +on. 14 further unit tests (`refinterp::tests`) pin each combinator against the +§4–§7 contract by hand. + +### Staged scope + +**Step 1** built the model-level interpreter, its differential, and the +causal-order properties — all in-process, no live PostgreSQL. + +**Step 2 (this slice) closes the loop to the live runtime.** The generation +binary now links `refinterp` (no longer `#[cfg(test)]`; only its count-projection +helpers — `path_counts`, `counts_match_render` — stay test-only). For every shape +the binary derives the happens-before edges via `Pomset::ordered_pairs()` and: + +- records them as an **`order` golden** in `manifest.json` (one `["earlier_path", + iter, "later_path", iter]` entry per `≺` edge) for **all** 88 edge-bearing + shapes — live *and* quarantined — since the interpreter computes order + regardless of class; and +- emits a **causal-order assertion block** into each **live** (non-quarantined) + `.sql` test (62 of the 128 live shapes — the rest are pure marker/join/race/if + trees with no `≺` edge). The block is one set-based aggregate: it joins each + edge's two endpoints back to `df_gen_trace` and raises iff any edge ran with + `earlier.wall_clock >= later.wall_clock`. Concurrent (join/race) siblings carry + **no** edge, so they are never compared — the partial order is exactly what + makes the assertion flake-free. + +The per-path COUNT assertions run **first**, so by the time the order block runs +every referenced `(node_path, iteration)` row is guaranteed to exist (iterations +are dense `1..=count`) — the inner joins can never silently drop an edge. Markers +run in distinct duroxide activities milliseconds apart, far above +`clock_timestamp()`'s microsecond resolution, so an honored edge always shows a +strict gap. + +**Clock assumptions (known limitation).** The oracle compares `clock_timestamp()` +values, which track wall-clock time rather than a monotonic counter. It is sound +under the assumption that the clock advances strictly between two *sequentially +scheduled* activities — which holds in practice because each duroxide activity +round-trip (orchestration await → re-dispatch → SQL) takes milliseconds, dwarfing +`clock_timestamp()`'s microsecond resolution, so a collision (equal timestamps) +is implausible. The one theoretical way an *honored* edge could be flagged is a +backward wall-clock step — e.g. an NTP correction — landing inside a single test's +sub-second window; this has not been observed, and it would surface as a loud, +reproducible `TEST FAILED [...]: causal-order violation(s)` rather than silent +corruption. Concurrent (join/race) siblings carry no edge and are never compared, +so the assumption applies only to genuinely ordered events. A fully +clock-independent variant (assert on a monotonic per-trace sequence instead of +`wall_clock`) is possible but deferred — it would require a `df_gen_trace` schema +change and is unwarranted at the observed timing margins. + +Step 2 regenerates `manifest.json` (now 198914 B — the added `order` field; this +intentionally breaks Steps 2–5-Step-1's byte-identical property). `meta-manifest.json` +(7730 B) is untouched. + +### Running + +```bash +# the interpreter's unit tests + the 6 causal-order properties run with the suite: +cargo test --manifest-path tests/e2e/generated/generator/Cargo.toml +PROPTEST_CASES=8192 make proptest # widen the random Shape budget too +``` + +## CI + +CI regenerates the matrix, runs the live set as a blocking gate, runs the +quarantine set non-blocking (`continue-on-error`), and runs `--check` to enforce +determinism. The live set and the determinism guard both cover the Phase 4 +`meta-*.sql` relations and `meta-manifest.json` automatically — the same generate +step emits them, `--include-generated` runs them, and `--check` diffs their +golden. The generator's unit tests (run as their own blocking gate) include the +Phase 4 interpreter and registry tests. The depth-2 live budget is sized to stay +within the E2E job's time envelope; deeper profiles are available on demand via +`--max-depth`. + +The same `Generated Matrix` job also gates Phase 3: a `cargo clippy --all-targets +-D warnings` step lints the `#[cfg(test)]` proptest module, and the existing +`cargo test` step runs the properties — replaying the committed +`proptest-regressions/` corpus first, then exploring `PROPTEST_CASES` (256 on +PRs) fresh random trees per property. A **nightly / on-demand** step widens that +budget with a fresh seed to hunt for new counterexamples; it is non-blocking +(mirroring the quarantine nightly) and surfaces any minimal counterexample it +writes for a human to commit as a permanent regression seed. diff --git a/tests/e2e/generated/generator/Cargo.lock b/tests/e2e/generated/generator/Cargo.lock new file mode 100644 index 00000000..d9e2834a --- /dev/null +++ b/tests/e2e/generated/generator/Cargo.lock @@ -0,0 +1,627 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pg_durable_matrix_gen" +version = "0.1.0" +dependencies = [ + "proptest", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/tests/e2e/generated/generator/Cargo.toml b/tests/e2e/generated/generator/Cargo.toml new file mode 100644 index 00000000..8e4673f2 --- /dev/null +++ b/tests/e2e/generated/generator/Cargo.toml @@ -0,0 +1,26 @@ +# Standalone matrix generator for the Phase 2 combinator-nesting test suite (#232). +# Deliberately carries an empty [workspace] table so cargo treats this directory as +# its own workspace root and does NOT fold it into the parent pg_durable extension +# build. It is std-only (no dependencies, hand-rolled JSON) so it stays fast and has +# no bearing on the extension's dependency graph. +[package] +name = "pg_durable_matrix_gen" +version = "0.1.0" +edition = "2021" +publish = false +license = "PostgreSQL" + +[[bin]] +name = "pg_durable_matrix_gen" +path = "src/main.rs" + +# Phase 3 (#232): proptest drives the recursive random-tree property suite in +# src/prop.rs. It is a DEV-dependency ONLY, so it compiles solely under +# `cargo test` — the generation binary (`cargo run`/`cargo build`) and the +# `--check` determinism gate stay dependency-free and the committed goldens +# (manifest.json / meta-manifest.json) remain byte-identical. +[dev-dependencies] +proptest = "1" + +# Empty workspace table: isolates this crate from the parent extension workspace. +[workspace] diff --git a/tests/e2e/generated/generator/src/emit.rs b/tests/e2e/generated/generator/src/emit.rs new file mode 100644 index 00000000..aef6e85a --- /dev/null +++ b/tests/e2e/generated/generator/src/emit.rs @@ -0,0 +1,501 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! Emits the two committed-or-generated artifacts from a rendered shape: +//! a self-contained `.sql` E2E test (gitignored, regenerated on demand) and an +//! entry in the golden `manifest.json` (committed regression baseline). + +use crate::shape::Comb; +use std::collections::BTreeMap; + +/// One fully-resolved shape ready to emit. +pub struct ShapeRecord { + pub id: String, + pub signature: String, + pub depth: u32, + /// `"live"` for shapes expected to pass, `"quarantine"` for shapes a known + /// product loop-nesting defect (#227/#230) makes fail. + pub class: &'static str, + /// The structural reason a shape is quarantined, or `None` when it is live. + pub reason: Option<&'static str>, + pub dsl: String, + pub expected: BTreeMap, + /// Phase 5 (#232): the reference interpreter's happens-before (`≺`) edges as + /// `((earlier_path, earlier_iter), (later_path, later_iter))` pairs. Live + /// `.sql` tests assert each pair as `earlier.wall_clock < later.wall_clock`; + /// the manifest records them as the committed causal-order golden. + pub ordered_pairs: Vec<((String, u64), (String, u64))>, +} + +/// Parameters that describe how a matrix was generated (manifest header). +pub struct MatrixMeta<'a> { + pub max_depth: u32, + pub combinators: &'a [Comb], + pub loop_iters: u64, + pub include_seeds: bool, +} + +/// Renders the full text of a self-contained `.sql` E2E test for one shape. +/// +/// The test starts the durable instance, waits for completion, asserts the +/// Phase 1 structural-invariant oracle passes, asserts the exact per-path +/// execution counts, asserts no unexpected node paths appear, and — for live +/// shapes — asserts the Phase 5 causal-order oracle: every happens-before edge +/// the reference interpreter derived holds as `earlier.wall_clock < later`. +pub fn sql_test(rec: &ShapeRecord, wait_timeout: u32) -> String { + let id = &rec.id; + // Phase 5 (#232): only live shapes get the causal-order block — quarantined + // shapes already fail their COUNT assertions, and an empty edge set (a lone + // marker, or concurrent-only join) has nothing to assert. + let emit_order = rec.reason.is_none() && !rec.ordered_pairs.is_empty(); + let mut out = String::new(); + + out.push_str("-- Copyright (c) Microsoft Corporation.\n"); + out.push_str("-- Licensed under the PostgreSQL License.\n"); + out.push_str("--\n"); + out.push_str("-- AUTO-GENERATED by pg_durable_matrix_gen — DO NOT EDIT BY HAND.\n"); + out.push_str( + "-- Regenerate: cargo run --manifest-path tests/e2e/generated/generator/Cargo.toml\n", + ); + out.push_str(&format!( + "-- Shape {id} signature={} depth={}\n", + rec.signature, rec.depth + )); + if let Some(reason) = rec.reason { + out.push_str(&format!( + "-- QUARANTINED ({reason}): a known product loop-nesting defect (#227/#230)\n\ + -- makes this shape fail today. The assertions below encode the CORRECT\n\ + -- expected behavior; this test is expected to start passing once the\n\ + -- product bug is fixed, at which point it should be promoted to live.\n" + )); + } + out.push('\n'); + + out.push_str("SET SESSION AUTHORIZATION df_e2e_user;\n\n"); + out.push_str( + "CREATE TABLE IF NOT EXISTS df_gen_trace (shape_id TEXT, node_path TEXT, iteration INT, \ +wall_clock TIMESTAMPTZ);\n", + ); + // A persistent test database may still hold a pre-isolation df_gen_trace + // (no shape_id column); add it idempotently so old and fresh runs agree. + out.push_str("ALTER TABLE df_gen_trace ADD COLUMN IF NOT EXISTS shape_id TEXT;\n"); + out.push_str(&format!( + "DELETE FROM df_gen_trace WHERE shape_id = '{id}';\n\n" + )); + + out.push_str("CREATE TEMP TABLE _gen_state (instance_id TEXT);\n"); + out.push_str("INSERT INTO _gen_state SELECT df.start(\n "); + out.push_str(&rec.dsl); + out.push_str(&format!(",\n '{id}'\n);\n\n")); + + out.push_str("DO $GEN$\n"); + out.push_str("DECLARE\n"); + out.push_str(" inst_id TEXT;\n"); + out.push_str(" status TEXT;\n"); + out.push_str(" all_passed BOOLEAN;\n"); + out.push_str(" viol TEXT;\n"); + out.push_str(" unexpected TEXT;\n"); + if emit_order { + out.push_str(" ord_viol TEXT;\n"); + } + out.push_str("BEGIN\n"); + out.push_str(" SELECT instance_id INTO inst_id FROM _gen_state;\n"); + out.push_str(&format!( + " SELECT df.wait_for_completion(inst_id, {wait_timeout}) INTO status;\n" + )); + out.push_str(" IF status != 'completed' THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}]: status = %', status;\n" + )); + out.push_str(" END IF;\n\n"); + + out.push_str(" -- Phase 1 structural-invariant oracle must pass.\n"); + out.push_str( + " SELECT COALESCE(bool_and(passed), false), \ +string_agg(invariant, ', ') FILTER (WHERE NOT passed)\n", + ); + out.push_str(" INTO all_passed, viol\n"); + out.push_str(" FROM df.assert_structural_invariants(inst_id);\n"); + out.push_str(" IF NOT all_passed THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}]: invariant violation(s): %', viol;\n" + )); + out.push_str(" END IF;\n\n"); + + out.push_str(" -- Per-path execution counts must match the generator ground truth.\n"); + for (path, count) in &rec.expected { + out.push_str(&format!( + " IF (SELECT COUNT(*) FROM df_gen_trace \ +WHERE shape_id = '{id}' AND node_path = '{path}') <> {count} THEN\n" + )); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}]: path {path} expected {count}, got %',\n" + )); + out.push_str(&format!( + " (SELECT COUNT(*) FROM df_gen_trace \ +WHERE shape_id = '{id}' AND node_path = '{path}');\n" + )); + out.push_str(" END IF;\n"); + } + out.push('\n'); + + out.push_str(" -- No unexpected node paths may appear in the trace.\n"); + if rec.expected.is_empty() { + // An empty expected-set would make `NOT IN ()` invalid SQL; any trace + // row at all is unexpected, so guard on the shape scope alone. + out.push_str(" SELECT string_agg(DISTINCT node_path, ', ') INTO unexpected\n"); + out.push_str(" FROM df_gen_trace\n"); + out.push_str(&format!(" WHERE shape_id = '{id}';\n")); + } else { + let known: Vec = rec.expected.keys().map(|p| format!("'{p}'")).collect(); + out.push_str(" SELECT string_agg(DISTINCT node_path, ', ') INTO unexpected\n"); + out.push_str(" FROM df_gen_trace\n"); + out.push_str(&format!( + " WHERE shape_id = '{id}' AND node_path NOT IN ({});\n", + known.join(", ") + )); + } + out.push_str(" IF unexpected IS NOT NULL THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}]: unexpected path(s): %', unexpected;\n" + )); + out.push_str(" END IF;\n"); + + if emit_order { + // Phase 5 (#232) causal-order oracle. Each VALUES row is one happens- + // before (≺) edge from the reference interpreter; the two LEFT JOINs + // resolve both endpoints to their trace rows and the WHERE keeps every + // edge the runtime VIOLATED. An edge is a violation when EITHER endpoint + // never executed at its (path, iteration) — the LEFT JOIN leaves its + // wall_clock NULL — OR the earlier marker fired at-or-after the later + // one. The LEFT (not INNER) JOIN is deliberate and fail-closed: an INNER + // JOIN silently DROPS an edge whose endpoint row is missing, so a dropped + // or duplicated iteration could pass unflagged; treating a missing + // endpoint as the violation means the oracle never trusts the marker's + // own MAX(iteration)+1 numbering (the very thing under test). Markers run + // in distinct duroxide activities milliseconds apart, so an honored edge + // always shows a strict clock_timestamp() gap. CONCURRENT siblings + // (join/race branches) carry no edge, so this can never flake on them. + out.push('\n'); + out.push_str(" -- Phase 1+2 counts pin WHAT ran; this pins the ORDER it ran in.\n"); + out.push_str(" SELECT string_agg(\n"); + out.push_str(" CASE WHEN tu.wall_clock IS NULL OR tv.wall_clock IS NULL\n"); + out.push_str(" THEN v.descr || ' (missing endpoint)' ELSE v.descr END,\n"); + out.push_str(" ', ' ORDER BY v.descr)\n"); + out.push_str(" INTO ord_viol\n"); + out.push_str(" FROM (VALUES\n"); + let n = rec.ordered_pairs.len(); + for (i, ((pu, iu), (pv, iv))) in rec.ordered_pairs.iter().enumerate() { + let sep = if i + 1 < n { "," } else { "" }; + out.push_str(&format!( + " ('{pu}', {iu}, '{pv}', {iv}, '{pu}#{iu} -> {pv}#{iv}'){sep}\n" + )); + } + out.push_str(" ) AS v(pu, iu, pv, iv, descr)\n"); + out.push_str(&format!( + " LEFT JOIN df_gen_trace tu ON tu.shape_id = '{id}' \ +AND tu.node_path = v.pu AND tu.iteration = v.iu\n" + )); + out.push_str(&format!( + " LEFT JOIN df_gen_trace tv ON tv.shape_id = '{id}' \ +AND tv.node_path = v.pv AND tv.iteration = v.iv\n" + )); + out.push_str( + " WHERE tu.wall_clock IS NULL OR tv.wall_clock IS NULL \ +OR tu.wall_clock >= tv.wall_clock;\n", + ); + out.push_str(" IF ord_viol IS NOT NULL THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}]: causal-order violation(s): %', ord_viol;\n" + )); + out.push_str(" END IF;\n"); + } + + out.push_str("END $GEN$;\n\n"); + out.push_str("DROP TABLE _gen_state;\n"); + out.push_str("SELECT 'TEST PASSED' AS result;\n"); + + out +} + +pub(crate) fn json_escape(s: &str) -> String { + let mut out = String::with_capacity(s.len() + 2); + for c in s.chars() { + match c { + '"' => out.push_str("\\\""), + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)), + c => out.push(c), + } + } + out +} + +/// Serializes the full matrix to the golden `manifest.json` (deterministic, +/// 2-space indented, sorted keys). +pub fn manifest_json(records: &[ShapeRecord], meta: &MatrixMeta) -> String { + let mut out = String::new(); + out.push_str("{\n"); + out.push_str(" \"version\": 1,\n"); + out.push_str(" \"generator\": \"pg_durable_matrix_gen\",\n"); + out.push_str(&format!(" \"max_depth\": {},\n", meta.max_depth)); + let combs: Vec = meta + .combinators + .iter() + .map(|c| format!("\"{}\"", c.name())) + .collect(); + out.push_str(&format!(" \"combinators\": [{}],\n", combs.join(", "))); + out.push_str(&format!(" \"loop_iters\": {},\n", meta.loop_iters)); + out.push_str(&format!(" \"include_seeds\": {},\n", meta.include_seeds)); + out.push_str(&format!(" \"shape_count\": {},\n", records.len())); + out.push_str(" \"shapes\": [\n"); + + for (i, rec) in records.iter().enumerate() { + out.push_str(" {\n"); + out.push_str(&format!(" \"id\": \"{}\",\n", rec.id)); + out.push_str(&format!( + " \"signature\": \"{}\",\n", + json_escape(&rec.signature) + )); + out.push_str(&format!(" \"depth\": {},\n", rec.depth)); + out.push_str(&format!(" \"class\": \"{}\",\n", rec.class)); + match rec.reason { + Some(reason) => { + out.push_str(&format!(" \"reason\": \"{}\",\n", json_escape(reason))) + } + None => out.push_str(" \"reason\": null,\n"), + } + out.push_str(&format!(" \"dsl\": \"{}\",\n", json_escape(&rec.dsl))); + + if rec.expected.is_empty() { + out.push_str(" \"expected\": {},\n"); + } else { + out.push_str(" \"expected\": {\n"); + let entries: Vec = rec + .expected + .iter() + .map(|(p, c)| format!(" \"{}\": {}", json_escape(p), c)) + .collect(); + out.push_str(&entries.join(",\n")); + out.push_str("\n },\n"); + } + + // Phase 5 (#232): the committed causal-order golden — the reference + // interpreter's happens-before edges as `["earlier_path", iter, + // "later_path", iter]`. Present for every shape (the interpreter derives + // order regardless of class); only the live `.sql` emission is gated. + if rec.ordered_pairs.is_empty() { + out.push_str(" \"order\": []\n"); + } else { + out.push_str(" \"order\": [\n"); + let entries: Vec = rec + .ordered_pairs + .iter() + .map(|((pu, iu), (pv, iv))| { + format!( + " [\"{}\", {}, \"{}\", {}]", + json_escape(pu), + iu, + json_escape(pv), + iv + ) + }) + .collect(); + out.push_str(&entries.join(",\n")); + out.push_str("\n ]\n"); + } + + if i + 1 < records.len() { + out.push_str(" },\n"); + } else { + out.push_str(" }\n"); + } + } + + out.push_str(" ]\n"); + out.push_str("}\n"); + out +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample() -> ShapeRecord { + let mut expected = BTreeMap::new(); + expected.insert("r.0".to_string(), 1u64); + expected.insert("r.1".to_string(), 1u64); + ShapeRecord { + id: "gen-0001".to_string(), + signature: "S(M,M)".to_string(), + depth: 1, + class: "live", + reason: None, + dsl: "df.seq(df.sql($mk$...$mk$), df.sql($mk$...$mk$))".to_string(), + expected, + ordered_pairs: vec![(("r.0".to_string(), 1), ("r.1".to_string(), 1))], + } + } + + fn quarantined_sample() -> ShapeRecord { + let mut expected = BTreeMap::new(); + expected.insert("r.1.b".to_string(), 2u64); + ShapeRecord { + id: "gen-0099".to_string(), + signature: "J(M,L(M))".to_string(), + depth: 2, + class: "quarantine", + reason: Some("loop-in-join"), + dsl: "df.join(df.sql($mk$...$mk$), df.loop(df.sql($mk$...$mk$)))".to_string(), + expected, + ordered_pairs: vec![(("r.1.b".to_string(), 1), ("r.1.b".to_string(), 2))], + } + } + + #[test] + fn sql_test_has_passed_sentinel_and_authorization() { + let t = sql_test(&sample(), 60); + assert!(t.contains("SET SESSION AUTHORIZATION df_e2e_user;")); + assert!(t.trim_end().ends_with("SELECT 'TEST PASSED' AS result;")); + assert!(t.contains("df.assert_structural_invariants(inst_id)")); + assert!(t.contains("df.wait_for_completion(inst_id, 60)")); + // The oracle aggregate must COALESCE so a zero-row result fails loudly + // instead of NULL → silent pass. + assert!(t.contains("COALESCE(bool_and(passed), false)")); + } + + #[test] + fn sql_test_asserts_each_path() { + let t = sql_test(&sample(), 60); + assert!(t.contains("AND node_path = 'r.0'")); + assert!(t.contains("AND node_path = 'r.1'")); + // Each expected count (here 1) is asserted with a `<> N THEN` guard. + assert!(t.contains("<> 1 THEN")); + assert!(t.contains("NOT IN ('r.0', 'r.1')")); + } + + #[test] + fn sql_test_empty_expected_avoids_invalid_not_in() { + let mut rec = sample(); + rec.expected = BTreeMap::new(); + let t = sql_test(&rec, 60); + // An empty expected-set must not emit invalid `NOT IN ()`. + assert!(!t.contains("NOT IN ()")); + // It falls back to a shape-scoped any-row guard. + assert!(t.contains("WHERE shape_id = 'gen-0001';")); + } + + #[test] + fn sql_test_scopes_trace_by_shape_id() { + let t = sql_test(&sample(), 60); + // Table carries shape_id; the per-test reset and the unexpected-path + // guard must both be scoped to this shape so a foreign zombie instance + // cannot fail an innocent shape. + assert!(t.contains("df_gen_trace (shape_id TEXT, node_path TEXT, iteration INT")); + assert!(t.contains("DELETE FROM df_gen_trace WHERE shape_id = 'gen-0001';")); + assert!(t.contains("WHERE shape_id = 'gen-0001' AND node_path NOT IN")); + } + + #[test] + fn manifest_is_deterministic() { + let meta = MatrixMeta { + max_depth: 2, + combinators: &[Comb::Seq], + loop_iters: 2, + include_seeds: false, + }; + let a = manifest_json(&[sample()], &meta); + let b = manifest_json(&[sample()], &meta); + assert_eq!(a, b); + assert!(a.contains("\"shape_count\": 1")); + assert!(a.contains("\"id\": \"gen-0001\"")); + } + + #[test] + fn manifest_carries_class_and_reason() { + let meta = MatrixMeta { + max_depth: 2, + combinators: &[Comb::Seq, Comb::Join, Comb::Loop], + loop_iters: 2, + include_seeds: false, + }; + let m = manifest_json(&[sample(), quarantined_sample()], &meta); + assert!(m.contains("\"class\": \"live\"")); + assert!(m.contains("\"reason\": null")); + assert!(m.contains("\"class\": \"quarantine\"")); + assert!(m.contains("\"reason\": \"loop-in-join\"")); + } + + #[test] + fn quarantined_test_carries_explanatory_header() { + let t = sql_test(&quarantined_sample(), 10); + assert!(t.contains("QUARANTINED (loop-in-join)")); + // Even quarantined, the assertions still encode correct behavior. + assert!(t.contains("df.assert_structural_invariants(inst_id)")); + assert!(t.trim_end().ends_with("SELECT 'TEST PASSED' AS result;")); + } + + #[test] + fn sql_test_emits_causal_order_block_for_live_shape() { + let t = sql_test(&sample(), 60); + // Live shape with edges: the order oracle declares its var, emits the + // edge as a VALUES row, joins both endpoints, and raises on violation. + assert!(t.contains(" ord_viol TEXT;\n")); + assert!(t.contains("('r.0', 1, 'r.1', 1, 'r.0#1 -> r.1#1')")); + // Hardened (fail-closed): LEFT JOINs make a missing endpoint a violation + // in its own right, so a dropped/duplicated iteration cannot slip past an + // INNER JOIN that would silently drop the unresolved edge. + assert!(t.contains("LEFT JOIN df_gen_trace tu ON")); + assert!(t.contains("LEFT JOIN df_gen_trace tv ON")); + assert!(t.contains( + "WHERE tu.wall_clock IS NULL OR tv.wall_clock IS NULL OR tu.wall_clock >= tv.wall_clock;" + )); + assert!(t.contains("(missing endpoint)")); + assert!(t.contains("causal-order violation(s)")); + // The order ORACLE BLOCK is the LAST assertion, after the + // unexpected-path guard (the `ord_viol TEXT` declare precedes both). + let order_at = t.find("causal-order violation").unwrap(); + let unexpected_at = t.find("unexpected path(s)").unwrap(); + assert!(order_at > unexpected_at); + } + + #[test] + fn sql_test_omits_causal_order_block_when_quarantined() { + // A quarantined shape fails its COUNT assertions first; emitting an + // order oracle on a known-buggy trace would be noise. + let t = sql_test(&quarantined_sample(), 10); + assert!(!t.contains("ord_viol")); + assert!(!t.contains("causal-order violation")); + } + + #[test] + fn sql_test_omits_causal_order_block_when_no_edges() { + // A lone marker (or a purely-concurrent join) has no happens-before + // edge, so there is nothing to assert. + let mut rec = sample(); + rec.ordered_pairs = Vec::new(); + let t = sql_test(&rec, 60); + assert!(!t.contains("ord_viol")); + } + + #[test] + fn manifest_carries_order_field() { + let meta = MatrixMeta { + max_depth: 2, + combinators: &[Comb::Seq], + loop_iters: 2, + include_seeds: false, + }; + let m = manifest_json(&[sample()], &meta); + assert!(m.contains("\"order\": [\n")); + assert!(m.contains("[\"r.0\", 1, \"r.1\", 1]")); + // An edge-free shape records an empty array, never omits the key. + let mut bare = sample(); + bare.ordered_pairs = Vec::new(); + let m2 = manifest_json(&[bare], &meta); + assert!(m2.contains("\"order\": []\n")); + } +} diff --git a/tests/e2e/generated/generator/src/main.rs b/tests/e2e/generated/generator/src/main.rs new file mode 100644 index 00000000..0cb54f2d --- /dev/null +++ b/tests/e2e/generated/generator/src/main.rs @@ -0,0 +1,408 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! `pg_durable_matrix_gen` — deterministic generator for the Phase 2 +//! combinator-nesting E2E test matrix (#232). +//! +//! It enumerates every combinator-nesting shape up to a bounded depth, renders +//! each to a pg_durable DSL program with marker leaves, and emits: +//! * `/sql/gen-NNNN.sql` — self-contained E2E tests (gitignored). +//! * `/manifest.json` — the committed golden regression baseline. +//! +//! `--check` regenerates the manifest in memory and diffs it against the +//! committed copy, guarding that generation stays deterministic. +//! +//! Usage: +//! pg_durable_matrix_gen [--max-depth N] [--combinators a,b,c] [--full] +//! [--loop-iters K] [--max-shapes N] [--no-seeds] +//! [--out DIR] [--check] + +mod emit; +mod meta; +mod render; +mod shape; + +// Phase 3 (#232): recursive proptest Strategy + property suite. Test-only +// (depends on the `proptest` dev-dependency), so it never enters the generation +// binary and has no bearing on `--check` determinism or the committed goldens. +#[cfg(test)] +mod prop; + +// Phase 5 (#232): synchronous tree-walking REFERENCE INTERPRETER over `Shape`, +// producing the expected causal trace (a pomset of (node_path, iteration) events +// + happens-before order). Step 2 promotes it into the generation binary: each +// live shape's happens-before edges become a causal-order assertion block in its +// `.sql` test and an `order` golden in `manifest.json`. Its count-projection +// helpers remain `#[cfg(test)]` (the model-level differential). +mod refinterp; + +use emit::{manifest_json, sql_test, MatrixMeta, ShapeRecord}; +use meta::{meta_manifest_json, meta_sql_test, registry, Relation}; +use shape::{build_matrix, Comb}; +use std::path::{Path, PathBuf}; +use std::process::ExitCode; + +const DEFAULT_COMBS: [Comb; 5] = [Comb::Seq, Comb::If, Comb::Loop, Comb::Join, Comb::Race]; +const FULL_COMBS: [Comb; 6] = [ + Comb::Seq, + Comb::If, + Comb::Loop, + Comb::Join, + Comb::Join3, + Comb::Race, +]; + +const DEFAULT_MAX_DEPTH: u32 = 2; +const DEFAULT_LOOP_ITERS: u64 = 2; +const DEFAULT_WAIT_TIMEOUT_SECS: u32 = 60; +const DEFAULT_QUARANTINE_TIMEOUT_SECS: u32 = 10; + +struct Config { + max_depth: u32, + combinators: Vec, + loop_iters: u64, + max_shapes: Option, + include_seeds: bool, + out: PathBuf, + check: bool, + wait_timeout: u32, + quarantine_timeout: u32, +} + +fn default_out() -> PathBuf { + // CARGO_MANIFEST_DIR = .../tests/e2e/generated/generator; parent = .../generated. + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .map(Path::to_path_buf) + .unwrap_or_else(|| PathBuf::from(".")) +} + +fn print_help() { + println!( + "pg_durable_matrix_gen — Phase 2 combinator-nesting matrix generator (#232)\n\n\ +Options:\n\ + --max-depth N Maximum combinator-nesting depth (default {DEFAULT_MAX_DEPTH})\n\ + --combinators LIST Comma list of: seq,if,loop,join,join3,race\n\ + (default: seq,if,loop,join,race)\n\ + --full Shortcut for the full set including join3\n\ + --loop-iters K Iterations each generated loop runs (default {DEFAULT_LOOP_ITERS})\n\ + --max-shapes N Cap (>=1) on the sorted shape list, applied AFTER enumeration;\n\ + bounds output size, not enumeration cost at high depth\n\ + --no-seeds Exclude the hand-written else/break seed shapes\n\ + --out DIR Output dir (default: tests/e2e/generated)\n\ + --wait-timeout N Seconds each test waits for completion (default {DEFAULT_WAIT_TIMEOUT_SECS})\n\ + --quarantine-timeout N Seconds each QUARANTINED test waits (default {DEFAULT_QUARANTINE_TIMEOUT_SECS})\n\ + --check Regenerate manifest in memory and diff vs committed copy\n\ + --help Show this help" + ); +} + +fn parse_args() -> Result, String> { + let mut cfg = Config { + max_depth: DEFAULT_MAX_DEPTH, + combinators: DEFAULT_COMBS.to_vec(), + loop_iters: DEFAULT_LOOP_ITERS, + max_shapes: None, + include_seeds: true, + out: default_out(), + check: false, + wait_timeout: DEFAULT_WAIT_TIMEOUT_SECS, + quarantine_timeout: DEFAULT_QUARANTINE_TIMEOUT_SECS, + }; + + let args: Vec = std::env::args().skip(1).collect(); + let mut i = 0; + let next = |i: &mut usize, flag: &str| -> Result { + *i += 1; + args.get(*i) + .cloned() + .ok_or_else(|| format!("{flag} requires a value")) + }; + + while i < args.len() { + let arg = args[i].as_str(); + match arg { + "--help" | "-h" => { + print_help(); + return Ok(None); + } + "--max-depth" => { + cfg.max_depth = next(&mut i, arg)? + .parse() + .map_err(|_| "--max-depth must be a non-negative integer".to_string())?; + } + "--loop-iters" => { + let k: u64 = next(&mut i, arg)? + .parse() + .map_err(|_| "--loop-iters must be an integer".to_string())?; + if k < 1 { + return Err("--loop-iters must be >= 1".to_string()); + } + cfg.loop_iters = k; + } + "--max-shapes" => { + let n: usize = next(&mut i, arg)? + .parse() + .map_err(|_| "--max-shapes must be an integer".to_string())?; + if n == 0 { + return Err("--max-shapes must be >= 1".to_string()); + } + cfg.max_shapes = Some(n); + } + "--wait-timeout" => { + let t: u32 = next(&mut i, arg)? + .parse() + .map_err(|_| "--wait-timeout must be a non-negative integer".to_string())?; + if t < 1 { + return Err("--wait-timeout must be >= 1".to_string()); + } + cfg.wait_timeout = t; + } + "--quarantine-timeout" => { + let t: u32 = next(&mut i, arg)?.parse().map_err(|_| { + "--quarantine-timeout must be a non-negative integer".to_string() + })?; + if t < 1 { + return Err("--quarantine-timeout must be >= 1".to_string()); + } + cfg.quarantine_timeout = t; + } + "--combinators" => { + let list = next(&mut i, arg)?; + let mut combs = Vec::new(); + for tok in list.split(',') { + if tok.trim().is_empty() { + continue; + } + combs.push(Comb::parse(tok)?); + } + if combs.is_empty() { + return Err("--combinators list is empty".to_string()); + } + cfg.combinators = combs; + } + "--full" => { + cfg.combinators = FULL_COMBS.to_vec(); + } + "--no-seeds" => { + cfg.include_seeds = false; + } + "--out" => { + cfg.out = PathBuf::from(next(&mut i, arg)?); + } + "--check" => { + cfg.check = true; + } + other => { + return Err(format!("unknown argument '{other}' (try --help)")); + } + } + i += 1; + } + + Ok(Some(cfg)) +} + +/// Builds the ordered shape records (id + signature + dsl + expected counts). +fn build_records(cfg: &Config) -> Vec { + let shapes = build_matrix( + &cfg.combinators, + cfg.max_depth, + cfg.include_seeds, + cfg.max_shapes, + ); + shapes + .iter() + .enumerate() + .map(|(idx, shape)| { + let id = format!("gen-{:04}", idx + 1); + let rendered = render::render(shape, cfg.loop_iters, &id); + let reason = shape.is_problematic(); + // Phase 5 (#232): derive the happens-before edges from the reference + // interpreter at the SAME K the renderer used, then flatten each + // edge to ((earlier_path, earlier_iter), (later_path, later_iter)). + let ordered_pairs = refinterp::interpret(shape, cfg.loop_iters) + .ordered_pairs() + .into_iter() + .map(|(a, b)| ((a.node_path, a.iteration), (b.node_path, b.iteration))) + .collect(); + ShapeRecord { + id, + signature: shape.signature(), + depth: shape.depth(), + class: if reason.is_some() { + "quarantine" + } else { + "live" + }, + reason, + dsl: rendered.dsl, + expected: rendered.expected, + ordered_pairs, + } + }) + .collect() +} + +/// Reports the first line where two manifests diverge (for `--check`). +fn first_diff_line(committed: &str, fresh: &str) -> Option { + let a: Vec<&str> = committed.lines().collect(); + let b: Vec<&str> = fresh.lines().collect(); + let max = a.len().max(b.len()); + for line in 0..max { + if a.get(line) != b.get(line) { + return Some(line + 1); + } + } + None +} + +/// Compares one freshly-generated artifact against its committed copy. +fn check_artifact(out: &Path, filename: &str, fresh: &str) -> bool { + let path = out.join(filename); + let committed = match std::fs::read_to_string(&path) { + Ok(s) => s, + Err(e) => { + eprintln!("--check: cannot read {}: {e}", path.display()); + eprintln!("Run the generator without --check to create it."); + return false; + } + }; + // Normalize line endings so a CRLF checkout never trips the comparison. + let committed_norm = committed.replace("\r\n", "\n"); + if committed_norm == *fresh { + println!("--check: {filename} is up to date ({} bytes).", fresh.len()); + true + } else { + eprintln!("--check: {filename} is STALE — regenerate it."); + if let Some(line) = first_diff_line(&committed_norm, fresh) { + eprintln!("First difference at line {line}."); + } + eprintln!("Regenerate: cargo run --manifest-path tests/e2e/generated/generator/Cargo.toml"); + false + } +} + +fn run_check(cfg: &Config, manifest: &str, meta_manifest: &str) -> ExitCode { + let ok_matrix = check_artifact(&cfg.out, "manifest.json", manifest); + let ok_meta = check_artifact(&cfg.out, "meta-manifest.json", meta_manifest); + if ok_matrix && ok_meta { + ExitCode::SUCCESS + } else { + ExitCode::FAILURE + } +} + +fn clean_generated_sql(sql_dir: &Path) { + if let Ok(entries) = std::fs::read_dir(sql_dir) { + for entry in entries.flatten() { + let name = entry.file_name(); + let name = name.to_string_lossy(); + if (name.starts_with("gen-") || name.starts_with("meta-")) && name.ends_with(".sql") { + let _ = std::fs::remove_file(entry.path()); + } + } + } +} + +fn run_generate( + cfg: &Config, + records: &[ShapeRecord], + manifest: &str, + relations: &[Relation], + meta_manifest: &str, +) -> ExitCode { + let sql_dir = cfg.out.join("sql"); + let quarantine_dir = cfg.out.join("quarantine"); + for dir in [&sql_dir, &quarantine_dir] { + if let Err(e) = std::fs::create_dir_all(dir) { + eprintln!("cannot create {}: {e}", dir.display()); + return ExitCode::FAILURE; + } + clean_generated_sql(dir); + } + + let mut live = 0usize; + let mut quarantined = 0usize; + for rec in records { + let (dir, timeout) = if rec.reason.is_some() { + quarantined += 1; + (&quarantine_dir, cfg.quarantine_timeout) + } else { + live += 1; + (&sql_dir, cfg.wait_timeout) + }; + let path = dir.join(format!("{}.sql", rec.id)); + if let Err(e) = std::fs::write(&path, sql_test(rec, timeout)) { + eprintln!("cannot write {}: {e}", path.display()); + return ExitCode::FAILURE; + } + } + + // Phase 4 metamorphic relations are live → sql/ (blocking, collected by the + // harness's --include-generated glob alongside the matrix tests). + for rel in relations { + let path = sql_dir.join(format!("{}.sql", rel.id)); + if let Err(e) = std::fs::write(&path, meta_sql_test(rel, cfg.wait_timeout)) { + eprintln!("cannot write {}: {e}", path.display()); + return ExitCode::FAILURE; + } + } + + let manifest_path = cfg.out.join("manifest.json"); + if let Err(e) = std::fs::write(&manifest_path, manifest) { + eprintln!("cannot write {}: {e}", manifest_path.display()); + return ExitCode::FAILURE; + } + let meta_manifest_path = cfg.out.join("meta-manifest.json"); + if let Err(e) = std::fs::write(&meta_manifest_path, meta_manifest) { + eprintln!("cannot write {}: {e}", meta_manifest_path.display()); + return ExitCode::FAILURE; + } + + println!( + "Generated {} shape(s): {} live → {}, {} quarantined → {} (manifest: {})", + records.len(), + live, + sql_dir.display(), + quarantined, + quarantine_dir.display(), + manifest_path.display() + ); + println!( + "Generated {} metamorphic relation(s) → {} (manifest: {})", + relations.len(), + sql_dir.display(), + meta_manifest_path.display() + ); + ExitCode::SUCCESS +} + +fn main() -> ExitCode { + let cfg = match parse_args() { + Ok(Some(cfg)) => cfg, + Ok(None) => return ExitCode::SUCCESS, // --help + Err(e) => { + eprintln!("error: {e}"); + return ExitCode::FAILURE; + } + }; + + let records = build_records(&cfg); + let matrix_meta = MatrixMeta { + max_depth: cfg.max_depth, + combinators: &cfg.combinators, + loop_iters: cfg.loop_iters, + include_seeds: cfg.include_seeds, + }; + let manifest = manifest_json(&records, &matrix_meta); + + let relations = registry(); + let meta_manifest = meta_manifest_json(&relations); + + if cfg.check { + run_check(&cfg, &manifest, &meta_manifest) + } else { + run_generate(&cfg, &records, &manifest, &relations, &meta_manifest) + } +} diff --git a/tests/e2e/generated/generator/src/meta.rs b/tests/e2e/generated/generator/src/meta.rs new file mode 100644 index 00000000..4e8eb6cc --- /dev/null +++ b/tests/e2e/generated/generator/src/meta.rs @@ -0,0 +1,741 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! Phase 4 (#232) — metamorphic relations. +//! +//! A metamorphic relation is an equivalence-class test: two DSL programs the +//! runtime should treat as observably equivalent, run side by side, asserting +//! that the same things happened in both. +//! +//! The Phase 2 matrix tags every marker by its STRUCTURAL `node_path` (`r`, +//! `r.0`, `r.b`…), which necessarily differs between two structurally-different +//! (but equivalent) programs. So metamorphic relations instead tag markers by a +//! STABLE LEAF LABEL (`a`, `b`, `c`): the same logical leaf carries the same +//! label in BOTH programs of a pair. The observable is then the multiset +//! `{label -> completed-count}`, and the equivalence predicate is multiset +//! equality. Untaken `if` branches and abandoned `race` losers simply produce +//! no trace rows, so they contribute 0 to the multiset automatically. +//! +//! Each relation emits ONE self-contained `.sql` test that starts both programs +//! (tagged `meta-NNNN-a` / `meta-NNNN-b` in the shared `df_gen_trace`), waits for +//! both, asserts the Phase 1 oracle passes for both, then asserts: +//! 1. HEADLINE — `observable(A) == observable(B)` (EXCEPT-based multiset diff). +//! 2. BACKSTOP — each side equals the generator-computed ground truth. This +//! catches a symmetric bug where A and B are wrong the *same* way (which the +//! pure-state Phase 1 invariants and the headline check would both miss). + +use crate::emit::json_escape; +use crate::render::marker_sql; +use crate::shape::Cond; +use std::collections::BTreeMap; + +/// Sleep (seconds) for the deterministic loser branch of a metamorphic `race`. +/// +/// The winner is a near-instant marker, so duroxide resolves the race and +/// abandons this sleep the moment the marker completes — the timer never fires +/// and adds no latency. A large value is therefore free insurance against +/// scheduler jitter ever letting the loser win. +const RACE_LOSER_SLEEP_SECS: u64 = 30; + +/// A label-tagged program: the metamorphic counterpart to a `Shape`. +/// +/// Unlike `Shape`, leaves carry an explicit, stable label so the same logical +/// leaf can appear in both programs of an equivalence pair. +#[derive(Clone, Debug)] +pub enum Meta { + /// A marker leaf that records one completion under `label`. + Leaf(String), + /// `a` then `b`. + Seq(Box, Box), + /// `df.if(cond, then, else)` — only the taken branch runs. + If(Cond, Box, Box), + /// `df.join(a, b)` — both branches run. + Join(Box, Box), + /// `df.race(winner, df.sleep(N))` — only `winner` runs to completion. + Race(Box), + /// A do-while loop that runs `body` exactly `k` times, terminating once the + /// `anchor` leaf (which `body` must contain) has executed `k` times. + DoWhile { + body: Box, + anchor: String, + k: u64, + }, + /// A loop that runs `body` then `df.break`s once the `anchor` leaf has + /// executed `n` times, yielding exactly `n` body runs. + LoopBreak { + body: Box, + anchor: String, + n: u64, + }, +} + +/// Computes the ground-truth observable multiset for a program. +/// +/// `mult` is the number of times the enclosing context runs this subtree (1 at +/// the root, scaled by loop counts). Leaves accumulate; untaken/abandoned +/// branches are simply never visited, so they never appear in the map. +fn eval(prog: &Meta, mult: u64, out: &mut BTreeMap) { + match prog { + Meta::Leaf(label) => *out.entry(label.clone()).or_insert(0) += mult, + Meta::Seq(a, b) => { + eval(a, mult, out); + eval(b, mult, out); + } + Meta::If(cond, then, els) => match cond { + Cond::True => eval(then, mult, out), + Cond::False => eval(els, mult, out), + }, + Meta::Join(a, b) => { + eval(a, mult, out); + eval(b, mult, out); + } + Meta::Race(winner) => eval(winner, mult, out), + Meta::DoWhile { body, k, .. } => eval(body, mult.saturating_mul(*k), out), + Meta::LoopBreak { body, n, .. } => eval(body, mult.saturating_mul(*n), out), + } +} + +/// The observable multiset `{label -> completed-count}` for a program. +pub fn observable(prog: &Meta) -> BTreeMap { + let mut m = BTreeMap::new(); + eval(prog, 1, &mut m); + m +} + +/// Renders a program to a pg_durable DSL string under trace tag `tag`. +/// +/// Reuses the exact Phase 2 constructs (`marker_sql`, `df.seq`, `df.if`, +/// `df.join`, `df.race`, `df.loop`, `df.break`) so the two infrastructures stay +/// semantically identical. +pub(crate) fn render_prog(prog: &Meta, tag: &str) -> String { + match prog { + Meta::Leaf(label) => marker_sql(label, tag), + Meta::Seq(a, b) => format!("df.seq({}, {})", render_prog(a, tag), render_prog(b, tag)), + Meta::If(cond, then, els) => format!( + "df.if($c${}$c$, {}, {})", + cond.sql(), + render_prog(then, tag), + render_prog(els, tag) + ), + Meta::Join(a, b) => format!("df.join({}, {})", render_prog(a, tag), render_prog(b, tag)), + Meta::Race(winner) => format!( + "df.race({}, df.sleep({RACE_LOSER_SLEEP_SECS}))", + render_prog(winner, tag) + ), + Meta::DoWhile { body, anchor, k } => { + // do-while: body runs, THEN this predicate is checked. Counting an + // EXISTING body leaf (the anchor) keeps the observable pure — no + // synthetic counter leaf pollutes the multiset. + // + // READ-YOUR-WRITES DEPENDENCY: the predicate COUNTs the anchor's + // prior marker rows in `df_gen_trace`, so iteration i's marker + // INSERT must be visible to the condition query that gates iteration + // i+1. This holds because each marker and each loop condition runs + // as its own duroxide activity — an autocommitted statement under + // READ COMMITTED — so a committed marker is always visible to the + // next condition read. The live meta E2E is the empirical check; if + // a future runtime ever batched body+condition into one uncommitted + // unit, the count could read stale and the loop could over-run. The + // same dependency applies to `LoopBreak` below. + let cond = format!( + "SELECT COUNT(*) < {k} FROM df_gen_trace \ +WHERE shape_id = '{tag}' AND node_path = '{anchor}'" + ); + format!("df.loop({}, $c${cond}$c$)", render_prog(body, tag)) + } + Meta::LoopBreak { body, anchor, n } => { + // See the read-your-writes note on `DoWhile`: the break predicate + // likewise reads committed anchor rows from `df_gen_trace`. + let break_cond = format!( + "SELECT (SELECT COUNT(*) FROM df_gen_trace \ +WHERE shape_id = '{tag}' AND node_path = '{anchor}') >= {n}" + ); + format!( + "df.loop(df.seq({}, df.if($c${break_cond}$c$, df.break(), $c$SELECT 1$c$)), \ +$c$SELECT true$c$)", + render_prog(body, tag) + ) + } + } +} + +/// One metamorphic relation: a pair of programs plus the multiset they must both +/// produce. +pub struct Relation { + /// Stable id, e.g. `meta-0001`. + pub id: String, + /// Short relation name, e.g. `seq-assoc`. + pub name: &'static str, + /// Why the two programs are equivalent (documentation / manifest). + pub rationale: &'static str, + pub prog_a: Meta, + pub prog_b: Meta, + /// Generator-computed observable both sides must equal. + pub expected: BTreeMap, +} + +fn leaf(label: &str) -> Box { + Box::new(Meta::Leaf(label.to_string())) +} + +/// The registry of metamorphic relations. +/// +/// Every relation holds under BOTH correct semantics and the *current* runtime, +/// so all are live. (None nests a loop in a non-root / join / race position, the +/// #227/#230/#233 defect zone — a loop at the root works correctly.) For each +/// relation this asserts `observable(A) == observable(B)`, so a mis-specified +/// pair fails loudly at generation time rather than producing a vacuous test. +pub fn registry() -> Vec { + let specs: Vec<(&'static str, &'static str, Meta, Meta)> = vec![ + ( + "seq-assoc", + "Sequence is associative: re-grouping nested seq does not change the \ +order or the set of side effects.", + Meta::Seq(leaf("a"), Box::new(Meta::Seq(leaf("b"), leaf("c")))), + Meta::Seq(Box::new(Meta::Seq(leaf("a"), leaf("b"))), leaf("c")), + ), + ( + "if-true", + "A constant-true if reduces to its then-branch; the else-branch never runs.", + Meta::If(Cond::True, leaf("a"), leaf("b")), + Meta::Leaf("a".to_string()), + ), + ( + "if-false", + "A constant-false if reduces to its else-branch; the then-branch never runs.", + Meta::If(Cond::False, leaf("a"), leaf("b")), + Meta::Leaf("b".to_string()), + ), + ( + "join-comm", + "Parallel join is commutative: swapping branches yields the same \ +multiset of side effects.", + Meta::Join(leaf("a"), leaf("b")), + Meta::Join(leaf("b"), leaf("a")), + ), + ( + "race-winner", + "A race whose only other branch is a long sleep reduces to its \ +deterministic winner.", + Meta::Race(leaf("a")), + Meta::Leaf("a".to_string()), + ), + ( + "do-while-once", + "A do-while loop whose condition is already false after the first body \ +run reduces to its body.", + Meta::DoWhile { + body: leaf("a"), + anchor: "a".to_string(), + k: 1, + }, + Meta::Leaf("a".to_string()), + ), + ( + "loop-break-once", + "A loop that breaks immediately after its first body run reduces to its body.", + Meta::LoopBreak { + body: leaf("a"), + anchor: "a".to_string(), + n: 1, + }, + Meta::Leaf("a".to_string()), + ), + ]; + + specs + .into_iter() + .enumerate() + .map(|(idx, (name, rationale, prog_a, prog_b))| { + let exp_a = observable(&prog_a); + let exp_b = observable(&prog_b); + assert_eq!( + exp_a, exp_b, + "relation '{name}' is mis-specified: observable(A)={exp_a:?} != observable(B)={exp_b:?}" + ); + // A relation whose observable is empty would be vacuous: both sides + // trivially agree (multiset {} == {}) even if the runtime executed + // nothing. Enforce non-vacuity in the generation path, not just in + // the unit tests, so such a relation can never be rendered. + assert!( + !exp_a.is_empty(), + "relation '{name}' has an empty observable — a vacuous metamorphic test" + ); + Relation { + id: format!("meta-{:04}", idx + 1), + name, + rationale, + prog_a, + prog_b, + expected: exp_a, + } + }) + .collect() +} + +/// Renders the full text of a self-contained `.sql` E2E test for one relation. +/// +/// This is the metamorphic sibling of `emit::sql_test` (the Phase 2 matrix +/// renderer). The two intentionally diverge — this one tags markers by STABLE +/// LEAF LABEL and asserts a multiset equivalence between two programs, whereas +/// `emit::sql_test` tags by STRUCTURAL `node_path` and asserts per-path counts +/// for a single program. They share the same `df_gen_trace` schema, Phase 1 +/// oracle call, and `SELECT 'TEST PASSED'` epilogue; a maintainer changing any +/// of those shared conventions in one renderer should mirror it in the other +/// (`backstop_guards_are_emitted` / `sql_test_has_expected_anatomy` pin the +/// emitted shapes so silent drift fails a unit test). +pub fn meta_sql_test(rel: &Relation, wait_timeout: u32) -> String { + let id = &rel.id; + let tag_a = format!("{id}-a"); + let tag_b = format!("{id}-b"); + let dsl_a = render_prog(&rel.prog_a, &tag_a); + let dsl_b = render_prog(&rel.prog_b, &tag_b); + let mut out = String::new(); + + out.push_str("-- Copyright (c) Microsoft Corporation.\n"); + out.push_str("-- Licensed under the PostgreSQL License.\n"); + out.push_str("--\n"); + out.push_str( + "-- AUTO-GENERATED by pg_durable_matrix_gen (metamorphic) — DO NOT EDIT BY HAND.\n", + ); + out.push_str( + "-- Regenerate: cargo run --manifest-path tests/e2e/generated/generator/Cargo.toml\n", + ); + out.push_str(&format!("-- Relation {id} name={}\n", rel.name)); + out.push_str(&format!("-- {}\n", rel.rationale)); + out.push_str( + "-- Metamorphic: programs A and B must produce the same observable (the\n\ + -- multiset of leaf-label execution counts). The test asserts\n\ + -- observable(A) == observable(B) AND that each side matches the\n\ + -- generator ground truth.\n", + ); + out.push('\n'); + + out.push_str("SET SESSION AUTHORIZATION df_e2e_user;\n\n"); + out.push_str( + "CREATE TABLE IF NOT EXISTS df_gen_trace (shape_id TEXT, node_path TEXT, iteration INT, \ +wall_clock TIMESTAMPTZ);\n", + ); + out.push_str("ALTER TABLE df_gen_trace ADD COLUMN IF NOT EXISTS shape_id TEXT;\n"); + out.push_str(&format!( + "DELETE FROM df_gen_trace WHERE shape_id IN ('{tag_a}', '{tag_b}');\n\n" + )); + + out.push_str("CREATE TEMP TABLE _meta_state (which TEXT, instance_id TEXT);\n"); + out.push_str("INSERT INTO _meta_state SELECT 'a', df.start(\n "); + out.push_str(&dsl_a); + out.push_str(&format!(",\n '{tag_a}'\n);\n")); + out.push_str("INSERT INTO _meta_state SELECT 'b', df.start(\n "); + out.push_str(&dsl_b); + out.push_str(&format!(",\n '{tag_b}'\n);\n\n")); + + out.push_str("DO $GEN$\n"); + out.push_str("DECLARE\n"); + out.push_str(" inst_a TEXT;\n"); + out.push_str(" inst_b TEXT;\n"); + out.push_str(" status TEXT;\n"); + out.push_str(" all_passed BOOLEAN;\n"); + out.push_str(" viol TEXT;\n"); + out.push_str(" unexpected TEXT;\n"); + out.push_str(" diff_rows INT;\n"); + out.push_str("BEGIN\n"); + out.push_str(" SELECT instance_id INTO inst_a FROM _meta_state WHERE which = 'a';\n"); + out.push_str(" SELECT instance_id INTO inst_b FROM _meta_state WHERE which = 'b';\n\n"); + + // Both instances must complete. + out.push_str(&format!( + " SELECT df.wait_for_completion(inst_a, {wait_timeout}) INTO status;\n" + )); + out.push_str(" IF status != 'completed' THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}/a]: status = %', status;\n" + )); + out.push_str(" END IF;\n"); + out.push_str(&format!( + " SELECT df.wait_for_completion(inst_b, {wait_timeout}) INTO status;\n" + )); + out.push_str(" IF status != 'completed' THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}/b]: status = %', status;\n" + )); + out.push_str(" END IF;\n\n"); + + // Phase 1 oracle must pass for both programs. + out.push_str(" -- Phase 1 structural-invariant oracle must pass for both programs.\n"); + for (which, inst) in [("a", "inst_a"), ("b", "inst_b")] { + out.push_str( + " SELECT COALESCE(bool_and(passed), false), \ +string_agg(invariant, ', ') FILTER (WHERE NOT passed)\n", + ); + out.push_str(&format!( + " INTO all_passed, viol\n FROM df.assert_structural_invariants({inst});\n" + )); + out.push_str(" IF NOT all_passed THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}/{which}]: invariant violation(s): %', viol;\n" + )); + out.push_str(" END IF;\n"); + } + out.push('\n'); + + // HEADLINE: observable(A) multiset == observable(B) multiset. + out.push_str( + " -- Metamorphic relation: observable(A) multiset == observable(B) multiset.\n", + ); + out.push_str(" SELECT COUNT(*) INTO diff_rows FROM (\n"); + out.push_str(&format!( + " (SELECT node_path, COUNT(*) AS n FROM df_gen_trace \ +WHERE shape_id = '{tag_a}' GROUP BY node_path\n" + )); + out.push_str(" EXCEPT\n"); + out.push_str(&format!( + " SELECT node_path, COUNT(*) AS n FROM df_gen_trace \ +WHERE shape_id = '{tag_b}' GROUP BY node_path)\n" + )); + out.push_str(" UNION ALL\n"); + out.push_str(&format!( + " (SELECT node_path, COUNT(*) AS n FROM df_gen_trace \ +WHERE shape_id = '{tag_b}' GROUP BY node_path\n" + )); + out.push_str(" EXCEPT\n"); + out.push_str(&format!( + " SELECT node_path, COUNT(*) AS n FROM df_gen_trace \ +WHERE shape_id = '{tag_a}' GROUP BY node_path)\n" + )); + out.push_str(" ) d;\n"); + out.push_str(" IF diff_rows <> 0 THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}]: observable(A) != observable(B) \ +(% differing label-count row(s))', diff_rows;\n" + )); + out.push_str(" END IF;\n\n"); + + // BACKSTOP: each side matches the generator-computed expected multiset. + out.push_str(" -- Backstop: each side matches the generator ground truth (catches a\n"); + out.push_str(" -- symmetric bug where A and B are wrong in the same way).\n"); + for (which, tag) in [("a", &tag_a), ("b", &tag_b)] { + for (label, count) in &rel.expected { + out.push_str(&format!( + " IF (SELECT COUNT(*) FROM df_gen_trace \ +WHERE shape_id = '{tag}' AND node_path = '{label}') <> {count} THEN\n" + )); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}/{which}]: label {label} expected {count}, got %',\n" + )); + out.push_str(&format!( + " (SELECT COUNT(*) FROM df_gen_trace \ +WHERE shape_id = '{tag}' AND node_path = '{label}');\n" + )); + out.push_str(" END IF;\n"); + } + if rel.expected.is_empty() { + out.push_str(" SELECT string_agg(DISTINCT node_path, ', ') INTO unexpected\n"); + out.push_str(&format!( + " FROM df_gen_trace WHERE shape_id = '{tag}';\n" + )); + } else { + let known: Vec = rel.expected.keys().map(|l| format!("'{l}'")).collect(); + out.push_str(" SELECT string_agg(DISTINCT node_path, ', ') INTO unexpected\n"); + out.push_str(&format!( + " FROM df_gen_trace WHERE shape_id = '{tag}' AND node_path NOT IN ({});\n", + known.join(", ") + )); + } + out.push_str(" IF unexpected IS NOT NULL THEN\n"); + out.push_str(&format!( + " RAISE EXCEPTION 'TEST FAILED [{id}/{which}]: unexpected label(s): %', unexpected;\n" + )); + out.push_str(" END IF;\n"); + } + + out.push_str("END $GEN$;\n\n"); + out.push_str("DROP TABLE _meta_state;\n"); + out.push_str("SELECT 'TEST PASSED' AS result;\n"); + + out +} + +/// Serializes the relation registry to the golden `meta-manifest.json` +/// (deterministic, 2-space indented). +pub fn meta_manifest_json(rels: &[Relation]) -> String { + let mut out = String::new(); + out.push_str("{\n"); + out.push_str(" \"version\": 1,\n"); + out.push_str(" \"generator\": \"pg_durable_matrix_gen\",\n"); + out.push_str(" \"kind\": \"metamorphic\",\n"); + out.push_str(&format!(" \"relation_count\": {},\n", rels.len())); + out.push_str(" \"relations\": [\n"); + + for (i, rel) in rels.iter().enumerate() { + let tag_a = format!("{}-a", rel.id); + let tag_b = format!("{}-b", rel.id); + out.push_str(" {\n"); + out.push_str(&format!(" \"id\": \"{}\",\n", rel.id)); + out.push_str(&format!(" \"name\": \"{}\",\n", json_escape(rel.name))); + out.push_str(&format!( + " \"rationale\": \"{}\",\n", + json_escape(rel.rationale) + )); + out.push_str(&format!( + " \"dsl_a\": \"{}\",\n", + json_escape(&render_prog(&rel.prog_a, &tag_a)) + )); + out.push_str(&format!( + " \"dsl_b\": \"{}\",\n", + json_escape(&render_prog(&rel.prog_b, &tag_b)) + )); + + if rel.expected.is_empty() { + out.push_str(" \"expected\": {}\n"); + } else { + out.push_str(" \"expected\": {\n"); + let entries: Vec = rel + .expected + .iter() + .map(|(l, c)| format!(" \"{}\": {}", json_escape(l), c)) + .collect(); + out.push_str(&entries.join(",\n")); + out.push_str("\n }\n"); + } + + if i + 1 < rels.len() { + out.push_str(" },\n"); + } else { + out.push_str(" }\n"); + } + } + + out.push_str(" ]\n"); + out.push_str("}\n"); + out +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Strips `$tag$…$tag$` dollar-quoted spans so paren-balance checks ignore + /// SQL text. Only the two tags the renderer emits (`$mk$`, `$c$`) are used. + fn strip_quoted(mut s: String, tag: &str) -> String { + while let Some(start) = s.find(tag) { + let Some(rel) = s[start + tag.len()..].find(tag) else { + break; + }; + let end = start + tag.len() + rel + tag.len(); + s.replace_range(start..end, ""); + } + s + } + + fn parens_balanced(dsl: &str) -> bool { + let stripped = strip_quoted(strip_quoted(dsl.to_string(), "$mk$"), "$c$"); + let mut depth: i32 = 0; + for c in stripped.chars() { + match c { + '(' => depth += 1, + ')' => { + depth -= 1; + if depth < 0 { + return false; + } + } + _ => {} + } + } + depth == 0 + } + + #[test] + fn registry_is_nonempty() { + assert!(!registry().is_empty()); + } + + #[test] + fn ids_are_sequential() { + for (i, rel) in registry().iter().enumerate() { + assert_eq!(rel.id, format!("meta-{:04}", i + 1)); + } + } + + #[test] + fn every_relation_is_equivalent_and_nonvacuous() { + for rel in registry() { + let a = observable(&rel.prog_a); + let b = observable(&rel.prog_b); + assert_eq!(a, b, "relation '{}' A != B", rel.name); + assert_eq!(a, rel.expected, "relation '{}' expected mismatch", rel.name); + assert!( + !rel.expected.is_empty(), + "relation '{}' has an empty observable", + rel.name + ); + } + } + + #[test] + fn interpreter_matches_hand_computed() { + // seq(a, seq(b, c)) -> {a:1, b:1, c:1} + let p = Meta::Seq(leaf("a"), Box::new(Meta::Seq(leaf("b"), leaf("c")))); + let o = observable(&p); + assert_eq!(o.get("a"), Some(&1)); + assert_eq!(o.get("b"), Some(&1)); + assert_eq!(o.get("c"), Some(&1)); + assert_eq!(o.len(), 3); + + // if(false, a, b) -> {b:1}; a never runs. + let p = Meta::If(Cond::False, leaf("a"), leaf("b")); + let o = observable(&p); + assert_eq!(o.get("b"), Some(&1)); + assert_eq!(o.get("a"), None); + assert_eq!(o.len(), 1); + + // race(a) -> {a:1}; the sleep loser contributes nothing. + let o = observable(&Meta::Race(leaf("a"))); + assert_eq!(o, BTreeMap::from([("a".to_string(), 1)])); + } + + #[test] + fn loop_multiplier_scales_body() { + // A do-while body run 3x multiplies its leaves. + let p = Meta::DoWhile { + body: Box::new(Meta::Seq(leaf("a"), leaf("b"))), + anchor: "a".to_string(), + k: 3, + }; + let o = observable(&p); + assert_eq!(o.get("a"), Some(&3)); + assert_eq!(o.get("b"), Some(&3)); + + // A loop-break body run 3x (anchor reaches n=3) likewise multiplies — + // covers the k>1 / n>1 case the single-iteration registry relations do + // not exercise. + let p = Meta::LoopBreak { + body: Box::new(Meta::Seq(leaf("a"), leaf("b"))), + anchor: "a".to_string(), + n: 3, + }; + let o = observable(&p); + assert_eq!(o.get("a"), Some(&3)); + assert_eq!(o.get("b"), Some(&3)); + } + + #[test] + fn eval_matches_registry_specs() { + // Independent ground truth: the expected observable for each relation, + // hand-written HERE rather than derived from eval(). This breaks the + // circularity in `every_relation_is_equivalent_and_nonvacuous`, where + // `rel.expected` is itself produced by eval() — so an eval() bug would + // agree with itself and ship silently. Both programs of every pair, and + // the stored `expected`, must match these literals. + fn hand_computed(name: &str) -> BTreeMap { + let m = |pairs: &[(&str, u64)]| -> BTreeMap { + pairs.iter().map(|(l, c)| (l.to_string(), *c)).collect() + }; + match name { + "seq-assoc" => m(&[("a", 1), ("b", 1), ("c", 1)]), + "if-true" => m(&[("a", 1)]), + "if-false" => m(&[("b", 1)]), + "join-comm" => m(&[("a", 1), ("b", 1)]), + "race-winner" => m(&[("a", 1)]), + "do-while-once" => m(&[("a", 1)]), + "loop-break-once" => m(&[("a", 1)]), + other => { + panic!("no hand-written ground truth for relation '{other}' — add one here") + } + } + } + for rel in registry() { + let want = hand_computed(rel.name); + assert_eq!( + observable(&rel.prog_a), + want, + "relation '{}': observable(A) != hand-computed", + rel.name + ); + assert_eq!( + observable(&rel.prog_b), + want, + "relation '{}': observable(B) != hand-computed", + rel.name + ); + assert_eq!( + rel.expected, want, + "relation '{}': stored expected != hand-computed", + rel.name + ); + } + } + + #[test] + fn backstop_guards_are_emitted() { + // Proves the symmetric-bug BACKSTOP is wired into the rendered SQL: for + // each side, every expected label gets a `<> count` guard, plus an + // unexpected-label `NOT IN (...)` guard pinning the side to exactly the + // known label set. Without these, a bug making A and B wrong the SAME + // way (equal-but-incorrect observables) would slip past the headline + // EXCEPT diff. seq-assoc has the richest observable ({a:1,b:1,c:1}). + let rels = registry(); + let rel = rels.iter().find(|r| r.name == "seq-assoc").unwrap(); + let sql = meta_sql_test(rel, 30); + let id = &rel.id; + for which in ["a", "b"] { + let tag = format!("{id}-{which}"); + for (label, count) in [("a", 1), ("b", 1), ("c", 1)] { + assert!( + sql.contains(&format!( + "WHERE shape_id = '{tag}' AND node_path = '{label}') <> {count}" + )), + "missing per-label backstop guard for {tag}/{label}" + ); + } + assert!( + sql.contains(&format!( + "WHERE shape_id = '{tag}' AND node_path NOT IN ('a', 'b', 'c')" + )), + "missing unexpected-label backstop guard for {tag}" + ); + } + } + + #[test] + fn renders_have_balanced_parens_and_dollar_quotes() { + for rel in registry() { + for (tag, prog) in [ + (format!("{}-a", rel.id), &rel.prog_a), + (format!("{}-b", rel.id), &rel.prog_b), + ] { + let dsl = render_prog(prog, &tag); + assert!(parens_balanced(&dsl), "unbalanced parens: {dsl}"); + assert_eq!(dsl.matches("$mk$").count() % 2, 0, "unbalanced $mk$: {dsl}"); + assert_eq!(dsl.matches("$c$").count() % 2, 0, "unbalanced $c$: {dsl}"); + // render_prog must never emit df.start (that's the test wrapper). + assert!(!dsl.contains("df.start"), "render leaked df.start: {dsl}"); + } + } + } + + #[test] + fn sql_test_has_expected_anatomy() { + let rels = registry(); + let rel = &rels[0]; + let sql = meta_sql_test(rel, 30); + assert!(sql.contains("SELECT 'TEST PASSED'")); + assert!(sql.contains(&format!("'{}-a'", rel.id))); + assert!(sql.contains(&format!("'{}-b'", rel.id))); + assert!(sql.contains("df.assert_structural_invariants(inst_a)")); + assert!(sql.contains("df.assert_structural_invariants(inst_b)")); + assert!(sql.contains("EXCEPT")); + assert!(sql.contains("observable(A) != observable(B)")); + // Exactly two durable instances are started. + assert_eq!(sql.matches("df.start(").count(), 2); + } + + #[test] + fn manifest_is_deterministic() { + let rels = registry(); + assert_eq!(meta_manifest_json(&rels), meta_manifest_json(&rels)); + let m = meta_manifest_json(&rels); + assert!(m.contains("\"kind\": \"metamorphic\"")); + assert!(m.contains(&format!("\"relation_count\": {}", rels.len()))); + } +} diff --git a/tests/e2e/generated/generator/src/prop.rs b/tests/e2e/generated/generator/src/prop.rs new file mode 100644 index 00000000..14d90d52 --- /dev/null +++ b/tests/e2e/generated/generator/src/prop.rs @@ -0,0 +1,623 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! Phase 3 (#232) — randomized property testing with shrinking. +//! +//! This module defines a recursive `proptest::Strategy` that generates +//! random labeled-leaf DSL trees with weighted depth and combinator-frequency +//! knobs, then asserts a battery of semantic and structural properties over +//! thousands of those trees per run. `Meta` is the std-only stand-in for the +//! issue's `FunctionGraph`; the real `FunctionGraph` is a pgrx/duroxide type +//! that does not exist in this dependency-free crate, so the model is the +//! faithful analogue (see `meta.rs`). +//! +//! ## Why model-level, not live PG +//! +//! proptest's defining feature is **shrinking**: when a property fails, it +//! automatically reduces the random input to a minimal counterexample. Shrinking +//! requires the property to execute **in-process** so proptest can drive the +//! reduction loop. A property that booted a live PostgreSQL instance per case +//! could not shrink (and could not run in this std-only crate, which has no +//! pgrx). So Phase 3 points proptest where it is strongest — the deterministic +//! reference interpreter (`meta::eval`/`observable`) and the renderer +//! (`meta::render_prog`), both pure and in-process. +//! +//! This is a deliberate, scoped choice, not a gap: +//! * Exhaustive depth-2 **live** oracle coverage already exists (Phases 2+4). +//! * The issue frames Phase 3's value-add as *shrinking* + unbounded random +//! coverage, which is realized fully at the model level. +//! * The properties harden the **same** `eval` that Phase 4's live tests use as +//! ground truth, so model-level hardening strengthens the live suite too. +//! +//! ## Persistent failure corpus +//! +//! proptest persists any discovered counterexample to +//! `generator/proptest-regressions/prop.txt` and replays it on every subsequent +//! run. That file IS the issue's "failure corpus checked into the repo"; commit +//! it whenever it appears (it is LF-normalized via `.gitattributes`). +//! +//! ## Reproducible vs. exploratory runs +//! +//! The committed config fixes `cases = 256`. proptest's native `PROPTEST_CASES` +//! environment variable overrides that, so the CI gate runs the fixed count +//! (deterministic, fast, replays the committed corpus) while the nightly job sets +//! a larger `PROPTEST_CASES` for a fresh-seed exploratory sweep. + +use crate::meta::{observable, render_prog, Meta}; +use crate::refinterp::{counts_match_render, interpret}; +use crate::render::render; +use crate::shape::{Cond, Shape}; +use proptest::prelude::*; +use std::collections::{BTreeMap, BTreeSet}; + +// --------------------------------------------------------------------------- +// Tree constructors (terser than spelling out Box::new at every call site). +// --------------------------------------------------------------------------- + +fn seq(a: Meta, b: Meta) -> Meta { + Meta::Seq(Box::new(a), Box::new(b)) +} + +fn join(a: Meta, b: Meta) -> Meta { + Meta::Join(Box::new(a), Box::new(b)) +} + +fn cond(taken: bool) -> Cond { + if taken { + Cond::True + } else { + Cond::False + } +} + +// --------------------------------------------------------------------------- +// The recursive strategy: random `Meta` trees. +// --------------------------------------------------------------------------- + +/// Random leaf label drawn from a small, readable alphabet. Keeping the alphabet +/// small means leaves frequently collide, exercising the multiset-summing paths +/// of the interpreter, and keeps shrunk counterexamples tidy. +fn arb_label() -> impl Strategy { + prop_oneof![Just("a"), Just("b"), Just("c"), Just("d"), Just("e"),].prop_map(String::from) +} + +/// Picks an anchor leaf for a generated loop that is GUARANTEED to execute (it is +/// a key of the body's observable, i.e. it survives any untaken `if`/`race` +/// branch). The rendered do-while/break predicate counts that leaf's trace rows, +/// so anchoring on an executing leaf keeps the loop terminating were it ever run +/// live. `observable` is always non-empty for our grammar (every subtree has at +/// least one executing leaf), so the fallback is purely defensive. +fn first_executing_label(body: &Meta) -> String { + observable(body) + .into_keys() + .next() + .unwrap_or_else(|| "a".to_string()) +} + +/// A recursive strategy producing random `Meta` trees. +/// +/// `prop_recursive` bounds the shape: at most depth 4, ~48 total nodes, with ~3 +/// children per recursive level. The `prop_oneof!` weights are the +/// combinator-frequency knobs the issue calls for — `seq` is the most common +/// glue, loops the rarest (they multiply work). Loop iteration counts are bounded +/// to `1..=4` so observable counts never approach `u64` overflow even under +/// nested loops. +fn arb_meta() -> impl Strategy { + let leaf = arb_label().prop_map(Meta::Leaf); + leaf.prop_recursive(4, 48, 3, |inner| { + prop_oneof![ + 3 => (inner.clone(), inner.clone()).prop_map(|(a, b)| seq(a, b)), + 2 => (inner.clone(), inner.clone()).prop_map(|(a, b)| join(a, b)), + 2 => (any::(), inner.clone(), inner.clone()) + .prop_map(|(c, t, e)| Meta::If(cond(c), Box::new(t), Box::new(e))), + 1 => inner.clone().prop_map(|w| Meta::Race(Box::new(w))), + 1 => (inner.clone(), 1u64..=4).prop_map(|(b, k)| { + let anchor = first_executing_label(&b); + Meta::DoWhile { body: Box::new(b), anchor, k } + }), + 1 => (inner, 1u64..=4).prop_map(|(b, n)| { + let anchor = first_executing_label(&b); + Meta::LoopBreak { body: Box::new(b), anchor, n } + }), + ] + }) +} + +// --------------------------------------------------------------------------- +// Independent oracles (deliberately NOT shared with meta.rs). +// --------------------------------------------------------------------------- + +/// An INDEPENDENT re-implementation of the observable semantics, written in a +/// deliberately different style from `meta::eval`: a functional fold that returns +/// a fresh map and multiplies explicitly, versus `eval`'s in-place accumulator +/// threading a `mult` argument. A transcription bug in one is therefore unlikely +/// to be mirrored in the other, making `observable == ref_observable` a genuine +/// differential check across the whole random-tree space — and a foothold for the +/// Phase 5 differential-testing work. +fn ref_observable(p: &Meta) -> BTreeMap { + fn merge(mut a: BTreeMap, b: BTreeMap) -> BTreeMap { + for (k, v) in b { + *a.entry(k).or_insert(0) += v; + } + a + } + fn scale(m: BTreeMap, by: u64) -> BTreeMap { + m.into_iter().map(|(k, v)| (k, v * by)).collect() + } + match p { + Meta::Leaf(l) => { + let mut m = BTreeMap::new(); + m.insert(l.clone(), 1); + m + } + Meta::Seq(a, b) | Meta::Join(a, b) => merge(ref_observable(a), ref_observable(b)), + Meta::If(Cond::True, t, _) => ref_observable(t), + Meta::If(Cond::False, _, e) => ref_observable(e), + Meta::Race(w) => ref_observable(w), + Meta::DoWhile { body, k, .. } => scale(ref_observable(body), *k), + Meta::LoopBreak { body, n, .. } => scale(ref_observable(body), *n), + } +} + +fn total(m: &BTreeMap) -> u64 { + m.values().sum() +} + +/// All labels syntactically present in the tree, including those in untaken `if` +/// branches and abandoned `race` losers — because the renderer emits a marker +/// node for every leaf regardless of reachability (the DSL graph still contains +/// it). So every label here must appear in the rendered SQL. +fn labels_of(p: &Meta, out: &mut BTreeSet) { + match p { + Meta::Leaf(l) => { + out.insert(l.clone()); + } + Meta::Seq(a, b) | Meta::Join(a, b) => { + labels_of(a, out); + labels_of(b, out); + } + Meta::If(_, t, e) => { + labels_of(t, out); + labels_of(e, out); + } + Meta::Race(w) => labels_of(w, out), + Meta::DoWhile { body, .. } | Meta::LoopBreak { body, .. } => labels_of(body, out), + } +} + +#[derive(Default)] +struct NodeCounts { + race: usize, + dowhile: usize, + loop_break: usize, +} + +/// Counts the combinator nodes whose rendering is structurally checkable: each +/// `Race` emits exactly one `df.race(`/`df.sleep(` pair; each loop emits one +/// `df.loop(`; each `LoopBreak` additionally emits one `df.break()`. +fn node_counts(p: &Meta) -> NodeCounts { + fn go(p: &Meta, c: &mut NodeCounts) { + match p { + Meta::Leaf(_) => {} + Meta::Seq(a, b) | Meta::Join(a, b) => { + go(a, c); + go(b, c); + } + Meta::If(_, t, e) => { + go(t, c); + go(e, c); + } + Meta::Race(w) => { + c.race += 1; + go(w, c); + } + Meta::DoWhile { body, .. } => { + c.dowhile += 1; + go(body, c); + } + Meta::LoopBreak { body, .. } => { + c.loop_break += 1; + go(body, c); + } + } + } + let mut c = NodeCounts::default(); + go(p, &mut c); + c +} + +/// Independent structural paren-balance checker (intentionally not the meta.rs +/// test helper of the same name): strips the renderer's two dollar-quoted span +/// kinds (`$mk$` marker SQL and `$c$` condition SQL) so the parens embedded in +/// that SQL don't confuse the balance of the DSL combinator parens, then verifies +/// the remaining parens nest cleanly. Having a second, independent implementation +/// guards against a bug in the shared helper masking a real render defect. +fn parens_balanced(dsl: &str) -> bool { + fn strip(mut s: String, tag: &str) -> String { + while let Some(start) = s.find(tag) { + match s[start + tag.len()..].find(tag) { + Some(rel) => { + let end = start + tag.len() + rel + tag.len(); + s.replace_range(start..end, ""); + } + None => break, + } + } + s + } + let stripped = strip(strip(dsl.to_string(), "$mk$"), "$c$"); + let mut depth: i32 = 0; + for ch in stripped.chars() { + match ch { + '(' => depth += 1, + ')' => { + depth -= 1; + if depth < 0 { + return false; + } + } + _ => {} + } + } + depth == 0 +} + +// --------------------------------------------------------------------------- +// Phase 5 (#232): random `Shape` trees, for differential + causal-order checks +// against the reference interpreter. `Shape` is the node-path-tagged model the +// live `df_gen_trace` records, so these properties harden the SAME ground truth +// the live Phase 2 matrix asserts. +// --------------------------------------------------------------------------- + +/// A recursive strategy producing random `Shape` trees. Mirrors `arb_meta`'s +/// shape budget (depth 4, ~48 nodes, ~3 children) and combinator weighting. A +/// single loop iteration count `K` is applied uniformly at interpret/render +/// time (see the properties below), bounded small so nested loops never approach +/// `u64` overflow. +fn arb_shape() -> impl Strategy { + let leaf = prop_oneof![ + 3 => Just(Shape::Marker), + 1 => (1u32..=4).prop_map(|n| Shape::LoopBreak { n }), + ]; + leaf.prop_recursive(4, 48, 3, |inner| { + prop_oneof![ + 3 => (inner.clone(), inner.clone()) + .prop_map(|(a, b)| Shape::Seq(Box::new(a), Box::new(b))), + 2 => (any::(), inner.clone(), inner.clone()).prop_map(|(c, t, e)| Shape::If { + then_b: Box::new(t), + else_b: Box::new(e), + cond: cond(c), + }), + 1 => inner.clone().prop_map(|b| Shape::Loop(Box::new(b))), + 2 => (inner.clone(), inner.clone()) + .prop_map(|(a, b)| Shape::Join(Box::new(a), Box::new(b))), + 1 => (inner.clone(), inner.clone(), inner.clone()) + .prop_map(|(a, b, c)| Shape::Join3(Box::new(a), Box::new(b), Box::new(c))), + 1 => (inner.clone(), inner) + .prop_map(|(a, b)| Shape::Race(Box::new(a), Box::new(b))), + ] + }) +} + +// --------------------------------------------------------------------------- +// Properties. +// --------------------------------------------------------------------------- + +proptest! { + // Fixed default for reproducible CI runs; proptest's native PROPTEST_CASES + // env var overrides this (the nightly exploration job sets it higher). + #![proptest_config(ProptestConfig::with_cases(256))] + + /// The interpreter is a pure function: same tree, same observable. + #[test] + fn eval_is_deterministic(p in arb_meta()) { + prop_assert_eq!(observable(&p), observable(&p)); + } + + /// DIFFERENTIAL: `meta::eval` agrees with an independently-written reference + /// interpreter on every random tree. Breaks the circularity in which the + /// metamorphic registry trusts `eval` to compute its own ground truth. + #[test] + fn eval_matches_independent_reference(p in arb_meta()) { + prop_assert_eq!(observable(&p), ref_observable(&p)); + } + + /// `seq` is associative under the observable (the issue's seq-assoc relation, + /// generalized from the single hand-picked case to arbitrary subtrees). + #[test] + fn seq_is_associative(a in arb_meta(), b in arb_meta(), c in arb_meta()) { + let left = seq(a.clone(), seq(b.clone(), c.clone())); + let right = seq(seq(a, b), c); + prop_assert_eq!(observable(&left), observable(&right)); + } + + /// `join` is commutative under the observable (join-comm, generalized). + #[test] + fn join_is_commutative(a in arb_meta(), b in arb_meta()) { + prop_assert_eq!(observable(&join(a.clone(), b.clone())), observable(&join(b, a))); + } + + /// `join` is associative under the observable. + #[test] + fn join_is_associative(a in arb_meta(), b in arb_meta(), c in arb_meta()) { + let left = join(a.clone(), join(b.clone(), c.clone())); + let right = join(join(a, b), c); + prop_assert_eq!(observable(&left), observable(&right)); + } + + /// `seq(a, b)` and `join(a, b)` yield the SAME observable: the multiset of + /// completion counts is order-free, so sequential vs. concurrent execution of + /// the same two children is observably indistinguishable. + #[test] + fn seq_and_join_have_same_observable(a in arb_meta(), b in arb_meta()) { + prop_assert_eq!(observable(&seq(a.clone(), b.clone())), observable(&join(a, b))); + } + + /// `if` reduces to exactly the taken branch (if-true / if-false, generalized). + #[test] + fn if_selects_the_taken_branch(a in arb_meta(), b in arb_meta()) { + let taken = Meta::If(Cond::True, Box::new(a.clone()), Box::new(b.clone())); + let untaken = Meta::If(Cond::False, Box::new(a.clone()), Box::new(b.clone())); + prop_assert_eq!(observable(&taken), observable(&a)); + prop_assert_eq!(observable(&untaken), observable(&b)); + } + + /// `race(winner, loser)` reduces to the winner (race-winner, generalized): the + /// abandoned loser contributes nothing to the observable. + #[test] + fn race_reduces_to_winner(a in arb_meta()) { + prop_assert_eq!(observable(&Meta::Race(Box::new(a.clone()))), observable(&a)); + } + + /// A loop scales every body count by its iteration factor (do-while `k` / + /// break-after `n`), generalizing meta.rs's fixed `loop_multiplier_scales_body` + /// to arbitrary bodies and factors. + #[test] + fn loop_scales_body_counts(b in arb_meta(), k in 1u64..=4) { + let base = observable(&b); + let anchor = first_executing_label(&b); + let dw = Meta::DoWhile { body: Box::new(b.clone()), anchor: anchor.clone(), k }; + let lb = Meta::LoopBreak { body: Box::new(b), anchor, n: k }; + let want: BTreeMap = + base.iter().map(|(l, c)| (l.clone(), *c * k)).collect(); + prop_assert_eq!(observable(&dw), want.clone()); + prop_assert_eq!(observable(&lb), want); + } + + /// Combinators that run all their children conserve the total completion + /// count; `race` conserves only the winner's. + #[test] + fn structural_combinators_conserve_total(a in arb_meta(), b in arb_meta()) { + let ta = total(&observable(&a)); + let tb = total(&observable(&b)); + prop_assert_eq!(total(&observable(&seq(a.clone(), b.clone()))), ta + tb); + prop_assert_eq!(total(&observable(&join(a.clone(), b.clone()))), ta + tb); + prop_assert_eq!(total(&observable(&Meta::Race(Box::new(a)))), ta); + } + + /// Rendering is a pure function: same tree, byte-identical SQL. + #[test] + fn render_is_deterministic(p in arb_meta()) { + prop_assert_eq!(render_prog(&p, "prop"), render_prog(&p, "prop")); + } + + /// Every random tree renders to structurally well-formed DSL: balanced + /// combinator parens, balanced dollar-quotes, no leaked `df.start`, one + /// `df.race(`+`df.sleep(` per race, one `df.loop(` per loop, one `df.break()` + /// per break-loop, and every label present. Generalizes meta.rs's fixed + /// `renders_have_balanced_parens_and_dollar_quotes` to thousands of trees. + #[test] + fn render_is_structurally_wellformed(p in arb_meta()) { + let dsl = render_prog(&p, "prop"); + + prop_assert!(parens_balanced(&dsl), "unbalanced parens: {}", dsl); + prop_assert_eq!(dsl.matches("$mk$").count() % 2, 0, "unbalanced $mk$: {}", dsl); + prop_assert_eq!(dsl.matches("$c$").count() % 2, 0, "unbalanced $c$: {}", dsl); + prop_assert!(!dsl.contains("df.start"), "render leaked df.start: {}", dsl); + + let counts = node_counts(&p); + prop_assert_eq!(dsl.matches("df.race(").count(), counts.race); + prop_assert_eq!(dsl.matches("df.sleep(").count(), counts.race); + prop_assert_eq!(dsl.matches("df.loop(").count(), counts.dowhile + counts.loop_break); + prop_assert_eq!(dsl.matches("df.break()").count(), counts.loop_break); + + let mut labels = BTreeSet::new(); + labels_of(&p, &mut labels); + for l in labels { + prop_assert!(dsl.contains(&format!("'{}'", l)), "label {} missing: {}", l, dsl); + } + } + + // ----------------------------------------------------------------------- + // Phase 5: reference-interpreter differential + causal-order laws over + // random `Shape` trees. The interpreter (`refinterp`) is a THIRD, + // independently-written semantics; these properties cross-check it against + // the renderer's closed-form counts and assert the happens-before structure + // the live wall-clock trace must obey. + // ----------------------------------------------------------------------- + + /// DIFFERENTIAL: the interpreter's per-path completion counts equal the + /// renderer's closed-form `expected` map on every random tree and every + /// `k`. Two independent implementations (step-by-step simulation vs. + /// arithmetic `mult`) — agreement is strong evidence both are correct. + #[test] + fn interp_counts_match_render(shape in arb_shape(), k in 1u64..=3) { + prop_assert!( + counts_match_render(&shape, k), + "count mismatch k={}: interp={:?} render={:?}", + k, + interpret(&shape, k).path_counts(), + render(&shape, k, "prop").expected, + ); + } + + /// The interpreter is a pure function: same tree + same `k`, identical + /// pomset (events AND happens-before edges). + #[test] + fn interp_is_deterministic(shape in arb_shape(), k in 1u64..=3) { + prop_assert_eq!(interpret(&shape, k), interpret(&shape, k)); + } + + /// Acyclicity, structurally: every happens-before edge points strictly + /// forward in the linearization (`u < v`), so the relation is a DAG and + /// the live wall-clock assertions can never be self-contradictory. + #[test] + fn interp_edges_point_forward(shape in arb_shape(), k in 1u64..=3) { + let pom = interpret(&shape, k); + for &(u, v) in &pom.edges { + prop_assert!(u < v, "non-forward edge ({}, {}) in {:?}", u, v, shape); + } + } + + /// Per node_path, the recorded `iteration` values are exactly the dense + /// range `1..=count` — no gaps, no duplicates — mirroring the live + /// `MAX(iteration) == count` contract the loop oracle relies on. + #[test] + fn interp_iterations_are_dense(shape in arb_shape(), k in 1u64..=3) { + let pom = interpret(&shape, k); + let mut per_path: BTreeMap> = BTreeMap::new(); + for ev in &pom.events { + per_path.entry(ev.node_path.clone()).or_default().push(ev.iteration); + } + for (path, mut iters) in per_path { + iters.sort_unstable(); + let expected: Vec = (1..=iters.len() as u64).collect(); + prop_assert_eq!(&iters, &expected, "iterations for {} not dense: {:?}", path, iters); + } + } + + /// CAUSAL-ORDER LAW (Seq), structurally: `Seq(a, b)` linearizes all of `a` + /// (event indices `0..na`) strictly before all of `b` (`na..`), PRESERVES + /// each child's happens-before relation untouched, and adds only forward + /// `a ≺ b` cross edges — at least one iff both sides execute. Asserting the + /// edge *partition* (not just its size) is the order-level analogue of + /// Phase 4's count laws and Phase 5's unique value-add: a size-only check + /// could pass while a dropped child edge masks a spurious cross edge. + #[test] + fn interp_seq_introduces_order(a in arb_shape(), b in arb_shape(), k in 1u64..=3) { + let pa = interpret(&a, k); + let pb = interpret(&b, k); + let na = pa.events.len(); + let seq = Shape::Seq(Box::new(a.clone()), Box::new(b.clone())); + let ps = interpret(&seq, k); + + // Seq concatenates a's events then b's, so the index space partitions at + // `na`. Split seq's edges into within-a, within-b (shifted back), and the + // cross edges, asserting every cross edge runs strictly a → b. + prop_assert_eq!(ps.events.len(), na + pb.events.len()); + let mut within_a: BTreeSet<(usize, usize)> = BTreeSet::new(); + let mut within_b: BTreeSet<(usize, usize)> = BTreeSet::new(); + let mut cross = 0usize; + for &(u, v) in &ps.edges { + if v < na { + within_a.insert((u, v)); + } else if u >= na { + within_b.insert((u - na, v - na)); + } else { + prop_assert!(u < na && v >= na, "stray seq edge ({}, {})", u, v); + cross += 1; + } + } + // Both child relations survive composition byte-for-byte. + prop_assert_eq!(&within_a, &pa.edges, "seq altered a's happens-before"); + prop_assert_eq!(&within_b, &pb.edges, "seq altered b's happens-before"); + // Sequencing introduces happens-before iff both sides actually execute. + if na == 0 || pb.events.is_empty() { + prop_assert_eq!(cross, 0, "empty side must add no cross edge"); + } else { + prop_assert!(cross > 0, "seq added no happens-before edge"); + } + } + + /// CAUSAL-ORDER LAW (Join), structurally: parallel branches are CONCURRENT. + /// `Join(a, b)`'s happens-before relation is EXACTLY a's edges unioned with + /// b's edges (shifted into the second index block) — no cross edge in either + /// direction. Asserting the edge *set* (not just its size) proves the + /// harness imposes no wall-clock ordering between branches and so stays + /// flake-free regardless of interleaving. + #[test] + fn interp_join_adds_no_order(a in arb_shape(), b in arb_shape(), k in 1u64..=3) { + let pa = interpret(&a, k); + let pb = interpret(&b, k); + let na = pa.events.len(); + let join = Shape::Join(Box::new(a), Box::new(b)); + let pj = interpret(&join, k); + + prop_assert_eq!(pj.events.len(), na + pb.events.len()); + let mut expected: BTreeSet<(usize, usize)> = pa.edges.clone(); + for &(u, v) in &pb.edges { + expected.insert((u + na, v + na)); + } + prop_assert_eq!(pj.edges, expected); + } +} + +// --------------------------------------------------------------------------- +// Unit tests for the strategy's own helpers (deterministic, no proptest needed). +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod helper_tests { + use super::*; + + #[test] + fn ref_observable_matches_eval_on_hand_cases() { + // seq(a, loop(b x3)) -> {a:1, b:3}; race(a) abandons nothing extra. + let p = seq( + Meta::Leaf("a".into()), + Meta::DoWhile { + body: Box::new(Meta::Leaf("b".into())), + anchor: "b".into(), + k: 3, + }, + ); + assert_eq!(observable(&p), ref_observable(&p)); + assert_eq!( + ref_observable(&p), + BTreeMap::from([("a".into(), 1), ("b".into(), 3)]) + ); + } + + #[test] + fn first_executing_label_skips_untaken_branch() { + // if(false, a, b) executes only b, so the anchor must be b, never a. + let body = Meta::If( + Cond::False, + Box::new(Meta::Leaf("a".into())), + Box::new(Meta::Leaf("b".into())), + ); + assert_eq!(first_executing_label(&body), "b"); + } + + #[test] + fn node_counts_and_labels_are_accurate() { + // race(loop_break(seq(a, b) x2)) over a join with c. + let p = join( + Meta::Race(Box::new(Meta::LoopBreak { + body: Box::new(seq(Meta::Leaf("a".into()), Meta::Leaf("b".into()))), + anchor: "a".into(), + n: 2, + })), + Meta::Leaf("c".into()), + ); + let c = node_counts(&p); + assert_eq!((c.race, c.dowhile, c.loop_break), (1, 0, 1)); + + let mut labels = BTreeSet::new(); + labels_of(&p, &mut labels); + assert_eq!( + labels, + BTreeSet::from(["a".to_string(), "b".to_string(), "c".to_string()]) + ); + } + + #[test] + fn parens_balanced_ignores_sql_inside_dollar_quotes() { + // A marker's embedded SQL has unbalanced-looking parens, but they live + // inside $mk$…$mk$, so the DSL itself is balanced. + let dsl = render_prog(&Meta::Leaf("a".into()), "prop"); + assert!(parens_balanced(&dsl)); + // A genuinely unbalanced DSL is rejected. + assert!(!parens_balanced("df.seq((a)")); + } +} diff --git a/tests/e2e/generated/generator/src/refinterp.rs b/tests/e2e/generated/generator/src/refinterp.rs new file mode 100644 index 00000000..c0b4942c --- /dev/null +++ b/tests/e2e/generated/generator/src/refinterp.rs @@ -0,0 +1,536 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! Phase 5 (#232): a synchronous, single-threaded tree-walking REFERENCE +//! INTERPRETER over [`Shape`] — the roadmap's strongest single oracle. +//! +//! # What it adds over Phases 2 and 4 +//! +//! Phases 2 and 4 already differential-test the live duroxide runtime, but only +//! along the COUNT/MULTISET dimension: they assert *how many times* each marker +//! executed (`render::Rendered::expected`, `meta::observable`). They say nothing +//! about ORDER. Phase 5 adds the missing causal dimension: this interpreter +//! computes, for any program, the trace the runtime *should* produce as a +//! **pomset** — a partially-ordered multiset of `(node_path, iteration)` events +//! plus the happens-before (`≺`) relation — implementing the intended semantics +//! from `docs/dsl-semantics.md` directly: +//! +//! * **Seq** (§4): `a` fully precedes `b` (`all-of-a ≺ all-of-b`). +//! * **If** (§5): the condition is a `$c$…$c$` predicate, not a marker, so it +//! emits no event; only the taken branch contributes. +//! * **Loop** (§6, do-while): each iteration runs `body` then the counter marker +//! (`body ≺ counter`), and iteration `i` fully precedes iteration `i+1`. +//! * **Join / Join3** (§7): branches are CONCURRENT — no ordering edge between +//! them (so a live assertion derived from this oracle never flakes on them). +//! * **Race** (§7): the winner runs; the loser is abandoned and emits nothing. +//! * **LoopBreak** (seed): the marker fires `n` times in sequence. +//! +//! # Why a second implementation is a real oracle +//! +//! `render::build` derives per-path counts in *closed form* (a single structural +//! pass multiplying by the loop factor `mult`). This interpreter instead derives +//! them by *simulating execution* step by step. Two independent implementations +//! of the same semantics agreeing (see [`counts_match_render`]) is a strong +//! correctness signal — and the projection of the pomset to per-path counts is +//! exactly the ground truth Phases 2/4 assert live, so this interpreter is the +//! bridge the issue asks Phase 5 to be. +//! +//! The live runtime records `(node_path, iteration)` (plus a `wall_clock` +//! column) into `df_gen_trace`, so the event set here is directly comparable to +//! the live trace, and each `≺` edge is directly checkable as +//! `earlier.wall_clock < later.wall_clock`. Step 2 of this phase emits those +//! order assertions into the generated live `.sql` tests from +//! [`Pomset::ordered_pairs`] (see `emit.rs`). + +#[cfg(test)] +use crate::render::render; +use crate::shape::{Cond, Shape}; +use std::collections::{BTreeMap, BTreeSet}; + +/// One observable trace event: the completion of a marker leaf at `node_path` on +/// its `iteration`-th execution (1-based), exactly as the live runtime records +/// it into `df_gen_trace(node_path, iteration, …)`. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub(crate) struct Event { + pub node_path: String, + pub iteration: u64, +} + +/// A pomset (partially-ordered multiset) of trace events: the causal trace the +/// runtime *should* produce for a program. +/// +/// `events` is a deterministic linearization — a valid topological order in +/// which the markers fire — and `edges` is the happens-before (`≺`) relation as +/// `(earlier, later)` index pairs into `events`. By construction every edge +/// points forward (`u < v`), so the relation is trivially acyclic. CONCURRENT +/// events (e.g. sibling `join` branches) have NO connecting edge. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub(crate) struct Pomset { + pub events: Vec, + pub edges: BTreeSet<(usize, usize)>, +} + +impl Pomset { + /// The order-erased projection: per-path execution counts. This is the + /// multiset the COUNT-based oracles check (`render::Rendered::expected` in + /// Phase 2, `meta::observable` in Phase 4); Phase 5 additionally pins the + /// order via [`Pomset::ordered_pairs`]. + /// + /// Test-only: the binary's live oracle uses [`Pomset::ordered_pairs`]; the + /// count projection is exercised only by the model-level differential and + /// the unit/proptest suites. + #[cfg(test)] + pub fn path_counts(&self) -> BTreeMap { + let mut m = BTreeMap::new(); + for e in &self.events { + *m.entry(e.node_path.clone()).or_insert(0) += 1; + } + m + } + + /// The happens-before edges resolved to concrete `(earlier, later)` event + /// pairs. Step 2 (live differential) emits, for each pair, an assertion that + /// the earlier event's `wall_clock` precedes the later's in `df_gen_trace`. + /// Concurrent siblings produce no pair, so such assertions never flake. + pub fn ordered_pairs(&self) -> Vec<(Event, Event)> { + self.edges + .iter() + .map(|&(u, v)| (self.events[u].clone(), self.events[v].clone())) + .collect() + } +} + +/// A fragment of the trace under construction: the indices of a subtree's +/// minimal (`first`) and maximal (`last`) events within the shared event vector. +/// An unreachable or marker-free subtree returns an empty fragment. +struct Frag { + first: Vec, + last: Vec, +} + +impl Frag { + fn empty() -> Self { + Frag { + first: Vec::new(), + last: Vec::new(), + } + } + + fn is_empty(&self) -> bool { + // `first` empty iff `last` empty — a fragment either has events or not. + self.first.is_empty() + } +} + +/// The interpreter state: the accumulating event list, the happens-before +/// edges, and a per-path execution counter that mirrors the live marker's +/// `(SELECT MAX(iteration) + 1 … WHERE node_path = …)`. +struct Interp { + events: Vec, + edges: BTreeSet<(usize, usize)>, + counters: BTreeMap, +} + +impl Interp { + fn new() -> Self { + Interp { + events: Vec::new(), + edges: BTreeSet::new(), + counters: BTreeMap::new(), + } + } + + /// Emits a single marker event at `path`, assigning the next iteration + /// number for that path (matching the live `MAX(iteration) + 1`). + fn emit(&mut self, path: &str) -> Frag { + let counter = self.counters.entry(path.to_string()).or_insert(0); + *counter += 1; + let iteration = *counter; + let idx = self.events.len(); + self.events.push(Event { + node_path: path.to_string(), + iteration, + }); + Frag { + first: vec![idx], + last: vec![idx], + } + } + + /// Records `a ≺ b`: every maximal event of `a` happens before every minimal + /// event of `b`. Both fragments were emitted earlier than nothing they + /// connect to, so each recorded edge points forward in `events`. + fn order(&mut self, a: &Frag, b: &Frag) { + for &u in &a.last { + for &v in &b.first { + self.edges.insert((u, v)); + } + } + } + + /// Sequential composition `a ; b`. + fn seq_frag(&mut self, a: Frag, b: Frag) -> Frag { + self.order(&a, &b); + let first = if a.is_empty() { + b.first.clone() + } else { + a.first + }; + let last = if b.is_empty() { a.last } else { b.last }; + Frag { first, last } + } + + /// Concurrent composition: the union frontier, with NO cross edges. + fn par_frag(frags: Vec) -> Frag { + let mut first = Vec::new(); + let mut last = Vec::new(); + for f in frags { + first.extend(f.first); + last.extend(f.last); + } + Frag { first, last } + } + + /// Recursively interprets `shape` at `path`. Mirrors `render::build`'s path + /// scheme exactly so the produced events line up with `df_gen_trace` rows. + /// `k` is the loop iteration count (the same `K` `render` uses). + fn go(&mut self, shape: &Shape, path: &str, reachable: bool, k: u64) -> Frag { + // An unreachable subtree never executes, so it contributes no event — + // the live counterpart leaves its markers pending (expected count 0). + if !reachable { + return Frag::empty(); + } + match shape { + Shape::Marker => self.emit(path), + + Shape::Seq(a, b) => { + let fa = self.go(a, &format!("{path}.0"), true, k); + let fb = self.go(b, &format!("{path}.1"), true, k); + self.seq_frag(fa, fb) + } + + Shape::If { + then_b, + else_b, + cond, + } => { + // The condition renders to a `$c$…$c$` predicate, not a marker, + // so it emits no event; exactly one branch is reachable. + let take_then = *cond == Cond::True; + let ft = self.go(then_b, &format!("{path}.t"), take_then, k); + let fe = self.go(else_b, &format!("{path}.e"), !take_then, k); + if take_then { + ft + } else { + fe + } + } + + Shape::Loop(body) => { + // do-while: `K` iterations of `seq(body, counter)`, each + // iteration fully preceding the next. The counter marker lives + // at `{path}.c`; the body at `{path}.b`. + let bpath = format!("{path}.b"); + let cpath = format!("{path}.c"); + let mut acc: Option = None; + for _ in 0..k { + let fb = self.go(body, &bpath, true, k); + let fc = self.emit(&cpath); + let iter_frag = self.seq_frag(fb, fc); // body ≺ counter + acc = Some(match acc { + None => iter_frag, + Some(prev) => self.seq_frag(prev, iter_frag), // iter i ≺ i+1 + }); + } + acc.unwrap_or_else(Frag::empty) + } + + Shape::Join(a, b) => { + let fa = self.go(a, &format!("{path}.0"), true, k); + let fb = self.go(b, &format!("{path}.1"), true, k); + Self::par_frag(vec![fa, fb]) // concurrent: no ordering between branches + } + + Shape::Join3(a, b, c) => { + let fa = self.go(a, &format!("{path}.0"), true, k); + let fb = self.go(b, &format!("{path}.1"), true, k); + let fc = self.go(c, &format!("{path}.2"), true, k); + Self::par_frag(vec![fa, fb, fc]) + } + + Shape::Race(a, b) => { + // The left branch wins deterministically; the right (loser) is + // abandoned and emits nothing (rendered as reachable=false). + let fw = self.go(a, &format!("{path}.w"), true, k); + let _loser = self.go(b, &format!("{path}.l"), false, k); + fw + } + + Shape::LoopBreak { n } => { + // The marker fires `n` times in sequence at `{path}.0`, then the + // break condition trips. No separate counter marker. + let mpath = format!("{path}.0"); + let mut acc: Option = None; + for _ in 0..*n { + let f = self.emit(&mpath); + acc = Some(match acc { + None => f, + Some(prev) => self.seq_frag(prev, f), + }); + } + acc.unwrap_or_else(Frag::empty) + } + } + } +} + +/// Interprets `shape` with `k` loop iterations, returning the expected causal +/// trace as a [`Pomset`]. The root path is `"r"`, matching `render`. +pub(crate) fn interpret(shape: &Shape, k: u64) -> Pomset { + let mut interp = Interp::new(); + interp.go(shape, "r", true, k); + Pomset { + events: interp.events, + edges: interp.edges, + } +} + +/// The Phase 5 model-level DIFFERENTIAL: the interpreter's order-erased +/// projection must equal the renderer's independently-derived ground truth +/// (after dropping `render`'s unreachable 0-count entries — this interpreter +/// simply omits paths that never execute). Closed-form count arithmetic +/// (`render::build`) and step-by-step execution simulation (this interpreter) +/// are two independent implementations of the same semantics; their agreement +/// is the oracle. +/// +/// Test-only: this is the model-level differential, exercised by the unit and +/// proptest suites. The live `.sql` tests assert the same counts against the +/// runtime directly (Phase 2) and additionally assert the causal ORDER derived +/// here via [`Pomset::ordered_pairs`]. +#[cfg(test)] +pub(crate) fn counts_match_render(shape: &Shape, k: u64) -> bool { + let pomset = interpret(shape, k); + let rendered = render(shape, k, "diff"); + let expected: BTreeMap = rendered + .expected + .into_iter() + .filter(|(_, count)| *count > 0) + .collect(); + pomset.path_counts() == expected +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shape::{seed_shapes, shapes_up_to, Comb}; + + const ALL_COMBS: [Comb; 6] = [ + Comb::Seq, + Comb::If, + Comb::Loop, + Comb::Join, + Comb::Join3, + Comb::Race, + ]; + + fn ev(path: &str, iter: u64) -> Event { + Event { + node_path: path.to_string(), + iteration: iter, + } + } + + fn marker() -> Shape { + Shape::Marker + } + + fn boxed(s: Shape) -> Box { + Box::new(s) + } + + #[test] + fn marker_emits_one_event_no_edges() { + let p = interpret(&marker(), 2); + assert_eq!(p.events, vec![ev("r", 1)]); + assert!(p.edges.is_empty()); + assert!(p.ordered_pairs().is_empty()); + } + + #[test] + fn seq_imposes_total_order() { + // S(M, M): r.0 ≺ r.1 + let s = Shape::Seq(boxed(marker()), boxed(marker())); + let p = interpret(&s, 2); + assert_eq!(p.events, vec![ev("r.0", 1), ev("r.1", 1)]); + assert_eq!(p.ordered_pairs(), vec![(ev("r.0", 1), ev("r.1", 1))]); + } + + #[test] + fn if_true_takes_then_only() { + let s = Shape::If { + then_b: boxed(marker()), + else_b: boxed(marker()), + cond: Cond::True, + }; + let p = interpret(&s, 2); + assert_eq!(p.events, vec![ev("r.t", 1)]); + } + + #[test] + fn if_false_takes_else_only() { + let s = Shape::If { + then_b: boxed(marker()), + else_b: boxed(marker()), + cond: Cond::False, + }; + let p = interpret(&s, 2); + assert_eq!(p.events, vec![ev("r.e", 1)]); + } + + #[test] + fn join_branches_are_concurrent() { + // J(M, M): both run, NO ordering edge between them. + let s = Shape::Join(boxed(marker()), boxed(marker())); + let p = interpret(&s, 2); + assert_eq!( + p.path_counts(), + BTreeMap::from([("r.0".into(), 1), ("r.1".into(), 1)]) + ); + assert!(p.edges.is_empty(), "join siblings must not be ordered"); + assert!(p.ordered_pairs().is_empty()); + } + + #[test] + fn join3_branches_are_concurrent() { + let s = Shape::Join3(boxed(marker()), boxed(marker()), boxed(marker())); + let p = interpret(&s, 2); + assert_eq!( + p.path_counts(), + BTreeMap::from([("r.0".into(), 1), ("r.1".into(), 1), ("r.2".into(), 1)]) + ); + assert!(p.edges.is_empty()); + } + + #[test] + fn seq_after_join_orders_every_branch_before_tail() { + // S(J(M, M), M): both join siblings ≺ the trailing marker, but NOT each + // other. Exercises the union frontier. + let s = Shape::Seq( + boxed(Shape::Join(boxed(marker()), boxed(marker()))), + boxed(marker()), + ); + let p = interpret(&s, 2); + // Events: r.0.0, r.0.1 (concurrent), then r.1. + assert_eq!(p.events, vec![ev("r.0.0", 1), ev("r.0.1", 1), ev("r.1", 1)]); + let pairs = p.ordered_pairs(); + assert!(pairs.contains(&(ev("r.0.0", 1), ev("r.1", 1)))); + assert!(pairs.contains(&(ev("r.0.1", 1), ev("r.1", 1)))); + // The two join siblings are NOT ordered relative to each other. + assert!(!pairs.contains(&(ev("r.0.0", 1), ev("r.0.1", 1)))); + assert_eq!(pairs.len(), 2); + } + + #[test] + fn loop_sequences_iterations_and_counter() { + // L(M) with K=3: body r.b and counter r.c each fire 3×, strictly + // alternating b1, c1, b2, c2, b3, c3. + let s = Shape::Loop(boxed(marker())); + let p = interpret(&s, 3); + assert_eq!( + p.events, + vec![ + ev("r.b", 1), + ev("r.c", 1), + ev("r.b", 2), + ev("r.c", 2), + ev("r.b", 3), + ev("r.c", 3), + ] + ); + // body ≺ counter within an iteration; counter ≺ next body across. + let pairs = p.ordered_pairs(); + assert!(pairs.contains(&(ev("r.b", 1), ev("r.c", 1)))); + assert!(pairs.contains(&(ev("r.c", 1), ev("r.b", 2)))); + assert!(pairs.contains(&(ev("r.b", 3), ev("r.c", 3)))); + } + + #[test] + fn race_keeps_winner_drops_loser() { + // R(M, M): only the winner at r.w runs; the loser r.l is abandoned. + let s = Shape::Race(boxed(marker()), boxed(marker())); + let p = interpret(&s, 2); + assert_eq!(p.events, vec![ev("r.w", 1)]); + } + + #[test] + fn loop_break_fires_marker_n_times() { + let s = Shape::LoopBreak { n: 3 }; + let p = interpret(&s, 2); + assert_eq!(p.events, vec![ev("r.0", 1), ev("r.0", 2), ev("r.0", 3)]); + // strictly sequential + let pairs = p.ordered_pairs(); + assert!(pairs.contains(&(ev("r.0", 1), ev("r.0", 2)))); + assert!(pairs.contains(&(ev("r.0", 2), ev("r.0", 3)))); + } + + #[test] + fn nested_loop_multiplies_body_count() { + // L(L(M)) with K=2: inner body fires K×K = 4 times. + let s = Shape::Loop(boxed(Shape::Loop(boxed(marker())))); + let p = interpret(&s, 2); + let counts = p.path_counts(); + assert_eq!(counts.get("r.b.b"), Some(&4)); // inner body 2×2 + assert_eq!(counts.get("r.b.c"), Some(&4)); // inner counter 2×2 + assert_eq!(counts.get("r.c"), Some(&2)); // outer counter 2 + } + + #[test] + fn all_edges_point_forward() { + // The recorded happens-before relation is acyclic by construction: every + // edge connects an earlier-emitted event to a later one. + for shape in shapes_up_to(&ALL_COMBS, 2) { + let p = interpret(&shape, 3); + for &(u, v) in &p.edges { + assert!(u < v, "edge ({u},{v}) is not forward for {shape:?}"); + } + } + } + + #[test] + fn iterations_are_dense_and_one_based_per_path() { + // For every path, its iteration numbers are exactly 1..=count with no + // gaps — matching the live MAX(iteration)+1 sequence. + for shape in shapes_up_to(&ALL_COMBS, 2) { + let p = interpret(&shape, 3); + let mut seen: BTreeMap> = BTreeMap::new(); + for e in &p.events { + seen.entry(e.node_path.clone()) + .or_default() + .push(e.iteration); + } + for (path, iters) in seen { + let want: Vec = (1..=iters.len() as u64).collect(); + assert_eq!(iters, want, "path {path} iterations not dense in {shape:?}"); + } + } + } + + #[test] + fn differential_matches_render_over_full_depth2_matrix() { + // The headline Phase 5 model-level oracle: the simulation interpreter and + // the closed-form renderer agree on per-path counts for EVERY shape in the + // depth-2 live matrix (plus the hand seeds), across several K values. + let mut shapes = shapes_up_to(&ALL_COMBS, 2); + shapes.extend(seed_shapes()); + for k in [1u64, 2, 3] { + for shape in &shapes { + assert!( + counts_match_render(shape, k), + "count differential failed at K={k} for {shape:?}:\n interp={:?}\n render={:?}", + interpret(shape, k).path_counts(), + render(shape, k, "diff").expected, + ); + } + } + } +} diff --git a/tests/e2e/generated/generator/src/render.rs b/tests/e2e/generated/generator/src/render.rs new file mode 100644 index 00000000..d0c9c809 --- /dev/null +++ b/tests/e2e/generated/generator/src/render.rs @@ -0,0 +1,365 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! Renders a [`Shape`] into a pg_durable DSL expression and the exact per-path +//! execution counts the resulting durable instance must produce. +//! +//! Every marker leaf is a `df.sql` node that appends one row to the shared +//! `df_gen_trace` table, tagged with its node path. The renderer walks the tree +//! once, emitting the DSL string and, in lockstep, the ground-truth count each +//! path is expected to reach. Those counts are what the generated SQL test (and +//! the committed manifest) assert against. +//! +//! Rendering conventions that make the expected counts deterministic: +//! - **loop**: `df.loop(seq(body, counter), 'COUNT(counter) < K')` → the body runs +//! exactly `K` times (do-while). Each enclosing loop multiplies child counts by `K`. +//! - **if**: the condition is a constant (`SELECT true`/`SELECT false`); the untaken +//! branch is reachable=false → its markers are expected 0 (and stay pending). +//! - **race**: the left child wins deterministically; the right child is wrapped in +//! `df.sleep(RACE_LOSER_DELAY_SECS)` and is abandoned (races are +//! complete-on-winner) → its markers are expected 0. + +use crate::shape::{Cond, Shape}; +use std::collections::BTreeMap; + +/// Seconds the losing race branch sleeps so the winner finishes first. Because +/// duroxide races are complete-on-winner (`execute_race_node` → `select2`), only +/// the dropped loser future waits this long — it adds no latency to a passing +/// test. The value must exceed the winner's runtime; it is safe at depth 2 where +/// every winner is a single `INSERT`. +const RACE_LOSER_DELAY_SECS: u64 = 2; + +/// A rendered shape: the DSL expression plus its ground-truth per-path counts. +pub struct Rendered { + pub dsl: String, + /// Map of node path → number of times that marker is expected to execute. + /// Unreachable markers are present with an expected count of 0. + pub expected: BTreeMap, +} + +/// Renders `shape` to DSL + expected counts, using `loop_iters` (K) iterations +/// for every generated loop. Every trace row is tagged with `shape_id` so +/// concurrent instances (e.g. a zombie left by a hung shape) cannot pollute +/// another shape's path counts — the marker INSERT, the loop-termination +/// condition, and the break condition all filter on `shape_id`. +pub fn render(shape: &Shape, loop_iters: u64, shape_id: &str) -> Rendered { + let mut expected = BTreeMap::new(); + let dsl = build(shape, "r", true, 1, loop_iters, shape_id, &mut expected); + Rendered { dsl, expected } +} + +/// The `df.sql` marker node for `path`: appends one trace row whose `iteration` +/// is the next ordinal for that `(shape_id, path)` pair. +/// +/// The `MAX(iteration) + 1` subquery and the INSERT are not one atomic +/// statement, but concurrent join/race branches always write *distinct* +/// `node_path`s (`.0`/`.1`/`.w`/`.l`/…), so two writers never contend for the +/// same `(shape_id, path)` ordinal; sequential re-entry inside a loop body is +/// single-threaded. The test's real correctness check is the per-path `COUNT(*)` +/// assertion, not the exact `iteration` value, so no UNIQUE constraint is needed +/// (and one could only cause spurious failures). +pub(crate) fn marker_sql(path: &str, shape_id: &str) -> String { + format!( + "df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) \ +VALUES ('{shape_id}', '{path}', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace \ +WHERE shape_id = '{shape_id}' AND node_path = '{path}'), clock_timestamp())$mk$)" + ) +} + +fn record(expected: &mut BTreeMap, path: &str, count: u64) { + *expected.entry(path.to_string()).or_insert(0) += count; +} + +/// Recursively renders `shape` at `path`. +/// +/// * `reachable` — whether this subtree actually executes (false under an untaken +/// `if` branch or a losing `race` branch). Controls expected counts only; the +/// DSL node is always emitted so the graph still contains it. +/// * `mult` — execution multiplier from enclosing loops (root = 1). +/// * `k` — loop iteration count. +fn build( + shape: &Shape, + path: &str, + reachable: bool, + mult: u64, + k: u64, + shape_id: &str, + expected: &mut BTreeMap, +) -> String { + match shape { + Shape::Marker => { + record(expected, path, if reachable { mult } else { 0 }); + marker_sql(path, shape_id) + } + Shape::Seq(a, b) => { + let sa = build( + a, + &format!("{path}.0"), + reachable, + mult, + k, + shape_id, + expected, + ); + let sb = build( + b, + &format!("{path}.1"), + reachable, + mult, + k, + shape_id, + expected, + ); + format!("df.seq({sa}, {sb})") + } + Shape::If { + then_b, + else_b, + cond, + } => { + let then_reach = reachable && *cond == Cond::True; + let else_reach = reachable && *cond == Cond::False; + let st = build( + then_b, + &format!("{path}.t"), + then_reach, + mult, + k, + shape_id, + expected, + ); + let se = build( + else_b, + &format!("{path}.e"), + else_reach, + mult, + k, + shape_id, + expected, + ); + format!("df.if($c${}$c$, {st}, {se})", cond.sql()) + } + Shape::Loop(body) => { + let cpath = format!("{path}.c"); + let inner = mult.saturating_mul(k); + let sb = build( + body, + &format!("{path}.b"), + reachable, + inner, + k, + shape_id, + expected, + ); + record(expected, &cpath, if reachable { inner } else { 0 }); + let counter = marker_sql(&cpath, shape_id); + let cond = format!( + "SELECT COUNT(*) < {k} FROM df_gen_trace \ +WHERE shape_id = '{shape_id}' AND node_path = '{cpath}'" + ); + format!("df.loop(df.seq({sb}, {counter}), $c${cond}$c$)") + } + Shape::Join(a, b) => { + let sa = build( + a, + &format!("{path}.0"), + reachable, + mult, + k, + shape_id, + expected, + ); + let sb = build( + b, + &format!("{path}.1"), + reachable, + mult, + k, + shape_id, + expected, + ); + format!("df.join({sa}, {sb})") + } + Shape::Join3(a, b, c) => { + let sa = build( + a, + &format!("{path}.0"), + reachable, + mult, + k, + shape_id, + expected, + ); + let sb = build( + b, + &format!("{path}.1"), + reachable, + mult, + k, + shape_id, + expected, + ); + let sc = build( + c, + &format!("{path}.2"), + reachable, + mult, + k, + shape_id, + expected, + ); + format!("df.join3({sa}, {sb}, {sc})") + } + Shape::Race(a, b) => { + // `execute_race_node` uses duroxide `select2` (complete-on-winner): + // the parent proceeds the instant either branch finishes and the + // loser future is dropped. The left branch is a marker that completes + // immediately, so it wins deterministically; the right branch is + // delayed by `RACE_LOSER_DELAY_SECS` so it is still pending at the + // finish and its markers stay expected=0 (reachable=false below). + let sw = build( + a, + &format!("{path}.w"), + reachable, + mult, + k, + shape_id, + expected, + ); + let sl = build(b, &format!("{path}.l"), false, mult, k, shape_id, expected); + format!("df.race({sw}, df.seq(df.sleep({RACE_LOSER_DELAY_SECS}), {sl}))") + } + Shape::LoopBreak { n } => { + let mpath = format!("{path}.0"); + record( + expected, + &mpath, + if reachable { + (*n as u64).saturating_mul(mult) + } else { + 0 + }, + ); + let marker = marker_sql(&mpath, shape_id); + let break_cond = format!( + "SELECT (SELECT COUNT(*) FROM df_gen_trace \ +WHERE shape_id = '{shape_id}' AND node_path = '{mpath}') >= {n}" + ); + format!( + "df.loop(df.seq({marker}, df.if($c${break_cond}$c$, df.break(), $c$SELECT 1$c$)), \ +$c$SELECT true$c$)" + ) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn m() -> Box { + Box::new(Shape::Marker) + } + + #[test] + fn single_marker_runs_once() { + let r = render(&Shape::Marker, 2, "gen-0001"); + assert_eq!(r.expected.get("r"), Some(&1)); + assert!(r.dsl.starts_with("df.sql(")); + } + + #[test] + fn seq_runs_both_children_once() { + let r = render(&Shape::Seq(m(), m()), 2, "gen-0001"); + assert_eq!(r.expected.get("r.0"), Some(&1)); + assert_eq!(r.expected.get("r.1"), Some(&1)); + assert!(r.dsl.starts_with("df.seq(")); + } + + #[test] + fn if_true_takes_then_else_zero() { + let r = render( + &Shape::If { + then_b: m(), + else_b: m(), + cond: Cond::True, + }, + 2, + "gen-0001", + ); + assert_eq!(r.expected.get("r.t"), Some(&1)); + assert_eq!(r.expected.get("r.e"), Some(&0)); + } + + #[test] + fn if_false_takes_else_then_zero() { + let r = render( + &Shape::If { + then_b: m(), + else_b: m(), + cond: Cond::False, + }, + 2, + "gen-0001", + ); + assert_eq!(r.expected.get("r.t"), Some(&0)); + assert_eq!(r.expected.get("r.e"), Some(&1)); + } + + #[test] + fn loop_runs_body_k_times() { + let r = render(&Shape::Loop(m()), 2, "gen-0001"); + assert_eq!(r.expected.get("r.b"), Some(&2)); + assert_eq!(r.expected.get("r.c"), Some(&2)); // counter marker + } + + #[test] + fn nested_loops_multiply() { + let r = render(&Shape::Loop(Box::new(Shape::Loop(m()))), 2, "gen-0001"); + // inner body runs 2 (outer) * 2 (inner) = 4 times. + assert_eq!(r.expected.get("r.b.b"), Some(&4)); + // inner counter runs 4 times; outer counter runs 2 times. + assert_eq!(r.expected.get("r.b.c"), Some(&4)); + assert_eq!(r.expected.get("r.c"), Some(&2)); + } + + #[test] + fn race_winner_runs_loser_zero() { + let r = render(&Shape::Race(m(), m()), 2, "gen-0001"); + assert_eq!(r.expected.get("r.w"), Some(&1)); + assert_eq!(r.expected.get("r.l"), Some(&0)); + assert!(r.dsl.contains("df.sleep(2)")); + } + + #[test] + fn join_in_loop_covers_230() { + // L(J(M,M)) — the #230 join-inside-loop bug pattern. + let r = render(&Shape::Loop(Box::new(Shape::Join(m(), m()))), 2, "gen-0001"); + assert_eq!(r.expected.get("r.b.0"), Some(&2)); + assert_eq!(r.expected.get("r.b.1"), Some(&2)); + } + + #[test] + fn loop_break_runs_n_times() { + let r = render(&Shape::LoopBreak { n: 3 }, 2, "gen-0001"); + assert_eq!(r.expected.get("r.0"), Some(&3)); + assert!(r.dsl.contains("df.break()")); + } + + #[test] + fn marker_sql_has_no_unbalanced_quotes() { + let r = render(&Shape::Seq(m(), m()), 2, "gen-0001"); + // Dollar-quoted marker/condition tags must be balanced. + assert_eq!(r.dsl.matches("$mk$").count() % 2, 0); + } + + #[test] + fn shape_id_scopes_every_trace_predicate() { + // The shape_id must appear in the INSERT, the iteration subquery, and the + // loop-termination condition so foreign instances cannot pollute counts. + let r = render(&Shape::Loop(m()), 2, "gen-0042"); + assert!(r.dsl.contains("shape_id, node_path, iteration, wall_clock")); + assert!(r.dsl.contains("VALUES ('gen-0042'")); + assert!(r.dsl.contains("WHERE shape_id = 'gen-0042' AND node_path")); + } +} diff --git a/tests/e2e/generated/generator/src/shape.rs b/tests/e2e/generated/generator/src/shape.rs new file mode 100644 index 00000000..7143989a --- /dev/null +++ b/tests/e2e/generated/generator/src/shape.rs @@ -0,0 +1,666 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the PostgreSQL License. + +//! Shape model and deterministic enumeration of combinator-nesting trees. +//! +//! A [`Shape`] is an abstract nesting tree over the pg_durable DSL combinators. +//! Leaves are markers (rendered as `df.sql` INSERTs into a shared trace table); +//! internal nodes are combinators (`seq`, `if`, `loop`, `join`, `join3`, `race`). +//! +//! Nesting depth is defined structurally: a marker has depth 0, and a combinator +//! has depth `1 + max(child depth)`. This matches the "combinator-nesting depth" +//! notion in issue #232 (e.g. `loop(marker)` is depth 1, `loop(join(m, m))` is +//! depth 2). + +use std::collections::BTreeMap; + +/// The branch a generated `df.if` deterministically takes. +/// +/// The canonical exhaustive enumeration always renders `Cond::True` (then-branch +/// taken, else-branch left pending). `Cond::False` is reserved for hand-written +/// seeds that exercise the else-taken path. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Cond { + True, + False, +} + +impl Cond { + /// The SQL predicate this condition renders to. + pub fn sql(self) -> &'static str { + match self { + Cond::True => "SELECT true", + Cond::False => "SELECT false", + } + } +} + +/// An abstract combinator-nesting tree. +#[derive(Clone, Debug)] +pub enum Shape { + /// A leaf marker — records one execution of its node path. + Marker, + /// `df.seq(a, b)` — runs `a` then `b`. + Seq(Box, Box), + /// `df.if(cond, then, else)` — takes exactly one branch. + If { + then_b: Box, + else_b: Box, + cond: Cond, + }, + /// `df.loop(body, cond)` — runs `body` a fixed number of iterations. + Loop(Box), + /// `df.join(a, b)` — runs both branches; completes when both finish. + Join(Box, Box), + /// `df.join3(a, b, c)` — runs all three branches. + Join3(Box, Box, Box), + /// `df.race(a, b)` — `a` deterministically wins; `b` is abandoned. + Race(Box, Box), + /// Seed-only: a loop whose body breaks after `n` marker executions. + LoopBreak { n: u32 }, +} + +/// The combinators that may appear in the exhaustive enumeration. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Comb { + Seq, + If, + Loop, + Join, + Join3, + Race, +} + +impl Comb { + /// Parses a comma-list token (e.g. `"seq"`) into a combinator. + pub fn parse(token: &str) -> Result { + match token.trim().to_ascii_lowercase().as_str() { + "seq" => Ok(Comb::Seq), + "if" => Ok(Comb::If), + "loop" => Ok(Comb::Loop), + "join" => Ok(Comb::Join), + "join3" => Ok(Comb::Join3), + "race" => Ok(Comb::Race), + other => Err(format!("unknown combinator '{other}'")), + } + } + + /// Stable lowercase name used in the manifest header. + pub fn name(self) -> &'static str { + match self { + Comb::Seq => "seq", + Comb::If => "if", + Comb::Loop => "loop", + Comb::Join => "join", + Comb::Join3 => "join3", + Comb::Race => "race", + } + } +} + +impl Shape { + /// Structural nesting depth (marker = 0, combinator = 1 + max child depth). + pub fn depth(&self) -> u32 { + match self { + Shape::Marker => 0, + Shape::Loop(a) => 1 + a.depth(), + Shape::Seq(a, b) | Shape::Join(a, b) | Shape::Race(a, b) => { + 1 + a.depth().max(b.depth()) + } + Shape::If { then_b, else_b, .. } => 1 + then_b.depth().max(else_b.depth()), + Shape::Join3(a, b, c) => 1 + a.depth().max(b.depth()).max(c.depth()), + // A break-loop is structurally a single loop over fixed leaves. + Shape::LoopBreak { .. } => 1, + } + } + + /// A compact, canonical, human-readable signature. + /// + /// Structurally distinct trees produce distinct signatures, so the signature + /// doubles as a dedup/sort key and as the stable identity in the manifest. + pub fn signature(&self) -> String { + match self { + Shape::Marker => "M".to_string(), + Shape::Seq(a, b) => format!("S({},{})", a.signature(), b.signature()), + Shape::If { + then_b, + else_b, + cond, + } => { + let tag = match cond { + Cond::True => "I", + Cond::False => "Ielse", + }; + format!("{tag}({},{})", then_b.signature(), else_b.signature()) + } + Shape::Loop(a) => format!("L({})", a.signature()), + Shape::Join(a, b) => format!("J({},{})", a.signature(), b.signature()), + Shape::Join3(a, b, c) => { + format!("J3({},{},{})", a.signature(), b.signature(), c.signature()) + } + Shape::Race(a, b) => format!("R({},{})", a.signature(), b.signature()), + Shape::LoopBreak { n } => format!("LB{n}"), + } + } + + /// Classifies whether this shape is expected to fail live execution because + /// it nests a `df.loop` in a host context the product's loop implementation + /// mishandles. Returns `Some(reason)` for shapes that must be **quarantined** + /// (run non-blocking / xfail) and `None` for shapes expected to pass. + /// + /// # Why this exists + /// + /// `execute_loop_node` ends each continuing iteration with `continue_as_new`, + /// which restarts the *currently executing* orchestration from its root: + /// + /// * Inside a `join`/`race`/`join3` branch the running orchestration is + /// `ExecuteSubtree`, whose input parser then receives a `FunctionInput` + /// instead of the `{graph, node_id, results}` it expects → the sub-orch + /// fails with "Missing graph in ExecuteSubtree input", so the parent + /// combinator never completes and the instance hangs (bug class #230). + /// * At top level it restarts `ExecuteFunctionGraph`, re-running the loop's + /// *preceding* siblings and inflating their marker counts (bug class #227). + /// + /// The predicate below is a purely structural model of those two failure + /// modes. It is validated to reproduce the live depth-2 failure set exactly + /// (see `is_problematic_matches_empirical_depth2_failset`). + pub fn is_problematic(&self) -> Option<&'static str> { + self.classify_for_quarantine(None) + } + + /// Recursive classifier. `inherited_host_danger` carries a non-`None` reason + /// when this subtree executes inside a host context where a `df.loop`'s + /// `continue_as_new` is mishandled (a join/race-winner branch, or a + /// non-leading sequence position). A `df.loop` reached with + /// `inherited_host_danger = Some(..)` is quarantined with that reason. + fn classify_for_quarantine( + &self, + inherited_host_danger: Option<&'static str>, + ) -> Option<&'static str> { + match self { + // Leaves never fail on their own. + Shape::Marker => None, + // `if` is transparent: it propagates the inherited context but never + // introduces one. A loop in an `if` branch at top level is safe. + Shape::If { then_b, else_b, .. } => then_b + .classify_for_quarantine(inherited_host_danger) + .or_else(|| else_b.classify_for_quarantine(inherited_host_danger)), + // In a sequence the leading element executes first and is re-entered + // harmlessly by a root restart, so it only inherits the ambient + // context. Every later element runs *after* its predecessors, so a + // loop there re-runs them on `continue_as_new` → over-count. The tail + // keeps any stricter inherited reason rather than masking a host + // join/race danger with the weaker seq-tail reason. + Shape::Seq(a, b) => a + .classify_for_quarantine(inherited_host_danger) + .or_else(|| { + b.classify_for_quarantine(inherited_host_danger.or(Some("loop-in-seq-tail"))) + }), + // Every join branch runs as an `ExecuteSubtree`, so any loop inside + // either branch is mishandled. + Shape::Join(a, b) => a + .classify_for_quarantine(Some("loop-in-join")) + .or_else(|| b.classify_for_quarantine(Some("loop-in-join"))), + Shape::Join3(a, b, c) => a + .classify_for_quarantine(Some("loop-in-join")) + .or_else(|| b.classify_for_quarantine(Some("loop-in-join"))) + .or_else(|| c.classify_for_quarantine(Some("loop-in-join"))), + // The race *winner* runs as an `ExecuteSubtree` and must complete, so + // a loop there hangs. The loser is abandoned, so a loop confined to + // it only inherits the ambient context (safe at top level). + Shape::Race(w, l) => w + .classify_for_quarantine(Some("loop-in-race-winner")) + .or_else(|| l.classify_for_quarantine(inherited_host_danger)), + // A loop reached inside a bad host context is quarantined. Otherwise + // a loop whose body itself contains a combinator (join/race/loop) + // re-enters that combinator's sub-orchestration on every iteration + // and is mishandled the same way. + Shape::Loop(body) => { + if let Some(reason) = inherited_host_danger { + Some(reason) + } else if body.contains_combinator() { + Some("loop-body-combinator") + } else { + // The body is a pure tree of `Marker`/`Seq`/`If` with no + // nested combinator, so it can never itself yield a reason; a + // plain loop over markers at the root is safe. + None + } + } + // A break-loop is a fixed-iteration loop over markers; same rules. + Shape::LoopBreak { .. } => inherited_host_danger, + } + } + + /// True when this subtree contains a combinator whose execution spawns a + /// sub-orchestration that a surrounding `df.loop` body would re-enter on + /// every iteration: `join`, `join3`, `race`, or a nested `loop`. `seq` and + /// `if` are transparent control flow and do not count on their own. + fn contains_combinator(&self) -> bool { + match self { + Shape::Marker => false, + Shape::Seq(a, b) => a.contains_combinator() || b.contains_combinator(), + Shape::If { then_b, else_b, .. } => { + then_b.contains_combinator() || else_b.contains_combinator() + } + Shape::Loop(_) + | Shape::LoopBreak { .. } + | Shape::Join(_, _) + | Shape::Join3(_, _, _) + | Shape::Race(_, _) => true, + } + } +} + +fn clone_box(s: &Shape) -> Box { + Box::new(s.clone()) +} + +/// Enumerates every shape whose depth is `<= max_depth`, built only from the +/// enabled `combs`. +/// +/// The construction is canonical: each structurally distinct tree is produced +/// exactly once. `shapes_up_to(d)` = `{Marker}` plus, for every enabled +/// combinator, that combinator applied to all child tuples drawn from +/// `shapes_up_to(d - 1)`. Because `shapes_up_to(d - 1) ⊆ shapes_up_to(d)`, the +/// single top-level call already contains all shallower trees, each once. +pub fn shapes_up_to(combs: &[Comb], max_depth: u32) -> Vec { + let mut out = vec![Shape::Marker]; + if max_depth == 0 { + return out; + } + let smaller = shapes_up_to(combs, max_depth - 1); + for &c in combs { + match c { + Comb::Seq => { + for a in &smaller { + for b in &smaller { + out.push(Shape::Seq(clone_box(a), clone_box(b))); + } + } + } + Comb::If => { + for a in &smaller { + for b in &smaller { + out.push(Shape::If { + then_b: clone_box(a), + else_b: clone_box(b), + cond: Cond::True, + }); + } + } + } + Comb::Loop => { + for a in &smaller { + out.push(Shape::Loop(clone_box(a))); + } + } + Comb::Join => { + for a in &smaller { + for b in &smaller { + out.push(Shape::Join(clone_box(a), clone_box(b))); + } + } + } + Comb::Join3 => { + for a in &smaller { + for b in &smaller { + for c2 in &smaller { + out.push(Shape::Join3(clone_box(a), clone_box(b), clone_box(c2))); + } + } + } + } + Comb::Race => { + for a in &smaller { + for b in &smaller { + out.push(Shape::Race(clone_box(a), clone_box(b))); + } + } + } + } + } + out +} + +/// Hand-written seed shapes that cover paths the canonical enumeration omits: +/// the else-taken `df.if` branch and `df.break` inside a loop. +/// +/// (`loop`-not-at-root and `join`-inside-loop — issues #227 and #230 — are +/// already covered structurally by the depth-2 enumeration, e.g. `S(M,L(M))` +/// and `L(J(M,M))`; they need no dedicated seed.) +pub fn seed_shapes() -> Vec { + vec![ + // else-taken: only the else marker runs. + Shape::If { + then_b: Box::new(Shape::Marker), + else_b: Box::new(Shape::Marker), + cond: Cond::False, + }, + // else-taken nested under a sequence. + Shape::Seq( + Box::new(Shape::If { + then_b: Box::new(Shape::Marker), + else_b: Box::new(Shape::Marker), + cond: Cond::False, + }), + Box::new(Shape::Marker), + ), + // break after 3 iterations. + Shape::LoopBreak { n: 3 }, + ] +} + +/// Builds the final, deterministically ordered shape list. +/// +/// Shapes are sorted by signature so identifiers are stable regardless of the +/// enumeration traversal order. An optional `max_shapes` cap truncates the +/// sorted list (used to bound deeper, combinatorially large profiles). +pub fn build_matrix( + combs: &[Comb], + max_depth: u32, + include_seeds: bool, + max_shapes: Option, +) -> Vec { + let mut all = shapes_up_to(combs, max_depth); + if include_seeds { + // Seeds are hand-written depth-1/2 shapes; honor the depth bound so + // `--max-depth` stays a reliable structural-complexity cap (a depth-2 + // seed must not leak into a depth-1 matrix). + all.extend(seed_shapes().into_iter().filter(|s| s.depth() <= max_depth)); + } + // Dedup by signature (seeds may coincide with enumerated shapes), then sort + // for a canonical, review-friendly order. + let mut by_sig: BTreeMap = BTreeMap::new(); + for s in all { + by_sig.entry(s.signature()).or_insert(s); + } + let mut ordered: Vec = by_sig.into_values().collect(); + ordered.sort_by_key(|s| s.signature()); + if let Some(cap) = max_shapes { + ordered.truncate(cap); + } + ordered +} + +#[cfg(test)] +mod tests { + use super::*; + + const DEFAULT: [Comb; 5] = [Comb::Seq, Comb::If, Comb::Loop, Comb::Join, Comb::Race]; + + #[test] + fn depth0_is_single_marker() { + let s = shapes_up_to(&DEFAULT, 0); + assert_eq!(s.len(), 1); + assert_eq!(s[0].signature(), "M"); + } + + #[test] + fn depth1_count_is_six() { + // 1 marker + seq + if + loop + join + race (each over a single marker). + let s = shapes_up_to(&DEFAULT, 1); + assert_eq!(s.len(), 6); + } + + #[test] + fn depth2_default_count_is_151() { + let s = shapes_up_to(&DEFAULT, 2); + assert_eq!(s.len(), 151); + } + + #[test] + fn depth2_with_join3_count_is_547() { + let full = [ + Comb::Seq, + Comb::If, + Comb::Loop, + Comb::Join, + Comb::Join3, + Comb::Race, + ]; + let s = shapes_up_to(&full, 2); + assert_eq!(s.len(), 547); + } + + #[test] + fn enumeration_has_no_duplicate_signatures() { + let s = shapes_up_to(&DEFAULT, 2); + let mut sigs: Vec = s.iter().map(|x| x.signature()).collect(); + sigs.sort(); + let before = sigs.len(); + sigs.dedup(); + assert_eq!(before, sigs.len(), "enumeration produced duplicate shapes"); + } + + #[test] + fn depth_matches_structure() { + assert_eq!(Shape::Marker.depth(), 0); + let loop_join = Shape::Loop(Box::new(Shape::Join( + Box::new(Shape::Marker), + Box::new(Shape::Marker), + ))); + assert_eq!(loop_join.depth(), 2); + } + + #[test] + fn build_matrix_is_deterministic() { + let a = build_matrix(&DEFAULT, 2, true, None); + let b = build_matrix(&DEFAULT, 2, true, None); + let sa: Vec = a.iter().map(|s| s.signature()).collect(); + let sb: Vec = b.iter().map(|s| s.signature()).collect(); + assert_eq!(sa, sb); + } + + /// The exact set of signatures that fail live execution in the depth-2 + /// matrix, captured from a clean, trace-isolated characterization run during + /// Phase 2 bring-up (a live `--include-generated` run on the depth-2 matrix; + /// see `tests/e2e/generated/README.md`). `is_problematic` must reproduce this + /// set precisely so the quarantine split stays in lock-step with observed + /// product behavior. + /// + /// To refresh after a product loop-handling fix: re-run the matrix live, + /// collect the signatures that now pass, remove them from this array, and + /// promote them out of `quarantine/`. A mismatch fails + /// `is_problematic_matches_empirical_depth2_failset` below. + const EMPIRICAL_FAILS: [&str; 26] = [ + "J(I(M,M),L(M))", + "J(J(M,M),L(M))", + "J(L(M),I(M,M))", + "J(L(M),J(M,M))", + "J(L(M),L(M))", + "J(L(M),M)", + "J(L(M),R(M,M))", + "J(L(M),S(M,M))", + "J(M,L(M))", + "J(R(M,M),L(M))", + "J(S(M,M),L(M))", + "L(J(M,M))", + "L(L(M))", + "L(R(M,M))", + "R(L(M),I(M,M))", + "R(L(M),J(M,M))", + "R(L(M),L(M))", + "R(L(M),M)", + "R(L(M),R(M,M))", + "R(L(M),S(M,M))", + "S(I(M,M),L(M))", + "S(J(M,M),L(M))", + "S(L(M),L(M))", + "S(M,L(M))", + "S(R(M,M),L(M))", + "S(S(M,M),L(M))", + ]; + + #[test] + fn is_problematic_matches_empirical_depth2_failset() { + use std::collections::BTreeSet; + let expected: BTreeSet<&str> = EMPIRICAL_FAILS.iter().copied().collect(); + + let matrix = build_matrix(&DEFAULT, 2, true, None); + let mut false_positives = Vec::new(); + let mut false_negatives = Vec::new(); + for shape in &matrix { + let sig = shape.signature(); + let predicted_fail = shape.is_problematic().is_some(); + let actually_fails = expected.contains(sig.as_str()); + match (predicted_fail, actually_fails) { + (true, false) => false_positives.push(sig), + (false, true) => false_negatives.push(sig), + _ => {} + } + } + assert!( + false_positives.is_empty() && false_negatives.is_empty(), + "classifier disagrees with empirical fail set:\n \ + false positives (predicted fail, actually pass): {false_positives:?}\n \ + false negatives (predicted pass, actually fail): {false_negatives:?}" + ); + + // Every empirical fail must correspond to a real matrix shape. + let matrix_sigs: BTreeSet = matrix.iter().map(|s| s.signature()).collect(); + for sig in &expected { + assert!( + matrix_sigs.contains(*sig), + "empirical fail '{sig}' is not present in the generated matrix" + ); + } + } + + #[test] + fn is_problematic_reasons_are_stable() { + let cases: [(&str, Shape); 5] = [ + // loop in a join branch + ( + "loop-in-join", + Shape::Join( + Box::new(Shape::Marker), + Box::new(Shape::Loop(Box::new(Shape::Marker))), + ), + ), + // loop in the race winner slot + ( + "loop-in-race-winner", + Shape::Race( + Box::new(Shape::Loop(Box::new(Shape::Marker))), + Box::new(Shape::Marker), + ), + ), + // loop in a non-leading sequence position + ( + "loop-in-seq-tail", + Shape::Seq( + Box::new(Shape::Marker), + Box::new(Shape::Loop(Box::new(Shape::Marker))), + ), + ), + // loop whose body is itself a combinator + ( + "loop-body-combinator", + Shape::Loop(Box::new(Shape::Join( + Box::new(Shape::Marker), + Box::new(Shape::Marker), + ))), + ), + // a top-level loop over a marker is fine + ("", Shape::Loop(Box::new(Shape::Marker))), + ]; + for (want, shape) in cases { + match (want, shape.is_problematic()) { + ("", got) => assert_eq!(got, None, "expected pass, got {got:?}"), + (reason, got) => assert_eq!(got, Some(reason)), + } + } + } + + #[test] + fn loop_in_race_loser_and_if_branch_pass() { + // loop confined to the abandoned race loser is safe + let race_loser = Shape::Race( + Box::new(Shape::Marker), + Box::new(Shape::Loop(Box::new(Shape::Marker))), + ); + assert_eq!(race_loser.is_problematic(), None); + // loop in an if branch at top level is safe + let if_branch = Shape::If { + then_b: Box::new(Shape::Loop(Box::new(Shape::Marker))), + else_b: Box::new(Shape::Marker), + cond: Cond::True, + }; + assert_eq!(if_branch.is_problematic(), None); + // leading loop in a sequence is safe + let seq_lead = Shape::Seq( + Box::new(Shape::Loop(Box::new(Shape::Marker))), + Box::new(Shape::Marker), + ); + assert_eq!(seq_lead.is_problematic(), None); + } + + #[test] + fn seq_tail_does_not_mask_host_join_danger() { + // Regression (S2): a loop in a seq tail that is itself inside a join + // branch must keep the stronger host reason, not be relabeled + // "loop-in-seq-tail". + let shape = Shape::Join( + Box::new(Shape::Seq( + Box::new(Shape::Marker), + Box::new(Shape::Loop(Box::new(Shape::Marker))), + )), + Box::new(Shape::Marker), + ); + assert_eq!(shape.is_problematic(), Some("loop-in-join")); + } + + #[test] + fn loop_over_pure_marker_tree_is_safe() { + // Regression (M1): a loop whose body is a combinator-free Seq/If tree is + // safe and must classify as `None` (guards the removed dead + // `loop-nested` recursion that used to run here). + let seq_body = Shape::Loop(Box::new(Shape::Seq( + Box::new(Shape::Marker), + Box::new(Shape::Marker), + ))); + assert_eq!(seq_body.is_problematic(), None); + let if_body = Shape::Loop(Box::new(Shape::If { + then_b: Box::new(Shape::Marker), + else_b: Box::new(Shape::Marker), + cond: Cond::True, + })); + assert_eq!(if_body.is_problematic(), None); + } + + #[test] + fn contains_combinator_is_exhaustive() { + let m = || Box::new(Shape::Marker); + // Transparent control flow / leaves spawn no sub-orchestration of their + // own. + assert!(!Shape::Marker.contains_combinator()); + assert!(!Shape::Seq(m(), m()).contains_combinator()); + assert!(!Shape::If { + then_b: m(), + else_b: m(), + cond: Cond::True, + } + .contains_combinator()); + // Combinators a surrounding loop body would re-enter every iteration. + assert!(Shape::Loop(m()).contains_combinator()); + assert!(Shape::LoopBreak { n: 2 }.contains_combinator()); + assert!(Shape::Join(m(), m()).contains_combinator()); + assert!(Shape::Join3(m(), m(), m()).contains_combinator()); + assert!(Shape::Race(m(), m()).contains_combinator()); + } + + #[test] + fn seeds_respect_max_depth() { + // Regression (S7): the `Seq(If(else),M)` seed has structural depth 2 and + // must not leak into a depth-1 matrix. + let d1 = build_matrix(&DEFAULT, 1, true, None); + assert!( + !d1.iter().any(|s| s.signature() == "S(Ielse(M,M),M)"), + "depth-2 seed leaked into a depth-1 matrix" + ); + // At depth 2 the seed is present. + let d2 = build_matrix(&DEFAULT, 2, true, None); + assert!(d2.iter().any(|s| s.signature() == "S(Ielse(M,M),M)")); + } +} diff --git a/tests/e2e/generated/manifest.json b/tests/e2e/generated/manifest.json new file mode 100644 index 00000000..188faa95 --- /dev/null +++ b/tests/e2e/generated/manifest.json @@ -0,0 +1,2570 @@ +{ + "version": 1, + "generator": "pg_durable_matrix_gen", + "max_depth": 2, + "combinators": ["seq", "if", "loop", "join", "race"], + "loop_iters": 2, + "include_seeds": true, + "shape_count": 154, + "shapes": [ + { + "id": "gen-0001", + "signature": "I(I(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0001', 'r.t.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0001' AND node_path = 'r.t.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0001', 'r.t.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0001' AND node_path = 'r.t.e'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0001', 'r.e.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0001' AND node_path = 'r.e.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0001', 'r.e.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0001' AND node_path = 'r.e.e'), clock_timestamp())$mk$)))", + "expected": { + "r.e.e": 0, + "r.e.t": 0, + "r.t.e": 0, + "r.t.t": 1 + }, + "order": [] + }, + { + "id": "gen-0002", + "signature": "I(I(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0002', 'r.t.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0002' AND node_path = 'r.t.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0002', 'r.t.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0002' AND node_path = 'r.t.e'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0002', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0002' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0002', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0002' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.e": 0, + "r.t.t": 1 + }, + "order": [] + }, + { + "id": "gen-0003", + "signature": "I(I(M,M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0003', 'r.t.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0003' AND node_path = 'r.t.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0003', 'r.t.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0003' AND node_path = 'r.t.e'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0003', 'r.e.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0003' AND node_path = 'r.e.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0003', 'r.e.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0003' AND node_path = 'r.e.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0003' AND node_path = 'r.e.c'$c$))", + "expected": { + "r.e.b": 0, + "r.e.c": 0, + "r.t.e": 0, + "r.t.t": 1 + }, + "order": [] + }, + { + "id": "gen-0004", + "signature": "I(I(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0004', 'r.t.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0004' AND node_path = 'r.t.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0004', 'r.t.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0004' AND node_path = 'r.t.e'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0004', 'r.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0004' AND node_path = 'r.e'), clock_timestamp())$mk$))", + "expected": { + "r.e": 0, + "r.t.e": 0, + "r.t.t": 1 + }, + "order": [] + }, + { + "id": "gen-0005", + "signature": "I(I(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0005', 'r.t.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0005' AND node_path = 'r.t.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0005', 'r.t.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0005' AND node_path = 'r.t.e'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0005', 'r.e.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0005' AND node_path = 'r.e.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0005', 'r.e.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0005' AND node_path = 'r.e.l'), clock_timestamp())$mk$))))", + "expected": { + "r.e.l": 0, + "r.e.w": 0, + "r.t.e": 0, + "r.t.t": 1 + }, + "order": [] + }, + { + "id": "gen-0006", + "signature": "I(I(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0006', 'r.t.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0006' AND node_path = 'r.t.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0006', 'r.t.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0006' AND node_path = 'r.t.e'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0006', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0006' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0006', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0006' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.e": 0, + "r.t.t": 1 + }, + "order": [] + }, + { + "id": "gen-0007", + "signature": "I(J(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0007', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0007' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0007', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0007' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0007', 'r.e.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0007' AND node_path = 'r.e.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0007', 'r.e.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0007' AND node_path = 'r.e.e'), clock_timestamp())$mk$)))", + "expected": { + "r.e.e": 0, + "r.e.t": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [] + }, + { + "id": "gen-0008", + "signature": "I(J(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0008', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0008' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0008', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0008' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0008', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0008' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0008', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0008' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [] + }, + { + "id": "gen-0009", + "signature": "I(J(M,M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0009', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0009' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0009', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0009' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0009', 'r.e.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0009' AND node_path = 'r.e.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0009', 'r.e.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0009' AND node_path = 'r.e.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0009' AND node_path = 'r.e.c'$c$))", + "expected": { + "r.e.b": 0, + "r.e.c": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [] + }, + { + "id": "gen-0010", + "signature": "I(J(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0010', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0010' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0010', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0010' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0010', 'r.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0010' AND node_path = 'r.e'), clock_timestamp())$mk$))", + "expected": { + "r.e": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [] + }, + { + "id": "gen-0011", + "signature": "I(J(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0011', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0011' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0011', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0011' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0011', 'r.e.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0011' AND node_path = 'r.e.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0011', 'r.e.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0011' AND node_path = 'r.e.l'), clock_timestamp())$mk$))))", + "expected": { + "r.e.l": 0, + "r.e.w": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [] + }, + { + "id": "gen-0012", + "signature": "I(J(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0012', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0012' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0012', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0012' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0012', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0012' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0012', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0012' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [] + }, + { + "id": "gen-0013", + "signature": "I(L(M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0013', 'r.t.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0013' AND node_path = 'r.t.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0013', 'r.t.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0013' AND node_path = 'r.t.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0013' AND node_path = 'r.t.c'$c$), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0013', 'r.e.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0013' AND node_path = 'r.e.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0013', 'r.e.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0013' AND node_path = 'r.e.e'), clock_timestamp())$mk$)))", + "expected": { + "r.e.e": 0, + "r.e.t": 0, + "r.t.b": 2, + "r.t.c": 2 + }, + "order": [ + ["r.t.b", 1, "r.t.c", 1], + ["r.t.c", 1, "r.t.b", 2], + ["r.t.b", 2, "r.t.c", 2] + ] + }, + { + "id": "gen-0014", + "signature": "I(L(M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0014', 'r.t.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0014' AND node_path = 'r.t.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0014', 'r.t.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0014' AND node_path = 'r.t.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0014' AND node_path = 'r.t.c'$c$), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0014', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0014' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0014', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0014' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.b": 2, + "r.t.c": 2 + }, + "order": [ + ["r.t.b", 1, "r.t.c", 1], + ["r.t.c", 1, "r.t.b", 2], + ["r.t.b", 2, "r.t.c", 2] + ] + }, + { + "id": "gen-0015", + "signature": "I(L(M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0015', 'r.t.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0015' AND node_path = 'r.t.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0015', 'r.t.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0015' AND node_path = 'r.t.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0015' AND node_path = 'r.t.c'$c$), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0015', 'r.e.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0015' AND node_path = 'r.e.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0015', 'r.e.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0015' AND node_path = 'r.e.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0015' AND node_path = 'r.e.c'$c$))", + "expected": { + "r.e.b": 0, + "r.e.c": 0, + "r.t.b": 2, + "r.t.c": 2 + }, + "order": [ + ["r.t.b", 1, "r.t.c", 1], + ["r.t.c", 1, "r.t.b", 2], + ["r.t.b", 2, "r.t.c", 2] + ] + }, + { + "id": "gen-0016", + "signature": "I(L(M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0016', 'r.t.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0016' AND node_path = 'r.t.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0016', 'r.t.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0016' AND node_path = 'r.t.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0016' AND node_path = 'r.t.c'$c$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0016', 'r.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0016' AND node_path = 'r.e'), clock_timestamp())$mk$))", + "expected": { + "r.e": 0, + "r.t.b": 2, + "r.t.c": 2 + }, + "order": [ + ["r.t.b", 1, "r.t.c", 1], + ["r.t.c", 1, "r.t.b", 2], + ["r.t.b", 2, "r.t.c", 2] + ] + }, + { + "id": "gen-0017", + "signature": "I(L(M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0017', 'r.t.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0017' AND node_path = 'r.t.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0017', 'r.t.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0017' AND node_path = 'r.t.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0017' AND node_path = 'r.t.c'$c$), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0017', 'r.e.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0017' AND node_path = 'r.e.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0017', 'r.e.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0017' AND node_path = 'r.e.l'), clock_timestamp())$mk$))))", + "expected": { + "r.e.l": 0, + "r.e.w": 0, + "r.t.b": 2, + "r.t.c": 2 + }, + "order": [ + ["r.t.b", 1, "r.t.c", 1], + ["r.t.c", 1, "r.t.b", 2], + ["r.t.b", 2, "r.t.c", 2] + ] + }, + { + "id": "gen-0018", + "signature": "I(L(M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0018', 'r.t.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0018' AND node_path = 'r.t.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0018', 'r.t.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0018' AND node_path = 'r.t.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0018' AND node_path = 'r.t.c'$c$), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0018', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0018' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0018', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0018' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.b": 2, + "r.t.c": 2 + }, + "order": [ + ["r.t.b", 1, "r.t.c", 1], + ["r.t.c", 1, "r.t.b", 2], + ["r.t.b", 2, "r.t.c", 2] + ] + }, + { + "id": "gen-0019", + "signature": "I(M,I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0019', 'r.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0019' AND node_path = 'r.t'), clock_timestamp())$mk$), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0019', 'r.e.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0019' AND node_path = 'r.e.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0019', 'r.e.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0019' AND node_path = 'r.e.e'), clock_timestamp())$mk$)))", + "expected": { + "r.e.e": 0, + "r.e.t": 0, + "r.t": 1 + }, + "order": [] + }, + { + "id": "gen-0020", + "signature": "I(M,J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0020', 'r.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0020' AND node_path = 'r.t'), clock_timestamp())$mk$), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0020', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0020' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0020', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0020' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t": 1 + }, + "order": [] + }, + { + "id": "gen-0021", + "signature": "I(M,L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0021', 'r.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0021' AND node_path = 'r.t'), clock_timestamp())$mk$), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0021', 'r.e.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0021' AND node_path = 'r.e.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0021', 'r.e.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0021' AND node_path = 'r.e.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0021' AND node_path = 'r.e.c'$c$))", + "expected": { + "r.e.b": 0, + "r.e.c": 0, + "r.t": 1 + }, + "order": [] + }, + { + "id": "gen-0022", + "signature": "I(M,M)", + "depth": 1, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0022', 'r.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0022' AND node_path = 'r.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0022', 'r.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0022' AND node_path = 'r.e'), clock_timestamp())$mk$))", + "expected": { + "r.e": 0, + "r.t": 1 + }, + "order": [] + }, + { + "id": "gen-0023", + "signature": "I(M,R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0023', 'r.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0023' AND node_path = 'r.t'), clock_timestamp())$mk$), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0023', 'r.e.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0023' AND node_path = 'r.e.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0023', 'r.e.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0023' AND node_path = 'r.e.l'), clock_timestamp())$mk$))))", + "expected": { + "r.e.l": 0, + "r.e.w": 0, + "r.t": 1 + }, + "order": [] + }, + { + "id": "gen-0024", + "signature": "I(M,S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0024', 'r.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0024' AND node_path = 'r.t'), clock_timestamp())$mk$), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0024', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0024' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0024', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0024' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t": 1 + }, + "order": [] + }, + { + "id": "gen-0025", + "signature": "I(R(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0025', 'r.t.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0025' AND node_path = 'r.t.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0025', 'r.t.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0025' AND node_path = 'r.t.l'), clock_timestamp())$mk$))), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0025', 'r.e.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0025' AND node_path = 'r.e.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0025', 'r.e.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0025' AND node_path = 'r.e.e'), clock_timestamp())$mk$)))", + "expected": { + "r.e.e": 0, + "r.e.t": 0, + "r.t.l": 0, + "r.t.w": 1 + }, + "order": [] + }, + { + "id": "gen-0026", + "signature": "I(R(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0026', 'r.t.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0026' AND node_path = 'r.t.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0026', 'r.t.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0026' AND node_path = 'r.t.l'), clock_timestamp())$mk$))), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0026', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0026' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0026', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0026' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.l": 0, + "r.t.w": 1 + }, + "order": [] + }, + { + "id": "gen-0027", + "signature": "I(R(M,M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0027', 'r.t.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0027' AND node_path = 'r.t.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0027', 'r.t.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0027' AND node_path = 'r.t.l'), clock_timestamp())$mk$))), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0027', 'r.e.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0027' AND node_path = 'r.e.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0027', 'r.e.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0027' AND node_path = 'r.e.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0027' AND node_path = 'r.e.c'$c$))", + "expected": { + "r.e.b": 0, + "r.e.c": 0, + "r.t.l": 0, + "r.t.w": 1 + }, + "order": [] + }, + { + "id": "gen-0028", + "signature": "I(R(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0028', 'r.t.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0028' AND node_path = 'r.t.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0028', 'r.t.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0028' AND node_path = 'r.t.l'), clock_timestamp())$mk$))), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0028', 'r.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0028' AND node_path = 'r.e'), clock_timestamp())$mk$))", + "expected": { + "r.e": 0, + "r.t.l": 0, + "r.t.w": 1 + }, + "order": [] + }, + { + "id": "gen-0029", + "signature": "I(R(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0029', 'r.t.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0029' AND node_path = 'r.t.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0029', 'r.t.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0029' AND node_path = 'r.t.l'), clock_timestamp())$mk$))), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0029', 'r.e.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0029' AND node_path = 'r.e.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0029', 'r.e.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0029' AND node_path = 'r.e.l'), clock_timestamp())$mk$))))", + "expected": { + "r.e.l": 0, + "r.e.w": 0, + "r.t.l": 0, + "r.t.w": 1 + }, + "order": [] + }, + { + "id": "gen-0030", + "signature": "I(R(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0030', 'r.t.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0030' AND node_path = 'r.t.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0030', 'r.t.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0030' AND node_path = 'r.t.l'), clock_timestamp())$mk$))), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0030', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0030' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0030', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0030' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.l": 0, + "r.t.w": 1 + }, + "order": [] + }, + { + "id": "gen-0031", + "signature": "I(S(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0031', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0031' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0031', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0031' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0031', 'r.e.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0031' AND node_path = 'r.e.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0031', 'r.e.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0031' AND node_path = 'r.e.e'), clock_timestamp())$mk$)))", + "expected": { + "r.e.e": 0, + "r.e.t": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [ + ["r.t.0", 1, "r.t.1", 1] + ] + }, + { + "id": "gen-0032", + "signature": "I(S(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0032', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0032' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0032', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0032' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0032', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0032' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0032', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0032' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [ + ["r.t.0", 1, "r.t.1", 1] + ] + }, + { + "id": "gen-0033", + "signature": "I(S(M,M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0033', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0033' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0033', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0033' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0033', 'r.e.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0033' AND node_path = 'r.e.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0033', 'r.e.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0033' AND node_path = 'r.e.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0033' AND node_path = 'r.e.c'$c$))", + "expected": { + "r.e.b": 0, + "r.e.c": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [ + ["r.t.0", 1, "r.t.1", 1] + ] + }, + { + "id": "gen-0034", + "signature": "I(S(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0034', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0034' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0034', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0034' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0034', 'r.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0034' AND node_path = 'r.e'), clock_timestamp())$mk$))", + "expected": { + "r.e": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [ + ["r.t.0", 1, "r.t.1", 1] + ] + }, + { + "id": "gen-0035", + "signature": "I(S(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0035', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0035' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0035', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0035' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0035', 'r.e.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0035' AND node_path = 'r.e.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0035', 'r.e.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0035' AND node_path = 'r.e.l'), clock_timestamp())$mk$))))", + "expected": { + "r.e.l": 0, + "r.e.w": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [ + ["r.t.0", 1, "r.t.1", 1] + ] + }, + { + "id": "gen-0036", + "signature": "I(S(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT true$c$, df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0036', 'r.t.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0036' AND node_path = 'r.t.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0036', 'r.t.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0036' AND node_path = 'r.t.1'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0036', 'r.e.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0036' AND node_path = 'r.e.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0036', 'r.e.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0036' AND node_path = 'r.e.1'), clock_timestamp())$mk$)))", + "expected": { + "r.e.0": 0, + "r.e.1": 0, + "r.t.0": 1, + "r.t.1": 1 + }, + "order": [ + ["r.t.0", 1, "r.t.1", 1] + ] + }, + { + "id": "gen-0037", + "signature": "Ielse(M,M)", + "depth": 1, + "class": "live", + "reason": null, + "dsl": "df.if($c$SELECT false$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0037', 'r.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0037' AND node_path = 'r.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0037', 'r.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0037' AND node_path = 'r.e'), clock_timestamp())$mk$))", + "expected": { + "r.e": 1, + "r.t": 0 + }, + "order": [] + }, + { + "id": "gen-0038", + "signature": "J(I(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0038', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0038' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0038', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0038' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0038', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0038' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0038', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0038' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [] + }, + { + "id": "gen-0039", + "signature": "J(I(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0039', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0039' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0039', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0039' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0039', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0039' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0039', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0039' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [] + }, + { + "id": "gen-0040", + "signature": "J(I(M,M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0040', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0040' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0040', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0040' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0040', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0040' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0040', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0040' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0040' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0041", + "signature": "J(I(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0041', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0041' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0041', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0041' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0041', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0041' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1": 1 + }, + "order": [] + }, + { + "id": "gen-0042", + "signature": "J(I(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0042', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0042' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0042', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0042' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0042', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0042' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0042', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0042' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [] + }, + { + "id": "gen-0043", + "signature": "J(I(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0043', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0043' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0043', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0043' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0043', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0043' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0043', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0043' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0044", + "signature": "J(J(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0044', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0044' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0044', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0044' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0044', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0044' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0044', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0044' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [] + }, + { + "id": "gen-0045", + "signature": "J(J(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0045', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0045' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0045', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0045' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0045', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0045' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0045', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0045' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [] + }, + { + "id": "gen-0046", + "signature": "J(J(M,M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0046', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0046' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0046', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0046' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0046', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0046' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0046', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0046' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0046' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0047", + "signature": "J(J(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0047', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0047' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0047', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0047' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0047', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0047' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1": 1 + }, + "order": [] + }, + { + "id": "gen-0048", + "signature": "J(J(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0048', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0048' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0048', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0048' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0048', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0048' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0048', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0048' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [] + }, + { + "id": "gen-0049", + "signature": "J(J(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0049', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0049' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0049', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0049' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0049', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0049' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0049', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0049' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0050", + "signature": "J(L(M),I(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0050', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0050' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0050', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0050' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0050' AND node_path = 'r.0.c'$c$), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0050', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0050' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0050', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0050' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2] + ] + }, + { + "id": "gen-0051", + "signature": "J(L(M),J(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0051', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0051' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0051', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0051' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0051' AND node_path = 'r.0.c'$c$), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0051', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0051' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0051', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0051' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2] + ] + }, + { + "id": "gen-0052", + "signature": "J(L(M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0052', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0052' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0052', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0052' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0052' AND node_path = 'r.0.c'$c$), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0052', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0052' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0052', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0052' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0052' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2], + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0053", + "signature": "J(L(M),M)", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0053', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0053' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0053', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0053' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0053' AND node_path = 'r.0.c'$c$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0053', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0053' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2] + ] + }, + { + "id": "gen-0054", + "signature": "J(L(M),R(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0054', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0054' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0054', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0054' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0054' AND node_path = 'r.0.c'$c$), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0054', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0054' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0054', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0054' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2] + ] + }, + { + "id": "gen-0055", + "signature": "J(L(M),S(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0055', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0055' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0055', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0055' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0055' AND node_path = 'r.0.c'$c$), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0055', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0055' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0055', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0055' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2], + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0056", + "signature": "J(M,I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0056', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0056' AND node_path = 'r.0'), clock_timestamp())$mk$), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0056', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0056' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0056', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0056' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [] + }, + { + "id": "gen-0057", + "signature": "J(M,J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0057', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0057' AND node_path = 'r.0'), clock_timestamp())$mk$), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0057', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0057' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0057', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0057' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [] + }, + { + "id": "gen-0058", + "signature": "J(M,L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0058', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0058' AND node_path = 'r.0'), clock_timestamp())$mk$), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0058', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0058' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0058', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0058' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0058' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0059", + "signature": "J(M,M)", + "depth": 1, + "class": "live", + "reason": null, + "dsl": "df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0059', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0059' AND node_path = 'r.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0059', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0059' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0": 1, + "r.1": 1 + }, + "order": [] + }, + { + "id": "gen-0060", + "signature": "J(M,R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0060', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0060' AND node_path = 'r.0'), clock_timestamp())$mk$), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0060', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0060' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0060', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0060' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [] + }, + { + "id": "gen-0061", + "signature": "J(M,S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0061', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0061' AND node_path = 'r.0'), clock_timestamp())$mk$), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0061', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0061' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0061', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0061' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0062", + "signature": "J(R(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0062', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0062' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0062', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0062' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0062', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0062' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0062', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0062' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [] + }, + { + "id": "gen-0063", + "signature": "J(R(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0063', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0063' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0063', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0063' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0063', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0063' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0063', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0063' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [] + }, + { + "id": "gen-0064", + "signature": "J(R(M,M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0064', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0064' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0064', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0064' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0064', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0064' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0064', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0064' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0064' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0065", + "signature": "J(R(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0065', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0065' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0065', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0065' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0065', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0065' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1": 1 + }, + "order": [] + }, + { + "id": "gen-0066", + "signature": "J(R(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0066', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0066' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0066', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0066' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0066', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0066' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0066', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0066' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [] + }, + { + "id": "gen-0067", + "signature": "J(R(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0067', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0067' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0067', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0067' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0067', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0067' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0067', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0067' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0068", + "signature": "J(S(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0068', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0068' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0068', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0068' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0068', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0068' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0068', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0068' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1] + ] + }, + { + "id": "gen-0069", + "signature": "J(S(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0069', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0069' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0069', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0069' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0069', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0069' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0069', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0069' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1] + ] + }, + { + "id": "gen-0070", + "signature": "J(S(M,M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-join", + "dsl": "df.join(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0070', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0070' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0070', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0070' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0070', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0070' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0070', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0070' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0070' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1], + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0071", + "signature": "J(S(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0071', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0071' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0071', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0071' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0071', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0071' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1] + ] + }, + { + "id": "gen-0072", + "signature": "J(S(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0072', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0072' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0072', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0072' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0072', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0072' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0072', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0072' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1] + ] + }, + { + "id": "gen-0073", + "signature": "J(S(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.join(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0073', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0073' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0073', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0073' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0073', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0073' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0073', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0073' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1], + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0074", + "signature": "L(I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.loop(df.seq(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0074', 'r.b.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0074' AND node_path = 'r.b.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0074', 'r.b.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0074' AND node_path = 'r.b.e'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0074', 'r.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0074' AND node_path = 'r.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0074' AND node_path = 'r.c'$c$)", + "expected": { + "r.b.e": 0, + "r.b.t": 2, + "r.c": 2 + }, + "order": [ + ["r.b.t", 1, "r.c", 1], + ["r.c", 1, "r.b.t", 2], + ["r.b.t", 2, "r.c", 2] + ] + }, + { + "id": "gen-0075", + "signature": "L(J(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-body-combinator", + "dsl": "df.loop(df.seq(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0075', 'r.b.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0075' AND node_path = 'r.b.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0075', 'r.b.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0075' AND node_path = 'r.b.1'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0075', 'r.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0075' AND node_path = 'r.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0075' AND node_path = 'r.c'$c$)", + "expected": { + "r.b.0": 2, + "r.b.1": 2, + "r.c": 2 + }, + "order": [ + ["r.b.0", 1, "r.c", 1], + ["r.b.1", 1, "r.c", 1], + ["r.c", 1, "r.b.0", 2], + ["r.c", 1, "r.b.1", 2], + ["r.b.0", 2, "r.c", 2], + ["r.b.1", 2, "r.c", 2] + ] + }, + { + "id": "gen-0076", + "signature": "L(L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-body-combinator", + "dsl": "df.loop(df.seq(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0076', 'r.b.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0076' AND node_path = 'r.b.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0076', 'r.b.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0076' AND node_path = 'r.b.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0076' AND node_path = 'r.b.c'$c$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0076', 'r.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0076' AND node_path = 'r.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0076' AND node_path = 'r.c'$c$)", + "expected": { + "r.b.b": 4, + "r.b.c": 4, + "r.c": 2 + }, + "order": [ + ["r.b.b", 1, "r.b.c", 1], + ["r.b.c", 1, "r.b.b", 2], + ["r.b.b", 2, "r.b.c", 2], + ["r.b.c", 2, "r.c", 1], + ["r.c", 1, "r.b.b", 3], + ["r.b.b", 3, "r.b.c", 3], + ["r.b.c", 3, "r.b.b", 4], + ["r.b.b", 4, "r.b.c", 4], + ["r.b.c", 4, "r.c", 2] + ] + }, + { + "id": "gen-0077", + "signature": "L(M)", + "depth": 1, + "class": "live", + "reason": null, + "dsl": "df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0077', 'r.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0077' AND node_path = 'r.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0077', 'r.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0077' AND node_path = 'r.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0077' AND node_path = 'r.c'$c$)", + "expected": { + "r.b": 2, + "r.c": 2 + }, + "order": [ + ["r.b", 1, "r.c", 1], + ["r.c", 1, "r.b", 2], + ["r.b", 2, "r.c", 2] + ] + }, + { + "id": "gen-0078", + "signature": "L(R(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-body-combinator", + "dsl": "df.loop(df.seq(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0078', 'r.b.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0078' AND node_path = 'r.b.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0078', 'r.b.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0078' AND node_path = 'r.b.l'), clock_timestamp())$mk$))), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0078', 'r.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0078' AND node_path = 'r.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0078' AND node_path = 'r.c'$c$)", + "expected": { + "r.b.l": 0, + "r.b.w": 2, + "r.c": 2 + }, + "order": [ + ["r.b.w", 1, "r.c", 1], + ["r.c", 1, "r.b.w", 2], + ["r.b.w", 2, "r.c", 2] + ] + }, + { + "id": "gen-0079", + "signature": "L(S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.loop(df.seq(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0079', 'r.b.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0079' AND node_path = 'r.b.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0079', 'r.b.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0079' AND node_path = 'r.b.1'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0079', 'r.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0079' AND node_path = 'r.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0079' AND node_path = 'r.c'$c$)", + "expected": { + "r.b.0": 2, + "r.b.1": 2, + "r.c": 2 + }, + "order": [ + ["r.b.0", 1, "r.b.1", 1], + ["r.b.1", 1, "r.c", 1], + ["r.c", 1, "r.b.0", 2], + ["r.b.0", 2, "r.b.1", 2], + ["r.b.1", 2, "r.c", 2] + ] + }, + { + "id": "gen-0080", + "signature": "LB3", + "depth": 1, + "class": "live", + "reason": null, + "dsl": "df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0080', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0080' AND node_path = 'r.0'), clock_timestamp())$mk$), df.if($c$SELECT (SELECT COUNT(*) FROM df_gen_trace WHERE shape_id = 'gen-0080' AND node_path = 'r.0') >= 3$c$, df.break(), $c$SELECT 1$c$)), $c$SELECT true$c$)", + "expected": { + "r.0": 3 + }, + "order": [ + ["r.0", 1, "r.0", 2], + ["r.0", 2, "r.0", 3] + ] + }, + { + "id": "gen-0081", + "signature": "M", + "depth": 0, + "class": "live", + "reason": null, + "dsl": "df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0081', 'r', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0081' AND node_path = 'r'), clock_timestamp())$mk$)", + "expected": { + "r": 1 + }, + "order": [] + }, + { + "id": "gen-0082", + "signature": "R(I(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0082', 'r.w.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0082' AND node_path = 'r.w.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0082', 'r.w.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0082' AND node_path = 'r.w.e'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0082', 'r.l.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0082' AND node_path = 'r.l.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0082', 'r.l.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0082' AND node_path = 'r.l.e'), clock_timestamp())$mk$))))", + "expected": { + "r.l.e": 0, + "r.l.t": 0, + "r.w.e": 0, + "r.w.t": 1 + }, + "order": [] + }, + { + "id": "gen-0083", + "signature": "R(I(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0083', 'r.w.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0083' AND node_path = 'r.w.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0083', 'r.w.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0083' AND node_path = 'r.w.e'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0083', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0083' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0083', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0083' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.e": 0, + "r.w.t": 1 + }, + "order": [] + }, + { + "id": "gen-0084", + "signature": "R(I(M,M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0084', 'r.w.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0084' AND node_path = 'r.w.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0084', 'r.w.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0084' AND node_path = 'r.w.e'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0084', 'r.l.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0084' AND node_path = 'r.l.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0084', 'r.l.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0084' AND node_path = 'r.l.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0084' AND node_path = 'r.l.c'$c$)))", + "expected": { + "r.l.b": 0, + "r.l.c": 0, + "r.w.e": 0, + "r.w.t": 1 + }, + "order": [] + }, + { + "id": "gen-0085", + "signature": "R(I(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0085', 'r.w.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0085' AND node_path = 'r.w.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0085', 'r.w.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0085' AND node_path = 'r.w.e'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0085', 'r.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0085' AND node_path = 'r.l'), clock_timestamp())$mk$)))", + "expected": { + "r.l": 0, + "r.w.e": 0, + "r.w.t": 1 + }, + "order": [] + }, + { + "id": "gen-0086", + "signature": "R(I(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0086', 'r.w.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0086' AND node_path = 'r.w.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0086', 'r.w.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0086' AND node_path = 'r.w.e'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0086', 'r.l.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0086' AND node_path = 'r.l.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0086', 'r.l.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0086' AND node_path = 'r.l.l'), clock_timestamp())$mk$)))))", + "expected": { + "r.l.l": 0, + "r.l.w": 0, + "r.w.e": 0, + "r.w.t": 1 + }, + "order": [] + }, + { + "id": "gen-0087", + "signature": "R(I(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0087', 'r.w.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0087' AND node_path = 'r.w.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0087', 'r.w.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0087' AND node_path = 'r.w.e'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0087', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0087' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0087', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0087' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.e": 0, + "r.w.t": 1 + }, + "order": [] + }, + { + "id": "gen-0088", + "signature": "R(J(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0088', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0088' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0088', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0088' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0088', 'r.l.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0088' AND node_path = 'r.l.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0088', 'r.l.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0088' AND node_path = 'r.l.e'), clock_timestamp())$mk$))))", + "expected": { + "r.l.e": 0, + "r.l.t": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [] + }, + { + "id": "gen-0089", + "signature": "R(J(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0089', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0089' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0089', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0089' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0089', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0089' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0089', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0089' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [] + }, + { + "id": "gen-0090", + "signature": "R(J(M,M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0090', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0090' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0090', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0090' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0090', 'r.l.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0090' AND node_path = 'r.l.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0090', 'r.l.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0090' AND node_path = 'r.l.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0090' AND node_path = 'r.l.c'$c$)))", + "expected": { + "r.l.b": 0, + "r.l.c": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [] + }, + { + "id": "gen-0091", + "signature": "R(J(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0091', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0091' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0091', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0091' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0091', 'r.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0091' AND node_path = 'r.l'), clock_timestamp())$mk$)))", + "expected": { + "r.l": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [] + }, + { + "id": "gen-0092", + "signature": "R(J(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0092', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0092' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0092', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0092' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0092', 'r.l.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0092' AND node_path = 'r.l.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0092', 'r.l.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0092' AND node_path = 'r.l.l'), clock_timestamp())$mk$)))))", + "expected": { + "r.l.l": 0, + "r.l.w": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [] + }, + { + "id": "gen-0093", + "signature": "R(J(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0093', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0093' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0093', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0093' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0093', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0093' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0093', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0093' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [] + }, + { + "id": "gen-0094", + "signature": "R(L(M),I(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-race-winner", + "dsl": "df.race(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0094', 'r.w.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0094' AND node_path = 'r.w.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0094', 'r.w.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0094' AND node_path = 'r.w.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0094' AND node_path = 'r.w.c'$c$), df.seq(df.sleep(2), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0094', 'r.l.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0094' AND node_path = 'r.l.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0094', 'r.l.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0094' AND node_path = 'r.l.e'), clock_timestamp())$mk$))))", + "expected": { + "r.l.e": 0, + "r.l.t": 0, + "r.w.b": 2, + "r.w.c": 2 + }, + "order": [ + ["r.w.b", 1, "r.w.c", 1], + ["r.w.c", 1, "r.w.b", 2], + ["r.w.b", 2, "r.w.c", 2] + ] + }, + { + "id": "gen-0095", + "signature": "R(L(M),J(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-race-winner", + "dsl": "df.race(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0095', 'r.w.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0095' AND node_path = 'r.w.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0095', 'r.w.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0095' AND node_path = 'r.w.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0095' AND node_path = 'r.w.c'$c$), df.seq(df.sleep(2), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0095', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0095' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0095', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0095' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.b": 2, + "r.w.c": 2 + }, + "order": [ + ["r.w.b", 1, "r.w.c", 1], + ["r.w.c", 1, "r.w.b", 2], + ["r.w.b", 2, "r.w.c", 2] + ] + }, + { + "id": "gen-0096", + "signature": "R(L(M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-race-winner", + "dsl": "df.race(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0096', 'r.w.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0096' AND node_path = 'r.w.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0096', 'r.w.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0096' AND node_path = 'r.w.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0096' AND node_path = 'r.w.c'$c$), df.seq(df.sleep(2), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0096', 'r.l.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0096' AND node_path = 'r.l.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0096', 'r.l.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0096' AND node_path = 'r.l.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0096' AND node_path = 'r.l.c'$c$)))", + "expected": { + "r.l.b": 0, + "r.l.c": 0, + "r.w.b": 2, + "r.w.c": 2 + }, + "order": [ + ["r.w.b", 1, "r.w.c", 1], + ["r.w.c", 1, "r.w.b", 2], + ["r.w.b", 2, "r.w.c", 2] + ] + }, + { + "id": "gen-0097", + "signature": "R(L(M),M)", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-race-winner", + "dsl": "df.race(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0097', 'r.w.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0097' AND node_path = 'r.w.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0097', 'r.w.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0097' AND node_path = 'r.w.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0097' AND node_path = 'r.w.c'$c$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0097', 'r.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0097' AND node_path = 'r.l'), clock_timestamp())$mk$)))", + "expected": { + "r.l": 0, + "r.w.b": 2, + "r.w.c": 2 + }, + "order": [ + ["r.w.b", 1, "r.w.c", 1], + ["r.w.c", 1, "r.w.b", 2], + ["r.w.b", 2, "r.w.c", 2] + ] + }, + { + "id": "gen-0098", + "signature": "R(L(M),R(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-race-winner", + "dsl": "df.race(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0098', 'r.w.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0098' AND node_path = 'r.w.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0098', 'r.w.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0098' AND node_path = 'r.w.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0098' AND node_path = 'r.w.c'$c$), df.seq(df.sleep(2), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0098', 'r.l.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0098' AND node_path = 'r.l.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0098', 'r.l.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0098' AND node_path = 'r.l.l'), clock_timestamp())$mk$)))))", + "expected": { + "r.l.l": 0, + "r.l.w": 0, + "r.w.b": 2, + "r.w.c": 2 + }, + "order": [ + ["r.w.b", 1, "r.w.c", 1], + ["r.w.c", 1, "r.w.b", 2], + ["r.w.b", 2, "r.w.c", 2] + ] + }, + { + "id": "gen-0099", + "signature": "R(L(M),S(M,M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-race-winner", + "dsl": "df.race(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0099', 'r.w.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0099' AND node_path = 'r.w.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0099', 'r.w.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0099' AND node_path = 'r.w.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0099' AND node_path = 'r.w.c'$c$), df.seq(df.sleep(2), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0099', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0099' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0099', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0099' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.b": 2, + "r.w.c": 2 + }, + "order": [ + ["r.w.b", 1, "r.w.c", 1], + ["r.w.c", 1, "r.w.b", 2], + ["r.w.b", 2, "r.w.c", 2] + ] + }, + { + "id": "gen-0100", + "signature": "R(M,I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0100', 'r.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0100' AND node_path = 'r.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0100', 'r.l.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0100' AND node_path = 'r.l.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0100', 'r.l.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0100' AND node_path = 'r.l.e'), clock_timestamp())$mk$))))", + "expected": { + "r.l.e": 0, + "r.l.t": 0, + "r.w": 1 + }, + "order": [] + }, + { + "id": "gen-0101", + "signature": "R(M,J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0101', 'r.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0101' AND node_path = 'r.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0101', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0101' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0101', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0101' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w": 1 + }, + "order": [] + }, + { + "id": "gen-0102", + "signature": "R(M,L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0102', 'r.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0102' AND node_path = 'r.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0102', 'r.l.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0102' AND node_path = 'r.l.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0102', 'r.l.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0102' AND node_path = 'r.l.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0102' AND node_path = 'r.l.c'$c$)))", + "expected": { + "r.l.b": 0, + "r.l.c": 0, + "r.w": 1 + }, + "order": [] + }, + { + "id": "gen-0103", + "signature": "R(M,M)", + "depth": 1, + "class": "live", + "reason": null, + "dsl": "df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0103', 'r.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0103' AND node_path = 'r.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0103', 'r.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0103' AND node_path = 'r.l'), clock_timestamp())$mk$)))", + "expected": { + "r.l": 0, + "r.w": 1 + }, + "order": [] + }, + { + "id": "gen-0104", + "signature": "R(M,R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0104', 'r.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0104' AND node_path = 'r.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0104', 'r.l.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0104' AND node_path = 'r.l.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0104', 'r.l.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0104' AND node_path = 'r.l.l'), clock_timestamp())$mk$)))))", + "expected": { + "r.l.l": 0, + "r.l.w": 0, + "r.w": 1 + }, + "order": [] + }, + { + "id": "gen-0105", + "signature": "R(M,S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0105', 'r.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0105' AND node_path = 'r.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0105', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0105' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0105', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0105' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w": 1 + }, + "order": [] + }, + { + "id": "gen-0106", + "signature": "R(R(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0106', 'r.w.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0106' AND node_path = 'r.w.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0106', 'r.w.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0106' AND node_path = 'r.w.l'), clock_timestamp())$mk$))), df.seq(df.sleep(2), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0106', 'r.l.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0106' AND node_path = 'r.l.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0106', 'r.l.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0106' AND node_path = 'r.l.e'), clock_timestamp())$mk$))))", + "expected": { + "r.l.e": 0, + "r.l.t": 0, + "r.w.l": 0, + "r.w.w": 1 + }, + "order": [] + }, + { + "id": "gen-0107", + "signature": "R(R(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0107', 'r.w.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0107' AND node_path = 'r.w.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0107', 'r.w.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0107' AND node_path = 'r.w.l'), clock_timestamp())$mk$))), df.seq(df.sleep(2), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0107', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0107' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0107', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0107' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.l": 0, + "r.w.w": 1 + }, + "order": [] + }, + { + "id": "gen-0108", + "signature": "R(R(M,M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0108', 'r.w.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0108' AND node_path = 'r.w.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0108', 'r.w.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0108' AND node_path = 'r.w.l'), clock_timestamp())$mk$))), df.seq(df.sleep(2), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0108', 'r.l.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0108' AND node_path = 'r.l.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0108', 'r.l.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0108' AND node_path = 'r.l.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0108' AND node_path = 'r.l.c'$c$)))", + "expected": { + "r.l.b": 0, + "r.l.c": 0, + "r.w.l": 0, + "r.w.w": 1 + }, + "order": [] + }, + { + "id": "gen-0109", + "signature": "R(R(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0109', 'r.w.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0109' AND node_path = 'r.w.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0109', 'r.w.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0109' AND node_path = 'r.w.l'), clock_timestamp())$mk$))), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0109', 'r.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0109' AND node_path = 'r.l'), clock_timestamp())$mk$)))", + "expected": { + "r.l": 0, + "r.w.l": 0, + "r.w.w": 1 + }, + "order": [] + }, + { + "id": "gen-0110", + "signature": "R(R(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0110', 'r.w.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0110' AND node_path = 'r.w.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0110', 'r.w.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0110' AND node_path = 'r.w.l'), clock_timestamp())$mk$))), df.seq(df.sleep(2), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0110', 'r.l.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0110' AND node_path = 'r.l.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0110', 'r.l.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0110' AND node_path = 'r.l.l'), clock_timestamp())$mk$)))))", + "expected": { + "r.l.l": 0, + "r.l.w": 0, + "r.w.l": 0, + "r.w.w": 1 + }, + "order": [] + }, + { + "id": "gen-0111", + "signature": "R(R(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0111', 'r.w.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0111' AND node_path = 'r.w.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0111', 'r.w.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0111' AND node_path = 'r.w.l'), clock_timestamp())$mk$))), df.seq(df.sleep(2), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0111', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0111' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0111', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0111' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.l": 0, + "r.w.w": 1 + }, + "order": [] + }, + { + "id": "gen-0112", + "signature": "R(S(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0112', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0112' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0112', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0112' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0112', 'r.l.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0112' AND node_path = 'r.l.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0112', 'r.l.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0112' AND node_path = 'r.l.e'), clock_timestamp())$mk$))))", + "expected": { + "r.l.e": 0, + "r.l.t": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [ + ["r.w.0", 1, "r.w.1", 1] + ] + }, + { + "id": "gen-0113", + "signature": "R(S(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0113', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0113' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0113', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0113' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0113', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0113' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0113', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0113' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [ + ["r.w.0", 1, "r.w.1", 1] + ] + }, + { + "id": "gen-0114", + "signature": "R(S(M,M),L(M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0114', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0114' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0114', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0114' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0114', 'r.l.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0114' AND node_path = 'r.l.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0114', 'r.l.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0114' AND node_path = 'r.l.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0114' AND node_path = 'r.l.c'$c$)))", + "expected": { + "r.l.b": 0, + "r.l.c": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [ + ["r.w.0", 1, "r.w.1", 1] + ] + }, + { + "id": "gen-0115", + "signature": "R(S(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0115', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0115' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0115', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0115' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0115', 'r.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0115' AND node_path = 'r.l'), clock_timestamp())$mk$)))", + "expected": { + "r.l": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [ + ["r.w.0", 1, "r.w.1", 1] + ] + }, + { + "id": "gen-0116", + "signature": "R(S(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0116', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0116' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0116', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0116' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0116', 'r.l.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0116' AND node_path = 'r.l.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0116', 'r.l.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0116' AND node_path = 'r.l.l'), clock_timestamp())$mk$)))))", + "expected": { + "r.l.l": 0, + "r.l.w": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [ + ["r.w.0", 1, "r.w.1", 1] + ] + }, + { + "id": "gen-0117", + "signature": "R(S(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.race(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0117', 'r.w.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0117' AND node_path = 'r.w.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0117', 'r.w.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0117' AND node_path = 'r.w.1'), clock_timestamp())$mk$)), df.seq(df.sleep(2), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0117', 'r.l.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0117' AND node_path = 'r.l.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0117', 'r.l.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0117' AND node_path = 'r.l.1'), clock_timestamp())$mk$))))", + "expected": { + "r.l.0": 0, + "r.l.1": 0, + "r.w.0": 1, + "r.w.1": 1 + }, + "order": [ + ["r.w.0", 1, "r.w.1", 1] + ] + }, + { + "id": "gen-0118", + "signature": "S(I(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0118', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0118' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0118', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0118' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0118', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0118' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0118', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0118' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [ + ["r.0.t", 1, "r.1.t", 1] + ] + }, + { + "id": "gen-0119", + "signature": "S(I(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0119', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0119' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0119', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0119' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0119', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0119' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0119', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0119' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.t", 1, "r.1.0", 1], + ["r.0.t", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0120", + "signature": "S(I(M,M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-seq-tail", + "dsl": "df.seq(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0120', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0120' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0120', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0120' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0120', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0120' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0120', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0120' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0120' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.0.t", 1, "r.1.b", 1], + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0121", + "signature": "S(I(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0121', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0121' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0121', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0121' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0121', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0121' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1": 1 + }, + "order": [ + ["r.0.t", 1, "r.1", 1] + ] + }, + { + "id": "gen-0122", + "signature": "S(I(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0122', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0122' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0122', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0122' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0122', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0122' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0122', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0122' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [ + ["r.0.t", 1, "r.1.w", 1] + ] + }, + { + "id": "gen-0123", + "signature": "S(I(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0123', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0123' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0123', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0123' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0123', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0123' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0123', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0123' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.e": 0, + "r.0.t": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.t", 1, "r.1.0", 1], + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0124", + "signature": "S(Ielse(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.if($c$SELECT false$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0124', 'r.0.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0124' AND node_path = 'r.0.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0124', 'r.0.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0124' AND node_path = 'r.0.e'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0124', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0124' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.e": 1, + "r.0.t": 0, + "r.1": 1 + }, + "order": [ + ["r.0.e", 1, "r.1", 1] + ] + }, + { + "id": "gen-0125", + "signature": "S(J(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0125', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0125' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0125', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0125' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0125', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0125' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0125', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0125' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [ + ["r.0.0", 1, "r.1.t", 1], + ["r.0.1", 1, "r.1.t", 1] + ] + }, + { + "id": "gen-0126", + "signature": "S(J(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0126', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0126' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0126', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0126' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0126', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0126' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0126', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0126' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.1.0", 1], + ["r.0.0", 1, "r.1.1", 1], + ["r.0.1", 1, "r.1.0", 1], + ["r.0.1", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0127", + "signature": "S(J(M,M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-seq-tail", + "dsl": "df.seq(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0127', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0127' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0127', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0127' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0127', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0127' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0127', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0127' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0127' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.0.0", 1, "r.1.b", 1], + ["r.0.1", 1, "r.1.b", 1], + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0128", + "signature": "S(J(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0128', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0128' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0128', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0128' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0128', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0128' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.1", 1], + ["r.0.1", 1, "r.1", 1] + ] + }, + { + "id": "gen-0129", + "signature": "S(J(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0129', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0129' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0129', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0129' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0129', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0129' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0129', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0129' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [ + ["r.0.0", 1, "r.1.w", 1], + ["r.0.1", 1, "r.1.w", 1] + ] + }, + { + "id": "gen-0130", + "signature": "S(J(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0130', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0130' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0130', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0130' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0130', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0130' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0130', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0130' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.1.0", 1], + ["r.0.1", 1, "r.1.0", 1], + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0131", + "signature": "S(L(M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0131', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0131' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0131', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0131' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0131' AND node_path = 'r.0.c'$c$), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0131', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0131' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0131', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0131' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2], + ["r.0.c", 2, "r.1.t", 1] + ] + }, + { + "id": "gen-0132", + "signature": "S(L(M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0132', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0132' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0132', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0132' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0132' AND node_path = 'r.0.c'$c$), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0132', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0132' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0132', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0132' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2], + ["r.0.c", 2, "r.1.0", 1], + ["r.0.c", 2, "r.1.1", 1] + ] + }, + { + "id": "gen-0133", + "signature": "S(L(M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-seq-tail", + "dsl": "df.seq(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0133', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0133' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0133', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0133' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0133' AND node_path = 'r.0.c'$c$), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0133', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0133' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0133', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0133' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0133' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2], + ["r.0.c", 2, "r.1.b", 1], + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0134", + "signature": "S(L(M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0134', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0134' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0134', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0134' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0134' AND node_path = 'r.0.c'$c$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0134', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0134' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2], + ["r.0.c", 2, "r.1", 1] + ] + }, + { + "id": "gen-0135", + "signature": "S(L(M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0135', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0135' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0135', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0135' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0135' AND node_path = 'r.0.c'$c$), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0135', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0135' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0135', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0135' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2], + ["r.0.c", 2, "r.1.w", 1] + ] + }, + { + "id": "gen-0136", + "signature": "S(L(M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0136', 'r.0.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0136' AND node_path = 'r.0.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0136', 'r.0.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0136' AND node_path = 'r.0.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0136' AND node_path = 'r.0.c'$c$), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0136', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0136' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0136', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0136' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.b": 2, + "r.0.c": 2, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.b", 1, "r.0.c", 1], + ["r.0.c", 1, "r.0.b", 2], + ["r.0.b", 2, "r.0.c", 2], + ["r.0.c", 2, "r.1.0", 1], + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0137", + "signature": "S(M,I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0137', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0137' AND node_path = 'r.0'), clock_timestamp())$mk$), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0137', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0137' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0137', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0137' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [ + ["r.0", 1, "r.1.t", 1] + ] + }, + { + "id": "gen-0138", + "signature": "S(M,J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0138', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0138' AND node_path = 'r.0'), clock_timestamp())$mk$), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0138', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0138' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0138', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0138' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0", 1, "r.1.0", 1], + ["r.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0139", + "signature": "S(M,L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-seq-tail", + "dsl": "df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0139', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0139' AND node_path = 'r.0'), clock_timestamp())$mk$), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0139', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0139' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0139', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0139' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0139' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.0", 1, "r.1.b", 1], + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0140", + "signature": "S(M,M)", + "depth": 1, + "class": "live", + "reason": null, + "dsl": "df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0140', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0140' AND node_path = 'r.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0140', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0140' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0": 1, + "r.1": 1 + }, + "order": [ + ["r.0", 1, "r.1", 1] + ] + }, + { + "id": "gen-0141", + "signature": "S(M,R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0141', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0141' AND node_path = 'r.0'), clock_timestamp())$mk$), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0141', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0141' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0141', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0141' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [ + ["r.0", 1, "r.1.w", 1] + ] + }, + { + "id": "gen-0142", + "signature": "S(M,S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0142', 'r.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0142' AND node_path = 'r.0'), clock_timestamp())$mk$), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0142', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0142' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0142', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0142' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0", 1, "r.1.0", 1], + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0143", + "signature": "S(R(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0143', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0143' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0143', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0143' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0143', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0143' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0143', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0143' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [ + ["r.0.w", 1, "r.1.t", 1] + ] + }, + { + "id": "gen-0144", + "signature": "S(R(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0144', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0144' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0144', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0144' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0144', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0144' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0144', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0144' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.w", 1, "r.1.0", 1], + ["r.0.w", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0145", + "signature": "S(R(M,M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-seq-tail", + "dsl": "df.seq(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0145', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0145' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0145', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0145' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0145', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0145' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0145', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0145' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0145' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.0.w", 1, "r.1.b", 1], + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0146", + "signature": "S(R(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0146', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0146' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0146', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0146' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0146', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0146' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1": 1 + }, + "order": [ + ["r.0.w", 1, "r.1", 1] + ] + }, + { + "id": "gen-0147", + "signature": "S(R(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0147', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0147' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0147', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0147' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0147', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0147' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0147', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0147' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [ + ["r.0.w", 1, "r.1.w", 1] + ] + }, + { + "id": "gen-0148", + "signature": "S(R(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0148', 'r.0.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0148' AND node_path = 'r.0.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0148', 'r.0.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0148' AND node_path = 'r.0.l'), clock_timestamp())$mk$))), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0148', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0148' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0148', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0148' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.l": 0, + "r.0.w": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.w", 1, "r.1.0", 1], + ["r.1.0", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0149", + "signature": "S(S(M,M),I(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0149', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0149' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0149', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0149' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0149', 'r.1.t', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0149' AND node_path = 'r.1.t'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0149', 'r.1.e', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0149' AND node_path = 'r.1.e'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.e": 0, + "r.1.t": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1], + ["r.0.1", 1, "r.1.t", 1] + ] + }, + { + "id": "gen-0150", + "signature": "S(S(M,M),J(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0150', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0150' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0150', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0150' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0150', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0150' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0150', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0150' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1], + ["r.0.1", 1, "r.1.0", 1], + ["r.0.1", 1, "r.1.1", 1] + ] + }, + { + "id": "gen-0151", + "signature": "S(S(M,M),L(M))", + "depth": 2, + "class": "quarantine", + "reason": "loop-in-seq-tail", + "dsl": "df.seq(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0151', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0151' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0151', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0151' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0151', 'r.1.b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0151' AND node_path = 'r.1.b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0151', 'r.1.c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0151' AND node_path = 'r.1.c'), clock_timestamp())$mk$)), $c$SELECT COUNT(*) < 2 FROM df_gen_trace WHERE shape_id = 'gen-0151' AND node_path = 'r.1.c'$c$))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.b": 2, + "r.1.c": 2 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1], + ["r.0.1", 1, "r.1.b", 1], + ["r.1.b", 1, "r.1.c", 1], + ["r.1.c", 1, "r.1.b", 2], + ["r.1.b", 2, "r.1.c", 2] + ] + }, + { + "id": "gen-0152", + "signature": "S(S(M,M),M)", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0152', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0152' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0152', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0152' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0152', 'r.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0152' AND node_path = 'r.1'), clock_timestamp())$mk$))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1], + ["r.0.1", 1, "r.1", 1] + ] + }, + { + "id": "gen-0153", + "signature": "S(S(M,M),R(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0153', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0153' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0153', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0153' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0153', 'r.1.w', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0153' AND node_path = 'r.1.w'), clock_timestamp())$mk$), df.seq(df.sleep(2), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0153', 'r.1.l', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0153' AND node_path = 'r.1.l'), clock_timestamp())$mk$))))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.l": 0, + "r.1.w": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1], + ["r.0.1", 1, "r.1.w", 1] + ] + }, + { + "id": "gen-0154", + "signature": "S(S(M,M),S(M,M))", + "depth": 2, + "class": "live", + "reason": null, + "dsl": "df.seq(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0154', 'r.0.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0154' AND node_path = 'r.0.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0154', 'r.0.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0154' AND node_path = 'r.0.1'), clock_timestamp())$mk$)), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0154', 'r.1.0', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0154' AND node_path = 'r.1.0'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('gen-0154', 'r.1.1', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'gen-0154' AND node_path = 'r.1.1'), clock_timestamp())$mk$)))", + "expected": { + "r.0.0": 1, + "r.0.1": 1, + "r.1.0": 1, + "r.1.1": 1 + }, + "order": [ + ["r.0.0", 1, "r.0.1", 1], + ["r.0.1", 1, "r.1.0", 1], + ["r.1.0", 1, "r.1.1", 1] + ] + } + ] +} diff --git a/tests/e2e/generated/meta-manifest.json b/tests/e2e/generated/meta-manifest.json new file mode 100644 index 00000000..10a5a9b8 --- /dev/null +++ b/tests/e2e/generated/meta-manifest.json @@ -0,0 +1,81 @@ +{ + "version": 1, + "generator": "pg_durable_matrix_gen", + "kind": "metamorphic", + "relation_count": 7, + "relations": [ + { + "id": "meta-0001", + "name": "seq-assoc", + "rationale": "Sequence is associative: re-grouping nested seq does not change the order or the set of side effects.", + "dsl_a": "df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0001-a', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0001-a' AND node_path = 'a'), clock_timestamp())$mk$), df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0001-a', 'b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0001-a' AND node_path = 'b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0001-a', 'c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0001-a' AND node_path = 'c'), clock_timestamp())$mk$)))", + "dsl_b": "df.seq(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0001-b', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0001-b' AND node_path = 'a'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0001-b', 'b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0001-b' AND node_path = 'b'), clock_timestamp())$mk$)), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0001-b', 'c', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0001-b' AND node_path = 'c'), clock_timestamp())$mk$))", + "expected": { + "a": 1, + "b": 1, + "c": 1 + } + }, + { + "id": "meta-0002", + "name": "if-true", + "rationale": "A constant-true if reduces to its then-branch; the else-branch never runs.", + "dsl_a": "df.if($c$SELECT true$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0002-a', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0002-a' AND node_path = 'a'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0002-a', 'b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0002-a' AND node_path = 'b'), clock_timestamp())$mk$))", + "dsl_b": "df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0002-b', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0002-b' AND node_path = 'a'), clock_timestamp())$mk$)", + "expected": { + "a": 1 + } + }, + { + "id": "meta-0003", + "name": "if-false", + "rationale": "A constant-false if reduces to its else-branch; the then-branch never runs.", + "dsl_a": "df.if($c$SELECT false$c$, df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0003-a', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0003-a' AND node_path = 'a'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0003-a', 'b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0003-a' AND node_path = 'b'), clock_timestamp())$mk$))", + "dsl_b": "df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0003-b', 'b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0003-b' AND node_path = 'b'), clock_timestamp())$mk$)", + "expected": { + "b": 1 + } + }, + { + "id": "meta-0004", + "name": "join-comm", + "rationale": "Parallel join is commutative: swapping branches yields the same multiset of side effects.", + "dsl_a": "df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0004-a', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0004-a' AND node_path = 'a'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0004-a', 'b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0004-a' AND node_path = 'b'), clock_timestamp())$mk$))", + "dsl_b": "df.join(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0004-b', 'b', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0004-b' AND node_path = 'b'), clock_timestamp())$mk$), df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0004-b', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0004-b' AND node_path = 'a'), clock_timestamp())$mk$))", + "expected": { + "a": 1, + "b": 1 + } + }, + { + "id": "meta-0005", + "name": "race-winner", + "rationale": "A race whose only other branch is a long sleep reduces to its deterministic winner.", + "dsl_a": "df.race(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0005-a', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0005-a' AND node_path = 'a'), clock_timestamp())$mk$), df.sleep(30))", + "dsl_b": "df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0005-b', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0005-b' AND node_path = 'a'), clock_timestamp())$mk$)", + "expected": { + "a": 1 + } + }, + { + "id": "meta-0006", + "name": "do-while-once", + "rationale": "A do-while loop whose condition is already false after the first body run reduces to its body.", + "dsl_a": "df.loop(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0006-a', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0006-a' AND node_path = 'a'), clock_timestamp())$mk$), $c$SELECT COUNT(*) < 1 FROM df_gen_trace WHERE shape_id = 'meta-0006-a' AND node_path = 'a'$c$)", + "dsl_b": "df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0006-b', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0006-b' AND node_path = 'a'), clock_timestamp())$mk$)", + "expected": { + "a": 1 + } + }, + { + "id": "meta-0007", + "name": "loop-break-once", + "rationale": "A loop that breaks immediately after its first body run reduces to its body.", + "dsl_a": "df.loop(df.seq(df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0007-a', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0007-a' AND node_path = 'a'), clock_timestamp())$mk$), df.if($c$SELECT (SELECT COUNT(*) FROM df_gen_trace WHERE shape_id = 'meta-0007-a' AND node_path = 'a') >= 1$c$, df.break(), $c$SELECT 1$c$)), $c$SELECT true$c$)", + "dsl_b": "df.sql($mk$INSERT INTO df_gen_trace (shape_id, node_path, iteration, wall_clock) VALUES ('meta-0007-b', 'a', (SELECT COALESCE(MAX(iteration), 0) + 1 FROM df_gen_trace WHERE shape_id = 'meta-0007-b' AND node_path = 'a'), clock_timestamp())$mk$)", + "expected": { + "a": 1 + } + } + ] +} diff --git a/tests/e2e/sql/24_structural_invariants.sql b/tests/e2e/sql/24_structural_invariants.sql new file mode 100644 index 00000000..e4838511 --- /dev/null +++ b/tests/e2e/sql/24_structural_invariants.sql @@ -0,0 +1,201 @@ +-- Copyright (c) Microsoft Corporation. +-- Licensed under the PostgreSQL License. + +-- Tests: df.assert_structural_invariants() structural-invariant oracle (#232, Phase 1). +-- Covers the happy path (sequence/IF/JOIN/loop all pass), the fail_on_violation +-- assertion form (no raise on a clean instance), and the missing-instance path +-- (instance_found=false, and a raise when fail_on_violation => true). +SET SESSION AUTHORIZATION df_e2e_user; + +-- === Test 1: happy-path sequence — every invariant passes === + +DROP TABLE IF EXISTS test_inv_log; +CREATE TABLE test_inv_log (id SERIAL, val INT); + +CREATE TEMP TABLE _test_state (instance_id TEXT); + +INSERT INTO _test_state SELECT df.start( + $$INSERT INTO test_inv_log (val) VALUES (1)$$ + ~> $$INSERT INTO test_inv_log (val) VALUES (2)$$, + 'test-inv-sequence' +); + +DO $$ +DECLARE + inst_id TEXT; + status TEXT; + all_passed BOOLEAN; + viol_count INT; +BEGIN + SELECT instance_id INTO inst_id FROM _test_state; + SELECT df.wait_for_completion(inst_id, 30) INTO status; + + IF status != 'completed' THEN + RAISE EXCEPTION 'TEST FAILED [sequence]: status = %', status; + END IF; + + SELECT bool_and(passed), count(*) FILTER (WHERE NOT passed) + INTO all_passed, viol_count + FROM df.assert_structural_invariants(inst_id); + + IF NOT all_passed OR viol_count > 0 THEN + RAISE EXCEPTION 'TEST FAILED [sequence]: % violation(s) on a clean instance', viol_count; + END IF; + + -- Assertion form must NOT raise on a clean instance. + PERFORM count(*) FROM df.assert_structural_invariants(inst_id, true); + + RAISE NOTICE 'PASSED: sequence invariants'; +END $$; + +DROP TABLE _test_state; +DROP TABLE test_inv_log; + +-- === Test 2: IF — taken branch completed, untaken branch pending === + +CREATE TEMP TABLE _test_state (instance_id TEXT); + +INSERT INTO _test_state SELECT df.start( + df.if('SELECT true', 'SELECT 1', 'SELECT 2'), + 'test-inv-if' +); + +DO $$ +DECLARE + inst_id TEXT; + status TEXT; + all_passed BOOLEAN; + untaken_passed BOOLEAN; +BEGIN + SELECT instance_id INTO inst_id FROM _test_state; + SELECT df.wait_for_completion(inst_id, 30) INTO status; + + IF status != 'completed' THEN + RAISE EXCEPTION 'TEST FAILED [if]: status = %', status; + END IF; + + SELECT bool_and(passed) INTO all_passed + FROM df.assert_structural_invariants(inst_id); + + SELECT bool_and(passed) INTO untaken_passed + FROM df.assert_structural_invariants(inst_id) + WHERE invariant = 'untaken_if_branch_pending'; + + IF NOT all_passed THEN + RAISE EXCEPTION 'TEST FAILED [if]: unexpected invariant violation'; + END IF; + + IF untaken_passed IS NOT TRUE THEN + RAISE EXCEPTION 'TEST FAILED [if]: untaken_if_branch_pending did not pass'; + END IF; + + RAISE NOTICE 'PASSED: IF invariants'; +END $$; + +DROP TABLE _test_state; + +-- === Test 3: JOIN — all parallel branches completed === + +CREATE TEMP TABLE _test_state (instance_id TEXT); + +INSERT INTO _test_state SELECT df.start( + df.join('SELECT 1', 'SELECT 2'), + 'test-inv-join' +); + +DO $$ +DECLARE + inst_id TEXT; + status TEXT; + branches_passed BOOLEAN; +BEGIN + SELECT instance_id INTO inst_id FROM _test_state; + SELECT df.wait_for_completion(inst_id, 30) INTO status; + + IF status != 'completed' THEN + RAISE EXCEPTION 'TEST FAILED [join]: status = %', status; + END IF; + + SELECT bool_and(passed) INTO branches_passed + FROM df.assert_structural_invariants(inst_id); + + IF branches_passed IS NOT TRUE THEN + RAISE EXCEPTION 'TEST FAILED [join]: unexpected invariant violation'; + END IF; + + RAISE NOTICE 'PASSED: JOIN invariants'; +END $$; + +DROP TABLE _test_state; + +-- === Test 4: terminating while-loop — relaxed loop-body checks still pass === + +DROP TABLE IF EXISTS test_inv_counter; +CREATE TABLE test_inv_counter (n INT); +INSERT INTO test_inv_counter VALUES (0); + +CREATE TEMP TABLE _test_state (instance_id TEXT); + +INSERT INTO _test_state SELECT df.start( + df.loop( + $$UPDATE test_inv_counter SET n = n + 1$$, + $$SELECT (SELECT n FROM test_inv_counter) < 3$$ + ), + 'test-inv-loop' +); + +DO $$ +DECLARE + inst_id TEXT; + status TEXT; + loop_passed BOOLEAN; +BEGIN + SELECT instance_id INTO inst_id FROM _test_state; + SELECT df.wait_for_completion(inst_id, 30) INTO status; + + IF status != 'completed' THEN + RAISE EXCEPTION 'TEST FAILED [loop]: status = %', status; + END IF; + + SELECT bool_and(passed) INTO loop_passed + FROM df.assert_structural_invariants(inst_id); + + IF loop_passed IS NOT TRUE THEN + RAISE EXCEPTION 'TEST FAILED [loop]: relaxed loop checks reported a false violation'; + END IF; + + RAISE NOTICE 'PASSED: loop invariants (relaxed)'; +END $$; + +DROP TABLE _test_state; +DROP TABLE test_inv_counter; + +-- === Test 5: missing / not-visible instance — instance_found=false and raises === + +DO $$ +DECLARE + found_passed BOOLEAN; + inv_name TEXT; +BEGIN + SELECT passed, invariant INTO found_passed, inv_name + FROM df.assert_structural_invariants('no-such-instance-0000') + LIMIT 1; + + IF inv_name IS DISTINCT FROM 'instance_found' OR found_passed IS DISTINCT FROM false THEN + RAISE EXCEPTION 'TEST FAILED [missing]: expected instance_found=false, got %/%', inv_name, found_passed; + END IF; + + -- fail_on_violation => true must raise for a missing instance. + BEGIN + PERFORM count(*) FROM df.assert_structural_invariants('no-such-instance-0000', true); + RAISE EXCEPTION 'TEST FAILED [missing]: fail_on_violation did not raise'; + EXCEPTION WHEN OTHERS THEN + IF SQLERRM LIKE 'TEST FAILED%' THEN + RAISE; + END IF; + RAISE NOTICE 'PASSED: missing instance raises with fail_on_violation (%)' , SQLERRM; + END; +END $$; + +RESET SESSION AUTHORIZATION; +SELECT 'TEST PASSED' AS result;