Skip to content

Commit

Permalink
feat: make compatible with the latest Slonik
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Nov 4, 2019
1 parent f82ee69 commit cbfe1a4
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 85 deletions.
35 changes: 16 additions & 19 deletions src/routines/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,55 @@ import {
} from 'slonik';
import type {
DatabaseConnectionType,
ValueExpressionType,
} from 'slonik';

type NamedValueBindingsType = {
+[key: string]: ValueExpressionType,
...,
};
import {
assignmentList,
normalizeIdentifier,
} from '../utilities';
import type {
NamedAssignmentPayloadType,
} from '../types';

export default async (
connection: DatabaseConnectionType,
tableName: string,
namedValueBindings: NamedValueBindingsType,
namedAssignmentPayload: NamedAssignmentPayloadType,

// eslint-disable-next-line flowtype/no-weak-types
booleanExpressionValues: Object = null,
) => {
if (booleanExpressionValues) {
const nonOverlappingNamedValueBindings = pickBy(namedValueBindings, (value, key) => {
const nonOverlappingNamedAssignmentBindings = pickBy(namedAssignmentPayload, (value, key) => {
return value !== booleanExpressionValues[key];
});

if (Object.keys(nonOverlappingNamedValueBindings).length === 0) {
if (Object.keys(nonOverlappingNamedAssignmentBindings).length === 0) {
return;
}

const assignmentList = sql.assignmentList(nonOverlappingNamedValueBindings);

const booleanExpression = sql.booleanExpression(
const booleanExpression = sql.join(
Object
.entries(booleanExpressionValues)
.map(([key, value]) => {
// $FlowFixMe
return sql.comparisonPredicate(sql.identifier([key]), '=', value);
return sql`${sql.identifier([normalizeIdentifier(key)])} = ${value}`;
}),
'AND',
sql` AND `,
);

await connection.query(sql`
UPDATE ${sql.identifier([tableName])}
SET ${assignmentList}
SET ${assignmentList(nonOverlappingNamedAssignmentBindings)}
WHERE ${booleanExpression}
`);
} else {
if (Object.keys(namedValueBindings).length === 0) {
if (Object.keys(namedAssignmentPayload).length === 0) {
return;
}

const assignmentList = sql.assignmentList(namedValueBindings);

await connection.query(sql`
UPDATE ${sql.identifier([tableName])}
SET ${assignmentList}
SET ${assignmentList(namedAssignmentPayload)}
`);
}
};
36 changes: 17 additions & 19 deletions src/routines/updateDistinct.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,53 @@ import {
} from 'slonik';
import type {
DatabaseConnectionType,
ValueExpressionType,
} from 'slonik';

type NamedValueBindingsType = {
+[key: string]: ValueExpressionType,
...,
};
import {
assignmentList,
} from '../utilities';
import type {
NamedAssignmentPayloadType,
} from '../types';

export default async (
connection: DatabaseConnectionType,
tableName: string,
namedValueBindings: NamedValueBindingsType,
namedAssignmentPayload: NamedAssignmentPayloadType,

// eslint-disable-next-line flowtype/no-weak-types
booleanExpressionValues: Object = null,
) => {
const assignmentList = sql.assignmentList(namedValueBindings);

let booleanExpression = sql.booleanExpression(
let booleanExpression = sql.join(
Object
.entries(namedValueBindings)
.entries(namedAssignmentPayload)
.map(([key, value]) => {
// $FlowFixMe
return sql.raw('$1 IS DISTINCT FROM $2', [sql.identifier([normalizeIdentifier(key)]), value]);
return sql`${sql.identifier([normalizeIdentifier(key)])} IS DISTINCT FROM ${value}`;
}),
'OR',
sql` OR `,
);

if (booleanExpressionValues) {
booleanExpression = sql.booleanExpression(
booleanExpression = sql.join(
[
booleanExpression,
sql.booleanExpression(
sql.join(
Object
.entries(booleanExpressionValues)
.map(([key, value]) => {
// $FlowFixMe
return sql.comparisonPredicate(sql.identifier([key]), '=', value);
return sql`${sql.identifier([normalizeIdentifier(key)])} = ${value}`;
}),
'AND',
sql` AND `,
),
],
'AND',
sql` AND `,
);
}

await connection.query(sql`
UPDATE ${sql.identifier([tableName])}
SET ${assignmentList}
SET ${assignmentList(namedAssignmentPayload)}
WHERE ${booleanExpression}
`);
};
57 changes: 22 additions & 35 deletions src/routines/upsert.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ import {
mapKeys,
snakeCase,
} from 'lodash';
import {
escapeIdentifier,
} from '../utilities';
import Logger from '../Logger';

type NamedValueBindingsType = {
Expand Down Expand Up @@ -86,34 +83,30 @@ export default async (
throw new Error('Named value bindings object must have properties.');
}

const columnIdentifiers = sql.identifierList(
const columnIdentifiers = sql.join(
columnNames
.map((columnName) => {
return [
columnName,
];
return sql.identifier([columnName]);
}),
sql`, `,
);

const values = sql.valueList(boundValues);

const conflictColumnIdentifiers = sql.identifierList(
const conflictColumnIdentifiers = sql.join(
uniqueConstraintColumnNames.map((uniqueConstraintColumnName) => {
return [
uniqueConstraintColumnName,
];
return sql.identifier([uniqueConstraintColumnName]);
}),
sql`, `,
);

let updateClause;

if (updateColumnNames.length) {
updateClause = sql.raw(
updateClause = sql.join(
updateColumnNames
.map((updateColumnName) => {
return escapeIdentifier(updateColumnName) + ' = EXCLUDED.' + escapeIdentifier(updateColumnName);
})
.join(', '),
return sql`${sql.identifier([updateColumnName])} = ${sql.identifier(['EXCLUDED', updateColumnName])}`;
}),
sql`, `,
);
}

Expand All @@ -122,24 +115,18 @@ export default async (
...updateColumnNames,
]);

const whereClause = sql.booleanExpression(targetColumnNames.map((targetColumnName) => {
const value = normalizedNamedValueBindings[normalizeNamedValueBindingName(targetColumnName)];
const whereClause = sql.join(
targetColumnNames.map((targetColumnName) => {
const value = normalizedNamedValueBindings[normalizeNamedValueBindingName(targetColumnName)];

if (value === null) {
return sql.raw(
'$1 IS NULL',
[
sql.identifier([targetColumnName]),
],
);
}
if (value === null) {
return sql`${sql.identifier([targetColumnName])} IS NULL`;
}

return sql.comparisonPredicate(
sql.identifier([targetColumnName]),
'=',
value,
);
}), 'AND');
return sql`${sql.identifier([targetColumnName])} = ${value}`;
}),
sql` AND `,
);

const selectQuery = sql`
SELECT ${sql.identifier([configuration.identifierName])}
Expand All @@ -159,7 +146,7 @@ export default async (
if (updateClause) {
return connection.oneFirst(sql`
INSERT INTO ${sql.identifier([tableName])} (${columnIdentifiers})
VALUES (${values})
VALUES (${sql.join(boundValues, sql`, `)})
ON CONFLICT (${conflictColumnIdentifiers})
DO UPDATE
SET
Expand All @@ -170,7 +157,7 @@ export default async (

maybeId = await connection.maybeOneFirst(sql`
INSERT INTO ${sql.identifier([tableName])} (${columnIdentifiers})
VALUES (${values})
VALUES (${sql.join(boundValues, sql`, `)})
ON CONFLICT (${conflictColumnIdentifiers})
DO NOTHING
`);
Expand Down
10 changes: 10 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow

import type {
ValueExpressionType,
} from 'slonik';

export type NamedAssignmentPayloadType = {
[key: string]: ValueExpressionType,
...,
};
24 changes: 24 additions & 0 deletions src/utilities/assignmentList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @flow

import {
sql,
} from 'slonik';
import type {
ListSqlTokenType,
} from 'slonik';
import type {
NamedAssignmentPayloadType,
} from '../types';
import normalizeIdentifier from './normalizeIdentifier';

export default (namedAssignment: NamedAssignmentPayloadType): ListSqlTokenType => {
const values = Object.values(Object
.entries(namedAssignment)
.map(([column, value]) => {
// $FlowFixMe
return sql`${sql.identifier([normalizeIdentifier(column)])} = ${value}`;
}));

// $FlowFixMe
return sql.join(values, sql`, `);
};
3 changes: 2 additions & 1 deletion src/utilities/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @flow

export {default as escapeIdentifier} from './escapeIdentifier';
export {default as assignmentList} from './assignmentList';
export {default as normalizeIdentifier} from './normalizeIdentifier';
9 changes: 9 additions & 0 deletions src/utilities/normalizeIdentifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @flow

import {
snakeCase,
} from 'lodash';

export default (propertyName: string): string => {
return snakeCase(propertyName);
};
2 changes: 1 addition & 1 deletion test/slonik-utilities/routines/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ test('executes UPDATE query with WHERE condition (AND boolean expression short-h

t.is(connection.query.callCount, 1);

t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar" = $1 WHERE ("qux" = $2)');
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar" = $1 WHERE "qux" = $2');
t.deepEqual(connection.query.firstCall.args[0].values, [
'baz',
'quux',
Expand Down
8 changes: 4 additions & 4 deletions test/slonik-utilities/routines/updateDistinct.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test('executes UPDATE query without WHERE condition (single column)', async (t)

t.is(connection.query.callCount, 1);

t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar" = $1 WHERE ("bar" IS DISTINCT FROM $2)');
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar" = $1 WHERE "bar" IS DISTINCT FROM $2');
t.deepEqual(connection.query.firstCall.args[0].values, [
'baz',
'baz',
Expand All @@ -53,7 +53,7 @@ test('executes UPDATE query without WHERE condition (multiple columns)', async (

t.is(connection.query.callCount, 1);

t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar_0" = $1, "bar_1" = $2, "bar_2" = $3 WHERE ("bar_0" IS DISTINCT FROM $4 OR "bar_1" IS DISTINCT FROM $5 OR "bar_2" IS DISTINCT FROM $6)');
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar_0" = $1, "bar_1" = $2, "bar_2" = $3 WHERE "bar_0" IS DISTINCT FROM $4 OR "bar_1" IS DISTINCT FROM $5 OR "bar_2" IS DISTINCT FROM $6');
t.deepEqual(connection.query.firstCall.args[0].values, [
'baz0',
'baz1',
Expand All @@ -79,7 +79,7 @@ test('executes UPDATE query without WHERE condition (SQL token)', async (t) => {

t.is(connection.query.callCount, 1);

t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar_0" = $1, "bar_1" = to_timestamp($2), "bar_2" = $3 WHERE ("bar_0" IS DISTINCT FROM $4 OR "bar_1" IS DISTINCT FROM to_timestamp($5) OR "bar_2" IS DISTINCT FROM $6)');
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar_0" = $1, "bar_1" = to_timestamp($2), "bar_2" = $3 WHERE "bar_0" IS DISTINCT FROM $4 OR "bar_1" IS DISTINCT FROM to_timestamp($5) OR "bar_2" IS DISTINCT FROM $6');
t.deepEqual(connection.query.firstCall.args[0].values, [
'baz0',
'baz1',
Expand All @@ -106,7 +106,7 @@ test('executes UPDATE query with WHERE condition (AND boolean expression short-h

t.is(connection.query.callCount, 1);

t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar" = $1 WHERE (("bar" IS DISTINCT FROM $2) AND ("qux" = $3))');
t.is(normalizeQuery(connection.query.firstCall.args[0].sql), 'UPDATE "foo" SET "bar" = $1 WHERE "bar" IS DISTINCT FROM $2 AND "qux" = $3');
t.deepEqual(connection.query.firstCall.args[0].values, [
'baz',
'baz',
Expand Down

0 comments on commit cbfe1a4

Please sign in to comment.