Skip to content

Commit 39da0d3

Browse files
authored
fix: Set DatabricksConnectionConfig to only use a shared_connection with U2M OAuth (#5859)
Signed-off-by: davem-bis <68955845+davem-bis@users.noreply.github.com>
1 parent b0bc176 commit 39da0d3

5 files changed

Lines changed: 71 additions & 17 deletions

File tree

sqlmesh/core/config/connection.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
from sys import version_info
1414

1515
import pydantic
16+
from pydantic import Field, computed_field
1617
from packaging import version
17-
from pydantic import Field
1818
from pydantic_core import from_json
1919
from sqlglot import exp
2020
from sqlglot.errors import ParseError
@@ -110,7 +110,14 @@ class ConnectionConfig(abc.ABC, BaseConfig):
110110
catalog_type_overrides: t.Optional[t.Dict[str, str]] = None
111111

112112
# Whether to share a single connection across threads or create a new connection per thread.
113-
shared_connection: t.ClassVar[bool] = False
113+
#
114+
# MyPy throws a "Decorators on top of @property are not supported" error despite this being a
115+
# valid decoration, and Pydantic recommend disabling the MyPy hint for this reason - see:
116+
# https://pydantic.dev/docs/validation/2.0/usage/computed_fields/
117+
@computed_field # type: ignore[prop-decorator]
118+
@property
119+
def shared_connection(self) -> bool:
120+
return False
114121

115122
@property
116123
@abc.abstractmethod
@@ -311,7 +318,10 @@ class BaseDuckDBConnectionConfig(ConnectionConfig):
311318

312319
token: t.Optional[str] = None
313320

314-
shared_connection: t.ClassVar[bool] = True
321+
@computed_field # type: ignore[prop-decorator]
322+
@property
323+
def shared_connection(self) -> bool:
324+
return True
315325

316326
_data_file_to_adapter: t.ClassVar[t.Dict[str, EngineAdapter]] = {}
317327

@@ -820,11 +830,15 @@ class DatabricksConnectionConfig(ConnectionConfig):
820830
DISPLAY_NAME: t.ClassVar[t.Literal["Databricks"]] = "Databricks"
821831
DISPLAY_ORDER: t.ClassVar[t.Literal[3]] = 3
822832

823-
shared_connection: t.ClassVar[bool] = True
824-
825833
_concurrent_tasks_validator = concurrent_tasks_validator
826834
_http_headers_validator = http_headers_validator
827835

836+
@computed_field # type: ignore[prop-decorator]
837+
@property
838+
def shared_connection(self) -> bool:
839+
"""The connection should only be shared if U2M OAuth is being used"""
840+
return self.auth_type is not None and self.oauth_client_secret is None
841+
828842
@model_validator(mode="before")
829843
def _databricks_connect_validator(cls, data: t.Any) -> t.Any:
830844
# SQLQueryContextLogger will output any error SQL queries even if they are in a try/except block.

sqlmesh/core/config/loader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,4 @@ def convert_config_type(
272272
config_obj: Config,
273273
config_type: t.Type[C],
274274
) -> C:
275-
return config_type.parse_obj(config_obj.dict())
275+
return config_type.parse_obj(config_obj.dict(exclude_computed_fields=True))

tests/core/test_config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ def test_connection_config_serialization():
532532
"extensions": [],
533533
"pre_ping": False,
534534
"pretty_sql": False,
535+
"shared_connection": True,
535536
"connector_config": {},
536537
"secrets": [],
537538
"filesystems": [],
@@ -544,6 +545,7 @@ def test_connection_config_serialization():
544545
"extensions": [],
545546
"pre_ping": False,
546547
"pretty_sql": False,
548+
"shared_connection": True,
547549
"connector_config": {},
548550
"secrets": [],
549551
"filesystems": [],

tests/core/test_connection_config.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,26 +1426,27 @@ def test_databricks(make_config):
14261426
)
14271427

14281428

1429-
def test_databricks_shared_connection(make_config):
1430-
"""Databricks should use a shared connection pool to prevent OAuth CSRF races.
1429+
def test_databricks__u2m_oauth__shared_connection_pool(make_config):
1430+
"""Databricks should use a shared connection pool when using OAuth to prevent CSRF races.
14311431
14321432
When concurrent_tasks > 1, ThreadLocalConnectionPool creates one connection per
14331433
thread. For U2M OAuth, each thread triggers its own browser-based OAuth flow;
14341434
these race on the CSRF state parameter and cause MismatchingStateError.
14351435
1436-
Setting shared_connection = True causes ThreadLocalSharedConnectionPool to be
1437-
used instead: a single connection is created (behind a lock) and each thread
1438-
gets its own cursor, so only one OAuth flow is ever initiated.
1436+
For non-U2M OAuth authentication types (e.g. access_token and M2M OAuth) then
1437+
ThreadLocalConnectionPool should still be used.
14391438
1440-
See: https://github.com/tobymao/sqlmesh/issues/5646
1439+
See:
1440+
https://github.com/tobymao/sqlmesh/issues/5646
1441+
https://github.com/SQLMesh/sqlmesh/issues/5858
14411442
"""
14421443
from sqlmesh.utils.connection_pool import ThreadLocalSharedConnectionPool
14431444

14441445
config = make_config(
14451446
type="databricks",
14461447
server_hostname="dbc-test.cloud.databricks.com",
14471448
http_path="sql/test/foo",
1448-
access_token="test-token",
1449+
auth_type="databricks-oauth",
14491450
concurrent_tasks=4,
14501451
)
14511452
assert isinstance(config, DatabricksConnectionConfig)
@@ -1455,6 +1456,43 @@ def test_databricks_shared_connection(make_config):
14551456
assert isinstance(adapter._connection_pool, ThreadLocalSharedConnectionPool)
14561457

14571458

1459+
@patch.object(DatabricksConnectionConfig, "_connection_factory_with_kwargs")
1460+
def test_databricks__m2m_oauth__connection_pool(mock_connection_factory_with_kwargs, make_config):
1461+
from sqlmesh.utils.connection_pool import ThreadLocalConnectionPool
1462+
1463+
config = make_config(
1464+
type="databricks",
1465+
server_hostname="dbc-test.cloud.databricks.com",
1466+
http_path="sql/test/foo",
1467+
auth_type="databricks-oauth",
1468+
oauth_client_id="oauth_client_id",
1469+
oauth_client_secret="oauth_client_secret",
1470+
concurrent_tasks=4,
1471+
)
1472+
assert isinstance(config, DatabricksConnectionConfig)
1473+
assert config.shared_connection is False
1474+
1475+
adapter = config.create_engine_adapter()
1476+
assert isinstance(adapter._connection_pool, ThreadLocalConnectionPool)
1477+
1478+
1479+
def test_databricks__access_token__connection_pool(make_config):
1480+
from sqlmesh.utils.connection_pool import ThreadLocalConnectionPool
1481+
1482+
config = make_config(
1483+
type="databricks",
1484+
server_hostname="dbc-test.cloud.databricks.com",
1485+
http_path="sql/test/foo",
1486+
access_token="any-token",
1487+
concurrent_tasks=4,
1488+
)
1489+
assert isinstance(config, DatabricksConnectionConfig)
1490+
assert config.shared_connection is False
1491+
1492+
adapter = config.create_engine_adapter()
1493+
assert isinstance(adapter._connection_pool, ThreadLocalConnectionPool)
1494+
1495+
14581496
def test_engine_import_validator():
14591497
with pytest.raises(
14601498
ConfigError,

tests/integrations/jupyter/test_magics.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -745,16 +745,16 @@ def test_info(notebook, sushi_context, convert_all_html_output_to_text, get_all_
745745
"Models: 20",
746746
"Macros: 8",
747747
"",
748-
"Connection:\n type: duckdb\n concurrent_tasks: 1\n register_comments: true\n pre_ping: false\n pretty_sql: false\n extensions: []\n connector_config: {}\n secrets: None\n filesystems: []",
749-
"Test Connection:\n type: duckdb\n concurrent_tasks: 1\n register_comments: true\n pre_ping: false\n pretty_sql: false\n extensions: []\n connector_config: {}\n secrets: None\n filesystems: []",
748+
"Connection:\n type: duckdb\n concurrent_tasks: 1\n register_comments: true\n pre_ping: false\n pretty_sql: false\n extensions: []\n connector_config: {}\n secrets: None\n filesystems: []\n shared_connection: true",
749+
"Test Connection:\n type: duckdb\n concurrent_tasks: 1\n register_comments: true\n pre_ping: false\n pretty_sql: false\n extensions: []\n connector_config: {}\n secrets: None\n filesystems: []\n shared_connection: true",
750750
"Data warehouse connection succeeded",
751751
]
752752
assert get_all_html_output(output) == [
753753
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Models: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">20</span></pre>",
754754
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Macros: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">8</span></pre>",
755755
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>",
756-
'<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace">Connection: type: duckdb concurrent_tasks: <span style="color: #008080; text-decoration-color: #008080; font-weight: bold">1</span> register_comments: true pre_ping: false pretty_sql: false extensions: <span style="font-weight: bold">[]</span> connector_config: <span style="font-weight: bold">{}</span> secrets: <span style="color: #800080; text-decoration-color: #800080; font-style: italic">None</span> filesystems: <span style="font-weight: bold">[]</span></pre>',
757-
'<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace">Test Connection: type: duckdb concurrent_tasks: <span style="color: #008080; text-decoration-color: #008080; font-weight: bold">1</span> register_comments: true pre_ping: false pretty_sql: false extensions: <span style="font-weight: bold">[]</span> connector_config: <span style="font-weight: bold">{}</span> secrets: <span style="color: #800080; text-decoration-color: #800080; font-style: italic">None</span> filesystems: <span style="font-weight: bold">[]</span></pre>',
756+
'<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace">Connection: type: duckdb concurrent_tasks: <span style="color: #008080; text-decoration-color: #008080; font-weight: bold">1</span> register_comments: true pre_ping: false pretty_sql: false extensions: <span style="font-weight: bold">[]</span> connector_config: <span style="font-weight: bold">{}</span> secrets: <span style="color: #800080; text-decoration-color: #800080; font-style: italic">None</span> filesystems: <span style="font-weight: bold">[]</span> shared_connection: true</pre>',
757+
'<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace">Test Connection: type: duckdb concurrent_tasks: <span style="color: #008080; text-decoration-color: #008080; font-weight: bold">1</span> register_comments: true pre_ping: false pretty_sql: false extensions: <span style="font-weight: bold">[]</span> connector_config: <span style="font-weight: bold">{}</span> secrets: <span style="color: #800080; text-decoration-color: #800080; font-style: italic">None</span> filesystems: <span style="font-weight: bold">[]</span> shared_connection: true</pre>',
758758
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">Data warehouse connection <span style=\"color: #008000; text-decoration-color: #008000\">succeeded</span></pre>",
759759
]
760760

0 commit comments

Comments
 (0)