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.
- pyoq/__init__.py +10 -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 +31 -0
- pyoq/config/connection.py +161 -0
- pyoq/config/loader.py +289 -0
- pyoq/config/models.py +245 -0
- pyoq/config/values.py +142 -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 +73 -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 +63 -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 +23 -0
- pyoq/serving/databases.py +107 -0
- pyoq/serving/opening.py +331 -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.2.dist-info/METADATA +3050 -0
- pyoq_sql-1.0.2.dist-info/RECORD +267 -0
- pyoq_sql-1.0.2.dist-info/WHEEL +4 -0
- pyoq_sql-1.0.2.dist-info/entry_points.txt +3 -0
- pyoq_sql-1.0.2.dist-info/licenses/LICENSE +373 -0
pyoq/query/writes.py
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
"""Typed immutable write statement construction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from dataclasses import dataclass, replace
|
|
7
|
+
from typing import (
|
|
8
|
+
Generic,
|
|
9
|
+
Protocol,
|
|
10
|
+
Self,
|
|
11
|
+
TypeVar,
|
|
12
|
+
TypeVarTuple,
|
|
13
|
+
overload,
|
|
14
|
+
runtime_checkable,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from pyoq.errors import QueryStateError, QueryValidationError
|
|
18
|
+
from pyoq.query.expressions import Expression, require_boolean
|
|
19
|
+
from pyoq.query.nodes import ExpressionNode, FieldNode, ScalarFamily
|
|
20
|
+
from pyoq.query.projections import Projection, projection_node
|
|
21
|
+
from pyoq.query.select_nodes import ProjectionNode
|
|
22
|
+
from pyoq.query.sources import TableLike, write_target
|
|
23
|
+
from pyoq.query.values import bind
|
|
24
|
+
from pyoq.query.write_nodes import (
|
|
25
|
+
AssignmentNode,
|
|
26
|
+
ConflictAction,
|
|
27
|
+
ConflictNode,
|
|
28
|
+
DeleteNode,
|
|
29
|
+
InsertNode,
|
|
30
|
+
UpdateNode,
|
|
31
|
+
WriteNode,
|
|
32
|
+
)
|
|
33
|
+
from pyoq.unset import UnsetType
|
|
34
|
+
|
|
35
|
+
Values = TypeVarTuple("Values")
|
|
36
|
+
NodeType = TypeVar("NodeType", bound=WriteNode, covariant=True)
|
|
37
|
+
ScopedNode = TypeVar("ScopedNode", bound="UpdateNode | DeleteNode")
|
|
38
|
+
AssignedValue = TypeVar("AssignedValue")
|
|
39
|
+
_EXCLUDED_ROW = "excluded"
|
|
40
|
+
ResultRow = TypeVar("ResultRow")
|
|
41
|
+
Value1 = TypeVar("Value1")
|
|
42
|
+
Value2 = TypeVar("Value2")
|
|
43
|
+
Value3 = TypeVar("Value3")
|
|
44
|
+
Value4 = TypeVar("Value4")
|
|
45
|
+
Value5 = TypeVar("Value5")
|
|
46
|
+
Value6 = TypeVar("Value6")
|
|
47
|
+
Value7 = TypeVar("Value7")
|
|
48
|
+
Value8 = TypeVar("Value8")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@runtime_checkable
|
|
52
|
+
class ColumnReference(Protocol):
|
|
53
|
+
@property
|
|
54
|
+
def node(self) -> ExpressionNode: ...
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def family(self) -> ScalarFamily: ...
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@runtime_checkable
|
|
61
|
+
class WriteColumn(Protocol):
|
|
62
|
+
@property
|
|
63
|
+
def writable(self) -> bool: ...
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def generated(self) -> bool: ...
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def field_name(self) -> str | None: ...
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True, slots=True)
|
|
73
|
+
class ColumnBinding:
|
|
74
|
+
database_name: str
|
|
75
|
+
field_name: str | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class WriteStatement(Generic[NodeType]):
|
|
79
|
+
__slots__ = ("_node",)
|
|
80
|
+
_node: NodeType
|
|
81
|
+
|
|
82
|
+
def __init__(self, node: NodeType) -> None:
|
|
83
|
+
object.__setattr__(self, "_node", node)
|
|
84
|
+
|
|
85
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
86
|
+
message = "write statements are immutable"
|
|
87
|
+
raise AttributeError(message)
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def node(self) -> NodeType:
|
|
91
|
+
return self._node
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ReturningStatement(WriteStatement[WriteNode], Generic[ResultRow]):
|
|
95
|
+
__slots__ = ()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class MutationStatement(WriteStatement[NodeType], Generic[NodeType]):
|
|
99
|
+
__slots__ = ()
|
|
100
|
+
|
|
101
|
+
def returning_all(self) -> ReturningStatement[tuple[object, ...]]:
|
|
102
|
+
"""Every column the table declares, without listing them.
|
|
103
|
+
|
|
104
|
+
Generation writes the column list onto the table, so the statement
|
|
105
|
+
already knows what they are. A table written by hand declares none,
|
|
106
|
+
and is asked to name them.
|
|
107
|
+
"""
|
|
108
|
+
target = self.node.target
|
|
109
|
+
if not target.columns:
|
|
110
|
+
message = (
|
|
111
|
+
"table names no columns to return; generated tables declare "
|
|
112
|
+
"them, and any other table has to name them"
|
|
113
|
+
)
|
|
114
|
+
raise QueryValidationError(message)
|
|
115
|
+
projections = tuple(
|
|
116
|
+
ProjectionNode(FieldNode(name, target.name, target.schema, target.catalog))
|
|
117
|
+
for name in target.columns
|
|
118
|
+
)
|
|
119
|
+
return ReturningStatement(replace(self.node, returning=projections))
|
|
120
|
+
|
|
121
|
+
@overload
|
|
122
|
+
def returning(
|
|
123
|
+
self,
|
|
124
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
125
|
+
/,
|
|
126
|
+
) -> ReturningStatement[tuple[Value1]]: ...
|
|
127
|
+
|
|
128
|
+
@overload
|
|
129
|
+
def returning(
|
|
130
|
+
self,
|
|
131
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
132
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
133
|
+
/,
|
|
134
|
+
) -> ReturningStatement[tuple[Value1, Value2]]: ...
|
|
135
|
+
|
|
136
|
+
@overload
|
|
137
|
+
def returning(
|
|
138
|
+
self,
|
|
139
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
140
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
141
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
142
|
+
/,
|
|
143
|
+
) -> ReturningStatement[tuple[Value1, Value2, Value3]]: ...
|
|
144
|
+
|
|
145
|
+
@overload
|
|
146
|
+
def returning(
|
|
147
|
+
self,
|
|
148
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
149
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
150
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
151
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
152
|
+
/,
|
|
153
|
+
) -> ReturningStatement[tuple[Value1, Value2, Value3, Value4]]: ...
|
|
154
|
+
|
|
155
|
+
@overload
|
|
156
|
+
def returning(
|
|
157
|
+
self,
|
|
158
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
159
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
160
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
161
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
162
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
163
|
+
/,
|
|
164
|
+
) -> ReturningStatement[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
|
|
165
|
+
|
|
166
|
+
@overload
|
|
167
|
+
def returning(
|
|
168
|
+
self,
|
|
169
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
170
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
171
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
172
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
173
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
174
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
175
|
+
/,
|
|
176
|
+
) -> ReturningStatement[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
|
|
177
|
+
|
|
178
|
+
@overload
|
|
179
|
+
def returning(
|
|
180
|
+
self,
|
|
181
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
182
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
183
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
184
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
185
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
186
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
187
|
+
value7: Expression[Value7] | Projection[Value7],
|
|
188
|
+
/,
|
|
189
|
+
) -> ReturningStatement[
|
|
190
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]
|
|
191
|
+
]: ...
|
|
192
|
+
|
|
193
|
+
@overload
|
|
194
|
+
def returning(
|
|
195
|
+
self,
|
|
196
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
197
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
198
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
199
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
200
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
201
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
202
|
+
value7: Expression[Value7] | Projection[Value7],
|
|
203
|
+
value8: Expression[Value8] | Projection[Value8],
|
|
204
|
+
/,
|
|
205
|
+
) -> ReturningStatement[
|
|
206
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
|
|
207
|
+
]: ...
|
|
208
|
+
|
|
209
|
+
def returning( # type: ignore[misc]
|
|
210
|
+
self,
|
|
211
|
+
*values: object,
|
|
212
|
+
) -> ReturningStatement[object]:
|
|
213
|
+
if not values:
|
|
214
|
+
message = "RETURNING requires at least one projection"
|
|
215
|
+
raise QueryValidationError(message)
|
|
216
|
+
projections = tuple(projection_node(value) for value in values)
|
|
217
|
+
return ReturningStatement(replace(self.node, returning=projections))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class ScopedStatement(MutationStatement[ScopedNode], Generic[ScopedNode]):
|
|
221
|
+
__slots__ = ()
|
|
222
|
+
|
|
223
|
+
def where(self, condition: Expression[bool], /) -> Self:
|
|
224
|
+
require_boolean(condition, "WHERE")
|
|
225
|
+
node = self.node
|
|
226
|
+
if node.condition is not None:
|
|
227
|
+
message = "WHERE condition is already defined"
|
|
228
|
+
raise QueryStateError(message)
|
|
229
|
+
if node.unconditional:
|
|
230
|
+
message = "a full-table statement cannot also define a WHERE condition"
|
|
231
|
+
raise QueryStateError(message)
|
|
232
|
+
return type(self)(replace(node, condition=condition.node))
|
|
233
|
+
|
|
234
|
+
def all_rows(self) -> Self:
|
|
235
|
+
node = self.node
|
|
236
|
+
if node.condition is not None:
|
|
237
|
+
message = "a conditional statement cannot also target every row"
|
|
238
|
+
raise QueryStateError(message)
|
|
239
|
+
return type(self)(replace(node, unconditional=True))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class InsertStatement(MutationStatement[InsertNode], Generic[*Values]):
|
|
243
|
+
__slots__ = ("_columns",)
|
|
244
|
+
_columns: tuple[ColumnBinding, ...]
|
|
245
|
+
|
|
246
|
+
def __init__(
|
|
247
|
+
self,
|
|
248
|
+
node: InsertNode,
|
|
249
|
+
columns: tuple[ColumnBinding, ...],
|
|
250
|
+
) -> None:
|
|
251
|
+
super().__init__(node)
|
|
252
|
+
object.__setattr__(self, "_columns", columns)
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def columns(self) -> tuple[ColumnBinding, ...]:
|
|
256
|
+
return self._columns
|
|
257
|
+
|
|
258
|
+
def values(self, *values: *Values) -> InsertStatement[*Values]:
|
|
259
|
+
row = _value_row(values, len(self._columns))
|
|
260
|
+
return self._with_rows((row,))
|
|
261
|
+
|
|
262
|
+
def values_many(self, rows: Iterable[object], /) -> InsertStatement[*Values]:
|
|
263
|
+
additions = tuple(_generated_row(row, self._columns) for row in rows)
|
|
264
|
+
if not additions:
|
|
265
|
+
message = "values_many requires at least one insert value"
|
|
266
|
+
raise QueryValidationError(message)
|
|
267
|
+
return self._with_rows(additions)
|
|
268
|
+
|
|
269
|
+
def on_conflict_do_nothing(
|
|
270
|
+
self,
|
|
271
|
+
*targets: ColumnReference,
|
|
272
|
+
) -> InsertStatement[*Values]:
|
|
273
|
+
conflict = ConflictNode(
|
|
274
|
+
ConflictAction.NOTHING,
|
|
275
|
+
self._conflict_targets(targets),
|
|
276
|
+
)
|
|
277
|
+
return InsertStatement[*Values](self._with_conflict(conflict), self._columns)
|
|
278
|
+
|
|
279
|
+
def on_conflict_do_update(
|
|
280
|
+
self,
|
|
281
|
+
*targets: ColumnReference,
|
|
282
|
+
) -> ConflictUpdate[*Values]:
|
|
283
|
+
conflict = ConflictNode(
|
|
284
|
+
ConflictAction.UPDATE,
|
|
285
|
+
self._conflict_targets(targets),
|
|
286
|
+
)
|
|
287
|
+
return ConflictUpdate[*Values](self._with_conflict(conflict), self._columns)
|
|
288
|
+
|
|
289
|
+
def _with_conflict(self, conflict: ConflictNode) -> InsertNode:
|
|
290
|
+
node = self.node
|
|
291
|
+
if node.conflict is not None:
|
|
292
|
+
message = "conflict resolution is already defined"
|
|
293
|
+
raise QueryStateError(message)
|
|
294
|
+
return replace(node, conflict=conflict)
|
|
295
|
+
|
|
296
|
+
def _conflict_targets(
|
|
297
|
+
self,
|
|
298
|
+
targets: tuple[ColumnReference, ...],
|
|
299
|
+
) -> tuple[str, ...]:
|
|
300
|
+
selected = frozenset(binding.database_name for binding in self._columns)
|
|
301
|
+
names: list[str] = []
|
|
302
|
+
for target in targets:
|
|
303
|
+
name = _write_column(target, "ON CONFLICT").database_name
|
|
304
|
+
if name not in selected:
|
|
305
|
+
message = f"conflict target is not an inserted column: {name}"
|
|
306
|
+
raise QueryValidationError(message)
|
|
307
|
+
names.append(name)
|
|
308
|
+
return tuple(names)
|
|
309
|
+
|
|
310
|
+
def _with_rows(
|
|
311
|
+
self,
|
|
312
|
+
rows: tuple[tuple[ExpressionNode, ...], ...],
|
|
313
|
+
) -> InsertStatement[*Values]:
|
|
314
|
+
node = self.node
|
|
315
|
+
return InsertStatement[*Values](
|
|
316
|
+
replace(node, rows=(*node.rows, *rows)),
|
|
317
|
+
self._columns,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class ConflictUpdate(InsertStatement[*Values], Generic[*Values]):
|
|
322
|
+
__slots__ = ()
|
|
323
|
+
|
|
324
|
+
def set(
|
|
325
|
+
self,
|
|
326
|
+
column: Expression[AssignedValue],
|
|
327
|
+
value: AssignedValue | Expression[AssignedValue],
|
|
328
|
+
/,
|
|
329
|
+
) -> ConflictUpdate[*Values]:
|
|
330
|
+
conflict = self._conflict
|
|
331
|
+
return self._with_resolution(
|
|
332
|
+
replace(
|
|
333
|
+
conflict,
|
|
334
|
+
assignments=(*conflict.assignments, _assignment(column, value)),
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def where(self, condition: Expression[bool], /) -> ConflictUpdate[*Values]:
|
|
339
|
+
require_boolean(condition, "ON CONFLICT")
|
|
340
|
+
conflict = self._conflict
|
|
341
|
+
if conflict.condition is not None:
|
|
342
|
+
message = "conflict update condition is already defined"
|
|
343
|
+
raise QueryStateError(message)
|
|
344
|
+
return self._with_resolution(replace(conflict, condition=condition.node))
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def _conflict(self) -> ConflictNode:
|
|
348
|
+
conflict = self.node.conflict
|
|
349
|
+
if conflict is None:
|
|
350
|
+
message = "conflict update statement has no conflict resolution"
|
|
351
|
+
raise QueryStateError(message)
|
|
352
|
+
return conflict
|
|
353
|
+
|
|
354
|
+
def _with_resolution(self, conflict: ConflictNode) -> ConflictUpdate[*Values]:
|
|
355
|
+
return ConflictUpdate[*Values](
|
|
356
|
+
replace(self.node, conflict=conflict),
|
|
357
|
+
self._columns,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class UpdateStatement(ScopedStatement[UpdateNode]):
|
|
362
|
+
__slots__ = ()
|
|
363
|
+
|
|
364
|
+
def set(
|
|
365
|
+
self,
|
|
366
|
+
column: Expression[AssignedValue],
|
|
367
|
+
value: AssignedValue | Expression[AssignedValue],
|
|
368
|
+
/,
|
|
369
|
+
) -> UpdateStatement:
|
|
370
|
+
return self._with_assignments((_assignment(column, value),))
|
|
371
|
+
|
|
372
|
+
def set_values(
|
|
373
|
+
self,
|
|
374
|
+
values: object,
|
|
375
|
+
/,
|
|
376
|
+
*columns: ColumnReference,
|
|
377
|
+
) -> UpdateStatement:
|
|
378
|
+
if not columns:
|
|
379
|
+
message = "set_values requires at least one column"
|
|
380
|
+
raise QueryValidationError(message)
|
|
381
|
+
assignments = _generated_assignments(values, columns)
|
|
382
|
+
if not assignments:
|
|
383
|
+
message = "update value leaves every selected column unset"
|
|
384
|
+
raise QueryValidationError(message)
|
|
385
|
+
return self._with_assignments(assignments)
|
|
386
|
+
|
|
387
|
+
def _with_assignments(
|
|
388
|
+
self,
|
|
389
|
+
assignments: tuple[AssignmentNode, ...],
|
|
390
|
+
) -> UpdateStatement:
|
|
391
|
+
node = self.node
|
|
392
|
+
return UpdateStatement(
|
|
393
|
+
replace(node, assignments=(*node.assignments, *assignments))
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class DeleteStatement(ScopedStatement[DeleteNode]):
|
|
398
|
+
__slots__ = ()
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def update(table: TableLike, /) -> UpdateStatement:
|
|
402
|
+
return UpdateStatement(UpdateNode(write_target(table)))
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def delete_from(table: TableLike, /) -> DeleteStatement:
|
|
406
|
+
return DeleteStatement(DeleteNode(write_target(table)))
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _assignment(
|
|
410
|
+
column: object,
|
|
411
|
+
value: object,
|
|
412
|
+
) -> AssignmentNode:
|
|
413
|
+
binding = _write_column(column, "UPDATE")
|
|
414
|
+
return AssignmentNode(binding.database_name, _value_node(value))
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _generated_assignments(
|
|
418
|
+
values: object,
|
|
419
|
+
columns: tuple[ColumnReference, ...],
|
|
420
|
+
) -> tuple[AssignmentNode, ...]:
|
|
421
|
+
assignments: list[AssignmentNode] = []
|
|
422
|
+
for column in columns:
|
|
423
|
+
binding = _write_column(column, "UPDATE")
|
|
424
|
+
attribute = _generated_attribute(values, binding)
|
|
425
|
+
if isinstance(attribute, UnsetType):
|
|
426
|
+
continue
|
|
427
|
+
assignments.append(
|
|
428
|
+
AssignmentNode(binding.database_name, _value_node(attribute))
|
|
429
|
+
)
|
|
430
|
+
return tuple(assignments)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@overload
|
|
434
|
+
def insert_into(
|
|
435
|
+
table: TableLike,
|
|
436
|
+
column1: Expression[Value1],
|
|
437
|
+
/,
|
|
438
|
+
) -> InsertStatement[Value1]: ...
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@overload
|
|
442
|
+
def insert_into(
|
|
443
|
+
table: TableLike,
|
|
444
|
+
column1: Expression[Value1],
|
|
445
|
+
column2: Expression[Value2],
|
|
446
|
+
/,
|
|
447
|
+
) -> InsertStatement[Value1, Value2]: ...
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@overload
|
|
451
|
+
def insert_into(
|
|
452
|
+
table: TableLike,
|
|
453
|
+
column1: Expression[Value1],
|
|
454
|
+
column2: Expression[Value2],
|
|
455
|
+
column3: Expression[Value3],
|
|
456
|
+
/,
|
|
457
|
+
) -> InsertStatement[Value1, Value2, Value3]: ...
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
@overload
|
|
461
|
+
def insert_into(
|
|
462
|
+
table: TableLike,
|
|
463
|
+
column1: Expression[Value1],
|
|
464
|
+
column2: Expression[Value2],
|
|
465
|
+
column3: Expression[Value3],
|
|
466
|
+
column4: Expression[Value4],
|
|
467
|
+
/,
|
|
468
|
+
) -> InsertStatement[Value1, Value2, Value3, Value4]: ...
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@overload
|
|
472
|
+
def insert_into(
|
|
473
|
+
table: TableLike,
|
|
474
|
+
column1: Expression[Value1],
|
|
475
|
+
column2: Expression[Value2],
|
|
476
|
+
column3: Expression[Value3],
|
|
477
|
+
column4: Expression[Value4],
|
|
478
|
+
column5: Expression[Value5],
|
|
479
|
+
/,
|
|
480
|
+
) -> InsertStatement[Value1, Value2, Value3, Value4, Value5]: ...
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
@overload
|
|
484
|
+
def insert_into(
|
|
485
|
+
table: TableLike,
|
|
486
|
+
column1: Expression[Value1],
|
|
487
|
+
column2: Expression[Value2],
|
|
488
|
+
column3: Expression[Value3],
|
|
489
|
+
column4: Expression[Value4],
|
|
490
|
+
column5: Expression[Value5],
|
|
491
|
+
column6: Expression[Value6],
|
|
492
|
+
/,
|
|
493
|
+
) -> InsertStatement[Value1, Value2, Value3, Value4, Value5, Value6]: ...
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@overload
|
|
497
|
+
def insert_into(
|
|
498
|
+
table: TableLike,
|
|
499
|
+
column1: Expression[Value1],
|
|
500
|
+
column2: Expression[Value2],
|
|
501
|
+
column3: Expression[Value3],
|
|
502
|
+
column4: Expression[Value4],
|
|
503
|
+
column5: Expression[Value5],
|
|
504
|
+
column6: Expression[Value6],
|
|
505
|
+
column7: Expression[Value7],
|
|
506
|
+
/,
|
|
507
|
+
) -> InsertStatement[Value1, Value2, Value3, Value4, Value5, Value6, Value7]: ...
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
@overload
|
|
511
|
+
def insert_into(
|
|
512
|
+
table: TableLike,
|
|
513
|
+
column1: Expression[Value1],
|
|
514
|
+
column2: Expression[Value2],
|
|
515
|
+
column3: Expression[Value3],
|
|
516
|
+
column4: Expression[Value4],
|
|
517
|
+
column5: Expression[Value5],
|
|
518
|
+
column6: Expression[Value6],
|
|
519
|
+
column7: Expression[Value7],
|
|
520
|
+
column8: Expression[Value8],
|
|
521
|
+
/,
|
|
522
|
+
) -> InsertStatement[
|
|
523
|
+
Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8
|
|
524
|
+
]: ...
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def insert_into( # type: ignore[misc]
|
|
528
|
+
table: TableLike,
|
|
529
|
+
*columns: object,
|
|
530
|
+
) -> InsertStatement[object]:
|
|
531
|
+
target = write_target(table)
|
|
532
|
+
if not columns:
|
|
533
|
+
message = "INSERT requires at least one column"
|
|
534
|
+
raise QueryValidationError(message)
|
|
535
|
+
bindings = tuple(_write_column(column, "INSERT") for column in columns)
|
|
536
|
+
names = tuple(binding.database_name for binding in bindings)
|
|
537
|
+
return InsertStatement(InsertNode(target, names), bindings)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def excluded(column: Expression[AssignedValue], /) -> Expression[AssignedValue]:
|
|
541
|
+
binding = _write_column(column, "ON CONFLICT")
|
|
542
|
+
return Expression(
|
|
543
|
+
FieldNode(binding.database_name, _EXCLUDED_ROW, family=column.family),
|
|
544
|
+
column.family,
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _write_column(value: object, clause: str) -> ColumnBinding:
|
|
549
|
+
if not isinstance(value, Expression):
|
|
550
|
+
message = f"{clause} columns must be typed column expressions"
|
|
551
|
+
raise QueryValidationError(message)
|
|
552
|
+
node = value.node
|
|
553
|
+
if not isinstance(node, FieldNode):
|
|
554
|
+
message = f"{clause} columns must reference table columns"
|
|
555
|
+
raise QueryValidationError(message)
|
|
556
|
+
if not isinstance(value, WriteColumn):
|
|
557
|
+
return ColumnBinding(node.name)
|
|
558
|
+
_require_writable_column(value, node.name)
|
|
559
|
+
return ColumnBinding(node.name, value.field_name)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _require_writable_column(column: WriteColumn, name: str) -> None:
|
|
563
|
+
if column.generated:
|
|
564
|
+
message = f"generated column cannot be written: {name}"
|
|
565
|
+
raise QueryValidationError(message)
|
|
566
|
+
if not column.writable:
|
|
567
|
+
message = f"column is not writable: {name}"
|
|
568
|
+
raise QueryValidationError(message)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def _value_row(
|
|
572
|
+
values: tuple[object, ...],
|
|
573
|
+
expected: int,
|
|
574
|
+
) -> tuple[ExpressionNode, ...]:
|
|
575
|
+
if len(values) != expected:
|
|
576
|
+
message = "INSERT values must match the selected column count"
|
|
577
|
+
raise QueryValidationError(message)
|
|
578
|
+
return tuple(_value_node(value) for value in values)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _generated_row(
|
|
582
|
+
value: object,
|
|
583
|
+
columns: tuple[ColumnBinding, ...],
|
|
584
|
+
) -> tuple[ExpressionNode, ...]:
|
|
585
|
+
return tuple(_generated_value(value, column) for column in columns)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _generated_attribute(value: object, column: ColumnBinding) -> object:
|
|
589
|
+
if column.field_name is None:
|
|
590
|
+
message = "generated write values require generated column descriptors"
|
|
591
|
+
raise QueryValidationError(message)
|
|
592
|
+
if not hasattr(value, column.field_name):
|
|
593
|
+
message = f"write value does not define column: {column.database_name}"
|
|
594
|
+
raise QueryValidationError(message)
|
|
595
|
+
return getattr(value, column.field_name)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def _generated_value(value: object, column: ColumnBinding) -> ExpressionNode:
|
|
599
|
+
attribute = _generated_attribute(value, column)
|
|
600
|
+
if isinstance(attribute, UnsetType):
|
|
601
|
+
message = f"insert value leaves column unset: {column.database_name}"
|
|
602
|
+
raise QueryValidationError(message)
|
|
603
|
+
return _value_node(attribute)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _value_node(value: object) -> ExpressionNode:
|
|
607
|
+
if isinstance(value, Expression):
|
|
608
|
+
return value.node
|
|
609
|
+
return bind(value).node
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
__all__ = (
|
|
613
|
+
"ColumnBinding",
|
|
614
|
+
"ColumnReference",
|
|
615
|
+
"ConflictUpdate",
|
|
616
|
+
"DeleteStatement",
|
|
617
|
+
"InsertStatement",
|
|
618
|
+
"MutationStatement",
|
|
619
|
+
"ReturningStatement",
|
|
620
|
+
"ScopedStatement",
|
|
621
|
+
"UpdateStatement",
|
|
622
|
+
"WriteColumn",
|
|
623
|
+
"WriteStatement",
|
|
624
|
+
"delete_from",
|
|
625
|
+
"excluded",
|
|
626
|
+
"insert_into",
|
|
627
|
+
"update",
|
|
628
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Typed relation facts, navigation, fetch plans, and loading state."""
|
|
2
|
+
|
|
3
|
+
from pyoq.relations.batching import (
|
|
4
|
+
DEFAULT_KEY_LIMIT,
|
|
5
|
+
KeyBatch,
|
|
6
|
+
RelationBatch,
|
|
7
|
+
RelationBatchLoader,
|
|
8
|
+
plan_key_batches,
|
|
9
|
+
)
|
|
10
|
+
from pyoq.relations.derivation import derive_relations, table_relations
|
|
11
|
+
from pyoq.relations.fetching import (
|
|
12
|
+
MAXIMUM_FETCH_DEPTH,
|
|
13
|
+
FetchJoin,
|
|
14
|
+
FetchOrder,
|
|
15
|
+
FetchPlan,
|
|
16
|
+
FetchRequest,
|
|
17
|
+
FetchStrategy,
|
|
18
|
+
by_column,
|
|
19
|
+
by_expression,
|
|
20
|
+
validate_fetch_plan,
|
|
21
|
+
)
|
|
22
|
+
from pyoq.relations.graph import RelationGraph, build_relation_graph
|
|
23
|
+
from pyoq.relations.loading import RelationState, RelationValue
|
|
24
|
+
from pyoq.relations.model import (
|
|
25
|
+
RelationCardinality,
|
|
26
|
+
RelationDirection,
|
|
27
|
+
RelationEndpoint,
|
|
28
|
+
TypedRelation,
|
|
29
|
+
)
|
|
30
|
+
from pyoq.relations.planning import (
|
|
31
|
+
FetchCapabilities,
|
|
32
|
+
ResolvedFetch,
|
|
33
|
+
ResolvedFetchPlan,
|
|
34
|
+
resolve_fetch_plan,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = (
|
|
38
|
+
"DEFAULT_KEY_LIMIT",
|
|
39
|
+
"MAXIMUM_FETCH_DEPTH",
|
|
40
|
+
"FetchCapabilities",
|
|
41
|
+
"FetchJoin",
|
|
42
|
+
"FetchOrder",
|
|
43
|
+
"FetchPlan",
|
|
44
|
+
"FetchRequest",
|
|
45
|
+
"FetchStrategy",
|
|
46
|
+
"KeyBatch",
|
|
47
|
+
"RelationBatch",
|
|
48
|
+
"RelationBatchLoader",
|
|
49
|
+
"RelationCardinality",
|
|
50
|
+
"RelationDirection",
|
|
51
|
+
"RelationEndpoint",
|
|
52
|
+
"RelationGraph",
|
|
53
|
+
"RelationState",
|
|
54
|
+
"RelationValue",
|
|
55
|
+
"ResolvedFetch",
|
|
56
|
+
"ResolvedFetchPlan",
|
|
57
|
+
"TypedRelation",
|
|
58
|
+
"build_relation_graph",
|
|
59
|
+
"by_column",
|
|
60
|
+
"by_expression",
|
|
61
|
+
"derive_relations",
|
|
62
|
+
"plan_key_batches",
|
|
63
|
+
"resolve_fetch_plan",
|
|
64
|
+
"table_relations",
|
|
65
|
+
"validate_fetch_plan",
|
|
66
|
+
)
|