|
| 1 | +"""Stage-B multiplicity vs the duckdb oracle (TASK-59), multiset parity. |
| 2 | +
|
| 3 | +Pins: docs/superpowers/specs/2026-07-28-stageB-multiplicity-pins.md — |
| 4 | +DuckDB's join output ORDER is a hash-join accident, so comparison is |
| 5 | +SORTED; the engine's own order (probe outer, insertion inner) is a |
| 6 | +documented contract of its own. |
| 7 | +""" |
| 8 | + |
| 9 | +from __future__ import annotations |
| 10 | + |
| 11 | +import duckdb |
| 12 | +import pyarrow as pa |
| 13 | +from pydantic import create_model |
| 14 | + |
| 15 | +from sql_transform._interpreter import DuckDBInferFn |
| 16 | + |
| 17 | +T = create_model("T", pid=(int | None, None)) |
| 18 | +ROWS = [{"pid": 1}, {"pid": 2}, {"pid": 3}, {"pid": None}] |
| 19 | +DIM = pa.table({"id": [1, 2, 1, 2, 1], "v": ["a", "b", "c", "d", "e"]}) |
| 20 | + |
| 21 | + |
| 22 | +def _many_check(sql: str): |
| 23 | + """Engine (shape='many') vs DuckDB, sorted-row multiset.""" |
| 24 | + fn = DuckDBInferFn( |
| 25 | + sql.replace("__THIS__", "__THIS__"), |
| 26 | + row_tables={"__THIS__": T}, |
| 27 | + static_tables={"d": DIM}, |
| 28 | + output="dict", |
| 29 | + shape="many", |
| 30 | + ) |
| 31 | + got = [tuple(r.values()) for r in fn.infer({"__THIS__": [T(**r) for r in ROWS]})] |
| 32 | + |
| 33 | + con = duckdb.connect() |
| 34 | + con.execute("CREATE TABLE __THIS__ (pid BIGINT)") |
| 35 | + for r in ROWS: |
| 36 | + con.execute("INSERT INTO __THIS__ VALUES (?)", [r["pid"]]) |
| 37 | + con.register("__arrow_d", DIM) |
| 38 | + con.execute('CREATE TABLE d AS SELECT * FROM "__arrow_d"') |
| 39 | + want = con.execute(sql).fetchall() |
| 40 | + key = lambda t: tuple((x is None, x) for x in t) # noqa: E731 |
| 41 | + assert sorted(got, key=key) == sorted(want, key=key), f"{sql}\n{got}\n{want}" |
| 42 | + |
| 43 | + |
| 44 | +def test_dup_key_fanout_vs_oracle(): |
| 45 | + _many_check("SELECT pid, v FROM __THIS__ JOIN d ON pid = d.id") |
| 46 | + _many_check("SELECT pid, v FROM __THIS__ LEFT JOIN d ON pid = d.id") |
| 47 | + _many_check("SELECT pid, v FROM __THIS__ LEFT JOIN d ON pid = d.id AND d.v > 'b'") |
| 48 | + _many_check("SELECT pid, v FROM __THIS__ LEFT JOIN d ON pid = d.id WHERE v IS NULL") |
| 49 | + _many_check("SELECT upper(v) AS u FROM __THIS__ JOIN d ON pid = d.id WHERE pid > 1") |
| 50 | + |
| 51 | + |
| 52 | +def test_cross_and_inequality_vs_oracle(): |
| 53 | + _many_check("SELECT pid, id, v FROM __THIS__, d") |
| 54 | + _many_check("SELECT pid, id FROM __THIS__ JOIN d ON pid > d.id") |
| 55 | + _many_check("SELECT pid, id FROM __THIS__ LEFT JOIN d ON pid > d.id") |
| 56 | + _many_check("SELECT pid, id FROM __THIS__ LEFT JOIN d ON NULL = 2") |
| 57 | + _many_check("SELECT pid, id FROM __THIS__, d WHERE pid >= id AND v <> 'c'") |
| 58 | + |
| 59 | + |
| 60 | +def test_engine_order_contract(): |
| 61 | + # The engine's OWN documented deterministic order: probe rows in input |
| 62 | + # order, matches contiguous in build INSERTION order, null-extension |
| 63 | + # in place. |
| 64 | + fn = DuckDBInferFn( |
| 65 | + "SELECT pid, v FROM __THIS__ LEFT JOIN d ON pid = d.id", |
| 66 | + row_tables={"__THIS__": T}, |
| 67 | + static_tables={"d": DIM}, |
| 68 | + output="dict", |
| 69 | + shape="many", |
| 70 | + ) |
| 71 | + got = [tuple(r.values()) for r in fn.infer({"__THIS__": [T(**r) for r in ROWS]})] |
| 72 | + assert got == [ |
| 73 | + (1, "a"), |
| 74 | + (1, "c"), |
| 75 | + (1, "e"), |
| 76 | + (2, "b"), |
| 77 | + (2, "d"), |
| 78 | + (3, None), |
| 79 | + (None, None), |
| 80 | + ] |
0 commit comments