1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# Tier 2 — WHERE: comparisons, boolean logic, IN, BETWEEN, LIKE, NULL, subqueries.
[[]]
= "where_numeric_gt"
= "trades.csv"
= "SELECT symbol, price FROM trades WHERE price > 185"
[[]]
= "where_string_eq"
= "trades.csv"
= "SELECT symbol, price FROM trades WHERE symbol = 'AAPL'"
[[]]
= "where_and_or"
= "international_sales.csv"
= "SELECT * FROM international_sales WHERE region = 'Europe' AND amount > 1000 OR currency = 'GBP'"
[[]]
= "where_in_list"
= "international_sales.csv"
= "SELECT * FROM international_sales WHERE currency IN ('USD', 'EUR')"
[[]]
= "where_not_in"
= "international_sales.csv"
= "SELECT * FROM international_sales WHERE currency NOT IN ('USD')"
[[]]
= "where_between"
= "trades.csv"
= "SELECT symbol, price FROM trades WHERE price BETWEEN 180 AND 190"
[[]]
= "where_like_prefix"
= "instruments.csv"
= "SELECT instrument_id, name FROM instruments WHERE name LIKE 'A%'"
[[]]
= "where_like_contains"
= "instruments.csv"
= "SELECT instrument_id, name FROM instruments WHERE name LIKE '%Stock%'"
[[]]
= "where_is_null"
= "instruments.csv"
= "SELECT instrument_id FROM instruments WHERE coupon_rate IS NULL"
[[]]
= "where_is_not_null"
= "instruments.csv"
= "SELECT instrument_id FROM instruments WHERE coupon_rate IS NOT NULL"
[[]]
= "where_not_expr"
= "trades.csv"
= "SELECT symbol FROM trades WHERE NOT (price > 185)"
[[]]
= "where_arith_predicate"
= "trades.csv"
= "SELECT symbol, price, volume FROM trades WHERE price * volume > 200000"
[[]]
= "where_in_subquery"
= "international_sales.csv"
= "SELECT region, amount FROM international_sales WHERE amount > (SELECT AVG(amount) FROM international_sales)"
[[]]
= "where_in_subquery_set"
= "trades.csv"
= "SELECT symbol FROM trades WHERE symbol IN (SELECT symbol FROM trades WHERE price > 185)"
[[]]
= "select_alias_in_in_subquery"
= "trades.csv"
= "SELECT symbol, price * 2 AS dbl FROM trades WHERE dbl IN (SELECT price * 2 FROM trades WHERE price > 185)"
# Was P11: a SELECT alias on the LHS of an IN-subquery errored ("Column 'dbl'
# not found"). Fixed 2026-07-25 in two layers: (1) WhereAliasExpander migrated
# onto walk::map_children, which visits the subquery LHS operand (the alias)
# while leaving the subquery body opaque; (2) the substituted IN-list's LHS may
# be an arbitrary expression (price*2), which the IN-subquery path never lifted,
# so evaluate_in_list/between now evaluate an expression LHS via the arithmetic
# evaluator. Now AGREE.
# --- P29 / P30: IN combined with another condition ---
[[]]
= "in_list_then_and"
= "null_edges.csv"
= "SELECT id FROM null_edges WHERE score IN (50, 70) AND team = 'alpha' ORDER BY id"
# P29, FIXED 2026-08-08. A boolean operator FOLLOWING an `IN (...)` predicate was
# not parsed: until P13 stage 1 the remainder was silently discarded (4 rows
# instead of 2), after it the query was a hard error. Root cause was IN being
# applied outside the OR/AND hierarchy — see P30 below, same bug.
[[]]
= "in_subquery_then_and"
= "null_edges.csv"
= "SELECT id FROM null_edges WHERE score IN (SELECT score FROM null_edges WHERE id < 5) AND team = 'alpha' ORDER BY id"
= "DIFFER"
# P29, the subquery form — pinned separately because the IN-list and IN-subquery
# paths are built differently (see P11) and a fix to one need not reach the other.
# In the event the P29/P30 precedence fix covered both, since they share
# `parse_in_operator`. Keep it: it is the control that proves that.
#
# It now DIFFERs on a *different* finding, which the parse error had been hiding:
# the subquery yields the scores of ids 1-4, which include id 3's NULL, and
# `NULL IN (.., NULL, ..)` matches for us where DuckDB gives UNKNOWN. 3 rows vs 2.
# That is P18 (`= NULL` matches instead of yielding UNKNOWN) reaching IN through
# the shared equality — see in_list_with_null_literal below, which isolates it
# without a subquery. Re-pin to AGREE when P18 is fixed.
[[]]
= "and_then_in_list"
= "null_edges.csv"
= "SELECT id, team, score FROM null_edges WHERE team = 'alpha' AND score IN (50, 70) ORDER BY id"
# P30, FIXED 2026-08-08, and it was the more alarming of the pair: with the
# operands this way round the query PARSED and returned ZERO rows where the
# answer is 2 (ids 1 and 2, both score 50).
#
# Filed as an evaluation bug; it was not. IN was applied at the top of
# parse_expression, outside the OR/AND hierarchy, so this parsed as
# InList { expr: (team = 'alpha' AND score), values: [50, 70] }
# — "is this boolean one of 50 or 70?" — false for every row. Same root cause as
# P29 above; the operand order only decided whether the mis-parse produced a
# wrong answer or leftover tokens.
[[]]
= "in_list_then_or"
= "null_edges.csv"
= "SELECT id, team, score FROM null_edges WHERE score IN (50) OR team = 'beta' ORDER BY id"
# Added with the P29/P30 fix. The defect was in how IN bound relative to the
# whole OR/AND hierarchy, so OR needs pinning in both operand orders too — AND
# alone would not have caught a fix that only reached parse_logical_and.
[[]]
= "or_then_in_list"
= "null_edges.csv"
= "SELECT id, team, score FROM null_edges WHERE team = 'beta' OR score IN (50) ORDER BY id"
# Added with the P29/P30 fix; the OR mirror of and_then_in_list.
[[]]
= "in_list_with_null_literal"
= "null_edges.csv"
= "SELECT id FROM null_edges WHERE score IN (50, NULL) ORDER BY id"
= "DIFFER"
# P18, found 2026-08-08 while fixing P29/P30 — the literal-list form that
# isolates what in_subquery_then_and trips over, with no subquery involved.
# A NULL in the list matches every NULL-scored row: we return ids 1,2,3,10,11,12
# where DuckDB returns 1,2. IN is built on the same equality as `= NULL`.
#
# Discriminating pair: where_in_with_null_col (tier 10) AGREEs — a NULL column
# value against a list with NO NULL in it is excluded correctly. The variable
# that matters is a NULL *in the list*, not a NULL in the column.
[[]]
= "in_list_between_two_conditions"
= "null_edges.csv"
= "SELECT id, team, score FROM null_edges WHERE team = 'alpha' AND score IN (50, 70) AND id < 3 ORDER BY id"
# Added with the P29/P30 fix. IN in the MIDDLE of a chain — the case that needs
# parse_comparison to both consume the IN and hand control back to the AND loop.
# The old top-level placement could not express this at all.