Skip to content

Commit 19fa170

Browse files
committed
Fix QueryBuilder objects being parameterized instead of inlined in t-strings
When interpolating a QueryBuilder into a regular t-string (e.g., as a subquery), it was incorrectly treated as a parameter instead of being inlined as SQL. Added proper handling in _sqlize() to detect objects with to_tsql() method and inline their SQL parts, matching behavior in as_values() and as_set().
1 parent 8ce9faf commit 19fa170

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

tests/test_query_builder.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,3 +1378,56 @@ def test_delete_with_all_rows_works():
13781378
assert 'DELETE FROM users' in sql
13791379
assert 'WHERE' not in sql
13801380
assert params == []
1381+
1382+
1383+
def test_nested_querybuilder_in_tstring():
1384+
"""Test that QueryBuilder can be interpolated into t-strings as subqueries"""
1385+
# Create a subquery
1386+
subquery = Posts.select(Posts.user_id).where(Posts.id == 42)
1387+
1388+
# Interpolate it into a regular t-string
1389+
query = tsql.TSQL(t'SELECT *, ({subquery}) as post_user FROM users')
1390+
sql, params = query.render()
1391+
1392+
# The subquery SQL should be inlined, not parameterized
1393+
assert 'SELECT *,' in sql
1394+
assert 'SELECT posts.user_id FROM posts WHERE posts.id = ?' in sql
1395+
assert 'as post_user FROM users' in sql
1396+
# The parameter from the subquery should be in the params list
1397+
assert params == [42]
1398+
1399+
1400+
def test_nested_querybuilder_with_multiple_params():
1401+
"""Test nested QueryBuilder with multiple parameters from both queries"""
1402+
# Create a subquery with a parameter
1403+
subquery = Posts.select(Posts.id).where(Posts.user_id == 100)
1404+
1405+
# Use it in a main query that also has parameters
1406+
query = tsql.TSQL(t'SELECT * FROM users WHERE id = {5} AND post_count > ({subquery})')
1407+
sql, params = query.render()
1408+
1409+
# Both parameters should be in the list
1410+
assert params == [5, 100]
1411+
# The subquery should be inlined
1412+
assert 'SELECT posts.id FROM posts WHERE posts.user_id = ?' in sql
1413+
1414+
1415+
def test_nested_querybuilder_with_different_styles():
1416+
"""Test that nested QueryBuilders work with different parameter styles"""
1417+
from tsql.styles import NUMERIC_DOLLAR, NAMED
1418+
1419+
subquery = Posts.select(Posts.user_id).where(Posts.id == 42)
1420+
1421+
# Test with NUMERIC_DOLLAR style
1422+
query = tsql.TSQL(t'SELECT *, ({subquery}) as post_user FROM users WHERE id = {5}')
1423+
sql, params = query.render(style=NUMERIC_DOLLAR)
1424+
assert '$1' in sql and '$2' in sql
1425+
assert params == [42, 5]
1426+
1427+
# Test with NAMED style
1428+
sql, params = query.render(style=NAMED)
1429+
# Check that both parameters are present (names may vary)
1430+
assert isinstance(params, dict)
1431+
assert len(params) == 2
1432+
assert 42 in params.values() and 5 in params.values()
1433+
assert 'SELECT posts.user_id FROM posts WHERE posts.id = :' in sql

tsql/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ def _sqlize(cls, val: Interpolation|Template|Any) -> list[str|Parameter]:
144144
return val.value._sql_parts
145145
case "", Template():
146146
return TSQL(value)._sql_parts
147+
case '', x if hasattr(x, 'to_tsql'):
148+
logger.debug("Inlining QueryBuilder object: %s", type(x).__name__)
149+
return x.to_tsql()._sql_parts
147150
case '', None:
148151
return [Parameter(val.expression, None)]
149152
# case 'as_array', list():

0 commit comments

Comments
 (0)