Comparing main (ash-project/ash_sqlite@f018e3c) against this branch (wtsnz/ash_sqlite@27c0923) using the actual AshSqlite query path: build with Ash.Query.data_layer_query/1, render with Ecto.Adapters.SQL.to_sql/3, then execute with Repo.all/1. The fixture used 20k posts; local SQLite reports MAX_VARIABLE_NUMBER=32766.
SQL shape at 900 list values:
| branch | SQL shape | SQL bytes | OR terms | IN terms | bind params |
|---|---|---|---|---|---|
main |
OR chain | 19,882 | 899 | 0 | 901 |
| this branch | compact IN | 1,905 | 0 | 1 | 901 |
Median execution time, unindexed posts.title:
| list size | main OR |
compact IN |
|---|---|---|
| 100 | 23.8ms | 2.25ms |
| 400 | 88.3ms | 3.05ms |
| 900 | 199.0ms | 4.07ms |
| 1000 | fails: max expression depth | 4.09ms |
| 2000 | fails: max expression depth | 5.58ms |
Median execution time, indexed posts.title:
| list size | main OR |
compact IN |
|---|---|---|
| 100 | 0.417ms | 0.190ms |
| 400 | 1.66ms | 0.587ms |
| 900 | 3.49ms | 1.24ms |
| 1000 | fails: max expression depth | 1.37ms |
| 2000 | fails: max expression depth | 2.98ms |
Median query build/render time, unindexed run:
| list size | main OR |
compact IN |
|---|---|---|
| 100 | 0.284ms | 0.060ms |
| 400 | 0.954ms | 0.190ms |
| 900 | 2.21ms | 0.362ms |
| 2000 | 4.82ms | 0.912ms |
The bind count is unchanged (n + 1, because the resource base filter contributes one bind). The improvement comes from avoiding the large SQL string and the deep SQLite expression tree.
Conclusion: this is both a correctness fix and a performance improvement. The old OR-chain form fails at 1000 scalar values on this SQLite build, indexed or not. Before that limit, compact IN is already materially faster, especially for unindexed scans, while keeping the same bind count and preserving an index-friendly left-hand column.