Skip to content

Commit

Permalink
feat: remove multiple methods in favor of sql.join
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

The latest changes remove a large number of helper methods for building queries:

* `assignmentList`
* `booleanExpression`
* `comparisonPredicate`
* `identifierList`
* `rawList`
* `tuple`
* `tupleList`
* `valueList`

The original motivation for having these methods was to provide static types that are specific to different contexts of the query (e.g. identifier list containing only of identifier tokens). However, as programs using Slonik grew in complexity, so did the requirements for having more complex expressions: a requirement to support any SQL expressions in all of the contexts became evident. This made strict types less useful.

Meanwhile, the API has proliferated with a long list of methods that carry semantic significance but do not significantly differ in their functionality. This created an unnecessary steep learning curve for new adopters and confusing code when any of those methods is misused (e.g. describing value list using tuple list would create confusion about the intent of the code).

All of the removed methods shared a common requirement: to create an expression consisting of a dynamic number of members. This requirement was implemented using a new method `sql.join`. `sql.join` works similar to `Array.join` and it can be used to express any of the above methods, e.g.

```js
sql.tuple(['foo', 'bar'])

```

can be writen as:

```js
sql`
  (${sql.list([
    sql.identifier('foo'),
    sql.identifier('bar')
  ]sql`, `)})
`

```

both produce:

```sql
(foo, bar)

```
`assignmentList` and `comparisonPredicate` were removed for a similar reason: both can be expressed using existing methods, e.g.

```js
sql.assignmentList({
  bar: 'baz',
  qux: 'quux'
});

```

becomes:

```js
sql.join([
  sql`${sql.identifier(['bar'])} = ${'baz'}`,
  sql`${sql.identifier(['qux'])} = ${'quux'}`
], sql`, `)

// of, if identifiers are static, then just:

sql.join([
  sql`bar = ${'baz'}`,
  sql`qux = ${'quux'}`
], sql`, `)

```
  • Loading branch information
gajus committed Sep 19, 2019
1 parent eaf77da commit 910177d
Show file tree
Hide file tree
Showing 30 changed files with 279 additions and 2,269 deletions.
15 changes: 12 additions & 3 deletions .README/ABOUT_SLONIK.md
Expand Up @@ -210,15 +210,24 @@ connection.query(sql`SELECT ${userInput}`);

Slonik takes over from here and constructs a query with value bindings, and sends the resulting query text and parameters to the PostgreSQL. As `sql` tagged template literal is the only way to execute the query, it adds a strong layer of protection against accidental unsafe user-input handling due to limited knowledge of the SQL client API.

As Slonik restricts user's ability to generate and execute dynamic SQL, it provides helper functions used to generate fragments of the query and the corresponding value bindings, e.g. [`sql.identifier`](#sqlidentifier), [`sql.tuple`](#sqltuple), [`sql.tupleList`](#sqltuplelist), [`sql.unnest`](#sqlunnest) and [`sql.valueList`](#sqlvaluelist). These methods generate tokens that the query executor interprets to construct a safe query, e.g.
As Slonik restricts user's ability to generate and execute dynamic SQL, it provides helper functions used to generate fragments of the query and the corresponding value bindings, e.g. [`sql.identifier`](#sqlidentifier), [`sql.join`](#sqljoin) and [`sql.unnest`](#sqlunnest). These methods generate tokens that the query executor interprets to construct a safe query, e.g.

```js
connection.query(sql`
SELECT ${sql.identifier(['foo', 'a'])}
FROM (
VALUES ${sql.tupleList([['a1', 'b1', 'c1'], ['a2', 'b2', 'c2']])}
VALUES
(
${sql.join(
[
sql.join(['a1', 'b1', 'c1'], sql`, `),
sql.join(['a2', 'b2', 'c2'], sql`, `)
],
sql`), (`
)}
)
) foo(a, b, c)
WHERE foo.b IN (${sql.valueList(['c1', 'a2'])})
WHERE foo.b IN (${sql.join(['c1', 'a2'], sql`, `)})
`);

```
Expand Down

0 comments on commit 910177d

Please sign in to comment.