'use strict'; const db = require('../infra/db'); const AUTO_COLUMNS = new Set(['id', 'created_at', 'updated_at']); const columnsCache = new Map(); /** * 取表列名(缓存)。DDL 稳定,进程级缓存即可。 * @param {string} table * @returns {Promise} */ async function getColumns(table) { if (columnsCache.has(table)) return columnsCache.get(table); const rows = await db.query( `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`, [table], ); const cols = rows.map((r) => r.COLUMN_NAME); columnsCache.set(table, cols); return cols; } /** * 依据表实际列,推断按月过滤条件(兼容三种月份维度命名)。 * 无月份维度(字典/全量表)→ 返回空条件。 * @param {string[]} columns * @param {number} year * @param {number} month * @returns {{ clause: string, params: any[] }} */ function monthWhere(columns, year, month) { if (columns.includes('exam_year') && columns.includes('exam_month')) { return { clause: 'exam_year = ? AND exam_month = ?', params: [year, month] }; } if (columns.includes('year') && columns.includes('month')) { return { clause: '`year` = ? AND `month` = ?', params: [year, month] }; } if (columns.includes('missing_month')) { return { clause: 'missing_month = ?', params: [`${year}-${String(month).padStart(2, '0')}`] }; } return { clause: '', params: [] }; } /** * 值归一化:对象/数组转 JSON 字符串;Buffer/Date/标量原样;undefined→null。 * @param {any} v */ function coerceValue(v) { if (v === undefined || v === null) return null; if (Buffer.isBuffer(v) || v instanceof Date) return v; if (typeof v === 'object') return JSON.stringify(v); return v; } /** * 生成多行 INSERT(列 = 表列 ∩ 行键,排除自增/时间戳自动列)。 * @param {string} table * @param {string[]} tableColumns * @param {Object[]} rows * @returns {{ sql: string, params: any[], columns: string[] } | null} 无有效列时返回 null */ function buildMultiInsert(table, tableColumns, rows) { const rowKeys = new Set(); for (const r of rows) for (const k of Object.keys(r)) rowKeys.add(k); const columns = tableColumns.filter((c) => !AUTO_COLUMNS.has(c) && rowKeys.has(c)); if (columns.length === 0) return null; const placeholderRow = `(${columns.map(() => '?').join(', ')})`; const sql = `INSERT INTO \`${table}\` (${columns.map((c) => `\`${c}\``).join(', ')}) ` + `VALUES ${rows.map(() => placeholderRow).join(', ')}`; const params = []; for (const r of rows) for (const c of columns) params.push(coerceValue(r[c])); return { sql, params, columns }; } /** 将数组按 size 切块 */ function chunk(arr, size) { const out = []; for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size)); return out; } module.exports = { getColumns, monthWhere, coerceValue, buildMultiInsert, chunk };