Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/duckdb_py/pyconnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1600,8 +1600,9 @@ unique_ptr<DuckDBPyRelation> DuckDBPyConnection::RunQuery(const py::object &quer

// Attempt to create a Relation for lazy execution if possible
shared_ptr<Relation> relation;
if (py::none().is(params)) {
// FIXME: currently we can't create relations with prepared parameters
bool has_params = !py::none().is(params) && py::len(params) > 0;
if (!has_params) {
// No params (or empty params) — use lazy QueryRelation path
{
D_ASSERT(py::gil_check());
py::gil_scoped_release gil;
Expand Down
40 changes: 40 additions & 0 deletions tests/fast/api/test_sql_params_performance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import time


class TestSqlEmptyParams:
"""Empty params should use lazy QueryRelation path (same as params=None)."""

def test_empty_list_returns_same_result(self, duckdb_cursor):
"""sql(params=[]) returns same data as sql(params=None)."""
duckdb_cursor.execute("CREATE TABLE t AS SELECT i FROM range(10) t(i)")
expected = duckdb_cursor.sql("SELECT * FROM t").fetchall()
result = duckdb_cursor.sql("SELECT * FROM t", params=[]).fetchall()
assert result == expected

def test_empty_dict_returns_same_result(self, duckdb_cursor):
"""sql(params={}) returns same data as sql(params=None)."""
duckdb_cursor.execute("CREATE TABLE t AS SELECT i FROM range(10) t(i)")
expected = duckdb_cursor.sql("SELECT * FROM t").fetchall()
result = duckdb_cursor.sql("SELECT * FROM t", params={}).fetchall()
assert result == expected

def test_empty_tuple_returns_same_result(self, duckdb_cursor):
"""sql(params=()) returns same data as sql(params=None)."""
duckdb_cursor.execute("CREATE TABLE t AS SELECT i FROM range(10) t(i)")
expected = duckdb_cursor.sql("SELECT * FROM t").fetchall()
result = duckdb_cursor.sql("SELECT * FROM t", params=()).fetchall()
assert result == expected

def test_empty_params_is_chainable(self, duckdb_cursor):
"""Empty params produces a real relation that supports chaining."""
duckdb_cursor.execute("CREATE TABLE t AS SELECT i FROM range(10) t(i)")
result = duckdb_cursor.sql("SELECT * FROM t", params=[]).filter("i < 3").order("i").fetchall()
assert result == [(0,), (1,), (2,)]

def test_empty_params_explain_is_fast(self, duckdb_cursor):
"""Empty params explain should not trigger expensive ToString."""
duckdb_cursor.execute("CREATE TABLE t AS SELECT i FROM range(100000) t(i)")
t0 = time.perf_counter()
duckdb_cursor.sql("SELECT * FROM t", params=[]).explain()
elapsed = time.perf_counter() - t0
assert elapsed < 5.0, f"explain() took {elapsed:.2f}s, expected < 5s"