Hacker News new | ask | show | jobs
by hn_throwaway_99 1202 days ago
> - Tagged template strings. This just feels dirty to me. Probably won't use, but when I see it in a code base I won't be so confused at least

Tagged template strings are an absolutely brilliant feature and have tons of valuable uses. In particular, many sql libraries in node let you do this:

    const query = sql`select foo from bar where zed = ${param}`;
From a developer standpoint it "feels" just like you're doing string concatenation, but in reality the query variable will contain a prepared statement so that it safely prevents any kind of SQL injection, e.g. it gets parsed to

    {
        sql: "select foo from bar where zed = ?",
        parameters: [param]
    }
There are lots of use cases where things are easily expressed as an interpolated string, but the thing you want back is NOT just a plain string, and tagged template literals are great for that. It's also a nice way to call a parser, e.g. many GraphQL libraries let you do:

    const parsedGraphQLSchema = gql`type Query { foo: Int }`;
2 comments

Is it really an good thing that a vulnerable sql string interpolation code pattern and this sql tagged string look and feel the same?
Actually, yes, it is. The way these libraries work, since the thing that is parsed is NOT just a plain string, in most cases it's impossible to have sql injection without doing some deliberately nasty stuff. That is, you can't just do this:

    const query = `select foo from bar where zed = ${param}`; // forgot the sql tag
    await runQuery(query);
In that case, the type of query is just string, but the `runQuery` method doesn't take strings, it takes a parsed query, so that wouldn't work.

After using the tagged template literal pattern for SQL queries exclusively for the past couple years, I can't say enough how awesome it is to use in practice. Libraries even let you do strong typing with TypeScript to define the expected structure of the result, e.g.

    sql<MyExpectedReturnType>`select foo from bar where zed = ${param}`
> Libraries even let you do strong typing with TypeScript to define the expected structure of the result.

The tagged template does not return a string in this case?

No, it usually returns a parsed object. For example the gql tag in the apollo client libraries return a completely parsed query with all its various children and sub-objects.
I was thinking the same thing... If I do that I'm going to forget the "sql" part at least once and nothing's going to alert me about it.
The way libraries work it's impossible to forget the "sql" part and still have that query be executed - see my sibling comment.
Why wouldn’t it be? Do you think developers get inspired by this slick API and decide to write functions that talk directly to the database using unescaped interpolated strings? I doubt it.
I fear that syntactic sugar creates as many problems as it solves. For instance, one might wish to sort the results by whitelisted column:

    query = sql`select foo from bar where zed = ${p} order by ${col} asc`;
Unless the lib implements a real SQL parser for the right dialect, it will quote each expression in the same way, and will either fail or produce a broken SQL.
Definitely a lot of misconceptions around how this would work. Just check out something like slonik, https://github.com/gajus/slonik, which is an excellent implementation.

The example you gave actually isn't valid, because what you're doing is generating SQL dynamically, and that doesn't work the way prepared statements work. That is, you can't have a prepared statement like "select foo from bar where zed = ? order by ? asc", because with prepared statements the question marks can only substitute for VALUES, not schema names. So if you wanted to do something like that it slonik, it would fail. With slonik you CAN do dynamic SQL, that is guaranteed to be safe and checked at compile time with TypeScript, because you can nest SQL tagged templates. That is you can do this:

    const colToSortBy = useFoo ? sql`foo` : sql`bar`;
    const query = sql`select col from mytable order by ${colToSortBy}`;
In that case slonik will know how to safely "merge" the parent and child parsed SQL.
We actually did the same for ArangoDB (I think we first did this in 2015, I remember being surprised nobody had done something similar for SQL at the time). Here's the JS driver's current implementation of it:

https://github.com/arangodb/arangojs/blob/main/src/aql.ts#L1...

Basically the `aql` template tag returns an object that can also be fed back into it and we also deduplicate arguments to avoid sending redundant data over the wire. There's also an escape hatch via a helper function (`aql.literal`) in cases where you need to insert literals that aren't known at compile time (e.g. you load query filters from a configuration file).