snowflake-connector-python 5.2.0__cp315-cp315-win_amd64.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.
- snowflake/connector/__init__.py +148 -0
- snowflake/connector/_common/__init__.py +0 -0
- snowflake/connector/_common/extras.py +134 -0
- snowflake/connector/_common/telemetry.py +221 -0
- snowflake/connector/_common/wif_util.py +92 -0
- snowflake/connector/_core/sf_core_python.cp315-win_amd64.pyd +0 -0
- snowflake/connector/_core/sf_core_python.pyi +79 -0
- snowflake/connector/_internal/__init__.py +6 -0
- snowflake/connector/_internal/_private_key_helper.py +31 -0
- snowflake/connector/_internal/api_client/__init__.py +93 -0
- snowflake/connector/_internal/api_client/bridge.py +41 -0
- snowflake/connector/_internal/api_client/client_api.py +976 -0
- snowflake/connector/_internal/arrow.py +47 -0
- snowflake/connector/_internal/arrow_context.py +240 -0
- snowflake/connector/_internal/arrow_stream_async.py +266 -0
- snowflake/connector/_internal/arrow_stream_iterator.cp311-win_amd64.pyd +0 -0
- snowflake/connector/_internal/arrow_stream_iterator.cp312-win_amd64.pyd +0 -0
- snowflake/connector/_internal/arrow_stream_iterator.cp313-win_amd64.pyd +0 -0
- snowflake/connector/_internal/arrow_stream_iterator.cp314-win_amd64.pyd +0 -0
- snowflake/connector/_internal/arrow_stream_iterator.cp314t-win_amd64.pyd +0 -0
- snowflake/connector/_internal/arrow_stream_iterator.cp315-win_amd64.pyd +0 -0
- snowflake/connector/_internal/arrow_stream_iterator.pyi +42 -0
- snowflake/connector/_internal/arrow_stream_utils.py +171 -0
- snowflake/connector/_internal/authenticator_type.py +53 -0
- snowflake/connector/_internal/backward_compatibility.py +245 -0
- snowflake/connector/_internal/binding_converters.py +741 -0
- snowflake/connector/_internal/config_utils.py +71 -0
- snowflake/connector/_internal/connection/__init__.py +39 -0
- snowflake/connector/_internal/connection/connection.py +616 -0
- snowflake/connector/_internal/connection/connection_types.py +13 -0
- snowflake/connector/_internal/connection/constants.py +25 -0
- snowflake/connector/_internal/connection/decorators.py +31 -0
- snowflake/connector/_internal/connection/freezable_proxy.py +104 -0
- snowflake/connector/_internal/connection/queries.py +10 -0
- snowflake/connector/_internal/connection_config_mixin.py +755 -0
- snowflake/connector/_internal/cursor/__init__.py +25 -0
- snowflake/connector/_internal/cursor/base.py +732 -0
- snowflake/connector/_internal/cursor/cursor_types.py +13 -0
- snowflake/connector/_internal/cursor/decorators.py +56 -0
- snowflake/connector/_internal/cursor/query_result.py +202 -0
- snowflake/connector/_internal/cursor/query_result_waiter.py +98 -0
- snowflake/connector/_internal/cursor/result_metadata.py +255 -0
- snowflake/connector/_internal/cursor/result_set_wrapper.py +66 -0
- snowflake/connector/_internal/decorators.py +539 -0
- snowflake/connector/_internal/error_kinds.py +111 -0
- snowflake/connector/_internal/errorcode.py +35 -0
- snowflake/connector/_internal/errorhandler.py +278 -0
- snowflake/connector/_internal/fips.py +28 -0
- snowflake/connector/_internal/logging/__init__.py +52 -0
- snowflake/connector/_internal/logging/config.py +134 -0
- snowflake/connector/_internal/logging/core_logger.py +218 -0
- snowflake/connector/_internal/logging/native_extension_logger.py +144 -0
- snowflake/connector/_internal/logout_config_mapping.py +187 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +57 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/ArrowTableConverter.cpp +943 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/ArrowTableConverter.hpp +63 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +18 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +118 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +116 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/CArrowStreamIterator.cpp +323 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/CArrowStreamIterator.hpp +133 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/CArrowStreamTableIterator.cpp +275 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/CArrowStreamTableIterator.hpp +168 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/ConverterUtil.cpp +439 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/ConverterUtil.hpp +30 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +48 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +76 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +90 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +63 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +62 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +29 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +41 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +65 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +54 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +79 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +52 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +117 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +54 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +34 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +33 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +18 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +39 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +326 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +138 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/Util/time.cpp +65 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/arrow_stream_iterator.cpp +13278 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/arrow_stream_iterator.pyx +204 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +109 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +125 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1914 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +208 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +128 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +58 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +144 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +160 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +98 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +243 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +207 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +80 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +83 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +201 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +108 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +164 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +926 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +182 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/flatcc.c +3052 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3019 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3354 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +344 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +29305 -0
- snowflake/connector/_internal/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +381 -0
- snowflake/connector/_internal/nanoarrow_cpp/Logging/logging.cpp +108 -0
- snowflake/connector/_internal/nanoarrow_cpp/Logging/logging.hpp +47 -0
- snowflake/connector/_internal/nanoarrow_cpp/scripts/.clang-format +8 -0
- snowflake/connector/_internal/nanoarrow_cpp/scripts/format.sh +8 -0
- snowflake/connector/_internal/protobuf_gen/__init__.py +0 -0
- snowflake/connector/_internal/protobuf_gen/database_driver_v1_pb2.py +408 -0
- snowflake/connector/_internal/protobuf_gen/database_driver_v1_pb2.pyi +4487 -0
- snowflake/connector/_internal/protobuf_gen/database_driver_v1_services.py +2088 -0
- snowflake/connector/_internal/protobuf_gen/proto_exception.py +17 -0
- snowflake/connector/_internal/result_batch.py +150 -0
- snowflake/connector/_internal/snowflake_restful.py +222 -0
- snowflake/connector/_internal/snowpark_backcompat_helpers.py +31 -0
- snowflake/connector/_internal/sqlstate.py +11 -0
- snowflake/connector/_internal/statement_utils.py +207 -0
- snowflake/connector/_internal/text_utils.py +112 -0
- snowflake/connector/_internal/type_codes.py +139 -0
- snowflake/connector/_internal/utils.py +65 -0
- snowflake/connector/_internal/write_pandas_operation.py +657 -0
- snowflake/connector/aio/README.md +168 -0
- snowflake/connector/aio/__init__.py +37 -0
- snowflake/connector/aio/connection/__init__.py +17 -0
- snowflake/connector/aio/connection/_connection.py +506 -0
- snowflake/connector/aio/cursor/__init__.py +35 -0
- snowflake/connector/aio/cursor/_base.py +1034 -0
- snowflake/connector/aio/cursor/_dict_cursor.py +78 -0
- snowflake/connector/aio/cursor/_result_set_wrapper.py +35 -0
- snowflake/connector/aio/cursor/_snowflake_cursor.py +72 -0
- snowflake/connector/aio/pandas_tools.py +279 -0
- snowflake/connector/aio/result_batch.py +165 -0
- snowflake/connector/auth/__init__.py +0 -0
- snowflake/connector/auth/workload_identity.py +5 -0
- snowflake/connector/compat.py +22 -0
- snowflake/connector/config_manager.py +392 -0
- snowflake/connector/connection/__init__.py +30 -0
- snowflake/connector/connection/_connection.py +641 -0
- snowflake/connector/connection_config.py +517 -0
- snowflake/connector/constants.py +52 -0
- snowflake/connector/cursor/__init__.py +37 -0
- snowflake/connector/cursor/_base.py +1169 -0
- snowflake/connector/cursor/_chunked_download_reader.py +75 -0
- snowflake/connector/cursor/_dict_cursor.py +78 -0
- snowflake/connector/cursor/_result_set_wrapper.py +35 -0
- snowflake/connector/cursor/_snowflake_cursor.py +72 -0
- snowflake/connector/description.py +7 -0
- snowflake/connector/errors.py +492 -0
- snowflake/connector/network.py +71 -0
- snowflake/connector/pandas_tools.py +191 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +216 -0
- snowflake/connector/telemetry.py +40 -0
- snowflake/connector/time_util.py +11 -0
- snowflake/connector/types.py +134 -0
- snowflake/connector/util_text.py +3 -0
- snowflake/connector/version.py +23 -0
- snowflake_connector_python-5.2.0.dist-info/DELVEWHEEL +2 -0
- snowflake_connector_python-5.2.0.dist-info/METADATA +190 -0
- snowflake_connector_python-5.2.0.dist-info/RECORD +204 -0
- snowflake_connector_python-5.2.0.dist-info/WHEEL +4 -0
- snowflake_connector_python.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class ProtoException(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
class ProtoApplicationException(ProtoException):
|
|
5
|
+
def __init__(self, error, *args, **kwargs):
|
|
6
|
+
self.api_error_pb = error
|
|
7
|
+
super().__init__(error, *args, **kwargs)
|
|
8
|
+
|
|
9
|
+
def __getattr__(self, name):
|
|
10
|
+
try:
|
|
11
|
+
return getattr(self.api_error_pb, name)
|
|
12
|
+
except AttributeError:
|
|
13
|
+
raise AttributeError(f"'{type(self).__name__}' underlying protobuf object has no attribute '{name}'")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ProtoTransportException(ProtoException):
|
|
17
|
+
pass
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Shared result-batch mixin for sync and async result batch implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum, unique
|
|
6
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
7
|
+
|
|
8
|
+
from .arrow_context import ArrowConverterContext
|
|
9
|
+
from .errorhandler import ErrorHandlerMixin
|
|
10
|
+
from .protobuf_gen.database_driver_v1_pb2 import ColumnMetadata, ResultChunk
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ..aio.connection import Connection as AsyncConnection
|
|
15
|
+
from ..connection import Connection
|
|
16
|
+
from .cursor.result_metadata import ResultMetadata
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_ResultBatchT = TypeVar("_ResultBatchT", bound="ResultBatchMixin")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@unique
|
|
23
|
+
class IterUnit(Enum):
|
|
24
|
+
"""Controls what ``ResultBatch.create_iter`` yields."""
|
|
25
|
+
|
|
26
|
+
ROW_UNIT = "row"
|
|
27
|
+
TABLE_UNIT = "table"
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def of(cls, value: IterUnit | str) -> IterUnit:
|
|
31
|
+
if isinstance(value, cls):
|
|
32
|
+
return value
|
|
33
|
+
return cls(value)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@unique
|
|
37
|
+
class IterTableStructure(Enum):
|
|
38
|
+
"""Controls what table format ``TABLE_UNIT`` iteration produces."""
|
|
39
|
+
|
|
40
|
+
ARROW = "arrow"
|
|
41
|
+
PANDAS = "pandas"
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def of(cls, value: IterTableStructure | str) -> IterTableStructure:
|
|
45
|
+
if isinstance(value, cls):
|
|
46
|
+
return value
|
|
47
|
+
return cls(value)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ResultBatchMixin(ErrorHandlerMixin):
|
|
51
|
+
"""Zero-I/O result-batch members shared by sync and async batch classes."""
|
|
52
|
+
|
|
53
|
+
_chunk: ResultChunk
|
|
54
|
+
_description: list[ResultMetadata]
|
|
55
|
+
_connection: Connection | AsyncConnection | None
|
|
56
|
+
_columns: list[ColumnMetadata]
|
|
57
|
+
_arrow_context: ArrowConverterContext
|
|
58
|
+
_numpy: bool
|
|
59
|
+
_use_core_arrow: bool
|
|
60
|
+
_arrow_stream_ptr: int | None
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
chunk: ResultChunk,
|
|
65
|
+
description: list[ResultMetadata],
|
|
66
|
+
connection: Connection | AsyncConnection | None,
|
|
67
|
+
columns: list[ColumnMetadata] | None = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
self._chunk = chunk
|
|
70
|
+
self._description = description
|
|
71
|
+
self._connection = connection
|
|
72
|
+
self._columns = list(columns) if columns else []
|
|
73
|
+
self._arrow_context = (
|
|
74
|
+
ArrowConverterContext.create(connection) if connection is not None else ArrowConverterContext()
|
|
75
|
+
)
|
|
76
|
+
self._numpy = bool(connection.config.numpy) if connection is not None else False
|
|
77
|
+
self._use_core_arrow = bool(connection.config.use_core_arrow) if connection is not None else False
|
|
78
|
+
self._arrow_stream_ptr = None
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_chunks(
|
|
82
|
+
cls: type[_ResultBatchT],
|
|
83
|
+
chunks: list[ResultChunk] | None,
|
|
84
|
+
description: list[ResultMetadata] | None,
|
|
85
|
+
connection: Connection | AsyncConnection | None,
|
|
86
|
+
columns: list[ColumnMetadata] | None = None,
|
|
87
|
+
) -> list[_ResultBatchT] | None:
|
|
88
|
+
"""Create a list of batches from raw result chunks, or ``None`` if unavailable."""
|
|
89
|
+
if chunks is None or description is None:
|
|
90
|
+
return None
|
|
91
|
+
return [cls(chunk=chunk, description=description, connection=connection, columns=columns) for chunk in chunks]
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
# Properties
|
|
95
|
+
# ------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def rowcount(self) -> int:
|
|
99
|
+
return self._chunk.row_count
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def compressed_size(self) -> int | None:
|
|
103
|
+
if self._chunk.HasField("remote"):
|
|
104
|
+
return self._chunk.remote.compressed_size
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def uncompressed_size(self) -> int | None:
|
|
109
|
+
if self._chunk.HasField("remote"):
|
|
110
|
+
return self._chunk.remote.uncompressed_size
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def column_names(self) -> list[str]:
|
|
115
|
+
return [col.name for col in self._description]
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def _errorhandler_connection(self) -> Connection | AsyncConnection | None:
|
|
119
|
+
return self._connection
|
|
120
|
+
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
# Pickle support
|
|
123
|
+
# ------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
def __getstate__(self) -> dict[str, Any]:
|
|
126
|
+
return {
|
|
127
|
+
"chunk_bytes": self._chunk.SerializeToString(),
|
|
128
|
+
"description": self._description,
|
|
129
|
+
"column_bytes": [c.SerializeToString() for c in self._columns],
|
|
130
|
+
"arrow_context": self._arrow_context,
|
|
131
|
+
"numpy": self._numpy,
|
|
132
|
+
"use_core_arrow": self._use_core_arrow,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
136
|
+
chunk = ResultChunk()
|
|
137
|
+
chunk.ParseFromString(state["chunk_bytes"])
|
|
138
|
+
self._chunk = chunk
|
|
139
|
+
self._description = state["description"]
|
|
140
|
+
columns: list[ColumnMetadata] = []
|
|
141
|
+
for raw in state.get("column_bytes", []):
|
|
142
|
+
col = ColumnMetadata()
|
|
143
|
+
col.ParseFromString(raw)
|
|
144
|
+
columns.append(col)
|
|
145
|
+
self._columns = columns
|
|
146
|
+
self._arrow_context = state.get("arrow_context", ArrowConverterContext())
|
|
147
|
+
self._numpy = state.get("numpy", False)
|
|
148
|
+
self._use_core_arrow = state.get("use_core_arrow", False)
|
|
149
|
+
self._connection = None
|
|
150
|
+
self._arrow_stream_ptr = None
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
from snowflake.connector._internal.api_client.client_api import core_driver
|
|
9
|
+
from snowflake.connector._internal.protobuf_gen.database_driver_v1_pb2 import (
|
|
10
|
+
TOKEN_REQUEST_TYPE_ISSUE,
|
|
11
|
+
TOKEN_REQUEST_TYPE_RENEW,
|
|
12
|
+
ConnectionGetInfoResponse,
|
|
13
|
+
)
|
|
14
|
+
from snowflake.connector.errors import OperationalError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..connection import SnowflakeConnection
|
|
19
|
+
|
|
20
|
+
DEFAULT_PROTOCOL = "https"
|
|
21
|
+
DEFAULT_PORT = 443
|
|
22
|
+
APPLICATION_JSON = "application/json"
|
|
23
|
+
|
|
24
|
+
_TOKEN_REQUEST_TYPE_MAP = {
|
|
25
|
+
"ISSUE": TOKEN_REQUEST_TYPE_ISSUE,
|
|
26
|
+
"RENEW": TOKEN_REQUEST_TYPE_RENEW,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SnowflakeRestful:
|
|
31
|
+
"""Backward-compatible REST interface wrapping the driver's internal gRPC API.
|
|
32
|
+
|
|
33
|
+
Used by Snowflake CLI to issue Snowflake REST API calls (v1 and v2)
|
|
34
|
+
through the driver's TLS-configured HTTP client.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, connection: SnowflakeConnection) -> None:
|
|
38
|
+
self._connection = connection
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def _connection_info(self) -> ConnectionGetInfoResponse:
|
|
42
|
+
return self._connection._get_connection_info()
|
|
43
|
+
|
|
44
|
+
def get_user_agent(self) -> str | None:
|
|
45
|
+
"""User-Agent string built by the driver core (None before connect).
|
|
46
|
+
|
|
47
|
+
The Rust core constructs the User-Agent from the client app id, version
|
|
48
|
+
and platform; consumers building REST requests should use this rather
|
|
49
|
+
than reconstructing the value themselves.
|
|
50
|
+
"""
|
|
51
|
+
user_agent: str | None = self._connection_info.user_agent
|
|
52
|
+
return user_agent if user_agent else None
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def token(self) -> str | None:
|
|
56
|
+
"""Required by Python API. Returns None after close (handle released)."""
|
|
57
|
+
if self._connection.is_closed():
|
|
58
|
+
return None
|
|
59
|
+
session_token: str | None = self._connection_info.session_token
|
|
60
|
+
return session_token if session_token else None
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def _host(self) -> str | None:
|
|
64
|
+
host: str | None = self._connection_info.host
|
|
65
|
+
return host
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def _protocol(self) -> str | None:
|
|
69
|
+
url = self._connection_info.server_url
|
|
70
|
+
if not url:
|
|
71
|
+
return DEFAULT_PROTOCOL
|
|
72
|
+
return urlparse(url).scheme or DEFAULT_PROTOCOL
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def _port(self) -> int | None:
|
|
76
|
+
info = self._connection_info
|
|
77
|
+
if info.port:
|
|
78
|
+
return info.port
|
|
79
|
+
url = info.server_url
|
|
80
|
+
if not url:
|
|
81
|
+
return DEFAULT_PORT
|
|
82
|
+
return urlparse(url).port or DEFAULT_PORT
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def server_url(self) -> str:
|
|
86
|
+
url = self._connection_info.server_url
|
|
87
|
+
if url:
|
|
88
|
+
url = url.rstrip("/")
|
|
89
|
+
parsed = urlparse(url)
|
|
90
|
+
if parsed.port is None:
|
|
91
|
+
port = self._port
|
|
92
|
+
return f"{parsed.scheme}://{parsed.hostname}:{port}{parsed.path}"
|
|
93
|
+
return url
|
|
94
|
+
host = self._host
|
|
95
|
+
if not host:
|
|
96
|
+
raise ValueError("server_url and host are both unavailable from connection info")
|
|
97
|
+
return f"{self._protocol}://{host}:{self._port}"
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def master_token(self) -> str | None:
|
|
101
|
+
"""Returns None after close (handle released)."""
|
|
102
|
+
if self._connection.is_closed():
|
|
103
|
+
return None
|
|
104
|
+
info = self._connection._get_connection_info(include_master_token=True)
|
|
105
|
+
master_token: str = info.master_token
|
|
106
|
+
return master_token if master_token else None
|
|
107
|
+
|
|
108
|
+
def _send_http(
|
|
109
|
+
self,
|
|
110
|
+
method: str,
|
|
111
|
+
url: str,
|
|
112
|
+
headers: dict[str, str] | None = None,
|
|
113
|
+
body: dict | str | bytes | None = None,
|
|
114
|
+
) -> tuple[int, dict[str, str], bytes]:
|
|
115
|
+
if url.startswith(("http://", "https://")):
|
|
116
|
+
parsed = urlparse(url)
|
|
117
|
+
server_parsed = urlparse(self.server_url)
|
|
118
|
+
if parsed.hostname and server_parsed.hostname and parsed.hostname != server_parsed.hostname:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"Absolute URL host '{parsed.hostname}' does not match "
|
|
121
|
+
f"connection server host '{server_parsed.hostname}'. "
|
|
122
|
+
f"Pass a relative path instead."
|
|
123
|
+
)
|
|
124
|
+
relative_url = parsed.path
|
|
125
|
+
if parsed.query:
|
|
126
|
+
relative_url = f"{relative_url}?{parsed.query}"
|
|
127
|
+
url = relative_url
|
|
128
|
+
|
|
129
|
+
if body is not None:
|
|
130
|
+
if isinstance(body, dict):
|
|
131
|
+
encoded_body = json.dumps(body).encode("utf-8")
|
|
132
|
+
elif isinstance(body, str):
|
|
133
|
+
encoded_body = body.encode("utf-8")
|
|
134
|
+
else:
|
|
135
|
+
encoded_body = body
|
|
136
|
+
else:
|
|
137
|
+
encoded_body = None
|
|
138
|
+
|
|
139
|
+
response = core_driver.connection_send_http(
|
|
140
|
+
conn_handle=self._connection.conn_handle, # type: ignore[arg-type]
|
|
141
|
+
method=method,
|
|
142
|
+
url=url,
|
|
143
|
+
headers=headers or {},
|
|
144
|
+
body=encoded_body,
|
|
145
|
+
)
|
|
146
|
+
return response.status_code, dict(response.headers), response.body
|
|
147
|
+
|
|
148
|
+
_SUPPORTED_REQUEST_METHODS = {"get", "post"}
|
|
149
|
+
|
|
150
|
+
def request(
|
|
151
|
+
self,
|
|
152
|
+
url: str,
|
|
153
|
+
body: dict | None = None,
|
|
154
|
+
method: str = "post",
|
|
155
|
+
client: str = "sfsql",
|
|
156
|
+
timeout: int | None = None,
|
|
157
|
+
_no_results: bool = False,
|
|
158
|
+
_include_retry_params: bool = False,
|
|
159
|
+
_no_retry: bool = False,
|
|
160
|
+
) -> dict:
|
|
161
|
+
if client == "sfsql":
|
|
162
|
+
raise NotImplementedError(
|
|
163
|
+
"request() with client='sfsql' is not supported. Use cursor.execute() for SQL queries."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if method.lower() not in self._SUPPORTED_REQUEST_METHODS:
|
|
167
|
+
raise ValueError(f"Unsupported HTTP method '{method}'. Only 'get' and 'post' are supported.")
|
|
168
|
+
|
|
169
|
+
headers = {
|
|
170
|
+
"Content-Type": APPLICATION_JSON,
|
|
171
|
+
"Accept": APPLICATION_JSON,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
status_code, _, response_body = self._send_http(
|
|
175
|
+
method=method,
|
|
176
|
+
url=url,
|
|
177
|
+
headers=headers,
|
|
178
|
+
body=body,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if status_code >= 400:
|
|
182
|
+
raise OperationalError.from_http_response(status_code, response_body)
|
|
183
|
+
|
|
184
|
+
return json.loads(response_body) if response_body else {}
|
|
185
|
+
|
|
186
|
+
def fetch(
|
|
187
|
+
self,
|
|
188
|
+
method: str,
|
|
189
|
+
full_url: str,
|
|
190
|
+
headers: dict[str, str],
|
|
191
|
+
data: str | None = None,
|
|
192
|
+
timeout: int | None = None,
|
|
193
|
+
token: str | None = None,
|
|
194
|
+
no_retry: bool = False,
|
|
195
|
+
raise_raw_http_failure: bool = False,
|
|
196
|
+
**kwargs: object,
|
|
197
|
+
) -> dict | list:
|
|
198
|
+
status_code, _, response_body = self._send_http(
|
|
199
|
+
method=method,
|
|
200
|
+
url=full_url,
|
|
201
|
+
headers=headers,
|
|
202
|
+
body=data,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if raise_raw_http_failure and status_code >= 400:
|
|
206
|
+
raise OperationalError.from_http_response(status_code, response_body)
|
|
207
|
+
|
|
208
|
+
return json.loads(response_body) if response_body else {}
|
|
209
|
+
|
|
210
|
+
def _token_request(self, request_type: str) -> dict:
|
|
211
|
+
proto_type = _TOKEN_REQUEST_TYPE_MAP.get(request_type.upper())
|
|
212
|
+
if proto_type is None:
|
|
213
|
+
raise ValueError(f"Unknown token request type: {request_type!r}. Must be 'ISSUE' or 'RENEW'.")
|
|
214
|
+
|
|
215
|
+
response = core_driver.connection_request_token(
|
|
216
|
+
conn_handle=self._connection.conn_handle, # type: ignore[arg-type]
|
|
217
|
+
request_type=proto_type,
|
|
218
|
+
)
|
|
219
|
+
data: dict = {"sessionToken": response.session_token}
|
|
220
|
+
if response.HasField("validity_in_seconds"):
|
|
221
|
+
data["validityInSecondsST"] = response.validity_in_seconds
|
|
222
|
+
return {"data": data, "success": True}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_STAGE_PREFIXES = ("@", "snow://", "/")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def file_uri(path: str) -> str:
|
|
10
|
+
"""Add a ``file://`` prefix unless *path* already has one or is quoted."""
|
|
11
|
+
stripped = path.strip()
|
|
12
|
+
return stripped if stripped.startswith(("'", "file://")) else f"file://{stripped}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def stage_ref(path: str) -> str:
|
|
16
|
+
"""Add an ``@`` prefix unless *path* already names a stage."""
|
|
17
|
+
stripped = path.strip()
|
|
18
|
+
return stripped if stripped.startswith(("'", *_STAGE_PREFIXES)) else f"@{stripped}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def unquoted_stage_ref(path: str) -> str:
|
|
22
|
+
"""Add an ``@`` prefix and strip quoting; the streaming RPC takes the stage name as a field, not SQL."""
|
|
23
|
+
stripped = path.strip()
|
|
24
|
+
if len(stripped) > 1 and stripped.startswith("'") and stripped.endswith("'"):
|
|
25
|
+
stripped = stripped[1:-1]
|
|
26
|
+
return stripped if stripped.startswith(_STAGE_PREFIXES) else f"@{stripped}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def put_get_options(options: dict[str, Any] | None) -> str:
|
|
30
|
+
"""Render an options mapping as trailing ``KEY=VALUE`` pairs, unquoted."""
|
|
31
|
+
return " ".join(f"{name}={value}" for name, value in (options or {}).items())
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncGenerator, Generator
|
|
4
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
5
|
+
|
|
6
|
+
from .api_client.client_api import async_core_driver, core_driver
|
|
7
|
+
from .protobuf_gen.database_driver_v1_pb2 import (
|
|
8
|
+
ConnectionHandle,
|
|
9
|
+
DatabaseFetchChunkResponse,
|
|
10
|
+
PrepareResult,
|
|
11
|
+
ResultSetDescriptor,
|
|
12
|
+
ResultSetGetStreamResponse,
|
|
13
|
+
StatementHandle,
|
|
14
|
+
)
|
|
15
|
+
from .sqlstate import SQLSTATE_SUCCESS
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@contextmanager
|
|
19
|
+
def statement(conn_handle: ConnectionHandle, query: str) -> Generator[StatementHandle]:
|
|
20
|
+
"""Context manager that owns the full lifecycle of a statement handle.
|
|
21
|
+
|
|
22
|
+
Allocates a new statement on the server, binds the given SQL query to it,
|
|
23
|
+
and yields the ``StatementHandle`` for execution. The statement is
|
|
24
|
+
guaranteed to be released when the context exits, even if an exception
|
|
25
|
+
is raised.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
conn_handle: Active Snowflake conn_handle.
|
|
29
|
+
query: SQL text to bind to the newly created statement.
|
|
30
|
+
|
|
31
|
+
Yields:
|
|
32
|
+
StatementHandle: A handle that can be passed to ``statement_execute``
|
|
33
|
+
or other statement-level APIs.
|
|
34
|
+
"""
|
|
35
|
+
stmt_handle = core_driver.statement_new(conn_handle=conn_handle).stmt_handle
|
|
36
|
+
try:
|
|
37
|
+
core_driver.statement_set_query(stmt_handle=stmt_handle, query=query)
|
|
38
|
+
yield stmt_handle
|
|
39
|
+
finally:
|
|
40
|
+
core_driver.statement_release(stmt_handle=stmt_handle)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@asynccontextmanager
|
|
44
|
+
async def async_statement(conn_handle: ConnectionHandle, query: str) -> AsyncGenerator[StatementHandle]:
|
|
45
|
+
stmt_handle = (await async_core_driver.statement_new(conn_handle=conn_handle)).stmt_handle
|
|
46
|
+
try:
|
|
47
|
+
await async_core_driver.statement_set_query(stmt_handle=stmt_handle, query=query)
|
|
48
|
+
yield stmt_handle
|
|
49
|
+
finally:
|
|
50
|
+
await async_core_driver.statement_release(stmt_handle=stmt_handle)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_stream_ptr(result: DatabaseFetchChunkResponse | PrepareResult | ResultSetGetStreamResponse | None) -> int:
|
|
54
|
+
"""Extract a C ArrowArrayStream pointer from an execute result.
|
|
55
|
+
|
|
56
|
+
The pointer is stored as an 8-byte little-endian value inside
|
|
57
|
+
``result.stream.value``. This function validates every step of
|
|
58
|
+
the extraction and raises descriptive errors on failure, so callers
|
|
59
|
+
can safely pass the returned integer to Arrow C Data Interface
|
|
60
|
+
consumers (e.g. PyArrow ``RecordBatchReader.from_stream``).
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
result: The protobuf response returned by ``statement_execute``.
|
|
64
|
+
Must not be ``None`` and must carry a populated ``stream``
|
|
65
|
+
field.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A non-zero integer representing the memory address of the
|
|
69
|
+
``ArrowArrayStream`` struct.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
RuntimeError: If *result* is ``None``, the stream or its value
|
|
73
|
+
is missing, the value has an unexpected length, or the
|
|
74
|
+
decoded pointer is null.
|
|
75
|
+
"""
|
|
76
|
+
if result is None:
|
|
77
|
+
raise RuntimeError("No query has been executed")
|
|
78
|
+
|
|
79
|
+
if not hasattr(result, "stream") or result.stream is None:
|
|
80
|
+
raise RuntimeError("Execute result does not contain a valid stream")
|
|
81
|
+
|
|
82
|
+
if not hasattr(result.stream, "value") or result.stream.value is None:
|
|
83
|
+
raise RuntimeError("Stream does not contain a valid pointer value")
|
|
84
|
+
|
|
85
|
+
stream_value = result.stream.value
|
|
86
|
+
# 8 bytes = 64-bit pointer, the size of a C ArrowArrayStream* on a 64-bit platform
|
|
87
|
+
if len(stream_value) != 8:
|
|
88
|
+
raise RuntimeError(f"Stream pointer value has wrong length: {len(stream_value)} (expected 8)")
|
|
89
|
+
|
|
90
|
+
stream_ptr = int.from_bytes(stream_value, byteorder="little", signed=False)
|
|
91
|
+
|
|
92
|
+
if stream_ptr == 0:
|
|
93
|
+
raise RuntimeError("Stream pointer is null")
|
|
94
|
+
|
|
95
|
+
return stream_ptr
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def extract_sqlstate(result: PrepareResult | ResultSetDescriptor | None) -> str | None:
|
|
99
|
+
"""Return the SQLSTATE code from an execute result, if meaningful.
|
|
100
|
+
|
|
101
|
+
SQLSTATE ``"00000"`` (successful completion) is normalized to ``None``
|
|
102
|
+
for backwards compatibility with the legacy connector, which omits
|
|
103
|
+
the code on success.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
result: A ``PrepareResult`` or ``ResultSetDescriptor``,
|
|
107
|
+
or ``None`` if no result is available.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
A five-character SQLSTATE string for warnings/errors, or ``None``
|
|
111
|
+
on success or when *result* is ``None``.
|
|
112
|
+
"""
|
|
113
|
+
sql_state = result.sql_state if result else None
|
|
114
|
+
if sql_state and sql_state != SQLSTATE_SUCCESS:
|
|
115
|
+
return sql_state
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Snowflake statement_type_id families/classes that produce a browsable result
|
|
120
|
+
# set or a real update count. When the server omits rows_affected for one of
|
|
121
|
+
# these, the row count is genuinely unknown (-1); for every other classified
|
|
122
|
+
# statement (DDL, session/transaction control, etc.) the legacy connector
|
|
123
|
+
# reported the generic success marker 1, which we reproduce for compatibility.
|
|
124
|
+
# Classification mirrors the core's mask-based taxonomy (level-3 0xF000 family,
|
|
125
|
+
# level-2 0xFF00 class) rather than exact ids, because the concrete ids the
|
|
126
|
+
# server returns (e.g. ALTER SESSION 0x4100, COMMIT 0x5100) do not match named
|
|
127
|
+
# constants but do classify correctly by family.
|
|
128
|
+
_CURSOR_OR_DML_FAMILIES = frozenset(
|
|
129
|
+
{
|
|
130
|
+
0x1000, # SELECT
|
|
131
|
+
0x2000, # EXPLAIN
|
|
132
|
+
0x3000, # DML (INSERT/UPDATE/DELETE/MERGE/COPY/...)
|
|
133
|
+
0x7000, # stage file operations (PUT/GET/LIST/REMOVE)
|
|
134
|
+
0x9000, # CALL
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
_CURSOR_CLASSES = frozenset(
|
|
138
|
+
{
|
|
139
|
+
0x4400, # SHOW
|
|
140
|
+
0x4500, # DESCRIBE
|
|
141
|
+
0x4700, # LIST_FILES
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
_CURSOR_EXACT_IDS = frozenset(
|
|
145
|
+
{
|
|
146
|
+
0x6244, # MANAGE_PATS: DDL-family id that returns a browsable result set
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
# Level-3 (0xF000) families that produce no affected-row count but which the
|
|
150
|
+
# legacy connector reported as rowcount == 1. Restricting the compat fallback
|
|
151
|
+
# to these known families means an unrecognized/future id (e.g. 0xBEEF) is
|
|
152
|
+
# reported as unknown (-1) rather than a spurious success.
|
|
153
|
+
_NO_RESULT_FAMILIES = frozenset(
|
|
154
|
+
{
|
|
155
|
+
0x4000, # SYSCMD (ALTER SESSION 0x4100, USE 0x4300, ...)
|
|
156
|
+
0x5000, # TCL (BEGIN/COMMIT/ROLLBACK, e.g. 0x5100, 0x5400)
|
|
157
|
+
0x6000, # DDL (CREATE/DROP/ALTER, e.g. 0x6100/0x6101/0x6300)
|
|
158
|
+
0x8000, # MISC_QUERY_TYPES
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _produces_result_or_count(statement_type_id: int) -> bool:
|
|
164
|
+
"""Whether the statement type produces a cursor or a real update count.
|
|
165
|
+
|
|
166
|
+
These are the statements for which an absent ``rows_affected`` means the
|
|
167
|
+
count is genuinely unknown; everything else classified is a no-result
|
|
168
|
+
statement that the legacy connector reported as ``1``.
|
|
169
|
+
"""
|
|
170
|
+
if (statement_type_id & 0xF000) in _CURSOR_OR_DML_FAMILIES:
|
|
171
|
+
return True
|
|
172
|
+
if (statement_type_id & 0xFF00) in _CURSOR_CLASSES:
|
|
173
|
+
return True
|
|
174
|
+
return statement_type_id in _CURSOR_EXACT_IDS
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def extract_rowcount(descriptor: ResultSetDescriptor | None) -> int:
|
|
178
|
+
"""Return the number of rows affected from a ResultSetDescriptor.
|
|
179
|
+
|
|
180
|
+
Returns the server's ``rows_affected`` when present (SELECT/DML, including
|
|
181
|
+
``0``). When it is absent the statement produced no affected-row count: for
|
|
182
|
+
a recognized no-result statement (DDL, session/transaction control, etc.)
|
|
183
|
+
this returns ``1`` to match the legacy connector, and ``-1`` for
|
|
184
|
+
cursor/DML statements with a missing count or an absent/unrecognized
|
|
185
|
+
statement type.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
descriptor: The ResultSetDescriptor from a proto response.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Row count from server, ``1`` for legacy no-result successes, or ``-1``
|
|
192
|
+
when unavailable.
|
|
193
|
+
"""
|
|
194
|
+
if not descriptor:
|
|
195
|
+
return -1
|
|
196
|
+
|
|
197
|
+
if descriptor.HasField("rows_affected"):
|
|
198
|
+
return descriptor.rows_affected
|
|
199
|
+
|
|
200
|
+
if not descriptor.HasField("statement_type_id"):
|
|
201
|
+
return -1
|
|
202
|
+
statement_type_id = descriptor.statement_type_id
|
|
203
|
+
if _produces_result_or_count(statement_type_id):
|
|
204
|
+
return -1
|
|
205
|
+
if (statement_type_id & 0xF000) in _NO_RESULT_FAMILIES:
|
|
206
|
+
return 1
|
|
207
|
+
return -1
|