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