Skip to content

Commit

Permalink
feat: allow sql tokens as values of valueList, tuple and tupleList
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Apr 12, 2019
1 parent cef16e1 commit 405ac65
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 65 deletions.
77 changes: 77 additions & 0 deletions .README/QUERY_BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,29 @@ Produces:

```

Value list can describe other SQL tokens, e.g.

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

```

Produces:

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

```

### `sql.array`

```js
Expand Down Expand Up @@ -121,6 +144,30 @@ Produces:

```

Tuple can describe other SQL tokens, e.g.

```js
await connection.query(sql`
INSERT INTO (foo, bar, baz)
VALUES ${sql.tuple([1, sql.raw('to_timestamp($1)', [2]), 3])}
`);

```

Produces:

```js
{
sql: 'INSERT INTO (foo, bar, baz) VALUES ($1, to_timestamp($2), $3)',
values: [
1,
2,
3
]
}

```

### `sql.tupleList`

```js
Expand Down Expand Up @@ -158,6 +205,36 @@ Produces:

```

Tuple list can describe other SQL tokens, e.g.

```js
await connection.query(sql`
INSERT INTO (foo, bar, baz)
VALUES ${sql.tupleList([
[1, sql.raw('to_timestamp($1)', [2]), 3],
[4, sql.raw('to_timestamp($1)', [5]), 6]
])}
`);

```

Produces:

```js
{
sql: 'INSERT INTO (foo, bar, baz) VALUES ($1, to_timestamp($2), $3), ($4, to_timestamp($5), $6)',
values: [
1,
2,
3,
4,
5,
6
]
}

```

### `sql.unnest`

```js
Expand Down
65 changes: 65 additions & 0 deletions src/factories/createSqlTokenSqlFragment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// @flow

import {
ArrayTokenSymbol,
SqlTokenSymbol,
RawSqlTokenSymbol,
IdentifierTokenSymbol,
IdentifierListTokenSymbol,
ValueListTokenSymbol,
TupleTokenSymbol,
TupleListTokenSymbol,
UnnestTokenSymbol
} from '../symbols';
import {
createArraySqlFragment,
createIdentifierSqlFragment,
createIdentifierListSqlFragment,
createRawSqlSqlFragment,
createSqlSqlFragment,
createTupleListSqlFragment,
createTupleSqlFragment,
createUnnestSqlFragment,
createValueListSqlFragment
} from '../sqlFragmentFactories';
import {
UnexpectedStateError
} from '../errors';
import type {
SqlTokenType,
SqlFragmentType
} from '../types';

export default (token: SqlTokenType, greatestParameterPosition: number): SqlFragmentType => {
if (token.type === SqlTokenSymbol) {
// @see https://github.com/gajus/slonik/issues/36 regarding FlowFixMe use.
// $FlowFixMe
return createSqlSqlFragment(token, greatestParameterPosition);
} else if (token.type === RawSqlTokenSymbol) {
// $FlowFixMe
return createRawSqlSqlFragment(token, greatestParameterPosition);
} else if (token.type === IdentifierTokenSymbol) {
// $FlowFixMe
return createIdentifierSqlFragment(token);
} else if (token.type === IdentifierListTokenSymbol) {
// $FlowFixMe
return createIdentifierListSqlFragment(token);
} else if (token.type === ArrayTokenSymbol) {
// $FlowFixMe
return createArraySqlFragment(token, greatestParameterPosition);
} else if (token.type === ValueListTokenSymbol) {
// $FlowFixMe
return createValueListSqlFragment(token, greatestParameterPosition);
} else if (token.type === TupleTokenSymbol) {
// $FlowFixMe
return createTupleSqlFragment(token, greatestParameterPosition);
} else if (token.type === TupleListTokenSymbol) {
// $FlowFixMe
return createTupleListSqlFragment(token, greatestParameterPosition);
} else if (token.type === UnnestTokenSymbol) {
// $FlowFixMe
return createUnnestSqlFragment(token, greatestParameterPosition);
}

throw new UnexpectedStateError();
};
1 change: 1 addition & 0 deletions src/factories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

export {default as createConnection} from './createConnection';
export {default as createPool} from './createPool';
export {default as createSqlTokenSqlFragment} from './createSqlTokenSqlFragment';
export {default as createTypeParserPreset} from './createTypeParserPreset';
21 changes: 18 additions & 3 deletions src/sqlFragmentFactories/createTupleListSqlFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import type {
import {
UnexpectedStateError
} from '../errors';
import {
isSqlToken
} from '../utilities';
import {
createSqlTokenSqlFragment
} from '../factories';

export default (token: TupleListSqlTokenType, greatestParameterPosition: number): SqlFragmentType => {
const values = [];
Expand All @@ -31,9 +37,18 @@ export default (token: TupleListSqlTokenType, greatestParameterPosition: number)
lastTupleSize = tuple.length;

for (const member of tuple) {
placeholders.push('$' + ++placeholderIndex);

values.push(member);
if (isSqlToken(member)) {
// $FlowFixMe
const sqlFragment = createSqlTokenSqlFragment(member, placeholderIndex);

placeholders.push(sqlFragment.sql);
placeholderIndex += sqlFragment.values.length;
values.push(...sqlFragment.values);
} else {
placeholders.push('$' + ++placeholderIndex);

values.push(member);
}
}

tupleListMemberSql.push('(' + placeholders.join(', ') + ')');
Expand Down
21 changes: 18 additions & 3 deletions src/sqlFragmentFactories/createTupleSqlFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import type {
import {
UnexpectedStateError
} from '../errors';
import {
isSqlToken
} from '../utilities';
import {
createSqlTokenSqlFragment
} from '../factories';

export default (token: TupleSqlTokenType, greatestParameterPosition: number): SqlFragmentType => {
const values = [];
Expand All @@ -19,9 +25,18 @@ export default (token: TupleSqlTokenType, greatestParameterPosition: number): Sq
}

for (const tupleValue of token.values) {
placeholders.push('$' + ++placeholderIndex);

values.push(tupleValue);
if (isSqlToken(tupleValue)) {
// $FlowFixMe
const sqlFragment = createSqlTokenSqlFragment(tupleValue, placeholderIndex);

placeholders.push(sqlFragment.sql);
placeholderIndex += sqlFragment.values.length;
values.push(...sqlFragment.values);
} else {
placeholders.push('$' + ++placeholderIndex);

values.push(tupleValue);
}
}

const sql = '(' + placeholders.join(', ') + ')';
Expand Down
25 changes: 19 additions & 6 deletions src/sqlFragmentFactories/createValueListSqlFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import type {
import {
UnexpectedStateError
} from '../errors';
import {
isSqlToken
} from '../utilities';
import {
createSqlTokenSqlFragment
} from '../factories';

export default (token: ValueListSqlTokenType, greatestParameterPosition: number): SqlFragmentType => {
const values = [];
Expand All @@ -19,15 +25,22 @@ export default (token: ValueListSqlTokenType, greatestParameterPosition: number)
}

for (const listValue of token.values) {
placeholders.push('$' + ++placeholderIndex);

values.push(listValue);
if (isSqlToken(listValue)) {
// $FlowFixMe
const sqlFragment = createSqlTokenSqlFragment(listValue, placeholderIndex);

placeholders.push(sqlFragment.sql);
placeholderIndex += sqlFragment.values.length;
values.push(...sqlFragment.values);
} else {
placeholders.push('$' + ++placeholderIndex);

values.push(listValue);
}
}

const sql = placeholders.join(', ');

return {
sql,
sql: placeholders.join(', '),
values
};
};
60 changes: 12 additions & 48 deletions src/templateTags/sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type {
IdentifierTokenType,
PrimitiveValueExpressionType,
RawSqlTokenType,
SqlFragmentType,
SqlSqlTokenType,
SqlTaggedTemplateType,
TupleListSqlTokenType,
Expand All @@ -18,20 +17,13 @@ import type {
} from '../types';
import {
deepFreeze,
isPrimitiveValueExpression
isPrimitiveValueExpression,
isSqlToken
} from '../utilities';
import Logger from '../Logger';
import {
createArraySqlFragment,
createIdentifierSqlFragment,
createIdentifierListSqlFragment,
createRawSqlSqlFragment,
createSqlSqlFragment,
createTupleListSqlFragment,
createTupleSqlFragment,
createUnnestSqlFragment,
createValueListSqlFragment
} from '../sqlFragmentFactories';
createSqlTokenSqlFragment
} from '../factories';
import {
ArrayTokenSymbol,
SqlTokenSymbol,
Expand Down Expand Up @@ -60,11 +52,6 @@ const sql: SqlTaggedTemplateType = (

let index = 0;

const appendSqlFragment = (sqlFragment: SqlFragmentType) => {
rawSql += sqlFragment.sql;
parameterValues.push(...sqlFragment.values);
};

for (const part of parts) {
const token = values[index++];

Expand All @@ -78,34 +65,11 @@ const sql: SqlTaggedTemplateType = (
rawSql += '$' + (parameterValues.length + 1);

parameterValues.push(token);
} else if (token && token.type === SqlTokenSymbol) {
// @see https://github.com/gajus/slonik/issues/36 regarding FlowFixMe use.
// $FlowFixMe
appendSqlFragment(createSqlSqlFragment(token, parameterValues.length));
} else if (token && token.type === RawSqlTokenSymbol) {
// $FlowFixMe
appendSqlFragment(createRawSqlSqlFragment(token, parameterValues.length));
} else if (token && token.type === IdentifierTokenSymbol) {
// $FlowFixMe
appendSqlFragment(createIdentifierSqlFragment(token));
} else if (token && token.type === IdentifierListTokenSymbol) {
// $FlowFixMe
appendSqlFragment(createIdentifierListSqlFragment(token));
} else if (token && token.type === ArrayTokenSymbol) {
// $FlowFixMe
appendSqlFragment(createArraySqlFragment(token, parameterValues.length));
} else if (token && token.type === ValueListTokenSymbol) {
// $FlowFixMe
appendSqlFragment(createValueListSqlFragment(token, parameterValues.length));
} else if (token && token.type === TupleTokenSymbol) {
// $FlowFixMe
appendSqlFragment(createTupleSqlFragment(token, parameterValues.length));
} else if (token && token.type === TupleListTokenSymbol) {
// $FlowFixMe
appendSqlFragment(createTupleListSqlFragment(token, parameterValues.length));
} else if (token && token.type === UnnestTokenSymbol) {
// $FlowFixMe
appendSqlFragment(createUnnestSqlFragment(token, parameterValues.length));
} else if (isSqlToken(token)) {
const sqlFragment = createSqlTokenSqlFragment(token, parameterValues.length);

rawSql += sqlFragment.sql;
parameterValues.push(...sqlFragment.values);
} else {
log.error({
constructedSql: rawSql,
Expand Down Expand Up @@ -157,7 +121,7 @@ sql.raw = (
};

sql.valueList = (
values: $ReadOnlyArray<PrimitiveValueExpressionType>
values: $ReadOnlyArray<ValueExpressionType>
): ValueListSqlTokenType => {
return deepFreeze({
type: ValueListTokenSymbol,
Expand All @@ -177,7 +141,7 @@ sql.array = (
};

sql.tuple = (
values: $ReadOnlyArray<PrimitiveValueExpressionType>
values: $ReadOnlyArray<ValueExpressionType>
): TupleSqlTokenType => {
return deepFreeze({
type: TupleTokenSymbol,
Expand All @@ -186,7 +150,7 @@ sql.tuple = (
};

sql.tupleList = (
tuples: $ReadOnlyArray<$ReadOnlyArray<PrimitiveValueExpressionType>>
tuples: $ReadOnlyArray<$ReadOnlyArray<ValueExpressionType>>
): TupleListSqlTokenType => {
return deepFreeze({
tuples,
Expand Down

0 comments on commit 405ac65

Please sign in to comment.