slonik-utilities
Version:
Utilities for manipulating data in PostgreSQL database using Slonik.
125 lines (124 loc) • 4.81 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.upsert = void 0;
const lodash_1 = require("lodash");
const slonik_1 = require("slonik");
const Logger_1 = require("../Logger");
const log = Logger_1.Logger.child({
namespace: 'upsert',
});
const normalizeNamedValueBindingName = (name) => {
return (0, lodash_1.snakeCase)(name);
};
const defaultConfiguration = {
identifierName: 'id',
};
const upsert = async (connection, tableName, namedValueBindings, inputUniqueConstraintColumnNames = null, inputConfiguration = null) => {
const configuration = {
...defaultConfiguration,
...inputConfiguration,
};
const namedValueBindingNamesWithUndefinedValues = [];
const boundValues = [];
const normalizedNamedValueBindings = (0, lodash_1.mapKeys)(namedValueBindings, (value, key) => {
if (value === undefined) {
namedValueBindingNamesWithUndefinedValues.push(key);
}
boundValues.push(value);
return normalizeNamedValueBindingName(key);
});
if (namedValueBindingNamesWithUndefinedValues.length > 0) {
log.warn({
namedValueBindingNamesWithUndefinedValues,
}, 'named value bindings with undefined values');
throw new Error('Named value binding values must be defined.');
}
const columnNames = Object.keys(normalizedNamedValueBindings);
const uniqueConstraintColumnNames = inputUniqueConstraintColumnNames !== null && inputUniqueConstraintColumnNames !== void 0 ? inputUniqueConstraintColumnNames : columnNames;
if ((0, lodash_1.difference)(uniqueConstraintColumnNames, columnNames).length > 0) {
throw new Error('Unique constraint column names must not contain column names not present in named value bindings.');
}
const updateColumnNames = (0, lodash_1.difference)(columnNames, uniqueConstraintColumnNames);
if (columnNames.length === 0) {
throw new Error('Named value bindings object must have properties.');
}
const columnIdentifiers = slonik_1.sql.join(columnNames.map((columnName) => {
return slonik_1.sql.identifier([
columnName,
]);
}), slonik_1.sql.fragment `, `);
const conflictColumnIdentifiers = slonik_1.sql.join(uniqueConstraintColumnNames.map((uniqueConstraintColumnName) => {
return slonik_1.sql.identifier([
uniqueConstraintColumnName,
]);
}), slonik_1.sql.fragment `, `);
let updateClause;
if (updateColumnNames.length) {
updateClause = slonik_1.sql.join(updateColumnNames.map((updateColumnName) => {
return slonik_1.sql.fragment `${slonik_1.sql.identifier([
updateColumnName,
])} = ${slonik_1.sql.identifier([
'excluded',
updateColumnName,
])}`;
}), slonik_1.sql.fragment `, `);
}
const targetColumnNames = (0, lodash_1.uniq)([
...uniqueConstraintColumnNames,
...updateColumnNames,
]);
const whereClause = slonik_1.sql.join(targetColumnNames.map((targetColumnName) => {
const value = normalizedNamedValueBindings[normalizeNamedValueBindingName(targetColumnName)];
if (value === null) {
return slonik_1.sql.fragment `${slonik_1.sql.identifier([
targetColumnName,
])} IS NULL`;
}
return slonik_1.sql.fragment `${slonik_1.sql.identifier([
targetColumnName,
])} = ${value}`;
}), slonik_1.sql.fragment ` AND `);
const selectQuery = slonik_1.sql.unsafe `
SELECT ${slonik_1.sql.identifier([
configuration.identifierName,
])}
FROM ${slonik_1.sql.identifier([
tableName,
])}
WHERE
${whereClause}
`;
let maybeId;
maybeId = await connection.maybeOneFirst(selectQuery);
if (maybeId) {
return maybeId;
}
if (updateClause) {
return await connection.oneFirst(slonik_1.sql.unsafe `
INSERT INTO ${slonik_1.sql.identifier([
tableName,
])} (${columnIdentifiers})
VALUES (${slonik_1.sql.join(boundValues, slonik_1.sql.fragment `, `)})
ON CONFLICT (${conflictColumnIdentifiers})
DO UPDATE
SET
${updateClause}
RETURNING ${slonik_1.sql.identifier([
configuration.identifierName,
])}
`);
}
maybeId = await connection.maybeOneFirst(slonik_1.sql.unsafe `
INSERT INTO ${slonik_1.sql.identifier([
tableName,
])} (${columnIdentifiers})
VALUES (${slonik_1.sql.join(boundValues, slonik_1.sql.fragment `, `)})
ON CONFLICT (${conflictColumnIdentifiers})
DO NOTHING
`);
if (maybeId) {
return maybeId;
}
return await connection.oneFirst(selectQuery);
};
exports.upsert = upsert;