snowflake-connector-python 3.16.0__cp313-cp313-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 +96 -0
- snowflake/connector/_query_context_cache.py +276 -0
- snowflake/connector/_sql_util.py +48 -0
- snowflake/connector/_utils.py +70 -0
- snowflake/connector/arrow_context.py +203 -0
- snowflake/connector/auth/__init__.py +53 -0
- snowflake/connector/auth/_auth.py +586 -0
- snowflake/connector/auth/_http_server.py +220 -0
- snowflake/connector/auth/_oauth_base.py +400 -0
- snowflake/connector/auth/by_plugin.py +219 -0
- snowflake/connector/auth/default.py +36 -0
- snowflake/connector/auth/idtoken.py +72 -0
- snowflake/connector/auth/keypair.py +222 -0
- snowflake/connector/auth/no_auth.py +39 -0
- snowflake/connector/auth/oauth.py +49 -0
- snowflake/connector/auth/oauth_code.py +479 -0
- snowflake/connector/auth/oauth_credentials.py +66 -0
- snowflake/connector/auth/okta.py +335 -0
- snowflake/connector/auth/pat.py +39 -0
- snowflake/connector/auth/usrpwdmfa.py +69 -0
- snowflake/connector/auth/webbrowser.py +502 -0
- snowflake/connector/auth/workload_identity.py +94 -0
- snowflake/connector/azure_storage_client.py +276 -0
- snowflake/connector/backoff_policies.py +141 -0
- snowflake/connector/bind_upload_agent.py +89 -0
- snowflake/connector/cache.py +696 -0
- snowflake/connector/compat.py +127 -0
- snowflake/connector/config_manager.py +496 -0
- snowflake/connector/connection.py +2273 -0
- snowflake/connector/connection_diagnostic.py +776 -0
- snowflake/connector/constants.py +442 -0
- snowflake/connector/converter.py +785 -0
- snowflake/connector/converter_issue23517.py +87 -0
- snowflake/connector/converter_null.py +14 -0
- snowflake/connector/converter_snowsql.py +205 -0
- snowflake/connector/cursor.py +1951 -0
- snowflake/connector/dbapi.py +53 -0
- snowflake/connector/description.py +19 -0
- snowflake/connector/direct_file_operation_utils.py +88 -0
- snowflake/connector/encryption_util.py +220 -0
- snowflake/connector/errorcode.py +91 -0
- snowflake/connector/errors.py +615 -0
- snowflake/connector/externals_utils/__init__.py +0 -0
- snowflake/connector/externals_utils/externals_setup.py +27 -0
- snowflake/connector/feature.py +4 -0
- snowflake/connector/file_compression_type.py +118 -0
- snowflake/connector/file_lock.py +72 -0
- snowflake/connector/file_transfer_agent.py +1215 -0
- snowflake/connector/file_util.py +153 -0
- snowflake/connector/gcs_storage_client.py +474 -0
- snowflake/connector/gzip_decoder.py +85 -0
- snowflake/connector/local_storage_client.py +90 -0
- snowflake/connector/log_configuration.py +60 -0
- snowflake/connector/logging_utils/__init__.py +0 -0
- snowflake/connector/logging_utils/filters.py +72 -0
- snowflake/connector/nanoarrow_arrow_iterator.cp313-win_amd64.pyd +0 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +557 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1005 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +71 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +56 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +65 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +256 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
- snowflake/connector/network.py +1297 -0
- snowflake/connector/ocsp_asn1crypto.py +447 -0
- snowflake/connector/ocsp_snowflake.py +1883 -0
- snowflake/connector/options.py +131 -0
- snowflake/connector/pandas_tools.py +732 -0
- snowflake/connector/proxy.py +43 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +786 -0
- snowflake/connector/result_set.py +319 -0
- snowflake/connector/s3_storage_client.py +605 -0
- snowflake/connector/secret_detector.py +181 -0
- snowflake/connector/sf_dirs.py +64 -0
- snowflake/connector/sfbinaryformat.py +35 -0
- snowflake/connector/sfdatetime.py +345 -0
- snowflake/connector/snow_logging.py +144 -0
- snowflake/connector/sqlstate.py +9 -0
- snowflake/connector/ssd_internal_keys.py +30 -0
- snowflake/connector/ssl_wrap_socket.py +134 -0
- snowflake/connector/storage_client.py +478 -0
- snowflake/connector/telemetry.py +249 -0
- snowflake/connector/telemetry_oob.py +543 -0
- snowflake/connector/test_util.py +30 -0
- snowflake/connector/time_util.py +159 -0
- snowflake/connector/token_cache.py +402 -0
- snowflake/connector/tool/__init__.py +0 -0
- snowflake/connector/tool/dump_certs.py +57 -0
- snowflake/connector/tool/dump_ocsp_response.py +139 -0
- snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
- snowflake/connector/tool/probe_connection.py +69 -0
- snowflake/connector/url_util.py +49 -0
- snowflake/connector/util_text.py +301 -0
- snowflake/connector/vendored/__init__.py +3 -0
- snowflake/connector/vendored/requests/LICENSE +175 -0
- snowflake/connector/vendored/requests/__init__.py +169 -0
- snowflake/connector/vendored/requests/__version__.py +14 -0
- snowflake/connector/vendored/requests/_internal_utils.py +50 -0
- snowflake/connector/vendored/requests/adapters.py +537 -0
- snowflake/connector/vendored/requests/api.py +157 -0
- snowflake/connector/vendored/requests/auth.py +315 -0
- snowflake/connector/vendored/requests/certs.py +17 -0
- snowflake/connector/vendored/requests/compat.py +79 -0
- snowflake/connector/vendored/requests/cookies.py +561 -0
- snowflake/connector/vendored/requests/exceptions.py +140 -0
- snowflake/connector/vendored/requests/help.py +134 -0
- snowflake/connector/vendored/requests/hooks.py +33 -0
- snowflake/connector/vendored/requests/models.py +1033 -0
- snowflake/connector/vendored/requests/sessions.py +833 -0
- snowflake/connector/vendored/requests/status_codes.py +128 -0
- snowflake/connector/vendored/requests/structures.py +99 -0
- snowflake/connector/vendored/requests/utils.py +1093 -0
- snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
- snowflake/connector/vendored/urllib3/__init__.py +85 -0
- snowflake/connector/vendored/urllib3/_collections.py +355 -0
- snowflake/connector/vendored/urllib3/_version.py +2 -0
- snowflake/connector/vendored/urllib3/connection.py +572 -0
- snowflake/connector/vendored/urllib3/connectionpool.py +1137 -0
- snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/_appengine_environ.py +36 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/bindings.py +519 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/low_level.py +397 -0
- snowflake/connector/vendored/urllib3/contrib/appengine.py +314 -0
- snowflake/connector/vendored/urllib3/contrib/ntlmpool.py +130 -0
- snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +509 -0
- snowflake/connector/vendored/urllib3/contrib/securetransport.py +920 -0
- snowflake/connector/vendored/urllib3/contrib/socks.py +216 -0
- snowflake/connector/vendored/urllib3/exceptions.py +323 -0
- snowflake/connector/vendored/urllib3/fields.py +274 -0
- snowflake/connector/vendored/urllib3/filepost.py +98 -0
- snowflake/connector/vendored/urllib3/packages/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/packages/backports/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/packages/backports/makefile.py +51 -0
- snowflake/connector/vendored/urllib3/packages/backports/weakref_finalize.py +155 -0
- snowflake/connector/vendored/urllib3/packages/six.py +1076 -0
- snowflake/connector/vendored/urllib3/poolmanager.py +540 -0
- snowflake/connector/vendored/urllib3/request.py +191 -0
- snowflake/connector/vendored/urllib3/response.py +885 -0
- snowflake/connector/vendored/urllib3/util/__init__.py +49 -0
- snowflake/connector/vendored/urllib3/util/connection.py +156 -0
- snowflake/connector/vendored/urllib3/util/proxy.py +57 -0
- snowflake/connector/vendored/urllib3/util/queue.py +22 -0
- snowflake/connector/vendored/urllib3/util/request.py +146 -0
- snowflake/connector/vendored/urllib3/util/response.py +107 -0
- snowflake/connector/vendored/urllib3/util/retry.py +620 -0
- snowflake/connector/vendored/urllib3/util/ssl_.py +495 -0
- snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
- snowflake/connector/vendored/urllib3/util/ssltransport.py +221 -0
- snowflake/connector/vendored/urllib3/util/timeout.py +271 -0
- snowflake/connector/vendored/urllib3/util/url.py +435 -0
- snowflake/connector/vendored/urllib3/util/wait.py +152 -0
- snowflake/connector/version.py +3 -0
- snowflake/connector/wif_util.py +407 -0
- snowflake_connector_python-3.16.0.dist-info/METADATA +1475 -0
- snowflake_connector_python-3.16.0.dist-info/RECORD +246 -0
- snowflake_connector_python-3.16.0.dist-info/WHEEL +5 -0
- snowflake_connector_python-3.16.0.dist-info/entry_points.txt +4 -0
- snowflake_connector_python-3.16.0.dist-info/licenses/LICENSE.txt +202 -0
- snowflake_connector_python-3.16.0.dist-info/licenses/NOTICE +8 -0
- snowflake_connector_python-3.16.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1951 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import collections
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import signal
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
import warnings
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from logging import getLogger
|
|
15
|
+
from threading import Lock
|
|
16
|
+
from types import TracebackType
|
|
17
|
+
from typing import (
|
|
18
|
+
IO,
|
|
19
|
+
TYPE_CHECKING,
|
|
20
|
+
Any,
|
|
21
|
+
Callable,
|
|
22
|
+
Iterator,
|
|
23
|
+
Literal,
|
|
24
|
+
NamedTuple,
|
|
25
|
+
NoReturn,
|
|
26
|
+
Sequence,
|
|
27
|
+
TypeVar,
|
|
28
|
+
overload,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from typing_extensions import Self
|
|
32
|
+
|
|
33
|
+
from snowflake.connector.result_batch import create_batches_from_response
|
|
34
|
+
from snowflake.connector.result_set import ResultSet
|
|
35
|
+
|
|
36
|
+
from . import compat
|
|
37
|
+
from ._sql_util import get_file_transfer_type
|
|
38
|
+
from ._utils import (
|
|
39
|
+
REQUEST_ID_STATEMENT_PARAM_NAME,
|
|
40
|
+
_TrackedQueryCancellationTimer,
|
|
41
|
+
is_uuid4,
|
|
42
|
+
)
|
|
43
|
+
from .bind_upload_agent import BindUploadAgent, BindUploadError
|
|
44
|
+
from .constants import (
|
|
45
|
+
CMD_TYPE_DOWNLOAD,
|
|
46
|
+
CMD_TYPE_UPLOAD,
|
|
47
|
+
FIELD_NAME_TO_ID,
|
|
48
|
+
PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT,
|
|
49
|
+
FileTransferType,
|
|
50
|
+
QueryStatus,
|
|
51
|
+
)
|
|
52
|
+
from .errorcode import (
|
|
53
|
+
ER_CURSOR_IS_CLOSED,
|
|
54
|
+
ER_FAILED_PROCESSING_PYFORMAT,
|
|
55
|
+
ER_FAILED_TO_REWRITE_MULTI_ROW_INSERT,
|
|
56
|
+
ER_INVALID_VALUE,
|
|
57
|
+
ER_NO_ARROW_RESULT,
|
|
58
|
+
ER_NO_PYARROW,
|
|
59
|
+
ER_NO_PYARROW_SNOWSQL,
|
|
60
|
+
ER_NOT_POSITIVE_SIZE,
|
|
61
|
+
ER_UNSUPPORTED_METHOD,
|
|
62
|
+
)
|
|
63
|
+
from .errors import (
|
|
64
|
+
DatabaseError,
|
|
65
|
+
Error,
|
|
66
|
+
IntegrityError,
|
|
67
|
+
InterfaceError,
|
|
68
|
+
NotSupportedError,
|
|
69
|
+
ProgrammingError,
|
|
70
|
+
)
|
|
71
|
+
from .options import installed_pandas
|
|
72
|
+
from .sqlstate import SQLSTATE_FEATURE_NOT_SUPPORTED
|
|
73
|
+
from .telemetry import TelemetryData, TelemetryField
|
|
74
|
+
from .time_util import get_time_millis
|
|
75
|
+
|
|
76
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
77
|
+
from pandas import DataFrame
|
|
78
|
+
from pyarrow import Table
|
|
79
|
+
|
|
80
|
+
from .connection import SnowflakeConnection
|
|
81
|
+
from .file_transfer_agent import SnowflakeProgressPercentage
|
|
82
|
+
from .result_batch import ResultBatch
|
|
83
|
+
|
|
84
|
+
T = TypeVar("T", bound=collections.abc.Sequence)
|
|
85
|
+
|
|
86
|
+
logger = getLogger(__name__)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if not installed_pandas:
|
|
90
|
+
logger.debug(
|
|
91
|
+
"Failed to import pyarrow or pandas. Cannot use pandas fetch API. Please "
|
|
92
|
+
"install snowflake-connector-python with the `pandas` extra to use these "
|
|
93
|
+
"features."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
from .nanoarrow_arrow_iterator import PyArrowIterator # NOQA
|
|
99
|
+
|
|
100
|
+
CAN_USE_ARROW_RESULT_FORMAT = True
|
|
101
|
+
except ImportError as e: # pragma: no cover
|
|
102
|
+
logger.warning(
|
|
103
|
+
f"Failed to import ArrowResult. No Apache Arrow result set format can be used. ImportError: {e}",
|
|
104
|
+
)
|
|
105
|
+
CAN_USE_ARROW_RESULT_FORMAT = False
|
|
106
|
+
|
|
107
|
+
STATEMENT_TYPE_ID_DML = 0x3000
|
|
108
|
+
STATEMENT_TYPE_ID_INSERT = STATEMENT_TYPE_ID_DML + 0x100
|
|
109
|
+
STATEMENT_TYPE_ID_UPDATE = STATEMENT_TYPE_ID_DML + 0x200
|
|
110
|
+
STATEMENT_TYPE_ID_DELETE = STATEMENT_TYPE_ID_DML + 0x300
|
|
111
|
+
STATEMENT_TYPE_ID_MERGE = STATEMENT_TYPE_ID_DML + 0x400
|
|
112
|
+
STATEMENT_TYPE_ID_MULTI_TABLE_INSERT = STATEMENT_TYPE_ID_DML + 0x500
|
|
113
|
+
|
|
114
|
+
STATEMENT_TYPE_ID_DML_SET = frozenset(
|
|
115
|
+
[
|
|
116
|
+
STATEMENT_TYPE_ID_DML,
|
|
117
|
+
STATEMENT_TYPE_ID_INSERT,
|
|
118
|
+
STATEMENT_TYPE_ID_UPDATE,
|
|
119
|
+
STATEMENT_TYPE_ID_DELETE,
|
|
120
|
+
STATEMENT_TYPE_ID_MERGE,
|
|
121
|
+
STATEMENT_TYPE_ID_MULTI_TABLE_INSERT,
|
|
122
|
+
]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
DESC_TABLE_RE = re.compile(r"desc(?:ribe)?\s+([\w_]+)\s*;?\s*$", flags=re.IGNORECASE)
|
|
126
|
+
|
|
127
|
+
LOG_MAX_QUERY_LENGTH = 80
|
|
128
|
+
|
|
129
|
+
ASYNC_NO_DATA_MAX_RETRY = 24
|
|
130
|
+
ASYNC_RETRY_PATTERN = [1, 1, 2, 3, 4, 8, 10]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class _NanoarrowUsage(str, Enum):
|
|
134
|
+
# follow the session parameter to use nanoarrow converter or not
|
|
135
|
+
FOLLOW_SESSION_PARAMETER = "follow_session_parameter"
|
|
136
|
+
# ignore the session parameter, use nanoarrow converter
|
|
137
|
+
ENABLE_NANOARROW = "enable_nanoarrow"
|
|
138
|
+
# ignore the session parameter, do not use nanoarrow converter
|
|
139
|
+
DISABLE_NANOARROW = "disable_nanoarrow"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ResultMetadata(NamedTuple):
|
|
143
|
+
name: str
|
|
144
|
+
type_code: int
|
|
145
|
+
display_size: int | None
|
|
146
|
+
internal_size: int | None
|
|
147
|
+
precision: int | None
|
|
148
|
+
scale: int | None
|
|
149
|
+
is_nullable: bool
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def from_column(cls, col: dict[str, Any]):
|
|
153
|
+
"""Initializes a ResultMetadata object from the column description in the query response."""
|
|
154
|
+
type_code = FIELD_NAME_TO_ID[
|
|
155
|
+
(
|
|
156
|
+
col["extTypeName"].upper()
|
|
157
|
+
if col.get("extTypeName")
|
|
158
|
+
else col["type"].upper()
|
|
159
|
+
)
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
return cls(
|
|
163
|
+
col["name"],
|
|
164
|
+
type_code,
|
|
165
|
+
None,
|
|
166
|
+
col["length"],
|
|
167
|
+
col["precision"],
|
|
168
|
+
col["scale"],
|
|
169
|
+
col["nullable"],
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ResultMetadataV2:
|
|
174
|
+
"""ResultMetadataV2 represents the type information of a single column.
|
|
175
|
+
|
|
176
|
+
It is a replacement for ResultMetadata that contains additional attributes, currently
|
|
177
|
+
`vector_dimension` and `fields`. This class will be unified with ResultMetadata in the
|
|
178
|
+
near future.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
name: str,
|
|
184
|
+
type_code: int,
|
|
185
|
+
is_nullable: bool,
|
|
186
|
+
display_size: int | None = None,
|
|
187
|
+
internal_size: int | None = None,
|
|
188
|
+
precision: int | None = None,
|
|
189
|
+
scale: int | None = None,
|
|
190
|
+
vector_dimension: int | None = None,
|
|
191
|
+
fields: list[ResultMetadataV2] | None = None,
|
|
192
|
+
):
|
|
193
|
+
self._name = name
|
|
194
|
+
self._type_code = type_code
|
|
195
|
+
self._is_nullable = is_nullable
|
|
196
|
+
self._display_size = display_size
|
|
197
|
+
self._internal_size = internal_size
|
|
198
|
+
self._precision = precision
|
|
199
|
+
self._scale = scale
|
|
200
|
+
self._vector_dimension = vector_dimension
|
|
201
|
+
self._fields = fields
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def from_column(cls, col: dict[str, Any]) -> ResultMetadataV2:
|
|
205
|
+
"""Initializes a ResultMetadataV2 object from the column description in the query response.
|
|
206
|
+
This differs from ResultMetadata in that it has newly-added fields which cannot be added to
|
|
207
|
+
ResultMetadata since it is a named tuple.
|
|
208
|
+
"""
|
|
209
|
+
col_type = (
|
|
210
|
+
col["extTypeName"].upper()
|
|
211
|
+
if col.get("extTypeName")
|
|
212
|
+
else col["type"].upper()
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
fields = col.get("fields")
|
|
216
|
+
processed_fields: Optional[List[ResultMetadataV2]] = None
|
|
217
|
+
if fields is not None:
|
|
218
|
+
if col_type in {"VECTOR", "ARRAY", "OBJECT", "MAP"}:
|
|
219
|
+
processed_fields = [
|
|
220
|
+
ResultMetadataV2.from_column({"name": None, **f})
|
|
221
|
+
for f in col["fields"]
|
|
222
|
+
]
|
|
223
|
+
else:
|
|
224
|
+
raise ValueError(
|
|
225
|
+
f"Field parsing is not supported for columns of type {col_type}."
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return cls(
|
|
229
|
+
col["name"],
|
|
230
|
+
FIELD_NAME_TO_ID[col_type],
|
|
231
|
+
col["nullable"],
|
|
232
|
+
None,
|
|
233
|
+
col["length"],
|
|
234
|
+
col["precision"],
|
|
235
|
+
col["scale"],
|
|
236
|
+
col.get("vectorDimension"),
|
|
237
|
+
processed_fields,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def _to_result_metadata_v1(self):
|
|
241
|
+
"""Initializes a ResultMetadata object from a ResultMetadataV2 object.
|
|
242
|
+
|
|
243
|
+
This method is for internal use only.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
return ResultMetadata(
|
|
247
|
+
self._name,
|
|
248
|
+
self._type_code,
|
|
249
|
+
self._display_size,
|
|
250
|
+
self._internal_size,
|
|
251
|
+
self._precision,
|
|
252
|
+
self._scale,
|
|
253
|
+
self._is_nullable,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def __str__(self) -> str:
|
|
257
|
+
return (
|
|
258
|
+
f"ResultMetadataV2(name={self._name},type_code={self._type_code},"
|
|
259
|
+
+ f"is_nullable={self._is_nullable},display_size={self._display_size},"
|
|
260
|
+
+ "internal_size={self._internal_size},precision={self._precision},"
|
|
261
|
+
+ "scale={self._scale},vector_dimension={self._vector_dimension},"
|
|
262
|
+
+ "fields={self.fields})"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def __eq__(self, other) -> bool:
|
|
266
|
+
if not isinstance(other, self.__class__):
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
self._name == other._name
|
|
271
|
+
and self._type_code == other._type_code
|
|
272
|
+
and self._is_nullable == other._is_nullable
|
|
273
|
+
and self._display_size == other._display_size
|
|
274
|
+
and self._internal_size == other._internal_size
|
|
275
|
+
and self._precision == other._precision
|
|
276
|
+
and self._scale == other._scale
|
|
277
|
+
and self._vector_dimension == other._vector_dimension
|
|
278
|
+
and self._fields == other._fields
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def name(self) -> str:
|
|
283
|
+
return self._name
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def type_code(self) -> int:
|
|
287
|
+
return self._type_code
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def is_nullable(self) -> bool:
|
|
291
|
+
return self._is_nullable
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def internal_size(self) -> int | None:
|
|
295
|
+
return self._internal_size
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def display_size(self) -> int | None:
|
|
299
|
+
return self._display_size
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def precision(self) -> int | None:
|
|
303
|
+
return self._precision
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def scale(self) -> int | None:
|
|
307
|
+
return self._scale
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def vector_dimension(self) -> int | None:
|
|
311
|
+
return self._vector_dimension
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def fields(self) -> list[ResultMetadataV2] | None:
|
|
315
|
+
return self._fields
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def exit_handler(*_) -> NoReturn:
|
|
319
|
+
"""Handler for signal. When called, it will raise SystemExit with exit code FORCE_EXIT."""
|
|
320
|
+
print("\nForce exit")
|
|
321
|
+
logger.info("Force exit")
|
|
322
|
+
sys.exit(1)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class ResultState(Enum):
|
|
326
|
+
DEFAULT = 1
|
|
327
|
+
VALID = 2
|
|
328
|
+
RESET = 3
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class SnowflakeCursor:
|
|
332
|
+
"""Implementation of Cursor object that is returned from Connection.cursor() method.
|
|
333
|
+
|
|
334
|
+
Attributes:
|
|
335
|
+
description: A list of namedtuples about metadata for all columns.
|
|
336
|
+
rowcount: The number of records updated or selected. If not clear, -1 is returned.
|
|
337
|
+
rownumber: The current 0-based index of the cursor in the result set or None if the index cannot be
|
|
338
|
+
determined.
|
|
339
|
+
sfqid: Snowflake query id in UUID form. Include this in the problem report to the customer support.
|
|
340
|
+
sqlstate: Snowflake SQL State code.
|
|
341
|
+
timestamp_output_format: Snowflake timestamp_output_format for timestamps.
|
|
342
|
+
timestamp_ltz_output_format: Snowflake output format for LTZ timestamps.
|
|
343
|
+
timestamp_tz_output_format: Snowflake output format for TZ timestamps.
|
|
344
|
+
timestamp_ntz_output_format: Snowflake output format for NTZ timestamps.
|
|
345
|
+
date_output_format: Snowflake output format for dates.
|
|
346
|
+
time_output_format: Snowflake output format for times.
|
|
347
|
+
timezone: Snowflake timezone.
|
|
348
|
+
binary_output_format: Snowflake output format for binary fields.
|
|
349
|
+
arraysize: The default number of rows fetched by fetchmany.
|
|
350
|
+
connection: The connection object by which the cursor was created.
|
|
351
|
+
errorhandle: The class that handles error handling.
|
|
352
|
+
is_file_transfer: Whether, or not the current command is a put, or get.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
# TODO:
|
|
356
|
+
# Most of these attributes have no reason to be properties, we could just store them in public variables.
|
|
357
|
+
# Calling a function is expensive in Python and most of these getters are unnecessary.
|
|
358
|
+
|
|
359
|
+
INSERT_SQL_RE = re.compile(r"^insert\s+into", flags=re.IGNORECASE)
|
|
360
|
+
COMMENT_SQL_RE = re.compile(r"/\*.*\*/")
|
|
361
|
+
INSERT_SQL_VALUES_RE = re.compile(
|
|
362
|
+
r".*VALUES\s*(\(.*\)).*", re.IGNORECASE | re.MULTILINE | re.DOTALL
|
|
363
|
+
)
|
|
364
|
+
ALTER_SESSION_RE = re.compile(
|
|
365
|
+
r"alter\s+session\s+set\s+(\w*?)\s*=\s*\'?([^\']+?)\'?\s*(?:;|$)",
|
|
366
|
+
flags=re.IGNORECASE | re.MULTILINE | re.DOTALL,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def get_file_transfer_type(sql: str) -> FileTransferType | None:
|
|
371
|
+
"""Decide whether a SQL is a file transfer and return its type.
|
|
372
|
+
|
|
373
|
+
None is returned if the SQL isn't a file transfer so that this function can be
|
|
374
|
+
used in an if-statement.
|
|
375
|
+
"""
|
|
376
|
+
return get_file_transfer_type(sql)
|
|
377
|
+
|
|
378
|
+
def __init__(
|
|
379
|
+
self,
|
|
380
|
+
connection: SnowflakeConnection,
|
|
381
|
+
use_dict_result: bool = False,
|
|
382
|
+
) -> None:
|
|
383
|
+
"""Inits a SnowflakeCursor with a connection.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
connection: The connection that created this cursor.
|
|
387
|
+
use_dict_result: Decides whether to use dict result or not.
|
|
388
|
+
"""
|
|
389
|
+
self._connection: SnowflakeConnection = connection
|
|
390
|
+
|
|
391
|
+
self._errorhandler: Callable[
|
|
392
|
+
[SnowflakeConnection, SnowflakeCursor, type[Error], dict[str, str]],
|
|
393
|
+
None,
|
|
394
|
+
] = Error.default_errorhandler
|
|
395
|
+
self.messages: list[
|
|
396
|
+
tuple[type[Error] | type[Exception], dict[str, str | bool]]
|
|
397
|
+
] = []
|
|
398
|
+
self._timebomb: _TrackedQueryCancellationTimer | None = (
|
|
399
|
+
None # must be here for abort_exit method
|
|
400
|
+
)
|
|
401
|
+
self._description: list[ResultMetadataV2] | None = None
|
|
402
|
+
self._sfqid: str | None = None
|
|
403
|
+
self._sqlstate = None
|
|
404
|
+
self._total_rowcount = -1
|
|
405
|
+
self._sequence_counter = -1
|
|
406
|
+
self._request_id: uuid.UUID | None = None
|
|
407
|
+
self._is_file_transfer = False
|
|
408
|
+
self._multi_statement_resultIds: collections.deque[str] = collections.deque()
|
|
409
|
+
self.multi_statement_savedIds: list[str] = []
|
|
410
|
+
|
|
411
|
+
self._timestamp_output_format = None
|
|
412
|
+
self._timestamp_ltz_output_format = None
|
|
413
|
+
self._timestamp_ntz_output_format = None
|
|
414
|
+
self._timestamp_tz_output_format = None
|
|
415
|
+
self._date_output_format = None
|
|
416
|
+
self._time_output_format = None
|
|
417
|
+
self._timezone = None
|
|
418
|
+
self._binary_output_format = None
|
|
419
|
+
self._result: Iterator[tuple] | Iterator[dict] | None = None
|
|
420
|
+
self._result_set: ResultSet | None = None
|
|
421
|
+
self._result_state: ResultState = ResultState.DEFAULT
|
|
422
|
+
self._use_dict_result = use_dict_result
|
|
423
|
+
self.query: str | None = None
|
|
424
|
+
# TODO: self._query_result_format could be defined as an enum
|
|
425
|
+
self._query_result_format: str | None = None
|
|
426
|
+
|
|
427
|
+
self._arraysize = 1 # PEP-0249: defaults to 1
|
|
428
|
+
|
|
429
|
+
self._lock_canceling = Lock()
|
|
430
|
+
|
|
431
|
+
self._first_chunk_time = None
|
|
432
|
+
|
|
433
|
+
self._log_max_query_length = connection.log_max_query_length
|
|
434
|
+
self._inner_cursor: SnowflakeCursor | None = None
|
|
435
|
+
self._prefetch_hook = None
|
|
436
|
+
self._rownumber: int | None = None
|
|
437
|
+
|
|
438
|
+
self.reset()
|
|
439
|
+
|
|
440
|
+
def __del__(self) -> None: # pragma: no cover
|
|
441
|
+
try:
|
|
442
|
+
self.close()
|
|
443
|
+
except compat.BASE_EXCEPTION_CLASS as e:
|
|
444
|
+
if logger.getEffectiveLevel() <= logging.INFO:
|
|
445
|
+
logger.info(e)
|
|
446
|
+
|
|
447
|
+
@property
|
|
448
|
+
def description(self) -> list[ResultMetadata]:
|
|
449
|
+
if self._description is None:
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
return [meta._to_result_metadata_v1() for meta in self._description]
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def _description_internal(self) -> list[ResultMetadataV2]:
|
|
456
|
+
"""Return the new format of result metadata for a query.
|
|
457
|
+
|
|
458
|
+
This method is for internal use only.
|
|
459
|
+
"""
|
|
460
|
+
return self._description
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
def rowcount(self) -> int | None:
|
|
464
|
+
return self._total_rowcount if self._total_rowcount >= 0 else None
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
def rownumber(self) -> int | None:
|
|
468
|
+
return self._rownumber if self._rownumber >= 0 else None
|
|
469
|
+
|
|
470
|
+
@property
|
|
471
|
+
def sfqid(self) -> str | None:
|
|
472
|
+
return self._sfqid
|
|
473
|
+
|
|
474
|
+
@property
|
|
475
|
+
def sqlstate(self):
|
|
476
|
+
return self._sqlstate
|
|
477
|
+
|
|
478
|
+
@property
|
|
479
|
+
def timestamp_output_format(self) -> str | None:
|
|
480
|
+
return self._timestamp_output_format
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def timestamp_ltz_output_format(self) -> str | None:
|
|
484
|
+
return (
|
|
485
|
+
self._timestamp_ltz_output_format
|
|
486
|
+
if self._timestamp_ltz_output_format
|
|
487
|
+
else self._timestamp_output_format
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def timestamp_tz_output_format(self) -> str | None:
|
|
492
|
+
return (
|
|
493
|
+
self._timestamp_tz_output_format
|
|
494
|
+
if self._timestamp_tz_output_format
|
|
495
|
+
else self._timestamp_output_format
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
@property
|
|
499
|
+
def timestamp_ntz_output_format(self) -> str | None:
|
|
500
|
+
return (
|
|
501
|
+
self._timestamp_ntz_output_format
|
|
502
|
+
if self._timestamp_ntz_output_format
|
|
503
|
+
else self._timestamp_output_format
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
@property
|
|
507
|
+
def date_output_format(self) -> str | None:
|
|
508
|
+
return self._date_output_format
|
|
509
|
+
|
|
510
|
+
@property
|
|
511
|
+
def time_output_format(self) -> str | None:
|
|
512
|
+
return self._time_output_format
|
|
513
|
+
|
|
514
|
+
@property
|
|
515
|
+
def timezone(self) -> str | None:
|
|
516
|
+
return self._timezone
|
|
517
|
+
|
|
518
|
+
@property
|
|
519
|
+
def binary_output_format(self) -> str | None:
|
|
520
|
+
return self._binary_output_format
|
|
521
|
+
|
|
522
|
+
@property
|
|
523
|
+
def arraysize(self) -> int:
|
|
524
|
+
return self._arraysize
|
|
525
|
+
|
|
526
|
+
@arraysize.setter
|
|
527
|
+
def arraysize(self, value) -> None:
|
|
528
|
+
self._arraysize = int(value)
|
|
529
|
+
|
|
530
|
+
@property
|
|
531
|
+
def connection(self) -> SnowflakeConnection:
|
|
532
|
+
return self._connection
|
|
533
|
+
|
|
534
|
+
@property
|
|
535
|
+
def errorhandler(self) -> Callable:
|
|
536
|
+
return self._errorhandler
|
|
537
|
+
|
|
538
|
+
@errorhandler.setter
|
|
539
|
+
def errorhandler(self, value: Callable | None) -> None:
|
|
540
|
+
logger.debug("setting errorhandler: %s", value)
|
|
541
|
+
if value is None:
|
|
542
|
+
raise ProgrammingError("Invalid errorhandler is specified")
|
|
543
|
+
self._errorhandler = value
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def is_file_transfer(self) -> bool:
|
|
547
|
+
"""Whether the command is PUT or GET."""
|
|
548
|
+
return hasattr(self, "_is_file_transfer") and self._is_file_transfer
|
|
549
|
+
|
|
550
|
+
@property
|
|
551
|
+
def lastrowid(self) -> None:
|
|
552
|
+
"""Snowflake does not support lastrowid in which case None should be returned as per PEP249."""
|
|
553
|
+
return None
|
|
554
|
+
|
|
555
|
+
@overload
|
|
556
|
+
def callproc(self, procname: str) -> tuple: ...
|
|
557
|
+
|
|
558
|
+
@overload
|
|
559
|
+
def callproc(self, procname: str, args: T) -> T: ...
|
|
560
|
+
|
|
561
|
+
def callproc(self, procname: str, args=tuple()):
|
|
562
|
+
"""Call a stored procedure.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
procname: The stored procedure to be called.
|
|
566
|
+
args: Parameters to be passed into the stored procedure.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
The input parameters.
|
|
570
|
+
"""
|
|
571
|
+
marker_format = "%s" if self._connection.is_pyformat else "?"
|
|
572
|
+
command = (
|
|
573
|
+
f"CALL {procname}({', '.join([marker_format for _ in range(len(args))])})"
|
|
574
|
+
)
|
|
575
|
+
self.execute(command, args)
|
|
576
|
+
return args
|
|
577
|
+
|
|
578
|
+
def close(self) -> bool | None:
|
|
579
|
+
"""Closes the cursor object.
|
|
580
|
+
|
|
581
|
+
Returns whether the cursor was closed during this call.
|
|
582
|
+
"""
|
|
583
|
+
try:
|
|
584
|
+
if self.is_closed():
|
|
585
|
+
return False
|
|
586
|
+
with self._lock_canceling:
|
|
587
|
+
self.reset(closing=True)
|
|
588
|
+
self._connection = None
|
|
589
|
+
del self.messages[:]
|
|
590
|
+
return True
|
|
591
|
+
except Exception:
|
|
592
|
+
return None
|
|
593
|
+
|
|
594
|
+
def is_closed(self) -> bool:
|
|
595
|
+
return self._connection is None or self._connection.is_closed()
|
|
596
|
+
|
|
597
|
+
def _execute_helper(
|
|
598
|
+
self,
|
|
599
|
+
query: str,
|
|
600
|
+
timeout: int = 0,
|
|
601
|
+
statement_params: dict[str, str] | None = None,
|
|
602
|
+
binding_params: tuple | dict[str, dict[str, str]] = None,
|
|
603
|
+
binding_stage: str | None = None,
|
|
604
|
+
is_internal: bool = False,
|
|
605
|
+
describe_only: bool = False,
|
|
606
|
+
_no_results: bool = False,
|
|
607
|
+
_is_put_get=None,
|
|
608
|
+
_no_retry: bool = False,
|
|
609
|
+
dataframe_ast: str | None = None,
|
|
610
|
+
) -> dict[str, Any]:
|
|
611
|
+
del self.messages[:]
|
|
612
|
+
|
|
613
|
+
if statement_params is not None and not isinstance(statement_params, dict):
|
|
614
|
+
Error.errorhandler_wrapper(
|
|
615
|
+
self.connection,
|
|
616
|
+
self,
|
|
617
|
+
ProgrammingError,
|
|
618
|
+
{
|
|
619
|
+
"msg": "The data type of statement params is invalid. It must be dict.",
|
|
620
|
+
"errno": ER_INVALID_VALUE,
|
|
621
|
+
},
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
# check if current installation include arrow extension or not,
|
|
625
|
+
# if not, we set statement level query result format to be JSON
|
|
626
|
+
if not CAN_USE_ARROW_RESULT_FORMAT:
|
|
627
|
+
logger.debug("Cannot use arrow result format, fallback to json format")
|
|
628
|
+
if statement_params is None:
|
|
629
|
+
statement_params = {
|
|
630
|
+
PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT: "JSON"
|
|
631
|
+
}
|
|
632
|
+
else:
|
|
633
|
+
result_format_val = statement_params.get(
|
|
634
|
+
PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT
|
|
635
|
+
)
|
|
636
|
+
if str(result_format_val).upper() == "ARROW":
|
|
637
|
+
self.check_can_use_arrow_resultset()
|
|
638
|
+
elif result_format_val is None:
|
|
639
|
+
statement_params[PARAMETER_PYTHON_CONNECTOR_QUERY_RESULT_FORMAT] = (
|
|
640
|
+
"JSON"
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
self._sequence_counter = self._connection._next_sequence_counter()
|
|
644
|
+
|
|
645
|
+
# If requestId is contained in statement parameters, use it to set request id. Verify here it is a valid uuid4
|
|
646
|
+
# identifier.
|
|
647
|
+
if (
|
|
648
|
+
statement_params is not None
|
|
649
|
+
and REQUEST_ID_STATEMENT_PARAM_NAME in statement_params
|
|
650
|
+
):
|
|
651
|
+
request_id = statement_params[REQUEST_ID_STATEMENT_PARAM_NAME]
|
|
652
|
+
|
|
653
|
+
if not is_uuid4(request_id):
|
|
654
|
+
# uuid.UUID will throw an error if invalid, but we explicitly check and throw here.
|
|
655
|
+
raise ValueError(f"requestId {request_id} is not a valid UUID4.")
|
|
656
|
+
self._request_id = uuid.UUID(str(request_id), version=4)
|
|
657
|
+
|
|
658
|
+
# Create a (deep copy) and remove the statement param, there is no need to encode it as extra parameter
|
|
659
|
+
# one more time.
|
|
660
|
+
statement_params = statement_params.copy()
|
|
661
|
+
statement_params.pop(REQUEST_ID_STATEMENT_PARAM_NAME)
|
|
662
|
+
else:
|
|
663
|
+
# Generate UUID for query.
|
|
664
|
+
self._request_id = uuid.uuid4()
|
|
665
|
+
|
|
666
|
+
logger.debug(f"Request id: {self._request_id}")
|
|
667
|
+
|
|
668
|
+
logger.debug("running query [%s]", self._format_query_for_log(query))
|
|
669
|
+
if _is_put_get is not None:
|
|
670
|
+
# if told the query is PUT or GET, use the information
|
|
671
|
+
self._is_file_transfer = _is_put_get
|
|
672
|
+
else:
|
|
673
|
+
# or detect it.
|
|
674
|
+
self._is_file_transfer = get_file_transfer_type(query) is not None
|
|
675
|
+
logger.debug(
|
|
676
|
+
"is_file_transfer: %s",
|
|
677
|
+
self._is_file_transfer if self._is_file_transfer is not None else "None",
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
real_timeout = (
|
|
681
|
+
timeout if timeout and timeout > 0 else self._connection.network_timeout
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
if real_timeout is not None:
|
|
685
|
+
self._timebomb = _TrackedQueryCancellationTimer(
|
|
686
|
+
real_timeout, self.__cancel_query, [query]
|
|
687
|
+
)
|
|
688
|
+
self._timebomb.start()
|
|
689
|
+
logger.debug("started timebomb in %ss", real_timeout)
|
|
690
|
+
else:
|
|
691
|
+
self._timebomb = None
|
|
692
|
+
|
|
693
|
+
original_sigint = signal.getsignal(signal.SIGINT)
|
|
694
|
+
|
|
695
|
+
def interrupt_handler(*_): # pragma: no cover
|
|
696
|
+
try:
|
|
697
|
+
signal.signal(signal.SIGINT, exit_handler)
|
|
698
|
+
except (ValueError, TypeError):
|
|
699
|
+
# ignore failures
|
|
700
|
+
pass
|
|
701
|
+
try:
|
|
702
|
+
if self._timebomb is not None:
|
|
703
|
+
self._timebomb.cancel()
|
|
704
|
+
logger.debug("cancelled timebomb in finally")
|
|
705
|
+
self._timebomb = None
|
|
706
|
+
self.__cancel_query(query)
|
|
707
|
+
finally:
|
|
708
|
+
if original_sigint:
|
|
709
|
+
try:
|
|
710
|
+
signal.signal(signal.SIGINT, original_sigint)
|
|
711
|
+
except (ValueError, TypeError):
|
|
712
|
+
# ignore failures
|
|
713
|
+
pass
|
|
714
|
+
raise KeyboardInterrupt
|
|
715
|
+
|
|
716
|
+
try:
|
|
717
|
+
if not original_sigint == exit_handler:
|
|
718
|
+
signal.signal(signal.SIGINT, interrupt_handler)
|
|
719
|
+
except ValueError: # pragma: no cover
|
|
720
|
+
logger.debug(
|
|
721
|
+
"Failed to set SIGINT handler. " "Not in main thread. Ignored..."
|
|
722
|
+
)
|
|
723
|
+
ret: dict[str, Any] = {"data": {}}
|
|
724
|
+
try:
|
|
725
|
+
ret = self._connection.cmd_query(
|
|
726
|
+
query,
|
|
727
|
+
self._sequence_counter,
|
|
728
|
+
self._request_id,
|
|
729
|
+
binding_params=binding_params,
|
|
730
|
+
binding_stage=binding_stage,
|
|
731
|
+
is_file_transfer=bool(self._is_file_transfer),
|
|
732
|
+
statement_params=statement_params,
|
|
733
|
+
is_internal=is_internal,
|
|
734
|
+
describe_only=describe_only,
|
|
735
|
+
_no_results=_no_results,
|
|
736
|
+
_no_retry=_no_retry,
|
|
737
|
+
timeout=real_timeout,
|
|
738
|
+
dataframe_ast=dataframe_ast,
|
|
739
|
+
)
|
|
740
|
+
finally:
|
|
741
|
+
try:
|
|
742
|
+
if original_sigint:
|
|
743
|
+
signal.signal(signal.SIGINT, original_sigint)
|
|
744
|
+
except (ValueError, TypeError): # pragma: no cover
|
|
745
|
+
logger.debug(
|
|
746
|
+
"Failed to reset SIGINT handler. Not in main " "thread. Ignored..."
|
|
747
|
+
)
|
|
748
|
+
if self._timebomb is not None:
|
|
749
|
+
self._timebomb.cancel()
|
|
750
|
+
logger.debug("cancelled timebomb in finally")
|
|
751
|
+
|
|
752
|
+
if "data" in ret and "parameters" in ret["data"]:
|
|
753
|
+
parameters = ret["data"].get("parameters", list())
|
|
754
|
+
# Set session parameters for cursor object
|
|
755
|
+
for kv in parameters:
|
|
756
|
+
if "TIMESTAMP_OUTPUT_FORMAT" in kv["name"]:
|
|
757
|
+
self._timestamp_output_format = kv["value"]
|
|
758
|
+
elif "TIMESTAMP_NTZ_OUTPUT_FORMAT" in kv["name"]:
|
|
759
|
+
self._timestamp_ntz_output_format = kv["value"]
|
|
760
|
+
elif "TIMESTAMP_LTZ_OUTPUT_FORMAT" in kv["name"]:
|
|
761
|
+
self._timestamp_ltz_output_format = kv["value"]
|
|
762
|
+
elif "TIMESTAMP_TZ_OUTPUT_FORMAT" in kv["name"]:
|
|
763
|
+
self._timestamp_tz_output_format = kv["value"]
|
|
764
|
+
elif "DATE_OUTPUT_FORMAT" in kv["name"]:
|
|
765
|
+
self._date_output_format = kv["value"]
|
|
766
|
+
elif "TIME_OUTPUT_FORMAT" in kv["name"]:
|
|
767
|
+
self._time_output_format = kv["value"]
|
|
768
|
+
elif "TIMEZONE" in kv["name"]:
|
|
769
|
+
self._timezone = kv["value"]
|
|
770
|
+
elif "BINARY_OUTPUT_FORMAT" in kv["name"]:
|
|
771
|
+
self._binary_output_format = kv["value"]
|
|
772
|
+
# Set session parameters for connection object
|
|
773
|
+
self._connection._update_parameters(
|
|
774
|
+
{p["name"]: p["value"] for p in parameters}
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
self.query = query
|
|
778
|
+
self._sequence_counter = -1
|
|
779
|
+
return ret
|
|
780
|
+
|
|
781
|
+
def _preprocess_pyformat_query(
|
|
782
|
+
self,
|
|
783
|
+
command: str,
|
|
784
|
+
params: Sequence[Any] | dict[Any, Any] | None = None,
|
|
785
|
+
) -> str:
|
|
786
|
+
# pyformat/format paramstyle
|
|
787
|
+
# client side binding
|
|
788
|
+
processed_params = self._connection._process_params_pyformat(params, self)
|
|
789
|
+
# SNOW-513061 collect telemetry for empty sequence usage before we make the breaking change announcement
|
|
790
|
+
if params is not None and len(params) == 0:
|
|
791
|
+
self._log_telemetry_job_data(
|
|
792
|
+
TelemetryField.EMPTY_SEQ_INTERPOLATION,
|
|
793
|
+
(
|
|
794
|
+
TelemetryData.TRUE
|
|
795
|
+
if self.connection._interpolate_empty_sequences
|
|
796
|
+
else TelemetryData.FALSE
|
|
797
|
+
),
|
|
798
|
+
)
|
|
799
|
+
if logger.getEffectiveLevel() <= logging.DEBUG:
|
|
800
|
+
logger.debug(
|
|
801
|
+
f"binding: [{self._format_query_for_log(command)}] "
|
|
802
|
+
f"with input=[{params}], "
|
|
803
|
+
f"processed=[{processed_params}]",
|
|
804
|
+
)
|
|
805
|
+
if (
|
|
806
|
+
self.connection._interpolate_empty_sequences
|
|
807
|
+
and processed_params is not None
|
|
808
|
+
) or (
|
|
809
|
+
not self.connection._interpolate_empty_sequences
|
|
810
|
+
and len(processed_params) > 0
|
|
811
|
+
):
|
|
812
|
+
query = command % processed_params
|
|
813
|
+
else:
|
|
814
|
+
query = command
|
|
815
|
+
return query
|
|
816
|
+
|
|
817
|
+
@overload
|
|
818
|
+
def execute(
|
|
819
|
+
self,
|
|
820
|
+
command: str,
|
|
821
|
+
params: Sequence[Any] | dict[Any, Any] | None = None,
|
|
822
|
+
_bind_stage: str | None = None,
|
|
823
|
+
timeout: int | None = None,
|
|
824
|
+
_exec_async: bool = False,
|
|
825
|
+
_no_retry: bool = False,
|
|
826
|
+
_do_reset: bool = True,
|
|
827
|
+
_put_callback: SnowflakeProgressPercentage = None,
|
|
828
|
+
_put_azure_callback: SnowflakeProgressPercentage = None,
|
|
829
|
+
_put_callback_output_stream: IO[str] = sys.stdout,
|
|
830
|
+
_get_callback: SnowflakeProgressPercentage = None,
|
|
831
|
+
_get_azure_callback: SnowflakeProgressPercentage = None,
|
|
832
|
+
_get_callback_output_stream: IO[str] = sys.stdout,
|
|
833
|
+
_show_progress_bar: bool = True,
|
|
834
|
+
_statement_params: dict[str, str] | None = None,
|
|
835
|
+
_is_internal: bool = False,
|
|
836
|
+
_describe_only: bool = False,
|
|
837
|
+
_no_results: Literal[False] = False,
|
|
838
|
+
_is_put_get: bool | None = None,
|
|
839
|
+
_raise_put_get_error: bool = True,
|
|
840
|
+
_force_put_overwrite: bool = False,
|
|
841
|
+
_skip_upload_on_content_match: bool = False,
|
|
842
|
+
file_stream: IO[bytes] | None = None,
|
|
843
|
+
num_statements: int | None = None,
|
|
844
|
+
_dataframe_ast: str | None = None,
|
|
845
|
+
) -> Self | None: ...
|
|
846
|
+
|
|
847
|
+
@overload
|
|
848
|
+
def execute(
|
|
849
|
+
self,
|
|
850
|
+
command: str,
|
|
851
|
+
params: Sequence[Any] | dict[Any, Any] | None = None,
|
|
852
|
+
_bind_stage: str | None = None,
|
|
853
|
+
timeout: int | None = None,
|
|
854
|
+
_exec_async: bool = False,
|
|
855
|
+
_no_retry: bool = False,
|
|
856
|
+
_do_reset: bool = True,
|
|
857
|
+
_put_callback: SnowflakeProgressPercentage = None,
|
|
858
|
+
_put_azure_callback: SnowflakeProgressPercentage = None,
|
|
859
|
+
_put_callback_output_stream: IO[str] = sys.stdout,
|
|
860
|
+
_get_callback: SnowflakeProgressPercentage = None,
|
|
861
|
+
_get_azure_callback: SnowflakeProgressPercentage = None,
|
|
862
|
+
_get_callback_output_stream: IO[str] = sys.stdout,
|
|
863
|
+
_show_progress_bar: bool = True,
|
|
864
|
+
_statement_params: dict[str, str] | None = None,
|
|
865
|
+
_is_internal: bool = False,
|
|
866
|
+
_describe_only: bool = False,
|
|
867
|
+
_no_results: Literal[True] = True,
|
|
868
|
+
_is_put_get: bool | None = None,
|
|
869
|
+
_raise_put_get_error: bool = True,
|
|
870
|
+
_force_put_overwrite: bool = False,
|
|
871
|
+
_skip_upload_on_content_match: bool = False,
|
|
872
|
+
file_stream: IO[bytes] | None = None,
|
|
873
|
+
num_statements: int | None = None,
|
|
874
|
+
_dataframe_ast: str | None = None,
|
|
875
|
+
) -> dict[str, Any] | None: ...
|
|
876
|
+
|
|
877
|
+
def execute(
|
|
878
|
+
self,
|
|
879
|
+
command: str,
|
|
880
|
+
params: Sequence[Any] | dict[Any, Any] | None = None,
|
|
881
|
+
_bind_stage: str | None = None,
|
|
882
|
+
timeout: int | None = None,
|
|
883
|
+
_exec_async: bool = False,
|
|
884
|
+
_no_retry: bool = False,
|
|
885
|
+
_do_reset: bool = True,
|
|
886
|
+
_put_callback: SnowflakeProgressPercentage = None,
|
|
887
|
+
_put_azure_callback: SnowflakeProgressPercentage = None,
|
|
888
|
+
_put_callback_output_stream: IO[str] = sys.stdout,
|
|
889
|
+
_get_callback: SnowflakeProgressPercentage = None,
|
|
890
|
+
_get_azure_callback: SnowflakeProgressPercentage = None,
|
|
891
|
+
_get_callback_output_stream: IO[str] = sys.stdout,
|
|
892
|
+
_show_progress_bar: bool = True,
|
|
893
|
+
_statement_params: dict[str, str] | None = None,
|
|
894
|
+
_is_internal: bool = False,
|
|
895
|
+
_describe_only: bool = False,
|
|
896
|
+
_no_results: bool = False,
|
|
897
|
+
_is_put_get: bool | None = None,
|
|
898
|
+
_raise_put_get_error: bool = True,
|
|
899
|
+
_force_put_overwrite: bool = False,
|
|
900
|
+
_skip_upload_on_content_match: bool = False,
|
|
901
|
+
file_stream: IO[bytes] | None = None,
|
|
902
|
+
num_statements: int | None = None,
|
|
903
|
+
_force_qmark_paramstyle: bool = False,
|
|
904
|
+
_dataframe_ast: str | None = None,
|
|
905
|
+
) -> Self | dict[str, Any] | None:
|
|
906
|
+
"""Executes a command/query.
|
|
907
|
+
|
|
908
|
+
Args:
|
|
909
|
+
command: The SQL command to be executed.
|
|
910
|
+
params: Parameters to be bound into the SQL statement.
|
|
911
|
+
_bind_stage: Path in temporary stage where binding parameters are uploaded as CSV files.
|
|
912
|
+
timeout: Number of seconds after which to abort the query.
|
|
913
|
+
_exec_async: Whether to execute this query asynchronously.
|
|
914
|
+
_no_retry: Whether or not to retry on known errors.
|
|
915
|
+
_do_reset: Whether or not the result set needs to be reset before executing query.
|
|
916
|
+
_put_callback: Function to which PUT command should call back to.
|
|
917
|
+
_put_azure_callback: Function to which an Azure PUT command should call back to.
|
|
918
|
+
_put_callback_output_stream: The output stream a PUT command's callback should report on.
|
|
919
|
+
_get_callback: Function to which GET command should call back to.
|
|
920
|
+
_get_azure_callback: Function to which an Azure GET command should call back to.
|
|
921
|
+
_get_callback_output_stream: The output stream a GET command's callback should report on.
|
|
922
|
+
_show_progress_bar: Whether or not to show progress bar.
|
|
923
|
+
_statement_params: Extra information that should be sent to Snowflake with query. This dict will not be
|
|
924
|
+
modified by the connector.
|
|
925
|
+
_is_internal: This flag indicates whether the query is issued internally by the connector.
|
|
926
|
+
_describe_only: If true, the query will not be executed but return the schema/description of this query.
|
|
927
|
+
_no_results: This flag tells the back-end to not return the result, just fire the query and return the
|
|
928
|
+
response returned by Snowflake's server.
|
|
929
|
+
_use_ijson: This flag doesn't do anything as ijson support has ended.
|
|
930
|
+
_is_put_get: Force decision of this SQL query being a PUT, or GET command. This is detected otherwise.
|
|
931
|
+
_raise_put_get_error: Whether to raise PUT and GET errors.
|
|
932
|
+
_force_put_overwrite: If the SQL query is a PUT, then this flag can force overwriting of an already
|
|
933
|
+
existing file on stage.
|
|
934
|
+
_skip_upload_on_content_match: If the SQL query is a PUT with overwrite enabled, then this flag will skip upload
|
|
935
|
+
if the file contents match to ease concurrent uploads.
|
|
936
|
+
file_stream: File-like object to be uploaded with PUT
|
|
937
|
+
num_statements: Query level parameter submitted in _statement_params constraining exact number of
|
|
938
|
+
statements being submitted (or 0 if submitting an uncounted number) when using a multi-statement query.
|
|
939
|
+
_force_qmark_paramstyle: Force the use of qmark paramstyle regardless of the connection's paramstyle.
|
|
940
|
+
_dataframe_ast: Base64-encoded dataframe request abstract syntax tree.
|
|
941
|
+
|
|
942
|
+
Returns:
|
|
943
|
+
The cursor itself, or None if some error happened, or the response returned
|
|
944
|
+
by Snowflake if the _no_results flag is on.
|
|
945
|
+
"""
|
|
946
|
+
if _exec_async:
|
|
947
|
+
_no_results = True
|
|
948
|
+
logger.debug("executing SQL/command")
|
|
949
|
+
if self.is_closed():
|
|
950
|
+
Error.errorhandler_wrapper(
|
|
951
|
+
self.connection,
|
|
952
|
+
self,
|
|
953
|
+
InterfaceError,
|
|
954
|
+
{"msg": "Cursor is closed in execute.", "errno": ER_CURSOR_IS_CLOSED},
|
|
955
|
+
)
|
|
956
|
+
|
|
957
|
+
if _do_reset:
|
|
958
|
+
self.reset()
|
|
959
|
+
command = command.strip(" \t\n\r") if command else ""
|
|
960
|
+
if not command:
|
|
961
|
+
if _dataframe_ast:
|
|
962
|
+
logger.debug("dataframe ast: [%s]", _dataframe_ast)
|
|
963
|
+
else:
|
|
964
|
+
logger.warning("execute: no query is given to execute")
|
|
965
|
+
return None
|
|
966
|
+
|
|
967
|
+
logger.debug("query: [%s]", self._format_query_for_log(command))
|
|
968
|
+
_statement_params = _statement_params or dict()
|
|
969
|
+
# If we need to add another parameter, please consider introducing a dict for all extra params
|
|
970
|
+
# See discussion in https://github.com/snowflakedb/snowflake-connector-python/pull/1524#discussion_r1174061775
|
|
971
|
+
if num_statements is not None:
|
|
972
|
+
_statement_params = {
|
|
973
|
+
**_statement_params,
|
|
974
|
+
"MULTI_STATEMENT_COUNT": num_statements,
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
kwargs: dict[str, Any] = {
|
|
978
|
+
"timeout": timeout,
|
|
979
|
+
"statement_params": _statement_params,
|
|
980
|
+
"is_internal": _is_internal,
|
|
981
|
+
"describe_only": _describe_only,
|
|
982
|
+
"_no_results": _no_results,
|
|
983
|
+
"_is_put_get": _is_put_get,
|
|
984
|
+
"_no_retry": _no_retry,
|
|
985
|
+
"dataframe_ast": _dataframe_ast,
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if self._connection.is_pyformat and not _force_qmark_paramstyle:
|
|
989
|
+
query = self._preprocess_pyformat_query(command, params)
|
|
990
|
+
else:
|
|
991
|
+
# qmark and numeric paramstyle
|
|
992
|
+
query = command
|
|
993
|
+
if _bind_stage:
|
|
994
|
+
kwargs["binding_stage"] = _bind_stage
|
|
995
|
+
else:
|
|
996
|
+
if params is not None and not isinstance(params, (list, tuple)):
|
|
997
|
+
errorvalue = {
|
|
998
|
+
"msg": f"Binding parameters must be a list: {params}",
|
|
999
|
+
"errno": ER_FAILED_PROCESSING_PYFORMAT,
|
|
1000
|
+
}
|
|
1001
|
+
Error.errorhandler_wrapper(
|
|
1002
|
+
self.connection, self, ProgrammingError, errorvalue
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
kwargs["binding_params"] = self._connection._process_params_qmarks(
|
|
1006
|
+
params, self
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
m = DESC_TABLE_RE.match(query)
|
|
1010
|
+
if m:
|
|
1011
|
+
query1 = f"describe table {m.group(1)}"
|
|
1012
|
+
logger.debug(
|
|
1013
|
+
"query was rewritten: org=%s, new=%s",
|
|
1014
|
+
" ".join(line.strip() for line in query.split("\n")),
|
|
1015
|
+
query1,
|
|
1016
|
+
)
|
|
1017
|
+
query = query1
|
|
1018
|
+
|
|
1019
|
+
ret = self._execute_helper(query, **kwargs)
|
|
1020
|
+
self._sfqid = (
|
|
1021
|
+
ret["data"]["queryId"]
|
|
1022
|
+
if "data" in ret and "queryId" in ret["data"]
|
|
1023
|
+
else None
|
|
1024
|
+
)
|
|
1025
|
+
logger.debug(f"sfqid: {self.sfqid}")
|
|
1026
|
+
self._sqlstate = (
|
|
1027
|
+
ret["data"]["sqlState"]
|
|
1028
|
+
if "data" in ret and "sqlState" in ret["data"]
|
|
1029
|
+
else None
|
|
1030
|
+
)
|
|
1031
|
+
logger.debug("query execution done")
|
|
1032
|
+
|
|
1033
|
+
self._first_chunk_time = get_time_millis()
|
|
1034
|
+
|
|
1035
|
+
# if server gives a send time, log the time it took to arrive
|
|
1036
|
+
if "data" in ret and "sendResultTime" in ret["data"]:
|
|
1037
|
+
time_consume_first_result = (
|
|
1038
|
+
self._first_chunk_time - ret["data"]["sendResultTime"]
|
|
1039
|
+
)
|
|
1040
|
+
self._log_telemetry_job_data(
|
|
1041
|
+
TelemetryField.TIME_CONSUME_FIRST_RESULT, time_consume_first_result
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
if ret["success"]:
|
|
1045
|
+
logger.debug("SUCCESS")
|
|
1046
|
+
data = ret["data"]
|
|
1047
|
+
|
|
1048
|
+
for m in self.ALTER_SESSION_RE.finditer(query):
|
|
1049
|
+
# session parameters
|
|
1050
|
+
param = m.group(1).upper()
|
|
1051
|
+
value = m.group(2)
|
|
1052
|
+
self._connection.converter.set_parameter(param, value)
|
|
1053
|
+
|
|
1054
|
+
if "resultIds" in data:
|
|
1055
|
+
self._init_multi_statement_results(data)
|
|
1056
|
+
return self
|
|
1057
|
+
else:
|
|
1058
|
+
self.multi_statement_savedIds = []
|
|
1059
|
+
|
|
1060
|
+
self._is_file_transfer = "command" in data and data["command"] in (
|
|
1061
|
+
"UPLOAD",
|
|
1062
|
+
"DOWNLOAD",
|
|
1063
|
+
)
|
|
1064
|
+
logger.debug("PUT OR GET: %s", self.is_file_transfer)
|
|
1065
|
+
if self.is_file_transfer:
|
|
1066
|
+
from .file_transfer_agent import SnowflakeFileTransferAgent
|
|
1067
|
+
|
|
1068
|
+
# Decide whether to use the old, or new code path
|
|
1069
|
+
sf_file_transfer_agent = SnowflakeFileTransferAgent(
|
|
1070
|
+
self,
|
|
1071
|
+
query,
|
|
1072
|
+
ret,
|
|
1073
|
+
put_callback=_put_callback,
|
|
1074
|
+
put_azure_callback=_put_azure_callback,
|
|
1075
|
+
put_callback_output_stream=_put_callback_output_stream,
|
|
1076
|
+
get_callback=_get_callback,
|
|
1077
|
+
get_azure_callback=_get_azure_callback,
|
|
1078
|
+
get_callback_output_stream=_get_callback_output_stream,
|
|
1079
|
+
show_progress_bar=_show_progress_bar,
|
|
1080
|
+
raise_put_get_error=_raise_put_get_error,
|
|
1081
|
+
force_put_overwrite=_force_put_overwrite
|
|
1082
|
+
or data.get("overwrite", False),
|
|
1083
|
+
skip_upload_on_content_match=_skip_upload_on_content_match,
|
|
1084
|
+
source_from_stream=file_stream,
|
|
1085
|
+
multipart_threshold=data.get("threshold"),
|
|
1086
|
+
use_s3_regional_url=self._connection.enable_stage_s3_privatelink_for_us_east_1,
|
|
1087
|
+
iobound_tpe_limit=self._connection.iobound_tpe_limit,
|
|
1088
|
+
unsafe_file_write=self._connection.unsafe_file_write,
|
|
1089
|
+
)
|
|
1090
|
+
sf_file_transfer_agent.execute()
|
|
1091
|
+
data = sf_file_transfer_agent.result()
|
|
1092
|
+
self._total_rowcount = len(data["rowset"]) if "rowset" in data else -1
|
|
1093
|
+
|
|
1094
|
+
if _exec_async:
|
|
1095
|
+
self.connection._async_sfqids[self._sfqid] = None
|
|
1096
|
+
if _no_results:
|
|
1097
|
+
self._total_rowcount = (
|
|
1098
|
+
ret["data"]["total"]
|
|
1099
|
+
if "data" in ret and "total" in ret["data"]
|
|
1100
|
+
else -1
|
|
1101
|
+
)
|
|
1102
|
+
return data
|
|
1103
|
+
self._init_result_and_meta(data)
|
|
1104
|
+
else:
|
|
1105
|
+
self._total_rowcount = (
|
|
1106
|
+
ret["data"]["total"] if "data" in ret and "total" in ret["data"] else -1
|
|
1107
|
+
)
|
|
1108
|
+
logger.debug(ret)
|
|
1109
|
+
err = ret["message"]
|
|
1110
|
+
code = ret.get("code", -1)
|
|
1111
|
+
if (
|
|
1112
|
+
self._timebomb
|
|
1113
|
+
and self._timebomb.executed
|
|
1114
|
+
and "SQL execution canceled" in err
|
|
1115
|
+
):
|
|
1116
|
+
# Modify the error message only if the server error response indicates the query was canceled.
|
|
1117
|
+
# If the error occurs before the cancellation request reaches the backend
|
|
1118
|
+
# (e.g., due to a very short timeout), we retain the original error message
|
|
1119
|
+
# as the query might have encountered an issue prior to cancellation.
|
|
1120
|
+
err = (
|
|
1121
|
+
f"SQL execution was cancelled by the client due to a timeout. "
|
|
1122
|
+
f"Error message received from the server: {err}"
|
|
1123
|
+
)
|
|
1124
|
+
if "data" in ret:
|
|
1125
|
+
err += ret["data"].get("errorMessage", "")
|
|
1126
|
+
errvalue = {
|
|
1127
|
+
"msg": err,
|
|
1128
|
+
"errno": int(code),
|
|
1129
|
+
"sqlstate": self._sqlstate,
|
|
1130
|
+
"sfqid": self._sfqid,
|
|
1131
|
+
"query": query,
|
|
1132
|
+
}
|
|
1133
|
+
is_integrity_error = (
|
|
1134
|
+
code == "100072"
|
|
1135
|
+
) # NULL result in a non-nullable column
|
|
1136
|
+
error_class = IntegrityError if is_integrity_error else ProgrammingError
|
|
1137
|
+
Error.errorhandler_wrapper(self.connection, self, error_class, errvalue)
|
|
1138
|
+
return self
|
|
1139
|
+
|
|
1140
|
+
def execute_async(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
1141
|
+
"""Convenience function to execute a query without waiting for results (asynchronously).
|
|
1142
|
+
|
|
1143
|
+
This function takes the same arguments as execute, please refer to that function
|
|
1144
|
+
for documentation. Please note that PUT and GET statements are not supported by this method.
|
|
1145
|
+
"""
|
|
1146
|
+
kwargs["_exec_async"] = True
|
|
1147
|
+
return self.execute(*args, **kwargs)
|
|
1148
|
+
|
|
1149
|
+
def describe(self, *args: Any, **kwargs: Any) -> list[ResultMetadata]:
|
|
1150
|
+
"""Obtain the schema of the result without executing the query.
|
|
1151
|
+
|
|
1152
|
+
This function takes the same arguments as execute, please refer to that function
|
|
1153
|
+
for documentation.
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
The schema of the result.
|
|
1157
|
+
"""
|
|
1158
|
+
kwargs["_describe_only"] = kwargs["_is_internal"] = True
|
|
1159
|
+
self.execute(*args, **kwargs)
|
|
1160
|
+
|
|
1161
|
+
if self._description is None:
|
|
1162
|
+
return None
|
|
1163
|
+
return [meta._to_result_metadata_v1() for meta in self._description]
|
|
1164
|
+
|
|
1165
|
+
def _describe_internal(self, *args: Any, **kwargs: Any) -> list[ResultMetadataV2]:
|
|
1166
|
+
"""Obtain the schema of the result without executing the query.
|
|
1167
|
+
|
|
1168
|
+
This function takes the same arguments as execute, please refer to that function
|
|
1169
|
+
for documentation.
|
|
1170
|
+
|
|
1171
|
+
This function is for internal use only
|
|
1172
|
+
|
|
1173
|
+
Returns:
|
|
1174
|
+
The schema of the result, in the new result metadata format.
|
|
1175
|
+
"""
|
|
1176
|
+
kwargs["_describe_only"] = kwargs["_is_internal"] = True
|
|
1177
|
+
self.execute(*args, **kwargs)
|
|
1178
|
+
return self._description
|
|
1179
|
+
|
|
1180
|
+
def _format_query_for_log(self, query: str) -> str:
|
|
1181
|
+
return self._connection._format_query_for_log(query)
|
|
1182
|
+
|
|
1183
|
+
def _is_dml(self, data: dict[Any, Any]) -> bool:
|
|
1184
|
+
return (
|
|
1185
|
+
"statementTypeId" in data
|
|
1186
|
+
and int(data["statementTypeId"]) in STATEMENT_TYPE_ID_DML_SET
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1189
|
+
def _init_result_and_meta(self, data: dict[Any, Any]) -> None:
|
|
1190
|
+
is_dml = self._is_dml(data)
|
|
1191
|
+
self._query_result_format = data.get("queryResultFormat", "json")
|
|
1192
|
+
logger.debug("Query result format: %s", self._query_result_format)
|
|
1193
|
+
|
|
1194
|
+
if self._total_rowcount == -1 and not is_dml and data.get("total") is not None:
|
|
1195
|
+
self._total_rowcount = data["total"]
|
|
1196
|
+
|
|
1197
|
+
self._description: list[ResultMetadataV2] = [
|
|
1198
|
+
ResultMetadataV2.from_column(col) for col in data["rowtype"]
|
|
1199
|
+
]
|
|
1200
|
+
|
|
1201
|
+
result_chunks = create_batches_from_response(
|
|
1202
|
+
self, self._query_result_format, data, self._description
|
|
1203
|
+
)
|
|
1204
|
+
|
|
1205
|
+
if not (is_dml or self.is_file_transfer):
|
|
1206
|
+
logger.debug(
|
|
1207
|
+
"Number of results in first chunk: %s", result_chunks[0].rowcount
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
self._result_set = ResultSet(
|
|
1211
|
+
self,
|
|
1212
|
+
result_chunks,
|
|
1213
|
+
self._connection.client_fetch_threads
|
|
1214
|
+
or self._connection.client_prefetch_threads,
|
|
1215
|
+
self._connection.client_fetch_use_mp,
|
|
1216
|
+
)
|
|
1217
|
+
self._rownumber = -1
|
|
1218
|
+
self._result_state = ResultState.VALID
|
|
1219
|
+
|
|
1220
|
+
# don't update the row count when the result is returned from `describe` method
|
|
1221
|
+
if is_dml and "rowset" in data and len(data["rowset"]) > 0:
|
|
1222
|
+
updated_rows = 0
|
|
1223
|
+
for idx, desc in enumerate(self._description):
|
|
1224
|
+
if desc.name in (
|
|
1225
|
+
"number of rows updated",
|
|
1226
|
+
"number of multi-joined rows updated",
|
|
1227
|
+
"number of rows deleted",
|
|
1228
|
+
) or desc.name.startswith("number of rows inserted"):
|
|
1229
|
+
updated_rows += int(data["rowset"][0][idx])
|
|
1230
|
+
if self._total_rowcount == -1:
|
|
1231
|
+
self._total_rowcount = updated_rows
|
|
1232
|
+
else:
|
|
1233
|
+
self._total_rowcount += updated_rows
|
|
1234
|
+
|
|
1235
|
+
def _init_multi_statement_results(self, data: dict) -> None:
|
|
1236
|
+
self._log_telemetry_job_data(TelemetryField.MULTI_STATEMENT, TelemetryData.TRUE)
|
|
1237
|
+
self.multi_statement_savedIds = data["resultIds"].split(",")
|
|
1238
|
+
self._multi_statement_resultIds = collections.deque(
|
|
1239
|
+
self.multi_statement_savedIds
|
|
1240
|
+
)
|
|
1241
|
+
if self._is_file_transfer:
|
|
1242
|
+
Error.errorhandler_wrapper(
|
|
1243
|
+
self.connection,
|
|
1244
|
+
self,
|
|
1245
|
+
ProgrammingError,
|
|
1246
|
+
{
|
|
1247
|
+
"msg": "PUT/GET commands are not supported for multi-statement queries and cannot be executed.",
|
|
1248
|
+
"errno": ER_INVALID_VALUE,
|
|
1249
|
+
},
|
|
1250
|
+
)
|
|
1251
|
+
self.nextset()
|
|
1252
|
+
|
|
1253
|
+
def check_can_use_arrow_resultset(self) -> None:
|
|
1254
|
+
global CAN_USE_ARROW_RESULT_FORMAT
|
|
1255
|
+
|
|
1256
|
+
if not CAN_USE_ARROW_RESULT_FORMAT:
|
|
1257
|
+
if self._connection.application == "SnowSQL":
|
|
1258
|
+
msg = "Currently SnowSQL doesn't support the result set in Apache Arrow format."
|
|
1259
|
+
errno = ER_NO_PYARROW_SNOWSQL
|
|
1260
|
+
else:
|
|
1261
|
+
msg = "The result set in Apache Arrow format is not supported for the platform."
|
|
1262
|
+
errno = ER_NO_ARROW_RESULT
|
|
1263
|
+
|
|
1264
|
+
Error.errorhandler_wrapper(
|
|
1265
|
+
self.connection,
|
|
1266
|
+
self,
|
|
1267
|
+
ProgrammingError,
|
|
1268
|
+
{
|
|
1269
|
+
"msg": msg,
|
|
1270
|
+
"errno": errno,
|
|
1271
|
+
},
|
|
1272
|
+
)
|
|
1273
|
+
|
|
1274
|
+
def check_can_use_pandas(self) -> None:
|
|
1275
|
+
if not installed_pandas:
|
|
1276
|
+
msg = (
|
|
1277
|
+
"Optional dependency: 'pandas' is not installed, please see the following link for install "
|
|
1278
|
+
"instructions: https://docs.snowflake.com/en/user-guide/python-connector-pandas.html#installation"
|
|
1279
|
+
)
|
|
1280
|
+
errno = ER_NO_PYARROW
|
|
1281
|
+
|
|
1282
|
+
Error.errorhandler_wrapper(
|
|
1283
|
+
self.connection,
|
|
1284
|
+
self,
|
|
1285
|
+
ProgrammingError,
|
|
1286
|
+
{
|
|
1287
|
+
"msg": msg,
|
|
1288
|
+
"errno": errno,
|
|
1289
|
+
},
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
def query_result(self, qid: str) -> SnowflakeCursor:
|
|
1293
|
+
"""Query the result of a previously executed query."""
|
|
1294
|
+
url = f"/queries/{qid}/result"
|
|
1295
|
+
ret = self._connection.rest.request(url=url, method="get")
|
|
1296
|
+
self._sfqid = (
|
|
1297
|
+
ret["data"]["queryId"]
|
|
1298
|
+
if "data" in ret and "queryId" in ret["data"]
|
|
1299
|
+
else None
|
|
1300
|
+
)
|
|
1301
|
+
self._sqlstate = (
|
|
1302
|
+
ret["data"]["sqlState"]
|
|
1303
|
+
if "data" in ret and "sqlState" in ret["data"]
|
|
1304
|
+
else None
|
|
1305
|
+
)
|
|
1306
|
+
logger.debug("sfqid=%s", self._sfqid)
|
|
1307
|
+
|
|
1308
|
+
if ret.get("success"):
|
|
1309
|
+
data = ret.get("data")
|
|
1310
|
+
self._init_result_and_meta(data)
|
|
1311
|
+
else:
|
|
1312
|
+
logger.debug("failed")
|
|
1313
|
+
logger.debug(ret)
|
|
1314
|
+
err = ret["message"]
|
|
1315
|
+
code = ret.get("code", -1)
|
|
1316
|
+
if "data" in ret:
|
|
1317
|
+
err += ret["data"].get("errorMessage", "")
|
|
1318
|
+
errvalue = {
|
|
1319
|
+
"msg": err,
|
|
1320
|
+
"errno": int(code),
|
|
1321
|
+
"sqlstate": self._sqlstate,
|
|
1322
|
+
"sfqid": self._sfqid,
|
|
1323
|
+
}
|
|
1324
|
+
Error.errorhandler_wrapper(
|
|
1325
|
+
self.connection, self, ProgrammingError, errvalue
|
|
1326
|
+
)
|
|
1327
|
+
return self
|
|
1328
|
+
|
|
1329
|
+
def fetch_arrow_batches(self) -> Iterator[Table]:
|
|
1330
|
+
self.check_can_use_arrow_resultset()
|
|
1331
|
+
if self._prefetch_hook is not None:
|
|
1332
|
+
self._prefetch_hook()
|
|
1333
|
+
if self._query_result_format != "arrow":
|
|
1334
|
+
raise NotSupportedError
|
|
1335
|
+
self._log_telemetry_job_data(
|
|
1336
|
+
TelemetryField.ARROW_FETCH_BATCHES, TelemetryData.TRUE
|
|
1337
|
+
)
|
|
1338
|
+
return self._result_set._fetch_arrow_batches()
|
|
1339
|
+
|
|
1340
|
+
@overload
|
|
1341
|
+
def fetch_arrow_all(self, force_return_table: Literal[False]) -> Table | None: ...
|
|
1342
|
+
|
|
1343
|
+
@overload
|
|
1344
|
+
def fetch_arrow_all(self, force_return_table: Literal[True]) -> Table: ...
|
|
1345
|
+
|
|
1346
|
+
def fetch_arrow_all(self, force_return_table: bool = False) -> Table | None:
|
|
1347
|
+
"""
|
|
1348
|
+
Args:
|
|
1349
|
+
force_return_table: Set to True so that when the query returns zero rows,
|
|
1350
|
+
an empty pyarrow table will be returned with schema using the highest bit length for each column.
|
|
1351
|
+
Default value is False in which case None is returned in case of zero rows.
|
|
1352
|
+
"""
|
|
1353
|
+
self.check_can_use_arrow_resultset()
|
|
1354
|
+
|
|
1355
|
+
if self._prefetch_hook is not None:
|
|
1356
|
+
self._prefetch_hook()
|
|
1357
|
+
if self._query_result_format != "arrow":
|
|
1358
|
+
raise NotSupportedError
|
|
1359
|
+
self._log_telemetry_job_data(TelemetryField.ARROW_FETCH_ALL, TelemetryData.TRUE)
|
|
1360
|
+
return self._result_set._fetch_arrow_all(force_return_table=force_return_table)
|
|
1361
|
+
|
|
1362
|
+
def fetch_pandas_batches(self, **kwargs: Any) -> Iterator[DataFrame]:
|
|
1363
|
+
"""Fetches a single Arrow Table."""
|
|
1364
|
+
self.check_can_use_pandas()
|
|
1365
|
+
if self._prefetch_hook is not None:
|
|
1366
|
+
self._prefetch_hook()
|
|
1367
|
+
if self._query_result_format != "arrow":
|
|
1368
|
+
raise NotSupportedError
|
|
1369
|
+
self._log_telemetry_job_data(
|
|
1370
|
+
TelemetryField.PANDAS_FETCH_BATCHES, TelemetryData.TRUE
|
|
1371
|
+
)
|
|
1372
|
+
return self._result_set._fetch_pandas_batches(**kwargs)
|
|
1373
|
+
|
|
1374
|
+
def fetch_pandas_all(self, **kwargs: Any) -> DataFrame:
|
|
1375
|
+
"""
|
|
1376
|
+
Fetch Pandas dataframes in batches, where 'batch' refers to Snowflake Chunk.
|
|
1377
|
+
|
|
1378
|
+
Returns:
|
|
1379
|
+
A pandas dataframe containing the cursors query results.
|
|
1380
|
+
|
|
1381
|
+
Note:
|
|
1382
|
+
Timestamp types that are nested in structured types may not be accurately represented
|
|
1383
|
+
due to a limitation in the pyarrow to pandas conversion.
|
|
1384
|
+
See here for more information: https://github.com/apache/arrow/issues/4116
|
|
1385
|
+
"""
|
|
1386
|
+
self.check_can_use_pandas()
|
|
1387
|
+
if self._prefetch_hook is not None:
|
|
1388
|
+
self._prefetch_hook()
|
|
1389
|
+
if self._query_result_format != "arrow":
|
|
1390
|
+
raise NotSupportedError
|
|
1391
|
+
self._log_telemetry_job_data(
|
|
1392
|
+
TelemetryField.PANDAS_FETCH_ALL, TelemetryData.TRUE
|
|
1393
|
+
)
|
|
1394
|
+
return self._result_set._fetch_pandas_all(**kwargs)
|
|
1395
|
+
|
|
1396
|
+
def abort_query(self, qid: str) -> bool:
|
|
1397
|
+
url = f"/queries/{qid}/abort-request"
|
|
1398
|
+
ret = self._connection.rest.request(url=url, method="post")
|
|
1399
|
+
return ret.get("success")
|
|
1400
|
+
|
|
1401
|
+
def executemany(
|
|
1402
|
+
self,
|
|
1403
|
+
command: str,
|
|
1404
|
+
seqparams: Sequence[Any] | dict[str, Any],
|
|
1405
|
+
**kwargs: Any,
|
|
1406
|
+
) -> SnowflakeCursor:
|
|
1407
|
+
"""Executes a command/query with the given set of parameters sequentially."""
|
|
1408
|
+
logger.debug("executing many SQLs/commands")
|
|
1409
|
+
command = command.strip(" \t\n\r") if command else None
|
|
1410
|
+
|
|
1411
|
+
if not seqparams:
|
|
1412
|
+
logger.warning(
|
|
1413
|
+
"No parameters provided to executemany, returning without doing anything."
|
|
1414
|
+
)
|
|
1415
|
+
return self
|
|
1416
|
+
|
|
1417
|
+
if self.INSERT_SQL_RE.match(command) and (
|
|
1418
|
+
"num_statements" not in kwargs or kwargs.get("num_statements") == 1
|
|
1419
|
+
):
|
|
1420
|
+
if self._connection.is_pyformat:
|
|
1421
|
+
# TODO(SNOW-940692) - utilize multi-statement instead of rewriting the query and
|
|
1422
|
+
# accumulate results to mock the result from a single insert statement as formatted below
|
|
1423
|
+
logger.debug("rewriting INSERT query")
|
|
1424
|
+
command_wo_comments = re.sub(self.COMMENT_SQL_RE, "", command)
|
|
1425
|
+
m = self.INSERT_SQL_VALUES_RE.match(command_wo_comments)
|
|
1426
|
+
if not m:
|
|
1427
|
+
Error.errorhandler_wrapper(
|
|
1428
|
+
self.connection,
|
|
1429
|
+
self,
|
|
1430
|
+
InterfaceError,
|
|
1431
|
+
{
|
|
1432
|
+
"msg": "Failed to rewrite multi-row insert",
|
|
1433
|
+
"errno": ER_FAILED_TO_REWRITE_MULTI_ROW_INSERT,
|
|
1434
|
+
},
|
|
1435
|
+
)
|
|
1436
|
+
|
|
1437
|
+
fmt = m.group(1)
|
|
1438
|
+
values = []
|
|
1439
|
+
for param in seqparams:
|
|
1440
|
+
logger.debug(f"parameter: {param}")
|
|
1441
|
+
values.append(
|
|
1442
|
+
fmt % self._connection._process_params_pyformat(param, self)
|
|
1443
|
+
)
|
|
1444
|
+
command = command.replace(fmt, ",".join(values), 1)
|
|
1445
|
+
self.execute(command, **kwargs)
|
|
1446
|
+
return self
|
|
1447
|
+
else:
|
|
1448
|
+
logger.debug("bulk insert")
|
|
1449
|
+
# sanity check
|
|
1450
|
+
row_size = len(seqparams[0])
|
|
1451
|
+
for row in seqparams:
|
|
1452
|
+
if len(row) != row_size:
|
|
1453
|
+
error_value = {
|
|
1454
|
+
"msg": f"Bulk data size don't match. expected: {row_size}, "
|
|
1455
|
+
f"got: {len(row)}, command: {command}",
|
|
1456
|
+
"errno": ER_INVALID_VALUE,
|
|
1457
|
+
}
|
|
1458
|
+
Error.errorhandler_wrapper(
|
|
1459
|
+
self.connection, self, InterfaceError, error_value
|
|
1460
|
+
)
|
|
1461
|
+
return self
|
|
1462
|
+
bind_size = len(seqparams) * row_size
|
|
1463
|
+
bind_stage = None
|
|
1464
|
+
if (
|
|
1465
|
+
bind_size
|
|
1466
|
+
>= self.connection._session_parameters[
|
|
1467
|
+
"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD"
|
|
1468
|
+
]
|
|
1469
|
+
> 0
|
|
1470
|
+
):
|
|
1471
|
+
# bind stage optimization
|
|
1472
|
+
try:
|
|
1473
|
+
rows = self.connection._write_params_to_byte_rows(seqparams)
|
|
1474
|
+
bind_uploader = BindUploadAgent(self, rows)
|
|
1475
|
+
bind_uploader.upload()
|
|
1476
|
+
bind_stage = bind_uploader.stage_path
|
|
1477
|
+
except BindUploadError:
|
|
1478
|
+
logger.debug(
|
|
1479
|
+
"Failed to upload binds to stage, sending binds to "
|
|
1480
|
+
"Snowflake instead."
|
|
1481
|
+
)
|
|
1482
|
+
binding_param = (
|
|
1483
|
+
None if bind_stage else list(map(list, zip(*seqparams)))
|
|
1484
|
+
) # transpose
|
|
1485
|
+
self.execute(
|
|
1486
|
+
command, params=binding_param, _bind_stage=bind_stage, **kwargs
|
|
1487
|
+
)
|
|
1488
|
+
return self
|
|
1489
|
+
|
|
1490
|
+
self.reset()
|
|
1491
|
+
if "num_statements" not in kwargs:
|
|
1492
|
+
# fall back to old driver behavior when the user does not provide the parameter to enable
|
|
1493
|
+
# multi-statement optimizations for executemany
|
|
1494
|
+
for param in seqparams:
|
|
1495
|
+
self.execute(command, params=param, _do_reset=False, **kwargs)
|
|
1496
|
+
else:
|
|
1497
|
+
if re.search(";/s*$", command) is None:
|
|
1498
|
+
command = command + "; "
|
|
1499
|
+
if self._connection.is_pyformat and not kwargs.get(
|
|
1500
|
+
"_force_qmark_paramstyle", False
|
|
1501
|
+
):
|
|
1502
|
+
processed_queries = [
|
|
1503
|
+
self._preprocess_pyformat_query(command, params)
|
|
1504
|
+
for params in seqparams
|
|
1505
|
+
]
|
|
1506
|
+
query = "".join(processed_queries)
|
|
1507
|
+
params = None
|
|
1508
|
+
else:
|
|
1509
|
+
query = command * len(seqparams)
|
|
1510
|
+
params = [param for parameters in seqparams for param in parameters]
|
|
1511
|
+
|
|
1512
|
+
kwargs["num_statements"]: int = kwargs.get("num_statements") * len(
|
|
1513
|
+
seqparams
|
|
1514
|
+
)
|
|
1515
|
+
|
|
1516
|
+
self.execute(query, params, _do_reset=False, **kwargs)
|
|
1517
|
+
|
|
1518
|
+
return self
|
|
1519
|
+
|
|
1520
|
+
def fetchone(self) -> dict | tuple | None:
|
|
1521
|
+
"""Fetches one row."""
|
|
1522
|
+
if self._prefetch_hook is not None:
|
|
1523
|
+
self._prefetch_hook()
|
|
1524
|
+
if self._result is None and self._result_set is not None:
|
|
1525
|
+
self._result = iter(self._result_set)
|
|
1526
|
+
self._result_state = ResultState.VALID
|
|
1527
|
+
|
|
1528
|
+
try:
|
|
1529
|
+
_next = next(self._result, None)
|
|
1530
|
+
if isinstance(_next, Exception):
|
|
1531
|
+
Error.errorhandler_wrapper_from_ready_exception(
|
|
1532
|
+
self._connection,
|
|
1533
|
+
self,
|
|
1534
|
+
_next,
|
|
1535
|
+
)
|
|
1536
|
+
if _next is not None:
|
|
1537
|
+
self._rownumber += 1
|
|
1538
|
+
return _next
|
|
1539
|
+
except TypeError as err:
|
|
1540
|
+
if self._result_state == ResultState.DEFAULT:
|
|
1541
|
+
raise err
|
|
1542
|
+
else:
|
|
1543
|
+
return None
|
|
1544
|
+
|
|
1545
|
+
def fetchmany(self, size: int | None = None) -> list[tuple] | list[dict]:
|
|
1546
|
+
"""Fetches the number of specified rows."""
|
|
1547
|
+
if size is None:
|
|
1548
|
+
size = self.arraysize
|
|
1549
|
+
|
|
1550
|
+
if size < 0:
|
|
1551
|
+
errorvalue = {
|
|
1552
|
+
"msg": (
|
|
1553
|
+
"The number of rows is not zero or " "positive number: {}"
|
|
1554
|
+
).format(size),
|
|
1555
|
+
"errno": ER_NOT_POSITIVE_SIZE,
|
|
1556
|
+
}
|
|
1557
|
+
Error.errorhandler_wrapper(
|
|
1558
|
+
self.connection, self, ProgrammingError, errorvalue
|
|
1559
|
+
)
|
|
1560
|
+
ret = []
|
|
1561
|
+
while size > 0:
|
|
1562
|
+
row = self.fetchone()
|
|
1563
|
+
if row is None:
|
|
1564
|
+
break
|
|
1565
|
+
ret.append(row)
|
|
1566
|
+
if size is not None:
|
|
1567
|
+
size -= 1
|
|
1568
|
+
|
|
1569
|
+
return ret
|
|
1570
|
+
|
|
1571
|
+
def fetchall(self) -> list[tuple] | list[dict]:
|
|
1572
|
+
"""Fetches all of the results."""
|
|
1573
|
+
ret = []
|
|
1574
|
+
while True:
|
|
1575
|
+
row = self.fetchone()
|
|
1576
|
+
if row is None:
|
|
1577
|
+
break
|
|
1578
|
+
ret.append(row)
|
|
1579
|
+
return ret
|
|
1580
|
+
|
|
1581
|
+
def nextset(self) -> SnowflakeCursor | None:
|
|
1582
|
+
"""
|
|
1583
|
+
Fetches the next set of results if the previously executed query was multi-statement so that subsequent calls
|
|
1584
|
+
to any of the fetch*() methods will return rows from the next query's set of results. Returns None if no more
|
|
1585
|
+
query results are available.
|
|
1586
|
+
"""
|
|
1587
|
+
if self._prefetch_hook is not None:
|
|
1588
|
+
self._prefetch_hook()
|
|
1589
|
+
self.reset()
|
|
1590
|
+
if self._multi_statement_resultIds:
|
|
1591
|
+
self.query_result(self._multi_statement_resultIds[0])
|
|
1592
|
+
logger.info(
|
|
1593
|
+
f"Retrieved results for query ID: {self._multi_statement_resultIds.popleft()}"
|
|
1594
|
+
)
|
|
1595
|
+
return self
|
|
1596
|
+
|
|
1597
|
+
return None
|
|
1598
|
+
|
|
1599
|
+
def setinputsizes(self, _) -> None:
|
|
1600
|
+
"""Not supported."""
|
|
1601
|
+
logger.debug("nop")
|
|
1602
|
+
|
|
1603
|
+
def setoutputsize(self, _, column=None) -> None:
|
|
1604
|
+
"""Not supported."""
|
|
1605
|
+
del column
|
|
1606
|
+
logger.debug("nop")
|
|
1607
|
+
|
|
1608
|
+
def scroll(self, value, mode: str = "relative") -> None:
|
|
1609
|
+
Error.errorhandler_wrapper(
|
|
1610
|
+
self.connection,
|
|
1611
|
+
self,
|
|
1612
|
+
NotSupportedError,
|
|
1613
|
+
{
|
|
1614
|
+
"msg": "scroll is not supported.",
|
|
1615
|
+
"errno": ER_UNSUPPORTED_METHOD,
|
|
1616
|
+
"sqlstate": SQLSTATE_FEATURE_NOT_SUPPORTED,
|
|
1617
|
+
},
|
|
1618
|
+
)
|
|
1619
|
+
|
|
1620
|
+
def reset(self, closing: bool = False) -> None:
|
|
1621
|
+
"""Resets the result set."""
|
|
1622
|
+
# SNOW-647539: Do not erase the rowcount
|
|
1623
|
+
# information when closing the cursor
|
|
1624
|
+
if not closing:
|
|
1625
|
+
self._total_rowcount = -1
|
|
1626
|
+
if self._result_state != ResultState.DEFAULT:
|
|
1627
|
+
self._result_state = ResultState.RESET
|
|
1628
|
+
if self._result is not None:
|
|
1629
|
+
self._result = None
|
|
1630
|
+
if self._inner_cursor is not None:
|
|
1631
|
+
self._inner_cursor.reset(closing=closing)
|
|
1632
|
+
self._result = None
|
|
1633
|
+
self._inner_cursor = None
|
|
1634
|
+
self._prefetch_hook = None
|
|
1635
|
+
if not self.connection._reuse_results:
|
|
1636
|
+
self._result_set = None
|
|
1637
|
+
|
|
1638
|
+
def __iter__(self) -> Iterator[dict] | Iterator[tuple]:
|
|
1639
|
+
"""Iteration over the result set."""
|
|
1640
|
+
while True:
|
|
1641
|
+
_next = self.fetchone()
|
|
1642
|
+
if _next is None:
|
|
1643
|
+
break
|
|
1644
|
+
yield _next
|
|
1645
|
+
|
|
1646
|
+
def __cancel_query(self, query) -> None:
|
|
1647
|
+
if self._sequence_counter >= 0 and not self.is_closed():
|
|
1648
|
+
logger.debug("canceled. %s, request_id: %s", query, self._request_id)
|
|
1649
|
+
with self._lock_canceling:
|
|
1650
|
+
self._connection._cancel_query(query, self._request_id)
|
|
1651
|
+
|
|
1652
|
+
def _log_telemetry_job_data(
|
|
1653
|
+
self, telemetry_field: TelemetryField, value: Any
|
|
1654
|
+
) -> None:
|
|
1655
|
+
"""Builds an instance of TelemetryData with the given field and logs it."""
|
|
1656
|
+
ts = get_time_millis()
|
|
1657
|
+
try:
|
|
1658
|
+
self._connection._log_telemetry(
|
|
1659
|
+
TelemetryData.from_telemetry_data_dict(
|
|
1660
|
+
from_dict={
|
|
1661
|
+
TelemetryField.KEY_TYPE.value: telemetry_field.value,
|
|
1662
|
+
TelemetryField.KEY_SFQID.value: self._sfqid,
|
|
1663
|
+
TelemetryField.KEY_VALUE.value: value,
|
|
1664
|
+
},
|
|
1665
|
+
timestamp=ts,
|
|
1666
|
+
connection=self._connection,
|
|
1667
|
+
)
|
|
1668
|
+
)
|
|
1669
|
+
except AttributeError:
|
|
1670
|
+
logger.warning(
|
|
1671
|
+
"Cursor failed to log to telemetry. Connection object may be None.",
|
|
1672
|
+
exc_info=True,
|
|
1673
|
+
)
|
|
1674
|
+
|
|
1675
|
+
def __enter__(self) -> Self:
|
|
1676
|
+
"""Context manager."""
|
|
1677
|
+
return self
|
|
1678
|
+
|
|
1679
|
+
def __exit__(
|
|
1680
|
+
self,
|
|
1681
|
+
exc_type: type[BaseException] | None,
|
|
1682
|
+
exc_val: BaseException | None,
|
|
1683
|
+
exc_tb: TracebackType | None,
|
|
1684
|
+
) -> None:
|
|
1685
|
+
"""Context manager with commit or rollback."""
|
|
1686
|
+
self.close()
|
|
1687
|
+
|
|
1688
|
+
def get_results_from_sfqid(self, sfqid: str) -> None:
|
|
1689
|
+
"""Gets the results from previously ran query. This methods differs from ``SnowflakeCursor.query_result``
|
|
1690
|
+
in that it monitors the ``sfqid`` until it is no longer running, and then retrieves the results.
|
|
1691
|
+
"""
|
|
1692
|
+
|
|
1693
|
+
def wait_until_ready() -> None:
|
|
1694
|
+
"""Makes sure query has finished executing and once it has retrieves results."""
|
|
1695
|
+
no_data_counter = 0
|
|
1696
|
+
retry_pattern_pos = 0
|
|
1697
|
+
while True:
|
|
1698
|
+
status, status_resp = self.connection._get_query_status(sfqid)
|
|
1699
|
+
self.connection._cache_query_status(sfqid, status)
|
|
1700
|
+
if not self.connection.is_still_running(status):
|
|
1701
|
+
break
|
|
1702
|
+
if status == QueryStatus.NO_DATA: # pragma: no cover
|
|
1703
|
+
no_data_counter += 1
|
|
1704
|
+
if no_data_counter > ASYNC_NO_DATA_MAX_RETRY:
|
|
1705
|
+
raise DatabaseError(
|
|
1706
|
+
"Cannot retrieve data on the status of this query. No information returned "
|
|
1707
|
+
"from server for query '{}'"
|
|
1708
|
+
)
|
|
1709
|
+
time.sleep(
|
|
1710
|
+
0.5 * ASYNC_RETRY_PATTERN[retry_pattern_pos]
|
|
1711
|
+
) # Same wait as JDBC
|
|
1712
|
+
# If we can advance in ASYNC_RETRY_PATTERN then do so
|
|
1713
|
+
if retry_pattern_pos < (len(ASYNC_RETRY_PATTERN) - 1):
|
|
1714
|
+
retry_pattern_pos += 1
|
|
1715
|
+
if status != QueryStatus.SUCCESS:
|
|
1716
|
+
logger.info(f"Status of query '{sfqid}' is {status.name}")
|
|
1717
|
+
self.connection._process_error_query_status(
|
|
1718
|
+
sfqid,
|
|
1719
|
+
status_resp,
|
|
1720
|
+
error_message=f"Status of query '{sfqid}' is {status.name}, results are unavailable",
|
|
1721
|
+
error_cls=DatabaseError,
|
|
1722
|
+
)
|
|
1723
|
+
self._inner_cursor.execute(f"select * from table(result_scan('{sfqid}'))")
|
|
1724
|
+
self._result = self._inner_cursor._result
|
|
1725
|
+
self._query_result_format = self._inner_cursor._query_result_format
|
|
1726
|
+
self._total_rowcount = self._inner_cursor._total_rowcount
|
|
1727
|
+
self._description = self._inner_cursor._description
|
|
1728
|
+
self._result_set = self._inner_cursor._result_set
|
|
1729
|
+
self._result_state = ResultState.VALID
|
|
1730
|
+
self._rownumber = 0
|
|
1731
|
+
# Unset this function, so that we don't block anymore
|
|
1732
|
+
self._prefetch_hook = None
|
|
1733
|
+
|
|
1734
|
+
if (
|
|
1735
|
+
self._inner_cursor._total_rowcount == 1
|
|
1736
|
+
and self._inner_cursor.fetchall()
|
|
1737
|
+
== [("Multiple statements executed successfully.",)]
|
|
1738
|
+
):
|
|
1739
|
+
url = f"/queries/{sfqid}/result"
|
|
1740
|
+
ret = self._connection.rest.request(url=url, method="get")
|
|
1741
|
+
if "data" in ret and "resultIds" in ret["data"]:
|
|
1742
|
+
self._init_multi_statement_results(ret["data"])
|
|
1743
|
+
|
|
1744
|
+
self.connection.get_query_status_throw_if_error(
|
|
1745
|
+
sfqid
|
|
1746
|
+
) # Trigger an exception if query failed
|
|
1747
|
+
klass = self.__class__
|
|
1748
|
+
self._inner_cursor = klass(self.connection)
|
|
1749
|
+
self._sfqid = sfqid
|
|
1750
|
+
self._prefetch_hook = wait_until_ready
|
|
1751
|
+
|
|
1752
|
+
def get_result_batches(self) -> list[ResultBatch] | None:
|
|
1753
|
+
"""Get the previously executed query's ``ResultBatch`` s if available.
|
|
1754
|
+
|
|
1755
|
+
If they are unavailable, in case nothing has been executed yet None will
|
|
1756
|
+
be returned.
|
|
1757
|
+
|
|
1758
|
+
For a detailed description of ``ResultBatch`` s please see the docstring of:
|
|
1759
|
+
``snowflake.connector.result_batches.ResultBatch``
|
|
1760
|
+
"""
|
|
1761
|
+
if self._result_set is None:
|
|
1762
|
+
return None
|
|
1763
|
+
self._log_telemetry_job_data(
|
|
1764
|
+
TelemetryField.GET_PARTITIONS_USED, TelemetryData.TRUE
|
|
1765
|
+
)
|
|
1766
|
+
return self._result_set.batches
|
|
1767
|
+
|
|
1768
|
+
def _download(
|
|
1769
|
+
self,
|
|
1770
|
+
stage_location: str,
|
|
1771
|
+
target_directory: str,
|
|
1772
|
+
options: dict[str, Any],
|
|
1773
|
+
_do_reset: bool = True,
|
|
1774
|
+
) -> None:
|
|
1775
|
+
"""Downloads from the stage location to the target directory.
|
|
1776
|
+
|
|
1777
|
+
Args:
|
|
1778
|
+
stage_location (str): The location of the stage to download from.
|
|
1779
|
+
target_directory (str): The destination directory to download into.
|
|
1780
|
+
options (dict[str, Any]): The download options.
|
|
1781
|
+
_do_reset (bool, optional): Whether to reset the cursor before
|
|
1782
|
+
downloading, by default we will reset the cursor.
|
|
1783
|
+
"""
|
|
1784
|
+
from .file_transfer_agent import SnowflakeFileTransferAgent
|
|
1785
|
+
|
|
1786
|
+
if _do_reset:
|
|
1787
|
+
self.reset()
|
|
1788
|
+
|
|
1789
|
+
# Interpret the file operation.
|
|
1790
|
+
ret = self.connection._file_operation_parser.parse_file_operation(
|
|
1791
|
+
stage_location=stage_location,
|
|
1792
|
+
local_file_name=None,
|
|
1793
|
+
target_directory=target_directory,
|
|
1794
|
+
command_type=CMD_TYPE_DOWNLOAD,
|
|
1795
|
+
options=options,
|
|
1796
|
+
)
|
|
1797
|
+
|
|
1798
|
+
# Execute the file operation based on the interpretation above.
|
|
1799
|
+
file_transfer_agent = SnowflakeFileTransferAgent(
|
|
1800
|
+
self,
|
|
1801
|
+
"", # empty command because it is triggered by directly calling this util not by a SQL query
|
|
1802
|
+
ret,
|
|
1803
|
+
)
|
|
1804
|
+
file_transfer_agent.execute()
|
|
1805
|
+
self._init_result_and_meta(file_transfer_agent.result())
|
|
1806
|
+
|
|
1807
|
+
def _upload(
|
|
1808
|
+
self,
|
|
1809
|
+
local_file_name: str,
|
|
1810
|
+
stage_location: str,
|
|
1811
|
+
options: dict[str, Any],
|
|
1812
|
+
_do_reset: bool = True,
|
|
1813
|
+
) -> None:
|
|
1814
|
+
"""Uploads the local file to the stage location.
|
|
1815
|
+
|
|
1816
|
+
Args:
|
|
1817
|
+
local_file_name (str): The local file to be uploaded.
|
|
1818
|
+
stage_location (str): The stage location to upload the local file to.
|
|
1819
|
+
options (dict[str, Any]): The upload options.
|
|
1820
|
+
_do_reset (bool, optional): Whether to reset the cursor before
|
|
1821
|
+
uploading, by default we will reset the cursor.
|
|
1822
|
+
"""
|
|
1823
|
+
from .file_transfer_agent import SnowflakeFileTransferAgent
|
|
1824
|
+
|
|
1825
|
+
if _do_reset:
|
|
1826
|
+
self.reset()
|
|
1827
|
+
|
|
1828
|
+
# Interpret the file operation.
|
|
1829
|
+
ret = self.connection._file_operation_parser.parse_file_operation(
|
|
1830
|
+
stage_location=stage_location,
|
|
1831
|
+
local_file_name=local_file_name,
|
|
1832
|
+
target_directory=None,
|
|
1833
|
+
command_type=CMD_TYPE_UPLOAD,
|
|
1834
|
+
options=options,
|
|
1835
|
+
)
|
|
1836
|
+
|
|
1837
|
+
# Execute the file operation based on the interpretation above.
|
|
1838
|
+
file_transfer_agent = SnowflakeFileTransferAgent(
|
|
1839
|
+
self,
|
|
1840
|
+
"", # empty command because it is triggered by directly calling this util not by a SQL query
|
|
1841
|
+
ret,
|
|
1842
|
+
force_put_overwrite=False, # _upload should respect user decision on overwriting
|
|
1843
|
+
)
|
|
1844
|
+
file_transfer_agent.execute()
|
|
1845
|
+
self._init_result_and_meta(file_transfer_agent.result())
|
|
1846
|
+
|
|
1847
|
+
def _download_stream(
|
|
1848
|
+
self, stage_location: str, decompress: bool = False
|
|
1849
|
+
) -> IO[bytes]:
|
|
1850
|
+
"""Downloads from the stage location as a stream.
|
|
1851
|
+
|
|
1852
|
+
Args:
|
|
1853
|
+
stage_location (str): The location of the stage to download from.
|
|
1854
|
+
decompress (bool, optional): Whether to decompress the file, by
|
|
1855
|
+
default we do not decompress.
|
|
1856
|
+
|
|
1857
|
+
Returns:
|
|
1858
|
+
IO[bytes]: A stream to read from.
|
|
1859
|
+
"""
|
|
1860
|
+
# Interpret the file operation.
|
|
1861
|
+
ret = self.connection._file_operation_parser.parse_file_operation(
|
|
1862
|
+
stage_location=stage_location,
|
|
1863
|
+
local_file_name=None,
|
|
1864
|
+
target_directory=None,
|
|
1865
|
+
command_type=CMD_TYPE_DOWNLOAD,
|
|
1866
|
+
options=None,
|
|
1867
|
+
has_source_from_stream=True,
|
|
1868
|
+
)
|
|
1869
|
+
|
|
1870
|
+
# Set up stream downloading based on the interpretation and return the stream for reading.
|
|
1871
|
+
return self.connection._stream_downloader.download_as_stream(ret, decompress)
|
|
1872
|
+
|
|
1873
|
+
def _upload_stream(
|
|
1874
|
+
self,
|
|
1875
|
+
input_stream: IO[bytes],
|
|
1876
|
+
stage_location: str,
|
|
1877
|
+
options: dict[str, Any],
|
|
1878
|
+
_do_reset: bool = True,
|
|
1879
|
+
) -> None:
|
|
1880
|
+
"""Uploads content in the input stream to the stage location.
|
|
1881
|
+
|
|
1882
|
+
Args:
|
|
1883
|
+
input_stream (IO[bytes]): A stream to read from.
|
|
1884
|
+
stage_location (str): The location of the stage to upload to.
|
|
1885
|
+
options (dict[str, Any]): The upload options.
|
|
1886
|
+
_do_reset (bool, optional): Whether to reset the cursor before
|
|
1887
|
+
uploading, by default we will reset the cursor.
|
|
1888
|
+
"""
|
|
1889
|
+
from .file_transfer_agent import SnowflakeFileTransferAgent
|
|
1890
|
+
|
|
1891
|
+
if _do_reset:
|
|
1892
|
+
self.reset()
|
|
1893
|
+
|
|
1894
|
+
# Interpret the file operation.
|
|
1895
|
+
ret = self.connection._file_operation_parser.parse_file_operation(
|
|
1896
|
+
stage_location=stage_location,
|
|
1897
|
+
local_file_name=None,
|
|
1898
|
+
target_directory=None,
|
|
1899
|
+
command_type=CMD_TYPE_UPLOAD,
|
|
1900
|
+
options=options,
|
|
1901
|
+
has_source_from_stream=input_stream,
|
|
1902
|
+
)
|
|
1903
|
+
|
|
1904
|
+
# Execute the file operation based on the interpretation above.
|
|
1905
|
+
file_transfer_agent = SnowflakeFileTransferAgent(
|
|
1906
|
+
self,
|
|
1907
|
+
"", # empty command because it is triggered by directly calling this util not by a SQL query
|
|
1908
|
+
ret,
|
|
1909
|
+
source_from_stream=input_stream,
|
|
1910
|
+
force_put_overwrite=False, # _upload_stream should respect user decision on overwriting
|
|
1911
|
+
)
|
|
1912
|
+
file_transfer_agent.execute()
|
|
1913
|
+
self._init_result_and_meta(file_transfer_agent.result())
|
|
1914
|
+
|
|
1915
|
+
|
|
1916
|
+
class DictCursor(SnowflakeCursor):
|
|
1917
|
+
"""Cursor returning results in a dictionary."""
|
|
1918
|
+
|
|
1919
|
+
def __init__(self, connection) -> None:
|
|
1920
|
+
super().__init__(
|
|
1921
|
+
connection,
|
|
1922
|
+
use_dict_result=True,
|
|
1923
|
+
)
|
|
1924
|
+
|
|
1925
|
+
|
|
1926
|
+
def __getattr__(name):
|
|
1927
|
+
if name == "NanoarrowUsage":
|
|
1928
|
+
warnings.warn(
|
|
1929
|
+
"snowflake.connector.cursor.NanoarrowUsage has been deprecated and will be removed in the future, "
|
|
1930
|
+
"please stop using the class.",
|
|
1931
|
+
DeprecationWarning,
|
|
1932
|
+
stacklevel=2,
|
|
1933
|
+
)
|
|
1934
|
+
return _NanoarrowUsage
|
|
1935
|
+
if name == "NANOARROW_USAGE":
|
|
1936
|
+
if "NANOARROW_USAGE" in os.environ:
|
|
1937
|
+
warnings.warn(
|
|
1938
|
+
"Environment variable NANOARROW_USAGE has been deprecated and will be removed in the future, "
|
|
1939
|
+
"please stop setting the variable.",
|
|
1940
|
+
DeprecationWarning,
|
|
1941
|
+
stacklevel=2,
|
|
1942
|
+
)
|
|
1943
|
+
|
|
1944
|
+
warnings.warn(
|
|
1945
|
+
"snowflake.connector.cursor.NANOARROW_USAGE has been deprecated and will be removed in the future, "
|
|
1946
|
+
"please stop using the variable.",
|
|
1947
|
+
DeprecationWarning,
|
|
1948
|
+
stacklevel=2,
|
|
1949
|
+
)
|
|
1950
|
+
return None
|
|
1951
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|