Skip to content

Commit e6b956a

Browse files
authored
ensure new connection for each test sql (#9356)
## 📝 Summary <!-- If this PR closes any issues, list them here by number (e.g., Closes #123). Detail the specific changes made in this pull request. Explain the problem addressed and how it was resolved. If applicable, provide before and after comparisons, screenshots, or any relevant details to help reviewers understand the changes easily. --> Supersedes #9352 by ensuring each test runs in a duckdb connection that closes automatically. ## 📋 Pre-Review Checklist <!-- These checks need to be completed before a PR is reviewed --> - [x] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [x] Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it. - [ ] Video or media evidence is provided for any visual changes (optional). <!-- PR is more likely to be merged if evidence is provided for changes made --> ## ✅ Merge Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [ ] Documentation has been updated where applicable, including docstrings for API changes. - [ ] Tests have been added for the changes made. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Use a fresh in-memory `duckdb` connection per test to isolate state and prevent flaky SQL error-handling tests. Tests now pass `engine=duckdb_conn` and call `duckdb_conn.sql(...)`, so connections close automatically after each test. - **Bug Fixes** - Add `pytest` fixture `duckdb_conn` that creates `duckdb.connect(':memory:')` and closes it after each test. - Replace implicit engine/global `duckdb.sql(...)` with explicit `engine=duckdb_conn` and `duckdb_conn.sql(...)`. - Stabilizes tests for missing tables/columns, syntax errors, hints, and long-statement truncation. <sup>Written for commit 359e017. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. -->
1 parent ccc1841 commit e6b956a

1 file changed

Lines changed: 80 additions & 54 deletions

File tree

tests/_sql/test_sql_error_handling.py

Lines changed: 80 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -20,48 +20,71 @@
2020
HAS_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+
2340
class 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

Comments
 (0)