@@ -437,6 +437,76 @@ def test_trino_with_jinja_templates_uses_qmark(
437437 self .assertIsInstance (bind_params , list )
438438 self .assertEqual (bind_params , [123 ])
439439
440+ @mock .patch ("pandas.read_sql_query" )
441+ def test_list_bind_params_converted_to_tuple_for_pandas (self , mocked_read_sql ):
442+ """Test that list bind_params are converted to tuple for pandas.read_sql_query"""
443+ from deepnote_toolkit .sql .sql_execution import _execute_sql_on_engine
444+
445+ mock_df = pd .DataFrame ({"col1" : [1 , 2 , 3 ]})
446+ mocked_read_sql .return_value = mock_df
447+
448+ # Mock engine and connection
449+ mock_engine = mock .Mock ()
450+ mock_connection = mock .Mock ()
451+ mock_engine .begin .return_value .__enter__ = mock .Mock (
452+ return_value = mock_connection
453+ )
454+ mock_engine .begin .return_value .__exit__ = mock .Mock (return_value = None )
455+
456+ # Test with list bind_params (qmark style for Trino)
457+ list_params = [123 , "test" ]
458+ _execute_sql_on_engine (
459+ mock_engine ,
460+ "SELECT * FROM test_table WHERE id = ? AND name = ?" ,
461+ list_params ,
462+ )
463+
464+ # Verify pandas.read_sql_query was called
465+ self .assertTrue (mocked_read_sql .called )
466+
467+ # Get the params argument passed to pandas.read_sql_query
468+ call_kwargs = mocked_read_sql .call_args [1 ]
469+ params_arg = call_kwargs .get ("params" )
470+
471+ # Verify that list was converted to tuple
472+ self .assertIsInstance (params_arg , tuple )
473+ self .assertEqual (params_arg , (123 , "test" ))
474+
475+ @mock .patch ("pandas.read_sql_query" )
476+ def test_dict_bind_params_not_converted_for_pandas (self , mocked_read_sql ):
477+ """Test that dict bind_params remain as dict for pandas.read_sql_query"""
478+ from deepnote_toolkit .sql .sql_execution import _execute_sql_on_engine
479+
480+ mock_df = pd .DataFrame ({"col1" : [1 , 2 , 3 ]})
481+ mocked_read_sql .return_value = mock_df
482+
483+ # Mock engine and connection
484+ mock_engine = mock .Mock ()
485+ mock_connection = mock .Mock ()
486+ mock_engine .begin .return_value .__enter__ = mock .Mock (
487+ return_value = mock_connection
488+ )
489+ mock_engine .begin .return_value .__exit__ = mock .Mock (return_value = None )
490+
491+ # Test with dict bind_params (pyformat style)
492+ dict_params = {"id" : 123 , "name" : "test" }
493+ _execute_sql_on_engine (
494+ mock_engine ,
495+ "SELECT * FROM test_table WHERE id = %(id)s AND name = %(name)s" ,
496+ dict_params ,
497+ )
498+
499+ # Verify pandas.read_sql_query was called
500+ self .assertTrue (mocked_read_sql .called )
501+
502+ # Get the params argument passed to pandas.read_sql_query
503+ call_kwargs = mocked_read_sql .call_args [1 ]
504+ params_arg = call_kwargs .get ("params" )
505+
506+ # Verify that dict was NOT converted (remains as dict)
507+ self .assertIsInstance (params_arg , dict )
508+ self .assertEqual (params_arg , {"id" : 123 , "name" : "test" })
509+
440510
441511class TestSanitizeDataframe (unittest .TestCase ):
442512 @parameterized .expand ([(key , df ) for key , df in testing_dataframes .items ()])
0 commit comments