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/choices.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Choosing one value out of several.
|
|
2
|
+
|
|
3
|
+
Every construct here answers the same question with different rules for what
|
|
4
|
+
counts as an answer: the first branch whose condition holds, the first
|
|
5
|
+
argument that is not null, the widest, the narrowest. The type of the answer
|
|
6
|
+
is the type of what was offered, so a chosen value stays as usable as the
|
|
7
|
+
column it came from.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, Generic, TypeVar, overload
|
|
13
|
+
|
|
14
|
+
from pyoq.query.expressions import (
|
|
15
|
+
ComputedExpression,
|
|
16
|
+
Expression,
|
|
17
|
+
family_for_value,
|
|
18
|
+
operand,
|
|
19
|
+
require_boolean,
|
|
20
|
+
)
|
|
21
|
+
from pyoq.query.nodes import CaseNode, FunctionName, FunctionNode, ScalarFamily
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from pyoq.query.nodes import ExpressionNode
|
|
25
|
+
|
|
26
|
+
Value = TypeVar("Value")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CaseBuilder:
|
|
30
|
+
"""A case with no branches yet, which is not a value until it has one."""
|
|
31
|
+
|
|
32
|
+
__slots__ = ()
|
|
33
|
+
|
|
34
|
+
@overload
|
|
35
|
+
def when(
|
|
36
|
+
self,
|
|
37
|
+
condition: Expression[bool],
|
|
38
|
+
result: Expression[Value],
|
|
39
|
+
/,
|
|
40
|
+
) -> CaseBranches[Value]: ...
|
|
41
|
+
|
|
42
|
+
@overload
|
|
43
|
+
def when(
|
|
44
|
+
self,
|
|
45
|
+
condition: Expression[bool],
|
|
46
|
+
result: Value,
|
|
47
|
+
/,
|
|
48
|
+
) -> CaseBranches[Value]: ...
|
|
49
|
+
|
|
50
|
+
def when(
|
|
51
|
+
self,
|
|
52
|
+
condition: Expression[bool],
|
|
53
|
+
result: Expression[Value] | Value,
|
|
54
|
+
/,
|
|
55
|
+
) -> CaseBranches[Value]:
|
|
56
|
+
require_boolean(condition, "case branch")
|
|
57
|
+
family = _family_of(result)
|
|
58
|
+
return CaseBranches(((condition.node, operand(result, family)),), family)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CaseBranches(Generic[Value]):
|
|
62
|
+
"""Branches gathered so far, with the type the whole case will answer."""
|
|
63
|
+
|
|
64
|
+
__slots__ = ("_branches", "_family")
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
branches: tuple[tuple[ExpressionNode, ExpressionNode], ...],
|
|
69
|
+
family: ScalarFamily,
|
|
70
|
+
) -> None:
|
|
71
|
+
self._branches = branches
|
|
72
|
+
self._family = family
|
|
73
|
+
|
|
74
|
+
def when(
|
|
75
|
+
self,
|
|
76
|
+
condition: Expression[bool],
|
|
77
|
+
result: Expression[Value] | Value,
|
|
78
|
+
/,
|
|
79
|
+
) -> CaseBranches[Value]:
|
|
80
|
+
require_boolean(condition, "case branch")
|
|
81
|
+
branch = (condition.node, operand(result, self._family))
|
|
82
|
+
return CaseBranches((*self._branches, branch), self._family)
|
|
83
|
+
|
|
84
|
+
def otherwise(
|
|
85
|
+
self,
|
|
86
|
+
result: Expression[Value] | Value,
|
|
87
|
+
/,
|
|
88
|
+
) -> ComputedExpression[Value]:
|
|
89
|
+
"""The value for rows no branch claimed, which makes the case total."""
|
|
90
|
+
node = CaseNode(self._branches, operand(result, self._family))
|
|
91
|
+
return ComputedExpression[Value](node, self._family)
|
|
92
|
+
|
|
93
|
+
def end(self) -> ComputedExpression[Value | None]:
|
|
94
|
+
"""Close without an else, so unclaimed rows answer null."""
|
|
95
|
+
return ComputedExpression[Value | None](CaseNode(self._branches), self._family)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def case() -> CaseBuilder:
|
|
99
|
+
"""Start a case, whose type is fixed by the result of its first branch."""
|
|
100
|
+
return CaseBuilder()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@overload
|
|
104
|
+
def coalesce(
|
|
105
|
+
first: Expression[Value | None],
|
|
106
|
+
fallback: Value,
|
|
107
|
+
/,
|
|
108
|
+
) -> ComputedExpression[Value]: ...
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@overload
|
|
112
|
+
def coalesce(
|
|
113
|
+
first: Expression[Value],
|
|
114
|
+
/,
|
|
115
|
+
*rest: Expression[Value] | Value,
|
|
116
|
+
) -> ComputedExpression[Value]: ...
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def coalesce(
|
|
120
|
+
first: Expression[Value] | Expression[Value | None],
|
|
121
|
+
/,
|
|
122
|
+
*rest: object,
|
|
123
|
+
) -> ComputedExpression[Value]:
|
|
124
|
+
"""The first argument that is not null."""
|
|
125
|
+
return _choosing(FunctionName.COALESCE, first, rest, least_arguments=2)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def nullif(
|
|
129
|
+
first: Expression[Value],
|
|
130
|
+
other: Expression[Value] | Value,
|
|
131
|
+
/,
|
|
132
|
+
) -> ComputedExpression[Value | None]:
|
|
133
|
+
"""The first argument, unless it equals the second, in which case null."""
|
|
134
|
+
chosen = _choosing(FunctionName.NULLIF, first, (other,), least_arguments=2)
|
|
135
|
+
return ComputedExpression[Value | None](chosen.node, chosen.family)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def greatest(
|
|
139
|
+
first: Expression[Value],
|
|
140
|
+
/,
|
|
141
|
+
*rest: Expression[Value] | Value,
|
|
142
|
+
) -> ComputedExpression[Value]:
|
|
143
|
+
"""The widest of its arguments."""
|
|
144
|
+
return _choosing(FunctionName.GREATEST, first, rest, least_arguments=2)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def least(
|
|
148
|
+
first: Expression[Value],
|
|
149
|
+
/,
|
|
150
|
+
*rest: Expression[Value] | Value,
|
|
151
|
+
) -> ComputedExpression[Value]:
|
|
152
|
+
"""The narrowest of its arguments."""
|
|
153
|
+
return _choosing(FunctionName.LEAST, first, rest, least_arguments=2)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _choosing(
|
|
157
|
+
name: FunctionName,
|
|
158
|
+
first: Expression[Value] | Expression[Value | None],
|
|
159
|
+
rest: tuple[object, ...],
|
|
160
|
+
*,
|
|
161
|
+
least_arguments: int,
|
|
162
|
+
) -> ComputedExpression[Value]:
|
|
163
|
+
if len(rest) + 1 < least_arguments:
|
|
164
|
+
message = f"{name.value} needs at least {least_arguments} arguments"
|
|
165
|
+
raise TypeError(message)
|
|
166
|
+
family = first.family
|
|
167
|
+
arguments = (first.node, *(operand(value, family) for value in rest))
|
|
168
|
+
return ComputedExpression[Value](FunctionNode(name, arguments), family)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _family_of(value: object) -> ScalarFamily:
|
|
172
|
+
if isinstance(value, Expression):
|
|
173
|
+
return value.family
|
|
174
|
+
return family_for_value(value)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
__all__ = (
|
|
178
|
+
"CaseBranches",
|
|
179
|
+
"CaseBuilder",
|
|
180
|
+
"case",
|
|
181
|
+
"coalesce",
|
|
182
|
+
"greatest",
|
|
183
|
+
"least",
|
|
184
|
+
"nullif",
|
|
185
|
+
)
|
pyoq/query/decoding.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""Giving a column back as the type it was declared to hold.
|
|
2
|
+
|
|
3
|
+
A driver answers with what its own protocol carries, which is not always what a
|
|
4
|
+
column was declared to be: SQLite hands back a float for a decimal and a string
|
|
5
|
+
for a date. A descriptor that says `Decimal` and yields a float is a promise the
|
|
6
|
+
runtime does not keep, and money is the usual casualty.
|
|
7
|
+
|
|
8
|
+
The declared type travels with the column, so the row can be given back as the
|
|
9
|
+
row was described. A column with no declared type is left exactly as the driver
|
|
10
|
+
answered.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from datetime import date, datetime, time, timedelta
|
|
18
|
+
from decimal import Decimal, InvalidOperation
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import TYPE_CHECKING, cast, get_origin
|
|
21
|
+
from uuid import UUID
|
|
22
|
+
|
|
23
|
+
from pyoq.errors import QueryExecutionError
|
|
24
|
+
from pyoq.query.nodes import FieldNode, ScalarFamily
|
|
25
|
+
from pyoq.query.results import result_descriptor
|
|
26
|
+
from pyoq.query.select_nodes import SelectNode, SetNode
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Sequence
|
|
30
|
+
|
|
31
|
+
from pyoq.query.select_nodes import ProjectionNode
|
|
32
|
+
|
|
33
|
+
_SECONDS = "seconds"
|
|
34
|
+
_ONE_DAY = timedelta(days=1)
|
|
35
|
+
_MILLION = 1_000_000
|
|
36
|
+
_MINUTE = 60
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def projected_types(node: object, /) -> tuple[type[object] | None, ...]:
|
|
40
|
+
"""The type each projected column was declared to hold, in order.
|
|
41
|
+
|
|
42
|
+
A set operation takes its columns from the first query in it, which is where
|
|
43
|
+
a database takes their names from too.
|
|
44
|
+
"""
|
|
45
|
+
projections = _projections(node)
|
|
46
|
+
return tuple(_declared(projection) for projection in projections)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def decode_rows(
|
|
50
|
+
rows: Sequence[object],
|
|
51
|
+
types: tuple[type[object] | None, ...],
|
|
52
|
+
/,
|
|
53
|
+
) -> list[object]:
|
|
54
|
+
"""Every row given back as it was described.
|
|
55
|
+
|
|
56
|
+
A statement whose columns declared nothing is handed straight back, so a
|
|
57
|
+
caller that never said what it wanted pays nothing for the question.
|
|
58
|
+
"""
|
|
59
|
+
if not types or not any(types):
|
|
60
|
+
return list(rows)
|
|
61
|
+
return [_decode_row(row, types) for row in rows]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def decode_row(row: object, types: tuple[type[object] | None, ...], /) -> object:
|
|
65
|
+
"""One row given back as it was described."""
|
|
66
|
+
if not types or not any(types):
|
|
67
|
+
return row
|
|
68
|
+
return _decode_row(row, types)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def decode_value(value: object, target: type[object] | None, /) -> object:
|
|
72
|
+
"""One value, as the type it was declared to hold."""
|
|
73
|
+
if target is None or value is None or type(value) is target:
|
|
74
|
+
return value
|
|
75
|
+
decoder = _decoder(target)
|
|
76
|
+
if decoder is None:
|
|
77
|
+
return value
|
|
78
|
+
try:
|
|
79
|
+
return decoder(value)
|
|
80
|
+
except (ArithmeticError, TypeError, ValueError, InvalidOperation) as error:
|
|
81
|
+
message = (
|
|
82
|
+
f"a {type(value).__name__} came back for a column declared "
|
|
83
|
+
f"{target.__name__}, and could not be read as one"
|
|
84
|
+
)
|
|
85
|
+
raise QueryExecutionError(message) from error
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _decode_row(
|
|
89
|
+
row: object,
|
|
90
|
+
types: tuple[type[object] | None, ...],
|
|
91
|
+
) -> object:
|
|
92
|
+
"""A row of the width it was described as, decoded column by column.
|
|
93
|
+
|
|
94
|
+
Both callers above have already turned away a statement that named no
|
|
95
|
+
types. A statement that projected a different number of columns than the
|
|
96
|
+
database answered with is
|
|
97
|
+
refused, because a star widens with the table and reading the old columns
|
|
98
|
+
out of a wider row would answer with values from the wrong ones.
|
|
99
|
+
"""
|
|
100
|
+
if not isinstance(row, tuple):
|
|
101
|
+
return row
|
|
102
|
+
# A driver answers with a tuple of driver values, and what those are is the
|
|
103
|
+
# question this is here to settle, so there is nothing narrower to say.
|
|
104
|
+
values = cast("tuple[object, ...]", row)
|
|
105
|
+
if len(values) != len(types):
|
|
106
|
+
message = (
|
|
107
|
+
f"the database answered with {len(values)} columns where the "
|
|
108
|
+
f"statement projected {len(types)}; a generated package that no "
|
|
109
|
+
f"longer matches its schema reads a row by the wrong columns, so "
|
|
110
|
+
f"regenerate it"
|
|
111
|
+
)
|
|
112
|
+
raise QueryExecutionError(message)
|
|
113
|
+
return _decoded(values, types)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _decoded(
|
|
117
|
+
values: tuple[object, ...],
|
|
118
|
+
types: tuple[type[object] | None, ...],
|
|
119
|
+
) -> tuple[object, ...]:
|
|
120
|
+
decoded: list[object] = []
|
|
121
|
+
for index, target in enumerate(types):
|
|
122
|
+
decoded.append(decode_value(values[index], target))
|
|
123
|
+
return tuple(decoded)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _projections(node: object) -> tuple[ProjectionNode, ...]:
|
|
127
|
+
while isinstance(node, SetNode):
|
|
128
|
+
node = node.left
|
|
129
|
+
if isinstance(node, SelectNode):
|
|
130
|
+
return node.projections
|
|
131
|
+
returning: tuple[ProjectionNode, ...] | None = getattr(node, "returning", None)
|
|
132
|
+
return returning if isinstance(returning, tuple) else ()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _declared(projection: ProjectionNode) -> type[object] | None:
|
|
136
|
+
"""What reads this projection back, for the two that say what they hold.
|
|
137
|
+
|
|
138
|
+
A column says what it holds, and an expression that carries a value
|
|
139
|
+
through says whatever that value was declared to be. An expression that
|
|
140
|
+
computes something new says nothing, so it is read back as its family
|
|
141
|
+
where that family names one Python type: a predicate is a boolean whether
|
|
142
|
+
a driver answers with one or with a one, and text is text whether a driver
|
|
143
|
+
kept it as text.
|
|
144
|
+
"""
|
|
145
|
+
expression = projection.expression
|
|
146
|
+
answers = result_descriptor(expression)
|
|
147
|
+
if isinstance(expression, FieldNode):
|
|
148
|
+
return _decodable(answers.declared, answers.family)
|
|
149
|
+
if answers.declared is not None and _decoder(answers.declared) is not None:
|
|
150
|
+
return answers.declared
|
|
151
|
+
return _COMPUTED_TYPES.get(answers.family)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _decodable(
|
|
155
|
+
declared: type[object] | None,
|
|
156
|
+
family: ScalarFamily,
|
|
157
|
+
) -> type[object] | None:
|
|
158
|
+
"""What reads a value back, which its declared type may not name.
|
|
159
|
+
|
|
160
|
+
A column holding any JSON is declared as a union of everything JSON can
|
|
161
|
+
be, and a union is not a type a decoder can be found under. The family
|
|
162
|
+
says JSON on its own, so it answers where the declared type cannot.
|
|
163
|
+
"""
|
|
164
|
+
if declared is not None and _decoder(declared) is not None:
|
|
165
|
+
return declared
|
|
166
|
+
return _FAMILY_TYPES.get(family)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _decoder(target: type[object]) -> Callable[[object], object] | None:
|
|
170
|
+
"""What reads a value back as this type, if anything does.
|
|
171
|
+
|
|
172
|
+
A column can be declared as a parameterised type, and what decodes one is
|
|
173
|
+
decided by what it is, not by what it holds. A generated enumeration is
|
|
174
|
+
its own decoder, because calling it is what turns a stored value into a
|
|
175
|
+
member of it.
|
|
176
|
+
"""
|
|
177
|
+
origin = get_origin(target) or target
|
|
178
|
+
decoder = _DECODERS.get(origin)
|
|
179
|
+
if decoder is not None:
|
|
180
|
+
return decoder
|
|
181
|
+
if isinstance(origin, type) and issubclass(origin, Enum):
|
|
182
|
+
return origin
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _to_decimal(value: object) -> Decimal:
|
|
187
|
+
if isinstance(value, float):
|
|
188
|
+
return Decimal(str(value))
|
|
189
|
+
if isinstance(value, (int, str)):
|
|
190
|
+
return Decimal(value)
|
|
191
|
+
message = f"cannot read {type(value).__name__} as a decimal"
|
|
192
|
+
raise TypeError(message)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _to_datetime(value: object) -> datetime:
|
|
196
|
+
if isinstance(value, datetime):
|
|
197
|
+
return value
|
|
198
|
+
if isinstance(value, str):
|
|
199
|
+
return datetime.fromisoformat(value)
|
|
200
|
+
message = f"cannot read {type(value).__name__} as a datetime"
|
|
201
|
+
raise TypeError(message)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _to_date(value: object) -> date:
|
|
205
|
+
if isinstance(value, datetime):
|
|
206
|
+
return value.date()
|
|
207
|
+
if isinstance(value, date):
|
|
208
|
+
return value
|
|
209
|
+
if isinstance(value, str):
|
|
210
|
+
return date.fromisoformat(value)
|
|
211
|
+
message = f"cannot read {type(value).__name__} as a date"
|
|
212
|
+
raise TypeError(message)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _to_time(value: object) -> time:
|
|
216
|
+
"""MySQL's `TIME` is a signed duration, and its driver answers with one.
|
|
217
|
+
|
|
218
|
+
A duration inside a single forward day names a clock time and is read as
|
|
219
|
+
one. Anything outside that does not, so it is refused rather than folded
|
|
220
|
+
into a time it does not mean.
|
|
221
|
+
"""
|
|
222
|
+
if isinstance(value, time):
|
|
223
|
+
return value
|
|
224
|
+
if isinstance(value, timedelta):
|
|
225
|
+
return _clock_time(value)
|
|
226
|
+
if isinstance(value, str):
|
|
227
|
+
return time.fromisoformat(value)
|
|
228
|
+
message = f"cannot read {type(value).__name__} as a time"
|
|
229
|
+
raise TypeError(message)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _clock_time(value: timedelta) -> time:
|
|
233
|
+
if not timedelta(0) <= value < _ONE_DAY:
|
|
234
|
+
message = f"{value} is not a time of day"
|
|
235
|
+
raise ValueError(message)
|
|
236
|
+
seconds, microseconds = divmod(value // timedelta(microseconds=1), _MILLION)
|
|
237
|
+
minutes, second = divmod(seconds, _MINUTE)
|
|
238
|
+
hour, minute = divmod(minutes, _MINUTE)
|
|
239
|
+
return time(hour, minute, second, microseconds)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _to_timedelta(value: object) -> timedelta:
|
|
243
|
+
if isinstance(value, timedelta):
|
|
244
|
+
return value
|
|
245
|
+
if isinstance(value, (int, float)):
|
|
246
|
+
return timedelta(**{_SECONDS: value})
|
|
247
|
+
message = f"cannot read {type(value).__name__} as an interval"
|
|
248
|
+
raise TypeError(message)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _to_uuid(value: object) -> UUID:
|
|
252
|
+
if isinstance(value, UUID):
|
|
253
|
+
return value
|
|
254
|
+
if isinstance(value, str):
|
|
255
|
+
return UUID(value)
|
|
256
|
+
if isinstance(value, bytes):
|
|
257
|
+
return UUID(bytes=value)
|
|
258
|
+
message = f"cannot read {type(value).__name__} as a uuid"
|
|
259
|
+
raise TypeError(message)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _to_bytes(value: object) -> bytes:
|
|
263
|
+
if isinstance(value, (bytes, bytearray)):
|
|
264
|
+
return bytes(value)
|
|
265
|
+
if isinstance(value, memoryview):
|
|
266
|
+
return value.tobytes()
|
|
267
|
+
if isinstance(value, str):
|
|
268
|
+
return value.encode()
|
|
269
|
+
message = f"cannot read {type(value).__name__} as bytes"
|
|
270
|
+
raise TypeError(message)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _to_bool(value: object) -> bool:
|
|
274
|
+
if isinstance(value, (bool, int)):
|
|
275
|
+
return bool(value)
|
|
276
|
+
message = f"cannot read {type(value).__name__} as a boolean"
|
|
277
|
+
raise TypeError(message)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _to_int(value: object) -> int:
|
|
281
|
+
if isinstance(value, (int, float, str)):
|
|
282
|
+
return int(value)
|
|
283
|
+
message = f"cannot read {type(value).__name__} as an integer"
|
|
284
|
+
raise TypeError(message)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _to_float(value: object) -> float:
|
|
288
|
+
if isinstance(value, (int, float, str, Decimal)):
|
|
289
|
+
return float(value)
|
|
290
|
+
message = f"cannot read {type(value).__name__} as a float"
|
|
291
|
+
raise TypeError(message)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _to_text(value: object) -> str:
|
|
295
|
+
"""A driver that answers with bytes for a text column says which encoding."""
|
|
296
|
+
if isinstance(value, (bytes, bytearray)):
|
|
297
|
+
return bytes(value).decode()
|
|
298
|
+
if isinstance(value, memoryview):
|
|
299
|
+
return value.tobytes().decode()
|
|
300
|
+
return str(value)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _to_tuple(value: object) -> tuple[object, ...]:
|
|
304
|
+
"""An array column is declared as a tuple, and a driver answers with a list."""
|
|
305
|
+
if isinstance(value, tuple):
|
|
306
|
+
return cast("tuple[object, ...]", value)
|
|
307
|
+
if isinstance(value, list):
|
|
308
|
+
return tuple(cast("list[object]", value))
|
|
309
|
+
message = f"cannot read {type(value).__name__} as an array"
|
|
310
|
+
raise TypeError(message)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _to_json(value: object) -> object:
|
|
314
|
+
"""A driver that has no JSON of its own answers with the text of it."""
|
|
315
|
+
if isinstance(value, (str, bytes, bytearray)):
|
|
316
|
+
return json.loads(value)
|
|
317
|
+
return value
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
_FAMILY_TYPES: dict[ScalarFamily, type[object]] = {ScalarFamily.JSON: dict}
|
|
321
|
+
"""The type that reads a column back, where no declared type can name one.
|
|
322
|
+
|
|
323
|
+
A column that declared nothing is left as it came, because a caller that
|
|
324
|
+
never said what it wanted pays nothing for the question. Only JSON is here,
|
|
325
|
+
because a column holding any JSON is declared as a union no decoder is found
|
|
326
|
+
under.
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
_COMPUTED_TYPES: dict[ScalarFamily, type[object]] = {
|
|
330
|
+
ScalarFamily.BOOLEAN: bool,
|
|
331
|
+
ScalarFamily.STRING: str,
|
|
332
|
+
ScalarFamily.JSON: dict,
|
|
333
|
+
}
|
|
334
|
+
"""What reads an expression back, which has no declared type to consult.
|
|
335
|
+
|
|
336
|
+
A predicate is a boolean whether a driver answers with one or with a one, and
|
|
337
|
+
text is text whether a driver kept it as text or handed back the number it
|
|
338
|
+
looked like. A numeric family names three Python types, so it is absent: what
|
|
339
|
+
a driver answered with is the only thing that says which.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
_DECODERS: dict[type[object], Callable[[object], object]] = {
|
|
343
|
+
Decimal: _to_decimal,
|
|
344
|
+
datetime: _to_datetime,
|
|
345
|
+
date: _to_date,
|
|
346
|
+
time: _to_time,
|
|
347
|
+
timedelta: _to_timedelta,
|
|
348
|
+
UUID: _to_uuid,
|
|
349
|
+
bytes: _to_bytes,
|
|
350
|
+
bool: _to_bool,
|
|
351
|
+
int: _to_int,
|
|
352
|
+
float: _to_float,
|
|
353
|
+
str: _to_text,
|
|
354
|
+
list: _to_json,
|
|
355
|
+
dict: _to_json,
|
|
356
|
+
tuple: _to_tuple,
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
__all__ = ("decode_row", "decode_rows", "decode_value", "projected_types")
|
pyoq/query/documents.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Building a JSON value out of the values around it.
|
|
2
|
+
|
|
3
|
+
Reading inside a document is asked of the document, so those questions are
|
|
4
|
+
methods on the value itself. Building one starts from values that are not
|
|
5
|
+
documents yet, so it starts here.
|
|
6
|
+
|
|
7
|
+
Every dialect writes these under its own name, and each one carries the name
|
|
8
|
+
it uses, so a document built here is the same document on all of them.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from pyoq.query.expressions import (
|
|
16
|
+
ComputedExpression,
|
|
17
|
+
family_for_value,
|
|
18
|
+
operand,
|
|
19
|
+
)
|
|
20
|
+
from pyoq.query.nodes import JsonBuild, JsonBuildNode, ScalarFamily
|
|
21
|
+
from pyoq.schema.models import JsonValue
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
|
|
26
|
+
from pyoq.query.nodes import ExpressionNode
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def json_object(members: Mapping[str, object], /) -> ComputedExpression[JsonValue]:
|
|
30
|
+
"""A JSON object with these members, in the order they are written.
|
|
31
|
+
|
|
32
|
+
A mapping rather than alternating arguments, because a member and its
|
|
33
|
+
value belong together and nothing else keeps them paired.
|
|
34
|
+
"""
|
|
35
|
+
arguments: list[ExpressionNode] = []
|
|
36
|
+
for name, value in members.items():
|
|
37
|
+
arguments.append(operand(name, ScalarFamily.STRING))
|
|
38
|
+
arguments.append(_value(value))
|
|
39
|
+
return ComputedExpression[JsonValue](
|
|
40
|
+
JsonBuildNode(JsonBuild.OBJECT, tuple(arguments)), ScalarFamily.JSON
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def json_array(*values: object) -> ComputedExpression[JsonValue]:
|
|
45
|
+
"""A JSON array holding these values, in the order they are written."""
|
|
46
|
+
return ComputedExpression[JsonValue](
|
|
47
|
+
JsonBuildNode(JsonBuild.ARRAY, tuple(_value(value) for value in values)),
|
|
48
|
+
ScalarFamily.JSON,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _value(value: object) -> ExpressionNode:
|
|
53
|
+
return operand(value, family_for_value(value))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ("json_array", "json_object")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Database-neutral execution results."""
|
|
2
|
+
|
|
3
|
+
from pyoq.query.execution.batch import (
|
|
4
|
+
BulkChunk,
|
|
5
|
+
BulkPlan,
|
|
6
|
+
BulkResult,
|
|
7
|
+
OperationBudget,
|
|
8
|
+
WriteProvider,
|
|
9
|
+
)
|
|
10
|
+
from pyoq.query.execution.binding_style import ParameterStyle
|
|
11
|
+
from pyoq.query.execution.compilation import CompiledQuery
|
|
12
|
+
from pyoq.query.execution.context import CompilationContext
|
|
13
|
+
from pyoq.query.execution.control import CancellationToken, ExecutionControl
|
|
14
|
+
from pyoq.query.execution.operations import (
|
|
15
|
+
DatabaseCursor,
|
|
16
|
+
QueryOperations,
|
|
17
|
+
fetch_all,
|
|
18
|
+
)
|
|
19
|
+
from pyoq.query.execution.planning import BulkPlanner
|
|
20
|
+
from pyoq.query.execution.pooling import (
|
|
21
|
+
ConnectionFactory,
|
|
22
|
+
ConnectionLease,
|
|
23
|
+
ConnectionPool,
|
|
24
|
+
PoolPolicy,
|
|
25
|
+
PoolStats,
|
|
26
|
+
)
|
|
27
|
+
from pyoq.query.execution.results import ExecuteResult, StatementKind
|
|
28
|
+
from pyoq.query.execution.streaming import (
|
|
29
|
+
CursorSession,
|
|
30
|
+
RowStream,
|
|
31
|
+
StreamPolicy,
|
|
32
|
+
StreamResource,
|
|
33
|
+
)
|
|
34
|
+
from pyoq.query.execution.transactions import TransactionState
|
|
35
|
+
|
|
36
|
+
__all__ = (
|
|
37
|
+
"BulkChunk",
|
|
38
|
+
"BulkPlan",
|
|
39
|
+
"BulkPlanner",
|
|
40
|
+
"BulkResult",
|
|
41
|
+
"CancellationToken",
|
|
42
|
+
"CompilationContext",
|
|
43
|
+
"CompiledQuery",
|
|
44
|
+
"ConnectionFactory",
|
|
45
|
+
"ConnectionLease",
|
|
46
|
+
"ConnectionPool",
|
|
47
|
+
"CursorSession",
|
|
48
|
+
"DatabaseCursor",
|
|
49
|
+
"ExecuteResult",
|
|
50
|
+
"ExecutionControl",
|
|
51
|
+
"OperationBudget",
|
|
52
|
+
"ParameterStyle",
|
|
53
|
+
"PoolPolicy",
|
|
54
|
+
"PoolStats",
|
|
55
|
+
"QueryOperations",
|
|
56
|
+
"RowStream",
|
|
57
|
+
"StatementKind",
|
|
58
|
+
"StreamPolicy",
|
|
59
|
+
"StreamResource",
|
|
60
|
+
"TransactionState",
|
|
61
|
+
"WriteProvider",
|
|
62
|
+
"fetch_all",
|
|
63
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Dialect-neutral asynchronous execution."""
|
|
2
|
+
|
|
3
|
+
from pyoq.query.execution.aio.operations import (
|
|
4
|
+
AsyncDatabaseCursor,
|
|
5
|
+
AsyncQueryOperations,
|
|
6
|
+
fetch_all,
|
|
7
|
+
)
|
|
8
|
+
from pyoq.query.execution.aio.pooling import (
|
|
9
|
+
AsyncConnectionFactory,
|
|
10
|
+
AsyncConnectionLease,
|
|
11
|
+
AsyncConnectionPool,
|
|
12
|
+
)
|
|
13
|
+
from pyoq.query.execution.aio.streaming import (
|
|
14
|
+
AsyncCursorSession,
|
|
15
|
+
AsyncRowStream,
|
|
16
|
+
AsyncStreamResource,
|
|
17
|
+
)
|
|
18
|
+
from pyoq.query.execution.aio.transactions import AsyncTransactionState
|
|
19
|
+
|
|
20
|
+
__all__ = (
|
|
21
|
+
"AsyncConnectionFactory",
|
|
22
|
+
"AsyncConnectionLease",
|
|
23
|
+
"AsyncConnectionPool",
|
|
24
|
+
"AsyncCursorSession",
|
|
25
|
+
"AsyncDatabaseCursor",
|
|
26
|
+
"AsyncQueryOperations",
|
|
27
|
+
"AsyncRowStream",
|
|
28
|
+
"AsyncStreamResource",
|
|
29
|
+
"AsyncTransactionState",
|
|
30
|
+
"fetch_all",
|
|
31
|
+
)
|