diff --git a/datasette/utils/actions_sql.py b/datasette/utils/actions_sql.py index 9c2add0e..8da8272e 100644 --- a/datasette/utils/actions_sql.py +++ b/datasette/utils/actions_sql.py @@ -405,8 +405,13 @@ async def _build_single_action_sql( # Add restriction filter if there are restrictions if restriction_sqls: + # Short-circuit optimization: if restriction_list is empty (e.g., due to + # INTERSECT of conflicting restrictions), the uncorrelated EXISTS check + # returns false once, avoiding per-row evaluation of the correlated subquery. + # See: https://emschwartz.me/short-circuiting-correlated-subqueries-in-sqlite/ query_parts.append( """ + AND EXISTS (SELECT 1 FROM restriction_list) AND EXISTS ( SELECT 1 FROM restriction_list r WHERE (r.parent = decisions.parent OR r.parent IS NULL) diff --git a/datasette/utils/permissions.py b/datasette/utils/permissions.py index 6c30a12a..fca45a36 100644 --- a/datasette/utils/permissions.py +++ b/datasette/utils/permissions.py @@ -324,7 +324,12 @@ async def resolve_permissions_from_catalog( filtered AS ( SELECT p.parent, p.child FROM permitted p - WHERE EXISTS ( + -- Short-circuit optimization: if restriction_list is empty (e.g., due to + -- INTERSECT of conflicting restrictions), the uncorrelated EXISTS check + -- returns false once, avoiding per-row evaluation of the correlated subquery. + -- See: https://emschwartz.me/short-circuiting-correlated-subqueries-in-sqlite/ + WHERE EXISTS (SELECT 1 FROM restriction_list) + AND EXISTS ( SELECT 1 FROM restriction_list r WHERE (r.parent = p.parent OR r.parent IS NULL) AND (r.child = p.child OR r.child IS NULL)