Coverage for app/backend/src/tests/sql_linkage.py: 97%
90 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-27 19:48 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-27 19:48 +0000
1"""
2Finds tables in a query that are tied to the rest of it by a value (e.g. `passport_date_of_birth = users.birthdate`)
3rather than by a key. SQLAlchemy's FROM-linter can't catch this: it counts any predicate as a connection, and never
4runs on view DDL.
5"""
7import itertools
8import warnings
9from typing import Any
11from sqlalchemy import Column, Table
12from sqlalchemy.sql import operators
13from sqlalchemy.sql.elements import (
14 BinaryExpression,
15 BooleanClauseList,
16 ColumnClause,
17 ColumnElement,
18 _anonymous_label,
19)
20from sqlalchemy.sql.selectable import CompoundSelect, FromClause, Join, Select
21from sqlalchemy.sql.util import find_tables
24def _conjuncts(clause: ColumnElement[Any] | None) -> list[ColumnElement[Any]]:
25 if clause is None:
26 return []
27 if isinstance(clause, BooleanClauseList) and clause.operator is operators.and_:
28 return [conjunct for sub in clause.clauses for conjunct in _conjuncts(sub)]
29 return [clause]
32def _origins(expr: Any) -> set[Column[Any]]:
33 if not isinstance(expr, ColumnClause): 33 ↛ 34line 33 didn't jump to line 34 because the condition on line 33 was never true
34 return set()
35 return {col for col in expr.proxy_set if isinstance(col, Column) and isinstance(col.table, Table)}
38def _fk_targets(col: Column[Any]) -> set[Column[Any]]:
39 return {fk.column for fk in col.foreign_keys}
42def _is_key_link(left: Any, right: Any) -> bool:
43 left_origins, right_origins = _origins(left), _origins(right)
44 if left_origins & right_origins:
45 return True
46 for a, b in itertools.product(left_origins, right_origins):
47 if b in _fk_targets(a) or a in _fk_targets(b) or _fk_targets(a) & _fk_targets(b):
48 return True
49 return False
52def _leaves(from_clause: FromClause) -> list[FromClause]:
53 if isinstance(from_clause, Join):
54 return _leaves(from_clause.left) + _leaves(from_clause.right)
55 return [from_clause]
58def _join_conditions(from_clause: FromClause) -> list[ColumnElement[Any]]:
59 if isinstance(from_clause, Join):
60 return [
61 *_conjuncts(from_clause.onclause),
62 *_join_conditions(from_clause.left),
63 *_join_conditions(from_clause.right),
64 ]
65 return []
68def _key(from_clause: FromClause) -> str:
69 # by name rather than identity: the ORM annotates its own copies of tables, so they compare equal but aren't the same
70 name = getattr(from_clause, "name", None)
71 return str(name) if name is not None else str(id(from_clause))
74def _describe(from_clause: FromClause) -> str:
75 name = getattr(from_clause, "name", None)
76 if name is not None and not isinstance(name, _anonymous_label):
77 return str(name)
78 inner = getattr(from_clause, "element", None)
79 tables = sorted({table.name for table in find_tables(inner)}) if inner is not None else []
80 return f"unnamed subquery over {', '.join(tables)}" if tables else "unnamed subquery"
83class _Components:
84 def __init__(self, keys: list[str]) -> None:
85 self._parent = {key: key for key in keys}
87 def find(self, key: str) -> str:
88 while self._parent[key] != key:
89 self._parent[key] = self._parent[self._parent[key]]
90 key = self._parent[key]
91 return key
93 def union(self, left: str, right: str) -> None:
94 self._parent[self.find(left)] = self.find(right)
96 def count(self) -> int:
97 return len({self.find(key) for key in self._parent})
100def find_unkeyed_joins(statement: Select[Any] | CompoundSelect[Any], path: str = "query") -> list[str]:
101 """Returns a problem for every FROM list (recursing into subqueries and unions) not connected by keys."""
102 if isinstance(statement, CompoundSelect):
103 return [
104 problem
105 for index, arm in enumerate(statement.selects)
106 if isinstance(arm, (Select, CompoundSelect))
107 for problem in find_unkeyed_joins(arm, f"{path}/union[{index}]")
108 ]
110 with warnings.catch_warnings():
111 # resolving the FROM list compiles against the default dialect, which warns about DISTINCT ON
112 warnings.filterwarnings("ignore", "DISTINCT ON is currently supported only by the PostgreSQL dialect")
113 froms = statement.get_final_froms()
114 leaves = [leaf for from_clause in froms for leaf in _leaves(from_clause)]
115 conditions = [
116 *(condition for from_clause in froms for condition in _join_conditions(from_clause)),
117 *_conjuncts(statement.whereclause),
118 ]
120 by_key = {_key(leaf): leaf for leaf in leaves}
121 components = _Components(list(by_key))
122 unkeyed: list[BinaryExpression[Any]] = []
123 for condition in conditions:
124 if not isinstance(condition, BinaryExpression) or condition.operator is not operators.eq:
125 continue
126 sides = [getattr(getattr(condition, side), "table", None) for side in ("left", "right")]
127 if any(side is None for side in sides):
128 continue
129 left, right = (_key(side) for side in sides) # type: ignore[arg-type]
130 if left not in by_key or right not in by_key or left == right: 130 ↛ 131line 130 didn't jump to line 131 because the condition on line 130 was never true
131 continue
132 if _is_key_link(condition.left, condition.right):
133 components.union(left, right)
134 else:
135 unkeyed.append(condition)
137 problems = []
138 if components.count() > 1:
139 mistaken = "".join(f"\n only linked by: {condition}" for condition in unkeyed)
140 problems.append(
141 f"{path}: {', '.join(sorted(_describe(leaf) for leaf in leaves))} are not all connected by keys, so rows "
142 f"are paired across every combination{mistaken}"
143 )
145 for leaf in leaves:
146 element = getattr(leaf, "element", None)
147 if isinstance(element, (Select, CompoundSelect)):
148 problems += find_unkeyed_joins(element, f"{path}/{_describe(leaf)}")
149 return problems