Commit 1d6a98f
Andrew Cheng
Flatten q filter operands into a single WHERE where possible
Builds on the prior pk__in subquery rewrite. That change removed the
set-operations, but still wrapped every operand of a NOT/AND/OR in its
own pk__in subquery, so a q expression compiled to one subquery per
operand and the SQL grew a nesting level for every AND/OR/NOT -- up to 8
stacked subqueries at the complexity cap. This is the "deep nesting"
cost the reviewer asked about.
Introduce a hybrid Q-composition. Each operand can now report an
`as_q()`:
- A leaf is Q-reducible only when it is a standard django-filter
Filter (no custom .filter() override, no method=, no distinct=). It
returns a plain `Q(field__lookup=value)` (negated for exclude
filters). Opaque operands -- label filters, repository_version,
href/prn/id resolvers, method filters, and ChoiceFilter (which
overrides .filter() for null handling) -- return None.
- NOT/AND/OR combine their children's Q objects with ~, &, | when all
children are reducible.
`evaluate()` then folds every reducible operand into a single flat Q and
applies only the genuinely opaque operands as pk__in subqueries. An
all-reducible expression -- however deeply nested -- collapses to one
flat SELECT; mixed expressions keep one subquery per opaque operand.
Opaque operands are always evaluated against the small base queryset, so
they never re-embed an accumulated filter chain.
Results are unchanged and the existing q filter functional tests
(test_q_filter_valid / test_q_filter_invalid, 21 cases) pass without
modification.
Benchmarks (100k rows, steady-state, count(), best of 4), old set-ops
vs. this change; SELECTs = number of SQL SELECT statements emitted:
Repositories (all-reducible name/number fields)
Expression set-ops this change
---------------------------------- -------------- --------------
name AND name 42 ms / 3 15 ms / 1
(A AND B) OR (C AND D) 79 ms / 7 20 ms / 1
(A AND (B OR C)) AND NOT D (cx 8) 144 ms / 8 19 ms / 1
name AND retain>=x AND retain<y 44 ms / 4 17 ms / 1
(num range) OR num=0 124 ms / 5 15 ms / 1
Tasks
name AND NOT name 68 ms / 4 17 ms / 1
name AND (NOT state OR state) 119 ms / 6 90 ms / 5
ContentGuards (name)
A AND B 56 ms / 3 19 ms / 1
(A AND B) OR C 155 ms / 5 20 ms / 1
Opaque-only expressions are unchanged: content repository_version
`X AND NOT Y` over 200k units stays ~2.45 s (199k pair) with no
regression, since both operands remain pk__in subqueries.
The speedup grows with nesting depth because the old plan added a
subquery per level while this one stays flat -- ~1.6x on a shallow OR up
to ~8x at the complexity-8 limit. The remaining opaque cases (e.g. task
`state`, a ChoiceFilter) could be reduced too in a follow-up.
Refs #7831
Assisted by: Claude Opus 4.81 parent 9386abb commit 1d6a98f
2 files changed
Lines changed: 70 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
206 | 206 | | |
207 | 207 | | |
208 | 208 | | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
209 | 226 | | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
210 | 230 | | |
211 | 231 | | |
212 | 232 | | |
213 | 233 | | |
214 | 234 | | |
215 | 235 | | |
216 | 236 | | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
217 | 241 | | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
218 | 246 | | |
219 | 247 | | |
220 | 248 | | |
221 | 249 | | |
222 | 250 | | |
223 | 251 | | |
224 | 252 | | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
225 | 262 | | |
| 263 | + | |
| 264 | + | |
226 | 265 | | |
| 266 | + | |
| 267 | + | |
227 | 268 | | |
228 | | - | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
229 | 277 | | |
230 | 278 | | |
231 | 279 | | |
232 | 280 | | |
233 | 281 | | |
234 | 282 | | |
235 | 283 | | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
236 | 293 | | |
| 294 | + | |
| 295 | + | |
237 | 296 | | |
238 | 297 | | |
239 | | - | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
240 | 303 | | |
241 | 304 | | |
242 | 305 | | |
| |||
0 commit comments