import dbHandler from "../db-handler"; import _ from "lodash"; import type { BunMariaDBConfig, DBResponseObject, SQLInsertGenValueType, ServerQueryParam, } from "../../types"; import sqlGenerator from "../../utils/sql-generator"; import sanitizeHtmlFields from "../../utils/sanitize-html-fields"; import { quoteIdentifier } from "../../utils/sql-identifier"; type Params< Schema extends { [k: string]: any } = { [k: string]: any }, Table extends string = string, > = { table: Table; data: Schema; query?: ServerQueryParam; targetId?: number | string; config?: BunMariaDBConfig; }; export default async function DbUpdate< Schema extends { [k: string]: any } = { [k: string]: any }, Table extends string = string, >({ table, data, query, targetId, config, }: Params): Promise { let sqlObj: ReturnType = { string: "", values: [], where: "", whereValues: [], }; try { let finalQuery = query || {}; if (targetId) { finalQuery = _.merge, ServerQueryParam>( finalQuery, { query: { id: { value: Number(targetId), }, }, }, ); } const sqlQueryObj = sqlGenerator({ tableName: table, genObject: finalQuery, }); const whereClause = sqlQueryObj.where; if (!whereClause) { return { success: false, msg: `No WHERE clause`, }; } /** * Capture the matching row ids first, so the updated rows can be * re-fetched even if the update changes a column used in the WHERE * clause. */ const matched_res = await dbHandler<{ id: number }>({ query: `SELECT id FROM ${quoteIdentifier(table)} ${whereClause}`, values: sqlQueryObj.whereValues as any, config, }); if (!matched_res.success) { return { ...matched_res, success: false, debug: { sqlObj: sqlQueryObj }, }; } const matched_ids = (matched_res.payload || []) .map((row) => row.id) .filter((id) => id !== undefined && id !== null); if (matched_ids.length === 0) { return { success: false, msg: "No rows matched the update query", payload: [], debug: { sqlObj: sqlQueryObj }, }; } let values: SQLInsertGenValueType[] = []; let sql = ``; sql += `UPDATE ${quoteIdentifier(table)} SET`; const sanitizedData = sanitizeHtmlFields({ table, data, config, }); const finalData: { [k: string]: SQLInsertGenValueType } = { updated_at: Date.now(), ...sanitizedData, }; const keys = Object.keys(finalData); for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (!key) continue; const isLast = i == keys.length - 1; sql += ` ${quoteIdentifier(key)}=?`; values.push(finalData[key] ?? null); if (!isLast) { sql += `,`; } } const idPlaceholders = matched_ids.map(() => "?").join(","); sql += ` WHERE id IN (${idPlaceholders})`; values = [...values, ...matched_ids]; const res = await dbHandler({ query: sql, values: values as any, config, }); sqlObj.string = sql; sqlObj.values = values as any[]; if (res.error) { return { ...res, debug: { sqlObj } }; } const updated_res = await dbHandler({ query: `SELECT * FROM ${quoteIdentifier(table)} WHERE id IN (${idPlaceholders})`, values: matched_ids, config, }); return { ...updated_res, debug: { sqlObj, }, db_res: res.db_res, }; } catch (error: any) { return { success: false, error: error.message, debug: { sqlObj, }, }; } }