Skip to content

Commit f02bfe2

Browse files
committed
Allow for nested templates
This allows for a template to be specified as a value as allows for complex query parts to be added/removed as desired.
1 parent 92a9a11 commit f02bfe2

File tree

4 files changed

+30
-9
lines changed

4 files changed

+30
-9
lines changed

src/sql_tstring/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
Group,
1717
Literal,
1818
Operator,
19-
parse_template,
19+
parse,
2020
Part,
2121
Placeholder,
2222
PlaceholderType,
@@ -95,7 +95,7 @@ def sql(
9595
else:
9696
raise ValueError("Must call with a template, or a query string and values")
9797

98-
parsed_queries = parse_template(template)
98+
parsed_queries = parse(template)
9999
result_str = ""
100100
result_values: list[typing.Any] = []
101101
ctx = get_context()

src/sql_tstring/parser.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from enum import auto, Enum, unique
66
from typing import cast
77

8+
from sql_tstring.t import Template as TTemplate
9+
810
try:
911
from string.templatelib import Interpolation, Template # type: ignore[import-untyped]
1012
except ImportError:
@@ -323,19 +325,24 @@ class Operator:
323325
type Element = Node | Operator | Part | Placeholder
324326

325327

326-
def parse_template(template: Template) -> list[Statement]:
328+
def parse(template: Template) -> list[Statement]:
327329
statements = [Statement()]
328330
current_node: Node = statements[0]
331+
_parse_template(template, current_node, statements)
332+
return statements
333+
329334

335+
def _parse_template(template: Template, current_node: Node, statements: list[Statement]) -> None:
330336
for item in template:
331337
match item:
332338
case Interpolation(value, _, _, _): # type: ignore[misc]
333-
_parse_placeholder(current_node, value)
339+
if isinstance(value, (Template, TTemplate)):
340+
_parse_template(value, current_node, statements)
341+
else:
342+
_parse_placeholder(current_node, value)
334343
case str() as raw:
335344
current_node = _parse_string(raw, current_node, statements)
336345

337-
return statements
338-
339346

340347
def _parse_placeholder(
341348
current_node: Node,

tests/test_parameters.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from sql_tstring import RewritingValue, sql, sql_context
5+
from sql_tstring import RewritingValue, sql, sql_context, t
66

77
TZ = "uk"
88

@@ -178,3 +178,11 @@ def test_is_null(query: str, expected_query: str, expected_values: list[Any]) ->
178178
def test_is_not_null(query: str, expected_query: str, expected_values: list[Any]) -> None:
179179
val = RewritingValue.IS_NOT_NULL
180180
assert (expected_query, expected_values) == sql(query, locals())
181+
182+
183+
def test_nested() -> None:
184+
a = "a"
185+
inner = t("x = {a}", locals())
186+
query, values = sql("SELECT x FROM y WHERE {inner}", locals())
187+
assert query == "SELECT x FROM y WHERE x = ?"
188+
assert values == ["a"]

tests/test_parsing.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from sql_tstring import sql
1+
from sql_tstring import sql, t
22

33

44
def test_literals() -> None:
@@ -35,7 +35,7 @@ def test_delete_from() -> None:
3535
assert query == "DELETE FROM y WHERE x = 'NONE'"
3636

3737

38-
def test_nested() -> None:
38+
def test_function() -> None:
3939
query, _ = sql("SELECT COALESCE(x, now())", locals())
4040
assert query == "SELECT COALESCE(x , now())"
4141

@@ -99,3 +99,9 @@ def test_functional_subquery() -> None:
9999
locals(),
100100
)
101101
assert query == "SELECT x , ARRAY(SELECT a FROM b) FROM y"
102+
103+
104+
def test_nested() -> None:
105+
inner = t("x = 'a'", locals())
106+
query, _ = sql("SELECT x FROM y WHERE {inner}", locals())
107+
assert query == "SELECT x FROM y WHERE x = 'a'"

0 commit comments

Comments
 (0)