pyoq-sql 1.0.0__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.
- pyoq/__init__.py +7 -0
- pyoq/__main__.py +5 -0
- pyoq/_native.pyi +5 -0
- pyoq/cli/__init__.py +5 -0
- pyoq/cli/commands.py +270 -0
- pyoq/cli/defaults.py +98 -0
- pyoq/cli/services.py +97 -0
- pyoq/config/__init__.py +19 -0
- pyoq/config/loader.py +204 -0
- pyoq/config/models.py +243 -0
- pyoq/descriptors.py +165 -0
- pyoq/diagnostics/__init__.py +68 -0
- pyoq/diagnostics/budget.py +136 -0
- pyoq/diagnostics/events.py +137 -0
- pyoq/diagnostics/fingerprint.py +267 -0
- pyoq/diagnostics/instrumented.py +237 -0
- pyoq/diagnostics/metrics.py +61 -0
- pyoq/diagnostics/observation.py +227 -0
- pyoq/diagnostics/scoped.py +103 -0
- pyoq/django/__init__.py +15 -0
- pyoq/django/apps.py +17 -0
- pyoq/django/execution.py +317 -0
- pyoq/django/generation.py +59 -0
- pyoq/django/management/__init__.py +0 -0
- pyoq/django/management/commands/__init__.py +0 -0
- pyoq/django/management/commands/makemigrations.py +53 -0
- pyoq/django/management/commands/pyoq_codegen.py +75 -0
- pyoq/django/parameters.py +101 -0
- pyoq/django/schema.py +379 -0
- pyoq/django/settings.py +87 -0
- pyoq/django/timeouts.py +105 -0
- pyoq/dsl/__init__.py +64 -0
- pyoq/dsl/aio/__init__.py +31 -0
- pyoq/dsl/aio/context.py +295 -0
- pyoq/dsl/aio/queries.py +335 -0
- pyoq/dsl/aio/writes.py +368 -0
- pyoq/dsl/context.py +326 -0
- pyoq/dsl/entry.py +37 -0
- pyoq/dsl/labels.py +36 -0
- pyoq/dsl/queries.py +339 -0
- pyoq/dsl/result.py +164 -0
- pyoq/dsl/writes.py +360 -0
- pyoq/errors.py +317 -0
- pyoq/fastapi/__init__.py +32 -0
- pyoq/fastapi/dependencies.py +167 -0
- pyoq/fastapi/lifespan.py +119 -0
- pyoq/fetching/__init__.py +55 -0
- pyoq/fetching/collections.py +136 -0
- pyoq/fetching/execution.py +587 -0
- pyoq/fetching/joined.py +79 -0
- pyoq/fetching/nesting.py +183 -0
- pyoq/fetching/plans.py +541 -0
- pyoq/fetching/select_in.py +149 -0
- pyoq/fetching/tables.py +110 -0
- pyoq/generation/__init__.py +54 -0
- pyoq/generation/cleanup.py +44 -0
- pyoq/generation/contracts.py +248 -0
- pyoq/generation/drift.py +169 -0
- pyoq/generation/lock.py +33 -0
- pyoq/generation/manifest.py +114 -0
- pyoq/generation/model.py +1001 -0
- pyoq/generation/pipeline.py +119 -0
- pyoq/generation/rendering/__init__.py +5 -0
- pyoq/generation/rendering/domains.py +51 -0
- pyoq/generation/rendering/enums.py +29 -0
- pyoq/generation/rendering/exports.py +70 -0
- pyoq/generation/rendering/imports.py +63 -0
- pyoq/generation/rendering/package.py +56 -0
- pyoq/generation/rendering/relations.py +133 -0
- pyoq/generation/rendering/routines.py +396 -0
- pyoq/generation/rendering/rows.py +79 -0
- pyoq/generation/rendering/source.py +121 -0
- pyoq/generation/rendering/tables.py +300 -0
- pyoq/generation/rendering/writes.py +514 -0
- pyoq/generation/validation.py +27 -0
- pyoq/generation/writer.py +184 -0
- pyoq/hydration/__init__.py +24 -0
- pyoq/hydration/engine.py +155 -0
- pyoq/hydration/identity.py +194 -0
- pyoq/hydration/plan.py +116 -0
- pyoq/migrations/__init__.py +9 -0
- pyoq/migrations/alembic.py +106 -0
- pyoq/migrations/hooks.py +75 -0
- pyoq/naming.py +261 -0
- pyoq/policies/__init__.py +47 -0
- pyoq/policies/bypass.py +122 -0
- pyoq/policies/governed.py +430 -0
- pyoq/policies/model.py +242 -0
- pyoq/policies/rewriting.py +263 -0
- pyoq/py.typed +1 -0
- pyoq/query/__init__.py +312 -0
- pyoq/query/aggregates.py +172 -0
- pyoq/query/arrays.py +65 -0
- pyoq/query/binding.py +52 -0
- pyoq/query/capabilities.py +317 -0
- pyoq/query/casts.py +73 -0
- pyoq/query/choices.py +185 -0
- pyoq/query/decoding.py +360 -0
- pyoq/query/documents.py +56 -0
- pyoq/query/execution/__init__.py +63 -0
- pyoq/query/execution/aio/__init__.py +31 -0
- pyoq/query/execution/aio/operations.py +228 -0
- pyoq/query/execution/aio/pooling.py +233 -0
- pyoq/query/execution/aio/streaming.py +161 -0
- pyoq/query/execution/aio/transactions.py +105 -0
- pyoq/query/execution/batch.py +96 -0
- pyoq/query/execution/binding_style.py +30 -0
- pyoq/query/execution/compilation.py +48 -0
- pyoq/query/execution/context.py +61 -0
- pyoq/query/execution/control.py +50 -0
- pyoq/query/execution/operations.py +224 -0
- pyoq/query/execution/planning.py +107 -0
- pyoq/query/execution/pooling.py +279 -0
- pyoq/query/execution/results.py +36 -0
- pyoq/query/execution/streaming.py +178 -0
- pyoq/query/execution/transactions.py +95 -0
- pyoq/query/expressions.py +1200 -0
- pyoq/query/fields.py +60 -0
- pyoq/query/mysql/__init__.py +59 -0
- pyoq/query/mysql/aio/__init__.py +38 -0
- pyoq/query/mysql/aio/commands.py +389 -0
- pyoq/query/mysql/aio/driver.py +196 -0
- pyoq/query/mysql/aio/executor.py +123 -0
- pyoq/query/mysql/aio/factory.py +26 -0
- pyoq/query/mysql/aio/operations.py +38 -0
- pyoq/query/mysql/aio/pool.py +53 -0
- pyoq/query/mysql/aio/transactions.py +313 -0
- pyoq/query/mysql/commands.py +354 -0
- pyoq/query/mysql/compiler.py +134 -0
- pyoq/query/mysql/context.py +20 -0
- pyoq/query/mysql/executor.py +126 -0
- pyoq/query/mysql/expressions.py +244 -0
- pyoq/query/mysql/factory.py +46 -0
- pyoq/query/mysql/health.py +66 -0
- pyoq/query/mysql/identifiers.py +9 -0
- pyoq/query/mysql/model.py +79 -0
- pyoq/query/mysql/operations.py +43 -0
- pyoq/query/mysql/parameters.py +69 -0
- pyoq/query/mysql/planning.py +20 -0
- pyoq/query/mysql/pool.py +67 -0
- pyoq/query/mysql/transactions.py +331 -0
- pyoq/query/mysql/writes.py +73 -0
- pyoq/query/nodes.py +750 -0
- pyoq/query/postgres/__init__.py +48 -0
- pyoq/query/postgres/aio/__init__.py +25 -0
- pyoq/query/postgres/aio/bulk.py +56 -0
- pyoq/query/postgres/aio/commands.py +264 -0
- pyoq/query/postgres/aio/executor.py +152 -0
- pyoq/query/postgres/aio/factory.py +26 -0
- pyoq/query/postgres/aio/operations.py +26 -0
- pyoq/query/postgres/aio/pool.py +40 -0
- pyoq/query/postgres/aio/transactions.py +295 -0
- pyoq/query/postgres/bulk.py +62 -0
- pyoq/query/postgres/commands.py +238 -0
- pyoq/query/postgres/compiler.py +114 -0
- pyoq/query/postgres/context.py +20 -0
- pyoq/query/postgres/executor.py +147 -0
- pyoq/query/postgres/expressions.py +311 -0
- pyoq/query/postgres/factory.py +24 -0
- pyoq/query/postgres/health.py +24 -0
- pyoq/query/postgres/identifiers.py +9 -0
- pyoq/query/postgres/model.py +81 -0
- pyoq/query/postgres/operations.py +25 -0
- pyoq/query/postgres/parameters.py +71 -0
- pyoq/query/postgres/planning.py +20 -0
- pyoq/query/postgres/pool.py +52 -0
- pyoq/query/postgres/transactions.py +295 -0
- pyoq/query/postgres/writes.py +37 -0
- pyoq/query/projections.py +105 -0
- pyoq/query/raw.py +90 -0
- pyoq/query/recursion.py +265 -0
- pyoq/query/rendering/__init__.py +1 -0
- pyoq/query/rendering/expressions.py +913 -0
- pyoq/query/rendering/identifiers.py +40 -0
- pyoq/query/rendering/projections.py +63 -0
- pyoq/query/rendering/queries.py +334 -0
- pyoq/query/rendering/sources.py +66 -0
- pyoq/query/rendering/writes.py +176 -0
- pyoq/query/results.py +459 -0
- pyoq/query/routines.py +196 -0
- pyoq/query/rows.py +156 -0
- pyoq/query/select.py +793 -0
- pyoq/query/select_nodes.py +277 -0
- pyoq/query/sources.py +236 -0
- pyoq/query/sqlite/__init__.py +43 -0
- pyoq/query/sqlite/commands.py +201 -0
- pyoq/query/sqlite/compiler.py +139 -0
- pyoq/query/sqlite/context.py +20 -0
- pyoq/query/sqlite/executor.py +119 -0
- pyoq/query/sqlite/expressions.py +224 -0
- pyoq/query/sqlite/factory.py +32 -0
- pyoq/query/sqlite/health.py +28 -0
- pyoq/query/sqlite/identifiers.py +9 -0
- pyoq/query/sqlite/model.py +73 -0
- pyoq/query/sqlite/operations.py +36 -0
- pyoq/query/sqlite/parameters.py +50 -0
- pyoq/query/sqlite/planning.py +20 -0
- pyoq/query/sqlite/pool.py +50 -0
- pyoq/query/sqlite/streaming.py +13 -0
- pyoq/query/sqlite/transactions.py +274 -0
- pyoq/query/sqlite/writes.py +35 -0
- pyoq/query/statements.py +27 -0
- pyoq/query/values.py +23 -0
- pyoq/query/vendor.py +162 -0
- pyoq/query/windows.py +424 -0
- pyoq/query/write_nodes.py +174 -0
- pyoq/query/writes.py +628 -0
- pyoq/relations/__init__.py +66 -0
- pyoq/relations/batching.py +219 -0
- pyoq/relations/derivation.py +111 -0
- pyoq/relations/fetching.py +355 -0
- pyoq/relations/graph.py +245 -0
- pyoq/relations/loading.py +74 -0
- pyoq/relations/model.py +75 -0
- pyoq/relations/planning.py +206 -0
- pyoq/runtime/__init__.py +9 -0
- pyoq/runtime/kernels.py +25 -0
- pyoq/runtime/python.py +43 -0
- pyoq/runtime/selection.py +73 -0
- pyoq/sanic/__init__.py +32 -0
- pyoq/sanic/scope.py +197 -0
- pyoq/sanic/workers.py +129 -0
- pyoq/schema/__init__.py +108 -0
- pyoq/schema/codec.py +711 -0
- pyoq/schema/models.py +604 -0
- pyoq/schema/mysql/__init__.py +16 -0
- pyoq/schema/mysql/connection.py +72 -0
- pyoq/schema/mysql/dsn.py +72 -0
- pyoq/schema/mysql/records.py +354 -0
- pyoq/schema/mysql/reflection.py +309 -0
- pyoq/schema/mysql/source.py +30 -0
- pyoq/schema/mysql/sql.py +128 -0
- pyoq/schema/mysql/types.py +105 -0
- pyoq/schema/postgres/__init__.py +13 -0
- pyoq/schema/postgres/connection.py +62 -0
- pyoq/schema/postgres/records.py +384 -0
- pyoq/schema/postgres/reflection.py +466 -0
- pyoq/schema/postgres/source.py +30 -0
- pyoq/schema/postgres/sql.py +246 -0
- pyoq/schema/postgres/types.py +98 -0
- pyoq/schema/registry.py +45 -0
- pyoq/schema/source.py +15 -0
- pyoq/schema/sqlite/__init__.py +6 -0
- pyoq/schema/sqlite/connection.py +54 -0
- pyoq/schema/sqlite/records.py +167 -0
- pyoq/schema/sqlite/reflection.py +393 -0
- pyoq/schema/sqlite/source.py +30 -0
- pyoq/schema/sqlite/sql.py +254 -0
- pyoq/schema/sqlite/types.py +74 -0
- pyoq/serving/__init__.py +19 -0
- pyoq/serving/databases.py +107 -0
- pyoq/snapshots/__init__.py +20 -0
- pyoq/snapshots/drift.py +312 -0
- pyoq/snapshots/files.py +96 -0
- pyoq/snapshots/routing.py +40 -0
- pyoq/snapshots/source.py +33 -0
- pyoq/tracing/__init__.py +5 -0
- pyoq/tracing/spans.py +89 -0
- pyoq/unset.py +14 -0
- pyoq_sql-1.0.0.dist-info/METADATA +3034 -0
- pyoq_sql-1.0.0.dist-info/RECORD +264 -0
- pyoq_sql-1.0.0.dist-info/WHEEL +4 -0
- pyoq_sql-1.0.0.dist-info/entry_points.txt +3 -0
- pyoq_sql-1.0.0.dist-info/licenses/LICENSE +373 -0
pyoq/dsl/aio/writes.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""The asynchronous counterpart of a write bound to its database."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Generic, TypeVar, TypeVarTuple, cast, overload
|
|
6
|
+
|
|
7
|
+
from pyoq.dsl.labels import labels_of
|
|
8
|
+
from pyoq.dsl.result import Result, Row
|
|
9
|
+
from pyoq.query.write_nodes import WriteNode
|
|
10
|
+
from pyoq.query.writes import (
|
|
11
|
+
ConflictUpdate,
|
|
12
|
+
DeleteStatement,
|
|
13
|
+
InsertStatement,
|
|
14
|
+
MutationStatement,
|
|
15
|
+
ReturningStatement,
|
|
16
|
+
UpdateStatement,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from collections.abc import Callable, Iterable
|
|
21
|
+
|
|
22
|
+
from pyoq.query.execution.aio import AsyncQueryOperations
|
|
23
|
+
from pyoq.query.expressions import Expression
|
|
24
|
+
from pyoq.query.writes import ColumnReference
|
|
25
|
+
|
|
26
|
+
Values = TypeVarTuple("Values")
|
|
27
|
+
Mutation = TypeVar("Mutation", bound=MutationStatement[WriteNode])
|
|
28
|
+
ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
|
|
29
|
+
Mapped = TypeVar("Mapped")
|
|
30
|
+
ScalarValue = TypeVar("ScalarValue")
|
|
31
|
+
Value1 = TypeVar("Value1")
|
|
32
|
+
Value2 = TypeVar("Value2")
|
|
33
|
+
Value3 = TypeVar("Value3")
|
|
34
|
+
Value4 = TypeVar("Value4")
|
|
35
|
+
Value5 = TypeVar("Value5")
|
|
36
|
+
Value6 = TypeVar("Value6")
|
|
37
|
+
Value7 = TypeVar("Value7")
|
|
38
|
+
Value8 = TypeVar("Value8")
|
|
39
|
+
AssignedValue = TypeVar("AssignedValue")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AsyncBoundReturning(Generic[ResultRow]):
|
|
43
|
+
"""Rows a write handed back, read the way a query's rows are read."""
|
|
44
|
+
|
|
45
|
+
__slots__ = ("_operations", "_statement")
|
|
46
|
+
_operations: AsyncQueryOperations
|
|
47
|
+
_statement: ReturningStatement[ResultRow]
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
operations: AsyncQueryOperations,
|
|
52
|
+
statement: ReturningStatement[ResultRow],
|
|
53
|
+
) -> None:
|
|
54
|
+
object.__setattr__(self, "_operations", operations)
|
|
55
|
+
object.__setattr__(self, "_statement", statement)
|
|
56
|
+
|
|
57
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
58
|
+
message = f"{type(self).__name__} is immutable"
|
|
59
|
+
raise AttributeError(message, name, value)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def statement(self) -> ReturningStatement[ResultRow]:
|
|
63
|
+
return self._statement
|
|
64
|
+
|
|
65
|
+
async def fetch_all(self) -> Result[ResultRow]:
|
|
66
|
+
rows = tuple(await self._operations.many(self._statement))
|
|
67
|
+
return Result(rows, labels_of(self._statement.node.returning))
|
|
68
|
+
|
|
69
|
+
async def fetch_one(self) -> Row[ResultRow]:
|
|
70
|
+
"""Exactly one row, because that is what the caller said to expect."""
|
|
71
|
+
return Row(
|
|
72
|
+
await self._operations.one(self._statement),
|
|
73
|
+
labels_of(self._statement.node.returning),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def fetch_optional(self) -> Row[ResultRow] | None:
|
|
77
|
+
row = await self._operations.one_or_none(self._statement)
|
|
78
|
+
return (
|
|
79
|
+
None if row is None else Row(row, labels_of(self._statement.node.returning))
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
async def fetch_optional_into(
|
|
83
|
+
self,
|
|
84
|
+
factory: Callable[..., Mapped],
|
|
85
|
+
/,
|
|
86
|
+
) -> Mapped | None:
|
|
87
|
+
"""One row built by the caller's own type, or nothing.
|
|
88
|
+
|
|
89
|
+
The row and the many-row result both take a factory inline.
|
|
90
|
+
A row that may be missing had to be tested for first, which
|
|
91
|
+
is the one shape that could not be asked for in the chain.
|
|
92
|
+
"""
|
|
93
|
+
row = await self.fetch_optional()
|
|
94
|
+
return None if row is None else row.into(factory)
|
|
95
|
+
|
|
96
|
+
async def fetch_value(
|
|
97
|
+
self: AsyncBoundReturning[tuple[ScalarValue]],
|
|
98
|
+
/,
|
|
99
|
+
) -> ScalarValue:
|
|
100
|
+
return await self._operations.scalar(self._statement)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AsyncBoundMutation(Generic[Mutation]):
|
|
104
|
+
"""What every bound write can do, whatever it is writing."""
|
|
105
|
+
|
|
106
|
+
__slots__ = ("_operations", "_statement")
|
|
107
|
+
_operations: AsyncQueryOperations
|
|
108
|
+
_statement: Mutation
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
operations: AsyncQueryOperations,
|
|
113
|
+
statement: Mutation,
|
|
114
|
+
) -> None:
|
|
115
|
+
object.__setattr__(self, "_operations", operations)
|
|
116
|
+
object.__setattr__(self, "_statement", statement)
|
|
117
|
+
|
|
118
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
119
|
+
message = f"{type(self).__name__} is immutable"
|
|
120
|
+
raise AttributeError(message, name, value)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def statement(self) -> Mutation:
|
|
124
|
+
"""The statement on its own, which cannot reach a database."""
|
|
125
|
+
return self._statement
|
|
126
|
+
|
|
127
|
+
async def execute(self) -> int:
|
|
128
|
+
"""How many rows the database says it wrote."""
|
|
129
|
+
written = await self._operations.execute(self._statement)
|
|
130
|
+
return written.rows_affected
|
|
131
|
+
|
|
132
|
+
def returning_all(self) -> AsyncBoundReturning[tuple[object, ...]]:
|
|
133
|
+
"""Every column the table declares, without listing them."""
|
|
134
|
+
return AsyncBoundReturning(self._operations, self._statement.returning_all())
|
|
135
|
+
|
|
136
|
+
@overload
|
|
137
|
+
def returning(
|
|
138
|
+
self,
|
|
139
|
+
value1: Expression[Value1],
|
|
140
|
+
/,
|
|
141
|
+
) -> AsyncBoundReturning[tuple[Value1]]: ...
|
|
142
|
+
|
|
143
|
+
@overload
|
|
144
|
+
def returning(
|
|
145
|
+
self,
|
|
146
|
+
value1: Expression[Value1],
|
|
147
|
+
value2: Expression[Value2],
|
|
148
|
+
/,
|
|
149
|
+
) -> AsyncBoundReturning[tuple[Value1, Value2]]: ...
|
|
150
|
+
|
|
151
|
+
@overload
|
|
152
|
+
def returning(
|
|
153
|
+
self,
|
|
154
|
+
value1: Expression[Value1],
|
|
155
|
+
value2: Expression[Value2],
|
|
156
|
+
value3: Expression[Value3],
|
|
157
|
+
/,
|
|
158
|
+
) -> AsyncBoundReturning[tuple[Value1, Value2, Value3]]: ...
|
|
159
|
+
|
|
160
|
+
@overload
|
|
161
|
+
def returning(
|
|
162
|
+
self,
|
|
163
|
+
value1: Expression[Value1],
|
|
164
|
+
value2: Expression[Value2],
|
|
165
|
+
value3: Expression[Value3],
|
|
166
|
+
value4: Expression[Value4],
|
|
167
|
+
/,
|
|
168
|
+
) -> AsyncBoundReturning[tuple[Value1, Value2, Value3, Value4]]: ...
|
|
169
|
+
|
|
170
|
+
@overload
|
|
171
|
+
def returning(
|
|
172
|
+
self,
|
|
173
|
+
value1: Expression[Value1],
|
|
174
|
+
value2: Expression[Value2],
|
|
175
|
+
value3: Expression[Value3],
|
|
176
|
+
value4: Expression[Value4],
|
|
177
|
+
value5: Expression[Value5],
|
|
178
|
+
/,
|
|
179
|
+
) -> AsyncBoundReturning[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
|
|
180
|
+
|
|
181
|
+
@overload
|
|
182
|
+
def returning(
|
|
183
|
+
self,
|
|
184
|
+
value1: Expression[Value1],
|
|
185
|
+
value2: Expression[Value2],
|
|
186
|
+
value3: Expression[Value3],
|
|
187
|
+
value4: Expression[Value4],
|
|
188
|
+
value5: Expression[Value5],
|
|
189
|
+
value6: Expression[Value6],
|
|
190
|
+
/,
|
|
191
|
+
) -> AsyncBoundReturning[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
|
|
192
|
+
|
|
193
|
+
@overload
|
|
194
|
+
def returning(
|
|
195
|
+
self,
|
|
196
|
+
value1: Expression[Value1],
|
|
197
|
+
value2: Expression[Value2],
|
|
198
|
+
value3: Expression[Value3],
|
|
199
|
+
value4: Expression[Value4],
|
|
200
|
+
value5: Expression[Value5],
|
|
201
|
+
value6: Expression[Value6],
|
|
202
|
+
value7: Expression[Value7],
|
|
203
|
+
/,
|
|
204
|
+
) -> AsyncBoundReturning[
|
|
205
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]
|
|
206
|
+
]: ...
|
|
207
|
+
|
|
208
|
+
@overload
|
|
209
|
+
def returning(
|
|
210
|
+
self,
|
|
211
|
+
value1: Expression[Value1],
|
|
212
|
+
value2: Expression[Value2],
|
|
213
|
+
value3: Expression[Value3],
|
|
214
|
+
value4: Expression[Value4],
|
|
215
|
+
value5: Expression[Value5],
|
|
216
|
+
value6: Expression[Value6],
|
|
217
|
+
value7: Expression[Value7],
|
|
218
|
+
value8: Expression[Value8],
|
|
219
|
+
/,
|
|
220
|
+
) -> AsyncBoundReturning[
|
|
221
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
|
|
222
|
+
]: ...
|
|
223
|
+
|
|
224
|
+
@overload
|
|
225
|
+
def returning(
|
|
226
|
+
self,
|
|
227
|
+
value1: object,
|
|
228
|
+
value2: object,
|
|
229
|
+
value3: object,
|
|
230
|
+
value4: object,
|
|
231
|
+
value5: object,
|
|
232
|
+
value6: object,
|
|
233
|
+
value7: object,
|
|
234
|
+
value8: object,
|
|
235
|
+
/,
|
|
236
|
+
*values: object,
|
|
237
|
+
) -> AsyncBoundReturning[tuple[object, ...]]:
|
|
238
|
+
"""Wider than the overloads above name, where the types stop.
|
|
239
|
+
|
|
240
|
+
A statement is still a statement past eight returned columns,
|
|
241
|
+
and matching no overload would take the whole chain down with
|
|
242
|
+
it rather than just the tuple.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
def returning( # type: ignore[misc]
|
|
246
|
+
self,
|
|
247
|
+
*values: object,
|
|
248
|
+
) -> AsyncBoundReturning[tuple[object, ...]]:
|
|
249
|
+
# The builder declares typed overloads for callers; this is the one
|
|
250
|
+
# implementation behind them, reached variadically.
|
|
251
|
+
build = cast(
|
|
252
|
+
"Callable[..., ReturningStatement[tuple[object, ...]]]",
|
|
253
|
+
self._statement.returning,
|
|
254
|
+
)
|
|
255
|
+
return AsyncBoundReturning(self._operations, build(*values))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class AsyncBoundInsert(
|
|
259
|
+
AsyncBoundMutation[InsertStatement[*Values]],
|
|
260
|
+
Generic[*Values],
|
|
261
|
+
):
|
|
262
|
+
"""An INSERT and the database it will run against."""
|
|
263
|
+
|
|
264
|
+
__slots__ = ()
|
|
265
|
+
|
|
266
|
+
def values(self, *values: *Values) -> AsyncBoundInsert[*Values]:
|
|
267
|
+
return AsyncBoundInsert(self._operations, self._statement.values(*values))
|
|
268
|
+
|
|
269
|
+
def values_many(self, rows: Iterable[object], /) -> AsyncBoundInsert[*Values]:
|
|
270
|
+
return AsyncBoundInsert(self._operations, self._statement.values_many(rows))
|
|
271
|
+
|
|
272
|
+
def on_conflict_do_nothing(
|
|
273
|
+
self,
|
|
274
|
+
*columns: ColumnReference,
|
|
275
|
+
) -> AsyncBoundInsert[*Values]:
|
|
276
|
+
return AsyncBoundInsert(
|
|
277
|
+
self._operations,
|
|
278
|
+
self._statement.on_conflict_do_nothing(*columns),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def on_conflict_do_update(
|
|
282
|
+
self,
|
|
283
|
+
*columns: ColumnReference,
|
|
284
|
+
) -> AsyncBoundConflictUpdate[*Values]:
|
|
285
|
+
return AsyncBoundConflictUpdate(
|
|
286
|
+
self._operations,
|
|
287
|
+
self._statement.on_conflict_do_update(*columns),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class AsyncBoundConflictUpdate(
|
|
292
|
+
AsyncBoundMutation[ConflictUpdate[*Values]],
|
|
293
|
+
Generic[*Values],
|
|
294
|
+
):
|
|
295
|
+
"""The SET and WHERE that belong to a conflict clause."""
|
|
296
|
+
|
|
297
|
+
__slots__ = ()
|
|
298
|
+
|
|
299
|
+
def set(
|
|
300
|
+
self,
|
|
301
|
+
column: Expression[AssignedValue],
|
|
302
|
+
value: AssignedValue | Expression[AssignedValue],
|
|
303
|
+
/,
|
|
304
|
+
) -> AsyncBoundConflictUpdate[*Values]:
|
|
305
|
+
return AsyncBoundConflictUpdate(
|
|
306
|
+
self._operations, self._statement.set(column, value)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def where(
|
|
310
|
+
self,
|
|
311
|
+
condition: Expression[bool],
|
|
312
|
+
/,
|
|
313
|
+
) -> AsyncBoundConflictUpdate[*Values]:
|
|
314
|
+
return AsyncBoundConflictUpdate(
|
|
315
|
+
self._operations, self._statement.where(condition)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class AsyncBoundUpdate(AsyncBoundMutation[UpdateStatement]):
|
|
320
|
+
"""An UPDATE, which has to say which rows it means."""
|
|
321
|
+
|
|
322
|
+
__slots__ = ()
|
|
323
|
+
|
|
324
|
+
def set(
|
|
325
|
+
self,
|
|
326
|
+
column: Expression[AssignedValue],
|
|
327
|
+
value: AssignedValue | Expression[AssignedValue],
|
|
328
|
+
/,
|
|
329
|
+
) -> AsyncBoundUpdate:
|
|
330
|
+
return AsyncBoundUpdate(self._operations, self._statement.set(column, value))
|
|
331
|
+
|
|
332
|
+
def set_values(
|
|
333
|
+
self,
|
|
334
|
+
values: object,
|
|
335
|
+
/,
|
|
336
|
+
*columns: ColumnReference,
|
|
337
|
+
) -> AsyncBoundUpdate:
|
|
338
|
+
return AsyncBoundUpdate(
|
|
339
|
+
self._operations, self._statement.set_values(values, *columns)
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def where(self, condition: Expression[bool], /) -> AsyncBoundUpdate:
|
|
343
|
+
return AsyncBoundUpdate(self._operations, self._statement.where(condition))
|
|
344
|
+
|
|
345
|
+
def all_rows(self) -> AsyncBoundUpdate:
|
|
346
|
+
return AsyncBoundUpdate(self._operations, self._statement.all_rows())
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class AsyncBoundDelete(AsyncBoundMutation[DeleteStatement]):
|
|
350
|
+
"""A DELETE, which has to say which rows it means."""
|
|
351
|
+
|
|
352
|
+
__slots__ = ()
|
|
353
|
+
|
|
354
|
+
def where(self, condition: Expression[bool], /) -> AsyncBoundDelete:
|
|
355
|
+
return AsyncBoundDelete(self._operations, self._statement.where(condition))
|
|
356
|
+
|
|
357
|
+
def all_rows(self) -> AsyncBoundDelete:
|
|
358
|
+
return AsyncBoundDelete(self._operations, self._statement.all_rows())
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
__all__ = (
|
|
362
|
+
"AsyncBoundConflictUpdate",
|
|
363
|
+
"AsyncBoundDelete",
|
|
364
|
+
"AsyncBoundInsert",
|
|
365
|
+
"AsyncBoundMutation",
|
|
366
|
+
"AsyncBoundReturning",
|
|
367
|
+
"AsyncBoundUpdate",
|
|
368
|
+
)
|
pyoq/dsl/context.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""The entry point that ties a query to a database.
|
|
2
|
+
|
|
3
|
+
PyOQ's builders describe a statement and never reach a database, which is what
|
|
4
|
+
makes them safe to share, cache, and test. A context does not change that: it
|
|
5
|
+
holds the operations to run against and hands back builders that carry it, so a
|
|
6
|
+
chain can end in a fetch instead of being passed to an executor.
|
|
7
|
+
|
|
8
|
+
The unattached builders are still there and still cannot execute. Anything built
|
|
9
|
+
with them runs through `fetch` or `execute` here.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, TypeVar, TypeVarTuple, cast, overload
|
|
15
|
+
|
|
16
|
+
from pyoq.dsl.labels import labels_of
|
|
17
|
+
from pyoq.dsl.queries import BoundSelect
|
|
18
|
+
from pyoq.dsl.result import Result
|
|
19
|
+
from pyoq.dsl.writes import BoundDelete, BoundInsert, BoundUpdate
|
|
20
|
+
from pyoq.query.select import SelectQuery, select, select_from
|
|
21
|
+
from pyoq.query.select_nodes import SelectNode
|
|
22
|
+
from pyoq.query.writes import (
|
|
23
|
+
InsertStatement,
|
|
24
|
+
ReturningStatement,
|
|
25
|
+
delete_from,
|
|
26
|
+
insert_into,
|
|
27
|
+
update,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from collections.abc import Callable
|
|
32
|
+
|
|
33
|
+
from pyoq.query.execution import QueryOperations
|
|
34
|
+
from pyoq.query.expressions import Expression
|
|
35
|
+
from pyoq.query.projections import Projection
|
|
36
|
+
from pyoq.query.routines import Call
|
|
37
|
+
from pyoq.query.select import Query
|
|
38
|
+
from pyoq.query.sources import ColumnedTable, TableLike
|
|
39
|
+
|
|
40
|
+
ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
|
|
41
|
+
Values = TypeVarTuple("Values")
|
|
42
|
+
Value1 = TypeVar("Value1")
|
|
43
|
+
Value2 = TypeVar("Value2")
|
|
44
|
+
Value3 = TypeVar("Value3")
|
|
45
|
+
Value4 = TypeVar("Value4")
|
|
46
|
+
Value5 = TypeVar("Value5")
|
|
47
|
+
Value6 = TypeVar("Value6")
|
|
48
|
+
Value7 = TypeVar("Value7")
|
|
49
|
+
Value8 = TypeVar("Value8")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class DslContext:
|
|
53
|
+
"""A database to build against."""
|
|
54
|
+
|
|
55
|
+
__slots__ = ("_operations",)
|
|
56
|
+
_operations: QueryOperations
|
|
57
|
+
|
|
58
|
+
def __init__(self, operations: QueryOperations) -> None:
|
|
59
|
+
object.__setattr__(self, "_operations", operations)
|
|
60
|
+
|
|
61
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
62
|
+
message = f"{type(self).__name__} is immutable"
|
|
63
|
+
raise AttributeError(message, name, value)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def operations(self) -> QueryOperations:
|
|
67
|
+
return self._operations
|
|
68
|
+
|
|
69
|
+
@overload
|
|
70
|
+
def select(
|
|
71
|
+
self,
|
|
72
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
73
|
+
/,
|
|
74
|
+
) -> BoundSelect[tuple[Value1]]: ...
|
|
75
|
+
|
|
76
|
+
@overload
|
|
77
|
+
def select(
|
|
78
|
+
self,
|
|
79
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
80
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
81
|
+
/,
|
|
82
|
+
) -> BoundSelect[tuple[Value1, Value2]]: ...
|
|
83
|
+
|
|
84
|
+
@overload
|
|
85
|
+
def select(
|
|
86
|
+
self,
|
|
87
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
88
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
89
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
90
|
+
/,
|
|
91
|
+
) -> BoundSelect[tuple[Value1, Value2, Value3]]: ...
|
|
92
|
+
|
|
93
|
+
@overload
|
|
94
|
+
def select(
|
|
95
|
+
self,
|
|
96
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
97
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
98
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
99
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
100
|
+
/,
|
|
101
|
+
) -> BoundSelect[tuple[Value1, Value2, Value3, Value4]]: ...
|
|
102
|
+
|
|
103
|
+
@overload
|
|
104
|
+
def select(
|
|
105
|
+
self,
|
|
106
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
107
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
108
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
109
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
110
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
111
|
+
/,
|
|
112
|
+
) -> BoundSelect[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
|
|
113
|
+
|
|
114
|
+
@overload
|
|
115
|
+
def select(
|
|
116
|
+
self,
|
|
117
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
118
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
119
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
120
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
121
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
122
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
123
|
+
/,
|
|
124
|
+
) -> BoundSelect[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
|
|
125
|
+
|
|
126
|
+
@overload
|
|
127
|
+
def select(
|
|
128
|
+
self,
|
|
129
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
130
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
131
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
132
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
133
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
134
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
135
|
+
value7: Expression[Value7] | Projection[Value7],
|
|
136
|
+
/,
|
|
137
|
+
) -> BoundSelect[tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]]: ...
|
|
138
|
+
|
|
139
|
+
@overload
|
|
140
|
+
def select(
|
|
141
|
+
self,
|
|
142
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
143
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
144
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
145
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
146
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
147
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
148
|
+
value7: Expression[Value7] | Projection[Value7],
|
|
149
|
+
value8: Expression[Value8] | Projection[Value8],
|
|
150
|
+
/,
|
|
151
|
+
) -> BoundSelect[
|
|
152
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
|
|
153
|
+
]: ...
|
|
154
|
+
|
|
155
|
+
@overload
|
|
156
|
+
def select(
|
|
157
|
+
self,
|
|
158
|
+
value1: object,
|
|
159
|
+
value2: object,
|
|
160
|
+
value3: object,
|
|
161
|
+
value4: object,
|
|
162
|
+
value5: object,
|
|
163
|
+
value6: object,
|
|
164
|
+
value7: object,
|
|
165
|
+
value8: object,
|
|
166
|
+
/,
|
|
167
|
+
*values: object,
|
|
168
|
+
) -> BoundSelect[tuple[object, ...]]:
|
|
169
|
+
"""Wider than the overloads above name, where the element types stop.
|
|
170
|
+
|
|
171
|
+
A query is still a query past eight projections, and matching no
|
|
172
|
+
overload would take the whole chain down with it rather than just the
|
|
173
|
+
tuple. Past here a projection is checked when it is turned into one,
|
|
174
|
+
and the row is `object` per column, which is what is still known.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def select( # type: ignore[misc]
|
|
178
|
+
self,
|
|
179
|
+
*values: object,
|
|
180
|
+
) -> BoundSelect[tuple[object, ...]]:
|
|
181
|
+
# `select` declares typed overloads for callers; this is the single
|
|
182
|
+
# implementation behind them, reached variadically. The overloads above
|
|
183
|
+
# are what a caller sees, and they carry the types across.
|
|
184
|
+
build = cast("Callable[..., SelectQuery[tuple[object, ...]]]", select)
|
|
185
|
+
return BoundSelect(self._operations, build(*values))
|
|
186
|
+
|
|
187
|
+
def select_from(self, table: ColumnedTable, /) -> BoundSelect[tuple[object, ...]]:
|
|
188
|
+
"""Every column the table declares, without naming them one by one.
|
|
189
|
+
|
|
190
|
+
The columns are written into the SQL rather than left as a star, so
|
|
191
|
+
adding one later cannot silently change what a caller reads back.
|
|
192
|
+
Values arrive in declared order, which is the order the generated row
|
|
193
|
+
type takes its arguments, so `.into(TableRow)` builds it.
|
|
194
|
+
"""
|
|
195
|
+
return BoundSelect(self._operations, select_from(table))
|
|
196
|
+
|
|
197
|
+
@overload
|
|
198
|
+
@overload
|
|
199
|
+
def insert_into(
|
|
200
|
+
self,
|
|
201
|
+
table: TableLike,
|
|
202
|
+
/,
|
|
203
|
+
column1: Expression[Value1],
|
|
204
|
+
) -> BoundInsert[Value1]: ...
|
|
205
|
+
|
|
206
|
+
@overload
|
|
207
|
+
def insert_into(
|
|
208
|
+
self,
|
|
209
|
+
table: TableLike,
|
|
210
|
+
/,
|
|
211
|
+
column1: Expression[Value1],
|
|
212
|
+
column2: Expression[Value2],
|
|
213
|
+
) -> BoundInsert[Value1, Value2]: ...
|
|
214
|
+
|
|
215
|
+
@overload
|
|
216
|
+
def insert_into(
|
|
217
|
+
self,
|
|
218
|
+
table: TableLike,
|
|
219
|
+
/,
|
|
220
|
+
column1: Expression[Value1],
|
|
221
|
+
column2: Expression[Value2],
|
|
222
|
+
column3: Expression[Value3],
|
|
223
|
+
) -> BoundInsert[Value1, Value2, Value3]: ...
|
|
224
|
+
|
|
225
|
+
@overload
|
|
226
|
+
def insert_into(
|
|
227
|
+
self,
|
|
228
|
+
table: TableLike,
|
|
229
|
+
/,
|
|
230
|
+
column1: Expression[Value1],
|
|
231
|
+
column2: Expression[Value2],
|
|
232
|
+
column3: Expression[Value3],
|
|
233
|
+
column4: Expression[Value4],
|
|
234
|
+
) -> BoundInsert[Value1, Value2, Value3, Value4]: ...
|
|
235
|
+
|
|
236
|
+
@overload
|
|
237
|
+
def insert_into(
|
|
238
|
+
self,
|
|
239
|
+
table: TableLike,
|
|
240
|
+
/,
|
|
241
|
+
column1: Expression[Value1],
|
|
242
|
+
column2: Expression[Value2],
|
|
243
|
+
column3: Expression[Value3],
|
|
244
|
+
column4: Expression[Value4],
|
|
245
|
+
column5: Expression[Value5],
|
|
246
|
+
) -> BoundInsert[Value1, Value2, Value3, Value4, Value5]: ...
|
|
247
|
+
|
|
248
|
+
@overload
|
|
249
|
+
def insert_into(
|
|
250
|
+
self,
|
|
251
|
+
table: TableLike,
|
|
252
|
+
/,
|
|
253
|
+
column1: Expression[Value1],
|
|
254
|
+
column2: Expression[Value2],
|
|
255
|
+
column3: Expression[Value3],
|
|
256
|
+
column4: Expression[Value4],
|
|
257
|
+
column5: Expression[Value5],
|
|
258
|
+
column6: Expression[Value6],
|
|
259
|
+
) -> BoundInsert[Value1, Value2, Value3, Value4, Value5, Value6]: ...
|
|
260
|
+
|
|
261
|
+
@overload
|
|
262
|
+
def insert_into(
|
|
263
|
+
self,
|
|
264
|
+
table: TableLike,
|
|
265
|
+
/,
|
|
266
|
+
column1: Expression[Value1],
|
|
267
|
+
column2: Expression[Value2],
|
|
268
|
+
column3: Expression[Value3],
|
|
269
|
+
column4: Expression[Value4],
|
|
270
|
+
column5: Expression[Value5],
|
|
271
|
+
column6: Expression[Value6],
|
|
272
|
+
column7: Expression[Value7],
|
|
273
|
+
) -> BoundInsert[Value1, Value2, Value3, Value4, Value5, Value6, Value7]: ...
|
|
274
|
+
|
|
275
|
+
@overload
|
|
276
|
+
def insert_into(
|
|
277
|
+
self,
|
|
278
|
+
table: TableLike,
|
|
279
|
+
/,
|
|
280
|
+
column1: Expression[Value1],
|
|
281
|
+
column2: Expression[Value2],
|
|
282
|
+
column3: Expression[Value3],
|
|
283
|
+
column4: Expression[Value4],
|
|
284
|
+
column5: Expression[Value5],
|
|
285
|
+
column6: Expression[Value6],
|
|
286
|
+
column7: Expression[Value7],
|
|
287
|
+
column8: Expression[Value8],
|
|
288
|
+
) -> BoundInsert[
|
|
289
|
+
Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8
|
|
290
|
+
]: ...
|
|
291
|
+
|
|
292
|
+
def insert_into( # type: ignore[misc]
|
|
293
|
+
self,
|
|
294
|
+
table: TableLike,
|
|
295
|
+
/,
|
|
296
|
+
*columns: object,
|
|
297
|
+
) -> BoundInsert[*tuple[object, ...]]:
|
|
298
|
+
build = cast("Callable[..., InsertStatement[*tuple[object, ...]]]", insert_into)
|
|
299
|
+
return BoundInsert(self._operations, build(table, *columns))
|
|
300
|
+
|
|
301
|
+
def update(self, table: TableLike, /) -> BoundUpdate:
|
|
302
|
+
return BoundUpdate(self._operations, update(table))
|
|
303
|
+
|
|
304
|
+
def delete_from(self, table: TableLike, /) -> BoundDelete:
|
|
305
|
+
return BoundDelete(self._operations, delete_from(table))
|
|
306
|
+
|
|
307
|
+
def fetch(
|
|
308
|
+
self,
|
|
309
|
+
query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
|
|
310
|
+
/,
|
|
311
|
+
) -> Result[ResultRow]:
|
|
312
|
+
"""Run something built the ordinary way, and read it the same way."""
|
|
313
|
+
rows = tuple(self._operations.many(query))
|
|
314
|
+
return Result(rows, _labels(query))
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _labels(
|
|
318
|
+
query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
|
|
319
|
+
) -> tuple[str | None, ...]:
|
|
320
|
+
if isinstance(query, ReturningStatement):
|
|
321
|
+
return labels_of(query.node.returning)
|
|
322
|
+
node = query.node
|
|
323
|
+
return labels_of(node.projections) if isinstance(node, SelectNode) else ()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
__all__ = ("DslContext",)
|