This is an automated email from the ASF dual-hosted git repository.

kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 24a809533ba Fix lost transaction in SnowflakeHook when 
`session_parameters` is set (#75425)
24a809533ba is described below

commit 24a809533ba882726b7e845d35cf5c0e3eebb37e
Author: Andrey Karazhev <[email protected]>
AuthorDate: Thu Oct 22 23:57:04 2026 +0300

    Fix lost transaction in SnowflakeHook when `session_parameters` is set 
(#75425)
    
    snowflake-connector-python merges the server's session parameters, 
AUTOCOMMIT
    among them, into the session_parameters dict it is given. The hook handed it
    its own dict, or the one cached from the connection extra, so run() then 
found
    AUTOCOMMIT there, skipped ALTER SESSION SET autocommit=False and the final
    COMMIT, and each statement of run(autocommit=False) was committed on its 
own.
    The task still succeeded and nothing was logged.
---
 .../airflow/providers/snowflake/hooks/snowflake.py |  6 +++-
 .../tests/unit/snowflake/hooks/test_snowflake.py   | 35 ++++++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git 
a/providers/snowflake/src/airflow/providers/snowflake/hooks/snowflake.py 
b/providers/snowflake/src/airflow/providers/snowflake/hooks/snowflake.py
index 52d2f03d541..3931f2cf4a2 100644
--- a/providers/snowflake/src/airflow/providers/snowflake/hooks/snowflake.py
+++ b/providers/snowflake/src/airflow/providers/snowflake/hooks/snowflake.py
@@ -488,7 +488,11 @@ class SnowflakeHook(DbApiHook):
         This is used in ``get_uri()`` and ``get_conn()``.
         """
         static_config = self._get_static_conn_params
-        conn_config = dict(static_config)
+        conn_config: dict[str, Any] = dict(static_config)
+        # The connector merges the server's session parameters, AUTOCOMMIT 
among them, into the dict it is
+        # given; a copy keeps them out of the dicts that 
_has_autocommit_session_parameter() reads.
+        if isinstance(conn_config.get("session_parameters"), dict):
+            conn_config["session_parameters"] = 
dict(conn_config["session_parameters"])
 
         if conn_config.get("authenticator") == "oauth":
             azure_conn_id = conn_config.get("azure_conn_id")
diff --git a/providers/snowflake/tests/unit/snowflake/hooks/test_snowflake.py 
b/providers/snowflake/tests/unit/snowflake/hooks/test_snowflake.py
index 897a7f8a224..2fdc6a030b7 100644
--- a/providers/snowflake/tests/unit/snowflake/hooks/test_snowflake.py
+++ b/providers/snowflake/tests/unit/snowflake/hooks/test_snowflake.py
@@ -32,6 +32,7 @@ from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization
 from cryptography.hazmat.primitives.asymmetric import rsa
 from requests.exceptions import ConnectionError, HTTPError
+from snowflake.connector.cursor import SnowflakeCursor
 
 from airflow.models import Connection
 from airflow.providers.common.compat.sdk import 
AirflowOptionalProviderFeatureException, timezone
@@ -1335,6 +1336,40 @@ class TestPytestSnowflakeHook:
 
         conn.autocommit.assert_called_once_with(False)
 
+    @pytest.mark.parametrize(
+        ("hook_kwargs", "extra"),
+        [
+            pytest.param({"session_parameters": {"QUERY_TAG": "airflow"}}, {}, 
id="hook"),
+            pytest.param({}, {"session_parameters": {"QUERY_TAG": "airflow"}}, 
id="connection"),
+        ],
+    )
+    @mock.patch("snowflake.connector.SnowflakeConnection", autospec=True)
+    def test_run_keeps_transaction_when_connect_merges_session_parameters(
+        self, mock_connection_class, hook_kwargs, extra
+    ):
+        """The connector merges the server's AUTOCOMMIT into the 
session_parameters dict it receives."""
+        conn = mock_connection_class.return_value
+        cur = mock.MagicMock(spec=SnowflakeCursor, rowcount=0)
+        conn.cursor.return_value = cur
+        type(cur).sfqid = mock.PropertyMock(return_value="qid")
+
+        def connect(**kwargs):
+            kwargs["session_parameters"].update({"AUTOCOMMIT": True})
+            return conn
+
+        mock_connection_class.side_effect = connect
+        connection_kwargs = deepcopy(BASE_CONNECTION_KWARGS)
+        connection_kwargs["extra"].update(extra)
+        with mock.patch.dict(
+            "os.environ",
+            
AIRFLOW_CONN_SNOWFLAKE_DEFAULT=Connection(**connection_kwargs).get_uri(),
+        ):
+            hook = SnowflakeHook(**hook_kwargs)
+            hook.run("DELETE FROM t; INSERT INTO t VALUES (1);", 
autocommit=False)
+
+        conn.autocommit.assert_called_once_with(False)
+        conn.commit.assert_called_once_with()
+
     def test_get_openlineage_default_schema_with_no_schema_set(self):
         connection_kwargs = {
             **BASE_CONNECTION_KWARGS,

Reply via email to