89 lines
2.9 KiB
JavaScript
89 lines
2.9 KiB
JavaScript
'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<string[]>}
|
||
*/
|
||
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 };
|