sqlspec 0.46.2__py3-none-any.whl → 0.47.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlspec/_typing.py +16 -1
- sqlspec/adapters/adbc/adk/store.py +28 -6
- sqlspec/adapters/adbc/data_dictionary.py +21 -16
- sqlspec/adapters/aiomysql/adk/store.py +26 -3
- sqlspec/adapters/aiomysql/config.py +0 -4
- sqlspec/adapters/aiomysql/core.py +91 -53
- sqlspec/adapters/aiomysql/data_dictionary.py +2 -4
- sqlspec/adapters/aiomysql/driver.py +6 -1
- sqlspec/adapters/aiosqlite/adk/store.py +89 -51
- sqlspec/adapters/aiosqlite/config.py +3 -7
- sqlspec/adapters/aiosqlite/data_dictionary.py +3 -9
- sqlspec/adapters/aiosqlite/driver.py +15 -12
- sqlspec/adapters/asyncmy/adk/store.py +26 -3
- sqlspec/adapters/asyncmy/config.py +0 -4
- sqlspec/adapters/asyncmy/core.py +91 -53
- sqlspec/adapters/asyncmy/data_dictionary.py +2 -4
- sqlspec/adapters/asyncmy/driver.py +6 -1
- sqlspec/adapters/asyncpg/adk/store.py +25 -11
- sqlspec/adapters/asyncpg/config.py +0 -4
- sqlspec/adapters/asyncpg/core.py +61 -2
- sqlspec/adapters/asyncpg/data_dictionary.py +2 -7
- sqlspec/adapters/asyncpg/events/_hub.py +181 -0
- sqlspec/adapters/asyncpg/events/backend.py +49 -125
- sqlspec/adapters/bigquery/data_dictionary.py +7 -15
- sqlspec/adapters/bigquery/driver.py +3 -2
- sqlspec/adapters/cockroach_asyncpg/__init__.py +2 -0
- sqlspec/adapters/cockroach_asyncpg/adk/store.py +44 -19
- sqlspec/adapters/cockroach_asyncpg/config.py +15 -4
- sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +3 -0
- sqlspec/adapters/cockroach_psycopg/__init__.py +2 -1
- sqlspec/adapters/cockroach_psycopg/adk/store.py +34 -4
- sqlspec/adapters/cockroach_psycopg/config.py +1 -7
- sqlspec/adapters/cockroach_psycopg/data_dictionary.py +5 -0
- sqlspec/adapters/duckdb/adk/store.py +38 -17
- sqlspec/adapters/duckdb/core.py +91 -32
- sqlspec/adapters/mysqlconnector/adk/store.py +54 -8
- sqlspec/adapters/mysqlconnector/core.py +89 -52
- sqlspec/adapters/mysqlconnector/data_dictionary.py +3 -8
- sqlspec/adapters/mysqlconnector/driver.py +12 -2
- sqlspec/adapters/oracledb/__init__.py +8 -56
- sqlspec/adapters/oracledb/_typing.py +12 -0
- sqlspec/adapters/oracledb/adk/__init__.py +12 -1
- sqlspec/adapters/oracledb/adk/store.py +303 -104
- sqlspec/adapters/oracledb/config.py +0 -4
- sqlspec/adapters/oracledb/data_dictionary.py +51 -183
- sqlspec/adapters/oracledb/events/_hub.py +345 -0
- sqlspec/adapters/oracledb/events/backend.py +89 -136
- sqlspec/adapters/psqlpy/_typing.py +4 -1
- sqlspec/adapters/psqlpy/adk/store.py +18 -2
- sqlspec/adapters/psqlpy/config.py +0 -4
- sqlspec/adapters/psqlpy/core.py +71 -3
- sqlspec/adapters/psqlpy/data_dictionary.py +2 -7
- sqlspec/adapters/psqlpy/driver.py +11 -3
- sqlspec/adapters/psqlpy/events/_hub.py +204 -0
- sqlspec/adapters/psqlpy/events/backend.py +52 -152
- sqlspec/adapters/psycopg/adk/store.py +34 -4
- sqlspec/adapters/psycopg/core.py +55 -39
- sqlspec/adapters/psycopg/data_dictionary.py +3 -14
- sqlspec/adapters/psycopg/events/_hub.py +388 -0
- sqlspec/adapters/psycopg/events/backend.py +78 -138
- sqlspec/adapters/pymysql/adk/store.py +28 -5
- sqlspec/adapters/pymysql/core.py +91 -53
- sqlspec/adapters/pymysql/data_dictionary.py +2 -4
- sqlspec/adapters/pymysql/driver.py +6 -1
- sqlspec/adapters/spanner/adk/store.py +92 -56
- sqlspec/adapters/spanner/driver.py +11 -1
- sqlspec/adapters/sqlite/adk/store.py +84 -52
- sqlspec/adapters/sqlite/data_dictionary.py +3 -7
- sqlspec/adapters/sqlite/driver.py +16 -13
- sqlspec/base.py +228 -156
- sqlspec/builder/_base.py +4 -3
- sqlspec/builder/_dml.py +21 -0
- sqlspec/builder/_factory.py +34 -95
- sqlspec/builder/_insert.py +107 -30
- sqlspec/builder/_parsing_utils.py +2 -2
- sqlspec/config.py +44 -6
- sqlspec/core/__init__.py +1 -3
- sqlspec/core/cache.py +3 -13
- sqlspec/core/compiler.py +55 -88
- sqlspec/core/config_runtime.py +21 -3
- sqlspec/core/filters.py +92 -188
- sqlspec/core/parameters/__init__.py +1 -9
- sqlspec/core/parameters/_converter.py +10 -4
- sqlspec/core/parameters/_processor.py +87 -71
- sqlspec/core/result/__init__.py +0 -2
- sqlspec/core/result/_base.py +1 -4
- sqlspec/core/splitter.py +44 -5
- sqlspec/core/statement.py +3 -3
- sqlspec/data_dictionary/_loader.py +23 -8
- sqlspec/data_dictionary/dialects/bigquery.py +32 -0
- sqlspec/data_dictionary/dialects/cockroachdb.py +11 -0
- sqlspec/data_dictionary/dialects/mysql.py +11 -0
- sqlspec/data_dictionary/dialects/oracle.py +163 -0
- sqlspec/data_dictionary/dialects/postgres.py +23 -0
- sqlspec/data_dictionary/dialects/sqlite.py +17 -0
- sqlspec/driver/_async.py +29 -116
- sqlspec/driver/_common.py +113 -16
- sqlspec/driver/_exception_handler.py +3 -3
- sqlspec/driver/_storage_helpers.py +9 -0
- sqlspec/driver/_sync.py +8 -116
- sqlspec/exceptions.py +26 -7
- sqlspec/extensions/adk/__init__.py +1 -1
- sqlspec/extensions/adk/_config_utils.py +199 -0
- sqlspec/extensions/adk/artifact/__init__.py +2 -2
- sqlspec/extensions/adk/artifact/service.py +1 -2
- sqlspec/extensions/adk/artifact/store.py +10 -12
- sqlspec/extensions/adk/memory/__init__.py +1 -1
- sqlspec/extensions/adk/memory/service.py +2 -2
- sqlspec/extensions/adk/memory/store.py +18 -62
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +14 -76
- sqlspec/extensions/adk/service.py +5 -7
- sqlspec/extensions/adk/store.py +15 -28
- sqlspec/extensions/events/_queue.py +1 -0
- sqlspec/extensions/fastapi/providers.py +431 -357
- sqlspec/extensions/litestar/plugin.py +149 -96
- sqlspec/extensions/litestar/providers.py +464 -281
- sqlspec/extensions/prometheus/__init__.py +1 -1
- sqlspec/loader.py +17 -5
- sqlspec/migrations/base.py +4 -0
- sqlspec/observability/_config.py +17 -12
- sqlspec/observability/_diagnostics.py +3 -3
- sqlspec/observability/_dispatcher.py +53 -7
- sqlspec/observability/_observer.py +1 -5
- sqlspec/observability/_runtime.py +29 -7
- sqlspec/observability/_spans.py +3 -3
- sqlspec/protocols.py +6 -2
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/_arrow_payload.py +68 -0
- sqlspec/storage/_paths.py +58 -0
- sqlspec/storage/_utils.py +3 -83
- sqlspec/storage/backends/base.py +10 -10
- sqlspec/storage/backends/fsspec.py +9 -6
- sqlspec/storage/backends/local.py +4 -3
- sqlspec/storage/backends/obstore.py +6 -5
- sqlspec/storage/errors.py +2 -4
- sqlspec/storage/pipeline.py +104 -70
- sqlspec/typing.py +10 -0
- sqlspec/utils/arrow_helpers.py +37 -0
- sqlspec/utils/logging.py +2 -1
- sqlspec/utils/serializers/_json.py +34 -2
- sqlspec/utils/serializers/_schema.py +62 -54
- sqlspec/utils/sync_tools.py +1 -1
- sqlspec/utils/text.py +3 -2
- {sqlspec-0.46.2.dist-info → sqlspec-0.47.0.dist-info}/METADATA +3 -2
- {sqlspec-0.46.2.dist-info → sqlspec-0.47.0.dist-info}/RECORD +148 -142
- sqlspec/extensions/_filter_aliases.py +0 -112
- {sqlspec-0.46.2.dist-info → sqlspec-0.47.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.46.2.dist-info → sqlspec-0.47.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.46.2.dist-info → sqlspec-0.47.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/_typing.py
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# ruff: noqa: RUF100, PLR0913, A002, DOC201, PLR6301, PLR0917, ARG004, ARG002, ARG001
|
|
2
|
-
"""
|
|
2
|
+
"""Private implementation for SQLSpec typing and optional dependency shims.
|
|
3
|
+
|
|
4
|
+
Public consumers should import from :mod:`sqlspec.typing`. This module is kept
|
|
5
|
+
private because it centralizes optional dependency fallbacks, compatibility
|
|
6
|
+
aliases, and mypyc-excluded type boundaries used by the package internals.
|
|
7
|
+
"""
|
|
3
8
|
|
|
4
9
|
import enum
|
|
5
10
|
from collections.abc import Iterable, Mapping
|
|
@@ -150,6 +155,11 @@ def convert_stub( # noqa: PLR0913
|
|
|
150
155
|
return {}
|
|
151
156
|
|
|
152
157
|
|
|
158
|
+
def msgspec_fields_stub(type_: Any, /) -> "tuple[Any, ...]": # noqa: ARG001
|
|
159
|
+
"""Placeholder implementation."""
|
|
160
|
+
return ()
|
|
161
|
+
|
|
162
|
+
|
|
153
163
|
class UnsetTypeStub(enum.Enum):
|
|
154
164
|
UNSET = "UNSET"
|
|
155
165
|
|
|
@@ -162,16 +172,19 @@ try:
|
|
|
162
172
|
from msgspec import Struct as _RealStruct
|
|
163
173
|
from msgspec import UnsetType as _RealUnsetType
|
|
164
174
|
from msgspec import convert as _real_convert
|
|
175
|
+
from msgspec.structs import fields as _real_msgspec_fields
|
|
165
176
|
|
|
166
177
|
Struct = _RealStruct
|
|
167
178
|
UnsetType = _RealUnsetType
|
|
168
179
|
UNSET = _REAL_UNSET
|
|
169
180
|
convert = _real_convert
|
|
181
|
+
msgspec_fields = _real_msgspec_fields
|
|
170
182
|
except ImportError:
|
|
171
183
|
Struct = StructStub # type: ignore[assignment,misc]
|
|
172
184
|
UnsetType = UnsetTypeStub # type: ignore[assignment,misc]
|
|
173
185
|
UNSET = UNSET_STUB # type: ignore[assignment] # pyright: ignore[reportConstantRedefinition]
|
|
174
186
|
convert = convert_stub
|
|
187
|
+
msgspec_fields = msgspec_fields_stub # type: ignore[assignment]
|
|
175
188
|
|
|
176
189
|
|
|
177
190
|
try:
|
|
@@ -695,5 +708,7 @@ __all__ = (
|
|
|
695
708
|
"convert",
|
|
696
709
|
"convert_stub",
|
|
697
710
|
"module_available",
|
|
711
|
+
"msgspec_fields",
|
|
712
|
+
"msgspec_fields_stub",
|
|
698
713
|
"trace",
|
|
699
714
|
)
|
|
@@ -714,12 +714,14 @@ class AdbcADKStore(BaseAsyncADKStore["AdbcConfig"]):
|
|
|
714
714
|
|
|
715
715
|
def _append_event_and_update_state(
|
|
716
716
|
self, event_record: "EventRecord", session_id: str, state: "dict[str, Any]"
|
|
717
|
-
) ->
|
|
717
|
+
) -> SessionRecord:
|
|
718
718
|
"""Atomically insert an event and update the session's durable state.
|
|
719
719
|
|
|
720
|
-
The event insert
|
|
721
|
-
connection and committed together.
|
|
722
|
-
|
|
720
|
+
The event insert, state update, and refresh-SELECT are executed within
|
|
721
|
+
a single connection and committed together. ADBC drivers wrap a
|
|
722
|
+
variety of backends (Postgres, SQLite, DuckDB, ...) so we use a
|
|
723
|
+
SELECT-after-UPDATE rather than relying on RETURNING which not every
|
|
724
|
+
backend supports.
|
|
723
725
|
|
|
724
726
|
Args:
|
|
725
727
|
event_record: Event record to store.
|
|
@@ -737,6 +739,11 @@ class AdbcADKStore(BaseAsyncADKStore["AdbcConfig"]):
|
|
|
737
739
|
SET state = ?, update_time = CURRENT_TIMESTAMP
|
|
738
740
|
WHERE id = ?
|
|
739
741
|
"""
|
|
742
|
+
select_sql = f"""
|
|
743
|
+
SELECT id, app_name, user_id, state, create_time, update_time
|
|
744
|
+
FROM {self._session_table}
|
|
745
|
+
WHERE id = ?
|
|
746
|
+
"""
|
|
740
747
|
state_json = self._serialize_state(state)
|
|
741
748
|
event_json = self._serialize_json_field(event_record["event_json"])
|
|
742
749
|
|
|
@@ -754,6 +761,8 @@ class AdbcADKStore(BaseAsyncADKStore["AdbcConfig"]):
|
|
|
754
761
|
),
|
|
755
762
|
)
|
|
756
763
|
cursor.execute(update_sql, (state_json, session_id))
|
|
764
|
+
cursor.execute(select_sql, (session_id,))
|
|
765
|
+
row = cursor.fetchone()
|
|
757
766
|
conn.commit()
|
|
758
767
|
except Exception:
|
|
759
768
|
with contextlib.suppress(Exception):
|
|
@@ -762,11 +771,24 @@ class AdbcADKStore(BaseAsyncADKStore["AdbcConfig"]):
|
|
|
762
771
|
finally:
|
|
763
772
|
cursor.close()
|
|
764
773
|
|
|
774
|
+
if row is None:
|
|
775
|
+
msg = f"Session {session_id} not found during append_event_and_update_state."
|
|
776
|
+
raise ValueError(msg)
|
|
777
|
+
|
|
778
|
+
return SessionRecord(
|
|
779
|
+
id=row[0],
|
|
780
|
+
app_name=row[1],
|
|
781
|
+
user_id=row[2],
|
|
782
|
+
state=self._deserialize_state(row[3]),
|
|
783
|
+
create_time=row[4],
|
|
784
|
+
update_time=row[5],
|
|
785
|
+
)
|
|
786
|
+
|
|
765
787
|
async def append_event_and_update_state(
|
|
766
788
|
self, event_record: EventRecord, session_id: str, state: "dict[str, Any]"
|
|
767
|
-
) ->
|
|
789
|
+
) -> SessionRecord:
|
|
768
790
|
"""Atomically append an event and update the session's durable state."""
|
|
769
|
-
await async_(self._append_event_and_update_state)(event_record, session_id, state)
|
|
791
|
+
return await async_(self._append_event_and_update_state)(event_record, session_id, state)
|
|
770
792
|
|
|
771
793
|
def _get_events(
|
|
772
794
|
self, session_id: str, after_timestamp: "datetime | None" = None, limit: "int | None" = None
|
|
@@ -11,6 +11,14 @@ from sqlspec.data_dictionary import (
|
|
|
11
11
|
list_registered_dialects,
|
|
12
12
|
normalize_dialect_name,
|
|
13
13
|
)
|
|
14
|
+
from sqlspec.data_dictionary.dialects.bigquery import (
|
|
15
|
+
format_bigquery_information_schema_tables,
|
|
16
|
+
format_bigquery_schema_prefix,
|
|
17
|
+
)
|
|
18
|
+
from sqlspec.data_dictionary.dialects.cockroachdb import resolve_cockroachdb_json_type
|
|
19
|
+
from sqlspec.data_dictionary.dialects.mysql import resolve_mysql_json_type
|
|
20
|
+
from sqlspec.data_dictionary.dialects.postgres import resolve_postgres_json_type
|
|
21
|
+
from sqlspec.data_dictionary.dialects.sqlite import resolve_sqlite_json_type
|
|
14
22
|
from sqlspec.driver import SyncDataDictionaryBase
|
|
15
23
|
from sqlspec.exceptions import SQLFileNotFoundError
|
|
16
24
|
from sqlspec.typing import ColumnMetadata, ForeignKeyMetadata, IndexMetadata, TableMetadata, VersionInfo
|
|
@@ -138,8 +146,17 @@ class AdbcDataDictionary(SyncDataDictionaryBase):
|
|
|
138
146
|
return self.get_default_type_mapping().get(type_category, "TEXT")
|
|
139
147
|
|
|
140
148
|
if type_category == "json":
|
|
141
|
-
json_version = config.get_feature_version("supports_json")
|
|
142
149
|
version_info = self.get_version(driver)
|
|
150
|
+
if dialect == "postgres":
|
|
151
|
+
return resolve_postgres_json_type(version_info)
|
|
152
|
+
if dialect == "sqlite":
|
|
153
|
+
return resolve_sqlite_json_type(version_info)
|
|
154
|
+
if dialect == "mysql":
|
|
155
|
+
return resolve_mysql_json_type(version_info)
|
|
156
|
+
if dialect == "cockroachdb":
|
|
157
|
+
return resolve_cockroachdb_json_type(version_info)
|
|
158
|
+
|
|
159
|
+
json_version = config.get_feature_version("supports_json")
|
|
143
160
|
if json_version and (version_info is None or version_info < json_version):
|
|
144
161
|
return "TEXT"
|
|
145
162
|
|
|
@@ -152,14 +169,7 @@ class AdbcDataDictionary(SyncDataDictionaryBase):
|
|
|
152
169
|
self._log_schema_introspect(driver, schema_name=schema_name, table_name=None, operation="tables")
|
|
153
170
|
|
|
154
171
|
if dialect == "bigquery":
|
|
155
|
-
|
|
156
|
-
tables_table = f"`{schema_name}.INFORMATION_SCHEMA.TABLES`"
|
|
157
|
-
kcu_table = f"`{schema_name}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE`"
|
|
158
|
-
rc_table = f"`{schema_name}.INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS`"
|
|
159
|
-
else:
|
|
160
|
-
tables_table = "INFORMATION_SCHEMA.TABLES"
|
|
161
|
-
kcu_table = "INFORMATION_SCHEMA.KEY_COLUMN_USAGE"
|
|
162
|
-
rc_table = "INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS"
|
|
172
|
+
tables_table, kcu_table, rc_table = format_bigquery_information_schema_tables(schema_name)
|
|
163
173
|
query_text = self._get_query_text(dialect, "tables_by_schema").format(
|
|
164
174
|
tables_table=tables_table, kcu_table=kcu_table, rc_table=rc_table
|
|
165
175
|
)
|
|
@@ -186,7 +196,7 @@ class AdbcDataDictionary(SyncDataDictionaryBase):
|
|
|
186
196
|
self._log_table_describe(driver, schema_name=schema_name, table_name=table, operation="columns")
|
|
187
197
|
|
|
188
198
|
if dialect == "bigquery":
|
|
189
|
-
schema_prefix =
|
|
199
|
+
schema_prefix = format_bigquery_schema_prefix(schema_name)
|
|
190
200
|
if table is None:
|
|
191
201
|
query_text = self._get_query_text(dialect, "columns_by_schema").format(schema_prefix=schema_prefix)
|
|
192
202
|
return driver.select(query_text, schema_name=schema_name, schema_type=ColumnMetadata)
|
|
@@ -293,12 +303,7 @@ class AdbcDataDictionary(SyncDataDictionaryBase):
|
|
|
293
303
|
self._log_table_describe(driver, schema_name=schema_name, table_name=table, operation="foreign_keys")
|
|
294
304
|
|
|
295
305
|
if dialect == "bigquery":
|
|
296
|
-
|
|
297
|
-
kcu_table = f"`{schema_name}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE`"
|
|
298
|
-
rc_table = f"`{schema_name}.INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS`"
|
|
299
|
-
else:
|
|
300
|
-
kcu_table = "INFORMATION_SCHEMA.KEY_COLUMN_USAGE"
|
|
301
|
-
rc_table = "INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS"
|
|
306
|
+
_, kcu_table, rc_table = format_bigquery_information_schema_tables(schema_name)
|
|
302
307
|
if table is None:
|
|
303
308
|
query_text = self._get_query_text(dialect, "foreign_keys_by_schema").format(
|
|
304
309
|
kcu_table=kcu_table, rc_table=rc_table
|
|
@@ -346,11 +346,12 @@ class AiomysqlADKStore(BaseAsyncADKStore["AiomysqlConfig"]):
|
|
|
346
346
|
|
|
347
347
|
async def append_event_and_update_state(
|
|
348
348
|
self, event_record: EventRecord, session_id: str, state: "dict[str, Any]"
|
|
349
|
-
) ->
|
|
349
|
+
) -> SessionRecord:
|
|
350
350
|
"""Atomically append an event and update the session's durable state.
|
|
351
351
|
|
|
352
|
-
|
|
353
|
-
|
|
352
|
+
MySQL doesn't support UPDATE...RETURNING; we follow the UPDATE with a
|
|
353
|
+
SELECT inside the same transaction so callers get the refreshed row
|
|
354
|
+
in a single round-trip pair (no separate connection acquisition).
|
|
354
355
|
|
|
355
356
|
Args:
|
|
356
357
|
event_record: Event record to store.
|
|
@@ -373,6 +374,12 @@ class AiomysqlADKStore(BaseAsyncADKStore["AiomysqlConfig"]):
|
|
|
373
374
|
WHERE id = %s
|
|
374
375
|
"""
|
|
375
376
|
|
|
377
|
+
select_sql = f"""
|
|
378
|
+
SELECT id, app_name, user_id, state, create_time, update_time
|
|
379
|
+
FROM {self._session_table}
|
|
380
|
+
WHERE id = %s
|
|
381
|
+
"""
|
|
382
|
+
|
|
376
383
|
async with (
|
|
377
384
|
self._config.provide_connection() as conn,
|
|
378
385
|
AiomysqlCursor(conn, cursor_class=AiomysqlRawCursor) as cursor,
|
|
@@ -388,8 +395,24 @@ class AiomysqlADKStore(BaseAsyncADKStore["AiomysqlConfig"]):
|
|
|
388
395
|
),
|
|
389
396
|
)
|
|
390
397
|
await cursor.execute(update_sql, (state_json, session_id))
|
|
398
|
+
await cursor.execute(select_sql, (session_id,))
|
|
399
|
+
row = await cursor.fetchone()
|
|
391
400
|
await conn.commit()
|
|
392
401
|
|
|
402
|
+
if row is None:
|
|
403
|
+
msg = f"Session {session_id} not found during append_event_and_update_state."
|
|
404
|
+
raise ValueError(msg)
|
|
405
|
+
|
|
406
|
+
state_value = row[3]
|
|
407
|
+
return SessionRecord(
|
|
408
|
+
id=row[0],
|
|
409
|
+
app_name=row[1],
|
|
410
|
+
user_id=row[2],
|
|
411
|
+
state=from_json(state_value) if isinstance(state_value, str) else state_value,
|
|
412
|
+
create_time=row[4],
|
|
413
|
+
update_time=row[5],
|
|
414
|
+
)
|
|
415
|
+
|
|
393
416
|
async def get_events(
|
|
394
417
|
self, session_id: str, after_timestamp: "datetime | None" = None, limit: "int | None" = None
|
|
395
418
|
) -> "list[EventRecord]":
|
|
@@ -253,10 +253,6 @@ class AiomysqlConfig(AsyncDatabaseConfig[AiomysqlConnection, "AiomysqlPool", Aio
|
|
|
253
253
|
await self.connection_instance.wait_closed()
|
|
254
254
|
self.connection_instance = None
|
|
255
255
|
|
|
256
|
-
async def close_pool(self) -> None:
|
|
257
|
-
"""Close the connection pool."""
|
|
258
|
-
await self._close_pool()
|
|
259
|
-
|
|
260
256
|
async def create_connection(self) -> AiomysqlConnection:
|
|
261
257
|
"""Create a single async connection (not from pool).
|
|
262
258
|
|
|
@@ -22,7 +22,7 @@ from sqlspec.exceptions import (
|
|
|
22
22
|
)
|
|
23
23
|
from sqlspec.utils.serializers import from_json, to_json
|
|
24
24
|
from sqlspec.utils.type_converters import build_uuid_coercions
|
|
25
|
-
from sqlspec.utils.type_guards import has_cursor_metadata, has_lastrowid, has_rowcount
|
|
25
|
+
from sqlspec.utils.type_guards import has_cursor_metadata, has_lastrowid, has_rowcount
|
|
26
26
|
|
|
27
27
|
if TYPE_CHECKING:
|
|
28
28
|
from collections.abc import Mapping, Sequence
|
|
@@ -67,6 +67,50 @@ MYSQL_CR_CONN_HOST_ERROR = 2003
|
|
|
67
67
|
MYSQL_CR_UNKNOWN_HOST = 2005
|
|
68
68
|
MYSQL_CR_SERVER_GONE_ERROR = 2006
|
|
69
69
|
MYSQL_CR_SERVER_LOST = 2013
|
|
70
|
+
MYSQL_SYNTAX_ERROR_MIN = 1064
|
|
71
|
+
MYSQL_SYNTAX_ERROR_MAX_EXCLUSIVE = 1100
|
|
72
|
+
|
|
73
|
+
_MYSQL_MIGRATION_ERROR_CODES = frozenset((1061, 1091))
|
|
74
|
+
_MYSQL_SQLSTATE_EXACT_DISPATCH: dict[str, tuple[type[SQLSpecError], str]] = {
|
|
75
|
+
"23505": (UniqueViolationError, "unique constraint violation"),
|
|
76
|
+
"23503": (ForeignKeyViolationError, "foreign key constraint violation"),
|
|
77
|
+
"23502": (NotNullViolationError, "not-null constraint violation"),
|
|
78
|
+
"23514": (CheckViolationError, "check constraint violation"),
|
|
79
|
+
}
|
|
80
|
+
_MYSQL_SQLSTATE_PREFIX_DISPATCH: dict[str, tuple[type[SQLSpecError], str]] = {
|
|
81
|
+
"23": (IntegrityError, "integrity constraint violation"),
|
|
82
|
+
"28": (PermissionDeniedError, "authorization error"),
|
|
83
|
+
"40": (TransactionError, "transaction error"),
|
|
84
|
+
"42": (SQLParsingError, "SQL syntax error"),
|
|
85
|
+
"08": (DatabaseConnectionError, "connection error"),
|
|
86
|
+
"22": (DataError, "data error"),
|
|
87
|
+
}
|
|
88
|
+
_MYSQL_CONSTRAINT_ERROR_DISPATCH: dict[int, tuple[type[SQLSpecError], str]] = {
|
|
89
|
+
MYSQL_ER_DUP_ENTRY: (UniqueViolationError, "unique constraint violation"),
|
|
90
|
+
1216: (ForeignKeyViolationError, "foreign key constraint violation"),
|
|
91
|
+
1217: (ForeignKeyViolationError, "foreign key constraint violation"),
|
|
92
|
+
1451: (ForeignKeyViolationError, "foreign key constraint violation"),
|
|
93
|
+
1452: (ForeignKeyViolationError, "foreign key constraint violation"),
|
|
94
|
+
1048: (NotNullViolationError, "not-null constraint violation"),
|
|
95
|
+
MYSQL_ER_NO_DEFAULT_FOR_FIELD: (NotNullViolationError, "not-null constraint violation"),
|
|
96
|
+
MYSQL_ER_CHECK_CONSTRAINT_VIOLATED: (CheckViolationError, "check constraint violation"),
|
|
97
|
+
}
|
|
98
|
+
_MYSQL_ACCESS_ERROR_DISPATCH: dict[int, tuple[type[SQLSpecError], str]] = {
|
|
99
|
+
MYSQL_ER_DBACCESS_DENIED: (PermissionDeniedError, "access denied"),
|
|
100
|
+
MYSQL_ER_ACCESS_DENIED: (PermissionDeniedError, "access denied"),
|
|
101
|
+
MYSQL_ER_TABLEACCESS_DENIED: (PermissionDeniedError, "access denied"),
|
|
102
|
+
}
|
|
103
|
+
_MYSQL_TRANSACTION_ERROR_DISPATCH: dict[int, tuple[type[SQLSpecError], str]] = {
|
|
104
|
+
MYSQL_ER_LOCK_DEADLOCK: (DeadlockError, "deadlock detected"),
|
|
105
|
+
MYSQL_ER_LOCK_WAIT_TIMEOUT: (QueryTimeoutError, "lock wait timeout"),
|
|
106
|
+
}
|
|
107
|
+
_MYSQL_CONNECTION_ERROR_DISPATCH: dict[int, tuple[type[SQLSpecError], str]] = {
|
|
108
|
+
MYSQL_CR_SERVER_LOST: (ConnectionTimeoutError, "connection lost"),
|
|
109
|
+
MYSQL_CR_CONNECTION_ERROR: (DatabaseConnectionError, "connection error"),
|
|
110
|
+
MYSQL_CR_CONN_HOST_ERROR: (DatabaseConnectionError, "connection error"),
|
|
111
|
+
MYSQL_CR_UNKNOWN_HOST: (DatabaseConnectionError, "connection error"),
|
|
112
|
+
MYSQL_CR_SERVER_GONE_ERROR: (DatabaseConnectionError, "connection error"),
|
|
113
|
+
}
|
|
70
114
|
|
|
71
115
|
|
|
72
116
|
def _bool_to_int(value: bool) -> int:
|
|
@@ -210,66 +254,60 @@ def create_mapped_exception(error: Any, *, logger: Any | None = None) -> "SQLSpe
|
|
|
210
254
|
Returns:
|
|
211
255
|
True to suppress expected migration errors, or a SQLSpec exception
|
|
212
256
|
"""
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
257
|
+
error_args = getattr(error, "args", ())
|
|
258
|
+
error_code = error_args[0] if error_args and isinstance(error_args[0], int) else None
|
|
259
|
+
sqlstate_attr = getattr(error, "sqlstate", None)
|
|
260
|
+
sqlstate = sqlstate_attr if isinstance(sqlstate_attr, str) else None
|
|
261
|
+
sqlstate_prefix = sqlstate[:2] if isinstance(sqlstate, str) and sqlstate else None
|
|
216
262
|
|
|
217
263
|
# Migration-specific errors to suppress
|
|
218
|
-
if error_code in
|
|
264
|
+
if error_code in _MYSQL_MIGRATION_ERROR_CODES:
|
|
219
265
|
if logger is not None:
|
|
220
266
|
logger.warning("aiomysql MySQL expected migration error (ignoring): %s", error)
|
|
221
267
|
return True
|
|
222
268
|
|
|
223
|
-
|
|
224
|
-
if
|
|
225
|
-
return _create_mysql_error(error, sqlstate, error_code,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return _create_mysql_error(error, sqlstate, error_code,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return _create_mysql_error(error, sqlstate, error_code,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if
|
|
253
|
-
return _create_mysql_error(error, sqlstate, error_code, SQLParsingError, "SQL syntax error")
|
|
254
|
-
if error_code in range(1064, 1100):
|
|
269
|
+
dispatch = _MYSQL_SQLSTATE_EXACT_DISPATCH.get(sqlstate) if sqlstate is not None else None
|
|
270
|
+
if dispatch is not None:
|
|
271
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
272
|
+
|
|
273
|
+
dispatch = _MYSQL_CONSTRAINT_ERROR_DISPATCH.get(error_code) if error_code is not None else None
|
|
274
|
+
if dispatch is not None:
|
|
275
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
276
|
+
|
|
277
|
+
if sqlstate_prefix == "23":
|
|
278
|
+
dispatch = _MYSQL_SQLSTATE_PREFIX_DISPATCH["23"]
|
|
279
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
280
|
+
|
|
281
|
+
dispatch = _MYSQL_ACCESS_ERROR_DISPATCH.get(error_code) if error_code is not None else None
|
|
282
|
+
if dispatch is not None:
|
|
283
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
284
|
+
if sqlstate_prefix == "28":
|
|
285
|
+
dispatch = _MYSQL_SQLSTATE_PREFIX_DISPATCH["28"]
|
|
286
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
287
|
+
|
|
288
|
+
dispatch = _MYSQL_TRANSACTION_ERROR_DISPATCH.get(error_code) if error_code is not None else None
|
|
289
|
+
if dispatch is not None:
|
|
290
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
291
|
+
if sqlstate_prefix == "40":
|
|
292
|
+
dispatch = _MYSQL_SQLSTATE_PREFIX_DISPATCH["40"]
|
|
293
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
294
|
+
|
|
295
|
+
if sqlstate_prefix == "42":
|
|
296
|
+
dispatch = _MYSQL_SQLSTATE_PREFIX_DISPATCH["42"]
|
|
297
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
298
|
+
if isinstance(error_code, int) and MYSQL_SYNTAX_ERROR_MIN <= error_code < MYSQL_SYNTAX_ERROR_MAX_EXCLUSIVE:
|
|
255
299
|
return _create_mysql_error(error, sqlstate, error_code, SQLParsingError, "SQL syntax error")
|
|
256
300
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return _create_mysql_error(error, sqlstate, error_code,
|
|
260
|
-
if error_code
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}:
|
|
268
|
-
return _create_mysql_error(error, sqlstate, error_code, DatabaseConnectionError, "connection error")
|
|
269
|
-
|
|
270
|
-
# Data errors
|
|
271
|
-
if sqlstate and sqlstate.startswith("22"):
|
|
272
|
-
return _create_mysql_error(error, sqlstate, error_code, DataError, "data error")
|
|
301
|
+
if sqlstate_prefix == "08":
|
|
302
|
+
dispatch = _MYSQL_SQLSTATE_PREFIX_DISPATCH["08"]
|
|
303
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
304
|
+
dispatch = _MYSQL_CONNECTION_ERROR_DISPATCH.get(error_code) if error_code is not None else None
|
|
305
|
+
if dispatch is not None:
|
|
306
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
307
|
+
|
|
308
|
+
if sqlstate_prefix == "22":
|
|
309
|
+
dispatch = _MYSQL_SQLSTATE_PREFIX_DISPATCH["22"]
|
|
310
|
+
return _create_mysql_error(error, sqlstate, error_code, dispatch[0], dispatch[1])
|
|
273
311
|
|
|
274
312
|
return _create_mysql_error(error, sqlstate, error_code, SQLSpecError, "database error")
|
|
275
313
|
|
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, ClassVar
|
|
|
4
4
|
|
|
5
5
|
from mypy_extensions import mypyc_attr
|
|
6
6
|
|
|
7
|
+
from sqlspec.data_dictionary.dialects.mysql import resolve_mysql_json_type
|
|
7
8
|
from sqlspec.driver import AsyncDataDictionaryBase
|
|
8
9
|
from sqlspec.typing import ColumnMetadata, ForeignKeyMetadata, IndexMetadata, TableMetadata, VersionInfo
|
|
9
10
|
|
|
@@ -56,10 +57,7 @@ class AiomysqlDataDictionary(AsyncDataDictionaryBase):
|
|
|
56
57
|
version_info = await self.get_version(driver)
|
|
57
58
|
|
|
58
59
|
if type_category == "json":
|
|
59
|
-
|
|
60
|
-
if version_info and json_version and version_info >= json_version:
|
|
61
|
-
return "JSON"
|
|
62
|
-
return "TEXT"
|
|
60
|
+
return resolve_mysql_json_type(version_info)
|
|
63
61
|
|
|
64
62
|
return config.get_optimal_type(type_category)
|
|
65
63
|
|
|
@@ -306,9 +306,14 @@ class AiomysqlDriver(AsyncDriverAdapterBase):
|
|
|
306
306
|
columns, records = self._arrow_table_to_rows(arrow_table)
|
|
307
307
|
if records:
|
|
308
308
|
insert_sql = build_insert_statement(table, columns)
|
|
309
|
+
prepared_records = (
|
|
310
|
+
self.prepare_driver_parameters(records, self.statement_config, is_many=True)
|
|
311
|
+
if self._arrow_table_needs_parameter_preparation(arrow_table)
|
|
312
|
+
else records
|
|
313
|
+
)
|
|
309
314
|
exc_handler = self.handle_database_exceptions()
|
|
310
315
|
async with exc_handler, self.with_cursor(self.connection) as cursor:
|
|
311
|
-
await cursor.executemany(insert_sql,
|
|
316
|
+
await cursor.executemany(insert_sql, prepared_records)
|
|
312
317
|
if exc_handler.pending_exception is not None:
|
|
313
318
|
raise exc_handler.pending_exception from None
|
|
314
319
|
|