UNPKG

slonik-utilities

Version:

Utilities for manipulating data in PostgreSQL database using Slonik.

125 lines (124 loc) 4.81 kB
"use strict"; 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;