sqlspec 0.56.1__py3-none-any.whl → 0.56.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.
@@ -46,6 +46,7 @@ from sqlspec.utils.module_loader import import_string
46
46
  from sqlspec.utils.serializers import to_json
47
47
  from sqlspec.utils.type_converters import build_uuid_coercions
48
48
  from sqlspec.utils.type_guards import has_rowcount, has_sqlstate
49
+ from sqlspec.utils.uuids import uuid_from_string
49
50
 
50
51
  if TYPE_CHECKING:
51
52
  from collections.abc import Callable, Mapping
@@ -1068,7 +1069,7 @@ def _convert_uuid_value(value: Any, ordinal: int, row_number: int, element_index
1068
1069
  if not isinstance(value, str):
1069
1070
  raise SQLSpecError(_uuid_binding_error(ordinal, value, row_number, element_index))
1070
1071
  try:
1071
- return str(UUID(value))
1072
+ return str(uuid_from_string(value))
1072
1073
  except (TypeError, ValueError) as exc:
1073
1074
  raise SQLSpecError(_uuid_binding_error(ordinal, value, row_number, element_index)) from exc
1074
1075
 
@@ -40,7 +40,7 @@ from sqlspec.core import (
40
40
  )
41
41
  from sqlspec.driver import BaseSyncExceptionHandler, SyncDriverAdapterBase, SyncRowStream
42
42
  from sqlspec.exceptions import DatabaseConnectionError, SQLSpecError
43
- from sqlspec.utils.arrow_helpers import arrow_reader_with_deferred_close
43
+ from sqlspec.utils.arrow_helpers import arrow_reader_with_deferred_close, arrow_table_to_pylist
44
44
  from sqlspec.utils.logging import get_logger
45
45
  from sqlspec.utils.module_loader import ensure_pyarrow
46
46
  from sqlspec.utils.serializers import to_json
@@ -129,7 +129,12 @@ class AdbcSelectStreamSource:
129
129
  batch = next(reader)
130
130
  except StopIteration:
131
131
  return []
132
- rows = cast("list[dict[str, Any]]", batch.to_pylist())
132
+ rows = arrow_table_to_pylist(
133
+ batch,
134
+ decode_arrow_extension_types=bool(
135
+ self._driver.driver_features.get("enable_arrow_extension_types", True)
136
+ ),
137
+ )
133
138
  if rows:
134
139
  return rows
135
140
 
@@ -224,7 +229,10 @@ class AdbcDriver(SyncDriverAdapterBase):
224
229
 
225
230
  if is_select_like:
226
231
  arrow_table = cursor.fetch_arrow_table()
227
- data = arrow_table.to_pylist()
232
+ data = arrow_table_to_pylist(
233
+ arrow_table,
234
+ decode_arrow_extension_types=bool(self.driver_features.get("enable_arrow_extension_types", True)),
235
+ )
228
236
  column_names = list(arrow_table.column_names)
229
237
  return self.create_execution_result(
230
238
  cursor,
@@ -772,14 +772,33 @@ def _bigquery_arrow_reader_from_iterable(batches: "Iterable[ArrowRecordBatch]")
772
772
  def _records_to_json_rows(
773
773
  records: "Sequence[Mapping[str, Any]] | Sequence[Sequence[Any]]", columns: "list[str] | None"
774
774
  ) -> "list[dict[str, Any]]":
775
- materialized = list(records)
775
+ materialized = records if isinstance(records, list) else list(records)
776
776
  if not materialized:
777
777
  msg = "load_from_records requires at least one record."
778
778
  raise ImproperConfigurationError(msg)
779
779
 
780
780
  first = materialized[0]
781
781
  if isinstance(first, Mapping):
782
- resolved = columns if columns is not None else list(first.keys())
782
+ if columns is None:
783
+ resolved = list(first.keys())
784
+ expected = set(resolved)
785
+ can_reuse = True
786
+ for record in materialized:
787
+ if not isinstance(record, Mapping):
788
+ msg = "load_from_records mapping records must all be mappings."
789
+ raise ImproperConfigurationError(msg)
790
+ record_keys = list(record.keys())
791
+ if set(record_keys) != expected:
792
+ msg = "load_from_records mapping records must all share the same keys."
793
+ raise ImproperConfigurationError(msg)
794
+ if type(record) is not dict or record_keys != resolved:
795
+ can_reuse = False
796
+ validated_records = cast("list[Mapping[str, Any]]", materialized)
797
+ if can_reuse:
798
+ return cast("list[dict[str, Any]]", validated_records)
799
+ return [{column: record[column] for column in resolved} for record in validated_records]
800
+
801
+ resolved = columns
783
802
  expected = set(resolved)
784
803
  rows: list[dict[str, Any]] = []
785
804
  for record in materialized:
@@ -4,7 +4,6 @@ import contextlib
4
4
  from datetime import date, datetime
5
5
  from decimal import Decimal
6
6
  from typing import TYPE_CHECKING, Any, Final, cast
7
- from uuid import UUID
8
7
 
9
8
  from sqlspec.core import DriverParameterProfile, ParameterStyle, StatementConfig, build_statement_config_from_profile
10
9
  from sqlspec.exceptions import (
@@ -24,6 +23,7 @@ from sqlspec.exceptions import (
24
23
  from sqlspec.utils.serializers import to_json
25
24
  from sqlspec.utils.type_converters import build_decimal_converter, build_uuid_coercions, time_iso_convert
26
25
  from sqlspec.utils.type_guards import has_rowcount
26
+ from sqlspec.utils.uuids import uuid_from_string
27
27
 
28
28
  if TYPE_CHECKING:
29
29
  from collections.abc import Callable, Mapping
@@ -395,7 +395,7 @@ def _restore_uuid_columns(rows: "list[dict[str, Any]]", description: "list[Any]
395
395
  for column in uuid_columns:
396
396
  value = row.get(column)
397
397
  if isinstance(value, str):
398
- row[column] = UUID(value)
398
+ row[column] = uuid_from_string(value)
399
399
 
400
400
 
401
401
  driver_profile = build_profile()
@@ -6,8 +6,9 @@ and Oracle's JSON storage types via connection type handlers.
6
6
  Routing matrix (input):
7
7
 
8
8
  * Oracle 21c+ native ``JSON``: bind via ``DB_TYPE_JSON`` (binary OSON).
9
- * Oracle 12c-20c with ``BLOB CHECK (... IS JSON)``: bind via ``DB_TYPE_BLOB`` with
10
- textual UTF-8 JSON bytes.
9
+ * Oracle 12c-20c with ``BLOB CHECK (... IS JSON)``: the driver pre-serializes
10
+ JSON-shaped values and creates a ``DB_TYPE_BLOB`` locator containing UTF-8
11
+ JSON before input-handler dispatch.
11
12
  * Oracle 11g and earlier: bind via ``DB_TYPE_CLOB`` with serialized JSON string.
12
13
  * The rung is chosen by ``resolve_oracle_json_storage``, the single JSON-threshold
13
14
  source shared with ``OracleVersionInfo`` and the extension stores.
@@ -33,11 +34,12 @@ handler returns ``None`` for values it does not own.
33
34
  """
34
35
 
35
36
  from functools import partial
36
- from typing import TYPE_CHECKING, Any, cast
37
+ from typing import TYPE_CHECKING, Any
37
38
 
38
39
  from oracledb import DB_TYPE_CHAR, DB_TYPE_NCHAR, DB_TYPE_NVARCHAR, DB_TYPE_VARCHAR
39
40
 
40
41
  from sqlspec.adapters.oracledb._typing import DB_TYPE_BLOB, DB_TYPE_CLOB, DB_TYPE_JSON, DB_TYPE_LONG, DB_TYPE_LONG_RAW
42
+ from sqlspec.adapters.oracledb.data_dictionary import resolve_oracle_connection_major
41
43
  from sqlspec.data_dictionary.dialects.oracle import (
42
44
  ORACLE_JSON_STORAGE_BLOB_JSON,
43
45
  ORACLE_JSON_STORAGE_NATIVE,
@@ -51,6 +53,7 @@ if TYPE_CHECKING:
51
53
  __all__ = (
52
54
  "chain_input_handler",
53
55
  "chain_output_handler",
56
+ "is_json_payload",
54
57
  "json_converter_in_blob",
55
58
  "json_converter_in_clob",
56
59
  "json_converter_out_blob",
@@ -131,7 +134,7 @@ def chain_input_handler(inner: Any, fallback: "Any | None") -> Any:
131
134
  instance: python-oracledb selects its calling convention via
132
135
  ``inspect.signature(handler)``, which succeeds for a partial of a (compiled)
133
136
  function but raises ``ValueError`` for a compiled ``__call__`` object -- the
134
- latter forces the legacy 6-argument call and breaks fetches.
137
+ latter forces the older 6-argument call and breaks fetches.
135
138
  """
136
139
  return partial(_chained_input_handler, inner, fallback)
137
140
 
@@ -144,7 +147,7 @@ def chain_output_handler(inner: Any, fallback: "Any | None") -> Any:
144
147
  return partial(_chained_output_handler, inner, fallback)
145
148
 
146
149
 
147
- def _is_json_payload(value: Any) -> bool:
150
+ def is_json_payload(value: Any) -> bool:
148
151
  """Return True if the value should be claimed by the JSON input handler.
149
152
 
150
153
  ``dict`` and ``tuple``/``list`` of dicts are claimed. Sequences whose first
@@ -167,10 +170,10 @@ def _is_json_payload(value: Any) -> bool:
167
170
 
168
171
  def _input_type_handler(cursor: "Cursor | AsyncCursor", value: Any, arraysize: int) -> Any:
169
172
  """Oracle input type handler for JSON-shaped Python values."""
170
- if not _is_json_payload(value):
173
+ if not is_json_payload(value):
171
174
  return None
172
175
 
173
- server_major = cast("int | None", getattr(cursor.connection, "_sqlspec_oracle_major", None))
176
+ server_major = resolve_oracle_connection_major(cursor.connection)
174
177
 
175
178
  if server_major is None:
176
179
  return cursor.var(DB_TYPE_JSON, arraysize=arraysize)
@@ -8,7 +8,8 @@ Wrap a parameter value to express explicit intent:
8
8
 
9
9
  * :class:`OracleClob` — bind as ``DB_TYPE_CLOB`` regardless of length.
10
10
  * :class:`OracleBlob` — bind as ``DB_TYPE_BLOB`` regardless of length.
11
- * :class:`OracleJson` — bind as native JSON; defers to the C1 input handler.
11
+ * :class:`OracleJson` — express JSON intent; the input handler selects the
12
+ storage type supported by the connected Oracle version.
12
13
 
13
14
  The wrappers themselves perform no validation — type discipline lives at the
14
15
  routing site so error messages can include database-context detail.
@@ -38,7 +39,7 @@ class OracleBlob:
38
39
 
39
40
 
40
41
  class OracleJson:
41
- """Mark a value to be bound as native ``DB_TYPE_JSON`` regardless of detected version."""
42
+ """Mark a value for version-aware Oracle JSON binding."""
42
43
 
43
44
  __slots__ = ("value",)
44
45
 
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any
10
10
  from sqlspec.adapters.oracledb._json_handlers import chain_input_handler, chain_output_handler
11
11
  from sqlspec.adapters.oracledb._typing import DB_TYPE_RAW
12
12
  from sqlspec.utils.logging import get_logger
13
+ from sqlspec.utils.uuids import uuid_from_bytes
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from oracledb import AsyncConnection, AsyncCursor, Connection, Cursor
@@ -59,7 +60,7 @@ def uuid_converter_out(value: bytes | None) -> "uuid.UUID | bytes | None":
59
60
  return value
60
61
 
61
62
  try:
62
- return uuid.UUID(bytes=value)
63
+ return uuid_from_bytes(value)
63
64
  except (ValueError, TypeError):
64
65
  logger.debug("RAW(16) value is not valid UUID format, returning as bytes", extra={"value_length": len(value)})
65
66
  return value
@@ -296,7 +296,7 @@ class OracleAsyncADKStore(BaseAsyncADKStore["OracleAsyncConfig"]):
296
296
  config: OracleAsyncConfig with extension_config["adk"] settings.
297
297
 
298
298
  Notes:
299
- - JSON storage type detected based on Oracle version (21c+, 12c+, legacy)
299
+ - JSON storage type detected by capability (native JSON, BLOB IS JSON, or plain LOB)
300
300
  - event_data stored as JSON (21c+) or BLOB (older versions)
301
301
  - TIMESTAMP WITH TIME ZONE for timezone-aware timestamps
302
302
  - Named parameters using :param_name
@@ -1265,7 +1265,7 @@ class OracleSyncADKStore(BaseSyncADKStore["OracleSyncConfig"]):
1265
1265
  config: OracleSyncConfig with extension_config["adk"] settings.
1266
1266
 
1267
1267
  Notes:
1268
- - JSON storage type detected based on Oracle version (21c+, 12c+, legacy)
1268
+ - JSON storage type detected by capability (native JSON, BLOB IS JSON, or plain LOB)
1269
1269
  - event_data stored as JSON (21c+) or BLOB (older versions)
1270
1270
  - TIMESTAMP WITH TIME ZONE for timezone-aware timestamps
1271
1271
  - Named parameters using :param_name
@@ -23,7 +23,7 @@ from sqlspec.adapters.oracledb._typing import (
23
23
  from sqlspec.adapters.oracledb._uuid_handlers import register_uuid_handlers
24
24
  from sqlspec.adapters.oracledb._vector_handlers import register_numpy_handlers # pyright: ignore[reportPrivateUsage]
25
25
  from sqlspec.adapters.oracledb.core import apply_driver_features, default_statement_config
26
- from sqlspec.adapters.oracledb.data_dictionary import OracleVersionCache
26
+ from sqlspec.adapters.oracledb.data_dictionary import OracleVersionCache, resolve_oracle_connection_major
27
27
  from sqlspec.adapters.oracledb.driver import (
28
28
  OracleAsyncDriver,
29
29
  OracleAsyncExceptionHandler,
@@ -32,7 +32,6 @@ from sqlspec.adapters.oracledb.driver import (
32
32
  )
33
33
  from sqlspec.adapters.oracledb.migrations import OracleAsyncMigrationTracker, OracleSyncMigrationTracker
34
34
  from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs, SyncDatabaseConfig
35
- from sqlspec.data_dictionary.dialects.oracle import parse_oracle_version_components
36
35
  from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
37
36
  from sqlspec.driver._sync import SyncPoolConnectionContext, SyncPoolSessionFactory
38
37
  from sqlspec.extensions.events import EventRuntimeHints
@@ -413,9 +412,9 @@ class OracleSyncConfig(SyncDatabaseConfig[OracleSyncConnection, "OracleSyncConne
413
412
  vector case) internally. UUID registration remains gated for
414
413
  backwards compatibility with existing user configurations.
415
414
 
416
- Caches ``connection._sqlspec_oracle_major`` so the JSON input handler
417
- can pick the right binding path (``DB_TYPE_JSON`` on 21c+, OSON-encoded
418
- ``DB_TYPE_BLOB`` on 19c-20c, JSON-string ``DB_TYPE_CLOB`` on 12c-18c)
415
+ Caches ``connection._sqlspec_oracle_major`` so JSON parameter coercion
416
+ can pick the right binding path (``DB_TYPE_JSON`` on 21c+, a textual
417
+ ``BLOB IS JSON`` locator on 12c-20c, and ``DB_TYPE_CLOB`` before 12c)
419
418
  without re-querying server metadata on every bind. Caches
420
419
  ``connection._sqlspec_vector_return_format`` so the vector output
421
420
  handler can dispatch to ``numpy`` / ``list`` / ``array`` without
@@ -434,7 +433,9 @@ class OracleSyncConfig(SyncDatabaseConfig[OracleSyncConnection, "OracleSyncConne
434
433
 
435
434
  # Stash detected major version on the connection so the JSON input handler
436
435
  # can pick the right binding path without per-bind metadata queries.
437
- setattr(connection, "_sqlspec_oracle_major", _resolve_connection_major(self._oracle_version_cache, connection))
436
+ setattr(
437
+ connection, "_sqlspec_oracle_major", resolve_oracle_connection_major(connection, self._oracle_version_cache)
438
+ )
438
439
  # Stash the vector-read format so the VECTOR output handler can
439
440
  # dispatch without re-reading driver-feature defaults on every fetch.
440
441
  setattr(connection, "_sqlspec_vector_return_format", self.driver_features.get("vector_return_format"))
@@ -628,7 +629,9 @@ class OracleAsyncConfig(AsyncDatabaseConfig[OracleAsyncConnection, "OracleAsyncC
628
629
 
629
630
  # Stash detected major version on the connection so the JSON input handler
630
631
  # can pick the right binding path without per-bind metadata queries.
631
- setattr(connection, "_sqlspec_oracle_major", _resolve_connection_major(self._oracle_version_cache, connection))
632
+ setattr(
633
+ connection, "_sqlspec_oracle_major", resolve_oracle_connection_major(connection, self._oracle_version_cache)
634
+ )
632
635
  # Stash the vector-read format so the VECTOR output handler can
633
636
  # dispatch without re-reading driver-feature defaults on every fetch.
634
637
  setattr(connection, "_sqlspec_vector_return_format", self.driver_features.get("vector_return_format"))
@@ -655,24 +658,3 @@ class OracleAsyncConfig(AsyncDatabaseConfig[OracleAsyncConnection, "OracleAsyncC
655
658
  await self.connection_instance.close()
656
659
  self.connection_instance = None
657
660
  self._oracle_version_cache.reset()
658
-
659
-
660
- def _resolve_connection_major(cache: "OracleVersionCache", connection: Any) -> "int | None":
661
- """Resolve the Oracle server major for connection-setup type handlers.
662
-
663
- Prefers the pool-scoped version cache once a driver has resolved it through
664
- the data-dictionary path. Before that, the major is parsed from
665
- ``connection.version`` — a connection attribute populated at connect time, so
666
- no query is issued — using the same version parser the data dictionary uses.
667
- Returns ``None`` when unavailable; callers treat ``None`` as "assume 21c+".
668
- """
669
- if cache.resolved and cache.version is not None:
670
- return cache.version.major
671
- try:
672
- version_str = connection.version
673
- except AttributeError:
674
- return None
675
- if not version_str:
676
- return None
677
- components = parse_oracle_version_components(str(version_str))
678
- return components[0] if components is not None else None