四端上传
This commit is contained in:
1 parent
25bbb5f9c8
commit
c6e2cb4d5b
276 files changed
+17812
-3
No files matched your search
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 极简 mysqldump 解析器。
|
||||
*
|
||||
* 只做一件事:从 mysqldump 导出的 .sql 文本里取出 `INSERT INTO `表名` VALUES (...),(...)` 的行数据。
|
||||
* 不解析 DDL,不处理多语句逻辑。
|
||||
*/
|
||||
|
||||
export type SqlValue = string | number | null;
|
||||
|
||||
/** 解码 MySQL 字符串中的反斜杠转义。未识别的转义按 MySQL 语义保留反斜杠。 */
|
||||
function decodeEscape(e: string): string {
|
||||
switch (e) {
|
||||
case '0':
|
||||
return '\0';
|
||||
case 'b':
|
||||
return '\b';
|
||||
case 'n':
|
||||
return '\n';
|
||||
case 'r':
|
||||
return '\r';
|
||||
case 't':
|
||||
return '\t';
|
||||
case 'Z':
|
||||
return '\x1a';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case "'":
|
||||
return "'";
|
||||
case '"':
|
||||
return '"';
|
||||
default:
|
||||
return '\\' + e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 `VALUES` 之后的载荷解析成二维数组。
|
||||
* 每个字段的三种形态:带引号的字符串 / NULL / 数字。
|
||||
*/
|
||||
export function parseTupleList(payload: string): SqlValue[][] {
|
||||
const rows: SqlValue[][] = [];
|
||||
const n = payload.length;
|
||||
let i = 0;
|
||||
|
||||
const skipSeparators = (): void => {
|
||||
while (i < n && (payload[i] === ',' || payload[i]! <= ' ')) i++;
|
||||
};
|
||||
|
||||
skipSeparators();
|
||||
while (i < n) {
|
||||
if (payload[i] !== '(') {
|
||||
throw new Error(`位置 ${i} 处期望 '(',实际是 ${JSON.stringify(payload[i])}`);
|
||||
}
|
||||
i++;
|
||||
|
||||
const row: SqlValue[] = [];
|
||||
let closed = false;
|
||||
while (!closed) {
|
||||
while (i < n && payload[i]! <= ' ') i++;
|
||||
|
||||
if (payload[i] === "'") {
|
||||
i++;
|
||||
let s = '';
|
||||
for (;;) {
|
||||
const c = payload[i];
|
||||
if (c === undefined) throw new Error('字符串未闭合');
|
||||
if (c === '\\') {
|
||||
s += decodeEscape(payload[i + 1] ?? '');
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (c === "'") {
|
||||
if (payload[i + 1] === "'") {
|
||||
s += "'";
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
s += c;
|
||||
i++;
|
||||
}
|
||||
row.push(s);
|
||||
} else {
|
||||
let token = '';
|
||||
while (i < n && payload[i] !== ',' && payload[i] !== ')') {
|
||||
token += payload[i];
|
||||
i++;
|
||||
}
|
||||
token = token.trim();
|
||||
row.push(token === '' || token.toUpperCase() === 'NULL' ? null : Number(token));
|
||||
}
|
||||
|
||||
while (i < n && payload[i]! <= ' ' && payload[i] !== ',' && payload[i] !== ')') i++;
|
||||
const c = payload[i];
|
||||
if (c === ',') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (c === ')') {
|
||||
i++;
|
||||
closed = true;
|
||||
continue;
|
||||
}
|
||||
throw new Error(`位置 ${i} 处期望 ',' 或 ')',实际是 ${JSON.stringify(c)}`);
|
||||
}
|
||||
|
||||
rows.push(row);
|
||||
skipSeparators();
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 取出某张表全部 INSERT 行。表不存在时返回空数组。 */
|
||||
export function readTable(sql: string, table: string): SqlValue[][] {
|
||||
const re = new RegExp('INSERT INTO `' + table.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '` VALUES (.*?);\\n', 'gs');
|
||||
const rows: SqlValue[][] = [];
|
||||
for (const m of sql.matchAll(re)) {
|
||||
rows.push(...parseTupleList(m[1]!));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 转成对象数组,便于按字段名取值。 */
|
||||
export function toObjects(rows: SqlValue[][], columns: string[]): Record<string, SqlValue>[] {
|
||||
return rows.map((row) => {
|
||||
const obj: Record<string, SqlValue> = {};
|
||||
columns.forEach((col, idx) => {
|
||||
obj[col] = row[idx] ?? null;
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user