import { Schema, tables } from '../schema'; import type { PreparedQueries, TableColumnNames, TableRow } from '../types'; /** * Creates a simple upsert query for sqlite. * * @param {string} table Table name * @param {Object} row Table row to insert or update. * @param {Array} conflictCheckKeys Custom list of columns to check conflicts for - https://www.sqlite.org/lang_UPSERT.html. By default conflicts are checked on primary keys. * @returns {string} Final upsert query for sqlite */ export const createUpsertQuery = ( table: T, row: Partial>, conflictCheckKeys?: Array>, ): PreparedQueries => { const filteredRow: typeof row = {}; // In case of "DO UPDATE SET", we only want to update the properties which // are provided, and not set undefined properties in database. // E.g., channel date such as `own_capabilities` is only available in response of client.queryChannels or channel.query. // But its not available in `event.channel`. And our mapper functions such as mapChannelToStorable will set fields which are not available as undefined. // So when you execute upsert query for storable value of `event.channel` in `channels` table, it will // unset ownCapabilities field for that channel in `channels` table. for (const key in row) { if (row[key] !== undefined) { filteredRow[key] = row[key]; } } const fields = Object.keys(filteredRow) as (keyof typeof row)[]; const questionMarks = Array(Object.keys(fields).length).fill('?').join(','); const conflictKeys = conflictCheckKeys || tables[table].primaryKey || []; const conflictMatchersWithoutPK: string[] = (fields as string[]) .filter((f) => !(conflictKeys as string[]).includes(f)) .map((f) => `${f}=excluded.${f}`); const conflictConstraint = conflictKeys.length > 0 ? `ON CONFLICT(${conflictKeys.join(',')}) DO UPDATE SET ${conflictMatchersWithoutPK.join(',')}` : ''; return [ `INSERT INTO ${table} (${fields.join(',')}) VALUES (${questionMarks}) ${conflictConstraint}`, Object.values(filteredRow), ]; };