2020HAS_POLARS = DependencyManager .polars .has ()
2121
2222
23+ @pytest .fixture
24+ def duckdb_conn ():
25+ """Per-test in-memory DuckDB connection.
26+
27+ Each test gets a fresh, isolated connection.
28+ """
29+ if not HAS_DUCKDB :
30+ pytest .skip ("DuckDB not installed" )
31+ import duckdb
32+
33+ conn = duckdb .connect (":memory:" )
34+ try :
35+ yield conn
36+ finally :
37+ conn .close ()
38+
39+
2340class TestDuckDBRuntimeErrors :
2441 """Test DuckDB errors that occur during SQL execution."""
2542
2643 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
27- def test_table_not_found_error (self ):
44+ def test_table_not_found_error (self , duckdb_conn ):
2845 """Test error when referencing a non-existent table."""
2946 with pytest .raises (MarimoSQLException ) as exc_info :
30- sql ("SELECT * FROM nonexistent_table" )
47+ sql ("SELECT * FROM nonexistent_table" , engine = duckdb_conn )
3148
3249 error = exc_info .value
3350 assert "nonexistent_table" in str (error ).lower ()
3451 assert "does not exist" in str (error ).lower ()
3552 assert error .sql_statement == "SELECT * FROM nonexistent_table"
3653
3754 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
38- def test_column_not_found_error (self ):
55+ def test_column_not_found_error (self , duckdb_conn ):
3956 """Test error when referencing a non-existent column."""
4057 # Create a test table first
41- sql ("CREATE OR REPLACE TABLE test_error_table (id INTEGER, name TEXT)" )
58+ sql (
59+ "CREATE OR REPLACE TABLE test_error_table (id INTEGER, name TEXT)" ,
60+ engine = duckdb_conn ,
61+ )
4262
4363 with pytest .raises (MarimoSQLException ) as exc_info :
44- sql ("SELECT invalid_column FROM test_error_table" )
64+ sql (
65+ "SELECT invalid_column FROM test_error_table" ,
66+ engine = duckdb_conn ,
67+ )
4568
4669 error = exc_info .value
4770 assert "invalid_column" in str (error )
4871 assert "test_error_table" in error .sql_statement
4972
5073 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
51- def test_syntax_error_missing_from (self ):
74+ def test_syntax_error_missing_from (self , duckdb_conn ):
5275 """Test syntax error when FROM keyword is misspelled."""
5376 with pytest .raises (MarimoSQLException ) as exc_info :
54- sql ("SELECT * FRM test_table" )
77+ sql ("SELECT * FRM test_table" , engine = duckdb_conn )
5578
5679 error = exc_info .value
5780 assert "syntax error" in str (error ).lower ()
5881 assert "SELECT * FRM test_table" in error .sql_statement
5982
6083 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
61- def test_syntax_error_malformed_expression (self ):
84+ def test_syntax_error_malformed_expression (self , duckdb_conn ):
6285 """Test syntax error with malformed SQL expression."""
6386 with pytest .raises (MarimoSQLException ) as exc_info :
64- sql ("SELECT ( FROM table" )
87+ sql ("SELECT ( FROM table" , engine = duckdb_conn )
6588
6689 error = exc_info .value
6790 assert (
@@ -73,20 +96,26 @@ def test_syntax_error_malformed_expression(self):
7396 not HAS_DUCKDB or not (HAS_PANDAS or HAS_POLARS ),
7497 reason = "DuckDB/Pandas not installed" ,
7598 )
76- def test_data_type_error (self ):
99+ def test_data_type_error (self , duckdb_conn ):
77100 """Test data type conversion errors."""
78- sql ("CREATE OR REPLACE TABLE test_type_table (id INTEGER)" )
79- sql ("INSERT INTO test_type_table VALUES (1)" )
101+ sql (
102+ "CREATE OR REPLACE TABLE test_type_table (id INTEGER)" ,
103+ engine = duckdb_conn ,
104+ )
105+ sql ("INSERT INTO test_type_table VALUES (1)" , engine = duckdb_conn )
80106
81107 with pytest .raises (MarimoSQLException ) as exc_info :
82- sql ("SELECT id / 'invalid_string' FROM test_type_table" )
108+ sql (
109+ "SELECT id / 'invalid_string' FROM test_type_table" ,
110+ engine = duckdb_conn ,
111+ )
83112
84113 error = exc_info .value
85114 # Error message varies by DuckDB version, just ensure we caught it
86115 assert len (str (error )) > 0
87116
88117 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
89- def test_long_sql_statement_truncation (self ):
118+ def test_long_sql_statement_truncation (self , duckdb_conn ):
90119 """Test that long SQL statements are truncated in error messages."""
91120 long_query = (
92121 "SELECT "
@@ -95,7 +124,7 @@ def test_long_sql_statement_truncation(self):
95124 )
96125
97126 with pytest .raises (MarimoSQLException ) as exc_info :
98- sql (long_query )
127+ sql (long_query , engine = duckdb_conn )
99128
100129 error = exc_info .value
101130 assert len (error .sql_statement ) == len (long_query )
@@ -155,16 +184,14 @@ class TestErrorUtilityFunctions:
155184 """Test marimo's SQL error handling utility functions."""
156185
157186 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
158- def test_is_sql_parse_error_duckdb (self ):
187+ def test_is_sql_parse_error_duckdb (self , duckdb_conn ):
159188 """Test detection of DuckDB parsing errors."""
160- import duckdb
161-
162189 with pytest .raises (Exception ) as exc_info :
163- duckdb .sql ("SELECT * FROM nonexistent_table" )
190+ duckdb_conn .sql ("SELECT * FROM nonexistent_table" )
164191 assert is_sql_parse_error (exc_info .value ) is True
165192
166193 with pytest .raises (Exception ) as exc_info :
167- duckdb .sql ("SELECT * FRM invalid_syntax" )
194+ duckdb_conn .sql ("SELECT * FRM invalid_syntax" )
168195 assert is_sql_parse_error (exc_info .value ) is True
169196
170197 @pytest .mark .skipif (not HAS_SQLGLOT , reason = "SQLGlot not installed" )
@@ -188,16 +215,15 @@ def test_is_sql_parse_error_marimo_sql_exception(self):
188215 assert is_sql_parse_error (marimo_sql_exception ) is True
189216
190217 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
191- def test_create_sql_error_from_exception (self ):
218+ def test_create_sql_error_from_exception (self , duckdb_conn ):
192219 """Test conversion of raw exception to MarimoSQLError."""
193- import duckdb
194220
195221 class MockCell :
196222 def __init__ (self , sql_statement : str ):
197223 self .sqls = [sql_statement ]
198224
199225 try :
200- duckdb .sql ("SELECT * FROM nonexistent_table" )
226+ duckdb_conn .sql ("SELECT * FROM nonexistent_table" )
201227 except Exception as e :
202228 mock_cell = MockCell ("SELECT * FROM nonexistent_table" )
203229 error = create_sql_error_from_exception (e , mock_cell )
@@ -210,10 +236,8 @@ def __init__(self, sql_statement: str):
210236 assert hasattr (error , "hint" )
211237
212238 @pytest .mark .requires ("duckdb" )
213- def test_create_sql_error_long_statement (self ):
239+ def test_create_sql_error_long_statement (self , duckdb_conn ):
214240 """Test SQL statement truncation in error creation."""
215- import duckdb
216-
217241 long_statement = (
218242 "SELECT "
219243 + ", " .join ([f"col_{ i } " for i in range (100 )])
@@ -225,7 +249,7 @@ def __init__(self, sql_statement: str):
225249 self .sqls = [sql_statement ]
226250
227251 try :
228- duckdb .sql (long_statement )
252+ duckdb_conn .sql (long_statement )
229253 except Exception as e :
230254 mock_cell = MockCell (long_statement )
231255 error = create_sql_error_from_exception (e , mock_cell )
@@ -277,10 +301,13 @@ class TestIntegrationAndEdgeCases:
277301 """Test complete error flow and edge cases."""
278302
279303 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
280- def test_sql_function_error_flow (self ):
304+ def test_sql_function_error_flow (self , duckdb_conn ):
281305 """Test complete error flow through mo.sql() function."""
282306 with pytest .raises (MarimoSQLException ) as exc_info :
283- sql ("SELECT * FROM definitely_nonexistent_table_12345" )
307+ sql (
308+ "SELECT * FROM definitely_nonexistent_table_12345" ,
309+ engine = duckdb_conn ,
310+ )
284311
285312 error = exc_info .value
286313 assert isinstance (error , MarimoSQLException )
@@ -312,34 +339,37 @@ class MockCellNoSqls:
312339 assert error .sql_statement == ""
313340
314341 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
315- def test_multiple_errors_in_sequence (self ):
342+ def test_multiple_errors_in_sequence (self , duckdb_conn ):
316343 """Test handling multiple SQL errors in sequence."""
317344 # First error
318345 with pytest .raises (MarimoSQLException ):
319- sql ("SELECT * FROM table1_nonexistent" )
346+ sql ("SELECT * FROM table1_nonexistent" , engine = duckdb_conn )
320347
321348 # Second error should still work
322349 with pytest .raises (MarimoSQLException ):
323- sql ("SELECT * FROM table2_nonexistent" )
350+ sql ("SELECT * FROM table2_nonexistent" , engine = duckdb_conn )
324351
325352 @pytest .mark .requires ("duckdb" )
326- def test_error_with_special_characters (self ):
353+ def test_error_with_special_characters (self , duckdb_conn ):
327354 """Test error handling with SQL containing special characters."""
328355 with pytest .raises (MarimoSQLException ):
329- sql ("SELECT * FROM 'table with spaces and quotes'" )
356+ sql (
357+ "SELECT * FROM 'table with spaces and quotes'" ,
358+ engine = duckdb_conn ,
359+ )
330360
331361 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
332- def test_duckdb_hints_preserved (self ):
362+ def test_duckdb_hints_preserved (self , duckdb_conn ):
333363 """Test that DuckDB hints like 'Did you mean?' are preserved in error messages."""
334- import duckdb
335-
336364 # Create a table to generate "Did you mean?" suggestions
337- duckdb .sql (
365+ duckdb_conn .sql (
338366 "CREATE OR REPLACE TABLE test_hints_table (id INT, name TEXT)"
339367 )
340368
341369 with pytest .raises (MarimoSQLException ) as exc_info :
342- sql ("SELECT * FROM test_hint" ) # Missing 's' in table name
370+ sql (
371+ "SELECT * FROM test_hint" , engine = duckdb_conn
372+ ) # Missing 's' in table name
343373
344374 error = exc_info .value
345375 error_msg = str (error )
@@ -349,17 +379,17 @@ def test_duckdb_hints_preserved(self):
349379 assert error .hint is None
350380
351381 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
352- def test_column_candidates_preserved (self ):
382+ def test_column_candidates_preserved (self , duckdb_conn ):
353383 """Test that column candidate hints are preserved in error messages."""
354- import duckdb
355-
356384 # Create a table to generate candidate binding suggestions
357- duckdb .sql (
385+ duckdb_conn .sql (
358386 "CREATE OR REPLACE TABLE test_columns (id INT, user_name TEXT, email TEXT)"
359387 )
360388
361389 with pytest .raises (MarimoSQLException ) as exc_info :
362- sql ("SELECT fullname FROM test_columns" ) # Wrong column name
390+ sql (
391+ "SELECT fullname FROM test_columns" , engine = duckdb_conn
392+ ) # Wrong column name
363393
364394 error = exc_info .value
365395 error_msg = str (error )
@@ -369,17 +399,15 @@ def test_column_candidates_preserved(self):
369399 assert error .hint is None
370400
371401 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
372- def test_hint_field_in_sql_error_struct (self ):
402+ def test_hint_field_in_sql_error_struct (self , duckdb_conn ):
373403 """Test that MarimoSQLError struct properly includes hint field."""
374- import duckdb
375-
376404 # Create table for hint generation
377- duckdb .sql (
405+ duckdb_conn .sql (
378406 "CREATE OR REPLACE TABLE hint_test_table (id INT, name TEXT)"
379407 )
380408
381409 try :
382- duckdb .sql ("SELECT * FROM hint_test" ) # Missing letters
410+ duckdb_conn .sql ("SELECT * FROM hint_test" ) # Missing letters
383411 except Exception as e :
384412
385413 class MockCell :
@@ -392,17 +420,15 @@ class MockCell:
392420 assert error_struct .hint is None
393421
394422 @pytest .mark .skipif (not HAS_DUCKDB , reason = "DuckDB not installed" )
395- def test_multiline_hints_preserved (self ):
423+ def test_multiline_hints_preserved (self , duckdb_conn ):
396424 """Test that multiline hints like function candidates are fully captured."""
397- import duckdb
398-
399425 # Create table for multiline hint generation
400- duckdb .sql (
426+ duckdb_conn .sql (
401427 "CREATE OR REPLACE TABLE hint_multiline_table (id INT, name TEXT)"
402428 )
403429
404430 try :
405- duckdb .sql (
431+ duckdb_conn .sql (
406432 "SELECT SUBSTRING(name) FROM hint_multiline_table"
407433 ) # Wrong args
408434 except Exception as e :
0 commit comments