Files
certd/packages/ui/certd-server/db/transform.js
T

181 lines
6.4 KiB
JavaScript
Raw Normal View History

2026-05-31 01:41:33 +08:00
import fs from "fs";
2026-06-04 01:14:21 +08:00
const MYSQL_INDEX_PREFIX_LENGTH = 191;
const MYSQL_TABLE_OPTIONS = "ENGINE = InnoDB ROW_FORMAT = DYNAMIC";
2024-10-14 12:33:09 +08:00
/**
* ## sqlite与postgres不同点
* 1.
* sqlite: AUTOINCREAMENT
* postgresql: GENERATED BY DEFAULT AS IDENTITY
*
* 2.
* sqlite: datetime
* postgresql: timestamp
*
* 3.
* sqlite: update sqlite_sequence set seq = 1000 where name = 'sys_role' ;
* postgresql: select setval('sys_role_id_seq', 1000);
*
* 4.
* sqlite: "disabled" boolean DEFAULT (0)
* postgresql: "disabled" boolean DEFAULT (false)
*
* 5.
* sqlite: last_insert_rowid()
* postgresql: LASTVAL()
*
* 6.
* sqlite: integer
* postgresql: bigint
*/
2024-12-09 17:47:01 +08:00
function transformPG() {
2024-10-14 12:33:09 +08:00
// 读取文件列表
2026-05-31 01:41:33 +08:00
const sqliteFiles = fs.readdirSync("./migration/");
const pgFiles = fs.readdirSync("./migration-pg/");
2024-10-14 12:33:09 +08:00
//找出pg里面没有的文件
const notFiles = sqliteFiles.filter(file => !pgFiles.includes(file));
for (const notFile of notFiles) {
//开始转换
2026-05-31 01:41:33 +08:00
const sqliteSql = fs.readFileSync(`./migration/${notFile}`, "utf-8");
let pgSql = sqliteSql.replaceAll(/AUTOINCREMENT/g, "GENERATED BY DEFAULT AS IDENTITY");
pgSql = pgSql.replaceAll(/datetime/g, "timestamp");
pgSql = pgSql.replaceAll(/boolean DEFAULT \(0\)/g, "boolean DEFAULT (false)");
pgSql = pgSql.replaceAll(/boolean DEFAULT \(1\)/g, "boolean DEFAULT (true)");
pgSql = pgSql.replaceAll(/boolean.*NOT NULL DEFAULT \(0\)/g, "boolean NOT NULL DEFAULT (false)");
pgSql = pgSql.replaceAll(/boolean.*NOT NULL DEFAULT \(1\)/g, "boolean NOT NULL DEFAULT (true)");
pgSql = pgSql.replaceAll(/integer/g, "bigint");
pgSql = pgSql.replaceAll(/INTEGER/g, "bigint");
pgSql = pgSql.replaceAll(/last_insert_rowid\(\)/g, "LASTVAL()");
2024-10-14 12:33:09 +08:00
fs.writeFileSync(`./migration-pg/${notFile}`, pgSql);
}
if (notFiles.length > 0) {
2026-05-31 01:41:33 +08:00
console.log("sqlite->pg 转换完成");
2024-10-14 12:33:09 +08:00
2026-05-31 01:41:33 +08:00
throw new Error("sqlite->pg 转换完成,有更新,需要测试pg");
2024-10-14 12:33:09 +08:00
} else {
2026-05-31 01:41:33 +08:00
console.log("sql无需更新");
2024-10-14 12:33:09 +08:00
}
}
2024-12-09 17:47:01 +08:00
2026-06-04 01:14:21 +08:00
function buildMysqlTableColumnMap(sql) {
const tableColumnMap = new Map();
const createTableReg = /CREATE TABLE `([^`]*)`[\s\S]*?;/gi;
for (const match of sql.matchAll(createTableReg)) {
const [statement, tableName] = match;
const tableBody = getCreateTableBody(statement);
const columnMap = new Map();
const columnReg = /`([^`]*)`\s+varchar\((\d+)\)/gi;
for (const columnMatch of tableBody.matchAll(columnReg)) {
const [, columnName, length] = columnMatch;
columnMap.set(columnName, Number(length));
}
tableColumnMap.set(tableName, columnMap);
}
const alterAddColumnReg = /ALTER TABLE\s+`?([^`\s]+)`?\s+ADD COLUMN\s+`?([^`\s]+)`?\s+varchar\((\d+)\)/gi;
for (const match of sql.matchAll(alterAddColumnReg)) {
const [, tableName, columnName, length] = match;
const columnMap = tableColumnMap.get(tableName) || new Map();
columnMap.set(columnName, Number(length));
tableColumnMap.set(tableName, columnMap);
}
return tableColumnMap;
}
function getCreateTableBody(statement) {
const start = statement.indexOf("(");
const end = statement.lastIndexOf(")");
if (start === -1 || end === -1 || end <= start) {
return "";
}
return statement.substring(start + 1, end);
}
function appendMysqlTableOptions(sql) {
const createTableReg = /CREATE TABLE `([^`]*)`[\s\S]*?;/gi;
return sql.replace(createTableReg, statement => {
const sqlWithoutSemicolon = statement.replace(/;\s*$/, "");
const sqlWithoutOptions = sqlWithoutSemicolon.replace(/\s+ENGINE\s*=\s*\w+(?:\s+ROW_FORMAT\s*=\s*\w+)?\s*$/i, "");
return `${sqlWithoutOptions} ${MYSQL_TABLE_OPTIONS};`;
});
}
function addMysqlIndexPrefix(sql) {
const tableColumnMap = buildMysqlTableColumnMap(sql);
const createIndexReg = /CREATE\s+(UNIQUE\s+)?INDEX\s+`([^`]*)`\s+ON\s+`([^`]*)`\s*\(([^;]*)\);/gi;
return sql.replace(createIndexReg, (statement, uniqueKeyword, indexName, tableName, columns) => {
const columnMap = tableColumnMap.get(tableName);
if (!columnMap) {
return statement;
}
const parsedColumns = columns.split(",").map(item => {
const columnMatch = item.trim().match(/^`([^`]*)`(?:\((\d+)\))?$/);
if (!columnMatch) {
return item.trim();
}
const [, columnName, prefixLength] = columnMatch;
const columnLength = columnMap.get(columnName);
if (!columnLength || columnLength <= MYSQL_INDEX_PREFIX_LENGTH) {
return item.trim();
}
if (prefixLength && Number(prefixLength) <= MYSQL_INDEX_PREFIX_LENGTH) {
return item.trim();
}
// MySQL 5.7 老配置下 utf8mb4 varchar(255) 完整索引可能超过 767/1000 字节限制。
if (uniqueKeyword) {
throw new Error(`唯一索引 ${indexName} 的字段 ${tableName}.${columnName} 长度超过 ${MYSQL_INDEX_PREFIX_LENGTH},不能自动改成前缀索引,请缩短字段长度或增加 hash 字段`);
}
return `\`${columnName}\`(${MYSQL_INDEX_PREFIX_LENGTH})`;
});
const unique = uniqueKeyword || "";
return `CREATE ${unique}INDEX \`${indexName}\` ON \`${tableName}\` (${parsedColumns.join(", ")});`;
});
}
2024-12-09 17:47:01 +08:00
function transformMysql() {
// 读取文件列表
2026-05-31 01:41:33 +08:00
const sqliteFiles = fs.readdirSync("./migration/");
const pgFiles = fs.readdirSync("./migration-mysql/");
2024-12-09 17:47:01 +08:00
//找出pg里面没有的文件
const notFiles = sqliteFiles.filter(file => !pgFiles.includes(file));
for (const notFile of notFiles) {
//开始转换
2026-05-31 01:41:33 +08:00
const sqliteSql = fs.readFileSync(`./migration/${notFile}`, "utf-8");
let pgSql = sqliteSql.replaceAll(/AUTOINCREMENT/g, "AUTO_INCREMENT");
pgSql = pgSql.replaceAll(/datetime/g, "timestamp");
2024-12-09 17:47:01 +08:00
//DEFAULT (xxx) 替换成 DEFAULT xxx
2026-05-31 01:41:33 +08:00
pgSql = pgSql.replaceAll(/DEFAULT \(([^)]*)\)/g, "DEFAULT $1");
pgSql = pgSql.replaceAll(/integer/g, "bigint");
pgSql = pgSql.replaceAll(/INTEGER/g, "bigint");
pgSql = pgSql.replaceAll(/last_insert_rowid\(\)/g, "LAST_INSERT_ID()");
2025-01-06 09:39:44 +08:00
//text 改成longtext
2026-05-31 01:41:33 +08:00
pgSql = pgSql.replaceAll(/text/g, "longtext");
2024-12-09 17:47:01 +08:00
//双引号 替换成反引号
2026-05-31 01:41:33 +08:00
pgSql = pgSql.replaceAll(/"/g, "`");
2026-02-09 18:40:15 +08:00
2026-06-04 01:14:21 +08:00
pgSql = appendMysqlTableOptions(pgSql);
pgSql = addMysqlIndexPrefix(pgSql);
2024-12-09 17:47:01 +08:00
fs.writeFileSync(`./migration-mysql/${notFile}`, pgSql);
}
if (notFiles.length > 0) {
2026-05-31 01:41:33 +08:00
console.log("sqlite->mysql 转换完成");
2024-12-09 17:47:01 +08:00
2026-05-31 01:41:33 +08:00
throw new Error("sqlite->mysql 转换完成,有更新,需要测试mysql");
2024-12-09 17:47:01 +08:00
} else {
2026-05-31 01:41:33 +08:00
console.log("sql无需更新");
2024-12-09 17:47:01 +08:00
}
}
transformPG();
transformMysql();