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/context.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""The asynchronous counterpart of a context to build against."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, TypeVar, cast, overload
|
|
6
|
+
|
|
7
|
+
from pyoq.dsl.aio.queries import AsyncBoundSelect
|
|
8
|
+
from pyoq.dsl.aio.writes import AsyncBoundDelete, AsyncBoundInsert, AsyncBoundUpdate
|
|
9
|
+
from pyoq.dsl.labels import labels_of
|
|
10
|
+
from pyoq.dsl.result import Result
|
|
11
|
+
from pyoq.query.select import SelectQuery, select, select_from
|
|
12
|
+
from pyoq.query.select_nodes import SelectNode
|
|
13
|
+
from pyoq.query.writes import (
|
|
14
|
+
InsertStatement,
|
|
15
|
+
ReturningStatement,
|
|
16
|
+
delete_from,
|
|
17
|
+
insert_into,
|
|
18
|
+
update,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
|
|
24
|
+
from pyoq.query.execution.aio import AsyncQueryOperations
|
|
25
|
+
from pyoq.query.expressions import Expression
|
|
26
|
+
from pyoq.query.projections import Projection
|
|
27
|
+
from pyoq.query.routines import Call
|
|
28
|
+
from pyoq.query.select import Query
|
|
29
|
+
from pyoq.query.sources import ColumnedTable, TableLike
|
|
30
|
+
|
|
31
|
+
ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
|
|
32
|
+
Value1 = TypeVar("Value1")
|
|
33
|
+
Value2 = TypeVar("Value2")
|
|
34
|
+
Value3 = TypeVar("Value3")
|
|
35
|
+
Value4 = TypeVar("Value4")
|
|
36
|
+
Value5 = TypeVar("Value5")
|
|
37
|
+
Value6 = TypeVar("Value6")
|
|
38
|
+
Value7 = TypeVar("Value7")
|
|
39
|
+
Value8 = TypeVar("Value8")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AsyncDslContext:
|
|
43
|
+
"""A database to build against, whose fetches are awaited."""
|
|
44
|
+
|
|
45
|
+
__slots__ = ("_operations",)
|
|
46
|
+
_operations: AsyncQueryOperations
|
|
47
|
+
|
|
48
|
+
def __init__(self, operations: AsyncQueryOperations) -> None:
|
|
49
|
+
object.__setattr__(self, "_operations", operations)
|
|
50
|
+
|
|
51
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
52
|
+
message = f"{type(self).__name__} is immutable"
|
|
53
|
+
raise AttributeError(message, name, value)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def operations(self) -> AsyncQueryOperations:
|
|
57
|
+
return self._operations
|
|
58
|
+
|
|
59
|
+
@overload
|
|
60
|
+
def select(
|
|
61
|
+
self,
|
|
62
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
63
|
+
/,
|
|
64
|
+
) -> AsyncBoundSelect[tuple[Value1]]: ...
|
|
65
|
+
|
|
66
|
+
@overload
|
|
67
|
+
def select(
|
|
68
|
+
self,
|
|
69
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
70
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
71
|
+
/,
|
|
72
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2]]: ...
|
|
73
|
+
|
|
74
|
+
@overload
|
|
75
|
+
def select(
|
|
76
|
+
self,
|
|
77
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
78
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
79
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
80
|
+
/,
|
|
81
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2, Value3]]: ...
|
|
82
|
+
|
|
83
|
+
@overload
|
|
84
|
+
def select(
|
|
85
|
+
self,
|
|
86
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
87
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
88
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
89
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
90
|
+
/,
|
|
91
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4]]: ...
|
|
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
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
101
|
+
/,
|
|
102
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
|
|
103
|
+
|
|
104
|
+
@overload
|
|
105
|
+
def select(
|
|
106
|
+
self,
|
|
107
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
108
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
109
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
110
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
111
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
112
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
113
|
+
/,
|
|
114
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
|
|
115
|
+
|
|
116
|
+
@overload
|
|
117
|
+
def select(
|
|
118
|
+
self,
|
|
119
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
120
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
121
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
122
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
123
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
124
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
125
|
+
value7: Expression[Value7] | Projection[Value7],
|
|
126
|
+
/,
|
|
127
|
+
) -> AsyncBoundSelect[
|
|
128
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]
|
|
129
|
+
]: ...
|
|
130
|
+
|
|
131
|
+
@overload
|
|
132
|
+
def select(
|
|
133
|
+
self,
|
|
134
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
135
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
136
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
137
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
138
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
139
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
140
|
+
value7: Expression[Value7] | Projection[Value7],
|
|
141
|
+
value8: Expression[Value8] | Projection[Value8],
|
|
142
|
+
/,
|
|
143
|
+
) -> AsyncBoundSelect[
|
|
144
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
|
|
145
|
+
]: ...
|
|
146
|
+
|
|
147
|
+
def select( # type: ignore[misc]
|
|
148
|
+
self,
|
|
149
|
+
*values: object,
|
|
150
|
+
) -> AsyncBoundSelect[tuple[object, ...]]:
|
|
151
|
+
build = cast("Callable[..., SelectQuery[tuple[object, ...]]]", select)
|
|
152
|
+
return AsyncBoundSelect(self._operations, build(*values))
|
|
153
|
+
|
|
154
|
+
def select_from(
|
|
155
|
+
self, table: ColumnedTable, /
|
|
156
|
+
) -> AsyncBoundSelect[tuple[object, ...]]:
|
|
157
|
+
"""Every column the table declares, without naming them one by one.
|
|
158
|
+
|
|
159
|
+
The columns are written into the SQL rather than left as a star, so
|
|
160
|
+
adding one later cannot silently change what a caller reads back.
|
|
161
|
+
Values arrive in declared order, which is the order the generated row
|
|
162
|
+
type takes its arguments, so `.into(TableRow)` builds it.
|
|
163
|
+
"""
|
|
164
|
+
return AsyncBoundSelect(self._operations, select_from(table))
|
|
165
|
+
|
|
166
|
+
@overload
|
|
167
|
+
@overload
|
|
168
|
+
def insert_into(
|
|
169
|
+
self,
|
|
170
|
+
table: TableLike,
|
|
171
|
+
/,
|
|
172
|
+
column1: Expression[Value1],
|
|
173
|
+
) -> AsyncBoundInsert[Value1]: ...
|
|
174
|
+
|
|
175
|
+
@overload
|
|
176
|
+
def insert_into(
|
|
177
|
+
self,
|
|
178
|
+
table: TableLike,
|
|
179
|
+
/,
|
|
180
|
+
column1: Expression[Value1],
|
|
181
|
+
column2: Expression[Value2],
|
|
182
|
+
) -> AsyncBoundInsert[Value1, Value2]: ...
|
|
183
|
+
|
|
184
|
+
@overload
|
|
185
|
+
def insert_into(
|
|
186
|
+
self,
|
|
187
|
+
table: TableLike,
|
|
188
|
+
/,
|
|
189
|
+
column1: Expression[Value1],
|
|
190
|
+
column2: Expression[Value2],
|
|
191
|
+
column3: Expression[Value3],
|
|
192
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3]: ...
|
|
193
|
+
|
|
194
|
+
@overload
|
|
195
|
+
def insert_into(
|
|
196
|
+
self,
|
|
197
|
+
table: TableLike,
|
|
198
|
+
/,
|
|
199
|
+
column1: Expression[Value1],
|
|
200
|
+
column2: Expression[Value2],
|
|
201
|
+
column3: Expression[Value3],
|
|
202
|
+
column4: Expression[Value4],
|
|
203
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3, Value4]: ...
|
|
204
|
+
|
|
205
|
+
@overload
|
|
206
|
+
def insert_into(
|
|
207
|
+
self,
|
|
208
|
+
table: TableLike,
|
|
209
|
+
/,
|
|
210
|
+
column1: Expression[Value1],
|
|
211
|
+
column2: Expression[Value2],
|
|
212
|
+
column3: Expression[Value3],
|
|
213
|
+
column4: Expression[Value4],
|
|
214
|
+
column5: Expression[Value5],
|
|
215
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5]: ...
|
|
216
|
+
|
|
217
|
+
@overload
|
|
218
|
+
def insert_into(
|
|
219
|
+
self,
|
|
220
|
+
table: TableLike,
|
|
221
|
+
/,
|
|
222
|
+
column1: Expression[Value1],
|
|
223
|
+
column2: Expression[Value2],
|
|
224
|
+
column3: Expression[Value3],
|
|
225
|
+
column4: Expression[Value4],
|
|
226
|
+
column5: Expression[Value5],
|
|
227
|
+
column6: Expression[Value6],
|
|
228
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5, Value6]: ...
|
|
229
|
+
|
|
230
|
+
@overload
|
|
231
|
+
def insert_into(
|
|
232
|
+
self,
|
|
233
|
+
table: TableLike,
|
|
234
|
+
/,
|
|
235
|
+
column1: Expression[Value1],
|
|
236
|
+
column2: Expression[Value2],
|
|
237
|
+
column3: Expression[Value3],
|
|
238
|
+
column4: Expression[Value4],
|
|
239
|
+
column5: Expression[Value5],
|
|
240
|
+
column6: Expression[Value6],
|
|
241
|
+
column7: Expression[Value7],
|
|
242
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5, Value6, Value7]: ...
|
|
243
|
+
|
|
244
|
+
@overload
|
|
245
|
+
def insert_into(
|
|
246
|
+
self,
|
|
247
|
+
table: TableLike,
|
|
248
|
+
/,
|
|
249
|
+
column1: Expression[Value1],
|
|
250
|
+
column2: Expression[Value2],
|
|
251
|
+
column3: Expression[Value3],
|
|
252
|
+
column4: Expression[Value4],
|
|
253
|
+
column5: Expression[Value5],
|
|
254
|
+
column6: Expression[Value6],
|
|
255
|
+
column7: Expression[Value7],
|
|
256
|
+
column8: Expression[Value8],
|
|
257
|
+
) -> AsyncBoundInsert[
|
|
258
|
+
Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8
|
|
259
|
+
]: ...
|
|
260
|
+
|
|
261
|
+
def insert_into( # type: ignore[misc]
|
|
262
|
+
self,
|
|
263
|
+
table: TableLike,
|
|
264
|
+
/,
|
|
265
|
+
*columns: object,
|
|
266
|
+
) -> AsyncBoundInsert[*tuple[object, ...]]:
|
|
267
|
+
build = cast("Callable[..., InsertStatement[*tuple[object, ...]]]", insert_into)
|
|
268
|
+
return AsyncBoundInsert(self._operations, build(table, *columns))
|
|
269
|
+
|
|
270
|
+
def update(self, table: TableLike, /) -> AsyncBoundUpdate:
|
|
271
|
+
return AsyncBoundUpdate(self._operations, update(table))
|
|
272
|
+
|
|
273
|
+
def delete_from(self, table: TableLike, /) -> AsyncBoundDelete:
|
|
274
|
+
return AsyncBoundDelete(self._operations, delete_from(table))
|
|
275
|
+
|
|
276
|
+
async def fetch(
|
|
277
|
+
self,
|
|
278
|
+
query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
|
|
279
|
+
/,
|
|
280
|
+
) -> Result[ResultRow]:
|
|
281
|
+
"""Run something built the ordinary way, and read it the same way."""
|
|
282
|
+
rows = tuple(await self._operations.many(query))
|
|
283
|
+
return Result(rows, _labels(query))
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _labels(
|
|
287
|
+
query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
|
|
288
|
+
) -> tuple[str | None, ...]:
|
|
289
|
+
if isinstance(query, ReturningStatement):
|
|
290
|
+
return labels_of(query.node.returning)
|
|
291
|
+
node = query.node
|
|
292
|
+
return labels_of(node.projections) if isinstance(node, SelectNode) else ()
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
__all__ = ("AsyncDslContext",)
|
pyoq/dsl/aio/queries.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""The asynchronous counterpart of a query bound to its database.
|
|
2
|
+
|
|
3
|
+
Only the ending differs. A chain is built the same way and describes the same
|
|
4
|
+
statement; the fetch at the end is awaited because the database is.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import TYPE_CHECKING, Generic, TypeVar
|
|
11
|
+
|
|
12
|
+
from pyoq.dsl.labels import labels_of
|
|
13
|
+
from pyoq.dsl.result import Result, Row
|
|
14
|
+
from pyoq.query.select import JoinStep, Query, SelectQuery, SemiJoinStep
|
|
15
|
+
from pyoq.query.select_nodes import SelectNode, SetNode
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from pyoq.descriptors import RelationshipDescriptor
|
|
19
|
+
from pyoq.query.execution.aio import AsyncQueryOperations
|
|
20
|
+
from pyoq.query.expressions import Expression
|
|
21
|
+
from pyoq.query.sources import CommonTable, SourceBase, TableLike
|
|
22
|
+
from pyoq.query.writes import ColumnReference
|
|
23
|
+
|
|
24
|
+
ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
|
|
25
|
+
Mapped = TypeVar("Mapped")
|
|
26
|
+
ScalarValue = TypeVar("ScalarValue")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AsyncBoundQuery(Generic[ResultRow]):
|
|
30
|
+
"""A query and the database it will run against."""
|
|
31
|
+
|
|
32
|
+
__slots__ = ("_operations", "_query")
|
|
33
|
+
_operations: AsyncQueryOperations
|
|
34
|
+
_query: Query[ResultRow]
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
operations: AsyncQueryOperations,
|
|
39
|
+
query: Query[ResultRow],
|
|
40
|
+
) -> None:
|
|
41
|
+
object.__setattr__(self, "_operations", operations)
|
|
42
|
+
object.__setattr__(self, "_query", query)
|
|
43
|
+
|
|
44
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
45
|
+
message = f"{type(self).__name__} is immutable"
|
|
46
|
+
raise AttributeError(message, name, value)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def query(self) -> Query[ResultRow]:
|
|
50
|
+
"""The query on its own, which cannot reach a database."""
|
|
51
|
+
return self._query
|
|
52
|
+
|
|
53
|
+
def order_by(self, *terms: object) -> AsyncBoundQuery[ResultRow]:
|
|
54
|
+
return self._with(self._query.order_by(*terms))
|
|
55
|
+
|
|
56
|
+
def limit(self, value: int, /) -> AsyncBoundQuery[ResultRow]:
|
|
57
|
+
return self._with(self._query.limit(value))
|
|
58
|
+
|
|
59
|
+
def offset(self, value: int, /) -> AsyncBoundQuery[ResultRow]:
|
|
60
|
+
return self._with(self._query.offset(value))
|
|
61
|
+
|
|
62
|
+
def union(self, other: Query[ResultRow], /) -> AsyncBoundQuery[ResultRow]:
|
|
63
|
+
return self._with(self._query.union(other))
|
|
64
|
+
|
|
65
|
+
def union_all(self, other: Query[ResultRow], /) -> AsyncBoundQuery[ResultRow]:
|
|
66
|
+
return self._with(self._query.union_all(other))
|
|
67
|
+
|
|
68
|
+
def intersect(self, other: Query[ResultRow], /) -> AsyncBoundQuery[ResultRow]:
|
|
69
|
+
return self._with(self._query.intersect(other))
|
|
70
|
+
|
|
71
|
+
def except_(self, other: Query[ResultRow], /) -> AsyncBoundQuery[ResultRow]:
|
|
72
|
+
return self._with(self._query.except_(other))
|
|
73
|
+
|
|
74
|
+
async def fetch_all(self) -> Result[ResultRow]:
|
|
75
|
+
rows = tuple(await self._operations.many(self._query))
|
|
76
|
+
return Result(rows, _labels(self._query))
|
|
77
|
+
|
|
78
|
+
async def fetch_one(self) -> Row[ResultRow]:
|
|
79
|
+
"""Exactly one row, because that is what the caller said to expect."""
|
|
80
|
+
return Row(await self._operations.one(self._query), _labels(self._query))
|
|
81
|
+
|
|
82
|
+
async def fetch_optional(self) -> Row[ResultRow] | None:
|
|
83
|
+
row = await self._operations.one_or_none(self._query)
|
|
84
|
+
return None if row is None else Row(row, _labels(self._query))
|
|
85
|
+
|
|
86
|
+
async def fetch_optional_into(
|
|
87
|
+
self,
|
|
88
|
+
factory: Callable[..., Mapped],
|
|
89
|
+
/,
|
|
90
|
+
) -> Mapped | None:
|
|
91
|
+
"""One row built by the caller's own type, or nothing.
|
|
92
|
+
|
|
93
|
+
The row and the many-row result both take a factory inline.
|
|
94
|
+
A row that may be missing had to be tested for first, which
|
|
95
|
+
is the one shape that could not be asked for in the chain.
|
|
96
|
+
"""
|
|
97
|
+
row = await self.fetch_optional()
|
|
98
|
+
return None if row is None else row.into(factory)
|
|
99
|
+
|
|
100
|
+
async def fetch_value(
|
|
101
|
+
self: AsyncBoundQuery[tuple[ScalarValue]],
|
|
102
|
+
/,
|
|
103
|
+
) -> ScalarValue:
|
|
104
|
+
"""The single column of the single row.
|
|
105
|
+
|
|
106
|
+
Only a query that selected one column can answer this, which is why the
|
|
107
|
+
annotation says so rather than a check at run time.
|
|
108
|
+
"""
|
|
109
|
+
return await self._operations.scalar(self._query)
|
|
110
|
+
|
|
111
|
+
def _with(self, query: Query[ResultRow]) -> AsyncBoundQuery[ResultRow]:
|
|
112
|
+
return AsyncBoundQuery(self._operations, query)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class AsyncBoundSelect(AsyncBoundQuery[ResultRow], Generic[ResultRow]):
|
|
116
|
+
"""A SELECT that knows its database, with the builder's own shape kept."""
|
|
117
|
+
|
|
118
|
+
__slots__ = ()
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def query(self) -> SelectQuery[ResultRow]:
|
|
122
|
+
return _as_select(self._query)
|
|
123
|
+
|
|
124
|
+
def distinct(self, enabled: bool = True, /) -> AsyncBoundSelect[ResultRow]:
|
|
125
|
+
return self._select(self.query.distinct(enabled))
|
|
126
|
+
|
|
127
|
+
def from_(
|
|
128
|
+
self,
|
|
129
|
+
source: SourceBase | TableLike,
|
|
130
|
+
/,
|
|
131
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
132
|
+
return self._select(self.query.from_(source))
|
|
133
|
+
|
|
134
|
+
def join(self, source: SourceBase | TableLike, /) -> AsyncBoundJoinStep[ResultRow]:
|
|
135
|
+
"""An inner join, qualified by the clause that follows it."""
|
|
136
|
+
return AsyncBoundJoinStep(self._select, self.query.join(source))
|
|
137
|
+
|
|
138
|
+
def inner_join(
|
|
139
|
+
self,
|
|
140
|
+
source: SourceBase | TableLike,
|
|
141
|
+
/,
|
|
142
|
+
) -> AsyncBoundJoinStep[ResultRow]:
|
|
143
|
+
return AsyncBoundJoinStep(self._select, self.query.inner_join(source))
|
|
144
|
+
|
|
145
|
+
def left_join(
|
|
146
|
+
self,
|
|
147
|
+
source: SourceBase | TableLike,
|
|
148
|
+
/,
|
|
149
|
+
) -> AsyncBoundJoinStep[ResultRow]:
|
|
150
|
+
return AsyncBoundJoinStep(self._select, self.query.left_join(source))
|
|
151
|
+
|
|
152
|
+
def right_join(
|
|
153
|
+
self,
|
|
154
|
+
source: SourceBase | TableLike,
|
|
155
|
+
/,
|
|
156
|
+
) -> AsyncBoundJoinStep[ResultRow]:
|
|
157
|
+
return AsyncBoundJoinStep(self._select, self.query.right_join(source))
|
|
158
|
+
|
|
159
|
+
def full_join(
|
|
160
|
+
self,
|
|
161
|
+
source: SourceBase | TableLike,
|
|
162
|
+
/,
|
|
163
|
+
) -> AsyncBoundJoinStep[ResultRow]:
|
|
164
|
+
return AsyncBoundJoinStep(self._select, self.query.full_join(source))
|
|
165
|
+
|
|
166
|
+
def semi_join(
|
|
167
|
+
self,
|
|
168
|
+
source: SourceBase | TableLike,
|
|
169
|
+
/,
|
|
170
|
+
) -> AsyncBoundSemiJoinStep[ResultRow]:
|
|
171
|
+
"""Rows that have a match, without bringing the match back."""
|
|
172
|
+
return AsyncBoundSemiJoinStep(self._select, self.query.semi_join(source))
|
|
173
|
+
|
|
174
|
+
def anti_join(
|
|
175
|
+
self,
|
|
176
|
+
source: SourceBase | TableLike,
|
|
177
|
+
/,
|
|
178
|
+
) -> AsyncBoundSemiJoinStep[ResultRow]:
|
|
179
|
+
"""Rows that have no match."""
|
|
180
|
+
return AsyncBoundSemiJoinStep(self._select, self.query.anti_join(source))
|
|
181
|
+
|
|
182
|
+
def cross_join(
|
|
183
|
+
self,
|
|
184
|
+
source: SourceBase | TableLike,
|
|
185
|
+
/,
|
|
186
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
187
|
+
return self._select(self.query.cross_join(source))
|
|
188
|
+
|
|
189
|
+
def natural_join(
|
|
190
|
+
self,
|
|
191
|
+
source: SourceBase | TableLike,
|
|
192
|
+
/,
|
|
193
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
194
|
+
return self._select(self.query.natural_join(source))
|
|
195
|
+
|
|
196
|
+
def natural_left_join(
|
|
197
|
+
self,
|
|
198
|
+
source: SourceBase | TableLike,
|
|
199
|
+
/,
|
|
200
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
201
|
+
return self._select(self.query.natural_left_join(source))
|
|
202
|
+
|
|
203
|
+
def natural_right_join(
|
|
204
|
+
self,
|
|
205
|
+
source: SourceBase | TableLike,
|
|
206
|
+
/,
|
|
207
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
208
|
+
return self._select(self.query.natural_right_join(source))
|
|
209
|
+
|
|
210
|
+
def natural_full_join(
|
|
211
|
+
self,
|
|
212
|
+
source: SourceBase | TableLike,
|
|
213
|
+
/,
|
|
214
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
215
|
+
return self._select(self.query.natural_full_join(source))
|
|
216
|
+
|
|
217
|
+
def where(
|
|
218
|
+
self,
|
|
219
|
+
condition: Expression[bool],
|
|
220
|
+
/,
|
|
221
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
222
|
+
return self._select(self.query.where(condition))
|
|
223
|
+
|
|
224
|
+
def group_by(self, *expressions: object) -> AsyncBoundSelect[ResultRow]:
|
|
225
|
+
return self._select(self.query.group_by(*expressions))
|
|
226
|
+
|
|
227
|
+
def having(
|
|
228
|
+
self,
|
|
229
|
+
condition: Expression[bool],
|
|
230
|
+
/,
|
|
231
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
232
|
+
return self._select(self.query.having(condition))
|
|
233
|
+
|
|
234
|
+
def with_(
|
|
235
|
+
self,
|
|
236
|
+
*common_tables: CommonTable[object],
|
|
237
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
238
|
+
return self._select(self.query.with_(*common_tables))
|
|
239
|
+
|
|
240
|
+
def order_by(self, *terms: object) -> AsyncBoundSelect[ResultRow]:
|
|
241
|
+
return self._select(self.query.order_by(*terms))
|
|
242
|
+
|
|
243
|
+
def limit(self, value: int, /) -> AsyncBoundSelect[ResultRow]:
|
|
244
|
+
return self._select(self.query.limit(value))
|
|
245
|
+
|
|
246
|
+
def offset(self, value: int, /) -> AsyncBoundSelect[ResultRow]:
|
|
247
|
+
return self._select(self.query.offset(value))
|
|
248
|
+
|
|
249
|
+
def _select(
|
|
250
|
+
self,
|
|
251
|
+
query: SelectQuery[ResultRow],
|
|
252
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
253
|
+
return AsyncBoundSelect(self._operations, query)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _as_select(query: Query[ResultRow]) -> SelectQuery[ResultRow]:
|
|
257
|
+
if not isinstance(query, SelectQuery):
|
|
258
|
+
message = "the query is no longer a SELECT"
|
|
259
|
+
raise TypeError(message)
|
|
260
|
+
return query
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _labels(query: Query[ResultRow]) -> tuple[str | None, ...]:
|
|
264
|
+
"""A set operation takes its names from the first query in it."""
|
|
265
|
+
select = _first_select(query.node)
|
|
266
|
+
return () if select is None else labels_of(select.projections)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _first_select(node: object) -> SelectNode | None:
|
|
270
|
+
while isinstance(node, SetNode):
|
|
271
|
+
node = node.left
|
|
272
|
+
return node if isinstance(node, SelectNode) else None
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class AsyncBoundJoinStep(Generic[ResultRow]):
|
|
276
|
+
"""A bound join waiting to be told how the two tables line up."""
|
|
277
|
+
|
|
278
|
+
__slots__ = ("_bind", "_step")
|
|
279
|
+
_bind: Callable[[SelectQuery[ResultRow]], AsyncBoundSelect[ResultRow]]
|
|
280
|
+
_step: JoinStep[ResultRow]
|
|
281
|
+
|
|
282
|
+
def __init__(
|
|
283
|
+
self,
|
|
284
|
+
bind: Callable[[SelectQuery[ResultRow]], AsyncBoundSelect[ResultRow]],
|
|
285
|
+
step: JoinStep[ResultRow],
|
|
286
|
+
) -> None:
|
|
287
|
+
object.__setattr__(self, "_bind", bind)
|
|
288
|
+
object.__setattr__(self, "_step", step)
|
|
289
|
+
|
|
290
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
291
|
+
message = "join steps are immutable"
|
|
292
|
+
raise AttributeError(message, name, value)
|
|
293
|
+
|
|
294
|
+
def on(self, condition: Expression[bool], /) -> AsyncBoundSelect[ResultRow]:
|
|
295
|
+
"""The predicate the rows are matched by."""
|
|
296
|
+
return self._bind(self._step.on(condition))
|
|
297
|
+
|
|
298
|
+
def using(self, *columns: str | ColumnReference) -> AsyncBoundSelect[ResultRow]:
|
|
299
|
+
"""Matched on columns both tables name the same way."""
|
|
300
|
+
return self._bind(self._step.using(*columns))
|
|
301
|
+
|
|
302
|
+
def on_key(
|
|
303
|
+
self,
|
|
304
|
+
relation: RelationshipDescriptor[object, object],
|
|
305
|
+
/,
|
|
306
|
+
) -> AsyncBoundSelect[ResultRow]:
|
|
307
|
+
"""Matched on the foreign key the schema already declares."""
|
|
308
|
+
return self._bind(self._step.on_key(relation))
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class AsyncBoundSemiJoinStep(Generic[ResultRow]):
|
|
312
|
+
"""A bound semi or anti join waiting for its predicate."""
|
|
313
|
+
|
|
314
|
+
__slots__ = ("_bind", "_step")
|
|
315
|
+
_bind: Callable[[SelectQuery[ResultRow]], AsyncBoundSelect[ResultRow]]
|
|
316
|
+
_step: SemiJoinStep[ResultRow]
|
|
317
|
+
|
|
318
|
+
def __init__(
|
|
319
|
+
self,
|
|
320
|
+
bind: Callable[[SelectQuery[ResultRow]], AsyncBoundSelect[ResultRow]],
|
|
321
|
+
step: SemiJoinStep[ResultRow],
|
|
322
|
+
) -> None:
|
|
323
|
+
object.__setattr__(self, "_bind", bind)
|
|
324
|
+
object.__setattr__(self, "_step", step)
|
|
325
|
+
|
|
326
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
327
|
+
message = "join steps are immutable"
|
|
328
|
+
raise AttributeError(message, name, value)
|
|
329
|
+
|
|
330
|
+
def on(self, condition: Expression[bool], /) -> AsyncBoundSelect[ResultRow]:
|
|
331
|
+
"""The predicate a row is matched by."""
|
|
332
|
+
return self._bind(self._step.on(condition))
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
__all__ = ("AsyncBoundQuery", "AsyncBoundSelect")
|