pyoq-sql 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. pyoq/__init__.py +10 -0
  2. pyoq/__main__.py +5 -0
  3. pyoq/_native.pyi +5 -0
  4. pyoq/cli/__init__.py +5 -0
  5. pyoq/cli/commands.py +270 -0
  6. pyoq/cli/defaults.py +98 -0
  7. pyoq/cli/services.py +97 -0
  8. pyoq/config/__init__.py +31 -0
  9. pyoq/config/connection.py +161 -0
  10. pyoq/config/loader.py +289 -0
  11. pyoq/config/models.py +245 -0
  12. pyoq/config/values.py +142 -0
  13. pyoq/descriptors.py +165 -0
  14. pyoq/diagnostics/__init__.py +68 -0
  15. pyoq/diagnostics/budget.py +136 -0
  16. pyoq/diagnostics/events.py +137 -0
  17. pyoq/diagnostics/fingerprint.py +267 -0
  18. pyoq/diagnostics/instrumented.py +237 -0
  19. pyoq/diagnostics/metrics.py +61 -0
  20. pyoq/diagnostics/observation.py +227 -0
  21. pyoq/diagnostics/scoped.py +103 -0
  22. pyoq/django/__init__.py +15 -0
  23. pyoq/django/apps.py +17 -0
  24. pyoq/django/execution.py +317 -0
  25. pyoq/django/generation.py +59 -0
  26. pyoq/django/management/__init__.py +0 -0
  27. pyoq/django/management/commands/__init__.py +0 -0
  28. pyoq/django/management/commands/makemigrations.py +53 -0
  29. pyoq/django/management/commands/pyoq_codegen.py +75 -0
  30. pyoq/django/parameters.py +101 -0
  31. pyoq/django/schema.py +379 -0
  32. pyoq/django/settings.py +87 -0
  33. pyoq/django/timeouts.py +105 -0
  34. pyoq/dsl/__init__.py +64 -0
  35. pyoq/dsl/aio/__init__.py +31 -0
  36. pyoq/dsl/aio/context.py +295 -0
  37. pyoq/dsl/aio/queries.py +335 -0
  38. pyoq/dsl/aio/writes.py +368 -0
  39. pyoq/dsl/context.py +326 -0
  40. pyoq/dsl/entry.py +37 -0
  41. pyoq/dsl/labels.py +36 -0
  42. pyoq/dsl/queries.py +339 -0
  43. pyoq/dsl/result.py +164 -0
  44. pyoq/dsl/writes.py +360 -0
  45. pyoq/errors.py +317 -0
  46. pyoq/fastapi/__init__.py +32 -0
  47. pyoq/fastapi/dependencies.py +167 -0
  48. pyoq/fastapi/lifespan.py +119 -0
  49. pyoq/fetching/__init__.py +55 -0
  50. pyoq/fetching/collections.py +136 -0
  51. pyoq/fetching/execution.py +587 -0
  52. pyoq/fetching/joined.py +79 -0
  53. pyoq/fetching/nesting.py +183 -0
  54. pyoq/fetching/plans.py +541 -0
  55. pyoq/fetching/select_in.py +149 -0
  56. pyoq/fetching/tables.py +110 -0
  57. pyoq/generation/__init__.py +54 -0
  58. pyoq/generation/cleanup.py +44 -0
  59. pyoq/generation/contracts.py +248 -0
  60. pyoq/generation/drift.py +169 -0
  61. pyoq/generation/lock.py +33 -0
  62. pyoq/generation/manifest.py +114 -0
  63. pyoq/generation/model.py +1001 -0
  64. pyoq/generation/pipeline.py +119 -0
  65. pyoq/generation/rendering/__init__.py +5 -0
  66. pyoq/generation/rendering/domains.py +51 -0
  67. pyoq/generation/rendering/enums.py +29 -0
  68. pyoq/generation/rendering/exports.py +70 -0
  69. pyoq/generation/rendering/imports.py +63 -0
  70. pyoq/generation/rendering/package.py +56 -0
  71. pyoq/generation/rendering/relations.py +133 -0
  72. pyoq/generation/rendering/routines.py +396 -0
  73. pyoq/generation/rendering/rows.py +79 -0
  74. pyoq/generation/rendering/source.py +121 -0
  75. pyoq/generation/rendering/tables.py +300 -0
  76. pyoq/generation/rendering/writes.py +514 -0
  77. pyoq/generation/validation.py +27 -0
  78. pyoq/generation/writer.py +184 -0
  79. pyoq/hydration/__init__.py +24 -0
  80. pyoq/hydration/engine.py +155 -0
  81. pyoq/hydration/identity.py +194 -0
  82. pyoq/hydration/plan.py +116 -0
  83. pyoq/migrations/__init__.py +9 -0
  84. pyoq/migrations/alembic.py +106 -0
  85. pyoq/migrations/hooks.py +75 -0
  86. pyoq/naming.py +261 -0
  87. pyoq/policies/__init__.py +47 -0
  88. pyoq/policies/bypass.py +122 -0
  89. pyoq/policies/governed.py +430 -0
  90. pyoq/policies/model.py +242 -0
  91. pyoq/policies/rewriting.py +263 -0
  92. pyoq/py.typed +1 -0
  93. pyoq/query/__init__.py +312 -0
  94. pyoq/query/aggregates.py +172 -0
  95. pyoq/query/arrays.py +65 -0
  96. pyoq/query/binding.py +52 -0
  97. pyoq/query/capabilities.py +317 -0
  98. pyoq/query/casts.py +73 -0
  99. pyoq/query/choices.py +185 -0
  100. pyoq/query/decoding.py +360 -0
  101. pyoq/query/documents.py +56 -0
  102. pyoq/query/execution/__init__.py +63 -0
  103. pyoq/query/execution/aio/__init__.py +31 -0
  104. pyoq/query/execution/aio/operations.py +228 -0
  105. pyoq/query/execution/aio/pooling.py +233 -0
  106. pyoq/query/execution/aio/streaming.py +161 -0
  107. pyoq/query/execution/aio/transactions.py +105 -0
  108. pyoq/query/execution/batch.py +96 -0
  109. pyoq/query/execution/binding_style.py +30 -0
  110. pyoq/query/execution/compilation.py +48 -0
  111. pyoq/query/execution/context.py +61 -0
  112. pyoq/query/execution/control.py +50 -0
  113. pyoq/query/execution/operations.py +224 -0
  114. pyoq/query/execution/planning.py +107 -0
  115. pyoq/query/execution/pooling.py +279 -0
  116. pyoq/query/execution/results.py +36 -0
  117. pyoq/query/execution/streaming.py +178 -0
  118. pyoq/query/execution/transactions.py +95 -0
  119. pyoq/query/expressions.py +1200 -0
  120. pyoq/query/fields.py +60 -0
  121. pyoq/query/mysql/__init__.py +59 -0
  122. pyoq/query/mysql/aio/__init__.py +38 -0
  123. pyoq/query/mysql/aio/commands.py +389 -0
  124. pyoq/query/mysql/aio/driver.py +196 -0
  125. pyoq/query/mysql/aio/executor.py +123 -0
  126. pyoq/query/mysql/aio/factory.py +26 -0
  127. pyoq/query/mysql/aio/operations.py +38 -0
  128. pyoq/query/mysql/aio/pool.py +53 -0
  129. pyoq/query/mysql/aio/transactions.py +313 -0
  130. pyoq/query/mysql/commands.py +354 -0
  131. pyoq/query/mysql/compiler.py +134 -0
  132. pyoq/query/mysql/context.py +20 -0
  133. pyoq/query/mysql/executor.py +126 -0
  134. pyoq/query/mysql/expressions.py +244 -0
  135. pyoq/query/mysql/factory.py +46 -0
  136. pyoq/query/mysql/health.py +66 -0
  137. pyoq/query/mysql/identifiers.py +9 -0
  138. pyoq/query/mysql/model.py +79 -0
  139. pyoq/query/mysql/operations.py +43 -0
  140. pyoq/query/mysql/parameters.py +69 -0
  141. pyoq/query/mysql/planning.py +20 -0
  142. pyoq/query/mysql/pool.py +67 -0
  143. pyoq/query/mysql/transactions.py +331 -0
  144. pyoq/query/mysql/writes.py +73 -0
  145. pyoq/query/nodes.py +750 -0
  146. pyoq/query/postgres/__init__.py +48 -0
  147. pyoq/query/postgres/aio/__init__.py +25 -0
  148. pyoq/query/postgres/aio/bulk.py +56 -0
  149. pyoq/query/postgres/aio/commands.py +264 -0
  150. pyoq/query/postgres/aio/executor.py +152 -0
  151. pyoq/query/postgres/aio/factory.py +26 -0
  152. pyoq/query/postgres/aio/operations.py +26 -0
  153. pyoq/query/postgres/aio/pool.py +40 -0
  154. pyoq/query/postgres/aio/transactions.py +295 -0
  155. pyoq/query/postgres/bulk.py +62 -0
  156. pyoq/query/postgres/commands.py +238 -0
  157. pyoq/query/postgres/compiler.py +114 -0
  158. pyoq/query/postgres/context.py +20 -0
  159. pyoq/query/postgres/executor.py +147 -0
  160. pyoq/query/postgres/expressions.py +311 -0
  161. pyoq/query/postgres/factory.py +24 -0
  162. pyoq/query/postgres/health.py +24 -0
  163. pyoq/query/postgres/identifiers.py +9 -0
  164. pyoq/query/postgres/model.py +81 -0
  165. pyoq/query/postgres/operations.py +25 -0
  166. pyoq/query/postgres/parameters.py +71 -0
  167. pyoq/query/postgres/planning.py +20 -0
  168. pyoq/query/postgres/pool.py +52 -0
  169. pyoq/query/postgres/transactions.py +295 -0
  170. pyoq/query/postgres/writes.py +37 -0
  171. pyoq/query/projections.py +105 -0
  172. pyoq/query/raw.py +90 -0
  173. pyoq/query/recursion.py +265 -0
  174. pyoq/query/rendering/__init__.py +1 -0
  175. pyoq/query/rendering/expressions.py +913 -0
  176. pyoq/query/rendering/identifiers.py +40 -0
  177. pyoq/query/rendering/projections.py +63 -0
  178. pyoq/query/rendering/queries.py +334 -0
  179. pyoq/query/rendering/sources.py +66 -0
  180. pyoq/query/rendering/writes.py +176 -0
  181. pyoq/query/results.py +459 -0
  182. pyoq/query/routines.py +196 -0
  183. pyoq/query/rows.py +156 -0
  184. pyoq/query/select.py +793 -0
  185. pyoq/query/select_nodes.py +277 -0
  186. pyoq/query/sources.py +236 -0
  187. pyoq/query/sqlite/__init__.py +43 -0
  188. pyoq/query/sqlite/commands.py +201 -0
  189. pyoq/query/sqlite/compiler.py +139 -0
  190. pyoq/query/sqlite/context.py +20 -0
  191. pyoq/query/sqlite/executor.py +119 -0
  192. pyoq/query/sqlite/expressions.py +224 -0
  193. pyoq/query/sqlite/factory.py +32 -0
  194. pyoq/query/sqlite/health.py +28 -0
  195. pyoq/query/sqlite/identifiers.py +9 -0
  196. pyoq/query/sqlite/model.py +73 -0
  197. pyoq/query/sqlite/operations.py +36 -0
  198. pyoq/query/sqlite/parameters.py +50 -0
  199. pyoq/query/sqlite/planning.py +20 -0
  200. pyoq/query/sqlite/pool.py +50 -0
  201. pyoq/query/sqlite/streaming.py +13 -0
  202. pyoq/query/sqlite/transactions.py +274 -0
  203. pyoq/query/sqlite/writes.py +35 -0
  204. pyoq/query/statements.py +27 -0
  205. pyoq/query/values.py +23 -0
  206. pyoq/query/vendor.py +162 -0
  207. pyoq/query/windows.py +424 -0
  208. pyoq/query/write_nodes.py +174 -0
  209. pyoq/query/writes.py +628 -0
  210. pyoq/relations/__init__.py +66 -0
  211. pyoq/relations/batching.py +219 -0
  212. pyoq/relations/derivation.py +111 -0
  213. pyoq/relations/fetching.py +355 -0
  214. pyoq/relations/graph.py +245 -0
  215. pyoq/relations/loading.py +74 -0
  216. pyoq/relations/model.py +75 -0
  217. pyoq/relations/planning.py +206 -0
  218. pyoq/runtime/__init__.py +9 -0
  219. pyoq/runtime/kernels.py +25 -0
  220. pyoq/runtime/python.py +43 -0
  221. pyoq/runtime/selection.py +73 -0
  222. pyoq/sanic/__init__.py +32 -0
  223. pyoq/sanic/scope.py +197 -0
  224. pyoq/sanic/workers.py +129 -0
  225. pyoq/schema/__init__.py +108 -0
  226. pyoq/schema/codec.py +711 -0
  227. pyoq/schema/models.py +604 -0
  228. pyoq/schema/mysql/__init__.py +16 -0
  229. pyoq/schema/mysql/connection.py +73 -0
  230. pyoq/schema/mysql/dsn.py +72 -0
  231. pyoq/schema/mysql/records.py +354 -0
  232. pyoq/schema/mysql/reflection.py +309 -0
  233. pyoq/schema/mysql/source.py +30 -0
  234. pyoq/schema/mysql/sql.py +128 -0
  235. pyoq/schema/mysql/types.py +105 -0
  236. pyoq/schema/postgres/__init__.py +13 -0
  237. pyoq/schema/postgres/connection.py +63 -0
  238. pyoq/schema/postgres/records.py +384 -0
  239. pyoq/schema/postgres/reflection.py +466 -0
  240. pyoq/schema/postgres/source.py +30 -0
  241. pyoq/schema/postgres/sql.py +246 -0
  242. pyoq/schema/postgres/types.py +98 -0
  243. pyoq/schema/registry.py +45 -0
  244. pyoq/schema/source.py +15 -0
  245. pyoq/schema/sqlite/__init__.py +6 -0
  246. pyoq/schema/sqlite/connection.py +54 -0
  247. pyoq/schema/sqlite/records.py +167 -0
  248. pyoq/schema/sqlite/reflection.py +393 -0
  249. pyoq/schema/sqlite/source.py +30 -0
  250. pyoq/schema/sqlite/sql.py +254 -0
  251. pyoq/schema/sqlite/types.py +74 -0
  252. pyoq/serving/__init__.py +23 -0
  253. pyoq/serving/databases.py +107 -0
  254. pyoq/serving/opening.py +331 -0
  255. pyoq/snapshots/__init__.py +20 -0
  256. pyoq/snapshots/drift.py +312 -0
  257. pyoq/snapshots/files.py +96 -0
  258. pyoq/snapshots/routing.py +40 -0
  259. pyoq/snapshots/source.py +33 -0
  260. pyoq/tracing/__init__.py +5 -0
  261. pyoq/tracing/spans.py +89 -0
  262. pyoq/unset.py +14 -0
  263. pyoq_sql-1.0.2.dist-info/METADATA +3050 -0
  264. pyoq_sql-1.0.2.dist-info/RECORD +267 -0
  265. pyoq_sql-1.0.2.dist-info/WHEEL +4 -0
  266. pyoq_sql-1.0.2.dist-info/entry_points.txt +3 -0
  267. pyoq_sql-1.0.2.dist-info/licenses/LICENSE +373 -0
@@ -0,0 +1,265 @@
1
+ """Declaring a recursive common table before it is defined.
2
+
3
+ A recursive term reads the table it is part of, so that table has to exist
4
+ before the query that defines it can be written. Declaring the name and the
5
+ columns first is what gives the recursive term something typed to read, and
6
+ what keeps the two halves agreeing on width and order.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from typing import TYPE_CHECKING, Generic, TypeVar, overload
13
+
14
+ from pyoq.errors import QueryValidationError
15
+ from pyoq.query.nodes import ScalarFamily
16
+ from pyoq.query.results import expression_family, family_for_type
17
+ from pyoq.query.select_nodes import (
18
+ CommonTableNode,
19
+ CommonTableSourceNode,
20
+ ProjectionNode,
21
+ SetNode,
22
+ SetOperator,
23
+ )
24
+ from pyoq.query.sources import CommonTable, SourceBase, require_identifier
25
+
26
+ if TYPE_CHECKING:
27
+ from pyoq.query.fields import Field
28
+ from pyoq.query.select import Query
29
+
30
+ FieldValue = TypeVar("FieldValue")
31
+ ColumnValue = TypeVar("ColumnValue", covariant=True)
32
+ ResultRow = TypeVar("ResultRow")
33
+ A = TypeVar("A")
34
+ B = TypeVar("B")
35
+ C = TypeVar("C")
36
+ D = TypeVar("D")
37
+ E = TypeVar("E")
38
+ F = TypeVar("F")
39
+ G = TypeVar("G")
40
+ H = TypeVar("H")
41
+
42
+
43
+ @dataclass(frozen=True, slots=True)
44
+ class RecursiveColumn(Generic[ColumnValue]):
45
+ """One column a recursive common table declares, and what it holds."""
46
+
47
+ name: str
48
+ value_type: type[ColumnValue]
49
+
50
+
51
+ class RecursiveTable(SourceBase, Generic[ResultRow]):
52
+ """A common table named and typed before either of its terms is written.
53
+
54
+ It is a source, so the recursive term reads it the way it reads any other
55
+ table, and the columns it answers with are the ones declared here.
56
+ """
57
+
58
+ __slots__ = ("_columns", "_name")
59
+ _name: str
60
+ _columns: tuple[RecursiveColumn[object], ...]
61
+
62
+ def __init__(self, name: str, columns: tuple[RecursiveColumn[object], ...]) -> None:
63
+ super().__init__(CommonTableSourceNode(name), name)
64
+ object.__setattr__(self, "_name", name)
65
+ object.__setattr__(self, "_columns", columns)
66
+
67
+ @property
68
+ def column_names(self) -> tuple[str, ...]:
69
+ return tuple(column.name for column in self._columns)
70
+
71
+ def field(
72
+ self,
73
+ column: RecursiveColumn[FieldValue],
74
+ /,
75
+ ) -> Field[FieldValue]:
76
+ """One column of this table, held to what the declaration said it holds.
77
+
78
+ The declaration owns the type, so reading a column as something else
79
+ is a disagreement the table can see and a database never would.
80
+ """
81
+ declared = {item.name: item.value_type for item in self._columns}
82
+ if column.name not in declared:
83
+ named = ", ".join(sorted(declared))
84
+ message = f"{self._name} declares {named}, not {column.name}"
85
+ raise QueryValidationError(message)
86
+ if declared[column.name] is not column.value_type:
87
+ message = (
88
+ f"{self._name}.{column.name} was declared to hold "
89
+ f"{declared[column.name].__name__}, read here as "
90
+ f"{column.value_type.__name__}"
91
+ )
92
+ raise QueryValidationError(message)
93
+ from pyoq.query.fields import field
94
+
95
+ return field(column.value_type, column.name, table_name=self._name)
96
+
97
+ def define(
98
+ self,
99
+ anchor: Query[ResultRow],
100
+ recursive: Query[ResultRow],
101
+ /,
102
+ ) -> CommonTable[ResultRow]:
103
+ """The rows it starts with, and the rows it reaches from those.
104
+
105
+ The two are combined with UNION ALL, which is what a recursive common
106
+ table is: a starting set and a rule for growing it.
107
+ """
108
+ _require_terms(anchor, self._columns, "anchor")
109
+ _require_terms(recursive, self._columns, "recursive")
110
+ combined = SetNode(anchor.node, SetOperator.UNION_ALL, recursive.node)
111
+ return CommonTable[ResultRow](
112
+ CommonTableNode(self._name, combined, self.column_names, recursive=True)
113
+ )
114
+
115
+
116
+ def column(name: str, value_type: type[FieldValue], /) -> RecursiveColumn[FieldValue]:
117
+ """One column of a recursive common table, and what it holds."""
118
+ require_identifier(name, "common table column")
119
+ return RecursiveColumn(name, value_type)
120
+
121
+
122
+ @overload
123
+ def recursive_table(
124
+ name: str, column_a: RecursiveColumn[A], /
125
+ ) -> RecursiveTable[tuple[A]]: ...
126
+
127
+
128
+ @overload
129
+ def recursive_table(
130
+ name: str, column_a: RecursiveColumn[A], column_b: RecursiveColumn[B], /
131
+ ) -> RecursiveTable[tuple[A, B]]: ...
132
+
133
+
134
+ @overload
135
+ def recursive_table(
136
+ name: str,
137
+ column_a: RecursiveColumn[A],
138
+ column_b: RecursiveColumn[B],
139
+ column_c: RecursiveColumn[C],
140
+ /,
141
+ ) -> RecursiveTable[tuple[A, B, C]]: ...
142
+
143
+
144
+ @overload
145
+ def recursive_table(
146
+ name: str,
147
+ column_a: RecursiveColumn[A],
148
+ column_b: RecursiveColumn[B],
149
+ column_c: RecursiveColumn[C],
150
+ column_d: RecursiveColumn[D],
151
+ /,
152
+ ) -> RecursiveTable[tuple[A, B, C, D]]: ...
153
+
154
+
155
+ @overload
156
+ def recursive_table(
157
+ name: str,
158
+ column_a: RecursiveColumn[A],
159
+ column_b: RecursiveColumn[B],
160
+ column_c: RecursiveColumn[C],
161
+ column_d: RecursiveColumn[D],
162
+ column_e: RecursiveColumn[E],
163
+ /,
164
+ ) -> RecursiveTable[tuple[A, B, C, D, E]]: ...
165
+
166
+
167
+ @overload
168
+ def recursive_table(
169
+ name: str,
170
+ column_a: RecursiveColumn[A],
171
+ column_b: RecursiveColumn[B],
172
+ column_c: RecursiveColumn[C],
173
+ column_d: RecursiveColumn[D],
174
+ column_e: RecursiveColumn[E],
175
+ column_f: RecursiveColumn[F],
176
+ /,
177
+ ) -> RecursiveTable[tuple[A, B, C, D, E, F]]: ...
178
+
179
+
180
+ @overload
181
+ def recursive_table(
182
+ name: str,
183
+ column_a: RecursiveColumn[A],
184
+ column_b: RecursiveColumn[B],
185
+ column_c: RecursiveColumn[C],
186
+ column_d: RecursiveColumn[D],
187
+ column_e: RecursiveColumn[E],
188
+ column_f: RecursiveColumn[F],
189
+ column_g: RecursiveColumn[G],
190
+ /,
191
+ ) -> RecursiveTable[tuple[A, B, C, D, E, F, G]]: ...
192
+
193
+
194
+ @overload
195
+ def recursive_table(
196
+ name: str,
197
+ column_a: RecursiveColumn[A],
198
+ column_b: RecursiveColumn[B],
199
+ column_c: RecursiveColumn[C],
200
+ column_d: RecursiveColumn[D],
201
+ column_e: RecursiveColumn[E],
202
+ column_f: RecursiveColumn[F],
203
+ column_g: RecursiveColumn[G],
204
+ column_h: RecursiveColumn[H],
205
+ /,
206
+ ) -> RecursiveTable[tuple[A, B, C, D, E, F, G, H]]: ...
207
+
208
+
209
+ def recursive_table(
210
+ name: str,
211
+ /,
212
+ *columns: RecursiveColumn[object],
213
+ ) -> object:
214
+ """Declare a recursive common table, so its own term can read it.
215
+
216
+ The columns are what the table answers with, and what the recursive term
217
+ reads from it. Each says what it holds, so both terms are held to the
218
+ types as well as to the width, and two terms that disagree are refused
219
+ here rather than reaching a database that would refuse them anyway.
220
+ """
221
+ if not columns:
222
+ message = "a recursive common table declares at least one column"
223
+ raise QueryValidationError(message)
224
+ require_identifier(name, "common table")
225
+ return RecursiveTable[tuple[object, ...]](name, columns)
226
+
227
+
228
+ def _require_terms(
229
+ query: Query[ResultRow],
230
+ columns: tuple[RecursiveColumn[object], ...],
231
+ term: str,
232
+ ) -> None:
233
+ """Both terms answer with what the table said it holds, in that order."""
234
+ node = query.node
235
+ projections = getattr(node, "projections", ())
236
+ if len(projections) != len(columns):
237
+ message = (
238
+ f"the {term} term selects {len(projections)} columns where the "
239
+ f"common table declares {len(columns)}"
240
+ )
241
+ raise QueryValidationError(message)
242
+ for position, (projection, declared) in enumerate(
243
+ zip(projections, columns, strict=True)
244
+ ):
245
+ _require_kind(projection, declared, term, position)
246
+
247
+
248
+ def _require_kind(
249
+ projection: ProjectionNode,
250
+ declared: RecursiveColumn[object],
251
+ term: str,
252
+ position: int,
253
+ ) -> None:
254
+ answered = expression_family(projection.expression)
255
+ wanted = family_for_type(declared.value_type)
256
+ if answered is wanted or ScalarFamily.NULL in {answered, wanted}:
257
+ return
258
+ message = (
259
+ f"the {term} term answers with {answered.value} at column "
260
+ f"{position + 1}, where {declared.name} holds {wanted.value}"
261
+ )
262
+ raise QueryValidationError(message)
263
+
264
+
265
+ __all__ = ("RecursiveColumn", "RecursiveTable", "column", "recursive_table")
@@ -0,0 +1 @@
1
+ """Shared SQL rendering for every dialect."""