import type { Sequelize, Transaction } from "sequelize"; import { QueryTypes } from "sequelize"; /** Identificadores internos; los valores externos siempre se parametrizan. */ export function sqlIdentifier(value: string) { return `\`${value.replaceAll("`", "``")}\``; } export async function writeResourceRow( database: Sequelize, table: string, row: Record, transaction: Transaction ) { const columns = Object.keys(row); await database.query( `INSERT INTO ${sqlIdentifier(table)} (${columns.map(sqlIdentifier).join(",")}) VALUES (${columns.map((key) => `:${key}`).join(",")}) ON DUPLICATE KEY UPDATE ${columns.map((key) => `${sqlIdentifier(key)}=VALUES(${sqlIdentifier(key)})`).join(",")}`, { replacements: row, transaction } ); } export async function readResourceRows( database: Sequelize, table: string, column: string, ids: readonly string[], transaction: Transaction ) { if (!ids.length) { return []; } return database.query( `SELECT * FROM ${sqlIdentifier(table)} WHERE ${sqlIdentifier(column)} IN (:ids)`, { replacements: { ids }, type: QueryTypes.SELECT, transaction } ); } export async function removeResourceColumn(database: Sequelize, table: string, column: string) { const columns = await database.getQueryInterface().describeTable(table); if (!(column in columns)) { return; } const constraints = await database.query<{ name: string }>( "SELECT CONSTRAINT_NAME AS name FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=:table AND COLUMN_NAME=:column AND REFERENCED_TABLE_NAME IS NOT NULL", { replacements: { table, column }, type: QueryTypes.SELECT } ); for (const { name } of constraints) { await database.query( `ALTER TABLE ${sqlIdentifier(table)} DROP FOREIGN KEY ${sqlIdentifier(name)}` ); } // Evita el fallo de QueryInterface.removeColumn con Sequelize 6 y mariadb 3. await database.query(`ALTER TABLE ${sqlIdentifier(table)} DROP COLUMN ${sqlIdentifier(column)}`); }