Agent/server/src/common/table-utils.js

89 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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 };