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/schema/mysql/dsn.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""MySQL data source name parsing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from urllib.parse import SplitResult, unquote, urlsplit
|
|
7
|
+
|
|
8
|
+
from pyoq.errors import ConfigurationValidationError
|
|
9
|
+
|
|
10
|
+
DEFAULT_PORT = 3306
|
|
11
|
+
SCHEME = "mysql"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class MySQLConnectionSettings:
|
|
16
|
+
host: str
|
|
17
|
+
port: int
|
|
18
|
+
user: str
|
|
19
|
+
password: str
|
|
20
|
+
database: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_data_source_name(dsn: str) -> MySQLConnectionSettings:
|
|
24
|
+
parts = urlsplit(dsn)
|
|
25
|
+
if parts.scheme != SCHEME:
|
|
26
|
+
message = f"MySQL data source names must use the {SCHEME} scheme"
|
|
27
|
+
raise ConfigurationValidationError(message)
|
|
28
|
+
if parts.query or parts.fragment:
|
|
29
|
+
message = (
|
|
30
|
+
"MySQL data source names do not accept query or fragment options; "
|
|
31
|
+
"supply host, port, user, password, and database only"
|
|
32
|
+
)
|
|
33
|
+
raise ConfigurationValidationError(message)
|
|
34
|
+
return MySQLConnectionSettings(
|
|
35
|
+
host=parts.hostname or "localhost",
|
|
36
|
+
port=_port(parts),
|
|
37
|
+
user=_user(parts.username),
|
|
38
|
+
password=unquote(parts.password or ""),
|
|
39
|
+
database=_database(parts.path),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _port(parts: SplitResult) -> int:
|
|
44
|
+
try:
|
|
45
|
+
port = parts.port
|
|
46
|
+
except ValueError as error:
|
|
47
|
+
message = f"MySQL data source name has an invalid port: {error}"
|
|
48
|
+
raise ConfigurationValidationError(message) from error
|
|
49
|
+
return DEFAULT_PORT if port is None else port
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _user(value: str | None) -> str:
|
|
53
|
+
user = unquote(value or "")
|
|
54
|
+
if user == "":
|
|
55
|
+
message = "MySQL data source names must name a user"
|
|
56
|
+
raise ConfigurationValidationError(message)
|
|
57
|
+
return user
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _database(path: str) -> str:
|
|
61
|
+
if not path.startswith("/") or path.count("/") != 1 or path == "/":
|
|
62
|
+
message = "MySQL data source names must name exactly one database"
|
|
63
|
+
raise ConfigurationValidationError(message)
|
|
64
|
+
return unquote(path[1:])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = (
|
|
68
|
+
"DEFAULT_PORT",
|
|
69
|
+
"SCHEME",
|
|
70
|
+
"MySQLConnectionSettings",
|
|
71
|
+
"parse_data_source_name",
|
|
72
|
+
)
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Typed MySQL information schema row records."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from pyoq.errors import SchemaInspectionError
|
|
8
|
+
from pyoq.schema.models import (
|
|
9
|
+
GeneratedKind,
|
|
10
|
+
GeneratedValue,
|
|
11
|
+
Identifier,
|
|
12
|
+
IndexTerm,
|
|
13
|
+
KeyKind,
|
|
14
|
+
ObjectReference,
|
|
15
|
+
ReferentialAction,
|
|
16
|
+
SqlExpression,
|
|
17
|
+
SqlType,
|
|
18
|
+
TypeKind,
|
|
19
|
+
)
|
|
20
|
+
from pyoq.schema.mysql.types import mysql_enum_values, mysql_type
|
|
21
|
+
|
|
22
|
+
_KEY_KINDS = {"PRIMARY KEY": KeyKind.PRIMARY, "UNIQUE": KeyKind.UNIQUE}
|
|
23
|
+
_FOREIGN_KEY = "FOREIGN KEY"
|
|
24
|
+
_REFERENTIAL_ACTIONS = {
|
|
25
|
+
"NO ACTION": ReferentialAction.NO_ACTION,
|
|
26
|
+
"RESTRICT": ReferentialAction.RESTRICT,
|
|
27
|
+
"CASCADE": ReferentialAction.CASCADE,
|
|
28
|
+
"SET NULL": ReferentialAction.SET_NULL,
|
|
29
|
+
"SET DEFAULT": ReferentialAction.SET_DEFAULT,
|
|
30
|
+
}
|
|
31
|
+
_BASE_TABLE = "BASE TABLE"
|
|
32
|
+
_VIEW_COMMENT = "VIEW"
|
|
33
|
+
_AUTO_INCREMENT = "AUTO_INCREMENT"
|
|
34
|
+
_GENERATED = "GENERATED"
|
|
35
|
+
_STORED = "STORED"
|
|
36
|
+
_EXPRESSION_DEFAULT = "DEFAULT_GENERATED"
|
|
37
|
+
_DESCENDING_COLLATION = "D"
|
|
38
|
+
_UNQUOTED_DEFAULT_KINDS = frozenset(
|
|
39
|
+
{
|
|
40
|
+
TypeKind.BOOLEAN,
|
|
41
|
+
TypeKind.SMALL_INTEGER,
|
|
42
|
+
TypeKind.INTEGER,
|
|
43
|
+
TypeKind.BIG_INTEGER,
|
|
44
|
+
TypeKind.DECIMAL,
|
|
45
|
+
TypeKind.REAL,
|
|
46
|
+
TypeKind.DOUBLE,
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class ObjectRecord:
|
|
53
|
+
name: str
|
|
54
|
+
table_type: str
|
|
55
|
+
comment_text: str
|
|
56
|
+
definition_text: str | None
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def is_table(self) -> bool:
|
|
60
|
+
return self.table_type == _BASE_TABLE
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def comment(self) -> str | None:
|
|
64
|
+
if not self.is_table or self.comment_text == "":
|
|
65
|
+
return None
|
|
66
|
+
return self.comment_text
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def definition(self) -> SqlExpression | None:
|
|
70
|
+
text = "" if self.definition_text is None else self.definition_text.strip()
|
|
71
|
+
return None if text == "" else SqlExpression(text)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True, slots=True)
|
|
75
|
+
class ColumnRecord:
|
|
76
|
+
table: str
|
|
77
|
+
name: str
|
|
78
|
+
column_type: str
|
|
79
|
+
nullable_text: str
|
|
80
|
+
default_text: str | None
|
|
81
|
+
extra: str
|
|
82
|
+
generation_expression: str
|
|
83
|
+
comment_text: str
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def enum_values(self) -> tuple[str, ...]:
|
|
87
|
+
"""The labels this column's enum lists, or none if it is not one."""
|
|
88
|
+
return mysql_enum_values(self.column_type)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def enum_name(self) -> Identifier:
|
|
92
|
+
"""What to call an enum MySQL keeps on a column rather than by name.
|
|
93
|
+
|
|
94
|
+
MySQL has no enum type of its own, so the column it belongs to is the
|
|
95
|
+
only thing that identifies it.
|
|
96
|
+
"""
|
|
97
|
+
return Identifier(f"{self.table}_{self.name}")
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def data_type(self) -> SqlType:
|
|
101
|
+
values = self.enum_values
|
|
102
|
+
if not values:
|
|
103
|
+
return mysql_type(self.column_type)
|
|
104
|
+
return SqlType(
|
|
105
|
+
TypeKind.ENUM,
|
|
106
|
+
self.column_type,
|
|
107
|
+
named_type=ObjectReference(self.enum_name),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def nullable(self) -> bool:
|
|
112
|
+
return self.nullable_text.upper() == "YES"
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def comment(self) -> str | None:
|
|
116
|
+
return None if self.comment_text == "" else self.comment_text
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def generated(self) -> GeneratedValue | None:
|
|
120
|
+
markers = frozenset(self.extra.upper().split())
|
|
121
|
+
if _AUTO_INCREMENT in markers:
|
|
122
|
+
return GeneratedValue(GeneratedKind.IDENTITY_BY_DEFAULT)
|
|
123
|
+
if _GENERATED not in markers:
|
|
124
|
+
return None
|
|
125
|
+
kind = (
|
|
126
|
+
GeneratedKind.COMPUTED_STORED
|
|
127
|
+
if _STORED in markers
|
|
128
|
+
else GeneratedKind.COMPUTED_VIRTUAL
|
|
129
|
+
)
|
|
130
|
+
return GeneratedValue(kind, SqlExpression(self.generation_expression))
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def default(self) -> SqlExpression | None:
|
|
134
|
+
if self.default_text is None or self.generated is not None:
|
|
135
|
+
return None
|
|
136
|
+
if _EXPRESSION_DEFAULT in frozenset(self.extra.upper().split()):
|
|
137
|
+
return SqlExpression(self.default_text)
|
|
138
|
+
if self.data_type.kind in _UNQUOTED_DEFAULT_KINDS:
|
|
139
|
+
return SqlExpression(self.default_text)
|
|
140
|
+
return SqlExpression(_quoted(self.default_text))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass(frozen=True, slots=True)
|
|
144
|
+
class ConstraintRecord:
|
|
145
|
+
table: str
|
|
146
|
+
name: str
|
|
147
|
+
constraint_type: str
|
|
148
|
+
column: str
|
|
149
|
+
target_schema: str | None
|
|
150
|
+
target_table: str | None
|
|
151
|
+
target_column: str | None
|
|
152
|
+
on_update: str | None
|
|
153
|
+
on_delete: str | None
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def key_kind(self) -> KeyKind | None:
|
|
157
|
+
return _KEY_KINDS.get(self.constraint_type)
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def is_foreign_key(self) -> bool:
|
|
161
|
+
return self.constraint_type == _FOREIGN_KEY
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def target_reference(self) -> ObjectReference | None:
|
|
165
|
+
if self.target_schema is None or self.target_table is None:
|
|
166
|
+
return None
|
|
167
|
+
return ObjectReference(
|
|
168
|
+
Identifier(self.target_table),
|
|
169
|
+
Identifier(self.target_schema),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def target_identifier(self) -> Identifier:
|
|
174
|
+
if self.target_column is None:
|
|
175
|
+
message = f"MySQL foreign key {self.name!r} has no referenced column"
|
|
176
|
+
raise SchemaInspectionError(message)
|
|
177
|
+
return Identifier(self.target_column)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def update_action(self) -> ReferentialAction:
|
|
181
|
+
return _referential_action(self.on_update)
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def delete_action(self) -> ReferentialAction:
|
|
185
|
+
return _referential_action(self.on_delete)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass(frozen=True, slots=True)
|
|
189
|
+
class CheckRecord:
|
|
190
|
+
table: str
|
|
191
|
+
name: str
|
|
192
|
+
clause: str
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(frozen=True, slots=True)
|
|
196
|
+
class IndexRecord:
|
|
197
|
+
table: str
|
|
198
|
+
name: str
|
|
199
|
+
non_unique: int
|
|
200
|
+
column: str | None
|
|
201
|
+
collation: str | None
|
|
202
|
+
expression: str | None
|
|
203
|
+
index_type: str
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def unique(self) -> bool:
|
|
207
|
+
return self.non_unique == 0
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def method(self) -> str:
|
|
211
|
+
return self.index_type.lower()
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def term(self) -> IndexTerm:
|
|
215
|
+
descending = self.collation == _DESCENDING_COLLATION
|
|
216
|
+
if self.column is not None:
|
|
217
|
+
return IndexTerm(Identifier(self.column), None, descending)
|
|
218
|
+
if self.expression is None:
|
|
219
|
+
message = f"MySQL index {self.name!r} has an unreadable term"
|
|
220
|
+
raise SchemaInspectionError(message)
|
|
221
|
+
return IndexTerm(None, SqlExpression(self.expression), descending)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def read_object(row: tuple[object, ...]) -> ObjectRecord:
|
|
225
|
+
values = _row(row, 4)
|
|
226
|
+
return ObjectRecord(
|
|
227
|
+
_text(values[0]),
|
|
228
|
+
_text(values[1]),
|
|
229
|
+
_text(values[2]),
|
|
230
|
+
_optional_text(values[3]),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def read_column(row: tuple[object, ...]) -> ColumnRecord:
|
|
235
|
+
values = _row(row, 8)
|
|
236
|
+
return ColumnRecord(
|
|
237
|
+
_text(values[0]),
|
|
238
|
+
_text(values[1]),
|
|
239
|
+
_text(values[2]),
|
|
240
|
+
_text(values[3]),
|
|
241
|
+
_optional_text(values[4]),
|
|
242
|
+
_text(values[5]),
|
|
243
|
+
_text(values[6]),
|
|
244
|
+
_text(values[7]),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def read_constraint(row: tuple[object, ...]) -> ConstraintRecord:
|
|
249
|
+
values = _row(row, 9)
|
|
250
|
+
return ConstraintRecord(
|
|
251
|
+
_text(values[0]),
|
|
252
|
+
_text(values[1]),
|
|
253
|
+
_text(values[2]),
|
|
254
|
+
_text(values[3]),
|
|
255
|
+
_optional_text(values[4]),
|
|
256
|
+
_optional_text(values[5]),
|
|
257
|
+
_optional_text(values[6]),
|
|
258
|
+
_optional_text(values[7]),
|
|
259
|
+
_optional_text(values[8]),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def read_check(row: tuple[object, ...]) -> CheckRecord:
|
|
264
|
+
values = _row(row, 3)
|
|
265
|
+
return CheckRecord(_text(values[0]), _text(values[1]), _text(values[2]))
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def read_index(row: tuple[object, ...]) -> IndexRecord:
|
|
269
|
+
values = _row(row, 7)
|
|
270
|
+
return IndexRecord(
|
|
271
|
+
_text(values[0]),
|
|
272
|
+
_text(values[1]),
|
|
273
|
+
_number(values[2]),
|
|
274
|
+
_optional_text(values[3]),
|
|
275
|
+
_optional_text(values[4]),
|
|
276
|
+
_optional_text(values[5]),
|
|
277
|
+
_text(values[6]),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _referential_action(rule: str | None) -> ReferentialAction:
|
|
282
|
+
action = None if rule is None else _REFERENTIAL_ACTIONS.get(rule.upper())
|
|
283
|
+
if action is None:
|
|
284
|
+
message = f"unsupported MySQL referential action: {rule!r}"
|
|
285
|
+
raise SchemaInspectionError(message)
|
|
286
|
+
return action
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _quoted(value: str) -> str:
|
|
290
|
+
escaped = value.replace("'", "''")
|
|
291
|
+
return f"'{escaped}'"
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _row(row: tuple[object, ...], width: int) -> tuple[object, ...]:
|
|
295
|
+
if len(row) != width:
|
|
296
|
+
message = "MySQL catalog row has an unexpected shape"
|
|
297
|
+
raise SchemaInspectionError(message)
|
|
298
|
+
return row
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _text(value: object) -> str:
|
|
302
|
+
if not isinstance(value, str):
|
|
303
|
+
message = f"MySQL catalog value is not text: {value!r}"
|
|
304
|
+
raise SchemaInspectionError(message)
|
|
305
|
+
return value
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _optional_text(value: object) -> str | None:
|
|
309
|
+
return None if value is None else _text(value)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _number(value: object) -> int:
|
|
313
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
314
|
+
message = f"MySQL catalog value is not a number: {value!r}"
|
|
315
|
+
raise SchemaInspectionError(message)
|
|
316
|
+
return value
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
__all__ = (
|
|
320
|
+
"CheckRecord",
|
|
321
|
+
"ColumnRecord",
|
|
322
|
+
"ConstraintRecord",
|
|
323
|
+
"IndexRecord",
|
|
324
|
+
"ObjectRecord",
|
|
325
|
+
"read_check",
|
|
326
|
+
"read_column",
|
|
327
|
+
"read_constraint",
|
|
328
|
+
"read_index",
|
|
329
|
+
"read_object",
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@dataclass(frozen=True, slots=True)
|
|
334
|
+
class RoutineRecord:
|
|
335
|
+
name: str
|
|
336
|
+
kind: str
|
|
337
|
+
returns: str | None
|
|
338
|
+
position: int | None
|
|
339
|
+
parameter_name: str | None
|
|
340
|
+
mode: str | None
|
|
341
|
+
parameter_type: str | None
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def read_routine(row: tuple[object, ...]) -> RoutineRecord:
|
|
345
|
+
values = _row(row, 7)
|
|
346
|
+
return RoutineRecord(
|
|
347
|
+
_text(values[0]),
|
|
348
|
+
_text(values[1]),
|
|
349
|
+
_optional_text(values[2]),
|
|
350
|
+
None if values[3] is None else _number(values[3]),
|
|
351
|
+
_optional_text(values[4]),
|
|
352
|
+
_optional_text(values[5]),
|
|
353
|
+
_optional_text(values[6]),
|
|
354
|
+
)
|