From 54db91208a0e44bd46a7ca7401b322e73d4100de Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 18:37:58 +0000 Subject: [PATCH 1/2] D-GLOBAL-WHERE P0: fail parity gate on missing comparisons; EXPLAIN after cold Review fixes (Codex P2 x2): - compare.py exits 1 on any missing comparison in either direction and on a thread set other than {1,4}; DuckDB's W8 (W2 before DELETE) is checked against ours W2. Verified: complete results exit 0, empty ours.json and a threads=1-only run exit 1, a corrupted W8 answer exits 1. - bench_duckdb.py collects EXPLAIN ANALYZE after measurement on a separate connection, so it no longer pre-warms the cold figure (and the engine-time profiling mode cannot turn the plan into JSON). The 20 affected records (dr256_medium, multi_skewed, t=1) were re-measured and spliced; answers identical, no cold verdict changed, one warm verdict WIN -> PARITY. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XAqKMNZqKXSaeb9g2ypywF --- .../2026-11-03-d-global-where/REPORT.md | 6 +- .../duckdb/bench_duckdb.py | 17 +- .../duckdb/compare.py | 55 +++- .../results/duckdb.json | 284 +++++++++--------- .../results/explain/dr256_medium_W2.txt | 2 +- .../results/explain/dr256_medium_W3.txt | 2 +- .../results/explain/dr256_medium_W4.txt | 2 +- .../results/explain/dr256_medium_W5a.txt | 2 +- .../results/explain/dr256_medium_W7.txt | 2 +- .../results/explain/dr256_medium_W7i.txt | 2 +- .../results/explain/multi_skewed_W1.txt | 2 +- .../results/explain/multi_skewed_W2.txt | 2 +- .../results/explain/multi_skewed_W3.txt | 2 +- .../results/explain/multi_skewed_W4.txt | 8 +- .../results/explain/multi_skewed_W5a.txt | 2 +- .../results/explain/multi_skewed_W6.txt | 2 +- .../results/explain/multi_skewed_W7.txt | 2 +- .../results/explain/multi_skewed_W7i.txt | 2 +- 18 files changed, 223 insertions(+), 173 deletions(-) diff --git a/.claude/probes/2026-11-03-d-global-where/REPORT.md b/.claude/probes/2026-11-03-d-global-where/REPORT.md index 5a94fe345..10e3c6e81 100644 --- a/.claude/probes/2026-11-03-d-global-where/REPORT.md +++ b/.claude/probes/2026-11-03-d-global-where/REPORT.md @@ -96,10 +96,12 @@ Rows where DuckDB's own engine time is far above its fixed cost. Warm, µs, Duck | dataset | workload | ours (best arm) | DuckDB t=1 | DuckDB t=4 | DuckDB intermediate | |---|---|---|---|---|---| | multi_dense (503,921 edges) | W4 3-hop distinct | 130.7 (`front`) | 1,500,724 | 418,289 | — | -| multi_skewed (32,648 edges) | W4 3-hop distinct | 89.2 (`front`) | 57,066 | 56,569 | 3,726,779 join rows for a 1,164 answer (`explain/multi_skewed_W4.txt`) | +| multi_skewed (32,648 edges) | W4 3-hop distinct | 89.2 (`front`) | 53,681 | 56,569 | 3,726,779 join rows for a 1,164 answer (`explain/multi_skewed_W4.txt`) | | multi_dense | W3 2-hop EXISTS | 7.7 (`csr`) | 29,097 | 17,989 | — | | multi_dense | W6 bag 2-paths (3,382,676) | 119.3 | 22,726 | 11,379 | — | -| multi_skewed | W3 | 7.6 (`csr`) | 6,767 | 6,391 | — | +| multi_skewed | W3 | 7.6 (`csr`) | 5,515 | 6,391 | — | + +> **Correction (review, 2026-11-03).** In the first run, `EXPLAIN ANALYZE` executed each query on the measuring connection *before* the cold measurement for `dr256_medium` and `multi_skewed` at t=1, so those 20 cold figures were taken on a warmed query. `bench_duckdb.py` now collects plans after measurement on a separate connection; those 20 records were re-measured and spliced into `duckdb.json` (answers identical). No cold verdict changed; one warm wall verdict moved (multi_skewed W7i `route_each`, WIN → PARITY). The two t=1 figures above are from the re-run. `compare.py` now also fails on any missing comparison in either direction, and checks DuckDB's W8 (W2 before the DELETE) against ours W2. Here the frontier masks never materialize the join, while DuckDB builds and aggregates the intermediate. This is the intended win: work the query never needed. diff --git a/.claude/probes/2026-11-03-d-global-where/duckdb/bench_duckdb.py b/.claude/probes/2026-11-03-d-global-where/duckdb/bench_duckdb.py index 7ac315f0e..77da9abbf 100644 --- a/.claude/probes/2026-11-03-d-global-where/duckdb/bench_duckdb.py +++ b/.claude/probes/2026-11-03-d-global-where/duckdb/bench_duckdb.py @@ -26,7 +26,8 @@ 5. floor_us = median of RUNS `EXECUTE f` for `PREPARE f AS SELECT 1` (python call + fetch overhead, measured on the same connection). 6. mem_bytes = sum(memory_usage_bytes) FROM duckdb_memory() after the workload. - 7. T=1 only, datasets dr256_medium and multi_skewed: `EXPLAIN ANALYZE ` + 7. T=1 only, datasets dr256_medium and multi_skewed, after the measurement: + `EXPLAIN ANALYZE ` text goes to $OUT/explain/_.txt. 8. W8: warm W2, time `DELETE FROM edges WHERE (src, dst) IN (SELECT src, dst FROM del)` (update_us), warm W2 again. Record "W8" (before delete, with @@ -187,13 +188,21 @@ def run_one(out, ds, T, wl, runs, tmpdir): records = [] if wl != "W8": sql = SQL[wl] + vals = measure(con, sql, runs, tmpdir) + fl = floor_us(con, runs) + # Plan collection runs AFTER the cold and warm measurement, on its own + # fresh connection: EXPLAIN ANALYZE's execution cannot pre-warm the + # cold figure, and the engine-time profiling mode set on `con` cannot + # turn the plan text into JSON. if T == 1 and ds in EXPLAIN_DATASETS: - txt = con.execute(f"EXPLAIN ANALYZE {sql}").fetchall()[0][1] + econ = duckdb.connect(":memory:") + econ.execute(f"SET threads={T}") + load(econ, out, ds) + txt = econ.execute(f"EXPLAIN ANALYZE {sql}").fetchall()[0][1] + econ.close() os.makedirs(os.path.join(out, "explain"), exist_ok=True) with open(os.path.join(out, "explain", f"{ds}_{wl}.txt"), "w") as f: f.write(f"-- {sql}\n{txt}\n") - vals = measure(con, sql, runs, tmpdir) - fl = floor_us(con, runs) records.append(rec(ds, T, wl, vals, fl, None, mem_bytes(con), load_us)) else: sql = SQL["W2"] diff --git a/.claude/probes/2026-11-03-d-global-where/duckdb/compare.py b/.claude/probes/2026-11-03-d-global-where/duckdb/compare.py index 13bb9dfc1..b550b59ea 100644 --- a/.claude/probes/2026-11-03-d-global-where/duckdb/compare.py +++ b/.claude/probes/2026-11-03-d-global-where/duckdb/compare.py @@ -7,7 +7,9 @@ writes compare.md, prints it) Exit status 1 if any answer disagrees (every ours arm vs DuckDB at both thread -counts), with the mismatching rows printed. Exit 2 on unreadable input. +counts) or any comparison is missing in either direction (including a thread +count other than 1 and 4). DuckDB's W8 (W2 before the DELETE) is checked +against ours W2. Exit 2 on unreadable input. All times are microseconds. Ratios are DuckDB / ours, so > 1 means ours is faster. Verdict thresholds on the ratio: WIN > 1.10, PARITY 0.90..1.10, @@ -25,6 +27,8 @@ import sys from collections import defaultdict +EXPECTED_THREADS = [1, 4] + def load(path): try: @@ -89,21 +93,50 @@ def main(argv): summary = defaultdict(lambda: defaultdict(lambda: defaultdict(int))) # summary[(threads, kind)][workload][verdict] - keys = sorted({(r["dataset"], r["workload"]) for r in dd}, + # Coverage is checked in both directions and any gap fails the gate: + # an incomplete run must never print "All compared answers agree". + missing = [] + if threads != EXPECTED_THREADS: + missing.append(f"duckdb thread counts {threads}, expected {EXPECTED_THREADS}") + ours_keys = set(ours_by) + dd_keys = {(r["dataset"], r["workload"]) for r in dd} + # DuckDB's W8 is W2 measured on the same connection before the DELETE; ours + # has no separate record for it, so its answer is checked against ours W2. + for ds, wl in sorted(ours_keys - dd_keys): + missing.append(f"no duckdb record for {ds} {wl}") + for ds, wl in sorted(dd_keys): + src = (ds, "W2") if wl == "W8" else (ds, wl) + if src not in ours_by: + missing.append(f"no ours record for {ds} {wl}") + for T in EXPECTED_THREADS: + if (ds, T, wl) not in dd_by: + missing.append(f"no duckdb record for {ds} threads={T} {wl}") + for ds, wl in sorted(dd_keys): + if wl != "W8" or (ds, "W2") not in ours_by: + continue + for T in threads: + d = dd_by.get((ds, T, wl)) + if d is None: + continue + for a in ours_by[(ds, "W2")]: + if a.get("answer") is None: + missing.append(f"ours answer missing: {ds} W2 arm={a.get('arm')}") + elif a["answer"] != d["answer"]: + mismatches.append((ds, "W8(before delete) vs ours W2", a.get("arm"), T, a["answer"], d["answer"])) + + keys = sorted((k for k in dd_keys if k[1] != "W8"), key=lambda k: (k[0], wl_order(k[1]))) for ds, wl in keys: arms = ours_by.get((ds, wl)) if not arms: - notes.append(f"no ours record for {ds} {wl}") continue for T in threads: d = dd_by.get((ds, T, wl)) if d is None: - notes.append(f"no duckdb record for {ds} threads={T} {wl}") continue for a in arms: if a.get("answer") is None: - notes.append(f"ours answer missing: {ds} {wl} arm={a.get('arm')}") + missing.append(f"ours answer missing: {ds} {wl} arm={a.get('arm')}") elif a["answer"] != d["answer"]: mismatches.append((ds, wl, a.get("arm"), T, a["answer"], d["answer"])) floor = d.get("floor_us") or 0.0 @@ -157,24 +190,30 @@ def main(argv): if notes: lines += ["## Notes", ""] + [f"- {n}" for n in notes] + [""] + if missing: + lines += ["## MISSING COMPARISONS", ""] + [f"- {m}" for m in missing] + [""] if mismatches: lines += ["## ANSWER MISMATCHES", "", "| dataset | workload | ours arm | duckdb threads | ours answer | duckdb answer |", "|---|---|---|---|---|---|"] for m in mismatches: lines.append("| " + " | ".join(str(x) for x in m) + " |") lines.append("") - else: - lines += ["All compared answers agree.", ""] + elif not missing: + lines += ["All compared answers agree; coverage complete in both directions.", ""] text = "\n".join(lines) with open(os.path.join(out, "compare.md"), "w") as f: f.write(text) print(text) + if missing: + print(f"\nFAIL: {len(missing)} missing comparison(s)", file=sys.stderr) + for m in missing: + print("MISSING " + m, file=sys.stderr) if mismatches: print(f"\nFAIL: {len(mismatches)} answer mismatch(es)", file=sys.stderr) for m in mismatches: print("MISMATCH dataset=%s workload=%s arm=%s duckdb_threads=%s ours=%s duckdb=%s" % m, file=sys.stderr) return 1 - return 0 + return 1 if missing else 0 if __name__ == "__main__": diff --git a/.claude/probes/2026-11-03-d-global-where/results/duckdb.json b/.claude/probes/2026-11-03-d-global-where/results/duckdb.json index cda8ecd1d..9a69495bd 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/duckdb.json +++ b/.claude/probes/2026-11-03-d-global-where/results/duckdb.json @@ -304,150 +304,150 @@ "threads": 1, "workload": "W1", "answer": 3296, - "cold_us": 1279.411, - "warm_med_us": 633.392, - "warm_p10_us": 536.172, - "warm_p90_us": 852.405, - "engine_us": 749.27, - "floor_us": 82.886, + "cold_us": 5014.265, + "warm_med_us": 720.414, + "warm_p10_us": 532.599, + "warm_p90_us": 1223.667, + "engine_us": 1266.909, + "floor_us": 101.207, "update_us": null, "mem_bytes": 1908544, - "load_us": 40558.021 + "load_us": 51209.52 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W2", "answer": 17, - "cold_us": 4000.862, - "warm_med_us": 2144.481, - "warm_p10_us": 2033.991, - "warm_p90_us": 2808.174, - "engine_us": 3148.895, - "floor_us": 152.474, + "cold_us": 4125.554, + "warm_med_us": 2023.248, + "warm_p10_us": 1934.567, + "warm_p90_us": 2214.473, + "engine_us": 2282.192, + "floor_us": 82.538, "update_us": null, "mem_bytes": 1908544, - "load_us": 35811.691 + "load_us": 49644.154 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W3", "answer": 17, - "cold_us": 4257.76, - "warm_med_us": 2836.698, - "warm_p10_us": 2586.32, - "warm_p90_us": 3664.888, - "engine_us": 4144.852, - "floor_us": 137.683, + "cold_us": 4426.171, + "warm_med_us": 2590.35, + "warm_p10_us": 2474.284, + "warm_p90_us": 2928.967, + "engine_us": 2762.833, + "floor_us": 79.524, "update_us": null, "mem_bytes": 1908544, - "load_us": 37030.978 + "load_us": 34719.979 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W4", "answer": 77, - "cold_us": 5976.233, - "warm_med_us": 3355.698, - "warm_p10_us": 3098.288, - "warm_p90_us": 4351.197, - "engine_us": 3498.273, - "floor_us": 110.746, + "cold_us": 6223.959, + "warm_med_us": 3165.408, + "warm_p10_us": 3023.097, + "warm_p90_us": 4287.13, + "engine_us": 3338.845, + "floor_us": 85.729, "update_us": null, "mem_bytes": 1908544, - "load_us": 51674.454 + "load_us": 34546.071 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W5a", "answer": 109, - "cold_us": 3014.468, - "warm_med_us": 1747.831, - "warm_p10_us": 1623.008, - "warm_p90_us": 2111.902, - "engine_us": 2455.033, - "floor_us": 148.426, + "cold_us": 3831.938, + "warm_med_us": 1656.708, + "warm_p10_us": 1553.865, + "warm_p90_us": 2000.166, + "engine_us": 1814.9, + "floor_us": 78.864, "update_us": null, "mem_bytes": 1908544, - "load_us": 37431.79 + "load_us": 32151.768 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W6", "answer": 1088, - "cold_us": 2838.465, - "warm_med_us": 1676.874, - "warm_p10_us": 1593.898, - "warm_p90_us": 1782.374, - "engine_us": 1822.886, - "floor_us": 85.834, + "cold_us": 3158.167, + "warm_med_us": 1657.991, + "warm_p10_us": 1565.375, + "warm_p90_us": 1884.095, + "engine_us": 1964.129, + "floor_us": 100.961, "update_us": null, "mem_bytes": 1908544, - "load_us": 38241.569 + "load_us": 32811.673 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W7", "answer": 17, - "cold_us": 2846.298, - "warm_med_us": 2069.385, - "warm_p10_us": 1493.967, - "warm_p90_us": 2251.163, - "engine_us": 2298.205, - "floor_us": 138.049, + "cold_us": 2907.128, + "warm_med_us": 1482.847, + "warm_p10_us": 1403.965, + "warm_p90_us": 1717.631, + "engine_us": 1666.628, + "floor_us": 83.038, "update_us": null, "mem_bytes": 1908544, - "load_us": 42374.51 + "load_us": 31868.794 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W7i", "answer": 4, - "cold_us": 2487.319, - "warm_med_us": 1283.04, - "warm_p10_us": 1027.095, - "warm_p90_us": 1452.763, - "engine_us": 1182.234, - "floor_us": 106.78, + "cold_us": 2599.546, + "warm_med_us": 884.717, + "warm_p10_us": 849.875, + "warm_p90_us": 1262.048, + "engine_us": 982.624, + "floor_us": 79.615, "update_us": null, "mem_bytes": 1908544, - "load_us": 53909.894 + "load_us": 48613.453 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W8", "answer": 17, - "cold_us": 3984.111, - "warm_med_us": 2161.605, - "warm_p10_us": 2006.843, - "warm_p90_us": 2874.802, - "engine_us": 2197.58, - "floor_us": 84.332, - "update_us": 1409.109, + "cold_us": 3566.839, + "warm_med_us": 2005.863, + "warm_p10_us": 1948.459, + "warm_p90_us": 2111.879, + "engine_us": 2253.699, + "floor_us": 85.842, + "update_us": 1459.478, "mem_bytes": 2170688, - "load_us": 39719.336 + "load_us": 31668.09 }, { "dataset": "dr256_medium", "threads": 1, "workload": "W8_after", "answer": 17, - "cold_us": 2294.872, - "warm_med_us": 2231.789, - "warm_p10_us": 2115.634, - "warm_p90_us": 2952.464, - "engine_us": 2389.968, - "floor_us": 84.332, + "cold_us": 2067.538, + "warm_med_us": 2088.58, + "warm_p10_us": 2002.999, + "warm_p90_us": 2299.572, + "engine_us": 2417.425, + "floor_us": 85.842, "update_us": null, "mem_bytes": 2170688, - "load_us": 39719.336 + "load_us": 31668.09 }, { "dataset": "dr256_medium", @@ -3004,150 +3004,150 @@ "threads": 1, "workload": "W1", "answer": 3296, - "cold_us": 1222.44, - "warm_med_us": 724.577, - "warm_p10_us": 559.879, - "warm_p90_us": 819.621, - "engine_us": 791.956, - "floor_us": 131.709, + "cold_us": 1460.384, + "warm_med_us": 624.71, + "warm_p10_us": 547.599, + "warm_p90_us": 827.137, + "engine_us": 747.125, + "floor_us": 80.717, "update_us": null, "mem_bytes": 2441008, - "load_us": 76234.596 + "load_us": 47027.437 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W2", "answer": 100, - "cold_us": 4443.505, - "warm_med_us": 3134.968, - "warm_p10_us": 2750.886, - "warm_p90_us": 4076.798, - "engine_us": 4514.796, - "floor_us": 136.07, + "cold_us": 4827.763, + "warm_med_us": 2772.933, + "warm_p10_us": 2634.272, + "warm_p90_us": 3114.93, + "engine_us": 2853.399, + "floor_us": 83.656, "update_us": null, "mem_bytes": 2441008, - "load_us": 49061.622 + "load_us": 45764.55 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W3", "answer": 209, - "cold_us": 10282.077, - "warm_med_us": 6951.048, - "warm_p10_us": 5703.641, - "warm_p90_us": 8204.517, - "engine_us": 8045.757, - "floor_us": 183.796, + "cold_us": 7532.693, + "warm_med_us": 5604.793, + "warm_p10_us": 5337.149, + "warm_p90_us": 6112.799, + "engine_us": 5922.842, + "floor_us": 89.35, "update_us": null, "mem_bytes": 2441008, - "load_us": 59294.607 + "load_us": 43584.346 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W4", "answer": 1164, - "cold_us": 87807.314, - "warm_med_us": 57161.812, - "warm_p10_us": 52935.725, - "warm_p90_us": 79569.829, - "engine_us": 56044.293, - "floor_us": 96.288, + "cold_us": 73722.485, + "warm_med_us": 53769.895, + "warm_p10_us": 51881.007, + "warm_p90_us": 64805.509, + "engine_us": 54907.815, + "floor_us": 88.985, "update_us": null, "mem_bytes": 2441008, - "load_us": 70594.018 + "load_us": 43922.704 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W5a", "answer": 636, - "cold_us": 5093.623, - "warm_med_us": 1828.63, - "warm_p10_us": 1744.309, - "warm_p90_us": 2633.849, - "engine_us": 1960.175, - "floor_us": 79.345, + "cold_us": 4417.97, + "warm_med_us": 1800.846, + "warm_p10_us": 1707.445, + "warm_p90_us": 2032.724, + "engine_us": 2011.662, + "floor_us": 80.752, "update_us": null, "mem_bytes": 2441008, - "load_us": 79972.705 + "load_us": 54922.798 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W6", "answer": 428569, - "cold_us": 5085.961, - "warm_med_us": 5558.173, - "warm_p10_us": 3967.832, - "warm_p90_us": 5871.947, - "engine_us": 5977.194, - "floor_us": 128.988, + "cold_us": 5228.468, + "warm_med_us": 3898.721, + "warm_p10_us": 3770.13, + "warm_p90_us": 4100.952, + "engine_us": 4101.441, + "floor_us": 78.809, "update_us": null, "mem_bytes": 2441008, - "load_us": 45669.369 + "load_us": 43426.755 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W7", "answer": 100, - "cold_us": 3237.112, - "warm_med_us": 1806.613, - "warm_p10_us": 1568.846, - "warm_p90_us": 2427.459, - "engine_us": 2086.532, - "floor_us": 96.82, + "cold_us": 4381.767, + "warm_med_us": 1635.853, + "warm_p10_us": 1552.86, + "warm_p90_us": 1851.942, + "engine_us": 1650.2, + "floor_us": 81.741, "update_us": null, "mem_bytes": 2441008, - "load_us": 51854.294 + "load_us": 47664.179 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W7i", "answer": 77, - "cold_us": 2446.581, - "warm_med_us": 1023.295, - "warm_p10_us": 903.43, - "warm_p90_us": 1236.427, - "engine_us": 1193.14, - "floor_us": 131.582, + "cold_us": 1866.103, + "warm_med_us": 905.693, + "warm_p10_us": 859.035, + "warm_p90_us": 981.475, + "engine_us": 958.761, + "floor_us": 76.991, "update_us": null, "mem_bytes": 2441008, - "load_us": 51200.513 + "load_us": 43598.442 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W8", "answer": 100, - "cold_us": 5088.035, - "warm_med_us": 2853.558, - "warm_p10_us": 2662.874, - "warm_p90_us": 4012.033, - "engine_us": 2945.035, - "floor_us": 81.664, - "update_us": 1718.07, + "cold_us": 4687.933, + "warm_med_us": 2722.566, + "warm_p10_us": 2604.707, + "warm_p90_us": 3159.731, + "engine_us": 2839.388, + "floor_us": 83.46, + "update_us": 1588.997, "mem_bytes": 2965296, - "load_us": 62955.698 + "load_us": 47053.187 }, { "dataset": "multi_skewed", "threads": 1, "workload": "W8_after", "answer": 99, - "cold_us": 3004.059, - "warm_med_us": 2956.967, - "warm_p10_us": 2778.441, - "warm_p90_us": 4022.714, - "engine_us": 3394.837, - "floor_us": 81.664, + "cold_us": 3374.398, + "warm_med_us": 2941.469, + "warm_p10_us": 2779.405, + "warm_p90_us": 3522.915, + "engine_us": 3065.678, + "floor_us": 83.46, "update_us": null, "mem_bytes": 2965296, - "load_us": 62955.698 + "load_us": 47053.187 }, { "dataset": "multi_skewed", diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W2.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W2.txt index 6d057958a..64325d740 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W2.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W2.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM nodes n WHERE n.a < 50 AND EXISTS (SELECT 1 FROM edges e JOIN nodes m ON m.id = e.dst WHERE e.src = n.id AND m.b < 300) ╭─ Summary ───────────╮ -│ Total Time: 0.0026s │ +│ Total Time: 0.0027s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ────────────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W3.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W3.txt index 9c32dc025..28bf2f279 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W3.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W3.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM nodes n WHERE n.a < 50 AND EXISTS (SELECT 1 FROM edges e1 JOIN edges e2 ON e2.src = e1.dst JOIN nodes m ON m.id = e2.dst WHERE e1.src = n.id AND m.b < 300) ╭─ Summary ───────────╮ -│ Total Time: 0.0030s │ +│ Total Time: 0.0029s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ────────────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W4.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W4.txt index e06133be4..64128456f 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W4.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W4.txt @@ -1,6 +1,6 @@ -- SELECT count(DISTINCT e3.dst) FROM nodes n JOIN edges e1 ON e1.src = n.id JOIN edges e2 ON e2.src = e1.dst JOIN edges e3 ON e3.src = e2.dst JOIN nodes m ON m.id = e3.dst WHERE n.a < 50 AND m.b < 300 ╭─ Summary ───────────╮ -│ Total Time: 0.0070s │ +│ Total Time: 0.0047s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ──────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W5a.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W5a.txt index 5904e02bc..ff76b8a40 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W5a.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W5a.txt @@ -1,6 +1,6 @@ -- SELECT count(DISTINCT e.dst) FROM edges e JOIN nodes n ON n.id = e.src WHERE n.a < 50 ╭─ Summary ───────────╮ -│ Total Time: 0.0033s │ +│ Total Time: 0.0026s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ───────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W7.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W7.txt index 23ab8cfc6..a62477049 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W7.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W7.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM selA s WHERE EXISTS (SELECT 1 FROM edges e JOIN selB t ON t.id = e.dst WHERE e.src = s.id) ╭─ Summary ───────────╮ -│ Total Time: 0.0021s │ +│ Total Time: 0.0019s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W7i.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W7i.txt index 49721de12..00a8a91af 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W7i.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/dr256_medium_W7i.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM selA JOIN selB USING (id) ╭─ Summary ───────────╮ -│ Total Time: 0.0016s │ +│ Total Time: 0.0011s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W1.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W1.txt index c24824c5c..9be1843fc 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W1.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W1.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM nodes WHERE a < 50 ╭─ Summary ───────────╮ -│ Total Time: 0.0008s │ +│ Total Time: 0.0007s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ─────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W2.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W2.txt index f584e881e..321f90efd 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W2.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W2.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM nodes n WHERE n.a < 50 AND EXISTS (SELECT 1 FROM edges e JOIN nodes m ON m.id = e.dst WHERE e.src = n.id AND m.b < 300) ╭─ Summary ───────────╮ -│ Total Time: 0.0037s │ +│ Total Time: 0.0033s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ────────────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W3.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W3.txt index 5a84cd3bc..944a31c19 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W3.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W3.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM nodes n WHERE n.a < 50 AND EXISTS (SELECT 1 FROM edges e1 JOIN edges e2 ON e2.src = e1.dst JOIN nodes m ON m.id = e2.dst WHERE e1.src = n.id AND m.b < 300) ╭─ Summary ───────────╮ -│ Total Time: 0.0075s │ +│ Total Time: 0.0064s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ──────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W4.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W4.txt index a94f4b388..40a4f33e9 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W4.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W4.txt @@ -1,6 +1,6 @@ -- SELECT count(DISTINCT e3.dst) FROM nodes n JOIN edges e1 ON e1.src = n.id JOIN edges e2 ON e2.src = e1.dst JOIN edges e3 ON e3.src = e2.dst JOIN nodes m ON m.id = e3.dst WHERE n.a < 50 AND m.b < 300 ╭─ Summary ───────────╮ -│ Total Time: 0.0845s │ +│ Total Time: 0.0601s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ──────────────╮ │ Aggregates: count_star() │ @@ -12,7 +12,7 @@ ╰──────────────────┬─────────────────╯ ╭─ Hash Group By ──┴─────────────────╮ │ Groups: #0 │ -│ 1,164 rows 30.0ms │ +│ 1,164 rows 20.0ms │ ╰──────────────────┒┎────────────────╯ ╭─ Projection ─────┚┖────────────────╮ │ Projections: dst │ @@ -25,13 +25,13 @@ ╭─ Hash Join ──────┚┖────────────────╮ │ Join Type: INNER │ │ Conditions: src = dst │ -│ 3,726,779 rows 30.0ms │ +│ 3,726,779 rows 20.0ms │ ╰──────────────────┬─────────────────╯ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ ╭─ Hash Join ──────┴─────────────────╮ ╭─ Hash Join ────┴───────────────╮ │ Join Type: INNER │ │ Join Type: INNER │ │ Conditions: dst = src │ │ Conditions: src = id │ -│ 983,021 rows 10.0ms │ │ 1,250 rows 0µs │ +│ 983,021 rows 0µs │ │ 1,250 rows 0µs │ ╰──────────────────┬─────────────────╯ ╰────────────────┬───────────────╯ ├─────────────────────────────────────╮ ├─────────────────────────────────────╮ ╭─ Table Scan ─────┴─────────────────╮ ╭─ Hash Join ────┴───────────────╮ ╭─ Table Scan ───┴───────────────╮ ╭─ Filter ─────────┴──────────────────╮ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W5a.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W5a.txt index 41ebc737c..31df56a75 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W5a.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W5a.txt @@ -1,6 +1,6 @@ -- SELECT count(DISTINCT e.dst) FROM edges e JOIN nodes n ON n.id = e.src WHERE n.a < 50 ╭─ Summary ───────────╮ -│ Total Time: 0.0048s │ +│ Total Time: 0.0033s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ──────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W6.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W6.txt index 48634a7d1..f96f25ddc 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W6.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W6.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM edges e1 JOIN edges e2 ON e2.src = e1.dst JOIN nodes n ON n.id = e1.src WHERE n.a < 50 ╭─ Summary ───────────╮ -│ Total Time: 0.0045s │ +│ Total Time: 0.0050s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ──────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W7.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W7.txt index 3cc2046d7..2e481ac62 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W7.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W7.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM selA s WHERE EXISTS (SELECT 1 FROM edges e JOIN selB t ON t.id = e.dst WHERE e.src = s.id) ╭─ Summary ───────────╮ -│ Total Time: 0.0025s │ +│ Total Time: 0.0020s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ────────────────────╮ │ Aggregates: count_star() │ diff --git a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W7i.txt b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W7i.txt index 177e28d8d..0e3aeaeac 100644 --- a/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W7i.txt +++ b/.claude/probes/2026-11-03-d-global-where/results/explain/multi_skewed_W7i.txt @@ -1,6 +1,6 @@ -- SELECT count(*) FROM selA JOIN selB USING (id) ╭─ Summary ───────────╮ -│ Total Time: 0.0015s │ +│ Total Time: 0.0011s │ ╰─────────────────────╯ ╭─ Ungrouped Aggregate ────────────────────╮ │ Aggregates: count_star() │ From 90abc8a184aae08c14c53df087bdc66111841f88 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 20:58:35 +0000 Subject: [PATCH 2/2] D-GLOBAL-WHERE P0 follow-up: recompute parity coverage (360 answer comparisons) With W8 (W2 before DELETE) now checked against ours W2, the gate compares 360 answers, not 336. REPORT.md and the P0 board entry updated. Verified on this tree: complete data exit 0; empty ours, empty duckdb, a dropped ours record, missing thread-1 and missing thread-4 coverage, and a corrupted W8 answer each exit 1. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XAqKMNZqKXSaeb9g2ypywF --- .claude/board/entries/2026-11-03-d-global-where-p0.md | 2 +- .claude/probes/2026-11-03-d-global-where/REPORT.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude/board/entries/2026-11-03-d-global-where-p0.md b/.claude/board/entries/2026-11-03-d-global-where-p0.md index e1b587d1f..e6a382597 100644 --- a/.claude/board/entries/2026-11-03-d-global-where-p0.md +++ b/.claude/board/entries/2026-11-03-d-global-where-p0.md @@ -11,7 +11,7 @@ - Folds allocate 0 bytes. - `where_dr3::LocalSelection` projects bit-identically for two methods. Its `SchemaChanged` on a second method is a coupling, not a correctness need; Scope refuses on its own. -**DuckDB 2.0.0 gate:** 336/336 answers agree; 8 workloads × DR64 / DR256 / multi-tile (16×DR256) × sparse / medium / dense / skewed, at threads 1 and 4. +**DuckDB 2.0.0 gate:** 360/360 answers agree (336 + 24 W8 before-delete checks, added by the follow-up fix); 8 workloads × DR64 / DR256 / multi-tile (16×DR256) × sparse / medium / dense / skewed, at threads 1 and 4. - Warm best arm: 12/0/0 W/P/L for every workload. - But DuckDB's fixed per-query cost (~600 µs; 703 µs at 65,536 rows against 12,106 µs at 10M) dominates most warm rows. Those are not claimed as algorithmic wins. - Work-avoided rows: W4 3-hop on `multi_dense` is 131 µs against 1,500,724 µs; on `multi_skewed` 89 µs against 57,066 µs, where DuckDB builds 3,726,779 join rows for a 1,164 answer. diff --git a/.claude/probes/2026-11-03-d-global-where/REPORT.md b/.claude/probes/2026-11-03-d-global-where/REPORT.md index 10e3c6e81..2d582f626 100644 --- a/.claude/probes/2026-11-03-d-global-where/REPORT.md +++ b/.claude/probes/2026-11-03-d-global-where/REPORT.md @@ -2,7 +2,7 @@ **Status:** MEASURED, lab only. - Falsifier `falsify`: 21/21 checks. Two guards were disabled to prove they carry weight: F4 and F6 both go red. -- Benchmark against DuckDB 2.0.0: all 336 answer comparisons agree (168 of ours × threads 1 and 4). +- Benchmark against DuckDB 2.0.0: all 360 answer comparisons agree: 168 of ours × threads 1 and 4, plus DuckDB's 12 W8 before-delete answers × 2 threads checked against ours W2. `compare.py` fails on any missing comparison. - No production change. dt64, Quack, mask-risc and ndarray are used unchanged. No Gather, no new identity table, no witness store, no per-object allocation. Machine: 4 vCPU Intel Xeon @ 2.10 GHz, rustc 1.99.0 release, DuckDB 2.0.0 (Python). @@ -73,7 +73,7 @@ Fixture: ## 3. DuckDB parity gate **Gate:** parity or better on clearly identified workloads with exact semantic equivalence; every losing workload is recorded with its cause. -- Answers: 336/336 agree. +- Answers: 360/360 agree (336 + 24 W8 before-delete checks against ours W2; coverage enforced in both directions). - DuckDB runs with its normal optimizer at threads 1 and 4, PREPARE/EXECUTE, 201 warm runs. The Python call floor (median 179 µs, `PREPARE f AS SELECT 1`) is subtracted. - Ours is single-threaded; "warm" includes the WHERE evaluation and the fold.