Skip to content

Commit

Permalink
support sql and sql.fragment with arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
mmkal committed Mar 13, 2024
1 parent 300b0ee commit 5af8ca4
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 9 deletions.
35 changes: 35 additions & 0 deletions packages/client/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Note that @pgkit/migra and @pgkit/schemainspect are pure ports of their Python e
- [sql.unnest](#sqlunnest)
- [sql.join](#sqljoin)
- [sql.fragment](#sqlfragment)
- [nested `sql` tag](#nested-sql-tag)
- [sql.fragment](#sqlfragment-1)
- [sql.interval](#sqlinterval)
- [sql.binary](#sqlbinary)
- [sql.json](#sqljson)
Expand Down Expand Up @@ -269,6 +271,38 @@ expect(result).toEqual({id: 100, name: 'one hundred'})

### sql.fragment

Use `sql.fragment` to build reusable pieces which can be plugged into full queries.

```typescript
const idGreaterThan = (id: number) => sql.fragment`id >= ${id}`
const result = await client.any(sql`
select * from usage_test where ${idGreaterThan(2)}
`)

expect(result).toEqual([
{id: 2, name: 'two'},
{id: 3, name: 'three'},
])
```

### nested `sql` tag

You can also use `` sql`...` `` to create a fragment of SQL, but it's recommended to use `sql.fragment` instead for explicitness. Support for [type-generation](https://npmjs.com/package/@pgkit/typegen) is better using `sql.fragment` too.

```typescript
const idGreaterThan = (id: number) => sql`id >= ${id}`
const result = await client.any(sql`
select * from usage_test where ${idGreaterThan(2)}
`)

expect(result).toEqual([
{id: 2, name: 'two'},
{id: 3, name: 'three'},
])
```

### sql.fragment

Lets you create reusable SQL fragments, for example a where clause. Note that right now, fragments do not allow parameters.

```typescript
Expand Down Expand Up @@ -895,6 +929,7 @@ Generally, usage of a _client_ (or pool, to use the slonik term), should be iden
- no `stream` support yet
- See [future](#👽-future) for more details/which parity features are planned


### Added features/improvements

#### `sql`
Expand Down
26 changes: 18 additions & 8 deletions packages/client/src/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const sqlMethodHelpers: SQLMethodHelpers = {
name: nameQuery([query]),
token: 'sql',
values: [],
templateArgs: () => [[query]],
}),
type:
type =>
Expand All @@ -31,6 +32,7 @@ const sqlMethodHelpers: SQLMethodHelpers = {
sql: strings.join(''),
token: 'sql',
values: parameters,
templateArgs: () => [strings, ...parameters],
}
},
}
Expand Down Expand Up @@ -99,14 +101,14 @@ const sqlFn: SQLTagFunction = (strings, ...inputParameters) => {
}

case 'sql': {
if (param.values?.length) {
throw new QueryError(`Can't handle nested SQL with parameters`, {
cause: {query: {name: nameQuery(strings), sql, values: inputParameters}},
})
const [parts, ...fragmentValues] = param.templateArgs()
for (let i = 0; i < parts.length; i++) {
sql += parts[i]
if (i < fragmentValues.length) {
values.push(fragmentValues[i])
sql += '$' + String(i + 1)
}
}

sql += param.sql
// values.push(...param.values);
break
}

Expand All @@ -132,7 +134,14 @@ const sqlFn: SQLTagFunction = (strings, ...inputParameters) => {
}

case 'fragment': {
sql += param.args[0][0]
const [parts, ...fragmentValues] = param.args
for (let i = 0; i < parts.length; i++) {
sql += parts[i]
if (i < fragmentValues.length) {
values.push(fragmentValues[i])
sql += '$' + String(i + 1)
}
}
break
}

Expand All @@ -155,6 +164,7 @@ const sqlFn: SQLTagFunction = (strings, ...inputParameters) => {
sql,
token: 'sql',
values,
templateArgs: () => [strings, ...inputParameters],
}
}

Expand Down
6 changes: 5 additions & 1 deletion packages/client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export interface SQLQuery<Result = Record<string, unknown>, Values extends unkno
sql: string
values: Values
parse: (input: unknown) => Result
/** @internal */
templateArgs: () => [strings: readonly string[], ...inputParameters: readonly any[]]
}

export type TimeUnit = 'years' | 'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds'
Expand Down Expand Up @@ -62,6 +64,8 @@ export type SqlFragment = {
token: 'sql'
sql: string
values: unknown[]
/** @internal */
templateArgs: () => [strings: readonly string[], ...inputParameters: readonly any[]]
}
/**
* "string" type covers all type name identifiers – the literal values are added only to assist developer
Expand Down Expand Up @@ -94,7 +98,7 @@ export type SQLTagHelperParameters = {
array: [values: readonly PrimitiveValueExpression[], memberType: MemberType]
binary: [data: Buffer]
date: [date: Date]
fragment: [parts: TemplateStringsArray]
fragment: [parts: TemplateStringsArray, ...values: readonly ValueExpression[]]
identifier: [names: readonly string[]]
interval: [interval: IntervalInput]
join: [members: readonly ValueExpression[], glue: SqlFragment]
Expand Down
31 changes: 31 additions & 0 deletions packages/client/test/api-usage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,37 @@ test('sql.join', async () => {
expect(result).toEqual({id: 100, name: 'one hundred'})
})

/**
* Use `sql.fragment` to build reusable pieces which can be plugged into full queries.
*/
test('sql.fragment', async () => {
const idGreaterThan = (id: number) => sql.fragment`id >= ${id}`
const result = await client.any(sql`
select * from usage_test where ${idGreaterThan(2)}
`)

expect(result).toEqual([
{id: 2, name: 'two'},
{id: 3, name: 'three'},
])
})

/**
* You can also use `` sql`...` `` to create a fragment of SQL, but it's recommended to use `sql.fragment` instead for explicitness.
* Support for [type-generation](https://npmjs.com/package/@pgkit/typegen) is better using `sql.fragment` too.
*/
test('nested `sql` tag', async () => {
const idGreaterThan = (id: number) => sql`id >= ${id}`
const result = await client.any(sql`
select * from usage_test where ${idGreaterThan(2)}
`)

expect(result).toEqual([
{id: 2, name: 'two'},
{id: 3, name: 'three'},
])
})

/**
* Lets you create reusable SQL fragments, for example a where clause. Note that right now, fragments do not allow parameters.
*/
Expand Down

0 comments on commit 5af8ca4

Please sign in to comment.