Skip to content

Commit

Permalink
feat: allow sql.raw as a sql.array memberType (closes #59)
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Aug 4, 2019
1 parent bf7c20f commit 60488bf
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 10 deletions.
32 changes: 30 additions & 2 deletions .README/QUERY_BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Queries are built using methods of the `sql` tagged template literal.
```js
(
values: $ReadOnlyArray<PrimitiveValueExpressionType>,
memberType: string
memberType: string | RawSqlTokenType
) => ArraySqlTokenType;

```
Expand All @@ -25,7 +25,7 @@ Produces:

```js
{
sql: 'SELECT $1::int4[]',
sql: 'SELECT $1::"int4"[]',
values: [
[
1,
Expand All @@ -37,6 +37,34 @@ Produces:

```

#### `sql.array` `memberType`

If `memberType` is a string, then it is treated as a type name identifier and will be quoted using double quotes, i.e. `sql.array([1, 2, 3], 'int4')` is equivalent to `$1::"int4"[]`. The implication is that keywrods that are often used interchangeably with type names are not going to work, e.g. [`int4`](https://github.com/postgres/postgres/blob/69edf4f8802247209e77f69e089799b3d83c13a4/src/include/catalog/pg_type.dat#L74-L78) is a type name identifier and will work. However, [`int`](https://github.com/postgres/postgres/blob/69edf4f8802247209e77f69e089799b3d83c13a4/src/include/parser/kwlist.h#L213) is a keyword and will not work. You can either use type name identifiers or you can construct custom member using `sql.raw`, e.g.

```js
await connection.query(sql`
SELECT (${sql.array([1, 2, 3], sql.raw('int[]'))})
`);

```

Produces:

```js
{
sql: 'SELECT $1::int[]',
values: [
[
1,
2,
3
]
]
}

```

#### `sql.array` vs `sql.valueList`

Unlike `sql.valueList`, `sql.array` generates a stable query of a predictable length, i.e. regardless of the number of the values in the array, the generated query remains the same:

Expand Down
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1137,7 +1137,7 @@ Queries are built using methods of the `sql` tagged template literal.
```js
(
values: $ReadOnlyArray<PrimitiveValueExpressionType>,
memberType: string
memberType: string | RawSqlTokenType
) => ArraySqlTokenType;

```
Expand All @@ -1155,7 +1155,7 @@ Produces:
```js
{
sql: 'SELECT $1::int4[]',
sql: 'SELECT $1::"int4"[]',
values: [
[
1,
Expand All @@ -1167,6 +1167,36 @@ Produces:

```
<a name="slonik-query-building-sql-array-sql-array-membertype"></a>
#### <code>sql.array</code> <code>memberType</code>
If `memberType` is a string, then it is treated as a type name identifier and will be quoted using double quotes, i.e. `sql.array([1, 2, 3], 'int4')` is equivalent to `$1::"int4"[]`. The implication is that keywrods that are often used interchangeably with type names are not going to work, e.g. [`int4`](https://github.com/postgres/postgres/blob/69edf4f8802247209e77f69e089799b3d83c13a4/src/include/catalog/pg_type.dat#L74-L78) is a type name identifier and will work. However, [`int`](https://github.com/postgres/postgres/blob/69edf4f8802247209e77f69e089799b3d83c13a4/src/include/parser/kwlist.h#L213) is a keyword and will not work. You can either use type name identifiers or you can construct custom member using `sql.raw`, e.g.
```js
await connection.query(sql`
SELECT (${sql.array([1, 2, 3], sql.raw('int[]'))})
`);

```
Produces:
```js
{
sql: 'SELECT $1::int[]',
values: [
[
1,
2,
3
]
]
}

```
<a name="slonik-query-building-sql-array-sql-array-vs-sql-valuelist"></a>
#### <code>sql.array</code> vs <code>sql.valueList</code>
Unlike `sql.valueList`, `sql.array` generates a stable query of a predictable length, i.e. regardless of the number of the values in the array, the generated query remains the same:
Expand Down
2 changes: 1 addition & 1 deletion src/factories/createSqlTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default (configuration?: SqlTagConfigurationType) => {

sql.array = (
values: $ReadOnlyArray<PrimitiveValueExpressionType>,
memberType: string
memberType: string | RawSqlTokenType
): ArraySqlTokenType => {
return deepFreeze({
memberType,
Expand Down
30 changes: 28 additions & 2 deletions src/sqlFragmentFactories/createArraySqlFragment.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
// @flow

import type {
SqlFragmentType,
ArraySqlTokenType,
SqlFragmentType,
} from '../types';
import {
escapeIdentifier,
isSqlToken,
} from '../utilities';
import {
createSqlTokenSqlFragment,
} from '../factories';
import {
UnexpectedStateError,
} from '../errors';

export default (token: ArraySqlTokenType, greatestParameterPosition: number): SqlFragmentType => {
let placeholderIndex = greatestParameterPosition;

const values = [
token.values,
];

const sql = '$' + (greatestParameterPosition + 1) + '::' + escapeIdentifier(token.memberType) + '[]';
placeholderIndex++;

let sql = '$' + placeholderIndex + '::';

if (isSqlToken(token.memberType) && token.memberType.type === 'SLONIK_TOKEN_RAW_SQL') {
// $FlowFixMe
const sqlFragment = createSqlTokenSqlFragment(token.memberType, placeholderIndex);

placeholderIndex += sqlFragment.values.length;

values.push(...sqlFragment.values);

sql += sqlFragment.sql;
} else if (typeof token.memberType === 'string') {
sql += escapeIdentifier(token.memberType) + '[]';
} else {
throw new UnexpectedStateError('Unsupported `memberType`. `memberType` must be a string or SqlToken of "SLONIK_TOKEN_RAW_SQL" type.');
}

return {
sql,
Expand Down
4 changes: 2 additions & 2 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export type IdentifierListMemberType = $ReadOnlyArray<string> |
|};

export type ArraySqlTokenType = {|
+memberType: string,
+memberType: string | RawSqlTokenType,
+type: 'SLONIK_TOKEN_ARRAY',
+values: PositionalParameterValuesType,
|};
Expand Down Expand Up @@ -339,7 +339,7 @@ export type SqlTaggedTemplateType = {|
) => SqlSqlTokenType,
array: (
values: $ReadOnlyArray<PrimitiveValueExpressionType>,
memberType: string
memberType: string | RawSqlTokenType
) => ArraySqlTokenType,
assignmentList: (
namedAssignmentValueBindings: NamedAssignmentType
Expand Down
25 changes: 24 additions & 1 deletion test/slonik/templateTags/sql/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {

const sql = createSqlTag();

test('creates a value list', (t) => {
test('creates a value list using type name identifier', (t) => {
const query = sql`SELECT ${sql.array([1, 2, 3], 'int4')}`;

t.deepEqual(query, {
Expand All @@ -24,6 +24,29 @@ test('creates a value list', (t) => {
});
});

test('creates a value list using SqlRawToken', (t) => {
const query = sql`SELECT ${sql.array([1, 2, 3], sql.raw('int[]'))}`;

t.deepEqual(query, {
sql: 'SELECT $1::int[]',
type: SqlToken,
values: [
[
1,
2,
3,
],
],
});
});

test('throws if memberType is not a string or SqlToken of different type than "SLONIK_TOKEN_RAW_SQL"', (t) => {
t.throws(() => {
// $FlowFixMe
sql`SELECT ${sql.array([1, 2, 3], sql.identifier(['int']))}`;
}, 'Unsupported `memberType`. `memberType` must be a string or SqlToken of "SLONIK_TOKEN_RAW_SQL" type.');
});

test('the resulting object is immutable', (t) => {
const token = sql.array([1, 2, 3], 'int4');

Expand Down

0 comments on commit 60488bf

Please sign in to comment.