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,13 @@
|
|
|
1
|
+
"""Shared type aliases and type variables for sync and async cursors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
|
+
from typing import Any, TypeVar
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Row = tuple[Any, ...]
|
|
10
|
+
DictRow = dict[str, Any]
|
|
11
|
+
|
|
12
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
13
|
+
Args = TypeVar("Args", bound=Sequence[Any])
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Cursor precondition decorators shared by sync and async cursor implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ...errors import InterfaceError
|
|
8
|
+
from ..decorators import wrap_method_with_awaitable_pre, wrap_method_with_sync_pre
|
|
9
|
+
from ..errorcode import ER_CURSOR_IS_CLOSED
|
|
10
|
+
from .cursor_types import F
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _raise_if_cursor_closed(self: Any) -> None:
|
|
14
|
+
if self.is_closed():
|
|
15
|
+
raise InterfaceError(msg="Cursor is closed.", errno=ER_CURSOR_IS_CLOSED)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _raise_if_cursor_flag_closed(self: Any) -> None:
|
|
19
|
+
if self._closed:
|
|
20
|
+
raise InterfaceError(msg="Cursor is closed.", errno=ER_CURSOR_IS_CLOSED)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _run_prefetch_hook(self: Any) -> None:
|
|
24
|
+
if self._prefetch_hook is not None:
|
|
25
|
+
self._prefetch_hook()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def _run_prefetch_hook_async(self: Any) -> None:
|
|
29
|
+
if self._prefetch_hook is not None:
|
|
30
|
+
await self._prefetch_hook()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def requires_open(func: F) -> F:
|
|
34
|
+
"""Reject the call when the cursor or its connection is closed."""
|
|
35
|
+
return wrap_method_with_sync_pre(func, pre=_raise_if_cursor_closed)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def requires_open_cursor_not_connection(func: F) -> F:
|
|
39
|
+
"""Guard that only checks ``self._closed``, ignoring the connection state.
|
|
40
|
+
|
|
41
|
+
Unlike ``requires_open`` (which delegates to ``is_closed()`` and therefore
|
|
42
|
+
also rejects cursors whose *connection* has been closed), this decorator
|
|
43
|
+
deliberately skips the connection check. This preserves backward
|
|
44
|
+
compatibility with the old driver, where fetch methods on a cursor with
|
|
45
|
+
already-buffered results still worked after ``connection.close()``.
|
|
46
|
+
"""
|
|
47
|
+
return wrap_method_with_sync_pre(func, pre=_raise_if_cursor_flag_closed)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def with_prefetch_hook(func: F) -> F:
|
|
51
|
+
"""Invoke the cursor's prefetch hook (if set) before entering the wrapped method."""
|
|
52
|
+
return wrap_method_with_awaitable_pre(
|
|
53
|
+
func,
|
|
54
|
+
sync_pre=_run_prefetch_hook,
|
|
55
|
+
async_pre=_run_prefetch_hook_async,
|
|
56
|
+
)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ...errors import ProgrammingError
|
|
4
|
+
from ..arrow_stream_utils import release_arrow_stream
|
|
5
|
+
from ..protobuf_gen.database_driver_v1_pb2 import (
|
|
6
|
+
MultiStatementResult,
|
|
7
|
+
PrepareResult,
|
|
8
|
+
ResultSetResponse,
|
|
9
|
+
)
|
|
10
|
+
from ..statement_utils import (
|
|
11
|
+
extract_rowcount,
|
|
12
|
+
extract_sqlstate,
|
|
13
|
+
get_stream_ptr,
|
|
14
|
+
)
|
|
15
|
+
from .result_metadata import QueryResultStats, ResultMetadata, ResultMetadataV2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MultiStatementQueryResultState:
|
|
19
|
+
"""Cursor-level navigation state for multi-statement query results.
|
|
20
|
+
|
|
21
|
+
Tracks the child query IDs returned by the server, which child
|
|
22
|
+
we're currently positioned on, and the parent query ID that
|
|
23
|
+
groups them together.
|
|
24
|
+
|
|
25
|
+
Also holds the submitting ``execute()``'s ``request_id``, which the per-child
|
|
26
|
+
``ResultSetResponse`` does not carry: this object is the only state
|
|
27
|
+
``nextset()`` rescues across ``reset()``, so it is the only place a
|
|
28
|
+
submission-scoped value can survive to be re-applied to each child.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__slots__ = ("parent_qid", "child_query_ids", "request_id", "_next_index")
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
parent_qid: str | None,
|
|
36
|
+
child_query_ids: list[str],
|
|
37
|
+
request_id: str | None,
|
|
38
|
+
) -> None:
|
|
39
|
+
self.parent_qid = parent_qid
|
|
40
|
+
self.child_query_ids = child_query_ids
|
|
41
|
+
self.request_id = request_id
|
|
42
|
+
self._next_index = 0
|
|
43
|
+
|
|
44
|
+
def advance(self) -> str | None:
|
|
45
|
+
"""Return the next child query ID and advance the index, or None if exhausted."""
|
|
46
|
+
if self._next_index >= len(self.child_query_ids):
|
|
47
|
+
return None
|
|
48
|
+
qid = self.child_query_ids[self._next_index]
|
|
49
|
+
self._next_index += 1
|
|
50
|
+
return qid
|
|
51
|
+
|
|
52
|
+
def current_child_query_id(self) -> str | None:
|
|
53
|
+
"""Return the query ID of the currently active child result set."""
|
|
54
|
+
if self._next_index == 0:
|
|
55
|
+
return None
|
|
56
|
+
return self.child_query_ids[self._next_index - 1]
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def from_result(
|
|
60
|
+
multi_result: MultiStatementResult, request_id: str | None
|
|
61
|
+
) -> MultiStatementQueryResultState | None:
|
|
62
|
+
"""Create from a proto MultiStatementResult, or None if there are no children.
|
|
63
|
+
|
|
64
|
+
``request_id`` is required: every caller has the submission's requestId
|
|
65
|
+
in scope, so a None default can never be correct.
|
|
66
|
+
"""
|
|
67
|
+
query_ids = list(multi_result.query_ids)
|
|
68
|
+
if not query_ids:
|
|
69
|
+
return None
|
|
70
|
+
parent = multi_result.parent
|
|
71
|
+
parent_qid = parent.query_id if parent.query_id else None
|
|
72
|
+
return MultiStatementQueryResultState(
|
|
73
|
+
parent_qid=parent_qid,
|
|
74
|
+
child_query_ids=query_ids,
|
|
75
|
+
request_id=request_id,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class QueryResult:
|
|
80
|
+
"""Pure metadata about a query execution result."""
|
|
81
|
+
|
|
82
|
+
__slots__ = (
|
|
83
|
+
"description_v2",
|
|
84
|
+
"_description_v1",
|
|
85
|
+
"sqlstate",
|
|
86
|
+
"sfqid",
|
|
87
|
+
"request_id",
|
|
88
|
+
"query",
|
|
89
|
+
"stats",
|
|
90
|
+
"rowcount",
|
|
91
|
+
"is_file_transfer",
|
|
92
|
+
"query_result_format",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
*,
|
|
98
|
+
description_v2: list[ResultMetadataV2] | None = None,
|
|
99
|
+
sqlstate: str | None = None,
|
|
100
|
+
sfqid: str | None = None,
|
|
101
|
+
request_id: str | None = None,
|
|
102
|
+
query: str | None = None,
|
|
103
|
+
stats: QueryResultStats | None = None,
|
|
104
|
+
rowcount: int | None = None,
|
|
105
|
+
is_file_transfer: bool = False,
|
|
106
|
+
query_result_format: str | None = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
self.description_v2 = description_v2
|
|
109
|
+
self._description_v1: list[ResultMetadata] | None = None
|
|
110
|
+
self.sqlstate = sqlstate
|
|
111
|
+
self.sfqid = sfqid
|
|
112
|
+
# Client-generated requestId of the query submission, populated on
|
|
113
|
+
# both the success and error paths.
|
|
114
|
+
self.request_id = request_id
|
|
115
|
+
self.query = query
|
|
116
|
+
self.stats = stats if stats is not None else QueryResultStats()
|
|
117
|
+
self.rowcount = rowcount
|
|
118
|
+
self.is_file_transfer = is_file_transfer
|
|
119
|
+
self.query_result_format = query_result_format
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def description(self) -> list[ResultMetadata] | None:
|
|
123
|
+
"""PEP 249 view of the column metadata, downcast from ``description_v2``.
|
|
124
|
+
|
|
125
|
+
``description_v2`` is the single parse of the proto column list; this
|
|
126
|
+
view is built on first read and cached.
|
|
127
|
+
"""
|
|
128
|
+
if self._description_v1 is None and self.description_v2 is not None:
|
|
129
|
+
self._description_v1 = [col._to_result_metadata_v1() for col in self.description_v2]
|
|
130
|
+
return self._description_v1
|
|
131
|
+
|
|
132
|
+
def reset(self, closing: bool = False) -> None:
|
|
133
|
+
"""Optionally clear the rowcount.
|
|
134
|
+
|
|
135
|
+
Only rowcount is reset — description, sqlstate, sfqid, request_id, query,
|
|
136
|
+
and stats are left intact for backward compatibility (callers may read them after close/reset).
|
|
137
|
+
They are overwritten wholesale when the cursor's _query_result is replaced on the next execute().
|
|
138
|
+
"""
|
|
139
|
+
if not closing:
|
|
140
|
+
self.rowcount = None
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def from_prepare_result(result: PrepareResult | None) -> QueryResult:
|
|
144
|
+
stream_ptr = get_stream_ptr(result)
|
|
145
|
+
release_arrow_stream(stream_ptr)
|
|
146
|
+
|
|
147
|
+
description = ResultMetadataV2.create_description(result)
|
|
148
|
+
return QueryResult(
|
|
149
|
+
description_v2=description,
|
|
150
|
+
sqlstate=extract_sqlstate(result),
|
|
151
|
+
sfqid=(result.query_id if result.query_id else None) if result else None,
|
|
152
|
+
request_id=(result.request_id if result.request_id else None) if result else None,
|
|
153
|
+
query=(result.query if result.query else None) if result else None,
|
|
154
|
+
rowcount=0 if description else None,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def from_programming_error(exc: ProgrammingError) -> QueryResult:
|
|
159
|
+
return QueryResult(
|
|
160
|
+
sqlstate=exc.sqlstate or None,
|
|
161
|
+
sfqid=exc.sfqid or None,
|
|
162
|
+
request_id=exc.request_id or None,
|
|
163
|
+
query=exc.query or None,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def from_result_set_response(
|
|
168
|
+
response: ResultSetResponse,
|
|
169
|
+
query: str | None = None,
|
|
170
|
+
request_id: str | None = None,
|
|
171
|
+
) -> QueryResult:
|
|
172
|
+
"""Create QueryResult from a ResultSetResponse (metadata only).
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
response: ResultSetResponse containing descriptor metadata.
|
|
176
|
+
query: Optional query text (not available in proto, must be passed separately).
|
|
177
|
+
request_id: Optional submission requestId (not carried on ResultSetResponse,
|
|
178
|
+
must be passed separately by callers that submitted the query).
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
QueryResult instance with metadata populated.
|
|
182
|
+
"""
|
|
183
|
+
descriptor = response.result_descriptor
|
|
184
|
+
|
|
185
|
+
return QueryResult(
|
|
186
|
+
description_v2=ResultMetadataV2.create_description(descriptor),
|
|
187
|
+
sqlstate=extract_sqlstate(descriptor),
|
|
188
|
+
sfqid=descriptor.query_id if descriptor.query_id else None,
|
|
189
|
+
request_id=request_id,
|
|
190
|
+
query=query,
|
|
191
|
+
rowcount=extract_rowcount(descriptor),
|
|
192
|
+
is_file_transfer=(
|
|
193
|
+
descriptor.statement_type_id in (0x7101, 0x7102) if descriptor.HasField("statement_type_id") else False
|
|
194
|
+
),
|
|
195
|
+
stats=(
|
|
196
|
+
QueryResultStats.from_query_stats(descriptor.stats)
|
|
197
|
+
if descriptor.HasField("stats")
|
|
198
|
+
else QueryResultStats()
|
|
199
|
+
),
|
|
200
|
+
# Legacy reports a format-less response as JSON.
|
|
201
|
+
query_result_format=descriptor.query_result_format or "json",
|
|
202
|
+
)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Waiter that polls query status until a query completes or errors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from ...constants import QueryStatus
|
|
11
|
+
from ...errors import DatabaseError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ...aio.connection import Connection as AsyncConnection
|
|
16
|
+
from ...connection import Connection
|
|
17
|
+
from ..connection import ConnectionMixin
|
|
18
|
+
|
|
19
|
+
_RETRY_PATTERN = [1, 1, 2, 3, 4, 8, 10]
|
|
20
|
+
_NO_DATA_MAX_RETRY = 24
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _no_data_error(sfqid: str) -> DatabaseError:
|
|
24
|
+
return DatabaseError(
|
|
25
|
+
f"Cannot retrieve data on the status of this query. No information returned from server for query '{sfqid}'"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _advance_no_data_counter(status: QueryStatus, no_data_counter: int, sfqid: str) -> int:
|
|
30
|
+
if status != QueryStatus.NO_DATA:
|
|
31
|
+
return no_data_counter
|
|
32
|
+
no_data_counter += 1
|
|
33
|
+
if no_data_counter > _NO_DATA_MAX_RETRY:
|
|
34
|
+
raise _no_data_error(sfqid)
|
|
35
|
+
return no_data_counter
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _poll_step(
|
|
39
|
+
connection: ConnectionMixin,
|
|
40
|
+
sfqid: str,
|
|
41
|
+
status: QueryStatus,
|
|
42
|
+
no_data_counter: int,
|
|
43
|
+
retry_idx: int,
|
|
44
|
+
) -> tuple[float | None, int, int]:
|
|
45
|
+
"""Advance one poll iteration.
|
|
46
|
+
|
|
47
|
+
Returns ``(sleep_seconds, no_data_counter, retry_idx)``. ``sleep_seconds``
|
|
48
|
+
is ``None`` when polling should stop.
|
|
49
|
+
"""
|
|
50
|
+
if not connection.is_still_running(status):
|
|
51
|
+
return None, no_data_counter, retry_idx
|
|
52
|
+
no_data_counter = _advance_no_data_counter(status, no_data_counter, sfqid)
|
|
53
|
+
delay = 0.5 * _RETRY_PATTERN[retry_idx]
|
|
54
|
+
if retry_idx < len(_RETRY_PATTERN) - 1:
|
|
55
|
+
retry_idx += 1
|
|
56
|
+
return delay, no_data_counter, retry_idx
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class QueryResultWaiter:
|
|
60
|
+
"""Sync waiter that polls query status with capped exponential backoff."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, connection: Connection, sfqid: str) -> None:
|
|
63
|
+
self._connection = connection
|
|
64
|
+
self._sfqid = sfqid
|
|
65
|
+
|
|
66
|
+
def wait(self) -> None:
|
|
67
|
+
"""Block until the query completes. Raise on terminal error status."""
|
|
68
|
+
no_data_counter = 0
|
|
69
|
+
retry_idx = 0
|
|
70
|
+
while True:
|
|
71
|
+
status = self._connection.get_query_status_throw_if_error(self._sfqid)
|
|
72
|
+
delay, no_data_counter, retry_idx = _poll_step(
|
|
73
|
+
self._connection, self._sfqid, status, no_data_counter, retry_idx
|
|
74
|
+
)
|
|
75
|
+
if delay is None:
|
|
76
|
+
return
|
|
77
|
+
time.sleep(delay)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AsyncQueryResultWaiter:
|
|
81
|
+
"""Async waiter that polls query status with capped exponential backoff."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, connection: AsyncConnection, sfqid: str) -> None:
|
|
84
|
+
self._connection = connection
|
|
85
|
+
self._sfqid = sfqid
|
|
86
|
+
|
|
87
|
+
async def wait(self) -> None:
|
|
88
|
+
"""Await query completion. Raise on terminal error status."""
|
|
89
|
+
no_data_counter = 0
|
|
90
|
+
retry_idx = 0
|
|
91
|
+
while True:
|
|
92
|
+
status = await self._connection.get_query_status_throw_if_error(self._sfqid)
|
|
93
|
+
delay, no_data_counter, retry_idx = _poll_step(
|
|
94
|
+
self._connection, self._sfqid, status, no_data_counter, retry_idx
|
|
95
|
+
)
|
|
96
|
+
if delay is None:
|
|
97
|
+
return
|
|
98
|
+
await asyncio.sleep(delay)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Result metadata and query statistics types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, NamedTuple
|
|
6
|
+
|
|
7
|
+
from ..protobuf_gen.database_driver_v1_pb2 import (
|
|
8
|
+
PrepareResult,
|
|
9
|
+
QueryStats,
|
|
10
|
+
ResultSetDescriptor,
|
|
11
|
+
)
|
|
12
|
+
from ..type_codes import get_type_code
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_TEXT_TYPES = ("TEXT", "VARCHAR", "CHAR", "STRING")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _type_code_name(col: Any) -> str:
|
|
19
|
+
ext = getattr(col, "ext_col_type_name", "")
|
|
20
|
+
if isinstance(ext, str) and ext:
|
|
21
|
+
return ext
|
|
22
|
+
return str(col.type)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _column_display_size(col: Any) -> int | None:
|
|
26
|
+
# Char count, text-only — a new-driver-only enhancement (BD#90); the old driver leaves it unset.
|
|
27
|
+
return col.length if col.HasField("length") and col.type.upper() in _TEXT_TYPES else None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _column_internal_size(col: Any) -> int | None:
|
|
31
|
+
# Char count (old-driver semantics), ungated — BINARY/VARBINARY need a real value too.
|
|
32
|
+
return col.length if col.HasField("length") else None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ResultMetadata(NamedTuple):
|
|
36
|
+
"""PEP 249 column description entry.
|
|
37
|
+
|
|
38
|
+
Each item in ``Cursor.description`` is a ``ResultMetadata`` instance.
|
|
39
|
+
Being a :class:`~typing.NamedTuple` it is fully tuple-compatible as
|
|
40
|
+
required by the spec, while also providing named attribute access.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
type_code: int
|
|
45
|
+
display_size: int | None
|
|
46
|
+
internal_size: int | None
|
|
47
|
+
precision: int | None
|
|
48
|
+
scale: int | None
|
|
49
|
+
is_nullable: bool | None
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_column(cls, col: Any) -> ResultMetadata:
|
|
53
|
+
"""Create a ``ResultMetadata`` from a protobuf ``ColumnMetadata``."""
|
|
54
|
+
type_code = get_type_code(_type_code_name(col))
|
|
55
|
+
|
|
56
|
+
display_size = _column_display_size(col)
|
|
57
|
+
internal_size = _column_internal_size(col)
|
|
58
|
+
precision = col.precision if col.HasField("precision") else None
|
|
59
|
+
scale = col.scale if col.HasField("scale") else None
|
|
60
|
+
|
|
61
|
+
return cls(
|
|
62
|
+
name=col.name,
|
|
63
|
+
type_code=type_code,
|
|
64
|
+
display_size=display_size,
|
|
65
|
+
internal_size=internal_size,
|
|
66
|
+
precision=precision,
|
|
67
|
+
scale=scale,
|
|
68
|
+
is_nullable=col.nullable,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ResultMetadataV2:
|
|
73
|
+
"""New-format column description carrying ``vector_dimension`` and ``fields``.
|
|
74
|
+
|
|
75
|
+
Matches the legacy ``snowflake-connector-python`` ``ResultMetadataV2``
|
|
76
|
+
interface so Snowpark can read ``.name``, ``.type_code``, ``.is_nullable``,
|
|
77
|
+
``.vector_dimension``, and ``.fields`` without connector-version guards.
|
|
78
|
+
|
|
79
|
+
``vector_dimension`` is populated from the proto ``dimension`` field.
|
|
80
|
+
``fields`` carries nested metadata for structured types: the element type
|
|
81
|
+
for VECTOR and ARRAY, the key and value types for MAP, and one entry per
|
|
82
|
+
attribute for structured OBJECT. It is ``None`` for every other type, and
|
|
83
|
+
also for semi-structured ARRAY/OBJECT/MAP, which the server describes
|
|
84
|
+
without a nested field list.
|
|
85
|
+
|
|
86
|
+
Note: ``_is_nullable`` is exposed both as the ``is_nullable`` public property
|
|
87
|
+
and as the ``_is_nullable`` private attribute, because Snowpark accesses the
|
|
88
|
+
private attribute directly on nested ARRAY/MAP element metadata objects
|
|
89
|
+
(``obj._is_nullable``). Both must exist by their exact names.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
__slots__ = (
|
|
93
|
+
"_name",
|
|
94
|
+
"_type_code",
|
|
95
|
+
"_is_nullable",
|
|
96
|
+
"_display_size",
|
|
97
|
+
"_internal_size",
|
|
98
|
+
"_precision",
|
|
99
|
+
"_scale",
|
|
100
|
+
"_vector_dimension",
|
|
101
|
+
"_fields",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
name: str | None,
|
|
107
|
+
type_code: int,
|
|
108
|
+
is_nullable: bool,
|
|
109
|
+
display_size: int | None = None,
|
|
110
|
+
internal_size: int | None = None,
|
|
111
|
+
precision: int | None = None,
|
|
112
|
+
scale: int | None = None,
|
|
113
|
+
vector_dimension: int | None = None,
|
|
114
|
+
fields: list[ResultMetadataV2] | None = None,
|
|
115
|
+
) -> None:
|
|
116
|
+
self._name = name
|
|
117
|
+
self._type_code = type_code
|
|
118
|
+
self._is_nullable = is_nullable
|
|
119
|
+
self._display_size = display_size
|
|
120
|
+
self._internal_size = internal_size
|
|
121
|
+
self._precision = precision
|
|
122
|
+
self._scale = scale
|
|
123
|
+
self._vector_dimension = vector_dimension
|
|
124
|
+
self._fields = fields
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def name(self) -> str | None:
|
|
128
|
+
return self._name
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def type_code(self) -> int:
|
|
132
|
+
return self._type_code
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def is_nullable(self) -> bool:
|
|
136
|
+
return self._is_nullable
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def display_size(self) -> int | None:
|
|
140
|
+
return self._display_size
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def internal_size(self) -> int | None:
|
|
144
|
+
return self._internal_size
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def precision(self) -> int | None:
|
|
148
|
+
return self._precision
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def scale(self) -> int | None:
|
|
152
|
+
return self._scale
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def vector_dimension(self) -> int | None:
|
|
156
|
+
return self._vector_dimension
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def fields(self) -> list[ResultMetadataV2] | None:
|
|
160
|
+
return self._fields
|
|
161
|
+
|
|
162
|
+
def __eq__(self, other: object) -> bool:
|
|
163
|
+
if not isinstance(other, ResultMetadataV2):
|
|
164
|
+
return NotImplemented
|
|
165
|
+
return (
|
|
166
|
+
self._name == other._name
|
|
167
|
+
and self._type_code == other._type_code
|
|
168
|
+
and self._is_nullable == other._is_nullable
|
|
169
|
+
and self._display_size == other._display_size
|
|
170
|
+
and self._internal_size == other._internal_size
|
|
171
|
+
and self._precision == other._precision
|
|
172
|
+
and self._scale == other._scale
|
|
173
|
+
and self._vector_dimension == other._vector_dimension
|
|
174
|
+
and self._fields == other._fields
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def _to_result_metadata_v1(self) -> ResultMetadata:
|
|
178
|
+
"""Downcast to a PEP 249-compatible ``ResultMetadata`` NamedTuple."""
|
|
179
|
+
return ResultMetadata(
|
|
180
|
+
name=self._name, # type: ignore[arg-type] # sub-fields carry name=None (legacy parity)
|
|
181
|
+
type_code=self._type_code,
|
|
182
|
+
display_size=self._display_size,
|
|
183
|
+
internal_size=self._internal_size,
|
|
184
|
+
precision=self._precision,
|
|
185
|
+
scale=self._scale,
|
|
186
|
+
is_nullable=self._is_nullable,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def __repr__(self) -> str:
|
|
190
|
+
return (
|
|
191
|
+
f"ResultMetadataV2(name={self._name!r}, type_code={self._type_code!r}, "
|
|
192
|
+
f"is_nullable={self._is_nullable!r}, vector_dimension={self._vector_dimension!r}, "
|
|
193
|
+
f"fields={self._fields!r})"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def from_column(cls, col: Any, *, nested: bool = False) -> ResultMetadataV2:
|
|
198
|
+
"""Build from a proto ``ColumnMetadata`` message.
|
|
199
|
+
|
|
200
|
+
``nested`` marks a sub-field of a structured type. Those carry a name
|
|
201
|
+
only for the attributes of a structured OBJECT; the proto renders an
|
|
202
|
+
absent name as the empty string, which maps back to ``None`` here for
|
|
203
|
+
parity with the old driver.
|
|
204
|
+
"""
|
|
205
|
+
type_code = get_type_code(_type_code_name(col))
|
|
206
|
+
display_size = _column_display_size(col)
|
|
207
|
+
internal_size = _column_internal_size(col)
|
|
208
|
+
precision = col.precision if col.HasField("precision") else None
|
|
209
|
+
scale = col.scale if col.HasField("scale") else None
|
|
210
|
+
vector_dimension = col.dimension if col.HasField("dimension") else None
|
|
211
|
+
return cls(
|
|
212
|
+
name=(col.name or None) if nested else col.name,
|
|
213
|
+
type_code=type_code,
|
|
214
|
+
is_nullable=col.nullable,
|
|
215
|
+
display_size=display_size,
|
|
216
|
+
internal_size=internal_size,
|
|
217
|
+
precision=precision,
|
|
218
|
+
scale=scale,
|
|
219
|
+
vector_dimension=vector_dimension,
|
|
220
|
+
# For MAP, `col.fields` carries exactly two entries, key type then value
|
|
221
|
+
# type, in that order. Snowpark's structured-type inference reads them
|
|
222
|
+
# positionally as fields[0]/fields[1] today, so this recursion must
|
|
223
|
+
# preserve wire order rather than resort or drop an entry.
|
|
224
|
+
fields=[cls.from_column(f, nested=True) for f in col.fields] if col.fields else None,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def create_description(cls, result: PrepareResult | ResultSetDescriptor | None) -> list[ResultMetadataV2] | None:
|
|
229
|
+
"""Build a V2 description list from a prepare/describe result."""
|
|
230
|
+
if result and result.columns:
|
|
231
|
+
return [cls.from_column(col) for col in result.columns]
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class QueryResultStats(NamedTuple):
|
|
236
|
+
"""DML operation statistics returned by Snowflake.
|
|
237
|
+
|
|
238
|
+
Exposes per-operation row counts for INSERT, UPDATE, DELETE,
|
|
239
|
+
and the number of duplicate rows skipped during DML.
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
num_rows_inserted: int | None = None
|
|
243
|
+
num_rows_deleted: int | None = None
|
|
244
|
+
num_rows_updated: int | None = None
|
|
245
|
+
num_dml_duplicates: int | None = None
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def from_query_stats(cls, s: QueryStats) -> QueryResultStats:
|
|
249
|
+
"""Create a ``QueryResultStats`` from a protobuf ``QueryStats``."""
|
|
250
|
+
return cls(
|
|
251
|
+
num_rows_inserted=s.num_rows_inserted if s.HasField("num_rows_inserted") else None,
|
|
252
|
+
num_rows_deleted=s.num_rows_deleted if s.HasField("num_rows_deleted") else None,
|
|
253
|
+
num_rows_updated=s.num_rows_updated if s.HasField("num_rows_updated") else None,
|
|
254
|
+
num_dml_duplicates=s.num_dml_duplicates if s.HasField("num_dml_duplicates") else None,
|
|
255
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ...errors import ProgrammingError
|
|
4
|
+
from ..api_client.client_api import core_driver
|
|
5
|
+
from ..errorcode import ER_NO_DATA_FOUND
|
|
6
|
+
from ..logging import get_logger
|
|
7
|
+
from ..protobuf_gen.database_driver_v1_pb2 import ResultSetHandle
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ResultSetWrapperBase:
|
|
14
|
+
"""Owns a ``ResultSetHandle`` and guards against misuse.
|
|
15
|
+
|
|
16
|
+
Lifecycle: ``replace()`` releases the previous handle before adopting a
|
|
17
|
+
new one; ``release()`` frees the current handle. ``__del__`` acts as a
|
|
18
|
+
safety net for handles that were never explicitly released.
|
|
19
|
+
|
|
20
|
+
The core layer supports repeated ``result_set_get_stream`` calls — each
|
|
21
|
+
invocation builds a fresh Arrow stream from the stored ``RowsetData``.
|
|
22
|
+
|
|
23
|
+
Release/replace are always synchronous (pure FFI handle deletion, no network I/O).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__slots__ = ("_handle",)
|
|
27
|
+
|
|
28
|
+
def __init__(self, handle: ResultSetHandle | None = None) -> None:
|
|
29
|
+
self._handle: ResultSetHandle | None = handle
|
|
30
|
+
|
|
31
|
+
def __del__(self) -> None:
|
|
32
|
+
self._do_release()
|
|
33
|
+
|
|
34
|
+
def replace(self, new_handle: ResultSetHandle | None) -> None:
|
|
35
|
+
"""Release the current handle (if any) and adopt *new_handle*."""
|
|
36
|
+
self._do_release()
|
|
37
|
+
self._handle = new_handle
|
|
38
|
+
|
|
39
|
+
def release(self) -> None:
|
|
40
|
+
"""Explicitly release the handle."""
|
|
41
|
+
self._do_release()
|
|
42
|
+
|
|
43
|
+
def _do_release(self) -> None:
|
|
44
|
+
handle = self._take_handle()
|
|
45
|
+
if handle is None:
|
|
46
|
+
return
|
|
47
|
+
try:
|
|
48
|
+
core_driver.result_set_release(result_set_handle=handle)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.warning("Failed to release ResultSet handle: %s", type(e).__name__)
|
|
51
|
+
logger.debug("Failed to release ResultSet handle", exc_info=True)
|
|
52
|
+
|
|
53
|
+
def _take_handle(self) -> ResultSetHandle | None:
|
|
54
|
+
handle = self._handle
|
|
55
|
+
if handle is None:
|
|
56
|
+
return None
|
|
57
|
+
self._handle = None
|
|
58
|
+
return handle
|
|
59
|
+
|
|
60
|
+
def _require_handle(self) -> ResultSetHandle:
|
|
61
|
+
if self._handle is None:
|
|
62
|
+
raise ProgrammingError(
|
|
63
|
+
msg="No results available (not produced by this query)",
|
|
64
|
+
errno=ER_NO_DATA_FOUND,
|
|
65
|
+
)
|
|
66
|
+
return self._handle
|