@@ -138,6 +138,7 @@ def test_ddl_returns_none(self):
138138
139139 assert result is None
140140 mock_cursor .fetch_pandas_all .assert_not_called ()
141+ mock_cursor .close .assert_called_once ()
141142
142143
143144# ---------------------------------------------------------------------------
@@ -504,6 +505,58 @@ def test_select_returns_dataframe(self, sample_df):
504505 assert isinstance (result , pd .DataFrame )
505506 assert list (result .columns ) == ["id" , "name" ]
506507
508+ def test_ddl_commits (self ):
509+ mock_cursor = MagicMock ()
510+ mock_cursor .description = None
511+
512+ mock_client = MagicMock ()
513+ mock_client .cursor .return_value = mock_cursor
514+
515+ with patch ("bruin._connection._create_mysql" , return_value = mock_client ):
516+ result = query ("CREATE TABLE t (id INT)" , "my_mysql" )
517+
518+ assert result is None
519+ mock_client .commit .assert_called_once ()
520+
521+
522+ # ---------------------------------------------------------------------------
523+ # Redshift (reuses postgres path)
524+ # ---------------------------------------------------------------------------
525+
526+
527+ class TestQueryRedshift :
528+ @pytest .fixture (autouse = True )
529+ def _setup (self , monkeypatch , redshift_connection_json ):
530+ monkeypatch .setenv ("BRUIN_CONNECTION_TYPES" , json .dumps ({"my_rs" : "redshift" }))
531+ monkeypatch .setenv ("my_rs" , json .dumps (redshift_connection_json ))
532+
533+ def test_select_returns_dataframe (self , sample_df ):
534+ mock_cursor = MagicMock ()
535+ mock_cursor .description = [("id" ,), ("name" ,)]
536+ mock_cursor .fetchall .return_value = [(1 , "a" ), (2 , "b" )]
537+
538+ mock_client = MagicMock ()
539+ mock_client .cursor .return_value = mock_cursor
540+
541+ with patch ("bruin._connection._create_redshift" , return_value = mock_client ):
542+ result = query ("SELECT 1" , "my_rs" )
543+
544+ assert isinstance (result , pd .DataFrame )
545+ assert list (result .columns ) == ["id" , "name" ]
546+
547+ def test_ddl_commits (self ):
548+ mock_cursor = MagicMock ()
549+ mock_cursor .description = None
550+
551+ mock_client = MagicMock ()
552+ mock_client .cursor .return_value = mock_cursor
553+
554+ with patch ("bruin._connection._create_redshift" , return_value = mock_client ):
555+ result = query ("DROP TABLE foo" , "my_rs" )
556+
557+ assert result is None
558+ mock_client .commit .assert_called_once ()
559+
507560
508561# ---------------------------------------------------------------------------
509562# Synapse (reuses MSSQL path)
@@ -530,6 +583,19 @@ def test_select_returns_dataframe(self, sample_df):
530583 assert isinstance (result , pd .DataFrame )
531584 assert list (result .columns ) == ["id" , "name" ]
532585
586+ def test_ddl_commits (self ):
587+ mock_cursor = MagicMock ()
588+ mock_cursor .description = None
589+
590+ mock_client = MagicMock ()
591+ mock_client .cursor .return_value = mock_cursor
592+
593+ with patch ("bruin._connection._create_mssql" , return_value = mock_client ):
594+ result = query ("CREATE TABLE t (id INT)" , "my_syn" )
595+
596+ assert result is None
597+ mock_client .commit .assert_called_once ()
598+
533599
534600class TestQueryFabric :
535601 @pytest .fixture (autouse = True )
@@ -859,3 +925,33 @@ def test_client_exception_wraps_in_query_error(self):
859925 with patch ("bruin._connection._create_snowflake" , return_value = mock_client ):
860926 with pytest .raises (QueryError , match = "connection refused" ):
861927 query ("SELECT 1" , "my_sf" )
928+
929+ @pytest .mark .usefixtures ("_setup_postgres" )
930+ def test_cursor_closed_on_exception (self ):
931+ """Cursor must be closed even when execute() raises."""
932+ mock_cursor = MagicMock ()
933+ mock_cursor .execute .side_effect = RuntimeError ("syntax error" )
934+
935+ mock_client = MagicMock ()
936+ mock_client .cursor .return_value = mock_cursor
937+
938+ with patch ("bruin._connection._create_postgres" , return_value = mock_client ):
939+ with pytest .raises (QueryError , match = "syntax error" ):
940+ query ("INVALID SQL" , "my_pg" )
941+
942+ mock_cursor .close .assert_called_once ()
943+
944+ @pytest .mark .usefixtures ("_setup_snowflake" )
945+ def test_snowflake_cursor_closed_on_exception (self ):
946+ """Snowflake cursor must be closed even when execute() raises."""
947+ mock_cursor = MagicMock ()
948+ mock_cursor .execute .side_effect = RuntimeError ("warehouse suspended" )
949+
950+ mock_client = MagicMock ()
951+ mock_client .cursor .return_value = mock_cursor
952+
953+ with patch ("bruin._connection._create_snowflake" , return_value = mock_client ):
954+ with pytest .raises (QueryError , match = "warehouse suspended" ):
955+ query ("SELECT 1" , "my_sf" )
956+
957+ mock_cursor .close .assert_called_once ()
0 commit comments