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,1169 @@
|
|
|
1
|
+
"""Base cursor class for database operations (PEP 249)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
9
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Self, cast, overload
|
|
10
|
+
|
|
11
|
+
from .._common.extras import pandas, pyarrow, requires_dependency
|
|
12
|
+
from .._internal.api_client.client_api import CHUNK_SIZE, core_driver
|
|
13
|
+
from .._internal.arrow_context import ArrowConverterContext
|
|
14
|
+
from .._internal.arrow_stream_utils import (
|
|
15
|
+
collect_arrow_table,
|
|
16
|
+
create_row_iterator,
|
|
17
|
+
create_table_iterator,
|
|
18
|
+
)
|
|
19
|
+
from .._internal.binding_converters import ParamStyle
|
|
20
|
+
from .._internal.cursor import (
|
|
21
|
+
Args,
|
|
22
|
+
CursorBaseMixin,
|
|
23
|
+
DictRow,
|
|
24
|
+
MultiStatementQueryResultState,
|
|
25
|
+
QueryResult,
|
|
26
|
+
QueryResultWaiter,
|
|
27
|
+
ResultMetadata,
|
|
28
|
+
ResultMetadataV2,
|
|
29
|
+
Row,
|
|
30
|
+
)
|
|
31
|
+
from .._internal.cursor.decorators import (
|
|
32
|
+
requires_open,
|
|
33
|
+
requires_open_cursor_not_connection,
|
|
34
|
+
with_prefetch_hook,
|
|
35
|
+
)
|
|
36
|
+
from .._internal.decorators import (
|
|
37
|
+
api_telemetry,
|
|
38
|
+
backward_compatibility,
|
|
39
|
+
pep249,
|
|
40
|
+
snowpark_compat,
|
|
41
|
+
)
|
|
42
|
+
from .._internal.errorcode import ER_CURSOR_IS_CLOSED, ER_INVALID_VALUE
|
|
43
|
+
from .._internal.errorhandler import simplified_error_handling
|
|
44
|
+
from .._internal.logging import get_logger
|
|
45
|
+
from .._internal.protobuf_gen.database_driver_v1_pb2 import (
|
|
46
|
+
ABORT_QUERY_OUTCOME_ABORTED,
|
|
47
|
+
ExecuteQueryResponse,
|
|
48
|
+
MultiStatementResult,
|
|
49
|
+
PrepareResult,
|
|
50
|
+
QueryBindings,
|
|
51
|
+
ResultSetResponse,
|
|
52
|
+
StatementHandle,
|
|
53
|
+
)
|
|
54
|
+
from .._internal.statement_utils import statement
|
|
55
|
+
from .._internal.utils import _coerce_executemany_params, _resolve_alias
|
|
56
|
+
from ..errors import InterfaceError, OperationalError, ProgrammingError
|
|
57
|
+
from ..result_batch import ResultBatch
|
|
58
|
+
from ._chunked_download_reader import _ChunkedDownloadReader
|
|
59
|
+
from ._result_set_wrapper import _ResultSetWrapper
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if TYPE_CHECKING:
|
|
63
|
+
from pandas import DataFrame
|
|
64
|
+
from pyarrow import Table
|
|
65
|
+
|
|
66
|
+
from .._internal.arrow import ArrowRowIterator
|
|
67
|
+
from ..connection import Connection
|
|
68
|
+
|
|
69
|
+
logger = get_logger(__name__)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class SnowflakeCursorBase(CursorBaseMixin, abc.ABC):
|
|
73
|
+
"""
|
|
74
|
+
Base cursor class for database operations (PEP 249).
|
|
75
|
+
|
|
76
|
+
This is the abstract base for all cursor types, equivalent to
|
|
77
|
+
``SnowflakeCursorBase`` in the old connector. Concrete subclasses
|
|
78
|
+
must override :pyattr:`_use_dict_result` and :pymeth:`fetchone`.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
_connection: Connection
|
|
82
|
+
_iterator: ArrowRowIterator | None
|
|
83
|
+
|
|
84
|
+
def __init__(self, connection: Connection) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Initialize a new cursor object.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
connection: Connection object that created this cursor
|
|
90
|
+
"""
|
|
91
|
+
self._connection = connection
|
|
92
|
+
super().__init__()
|
|
93
|
+
self._iterator = None
|
|
94
|
+
|
|
95
|
+
# -- ResultSet guard (set by _execute; released on reset unless reuse_results) --
|
|
96
|
+
self._result_set = _ResultSetWrapper()
|
|
97
|
+
# Deferred result loading (set by get_results_from_sfqid, invoked on first fetch)
|
|
98
|
+
self._prefetch_hook: Callable[[], None] | None = None
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
# PEP 249 attributes
|
|
102
|
+
# ------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
@pep249
|
|
106
|
+
@api_telemetry
|
|
107
|
+
def connection(self) -> Connection:
|
|
108
|
+
"""The :class:`Connection` object that created this cursor."""
|
|
109
|
+
return self._connection
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------------------------------
|
|
112
|
+
# Result format control
|
|
113
|
+
# ------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
@abc.abstractmethod
|
|
117
|
+
def _use_dict_result(self) -> bool:
|
|
118
|
+
"""Whether fetch methods return dicts instead of tuples."""
|
|
119
|
+
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
# Error handling
|
|
122
|
+
# ------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def _errorhandler_cursor(self) -> SnowflakeCursorBase:
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def _errorhandler_connection(self) -> Connection:
|
|
130
|
+
return self._connection
|
|
131
|
+
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
# Execution
|
|
134
|
+
# ------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
@overload
|
|
137
|
+
def callproc(self, procname: str) -> tuple: ...
|
|
138
|
+
|
|
139
|
+
@overload
|
|
140
|
+
def callproc(self, procname: str, args: Args) -> Args: ...
|
|
141
|
+
|
|
142
|
+
@pep249
|
|
143
|
+
@api_telemetry
|
|
144
|
+
@requires_open
|
|
145
|
+
def callproc(self, procname: str, args: Any = None) -> Any:
|
|
146
|
+
"""Call a stored procedure.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
procname: The stored procedure to be called.
|
|
150
|
+
args: Parameters to be passed into the stored procedure.
|
|
151
|
+
``None`` is treated as no arguments.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
The input parameters.
|
|
155
|
+
"""
|
|
156
|
+
command, args = self._prepare_call_proc_statement(procname, args)
|
|
157
|
+
self.execute(command, args)
|
|
158
|
+
return args
|
|
159
|
+
|
|
160
|
+
@pep249
|
|
161
|
+
@api_telemetry
|
|
162
|
+
@requires_open
|
|
163
|
+
def execute(
|
|
164
|
+
self,
|
|
165
|
+
operation: str,
|
|
166
|
+
parameters: Sequence[Any] | dict[str, Any] | None = None,
|
|
167
|
+
_is_put_get: bool | None = None,
|
|
168
|
+
num_statements: int | None = None,
|
|
169
|
+
_skip_upload_on_content_match: bool = False,
|
|
170
|
+
*,
|
|
171
|
+
params: Sequence[Any] | dict[str, Any] | None = None,
|
|
172
|
+
_force_qmark_paramstyle: bool = False,
|
|
173
|
+
_statement_params: dict[str, Any] | None = None,
|
|
174
|
+
file_stream: BinaryIO | None = None,
|
|
175
|
+
**kwargs: Any,
|
|
176
|
+
) -> Self:
|
|
177
|
+
"""
|
|
178
|
+
Execute a database operation (query or command).
|
|
179
|
+
Resets the cursor state before the execution.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
operation (str): SQL statement to execute
|
|
183
|
+
parameters (sequence or dict): Parameters for the operation.
|
|
184
|
+
For qmark/numeric paramstyle: sequence of values
|
|
185
|
+
For pyformat/format paramstyle: sequence (%s) or dict (%(name)s)
|
|
186
|
+
For format paramstyle: sequence (%s)
|
|
187
|
+
num_statements (int, optional): Number of statements in a multistatement query.
|
|
188
|
+
_skip_upload_on_content_match (bool, optional): On PUT, skip
|
|
189
|
+
re-upload when the remote stored digest metadata (S3
|
|
190
|
+
``x-amz-meta-sfc-digest`` / Azure ``x-ms-meta-sfcdigest`` / GCS
|
|
191
|
+
``x-goog-meta-sfc-digest``) matches the locally-computed
|
|
192
|
+
SHA-256. Opt-in optimization for racing concurrent uploaders;
|
|
193
|
+
only meaningful with ``OVERWRITE=TRUE``. Underscore-prefixed
|
|
194
|
+
for parity with the legacy Python-connector kwarg name.
|
|
195
|
+
params: Legacy alias for ``parameters`` (kwarg-only). Cannot be
|
|
196
|
+
supplied together with ``parameters``.
|
|
197
|
+
_force_qmark_paramstyle: If True, bind as qmark (``?``) even when
|
|
198
|
+
the connection's paramstyle is pyformat/format. Used by
|
|
199
|
+
callers that emit ``?`` placeholders unconditionally.
|
|
200
|
+
_statement_params: Extra per-statement parameters (e.g. ``QUERY_TAG``)
|
|
201
|
+
sent to Snowflake with this query only. Never persisted on the
|
|
202
|
+
cursor; forwarded as query-request parameters, so they tag only
|
|
203
|
+
this query without mutating session state.
|
|
204
|
+
file_stream: When set, ``operation`` must be a PUT statement with
|
|
205
|
+
a ``file://`` path (only the basename is used as the
|
|
206
|
+
destination filename). Its contents come from this stream
|
|
207
|
+
instead of disk, forwarded to the core driver in chunks so
|
|
208
|
+
the whole payload is never buffered in the wrapper. Non-PUT
|
|
209
|
+
``operation`` raises ProgrammingError.
|
|
210
|
+
"""
|
|
211
|
+
parameters = _resolve_alias(parameters, params, "parameters", "params") # type: ignore[assignment]
|
|
212
|
+
|
|
213
|
+
# Per-call params: this execute() only, never persisted on the cursor.
|
|
214
|
+
statement_parameters = self._collect_statement_params(
|
|
215
|
+
skip_upload_on_content_match=_skip_upload_on_content_match,
|
|
216
|
+
num_statements=num_statements,
|
|
217
|
+
statement_params=_statement_params,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
self.reset()
|
|
221
|
+
return self._execute(
|
|
222
|
+
operation,
|
|
223
|
+
parameters,
|
|
224
|
+
_is_put_get,
|
|
225
|
+
statement_parameters=statement_parameters,
|
|
226
|
+
_force_qmark_paramstyle=_force_qmark_paramstyle,
|
|
227
|
+
file_stream=file_stream,
|
|
228
|
+
**kwargs,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def _execute(
|
|
232
|
+
self,
|
|
233
|
+
operation: str,
|
|
234
|
+
parameters: Sequence[Any] | dict[str, Any] | None = None,
|
|
235
|
+
_is_put_get: bool | None = None,
|
|
236
|
+
*,
|
|
237
|
+
statement_parameters: dict[str, Any] | None = None,
|
|
238
|
+
_force_qmark_paramstyle: bool = False,
|
|
239
|
+
file_stream: BinaryIO | None = None,
|
|
240
|
+
**kwargs: Any,
|
|
241
|
+
) -> Self:
|
|
242
|
+
"""Execute query logic."""
|
|
243
|
+
operation = operation.strip(" \t\n\r") if operation else ""
|
|
244
|
+
|
|
245
|
+
if file_stream is not None:
|
|
246
|
+
self._execute_upload_stream(operation, file_stream)
|
|
247
|
+
self._rownumber = -1
|
|
248
|
+
return self
|
|
249
|
+
|
|
250
|
+
if not operation:
|
|
251
|
+
logger.warning("execute: no query is given to execute")
|
|
252
|
+
return self
|
|
253
|
+
|
|
254
|
+
if logger.is_enabled_for(logging.INFO) and self._connection.config.log_query_text:
|
|
255
|
+
logger.info("query: [%s]", self._format_query_for_log(operation))
|
|
256
|
+
|
|
257
|
+
query, binding_params = self._prepare_query(
|
|
258
|
+
operation, parameters, _force_qmark_paramstyle=_force_qmark_paramstyle
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
with statement(self.connection.conn_handle, query) as stmt_handle: # type: ignore[arg-type]
|
|
262
|
+
self._apply_statement_parameters(stmt_handle, statement_parameters)
|
|
263
|
+
|
|
264
|
+
bindings = self._build_query_bindings(binding_params, query) if binding_params is not None else None
|
|
265
|
+
try:
|
|
266
|
+
response = self._execute_query(stmt_handle, bindings)
|
|
267
|
+
except OperationalError as exc:
|
|
268
|
+
if not self._should_retry_with_inline_json(bindings, exc):
|
|
269
|
+
raise
|
|
270
|
+
bindings = self._build_query_bindings(cast(Sequence[Any], binding_params), query, force_json=True)
|
|
271
|
+
response = self._execute_query(stmt_handle, bindings)
|
|
272
|
+
request_id = response.request_id or None
|
|
273
|
+
|
|
274
|
+
if response.HasField("multi"):
|
|
275
|
+
self._handle_multi_statement_response(response.multi, query, request_id)
|
|
276
|
+
else:
|
|
277
|
+
self._apply_result_set(response.single, query, request_id)
|
|
278
|
+
|
|
279
|
+
self._rownumber = -1 # reset the rownumber (rownumber is not reset in reset() for backward compatibility)
|
|
280
|
+
return self
|
|
281
|
+
|
|
282
|
+
def _execute_upload_stream(self, query: str, file_stream: BinaryIO) -> None:
|
|
283
|
+
"""Chunked streaming PUT: feed *file_stream* to the core driver in bounded chunks.
|
|
284
|
+
|
|
285
|
+
Bytes cross the RPC boundary via begin/chunk/finish, bounding wrapper
|
|
286
|
+
memory to one chunk. A failure after begin aborts the session
|
|
287
|
+
server-side before re-raising.
|
|
288
|
+
"""
|
|
289
|
+
# Callers commonly hand over a stream whose position is not at the start.
|
|
290
|
+
file_stream.seek(0)
|
|
291
|
+
upload_handle = core_driver.upload_stream_begin(
|
|
292
|
+
conn_handle=self._connection.conn_handle, # type: ignore[arg-type]
|
|
293
|
+
sql=query,
|
|
294
|
+
).upload_handle
|
|
295
|
+
try:
|
|
296
|
+
while chunk := file_stream.read(CHUNK_SIZE):
|
|
297
|
+
core_driver.upload_stream_chunk(upload_handle, chunk)
|
|
298
|
+
finish_response = core_driver.upload_stream_finish(upload_handle)
|
|
299
|
+
except BaseException:
|
|
300
|
+
try:
|
|
301
|
+
core_driver.upload_stream_abort(upload_handle)
|
|
302
|
+
except Exception:
|
|
303
|
+
logger.debug("upload_stream_abort during cleanup failed; propagating original error", exc_info=True)
|
|
304
|
+
raise
|
|
305
|
+
self._apply_result_set(finish_response, query) # type: ignore[arg-type]
|
|
306
|
+
|
|
307
|
+
@api_telemetry
|
|
308
|
+
@requires_open
|
|
309
|
+
def download_stream(self, stage_location: str, decompress: bool = False) -> BinaryIO:
|
|
310
|
+
"""Return a lazily-read binary stream for a stage file (chunked, bounded memory).
|
|
311
|
+
|
|
312
|
+
Splits *stage_location* into stage name and filename on the last ``/``
|
|
313
|
+
and opens via the Begin RPC. Nothing happens until you read; each read
|
|
314
|
+
pulls one chunk. Close the stream (or use ``with``) to release the
|
|
315
|
+
download session early.
|
|
316
|
+
"""
|
|
317
|
+
logger.info("download_stream: entry")
|
|
318
|
+
try:
|
|
319
|
+
stage_name, sep, source_filename = stage_location.rpartition("/")
|
|
320
|
+
if not sep:
|
|
321
|
+
# No "/": treat the whole string as the stage name, no filename.
|
|
322
|
+
stage_name, source_filename = stage_location, ""
|
|
323
|
+
begin = core_driver.download_stream_begin(
|
|
324
|
+
conn_handle=self._connection.conn_handle, # type: ignore[arg-type]
|
|
325
|
+
stage_name=stage_name,
|
|
326
|
+
source_filename=source_filename,
|
|
327
|
+
decompress=decompress,
|
|
328
|
+
)
|
|
329
|
+
return _ChunkedDownloadReader(begin.download_handle) # type: ignore[return-value]
|
|
330
|
+
finally:
|
|
331
|
+
logger.info("download_stream: exit")
|
|
332
|
+
|
|
333
|
+
@snowpark_compat
|
|
334
|
+
@backward_compatibility
|
|
335
|
+
@requires_open
|
|
336
|
+
def _upload(
|
|
337
|
+
self,
|
|
338
|
+
local_file_name: str,
|
|
339
|
+
stage_location: str,
|
|
340
|
+
options: dict[str, Any],
|
|
341
|
+
_do_reset: bool = True,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""Synthesize and run the PUT that ``session.file.put`` would otherwise issue as SQL."""
|
|
344
|
+
from .._internal.snowpark_backcompat_helpers import file_uri, put_get_options, stage_ref
|
|
345
|
+
|
|
346
|
+
sql = f"PUT {file_uri(local_file_name)} {stage_ref(stage_location)} {put_get_options(options)}"
|
|
347
|
+
self._execute_file_transfer(sql.rstrip(), _do_reset=_do_reset)
|
|
348
|
+
|
|
349
|
+
@snowpark_compat
|
|
350
|
+
@backward_compatibility
|
|
351
|
+
@requires_open
|
|
352
|
+
def _download(
|
|
353
|
+
self,
|
|
354
|
+
stage_location: str,
|
|
355
|
+
target_directory: str,
|
|
356
|
+
options: dict[str, Any],
|
|
357
|
+
_do_reset: bool = True,
|
|
358
|
+
) -> None:
|
|
359
|
+
"""Synthesize and run the GET that ``session.file.get`` would otherwise issue as SQL."""
|
|
360
|
+
from .._internal.snowpark_backcompat_helpers import file_uri, put_get_options, stage_ref
|
|
361
|
+
|
|
362
|
+
sql = f"GET {stage_ref(stage_location)} {file_uri(target_directory)} {put_get_options(options)}"
|
|
363
|
+
self._execute_file_transfer(sql.rstrip(), _do_reset=_do_reset)
|
|
364
|
+
|
|
365
|
+
@snowpark_compat
|
|
366
|
+
@backward_compatibility
|
|
367
|
+
@requires_open
|
|
368
|
+
def _upload_stream(
|
|
369
|
+
self,
|
|
370
|
+
input_stream: BinaryIO,
|
|
371
|
+
stage_location: str,
|
|
372
|
+
options: dict[str, Any],
|
|
373
|
+
_do_reset: bool = True,
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Stream-upload to the file named by *stage_location* (a file, not a directory)."""
|
|
376
|
+
from .._internal.snowpark_backcompat_helpers import put_get_options, unquoted_stage_ref
|
|
377
|
+
|
|
378
|
+
stage_dir, _, filename = unquoted_stage_ref(stage_location).rpartition("/")
|
|
379
|
+
sql = f"PUT file://{filename} {stage_dir} {put_get_options(options)}"
|
|
380
|
+
self._execute_file_transfer(sql.rstrip(), _do_reset=_do_reset, file_stream=input_stream)
|
|
381
|
+
|
|
382
|
+
@snowpark_compat
|
|
383
|
+
@backward_compatibility
|
|
384
|
+
def _download_stream(self, stage_location: str, decompress: bool = False) -> BinaryIO:
|
|
385
|
+
"""Delegate to :meth:`download_stream`, adding the ``@`` prefix Snowpark omits."""
|
|
386
|
+
from .._internal.snowpark_backcompat_helpers import unquoted_stage_ref
|
|
387
|
+
|
|
388
|
+
return self.download_stream(unquoted_stage_ref(stage_location), decompress)
|
|
389
|
+
|
|
390
|
+
def _execute_file_transfer(
|
|
391
|
+
self,
|
|
392
|
+
sql: str,
|
|
393
|
+
*,
|
|
394
|
+
_do_reset: bool,
|
|
395
|
+
file_stream: BinaryIO | None = None,
|
|
396
|
+
) -> None:
|
|
397
|
+
"""Run a synthesized PUT/GET, resetting the cursor first unless told not to."""
|
|
398
|
+
if _do_reset:
|
|
399
|
+
self.execute(sql, file_stream=file_stream)
|
|
400
|
+
else:
|
|
401
|
+
self._execute(sql, file_stream=file_stream)
|
|
402
|
+
|
|
403
|
+
def _execute_query(self, stmt_handle: StatementHandle, bindings: QueryBindings | None) -> ExecuteQueryResponse:
|
|
404
|
+
"""Execute query and return ExecuteQueryResponse (single or multi)."""
|
|
405
|
+
try:
|
|
406
|
+
return core_driver.statement_execute_query(stmt_handle=stmt_handle, bindings=bindings)
|
|
407
|
+
except ProgrammingError as exc:
|
|
408
|
+
self._query_result = QueryResult.from_programming_error(exc)
|
|
409
|
+
raise
|
|
410
|
+
|
|
411
|
+
def _handle_multi_statement_response(
|
|
412
|
+
self, result: MultiStatementResult, query: str | None, request_id: str | None
|
|
413
|
+
) -> None:
|
|
414
|
+
self._multi_statement = MultiStatementQueryResultState.from_result(result, request_id)
|
|
415
|
+
|
|
416
|
+
# Edge case: empty multi-statement result
|
|
417
|
+
if self._multi_statement is None:
|
|
418
|
+
self._query_result = QueryResult(query=query, request_id=request_id)
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
first_qid = self._multi_statement.advance() # always non-None: from_result() guarantees non-empty children
|
|
422
|
+
# already populate cursor with first child query results
|
|
423
|
+
rs_response = self._fetch_result_set_by_query_id(first_qid) # type: ignore[arg-type]
|
|
424
|
+
self._apply_result_set(rs_response, query, request_id)
|
|
425
|
+
|
|
426
|
+
def _apply_result_set(
|
|
427
|
+
self, rs_response: ResultSetResponse, query: str | None, request_id: str | None = None
|
|
428
|
+
) -> None:
|
|
429
|
+
self._result_set.replace(rs_response.result_set_handle)
|
|
430
|
+
self._query_result = QueryResult.from_result_set_response(rs_response, query, request_id)
|
|
431
|
+
|
|
432
|
+
def _fetch_result_set_by_query_id(self, query_id: str) -> ResultSetResponse:
|
|
433
|
+
"""Fetch a ResultSetResponse (handle + descriptor) for a given query ID."""
|
|
434
|
+
try:
|
|
435
|
+
return core_driver.connection_get_result_set(
|
|
436
|
+
conn_handle=self._connection.conn_handle, # type: ignore[arg-type]
|
|
437
|
+
query_id=query_id,
|
|
438
|
+
)
|
|
439
|
+
except Exception as exc:
|
|
440
|
+
if isinstance(exc, ProgrammingError):
|
|
441
|
+
raise
|
|
442
|
+
raise ProgrammingError(msg=f"Failed to fetch result set for query_id={query_id}: {exc}") from exc
|
|
443
|
+
|
|
444
|
+
def _prepare(self, stmt_handle: StatementHandle, bindings: QueryBindings | None) -> PrepareResult | None:
|
|
445
|
+
try:
|
|
446
|
+
return core_driver.statement_prepare(stmt_handle=stmt_handle, bindings=bindings).result
|
|
447
|
+
except ProgrammingError as exc:
|
|
448
|
+
self._query_result = QueryResult.from_programming_error(exc)
|
|
449
|
+
raise
|
|
450
|
+
|
|
451
|
+
def _executemany_per_row(
|
|
452
|
+
self,
|
|
453
|
+
operation: str,
|
|
454
|
+
seq_of_parameters: Sequence[Sequence[Any] | dict[str, Any]],
|
|
455
|
+
_force_qmark_paramstyle: bool = False,
|
|
456
|
+
num_statements: int | None = None,
|
|
457
|
+
) -> None:
|
|
458
|
+
"""Execute each parameter set individually and aggregate rowcount.
|
|
459
|
+
|
|
460
|
+
Used by ``executemany`` whenever array binding does not apply: client-side
|
|
461
|
+
paramstyles, dict parameters, or statements the server reports as not
|
|
462
|
+
array-bindable (e.g. UPDATE, DELETE, MERGE).
|
|
463
|
+
"""
|
|
464
|
+
statement_parameters = None
|
|
465
|
+
if num_statements is not None:
|
|
466
|
+
statement_parameters = self._collect_statement_params(
|
|
467
|
+
skip_upload_on_content_match=False,
|
|
468
|
+
num_statements=num_statements,
|
|
469
|
+
)
|
|
470
|
+
self.reset()
|
|
471
|
+
total_rowcount = 0
|
|
472
|
+
unknown_rowcount = False
|
|
473
|
+
for params in seq_of_parameters:
|
|
474
|
+
self._execute(
|
|
475
|
+
operation,
|
|
476
|
+
params,
|
|
477
|
+
_force_qmark_paramstyle=_force_qmark_paramstyle,
|
|
478
|
+
statement_parameters=statement_parameters,
|
|
479
|
+
) # no reset between calls
|
|
480
|
+
rc = self._query_result.rowcount
|
|
481
|
+
if rc is None or rc == -1:
|
|
482
|
+
unknown_rowcount = True
|
|
483
|
+
elif not unknown_rowcount:
|
|
484
|
+
total_rowcount += rc
|
|
485
|
+
# Per PEP 249, -1 indicates that the number of rows is unknown,
|
|
486
|
+
# but for backward compatibility it's set to None.
|
|
487
|
+
self._query_result.rowcount = None if unknown_rowcount else total_rowcount
|
|
488
|
+
|
|
489
|
+
@pep249
|
|
490
|
+
@api_telemetry
|
|
491
|
+
@requires_open
|
|
492
|
+
def executemany(
|
|
493
|
+
self,
|
|
494
|
+
operation: str,
|
|
495
|
+
seq_of_parameters: Iterable[Sequence[Any] | dict[str, Any]] | None = None,
|
|
496
|
+
num_statements: int | None = None,
|
|
497
|
+
*,
|
|
498
|
+
seqparams: Iterable[Sequence[Any] | dict[str, Any]] | None = None,
|
|
499
|
+
_force_qmark_paramstyle: bool = False,
|
|
500
|
+
) -> Self:
|
|
501
|
+
"""
|
|
502
|
+
Execute a database operation repeatedly for each element in seq_of_parameters.
|
|
503
|
+
|
|
504
|
+
For qmark/numeric paramstyles with sequence parameters, issues a describe
|
|
505
|
+
request first to read the server's ``arrayBindSupported`` flag (the same
|
|
506
|
+
mechanism used by the JDBC and ODBC drivers). If the server confirms array
|
|
507
|
+
binding is supported, all rows are transposed into column-major arrays and
|
|
508
|
+
sent in a single request; when the cell count meets
|
|
509
|
+
``CLIENT_STAGE_ARRAY_BINDING_THRESHOLD`` the bindings are serialized to CSV
|
|
510
|
+
and staged. If the server reports array binding is not supported (e.g. for
|
|
511
|
+
UPDATE, DELETE, MERGE), each parameter set is executed individually with
|
|
512
|
+
scalar JSON binding.
|
|
513
|
+
|
|
514
|
+
For pyformat/format paramstyles and dict parameters, executes each row
|
|
515
|
+
individually with client-side interpolation.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
operation (str): SQL statement (INSERT, UPDATE, DELETE, etc.)
|
|
519
|
+
seq_of_parameters: Iterable of parameter sequences or dicts. Generators
|
|
520
|
+
and other one-shot iterators are materialized so the first row can
|
|
521
|
+
be inspected and array-binding can walk the collection more than once.
|
|
522
|
+
num_statements (int, optional): Number of statements in a
|
|
523
|
+
multistatement query. Forwarded to each ``execute()`` call.
|
|
524
|
+
seqparams: Legacy alias for ``seq_of_parameters`` (kwarg-only).
|
|
525
|
+
Cannot be supplied together with ``seq_of_parameters``.
|
|
526
|
+
_force_qmark_paramstyle: If True, treat as qmark even when the
|
|
527
|
+
connection's paramstyle is pyformat/format.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
The cursor itself (``self``), so callers can read ``.sfqid`` after
|
|
531
|
+
execution — matching the legacy connector.
|
|
532
|
+
|
|
533
|
+
Raises:
|
|
534
|
+
InterfaceError: If parameter sequences have inconsistent lengths.
|
|
535
|
+
ProgrammingError: If both ``seq_of_parameters`` and ``seqparams`` are
|
|
536
|
+
supplied, or if ``seq_of_parameters`` is not iterable.
|
|
537
|
+
"""
|
|
538
|
+
seq_of_parameters = _coerce_executemany_params(
|
|
539
|
+
_resolve_alias(seq_of_parameters, seqparams, "seq_of_parameters", "seqparams")
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
if not seq_of_parameters:
|
|
543
|
+
return self # Empty sequence - no-op per PEP 249
|
|
544
|
+
|
|
545
|
+
paramstyle = ParamStyle.QMARK if _force_qmark_paramstyle else self._connection.paramstyle
|
|
546
|
+
first_params = seq_of_parameters[0]
|
|
547
|
+
|
|
548
|
+
# Execute individually for:
|
|
549
|
+
# - Client-side binding (pyformat/format)
|
|
550
|
+
# - Dict parameters (server-side doesn't support named binding)
|
|
551
|
+
if paramstyle.is_client_side() or isinstance(first_params, dict):
|
|
552
|
+
# A caller-supplied count other than 1 is a multi-statement batch.
|
|
553
|
+
# Rewriting that into one INSERT would drop the later statements.
|
|
554
|
+
if paramstyle.is_client_side() and num_statements in (None, 1):
|
|
555
|
+
# INSERT with client-side binding: rewrite into a single
|
|
556
|
+
# multi-row INSERT to avoid one HTTP request per row.
|
|
557
|
+
rewritten = self._rewrite_multirow_insert(operation, seq_of_parameters)
|
|
558
|
+
if rewritten is not None:
|
|
559
|
+
# Values were already interpolated and escaped by
|
|
560
|
+
# ClientSideBindingConverter.interpolate_query — no further binding needed.
|
|
561
|
+
return self.execute(rewritten, num_statements=num_statements)
|
|
562
|
+
self._executemany_per_row(
|
|
563
|
+
operation,
|
|
564
|
+
seq_of_parameters,
|
|
565
|
+
_force_qmark_paramstyle,
|
|
566
|
+
num_statements=num_statements,
|
|
567
|
+
)
|
|
568
|
+
return self
|
|
569
|
+
|
|
570
|
+
# A caller-supplied count other than 1 is a multi-statement batch.
|
|
571
|
+
# Array binding sends one statement, so each row is executed on its own.
|
|
572
|
+
if num_statements not in (None, 1):
|
|
573
|
+
self._executemany_per_row(
|
|
574
|
+
operation,
|
|
575
|
+
seq_of_parameters,
|
|
576
|
+
_force_qmark_paramstyle,
|
|
577
|
+
num_statements=num_statements,
|
|
578
|
+
)
|
|
579
|
+
return self
|
|
580
|
+
|
|
581
|
+
# Validate row widths and pre-compute column-major layout before the server
|
|
582
|
+
# round-trip so InterfaceError from mismatched lengths surfaces early.
|
|
583
|
+
transposed = self._build_array_binding_params(operation, seq_of_parameters, first_params)
|
|
584
|
+
|
|
585
|
+
# Ask the server whether array binding is supported for this statement.
|
|
586
|
+
# This mirrors how JDBC (describeSqlIfNotTried) and the C/ODBC driver
|
|
587
|
+
# (_snowflake_execute_ex describe-only request) gate batch execution.
|
|
588
|
+
with statement(self.connection.conn_handle, operation) as stmt_handle: # type: ignore[arg-type]
|
|
589
|
+
prepare_result = self._prepare(stmt_handle, None)
|
|
590
|
+
|
|
591
|
+
if prepare_result is None or not prepare_result.array_bind_supported:
|
|
592
|
+
# Per-row fallback: server does not support array binding for this
|
|
593
|
+
# statement type (e.g. UPDATE, DELETE, MERGE).
|
|
594
|
+
self._executemany_per_row(
|
|
595
|
+
operation,
|
|
596
|
+
seq_of_parameters,
|
|
597
|
+
_force_qmark_paramstyle,
|
|
598
|
+
num_statements=num_statements,
|
|
599
|
+
)
|
|
600
|
+
return self
|
|
601
|
+
|
|
602
|
+
# Server confirmed array binding: use pre-computed column-major params.
|
|
603
|
+
return self.execute(
|
|
604
|
+
operation,
|
|
605
|
+
transposed,
|
|
606
|
+
_force_qmark_paramstyle=_force_qmark_paramstyle,
|
|
607
|
+
num_statements=num_statements,
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
@api_telemetry
|
|
611
|
+
@requires_open
|
|
612
|
+
def describe(
|
|
613
|
+
self,
|
|
614
|
+
operation: str,
|
|
615
|
+
parameters: Sequence[Any] | dict[str, Any] | None = None,
|
|
616
|
+
**kwargs: Any,
|
|
617
|
+
) -> list[ResultMetadata] | None:
|
|
618
|
+
"""Obtain the schema of the result without executing the query.
|
|
619
|
+
|
|
620
|
+
This method prepares the query on the server with describeOnly=true to obtain
|
|
621
|
+
column metadata without actually executing the query or returning data rows.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
operation: SQL statement to describe
|
|
625
|
+
parameters: Parameters for the SQL statement (same as execute())
|
|
626
|
+
**kwargs: Additional keyword arguments (for future compatibility)
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
List of ResultMetadata tuples describing result columns, or None if the
|
|
630
|
+
statement produces no result set (e.g., INSERT, UPDATE, DELETE, DDL).
|
|
631
|
+
|
|
632
|
+
Side effects:
|
|
633
|
+
- Updates cursor.description with the column metadata
|
|
634
|
+
"""
|
|
635
|
+
self.reset()
|
|
636
|
+
query, binding_params = self._prepare_query(operation, parameters)
|
|
637
|
+
|
|
638
|
+
prepare_result: PrepareResult | None = None
|
|
639
|
+
with statement(self.connection.conn_handle, query) as stmt_handle: # type: ignore[arg-type]
|
|
640
|
+
bindings = self._build_query_bindings(binding_params, query) if binding_params is not None else None
|
|
641
|
+
prepare_result = self._prepare(stmt_handle, bindings)
|
|
642
|
+
|
|
643
|
+
self._query_result = QueryResult.from_prepare_result(prepare_result)
|
|
644
|
+
|
|
645
|
+
if self._query_result.description:
|
|
646
|
+
self._rownumber = -1
|
|
647
|
+
|
|
648
|
+
return self._query_result.description
|
|
649
|
+
|
|
650
|
+
@snowpark_compat
|
|
651
|
+
@backward_compatibility
|
|
652
|
+
@requires_open
|
|
653
|
+
def _describe_internal(
|
|
654
|
+
self,
|
|
655
|
+
operation: str,
|
|
656
|
+
parameters: Sequence[Any] | dict[str, Any] | None = None,
|
|
657
|
+
*,
|
|
658
|
+
params: Sequence[Any] | dict[str, Any] | None = None,
|
|
659
|
+
**kwargs: Any,
|
|
660
|
+
) -> list[ResultMetadataV2] | None:
|
|
661
|
+
"""Describe-only path returning new-format metadata.
|
|
662
|
+
|
|
663
|
+
Returns ``list[ResultMetadataV2]`` with ``vector_dimension`` and
|
|
664
|
+
``fields`` populated. ``fields`` carries nested metadata for structured
|
|
665
|
+
types and is ``None`` for every other type.
|
|
666
|
+
"""
|
|
667
|
+
parameters = _resolve_alias(parameters, params, "parameters", "params") # type: ignore[assignment]
|
|
668
|
+
self.reset()
|
|
669
|
+
query, binding_params = self._prepare_query(operation, parameters)
|
|
670
|
+
prepare_result: PrepareResult | None = None
|
|
671
|
+
with statement(self.connection.conn_handle, query) as stmt_handle: # type: ignore[arg-type]
|
|
672
|
+
bindings = self._build_query_bindings(binding_params, query) if binding_params is not None else None
|
|
673
|
+
prepare_result = self._prepare(stmt_handle, bindings)
|
|
674
|
+
self._query_result = QueryResult.from_prepare_result(prepare_result)
|
|
675
|
+
if self._query_result.description:
|
|
676
|
+
self._rownumber = -1
|
|
677
|
+
return self._query_result.description_v2
|
|
678
|
+
|
|
679
|
+
@property
|
|
680
|
+
@snowpark_compat
|
|
681
|
+
@with_prefetch_hook
|
|
682
|
+
def _query_result_format(self) -> str | None:
|
|
683
|
+
"""Like the base property, but loads a deferred async result first."""
|
|
684
|
+
return super()._query_result_format
|
|
685
|
+
|
|
686
|
+
# ------------------------------------------------------------------
|
|
687
|
+
# Fetch – shared implementation
|
|
688
|
+
# Intentionally no @api_telemetry on fetch methods - they are hot paths.
|
|
689
|
+
# fetchone / fetchmany use @simplified_error_handling so the success path
|
|
690
|
+
# avoids a ContextVar round-trip per call.
|
|
691
|
+
# ------------------------------------------------------------------
|
|
692
|
+
|
|
693
|
+
def _prepare_fetch(self) -> None:
|
|
694
|
+
# Consciously skip @requires_open_cursor_not_connection and
|
|
695
|
+
# @with_prefetch_hook on fetch hot paths - both checks live here.
|
|
696
|
+
if self._closed:
|
|
697
|
+
raise InterfaceError(msg="Cursor is closed.", errno=ER_CURSOR_IS_CLOSED)
|
|
698
|
+
if self._prefetch_hook is not None:
|
|
699
|
+
self._prefetch_hook()
|
|
700
|
+
|
|
701
|
+
def _fetchone(self) -> Row | DictRow | None:
|
|
702
|
+
"""Fetch the next row internally.
|
|
703
|
+
|
|
704
|
+
Return a dict if ``_use_dict_result`` is True, otherwise a tuple.
|
|
705
|
+
Concrete subclasses expose this through a type-safe ``fetchone``.
|
|
706
|
+
"""
|
|
707
|
+
try:
|
|
708
|
+
self._prepare_fetch()
|
|
709
|
+
if not self._iterator:
|
|
710
|
+
self._iterator = self._create_row_iterator()
|
|
711
|
+
row: Row | DictRow = next(self._iterator)
|
|
712
|
+
self._rownumber += 1
|
|
713
|
+
return row
|
|
714
|
+
except StopIteration:
|
|
715
|
+
return None
|
|
716
|
+
|
|
717
|
+
@pep249
|
|
718
|
+
@simplified_error_handling
|
|
719
|
+
@abc.abstractmethod
|
|
720
|
+
def fetchone(self) -> Row | DictRow | None:
|
|
721
|
+
"""Fetch the next row of a query result set."""
|
|
722
|
+
|
|
723
|
+
@pep249
|
|
724
|
+
@simplified_error_handling
|
|
725
|
+
def fetchmany(self, size: int | None = None) -> list[Any]:
|
|
726
|
+
"""
|
|
727
|
+
Fetch the next set of rows of a query result.
|
|
728
|
+
|
|
729
|
+
Args:
|
|
730
|
+
size (int): Number of rows to fetch (defaults to arraysize)
|
|
731
|
+
|
|
732
|
+
Returns:
|
|
733
|
+
sequence: List of rows
|
|
734
|
+
|
|
735
|
+
Raises:
|
|
736
|
+
ProgrammingError: If the number of rows is not zero or positive number
|
|
737
|
+
"""
|
|
738
|
+
self._prepare_fetch()
|
|
739
|
+
if size is None:
|
|
740
|
+
size = self.arraysize
|
|
741
|
+
|
|
742
|
+
if size < 0:
|
|
743
|
+
raise ProgrammingError(
|
|
744
|
+
msg=f"The number of rows is not zero or positive number: {size}", errno=ER_INVALID_VALUE
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
if size == 0:
|
|
748
|
+
return []
|
|
749
|
+
|
|
750
|
+
if not self._iterator:
|
|
751
|
+
self._iterator = self._create_row_iterator()
|
|
752
|
+
rows = self._iterator.fetch_many(size)
|
|
753
|
+
self._rownumber += len(rows)
|
|
754
|
+
return rows
|
|
755
|
+
|
|
756
|
+
@pep249
|
|
757
|
+
def fetchall(self) -> list[Any]:
|
|
758
|
+
"""
|
|
759
|
+
Fetch all (remaining) rows of a query result.
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
sequence: List of all remaining rows
|
|
763
|
+
"""
|
|
764
|
+
self._prepare_fetch()
|
|
765
|
+
if not self._iterator:
|
|
766
|
+
self._iterator = self._create_row_iterator()
|
|
767
|
+
rows = self._iterator.fetch_all()
|
|
768
|
+
self._rownumber += len(rows)
|
|
769
|
+
return rows
|
|
770
|
+
|
|
771
|
+
# ------------------------------------------------------------------
|
|
772
|
+
# Iterator protocol
|
|
773
|
+
# ------------------------------------------------------------------
|
|
774
|
+
|
|
775
|
+
def _create_row_iterator(self) -> ArrowRowIterator:
|
|
776
|
+
stream_ptr = self._result_set.get_arrow_stream_ptr()
|
|
777
|
+
return create_row_iterator(
|
|
778
|
+
stream_ptr=stream_ptr,
|
|
779
|
+
context=ArrowConverterContext.create(self._connection),
|
|
780
|
+
use_dict_result=self._use_dict_result,
|
|
781
|
+
use_numpy=bool(self._connection.config.numpy),
|
|
782
|
+
use_core_arrow=bool(self._connection.config.use_core_arrow),
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
@pep249
|
|
786
|
+
def __iter__(self) -> Self:
|
|
787
|
+
"""Return the cursor itself as an iterator."""
|
|
788
|
+
return self
|
|
789
|
+
|
|
790
|
+
def __next__(self) -> Row | DictRow:
|
|
791
|
+
"""
|
|
792
|
+
Fetch the next row from the currently executed statement.
|
|
793
|
+
|
|
794
|
+
Returns:
|
|
795
|
+
sequence: Next row
|
|
796
|
+
|
|
797
|
+
Raises:
|
|
798
|
+
StopIteration: When no more rows are available
|
|
799
|
+
"""
|
|
800
|
+
row = self.fetchone()
|
|
801
|
+
if row is None:
|
|
802
|
+
raise StopIteration
|
|
803
|
+
return row
|
|
804
|
+
|
|
805
|
+
@pep249
|
|
806
|
+
def next(self) -> Row | DictRow:
|
|
807
|
+
"""Python 2 compatibility method."""
|
|
808
|
+
return self.__next__()
|
|
809
|
+
|
|
810
|
+
# ------------------------------------------------------------------
|
|
811
|
+
# PEP 249 optional / no-op methods
|
|
812
|
+
# ------------------------------------------------------------------
|
|
813
|
+
|
|
814
|
+
@pep249
|
|
815
|
+
@api_telemetry
|
|
816
|
+
@requires_open
|
|
817
|
+
@with_prefetch_hook
|
|
818
|
+
def nextset(self) -> SnowflakeCursorBase | None:
|
|
819
|
+
"""
|
|
820
|
+
Skip to the next available result set, discarding remaining rows from current set.
|
|
821
|
+
|
|
822
|
+
This method is used for multi-statement queries where a single execute() produces
|
|
823
|
+
multiple result sets. Call nextset() to advance to the next query's results.
|
|
824
|
+
|
|
825
|
+
Loads a deferred async result first.
|
|
826
|
+
|
|
827
|
+
Returns:
|
|
828
|
+
SnowflakeCursorBase: Self if next set is available.
|
|
829
|
+
None: If no more result sets are available.
|
|
830
|
+
|
|
831
|
+
Raises:
|
|
832
|
+
InterfaceError: If cursor is closed.
|
|
833
|
+
|
|
834
|
+
Example:
|
|
835
|
+
cursor.set_statement_parameter("MULTI_STATEMENT_COUNT", 3)
|
|
836
|
+
cursor.execute("SELECT 1; SELECT 2; SELECT 3")
|
|
837
|
+
print(cursor.fetchone()) # (1,)
|
|
838
|
+
cursor.nextset()
|
|
839
|
+
print(cursor.fetchone()) # (2,)
|
|
840
|
+
cursor.nextset()
|
|
841
|
+
print(cursor.fetchone()) # (3,)
|
|
842
|
+
result = cursor.nextset() # None - no more results
|
|
843
|
+
"""
|
|
844
|
+
if self._multi_statement is None:
|
|
845
|
+
return None
|
|
846
|
+
|
|
847
|
+
query_id = self._multi_statement.advance()
|
|
848
|
+
if query_id is None:
|
|
849
|
+
return None
|
|
850
|
+
|
|
851
|
+
# Detach multi-statement state so reset() doesn't clear it
|
|
852
|
+
ms = self._multi_statement
|
|
853
|
+
self._multi_statement = None
|
|
854
|
+
self.reset()
|
|
855
|
+
self._multi_statement = ms
|
|
856
|
+
|
|
857
|
+
rs_response = self._fetch_result_set_by_query_id(query_id)
|
|
858
|
+
self._apply_result_set(rs_response, query=None, request_id=ms.request_id)
|
|
859
|
+
self._rownumber = -1
|
|
860
|
+
|
|
861
|
+
return self
|
|
862
|
+
|
|
863
|
+
# ------------------------------------------------------------------
|
|
864
|
+
# Context manager
|
|
865
|
+
# ------------------------------------------------------------------
|
|
866
|
+
|
|
867
|
+
def __enter__(self) -> Self:
|
|
868
|
+
"""Enter the runtime context for the cursor."""
|
|
869
|
+
return self
|
|
870
|
+
|
|
871
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
872
|
+
"""Exit the runtime context for the cursor."""
|
|
873
|
+
self.close()
|
|
874
|
+
|
|
875
|
+
@api_telemetry
|
|
876
|
+
@requires_open_cursor_not_connection
|
|
877
|
+
def reset(self, closing: bool = False) -> None:
|
|
878
|
+
"""Reset the result set.
|
|
879
|
+
|
|
880
|
+
Frees heavy result data (arrow streams, multi-statement state) while
|
|
881
|
+
for backward compatibility preserving metadata that the old driver
|
|
882
|
+
also keeps across resets: ``description``, ``rownumber``, ``sfqid``,
|
|
883
|
+
``query``, and ``sqlstate``.
|
|
884
|
+
|
|
885
|
+
Also clears the ``messages`` list so that errors from previous
|
|
886
|
+
operations do not leak into the next one.
|
|
887
|
+
|
|
888
|
+
When the connection was created with ``reuse_results=True``, the result
|
|
889
|
+
handle is kept so a later fetch can re-read the same rows. ``close()``
|
|
890
|
+
always releases the handle.
|
|
891
|
+
|
|
892
|
+
Args:
|
|
893
|
+
closing: If True, do not reset rowcount,
|
|
894
|
+
see: SNOW-647539: Do not erase the rowcount information when closing the cursor.
|
|
895
|
+
If False, reset rowcount to None.
|
|
896
|
+
"""
|
|
897
|
+
del self._messages[:]
|
|
898
|
+
self._query_result.reset(closing=closing)
|
|
899
|
+
if closing or self._connection._reuse_results is not True:
|
|
900
|
+
self._result_set.release()
|
|
901
|
+
self._iterator = None
|
|
902
|
+
self._binding_data = None
|
|
903
|
+
self._prefetch_hook = None
|
|
904
|
+
# Clear multistatement state
|
|
905
|
+
self._multi_statement = None
|
|
906
|
+
|
|
907
|
+
# ------------------------------------------------------------------
|
|
908
|
+
# Fetch – Arrow / Pandas
|
|
909
|
+
# ------------------------------------------------------------------
|
|
910
|
+
|
|
911
|
+
@requires_dependency(pyarrow)
|
|
912
|
+
@api_telemetry
|
|
913
|
+
@requires_open
|
|
914
|
+
@with_prefetch_hook
|
|
915
|
+
def fetch_arrow_batches(
|
|
916
|
+
self,
|
|
917
|
+
force_microsecond_precision: bool | None = None,
|
|
918
|
+
) -> Iterator[Table]:
|
|
919
|
+
"""Fetch Arrow Tables in batches.
|
|
920
|
+
|
|
921
|
+
Timestamp values stay at nanosecond precision unless
|
|
922
|
+
``force_microsecond_precision`` is true. When the argument is omitted,
|
|
923
|
+
the connection's ``force_microsecond_precision`` is used.
|
|
924
|
+
"""
|
|
925
|
+
force_microsecond_precision = self._resolve_force_microsecond_precision(force_microsecond_precision)
|
|
926
|
+
stream_ptr = self._result_set.get_arrow_stream_ptr()
|
|
927
|
+
iterator = create_table_iterator(
|
|
928
|
+
stream_ptr=stream_ptr,
|
|
929
|
+
context=ArrowConverterContext.create(self._connection),
|
|
930
|
+
number_to_decimal=self._connection.arrow_number_to_decimal,
|
|
931
|
+
force_microsecond_precision=force_microsecond_precision,
|
|
932
|
+
use_core_arrow=bool(self._connection.config.use_core_arrow),
|
|
933
|
+
)
|
|
934
|
+
for batch in iterator:
|
|
935
|
+
yield pyarrow.Table.from_batches([batch])
|
|
936
|
+
|
|
937
|
+
@requires_dependency(pyarrow)
|
|
938
|
+
@api_telemetry
|
|
939
|
+
@requires_open
|
|
940
|
+
@with_prefetch_hook
|
|
941
|
+
def fetch_arrow_all(
|
|
942
|
+
self,
|
|
943
|
+
force_return_table: bool = False,
|
|
944
|
+
force_microsecond_precision: bool | None = None,
|
|
945
|
+
) -> Table | None:
|
|
946
|
+
"""Fetch all results as a single Arrow Table.
|
|
947
|
+
|
|
948
|
+
Timestamp values stay at nanosecond precision unless
|
|
949
|
+
``force_microsecond_precision`` is true. When the argument is omitted,
|
|
950
|
+
the connection's ``force_microsecond_precision`` is used.
|
|
951
|
+
"""
|
|
952
|
+
force_microsecond_precision = self._resolve_force_microsecond_precision(force_microsecond_precision)
|
|
953
|
+
stream_ptr = self._result_set.get_arrow_stream_ptr()
|
|
954
|
+
iterator = create_table_iterator(
|
|
955
|
+
stream_ptr=stream_ptr,
|
|
956
|
+
context=ArrowConverterContext.create(self._connection),
|
|
957
|
+
number_to_decimal=self._connection.arrow_number_to_decimal,
|
|
958
|
+
force_microsecond_precision=force_microsecond_precision,
|
|
959
|
+
use_core_arrow=bool(self._connection.config.use_core_arrow),
|
|
960
|
+
)
|
|
961
|
+
return collect_arrow_table(
|
|
962
|
+
table_iterator=iterator,
|
|
963
|
+
columns_metadata=self._query_result.description,
|
|
964
|
+
force_return_table=force_return_table,
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
@requires_dependency(pandas)
|
|
968
|
+
@api_telemetry
|
|
969
|
+
@requires_open
|
|
970
|
+
def fetch_pandas_batches(self, **kwargs: Any) -> Iterator[DataFrame]:
|
|
971
|
+
"""Fetch Pandas DataFrames in batches.
|
|
972
|
+
|
|
973
|
+
``force_microsecond_precision`` overrides the connection setting for
|
|
974
|
+
the Arrow conversion when the caller passes it, and is taken from the
|
|
975
|
+
connection when omitted. Every other kwarg is forwarded to
|
|
976
|
+
``pyarrow.Table.to_pandas`` (for example ``split_blocks``).
|
|
977
|
+
"""
|
|
978
|
+
force_microsecond_precision = self._resolve_force_microsecond_precision(
|
|
979
|
+
kwargs.pop("force_microsecond_precision", None)
|
|
980
|
+
)
|
|
981
|
+
for table in self.fetch_arrow_batches(force_microsecond_precision=force_microsecond_precision):
|
|
982
|
+
yield table.to_pandas(**kwargs)
|
|
983
|
+
|
|
984
|
+
@requires_dependency(pandas)
|
|
985
|
+
@api_telemetry
|
|
986
|
+
@requires_open
|
|
987
|
+
def fetch_pandas_all(self, **kwargs: Any) -> DataFrame:
|
|
988
|
+
"""Fetch all results as a single Pandas DataFrame.
|
|
989
|
+
|
|
990
|
+
``force_microsecond_precision`` overrides the connection setting for
|
|
991
|
+
the Arrow conversion when the caller passes it, and is taken from the
|
|
992
|
+
connection when omitted. Every other kwarg is forwarded to
|
|
993
|
+
``pyarrow.Table.to_pandas``.
|
|
994
|
+
"""
|
|
995
|
+
force_microsecond_precision = self._resolve_force_microsecond_precision(
|
|
996
|
+
kwargs.pop("force_microsecond_precision", None)
|
|
997
|
+
)
|
|
998
|
+
table: Table = self.fetch_arrow_all(
|
|
999
|
+
force_return_table=True, force_microsecond_precision=force_microsecond_precision
|
|
1000
|
+
)
|
|
1001
|
+
return table.to_pandas(**kwargs)
|
|
1002
|
+
|
|
1003
|
+
# ------------------------------------------------------------------
|
|
1004
|
+
# Distributed fetch
|
|
1005
|
+
# ------------------------------------------------------------------
|
|
1006
|
+
|
|
1007
|
+
@api_telemetry
|
|
1008
|
+
@requires_open
|
|
1009
|
+
@with_prefetch_hook
|
|
1010
|
+
def get_result_batches(self) -> list[ResultBatch] | None:
|
|
1011
|
+
"""Get the previously executed query's ResultBatches if available."""
|
|
1012
|
+
result_chunks = self._result_set.get_chunks()
|
|
1013
|
+
if result_chunks is None:
|
|
1014
|
+
return None
|
|
1015
|
+
return ResultBatch.from_chunks(
|
|
1016
|
+
list(result_chunks.chunks),
|
|
1017
|
+
self._query_result.description,
|
|
1018
|
+
self._connection,
|
|
1019
|
+
list(result_chunks.columns),
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# ------------------------------------------------------------------
|
|
1023
|
+
# Async query support
|
|
1024
|
+
# ------------------------------------------------------------------
|
|
1025
|
+
|
|
1026
|
+
@api_telemetry
|
|
1027
|
+
@requires_open
|
|
1028
|
+
def query_result(self, qid: str) -> SnowflakeCursorBase:
|
|
1029
|
+
"""
|
|
1030
|
+
Fetch the result of a previously executed query by its Snowflake Query ID.
|
|
1031
|
+
|
|
1032
|
+
Resets the cursor and populates it with the results from the specified
|
|
1033
|
+
query, making them available through the standard fetch methods
|
|
1034
|
+
(fetchone, fetchall, fetch_arrow_all, etc.).
|
|
1035
|
+
|
|
1036
|
+
Args:
|
|
1037
|
+
qid: Snowflake Query ID (sfqid) of the previously executed query.
|
|
1038
|
+
|
|
1039
|
+
Returns:
|
|
1040
|
+
This cursor instance, now populated with the query results.
|
|
1041
|
+
|
|
1042
|
+
Raises:
|
|
1043
|
+
ProgrammingError: If the query ID is invalid, the query is still
|
|
1044
|
+
running, or the results are no longer available.
|
|
1045
|
+
"""
|
|
1046
|
+
self.reset()
|
|
1047
|
+
|
|
1048
|
+
response = core_driver.connection_get_query_result(
|
|
1049
|
+
conn_handle=self._connection.conn_handle, # type: ignore[arg-type]
|
|
1050
|
+
query_id=qid,
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
if response.HasField("multi"):
|
|
1054
|
+
self._handle_multi_statement_response(response.multi, query=None, request_id=None)
|
|
1055
|
+
else:
|
|
1056
|
+
rs_response = response.single
|
|
1057
|
+
self._apply_result_set(rs_response, query=None)
|
|
1058
|
+
|
|
1059
|
+
self._rownumber = -1
|
|
1060
|
+
|
|
1061
|
+
return self
|
|
1062
|
+
|
|
1063
|
+
@api_telemetry
|
|
1064
|
+
@requires_open
|
|
1065
|
+
def get_results_from_sfqid(self, sfqid: str) -> None:
|
|
1066
|
+
"""Get results from a previously executed query.
|
|
1067
|
+
|
|
1068
|
+
Polls query status until completion, then loads results lazily
|
|
1069
|
+
on first fetch call.
|
|
1070
|
+
|
|
1071
|
+
Before the first fetch triggers the prefetch hook, result-dependent
|
|
1072
|
+
cursor attributes and methods such as ``description``, ``rowcount``,
|
|
1073
|
+
and ``fetch*`` are not populated and MUST NOT be relied upon.
|
|
1074
|
+
|
|
1075
|
+
Args:
|
|
1076
|
+
sfqid: Snowflake Query ID of the target query.
|
|
1077
|
+
|
|
1078
|
+
Raises:
|
|
1079
|
+
ProgrammingError: If the query has already failed at call time,
|
|
1080
|
+
or if it reaches a terminal error status while being polled
|
|
1081
|
+
for completion.
|
|
1082
|
+
DatabaseError: If the server stops returning status information
|
|
1083
|
+
while polling for query completion.
|
|
1084
|
+
"""
|
|
1085
|
+
self.reset()
|
|
1086
|
+
self.connection.get_query_status_throw_if_error(sfqid)
|
|
1087
|
+
self._query_result.sfqid = sfqid
|
|
1088
|
+
waiter = QueryResultWaiter(self._connection, sfqid)
|
|
1089
|
+
|
|
1090
|
+
def prefetch_hook() -> None:
|
|
1091
|
+
waiter.wait()
|
|
1092
|
+
self._prefetch_hook = None
|
|
1093
|
+
self.query_result(sfqid)
|
|
1094
|
+
|
|
1095
|
+
self._prefetch_hook = prefetch_hook
|
|
1096
|
+
|
|
1097
|
+
@api_telemetry
|
|
1098
|
+
@requires_open
|
|
1099
|
+
def execute_async(
|
|
1100
|
+
self,
|
|
1101
|
+
command: str,
|
|
1102
|
+
params: Sequence[Any] | dict[str, Any] | None = None,
|
|
1103
|
+
num_statements: int | None = None,
|
|
1104
|
+
*,
|
|
1105
|
+
_statement_params: dict[str, Any] | None = None,
|
|
1106
|
+
**kwargs: Any,
|
|
1107
|
+
) -> dict[str, str | None]:
|
|
1108
|
+
"""Submit a query for async execution and return immediately with the query ID.
|
|
1109
|
+
|
|
1110
|
+
This is the first step in the async query lifecycle::
|
|
1111
|
+
|
|
1112
|
+
# 1. Submit the query
|
|
1113
|
+
result = cursor.execute_async("SELECT ...")
|
|
1114
|
+
query_id = result["queryId"]
|
|
1115
|
+
|
|
1116
|
+
# 2. Poll until complete
|
|
1117
|
+
status = connection.get_query_status(query_id)
|
|
1118
|
+
|
|
1119
|
+
# 3. Retrieve results
|
|
1120
|
+
cursor.get_results_from_sfqid(query_id)
|
|
1121
|
+
|
|
1122
|
+
Args:
|
|
1123
|
+
command: SQL statement to execute.
|
|
1124
|
+
params: Parameters for the operation (sequence or dict).
|
|
1125
|
+
num_statements: Number of statements in a multistatement query.
|
|
1126
|
+
_statement_params: Extra per-statement parameters (e.g. ``QUERY_TAG``)
|
|
1127
|
+
sent to Snowflake with this query only. Never persisted on the cursor.
|
|
1128
|
+
|
|
1129
|
+
Returns:
|
|
1130
|
+
dict with a ``queryId`` key containing the Snowflake Query ID.
|
|
1131
|
+
"""
|
|
1132
|
+
statement_parameters = self._collect_statement_params(
|
|
1133
|
+
skip_upload_on_content_match=False,
|
|
1134
|
+
num_statements=num_statements,
|
|
1135
|
+
statement_params=_statement_params,
|
|
1136
|
+
)
|
|
1137
|
+
# TODO: deprecate returning the dict, return just the sfqid itself
|
|
1138
|
+
self.reset()
|
|
1139
|
+
return self._execute_async(command, params, statement_parameters=statement_parameters)
|
|
1140
|
+
|
|
1141
|
+
def _execute_async(
|
|
1142
|
+
self,
|
|
1143
|
+
command: str,
|
|
1144
|
+
params: Sequence[Any] | dict[str, Any] | None,
|
|
1145
|
+
*,
|
|
1146
|
+
statement_parameters: dict[str, Any] | None = None,
|
|
1147
|
+
) -> dict[str, str | None]:
|
|
1148
|
+
query, binding_params = self._prepare_query(command, params)
|
|
1149
|
+
|
|
1150
|
+
with statement(self._connection.conn_handle, query) as stmt_handle: # type: ignore[arg-type]
|
|
1151
|
+
self._apply_statement_parameters(stmt_handle, statement_parameters)
|
|
1152
|
+
bindings = self._build_query_bindings(binding_params, query) if binding_params is not None else None
|
|
1153
|
+
response = core_driver.statement_execute_async(stmt_handle=stmt_handle, bindings=bindings)
|
|
1154
|
+
|
|
1155
|
+
query_id = response.query_id or None
|
|
1156
|
+
request_id = response.request_id or None
|
|
1157
|
+
self._query_result = QueryResult(sfqid=query_id, request_id=request_id)
|
|
1158
|
+
|
|
1159
|
+
return {"queryId": query_id}
|
|
1160
|
+
|
|
1161
|
+
@api_telemetry
|
|
1162
|
+
@requires_open
|
|
1163
|
+
def abort_query(self, qid: str) -> bool:
|
|
1164
|
+
"""Abort a running query."""
|
|
1165
|
+
response = core_driver.connection_abort_query(
|
|
1166
|
+
conn_handle=self._connection.conn_handle, # type: ignore[arg-type]
|
|
1167
|
+
query_id=qid,
|
|
1168
|
+
)
|
|
1169
|
+
return response.outcome == ABORT_QUERY_OUTCOME_ABORTED
|