TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Show HN: SQL-tString a t-string SQL builder in Python

85 pointsby pgjones3 days ago
SQL-tString is a SQL builder that utilises the recently accepted PEP-750, <a href="https:&#x2F;&#x2F;peps.python.org&#x2F;pep-0750&#x2F;" rel="nofollow">https:&#x2F;&#x2F;peps.python.org&#x2F;pep-0750&#x2F;</a>, t-strings to build SQL queries, for example,<p><pre><code> from sql_tstring import sql val = 2 query, values = sql(t&quot;SELECT x FROM y WHERE x = {val}&quot;) assert query == &quot;SELECT x FROM y WHERE x = ?&quot; assert values == [2] db.execute(query, values) # Most DB engines support this </code></pre> The placeholder ? protects against SQL injection, but cannot be used everywhere. For example, a column name cannot be a placeholder. If you try this SQL-tString will raise an error,<p><pre><code> col = &quot;x&quot; sql(t&quot;SELECT {col} FROM y&quot;) # Raises ValueError </code></pre> To proceed you&#x27;ll need to declare what the valid values of col can be,<p><pre><code> from sql_tstring import sql_context with sql_context(columns=&quot;x&quot;): query, values = sql(t&quot;SELECT {col} FROM y&quot;) assert query == &quot;SELECT x FROM y&quot; assert values == [] </code></pre> Thus allowing you to protect against SQL injection.<p>As t-strings are format strings you can safely format the literals you&#x27;d like to pass as variables,<p><pre><code> text = &quot;world&quot; query, values = sql(t&quot;SELECT x FROM y WHERE x LIKE &#x27;%{text}&#x27;&quot;) assert query == &quot;SELECT x FROM y WHERE x LIKE ?&quot; assert values == [&quot;%world&quot;] </code></pre> This is especially useful when used with the Absent rewriting value.<p>SQL-tString is a SQL builder and as such you can use special RewritingValues to alter and build the query you want at runtime. This is best shown by considering a query you sometimes want to search by one column a, sometimes by b, and sometimes both,<p><pre><code> def search( *, a: str | AbsentType = Absent, b: str | AbsentType = Absent ) -&gt; tuple[str, list[str]]: return sql(t&quot;SELECT x FROM y WHERE a = {a} AND b = {b}&quot;) assert search() == &quot;SELECT x FROM y&quot;, [] assert search(a=&quot;hello&quot;) == &quot;SELECT x FROM y WHERE a = ?&quot;, [&quot;hello&quot;] assert search(b=&quot;world&quot;) == &quot;SELECT x FROM y WHERE b = ?&quot;, [&quot;world&quot;] assert search(a=&quot;hello&quot;, b=&quot;world&quot;) == ( &quot;SELECT x FROM y WHERE a = ? AND b = ?&quot;, [&quot;hello&quot;, &quot;world&quot;] ) </code></pre> Specifically Absent (which is an alias of RewritingValue.ABSENT) will remove the expression it is present in, and if there an no expressions left after the removal it will also remove the clause.<p>The other rewriting values I&#x27;ve included are handle the frustrating case of comparing to NULL, for example the following is valid but won&#x27;t work as you&#x27;d likely expect,<p><pre><code> optional = None sql(t&quot;SELECT x FROM y WHERE x = {optional}&quot;) </code></pre> Instead you can use IsNull to achieve the right result,<p><pre><code> from sql_tstring import IsNull optional = IsNull query, values = sql(t&quot;SELECT x FROM y WHERE x = {optional}&quot;) assert query == &quot;SELECT x FROM y WHERE x IS NULL&quot; assert values == [] </code></pre> There is also a IsNotNull for the negated comparison.<p>The final feature allows for complex query building by nesting a t-string within the existing,<p><pre><code> inner = t&quot;x = &#x27;a&#x27;&quot; query, _ = sql(t&quot;SELECT x FROM y WHERE {inner}&quot;) assert query == &quot;SELECT x FROM y WHERE x = &#x27;a&#x27;&quot; </code></pre> This library can be used today without Python3.14&#x27;s t-strings with some limitations, <a href="https:&#x2F;&#x2F;github.com&#x2F;pgjones&#x2F;sql-tstring?tab=readme-ov-file#pre-python-314-usage">https:&#x2F;&#x2F;github.com&#x2F;pgjones&#x2F;sql-tstring?tab=readme-ov-file#pr...</a>, and I&#x27;ve been doing so this year. Thoughts and feedback very welcome.

13 comments

hombre_fatal3 days ago
I thought this was just going to be the same ol &quot;where id = {id}&quot; interpolation but dang, those are some crazy examples.<p>I can imagine the behavior takes some trial and error to figure out, but it looks like you can write a search() query that contains fully-loaded sql statement as if all facets were provided, yet you can make each facet optional and those expressions will get removed from the statement.<p>That would be much nicer than the traditional route of building up a where clause with a bunch of if-statements where it&#x27;s very hard to understand what the final where clause might look like without print(statement).<p>I&#x27;d rather write the whole SQL statement upfront which this seems to let you do.
评论 #44008289 未加载
bvrmn2 days ago
I&#x27;m author of sqlbind[1] and since t-strings announce have been thinking really hard how to incorporate it. Like obvious cases `t&quot;select * from table where id = {id}&quot;` are tempting only at first glance. But simple queries could be written by other means. Dynamic ones where you should drop part of query by some condition is a real problem.<p>Your solution is impressive. It would be quite hard to support crazy sql extensions, for example for ClickHouse but as a concept it really ingenious.<p>[1]: <a href="https:&#x2F;&#x2F;github.com&#x2F;baverman&#x2F;sqlbind">https:&#x2F;&#x2F;github.com&#x2F;baverman&#x2F;sqlbind</a>
schultzer3 days ago
Just took a quick look, and it seams like the parser is hand written which is great, but you probably want to build a lexer and parser based on the BNF grammar take a look at how I do it here <a href="https:&#x2F;&#x2F;github.com&#x2F;elixir-dbvisor&#x2F;sql&#x2F;tree&#x2F;main&#x2F;lib">https:&#x2F;&#x2F;github.com&#x2F;elixir-dbvisor&#x2F;sql&#x2F;tree&#x2F;main&#x2F;lib</a> and do conformance testing with <a href="https:&#x2F;&#x2F;github.com&#x2F;elliotchance&#x2F;sqltest">https:&#x2F;&#x2F;github.com&#x2F;elliotchance&#x2F;sqltest</a>
评论 #44006014 未加载
caturopath3 days ago
For any of you also confused by<p><pre><code> with sql_context(columns=&quot;x&quot;): query, values = sql(t&quot;SELECT {col} FROM y&quot;) </code></pre> I think<p>1. this is relying on the `col = &quot;x&quot;` in the previous example<p>2. columns is a set of strings, so it might be sql_context(columns={&quot;foo&quot;, &quot;bar&quot;, &quot;x&quot;}) to allow those as valid options. It just happens that &quot;x&quot; is a collection supporting the `in` operator so it works much like the set {&quot;x&quot;} would.<p>2a. (You might hope that something would convert such a string to a singleton set, but I don&#x27;t think it does, which would have weird results with a multi-letter string.)
评论 #44007704 未加载
jitl3 days ago
How does the SQL parsing work for the rewrites like removing expressions? I have a project using some non-standard SQL features and we have quite complex queries going on, so the rewriting makes me a bit nervous. The great thing about tstrings for sql is that it’s a total escape from “magick” creating ineffable and unknown sql replacing with very straightforward what you see is what you get sql right in the source code.<p>Do you support templating a sql tstring into an sql tstring for composition?<p>I use that feature a lot with the roughly equivalent TypeScript sql`…` template literals for the NOT NULL thing and handling absence but it’s all ternaries in “user space”.
评论 #44006039 未加载
throwaway1274822 days ago
The columns feature seems somewhat dangerous at first glance. What if I wanted different sets of allowed columns for different parts of the query? Would I have to provide the superset of columns supported in both subqueries &#x2F; tables?
评论 #44016725 未加载
badmonster3 days ago
I had a question about dynamic query fragments: is there a recommended way to compose larger queries from smaller pieces while preserving placeholder safety and avoiding manual string concatenation? For example, conditionally building WHERE clauses or joins from pre-defined fragments?
ilyagr3 days ago
I think that, since you don&#x27;t allow `sql(t&quot;SELECT {a-1}&quot;)`, you should allow `sql(t&quot;SELECT {}&quot;, a - 1)`, as long as that is possible with t-strings. This would be similar to Rust&#x27;s `format!` then.
评论 #44008977 未加载
bencyoung3 days ago
Looks very nice but the Absent functionality seems like a potential foot gun to me, easy to remove an entire WHERE clause and blow up a whole table! If I have a filter in sql query it&#x27;s because I really want it to be used!
评论 #44008717 未加载
schultzer3 days ago
Not really sure what a t string is or if it’s a macro, but feel similar to <a href="https:&#x2F;&#x2F;github.com&#x2F;elixir-dbvisor&#x2F;sql">https:&#x2F;&#x2F;github.com&#x2F;elixir-dbvisor&#x2F;sql</a> but less elegant and ergonomic.
评论 #44006827 未加载
评论 #44006927 未加载
sirfz3 days ago
Technically this enforces parameterized queries since all it does is basically return the parameterized query in the given db client dialect and the list of parameters. It could be useful for building a unified interface for all sql db clients for example (instead of having to remember whether parameters are %s or ? or {param}, etc). On the other hand, db clients can utilize t-strings to directly allow you to safely execute queries such as t&quot;select * from table where id = {id}&quot; without passing any extra parameters.
评论 #44006695 未加载
owlstuffing3 days ago
Languages should strive to <i>type safely</i> inline SQL and other structured data.<p>With Java the manifold project achieves this via compiler plugin. The manifold-sql[1] module provides inline, type safe, native SQL.<p>1.<a href="https:&#x2F;&#x2F;github.com&#x2F;manifold-systems&#x2F;manifold&#x2F;blob&#x2F;master&#x2F;manifold-deps-parent&#x2F;manifold-sql&#x2F;readme.md">https:&#x2F;&#x2F;github.com&#x2F;manifold-systems&#x2F;manifold&#x2F;blob&#x2F;master&#x2F;man...</a>
评论 #44006657 未加载
评论 #44006691 未加载
90s_dev3 days ago
Your library looks great. But a tangential rant about t-strings, using lexical scope for placeholder lookup is just a terrible, <i>terrible</i> design. It should be explicitly passed in a dictionary. I&#x27;m not sure why they made this decision.
评论 #44005924 未加载
评论 #44006033 未加载