databricks-sql-connector 4.0.0b4__tar.gz → 4.0.1__tar.gz
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.
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/CHANGELOG.md +31 -2
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/PKG-INFO +6 -5
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/pyproject.toml +13 -8
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/__init__.py +1 -1
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/retry.py +32 -18
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/thrift_http_client.py +6 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/client.py +129 -6
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/thrift_backend.py +90 -10
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/utils.py +2 -11
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/LICENSE +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/README.md +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/__init__.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/__init__.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/auth.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/authenticators.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/endpoint.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/oauth.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/oauth_http_handler.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/cloudfetch/download_manager.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/cloudfetch/downloader.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/exc.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/experimental/__init__.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/experimental/oauth_persistence.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/parameters/__init__.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/parameters/native.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/parameters/py.typed +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/py.typed +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/thrift_api/TCLIService/TCLIService-remote +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/thrift_api/TCLIService/TCLIService.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/thrift_api/TCLIService/__init__.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/thrift_api/TCLIService/constants.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/thrift_api/TCLIService/ttypes.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/thrift_api/__init__.py +0 -0
- {databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/types.py +0 -0
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
-
# 4.0.
|
|
3
|
+
# 4.0.1 (2025-03-19)
|
|
4
|
+
|
|
5
|
+
- Support for multiple timestamp formats parsing (databricks/databricks-sql-python#533 by @jprakash-db)
|
|
6
|
+
- Rename `_user_agent_entry` in connect call to `user_agent_entry` to expose it as a public parameter. (databricks/databricks-sql-python#530 by @shivam2680)
|
|
7
|
+
- Fix: compatibility with urllib3 versions less than 2.x. (databricks/databricks-sql-python#526 by @shivam2680)
|
|
8
|
+
- Support for Python 3.13 and updated dependencies (databricks/databricks-sql-python#510 by @dhirschfeld and @dbaxa)
|
|
9
|
+
|
|
10
|
+
# 4.0.0 (2025-01-19)
|
|
4
11
|
|
|
5
12
|
- Split the connector into two separate packages: `databricks-sql-connector` and `databricks-sqlalchemy`. The `databricks-sql-connector` package contains the core functionality of the connector, while the `databricks-sqlalchemy` package contains the SQLAlchemy dialect for the connector.
|
|
6
|
-
- Pyarrow dependency is now optional in `databricks-sql-connector`. Users needing arrow are supposed to explicitly install pyarrow
|
|
13
|
+
- Pyarrow dependency is now optional in `databricks-sql-connector`. Users needing arrow are supposed to explicitly install pyarrow
|
|
14
|
+
|
|
15
|
+
# 3.7.3 (2025-03-28)
|
|
16
|
+
|
|
17
|
+
- Fix: Unable to poll small results in execute_async function (databricks/databricks-sql-python#515 by @jprakash-db)
|
|
18
|
+
- Updated log messages to show the status code and error messages of requests (databricks/databricks-sql-python#511 by @jprakash-db)
|
|
19
|
+
- Fix: Incorrect metadata was fetched in case of queries with the same alias (databricks/databricks-sql-python#505 by @jprakash-db)
|
|
20
|
+
|
|
21
|
+
# 3.7.2 (2025-01-31)
|
|
22
|
+
|
|
23
|
+
- Updated the retry_dela_max and retry_timeout (databricks/databricks-sql-python#497 by @jprakash-db)
|
|
24
|
+
|
|
25
|
+
# 3.7.1 (2025-01-07)
|
|
26
|
+
|
|
27
|
+
- Relaxed the number of Http retry attempts (databricks/databricks-sql-python#486 by @jprakash-db)
|
|
28
|
+
|
|
29
|
+
# 3.7.0 (2024-12-23)
|
|
30
|
+
|
|
31
|
+
- Fix: Incorrect number of rows fetched in inline results when fetching results with FETCH_NEXT orientation (databricks/databricks-sql-python#479 by @jprakash-db)
|
|
32
|
+
- Updated the doc to specify native parameters are not supported in PUT operation (databricks/databricks-sql-python#477 by @jprakash-db)
|
|
33
|
+
- Relax `pyarrow` and `numpy` pin (databricks/databricks-sql-python#452 by @arredond)
|
|
34
|
+
- Feature: Support for async execute has been added (databricks/databricks-sql-python#463 by @jprakash-db)
|
|
35
|
+
- Updated the HTTP retry logic to be similar to the other Databricks drivers (databricks/databricks-sql-python#467 by @jprakash-db)
|
|
7
36
|
|
|
8
37
|
# 3.6.0 (2024-10-25)
|
|
9
38
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: databricks-sql-connector
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.1
|
|
4
4
|
Summary: Databricks SQL Connector for Python
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: Databricks
|
|
@@ -15,12 +15,13 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Provides-Extra: pyarrow
|
|
17
17
|
Requires-Dist: lz4 (>=4.0.2,<5.0.0)
|
|
18
|
-
Requires-Dist: numpy (>=1.16.6,<2.0.0) ; python_version >= "3.8" and python_version < "3.11"
|
|
19
|
-
Requires-Dist: numpy (>=1.23.4,<2.0.0) ; python_version >= "3.11"
|
|
20
18
|
Requires-Dist: oauthlib (>=3.1.0,<4.0.0)
|
|
21
19
|
Requires-Dist: openpyxl (>=3.0.10,<4.0.0)
|
|
22
|
-
Requires-Dist: pandas (>=1.2.5,<2.3.0) ; python_version >= "3.8"
|
|
23
|
-
Requires-Dist:
|
|
20
|
+
Requires-Dist: pandas (>=1.2.5,<2.3.0) ; python_version >= "3.8" and python_version < "3.13"
|
|
21
|
+
Requires-Dist: pandas (>=2.2.3,<2.3.0) ; python_version >= "3.13"
|
|
22
|
+
Requires-Dist: pyarrow (>=14.0.1) ; (python_version >= "3.8" and python_version < "3.13") and (extra == "pyarrow")
|
|
23
|
+
Requires-Dist: pyarrow (>=18.0.0) ; (python_version >= "3.13") and (extra == "pyarrow")
|
|
24
|
+
Requires-Dist: python-dateutil (>=2.9.0,<3.0.0)
|
|
24
25
|
Requires-Dist: requests (>=2.18.1,<3.0.0)
|
|
25
26
|
Requires-Dist: thrift (>=0.16.0,<0.21.0)
|
|
26
27
|
Requires-Dist: urllib3 (>=1.26)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "databricks-sql-connector"
|
|
3
|
-
version = "4.0.
|
|
3
|
+
version = "4.0.1"
|
|
4
4
|
description = "Databricks SQL Connector for Python"
|
|
5
5
|
authors = ["Databricks <databricks-sql-connector-maintainers@databricks.com>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -12,18 +12,19 @@ include = ["CHANGELOG.md"]
|
|
|
12
12
|
python = "^3.8.0"
|
|
13
13
|
thrift = ">=0.16.0,<0.21.0"
|
|
14
14
|
pandas = [
|
|
15
|
-
{ version = ">=1.2.5,<2.3.0", python = ">=3.8" }
|
|
15
|
+
{ version = ">=1.2.5,<2.3.0", python = ">=3.8,<3.13" },
|
|
16
|
+
{ version = ">=2.2.3,<2.3.0", python = ">=3.13" }
|
|
16
17
|
]
|
|
17
18
|
lz4 = "^4.0.2"
|
|
18
19
|
requests = "^2.18.1"
|
|
19
20
|
oauthlib = "^3.1.0"
|
|
20
|
-
numpy = [
|
|
21
|
-
{ version = "^1.16.6", python = ">=3.8,<3.11" },
|
|
22
|
-
{ version = "^1.23.4", python = ">=3.11" },
|
|
23
|
-
]
|
|
24
21
|
openpyxl = "^3.0.10"
|
|
25
22
|
urllib3 = ">=1.26"
|
|
26
|
-
pyarrow =
|
|
23
|
+
pyarrow = [
|
|
24
|
+
{ version = ">=14.0.1", python = ">=3.8,<3.13", optional=true },
|
|
25
|
+
{ version = ">=18.0.0", python = ">=3.13", optional=true }
|
|
26
|
+
]
|
|
27
|
+
python-dateutil = "^2.9.0"
|
|
27
28
|
|
|
28
29
|
[tool.poetry.extras]
|
|
29
30
|
pyarrow = ["pyarrow"]
|
|
@@ -34,6 +35,10 @@ mypy = "^1.10.1"
|
|
|
34
35
|
pylint = ">=2.12.0"
|
|
35
36
|
black = "^22.3.0"
|
|
36
37
|
pytest-dotenv = "^0.5.2"
|
|
38
|
+
numpy = [
|
|
39
|
+
{ version = ">=1.16.6", python = ">=3.8,<3.11" },
|
|
40
|
+
{ version = ">=1.23.4", python = ">=3.11" },
|
|
41
|
+
]
|
|
37
42
|
|
|
38
43
|
[tool.poetry.urls]
|
|
39
44
|
"Homepage" = "https://github.com/databricks/databricks-sql-python"
|
|
@@ -56,4 +61,4 @@ minversion = "6.0"
|
|
|
56
61
|
log_cli = "false"
|
|
57
62
|
log_cli_level = "INFO"
|
|
58
63
|
testpaths = ["tests"]
|
|
59
|
-
env_files = ["test.env"]
|
|
64
|
+
env_files = ["test.env"]
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/retry.py
RENAMED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import random
|
|
2
3
|
import time
|
|
3
4
|
import typing
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from typing import List, Optional, Tuple, Union
|
|
6
7
|
|
|
8
|
+
import urllib3
|
|
9
|
+
|
|
7
10
|
# We only use this import for type hinting
|
|
8
11
|
try:
|
|
9
12
|
# If urllib3~=2.0 is installed
|
|
@@ -13,6 +16,8 @@ except ImportError:
|
|
|
13
16
|
from urllib3 import HTTPResponse as BaseHTTPResponse
|
|
14
17
|
from urllib3 import Retry
|
|
15
18
|
from urllib3.util.retry import RequestHistory
|
|
19
|
+
from packaging import version
|
|
20
|
+
|
|
16
21
|
|
|
17
22
|
from databricks.sql.exc import (
|
|
18
23
|
CursorAlreadyClosedError,
|
|
@@ -285,25 +290,32 @@ class DatabricksRetryPolicy(Retry):
|
|
|
285
290
|
"""
|
|
286
291
|
retry_after = self.get_retry_after(response)
|
|
287
292
|
if retry_after:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
self.
|
|
291
|
-
time.sleep(proposed_wait)
|
|
292
|
-
return True
|
|
293
|
+
proposed_wait = retry_after
|
|
294
|
+
else:
|
|
295
|
+
proposed_wait = self.get_backoff_time()
|
|
293
296
|
|
|
294
|
-
|
|
297
|
+
proposed_wait = max(proposed_wait, self.delay_max)
|
|
298
|
+
self.check_proposed_wait(proposed_wait)
|
|
299
|
+
logger.debug(f"Retrying after {proposed_wait} seconds")
|
|
300
|
+
time.sleep(proposed_wait)
|
|
301
|
+
return True
|
|
295
302
|
|
|
296
303
|
def get_backoff_time(self) -> float:
|
|
297
|
-
"""
|
|
304
|
+
"""
|
|
305
|
+
This method implements the exponential backoff algorithm to calculate the delay between retries.
|
|
298
306
|
|
|
299
307
|
Never returns a value larger than self.delay_max
|
|
300
308
|
A MaxRetryDurationError will be raised if the calculated backoff would exceed self.max_attempts_duration
|
|
301
309
|
|
|
302
|
-
|
|
303
|
-
in the previous unsuccessful request and `self.respect_retry_after_header` is True (which is always true)
|
|
310
|
+
:return:
|
|
304
311
|
"""
|
|
305
312
|
|
|
306
|
-
|
|
313
|
+
current_attempt = self.stop_after_attempts_count - int(self.total or 0)
|
|
314
|
+
proposed_backoff = (2**current_attempt) * self.delay_min
|
|
315
|
+
if version.parse(urllib3.__version__) >= version.parse("2.0.0"):
|
|
316
|
+
if self.backoff_jitter != 0.0:
|
|
317
|
+
proposed_backoff += random.random() * self.backoff_jitter
|
|
318
|
+
|
|
307
319
|
proposed_backoff = min(proposed_backoff, self.delay_max)
|
|
308
320
|
self.check_proposed_wait(proposed_backoff)
|
|
309
321
|
|
|
@@ -338,23 +350,24 @@ class DatabricksRetryPolicy(Retry):
|
|
|
338
350
|
if a retry would violate the configured policy.
|
|
339
351
|
"""
|
|
340
352
|
|
|
353
|
+
logger.info(f"Received status code {status_code} for {method} request")
|
|
354
|
+
|
|
341
355
|
# Request succeeded. Don't retry.
|
|
342
356
|
if status_code == 200:
|
|
343
357
|
return False, "200 codes are not retried"
|
|
344
358
|
|
|
345
359
|
if status_code == 401:
|
|
346
|
-
|
|
347
|
-
|
|
360
|
+
return (
|
|
361
|
+
False,
|
|
362
|
+
"Received 401 - UNAUTHORIZED. Confirm your authentication credentials.",
|
|
348
363
|
)
|
|
349
364
|
|
|
350
365
|
if status_code == 403:
|
|
351
|
-
|
|
352
|
-
"Received 403 - FORBIDDEN. Confirm your authentication credentials."
|
|
353
|
-
)
|
|
366
|
+
return False, "403 codes are not retried"
|
|
354
367
|
|
|
355
368
|
# Request failed and server said NotImplemented. This isn't recoverable. Don't retry.
|
|
356
369
|
if status_code == 501:
|
|
357
|
-
|
|
370
|
+
return False, "Received code 501 from server."
|
|
358
371
|
|
|
359
372
|
# Request failed and this method is not retryable. We only retry POST requests.
|
|
360
373
|
if not self._is_method_retryable(method):
|
|
@@ -393,8 +406,9 @@ class DatabricksRetryPolicy(Retry):
|
|
|
393
406
|
and status_code not in self.status_forcelist
|
|
394
407
|
and status_code not in self.force_dangerous_codes
|
|
395
408
|
):
|
|
396
|
-
|
|
397
|
-
|
|
409
|
+
return (
|
|
410
|
+
False,
|
|
411
|
+
"ExecuteStatement command can only be retried for codes 429 and 503",
|
|
398
412
|
)
|
|
399
413
|
|
|
400
414
|
# Request failed with a dangerous code, was an ExecuteStatement, but user forced retries for this
|
|
@@ -198,6 +198,12 @@ class THttpClient(thrift.transport.THttpClient.THttpClient):
|
|
|
198
198
|
self.message = self.__resp.reason
|
|
199
199
|
self.headers = self.__resp.headers
|
|
200
200
|
|
|
201
|
+
logger.info(
|
|
202
|
+
"HTTP Response with status code {}, message: {}".format(
|
|
203
|
+
self.code, self.message
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
201
207
|
@staticmethod
|
|
202
208
|
def basic_proxy_auth_headers(proxy):
|
|
203
209
|
if proxy is None or not proxy.username:
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/client.py
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import time
|
|
1
2
|
from typing import Dict, Tuple, List, Optional, Any, Union, Sequence
|
|
2
3
|
|
|
3
4
|
import pandas
|
|
@@ -47,6 +48,7 @@ from databricks.sql.experimental.oauth_persistence import OAuthPersistence
|
|
|
47
48
|
|
|
48
49
|
from databricks.sql.thrift_api.TCLIService.ttypes import (
|
|
49
50
|
TSparkParameter,
|
|
51
|
+
TOperationState,
|
|
50
52
|
)
|
|
51
53
|
|
|
52
54
|
|
|
@@ -120,6 +122,9 @@ class Connection:
|
|
|
120
122
|
port of the oauth redirect uri (localhost). This is required when custom oauth client_id
|
|
121
123
|
`oauth_client_id` is set
|
|
122
124
|
|
|
125
|
+
user_agent_entry: `str`, optional
|
|
126
|
+
A custom tag to append to the User-Agent header. This is typically used by partners to identify their applications.. If not specified, it will use the default user agent PyDatabricksSqlConnector
|
|
127
|
+
|
|
123
128
|
experimental_oauth_persistence: configures preferred storage for persisting oauth tokens.
|
|
124
129
|
This has to be a class implementing `OAuthPersistence`.
|
|
125
130
|
When `auth_type` is set to `databricks-oauth` or `azure-oauth` without persisting the oauth token in a
|
|
@@ -174,8 +179,6 @@ class Connection:
|
|
|
174
179
|
"""
|
|
175
180
|
|
|
176
181
|
# Internal arguments in **kwargs:
|
|
177
|
-
# _user_agent_entry
|
|
178
|
-
# Tag to add to User-Agent header. For use by partners.
|
|
179
182
|
# _use_cert_as_auth
|
|
180
183
|
# Use a TLS cert instead of a token
|
|
181
184
|
# _enable_ssl
|
|
@@ -225,12 +228,21 @@ class Connection:
|
|
|
225
228
|
server_hostname, **kwargs
|
|
226
229
|
)
|
|
227
230
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
+
user_agent_entry = kwargs.get("user_agent_entry")
|
|
232
|
+
if user_agent_entry is None:
|
|
233
|
+
user_agent_entry = kwargs.get("_user_agent_entry")
|
|
234
|
+
if user_agent_entry is not None:
|
|
235
|
+
logger.warning(
|
|
236
|
+
"[WARN] Parameter '_user_agent_entry' is deprecated; use 'user_agent_entry' instead. "
|
|
237
|
+
"This parameter will be removed in the upcoming releases."
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if user_agent_entry:
|
|
231
241
|
useragent_header = "{}/{} ({})".format(
|
|
232
|
-
USER_AGENT_NAME, __version__,
|
|
242
|
+
USER_AGENT_NAME, __version__, user_agent_entry
|
|
233
243
|
)
|
|
244
|
+
else:
|
|
245
|
+
useragent_header = "{}/{}".format(USER_AGENT_NAME, __version__)
|
|
234
246
|
|
|
235
247
|
base_headers = [("User-Agent", useragent_header)]
|
|
236
248
|
|
|
@@ -437,6 +449,8 @@ class Cursor:
|
|
|
437
449
|
self.escaper = ParamEscaper()
|
|
438
450
|
self.lastrowid = None
|
|
439
451
|
|
|
452
|
+
self.ASYNC_DEFAULT_POLLING_INTERVAL = 2
|
|
453
|
+
|
|
440
454
|
# The ideal return type for this method is perhaps Self, but that was not added until 3.11, and we support pre-3.11 pythons, currently.
|
|
441
455
|
def __enter__(self) -> "Cursor":
|
|
442
456
|
return self
|
|
@@ -740,6 +754,7 @@ class Cursor:
|
|
|
740
754
|
self,
|
|
741
755
|
operation: str,
|
|
742
756
|
parameters: Optional[TParameterCollection] = None,
|
|
757
|
+
enforce_embedded_schema_correctness=False,
|
|
743
758
|
) -> "Cursor":
|
|
744
759
|
"""
|
|
745
760
|
Execute a query and wait for execution to complete.
|
|
@@ -803,6 +818,8 @@ class Cursor:
|
|
|
803
818
|
cursor=self,
|
|
804
819
|
use_cloud_fetch=self.connection.use_cloud_fetch,
|
|
805
820
|
parameters=prepared_params,
|
|
821
|
+
async_op=False,
|
|
822
|
+
enforce_embedded_schema_correctness=enforce_embedded_schema_correctness,
|
|
806
823
|
)
|
|
807
824
|
self.active_result_set = ResultSet(
|
|
808
825
|
self.connection,
|
|
@@ -810,6 +827,7 @@ class Cursor:
|
|
|
810
827
|
self.thrift_backend,
|
|
811
828
|
self.buffer_size_bytes,
|
|
812
829
|
self.arraysize,
|
|
830
|
+
self.connection.use_cloud_fetch,
|
|
813
831
|
)
|
|
814
832
|
|
|
815
833
|
if execute_response.is_staging_operation:
|
|
@@ -819,6 +837,108 @@ class Cursor:
|
|
|
819
837
|
|
|
820
838
|
return self
|
|
821
839
|
|
|
840
|
+
def execute_async(
|
|
841
|
+
self,
|
|
842
|
+
operation: str,
|
|
843
|
+
parameters: Optional[TParameterCollection] = None,
|
|
844
|
+
enforce_embedded_schema_correctness=False,
|
|
845
|
+
) -> "Cursor":
|
|
846
|
+
"""
|
|
847
|
+
|
|
848
|
+
Execute a query and do not wait for it to complete and just move ahead
|
|
849
|
+
|
|
850
|
+
:param operation:
|
|
851
|
+
:param parameters:
|
|
852
|
+
:return:
|
|
853
|
+
"""
|
|
854
|
+
param_approach = self._determine_parameter_approach(parameters)
|
|
855
|
+
if param_approach == ParameterApproach.NONE:
|
|
856
|
+
prepared_params = NO_NATIVE_PARAMS
|
|
857
|
+
prepared_operation = operation
|
|
858
|
+
|
|
859
|
+
elif param_approach == ParameterApproach.INLINE:
|
|
860
|
+
prepared_operation, prepared_params = self._prepare_inline_parameters(
|
|
861
|
+
operation, parameters
|
|
862
|
+
)
|
|
863
|
+
elif param_approach == ParameterApproach.NATIVE:
|
|
864
|
+
normalized_parameters = self._normalize_tparametercollection(parameters)
|
|
865
|
+
param_structure = self._determine_parameter_structure(normalized_parameters)
|
|
866
|
+
transformed_operation = transform_paramstyle(
|
|
867
|
+
operation, normalized_parameters, param_structure
|
|
868
|
+
)
|
|
869
|
+
prepared_operation, prepared_params = self._prepare_native_parameters(
|
|
870
|
+
transformed_operation, normalized_parameters, param_structure
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
self._check_not_closed()
|
|
874
|
+
self._close_and_clear_active_result_set()
|
|
875
|
+
self.thrift_backend.execute_command(
|
|
876
|
+
operation=prepared_operation,
|
|
877
|
+
session_handle=self.connection._session_handle,
|
|
878
|
+
max_rows=self.arraysize,
|
|
879
|
+
max_bytes=self.buffer_size_bytes,
|
|
880
|
+
lz4_compression=self.connection.lz4_compression,
|
|
881
|
+
cursor=self,
|
|
882
|
+
use_cloud_fetch=self.connection.use_cloud_fetch,
|
|
883
|
+
parameters=prepared_params,
|
|
884
|
+
async_op=True,
|
|
885
|
+
enforce_embedded_schema_correctness=enforce_embedded_schema_correctness,
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
return self
|
|
889
|
+
|
|
890
|
+
def get_query_state(self) -> "TOperationState":
|
|
891
|
+
"""
|
|
892
|
+
Get the state of the async executing query or basically poll the status of the query
|
|
893
|
+
|
|
894
|
+
:return:
|
|
895
|
+
"""
|
|
896
|
+
self._check_not_closed()
|
|
897
|
+
return self.thrift_backend.get_query_state(self.active_op_handle)
|
|
898
|
+
|
|
899
|
+
def get_async_execution_result(self):
|
|
900
|
+
"""
|
|
901
|
+
|
|
902
|
+
Checks for the status of the async executing query and fetches the result if the query is finished
|
|
903
|
+
Otherwise it will keep polling the status of the query till there is a Not pending state
|
|
904
|
+
:return:
|
|
905
|
+
"""
|
|
906
|
+
self._check_not_closed()
|
|
907
|
+
|
|
908
|
+
def is_executing(operation_state) -> "bool":
|
|
909
|
+
return not operation_state or operation_state in [
|
|
910
|
+
ttypes.TOperationState.RUNNING_STATE,
|
|
911
|
+
ttypes.TOperationState.PENDING_STATE,
|
|
912
|
+
]
|
|
913
|
+
|
|
914
|
+
while is_executing(self.get_query_state()):
|
|
915
|
+
# Poll after some default time
|
|
916
|
+
time.sleep(self.ASYNC_DEFAULT_POLLING_INTERVAL)
|
|
917
|
+
|
|
918
|
+
operation_state = self.get_query_state()
|
|
919
|
+
if operation_state == ttypes.TOperationState.FINISHED_STATE:
|
|
920
|
+
execute_response = self.thrift_backend.get_execution_result(
|
|
921
|
+
self.active_op_handle, self
|
|
922
|
+
)
|
|
923
|
+
self.active_result_set = ResultSet(
|
|
924
|
+
self.connection,
|
|
925
|
+
execute_response,
|
|
926
|
+
self.thrift_backend,
|
|
927
|
+
self.buffer_size_bytes,
|
|
928
|
+
self.arraysize,
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
if execute_response.is_staging_operation:
|
|
932
|
+
self._handle_staging_operation(
|
|
933
|
+
staging_allowed_local_path=self.thrift_backend.staging_allowed_local_path
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
return self
|
|
937
|
+
else:
|
|
938
|
+
raise Error(
|
|
939
|
+
f"get_execution_result failed with Operation status {operation_state}"
|
|
940
|
+
)
|
|
941
|
+
|
|
822
942
|
def executemany(self, operation, seq_of_parameters):
|
|
823
943
|
"""
|
|
824
944
|
Execute the operation once for every set of passed in parameters.
|
|
@@ -1104,6 +1224,7 @@ class ResultSet:
|
|
|
1104
1224
|
thrift_backend: ThriftBackend,
|
|
1105
1225
|
result_buffer_size_bytes: int = DEFAULT_RESULT_BUFFER_SIZE_BYTES,
|
|
1106
1226
|
arraysize: int = 10000,
|
|
1227
|
+
use_cloud_fetch: bool = True,
|
|
1107
1228
|
):
|
|
1108
1229
|
"""
|
|
1109
1230
|
A ResultSet manages the results of a single command.
|
|
@@ -1125,6 +1246,7 @@ class ResultSet:
|
|
|
1125
1246
|
self.description = execute_response.description
|
|
1126
1247
|
self._arrow_schema_bytes = execute_response.arrow_schema_bytes
|
|
1127
1248
|
self._next_row_index = 0
|
|
1249
|
+
self._use_cloud_fetch = use_cloud_fetch
|
|
1128
1250
|
|
|
1129
1251
|
if execute_response.arrow_queue:
|
|
1130
1252
|
# In this case the server has taken the fast path and returned an initial batch of
|
|
@@ -1152,6 +1274,7 @@ class ResultSet:
|
|
|
1152
1274
|
lz4_compressed=self.lz4_compressed,
|
|
1153
1275
|
arrow_schema_bytes=self._arrow_schema_bytes,
|
|
1154
1276
|
description=self.description,
|
|
1277
|
+
use_cloud_fetch=self._use_cloud_fetch,
|
|
1155
1278
|
)
|
|
1156
1279
|
self.results = results
|
|
1157
1280
|
self.has_more_rows = has_more_rows
|
|
@@ -7,6 +7,8 @@ import uuid
|
|
|
7
7
|
import threading
|
|
8
8
|
from typing import List, Union
|
|
9
9
|
|
|
10
|
+
from databricks.sql.thrift_api.TCLIService.ttypes import TOperationState
|
|
11
|
+
|
|
10
12
|
try:
|
|
11
13
|
import pyarrow
|
|
12
14
|
except ImportError:
|
|
@@ -93,8 +95,6 @@ class ThriftBackend:
|
|
|
93
95
|
**kwargs,
|
|
94
96
|
):
|
|
95
97
|
# Internal arguments in **kwargs:
|
|
96
|
-
# _user_agent_entry
|
|
97
|
-
# Tag to add to User-Agent header. For use by partners.
|
|
98
98
|
# _username, _password
|
|
99
99
|
# Username and password Basic authentication (no official support)
|
|
100
100
|
# _connection_uri
|
|
@@ -319,7 +319,7 @@ class ThriftBackend:
|
|
|
319
319
|
|
|
320
320
|
# FUTURE: Consider moving to https://github.com/litl/backoff or
|
|
321
321
|
# https://github.com/jd/tenacity for retry logic.
|
|
322
|
-
def make_request(self, method, request):
|
|
322
|
+
def make_request(self, method, request, retryable=True):
|
|
323
323
|
"""Execute given request, attempting retries when
|
|
324
324
|
1. Receiving HTTP 429/503 from server
|
|
325
325
|
2. OSError is raised during a GetOperationStatus
|
|
@@ -458,7 +458,7 @@ class ThriftBackend:
|
|
|
458
458
|
# return on success
|
|
459
459
|
# if available: bounded delay and retry
|
|
460
460
|
# if not: raise error
|
|
461
|
-
max_attempts = self._retry_stop_after_attempts_count
|
|
461
|
+
max_attempts = self._retry_stop_after_attempts_count if retryable else 1
|
|
462
462
|
|
|
463
463
|
# use index-1 counting for logging/human consistency
|
|
464
464
|
for attempt in range(1, max_attempts + 1):
|
|
@@ -769,6 +769,63 @@ class ThriftBackend:
|
|
|
769
769
|
arrow_schema_bytes=schema_bytes,
|
|
770
770
|
)
|
|
771
771
|
|
|
772
|
+
def get_execution_result(self, op_handle, cursor):
|
|
773
|
+
|
|
774
|
+
assert op_handle is not None
|
|
775
|
+
|
|
776
|
+
req = ttypes.TFetchResultsReq(
|
|
777
|
+
operationHandle=ttypes.TOperationHandle(
|
|
778
|
+
op_handle.operationId,
|
|
779
|
+
op_handle.operationType,
|
|
780
|
+
False,
|
|
781
|
+
op_handle.modifiedRowCount,
|
|
782
|
+
),
|
|
783
|
+
maxRows=cursor.arraysize,
|
|
784
|
+
maxBytes=cursor.buffer_size_bytes,
|
|
785
|
+
orientation=ttypes.TFetchOrientation.FETCH_NEXT,
|
|
786
|
+
includeResultSetMetadata=True,
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
resp = self.make_request(self._client.FetchResults, req)
|
|
790
|
+
|
|
791
|
+
t_result_set_metadata_resp = resp.resultSetMetadata
|
|
792
|
+
|
|
793
|
+
lz4_compressed = t_result_set_metadata_resp.lz4Compressed
|
|
794
|
+
is_staging_operation = t_result_set_metadata_resp.isStagingOperation
|
|
795
|
+
has_more_rows = resp.hasMoreRows
|
|
796
|
+
description = self._hive_schema_to_description(
|
|
797
|
+
t_result_set_metadata_resp.schema
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
schema_bytes = (
|
|
801
|
+
t_result_set_metadata_resp.arrowSchema
|
|
802
|
+
or self._hive_schema_to_arrow_schema(t_result_set_metadata_resp.schema)
|
|
803
|
+
.serialize()
|
|
804
|
+
.to_pybytes()
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
queue = ResultSetQueueFactory.build_queue(
|
|
808
|
+
row_set_type=resp.resultSetMetadata.resultFormat,
|
|
809
|
+
t_row_set=resp.results,
|
|
810
|
+
arrow_schema_bytes=schema_bytes,
|
|
811
|
+
max_download_threads=self.max_download_threads,
|
|
812
|
+
lz4_compressed=lz4_compressed,
|
|
813
|
+
description=description,
|
|
814
|
+
ssl_options=self._ssl_options,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
return ExecuteResponse(
|
|
818
|
+
arrow_queue=queue,
|
|
819
|
+
status=resp.status,
|
|
820
|
+
has_been_closed_server_side=False,
|
|
821
|
+
has_more_rows=has_more_rows,
|
|
822
|
+
lz4_compressed=lz4_compressed,
|
|
823
|
+
is_staging_operation=is_staging_operation,
|
|
824
|
+
command_handle=op_handle,
|
|
825
|
+
description=description,
|
|
826
|
+
arrow_schema_bytes=schema_bytes,
|
|
827
|
+
)
|
|
828
|
+
|
|
772
829
|
def _wait_until_command_done(self, op_handle, initial_operation_status_resp):
|
|
773
830
|
if initial_operation_status_resp:
|
|
774
831
|
self._check_command_not_in_error_or_closed_state(
|
|
@@ -787,6 +844,12 @@ class ThriftBackend:
|
|
|
787
844
|
self._check_command_not_in_error_or_closed_state(op_handle, poll_resp)
|
|
788
845
|
return operation_state
|
|
789
846
|
|
|
847
|
+
def get_query_state(self, op_handle) -> "TOperationState":
|
|
848
|
+
poll_resp = self._poll_for_status(op_handle)
|
|
849
|
+
operation_state = poll_resp.operationState
|
|
850
|
+
self._check_command_not_in_error_or_closed_state(op_handle, poll_resp)
|
|
851
|
+
return operation_state
|
|
852
|
+
|
|
790
853
|
@staticmethod
|
|
791
854
|
def _check_direct_results_for_error(t_spark_direct_results):
|
|
792
855
|
if t_spark_direct_results:
|
|
@@ -817,6 +880,8 @@ class ThriftBackend:
|
|
|
817
880
|
cursor,
|
|
818
881
|
use_cloud_fetch=True,
|
|
819
882
|
parameters=[],
|
|
883
|
+
async_op=False,
|
|
884
|
+
enforce_embedded_schema_correctness=False,
|
|
820
885
|
):
|
|
821
886
|
assert session_handle is not None
|
|
822
887
|
|
|
@@ -832,8 +897,12 @@ class ThriftBackend:
|
|
|
832
897
|
sessionHandle=session_handle,
|
|
833
898
|
statement=operation,
|
|
834
899
|
runAsync=True,
|
|
835
|
-
|
|
836
|
-
|
|
900
|
+
# For async operation we don't want the direct results
|
|
901
|
+
getDirectResults=None
|
|
902
|
+
if async_op
|
|
903
|
+
else ttypes.TSparkGetDirectResults(
|
|
904
|
+
maxRows=max_rows,
|
|
905
|
+
maxBytes=max_bytes,
|
|
837
906
|
),
|
|
838
907
|
canReadArrowResult=True if pyarrow else False,
|
|
839
908
|
canDecompressLZ4Result=lz4_compression,
|
|
@@ -844,9 +913,14 @@ class ThriftBackend:
|
|
|
844
913
|
},
|
|
845
914
|
useArrowNativeTypes=spark_arrow_types,
|
|
846
915
|
parameters=parameters,
|
|
916
|
+
enforceEmbeddedSchemaCorrectness=enforce_embedded_schema_correctness,
|
|
847
917
|
)
|
|
848
918
|
resp = self.make_request(self._client.ExecuteStatement, req)
|
|
849
|
-
|
|
919
|
+
|
|
920
|
+
if async_op:
|
|
921
|
+
self._handle_execute_response_async(resp, cursor)
|
|
922
|
+
else:
|
|
923
|
+
return self._handle_execute_response(resp, cursor)
|
|
850
924
|
|
|
851
925
|
def get_catalogs(self, session_handle, max_rows, max_bytes, cursor):
|
|
852
926
|
assert session_handle is not None
|
|
@@ -945,6 +1019,10 @@ class ThriftBackend:
|
|
|
945
1019
|
|
|
946
1020
|
return self._results_message_to_execute_response(resp, final_operation_state)
|
|
947
1021
|
|
|
1022
|
+
def _handle_execute_response_async(self, resp, cursor):
|
|
1023
|
+
cursor.active_op_handle = resp.operationHandle
|
|
1024
|
+
self._check_direct_results_for_error(resp.directResults)
|
|
1025
|
+
|
|
948
1026
|
def fetch_results(
|
|
949
1027
|
self,
|
|
950
1028
|
op_handle,
|
|
@@ -954,6 +1032,7 @@ class ThriftBackend:
|
|
|
954
1032
|
lz4_compressed,
|
|
955
1033
|
arrow_schema_bytes,
|
|
956
1034
|
description,
|
|
1035
|
+
use_cloud_fetch=True,
|
|
957
1036
|
):
|
|
958
1037
|
assert op_handle is not None
|
|
959
1038
|
|
|
@@ -970,10 +1049,11 @@ class ThriftBackend:
|
|
|
970
1049
|
includeResultSetMetadata=True,
|
|
971
1050
|
)
|
|
972
1051
|
|
|
973
|
-
|
|
1052
|
+
# Fetch results in Inline mode with FETCH_NEXT orientation are not idempotent and hence not retried
|
|
1053
|
+
resp = self.make_request(self._client.FetchResults, req, use_cloud_fetch)
|
|
974
1054
|
if resp.results.startRowOffset > expected_row_start_offset:
|
|
975
|
-
|
|
976
|
-
"Expected results to start from {} but they instead start at {}".format(
|
|
1055
|
+
raise DataError(
|
|
1056
|
+
"fetch_results failed due to inconsistency in the state between the client and the server. Expected results to start from {} but they instead start at {}, some result batches must have been skipped".format(
|
|
977
1057
|
expected_row_start_offset, resp.results.startRowOffset
|
|
978
1058
|
)
|
|
979
1059
|
)
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/utils.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from dateutil import parser
|
|
4
4
|
import datetime
|
|
5
5
|
import decimal
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
@@ -642,16 +642,7 @@ def convert_to_assigned_datatypes_in_column_table(column_table, description):
|
|
|
642
642
|
)
|
|
643
643
|
elif description[i][1] == "timestamp":
|
|
644
644
|
converted_column_table.append(
|
|
645
|
-
tuple(
|
|
646
|
-
(
|
|
647
|
-
v
|
|
648
|
-
if v is None
|
|
649
|
-
else datetime.datetime.strptime(
|
|
650
|
-
v, "%Y-%m-%d %H:%M:%S.%f"
|
|
651
|
-
).replace(tzinfo=pytz.UTC)
|
|
652
|
-
)
|
|
653
|
-
for v in col
|
|
654
|
-
)
|
|
645
|
+
tuple((v if v is None else parser.parse(v)) for v in col)
|
|
655
646
|
)
|
|
656
647
|
else:
|
|
657
648
|
converted_column_table.append(col)
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/auth/oauth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/exc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-4.0.0b4 → databricks_sql_connector-4.0.1}/src/databricks/sql/types.py
RENAMED
|
File without changes
|