gaojintao01 commited on
Commit ·
e99cbb0
1
Parent(s): c8317fa
pg
Browse files
server/prisma/check-migration-dependencies.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fs = require('fs');
|
| 2 |
+
const path = require('path');
|
| 3 |
+
|
| 4 |
+
/**
|
| 5 |
+
* 检查迁移文件的外键依赖关系
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
// 存储表创建和引用信息的映射
|
| 9 |
+
const tableCreationOrder = {};
|
| 10 |
+
const foreignKeyReferences = {};
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* 解析迁移文件中的表创建和外键引用
|
| 14 |
+
*/
|
| 15 |
+
function parseMigrationFile(filePath) {
|
| 16 |
+
try {
|
| 17 |
+
const sqlContent = fs.readFileSync(filePath, 'utf8');
|
| 18 |
+
const lines = sqlContent.split('\n');
|
| 19 |
+
|
| 20 |
+
let currentTable = null;
|
| 21 |
+
let inCreateTable = false;
|
| 22 |
+
|
| 23 |
+
lines.forEach((line, index) => {
|
| 24 |
+
const trimmedLine = line.trim();
|
| 25 |
+
|
| 26 |
+
// 检测 CREATE TABLE 语句
|
| 27 |
+
if (trimmedLine.startsWith('CREATE TABLE')) {
|
| 28 |
+
const tableMatch = trimmedLine.match(/CREATE TABLE "([^"]+)"/);
|
| 29 |
+
if (tableMatch) {
|
| 30 |
+
currentTable = tableMatch[1];
|
| 31 |
+
inCreateTable = true;
|
| 32 |
+
tableCreationOrder[currentTable] = path.basename(path.dirname(filePath));
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// 检测外键约束
|
| 37 |
+
if (trimmedLine.includes('FOREIGN KEY') && trimmedLine.includes('REFERENCES')) {
|
| 38 |
+
const fkMatch = trimmedLine.match(/FOREIGN KEY \("[^"]+"\)\s+REFERENCES\s+"([^"]+)"/);
|
| 39 |
+
if (fkMatch && currentTable) {
|
| 40 |
+
const referencedTable = fkMatch[1];
|
| 41 |
+
if (!foreignKeyReferences[currentTable]) {
|
| 42 |
+
foreignKeyReferences[currentTable] = [];
|
| 43 |
+
}
|
| 44 |
+
foreignKeyReferences[currentTable].push({
|
| 45 |
+
referencedTable,
|
| 46 |
+
line: index + 1
|
| 47 |
+
});
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
// 检测表创建结束
|
| 52 |
+
if (inCreateTable && trimmedLine === ');') {
|
| 53 |
+
inCreateTable = false;
|
| 54 |
+
currentTable = null;
|
| 55 |
+
}
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
return true;
|
| 59 |
+
} catch (error) {
|
| 60 |
+
console.error(`解析文件失败: ${filePath}`, error.message);
|
| 61 |
+
return false;
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/**
|
| 66 |
+
* 递归处理目录中的所有迁移文件
|
| 67 |
+
*/
|
| 68 |
+
function processMigrationsDirectory(migrationsDir) {
|
| 69 |
+
const items = fs.readdirSync(migrationsDir);
|
| 70 |
+
|
| 71 |
+
items.forEach(item => {
|
| 72 |
+
const itemPath = path.join(migrationsDir, item);
|
| 73 |
+
const stat = fs.statSync(itemPath);
|
| 74 |
+
|
| 75 |
+
if (stat.isDirectory() && item !== 'migration_lock.toml') {
|
| 76 |
+
// 递归处理子目录
|
| 77 |
+
processMigrationsDirectory(itemPath);
|
| 78 |
+
} else if (item === 'migration.sql') {
|
| 79 |
+
// 处理迁移文件
|
| 80 |
+
parseMigrationFile(itemPath);
|
| 81 |
+
}
|
| 82 |
+
});
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/**
|
| 86 |
+
* 检查依赖关系
|
| 87 |
+
*/
|
| 88 |
+
function checkDependencies() {
|
| 89 |
+
console.log('=== 迁移文件外键依赖关系检查 ===\n');
|
| 90 |
+
|
| 91 |
+
const issues = [];
|
| 92 |
+
|
| 93 |
+
// 检查每个表的外键引用
|
| 94 |
+
Object.entries(foreignKeyReferences).forEach(([table, references]) => {
|
| 95 |
+
references.forEach(({ referencedTable, line }) => {
|
| 96 |
+
// 检查被引用的表是否在当前迁移或更早的迁移中创建
|
| 97 |
+
if (tableCreationOrder[referencedTable]) {
|
| 98 |
+
const currentMigration = tableCreationOrder[table];
|
| 99 |
+
const referencedMigration = tableCreationOrder[referencedTable];
|
| 100 |
+
|
| 101 |
+
// 如果被引用的表在更新的迁移中创建,这可能有问题
|
| 102 |
+
if (currentMigration < referencedMigration) {
|
| 103 |
+
issues.push({
|
| 104 |
+
table,
|
| 105 |
+
referencedTable,
|
| 106 |
+
issue: `表 ${table} 引用了在更晚迁移中创建的表 ${referencedTable}`,
|
| 107 |
+
migration: currentMigration
|
| 108 |
+
});
|
| 109 |
+
}
|
| 110 |
+
} else {
|
| 111 |
+
issues.push({
|
| 112 |
+
table,
|
| 113 |
+
referencedTable,
|
| 114 |
+
issue: `表 ${table} 引用了不存在的表 ${referencedTable}`,
|
| 115 |
+
migration: tableCreationOrder[table]
|
| 116 |
+
});
|
| 117 |
+
}
|
| 118 |
+
});
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
if (issues.length === 0) {
|
| 122 |
+
console.log('✅ 没有发现外键依赖关系问题');
|
| 123 |
+
} else {
|
| 124 |
+
console.log('❌ 发现以下外键依赖关系问题:');
|
| 125 |
+
issues.forEach(issue => {
|
| 126 |
+
console.log(` 表: ${issue.table}`);
|
| 127 |
+
console.log(` 引用表: ${issue.referencedTable}`);
|
| 128 |
+
console.log(` 问题: ${issue.issue}`);
|
| 129 |
+
console.log(` 迁移: ${issue.migration}`);
|
| 130 |
+
console.log('');
|
| 131 |
+
});
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
return issues;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
/**
|
| 138 |
+
* 显示表创建顺序
|
| 139 |
+
*/
|
| 140 |
+
function showTableCreationOrder() {
|
| 141 |
+
console.log('\n=== 表创建顺序 ===');
|
| 142 |
+
|
| 143 |
+
const sortedTables = Object.entries(tableCreationOrder)
|
| 144 |
+
.sort(([,a], [,b]) => a.localeCompare(b))
|
| 145 |
+
.map(([table, migration]) => ` ${table} (${migration})`);
|
| 146 |
+
|
| 147 |
+
sortedTables.forEach(table => console.log(table));
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
/**
|
| 151 |
+
* 主函数
|
| 152 |
+
*/
|
| 153 |
+
function main() {
|
| 154 |
+
const migrationsDir = path.join(__dirname, 'migrations');
|
| 155 |
+
|
| 156 |
+
console.log('开始检查迁移文件外键依赖关系...\n');
|
| 157 |
+
|
| 158 |
+
processMigrationsDirectory(migrationsDir);
|
| 159 |
+
|
| 160 |
+
showTableCreationOrder();
|
| 161 |
+
const issues = checkDependencies();
|
| 162 |
+
|
| 163 |
+
return {
|
| 164 |
+
tableCreationOrder,
|
| 165 |
+
foreignKeyReferences,
|
| 166 |
+
issues: issues.length
|
| 167 |
+
};
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
// 运行检查
|
| 171 |
+
if (require.main === module) {
|
| 172 |
+
main();
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
module.exports = { parseMigrationFile, processMigrationsDirectory, checkDependencies };
|
server/prisma/migrations/20230921191814_init/migration.sql
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
-- CreateTable
|
| 2 |
CREATE TABLE "api_keys" (
|
| 3 |
"id" SERIAL PRIMARY KEY,
|
| 4 |
"secret" TEXT,
|
|
@@ -7,30 +7,6 @@ CREATE TABLE "api_keys" (
|
|
| 7 |
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 8 |
);
|
| 9 |
|
| 10 |
-
-- CreateTable
|
| 11 |
-
CREATE TABLE "workspace_documents" (
|
| 12 |
-
"id" SERIAL PRIMARY KEY,
|
| 13 |
-
"docId" TEXT NOT NULL,
|
| 14 |
-
"filename" TEXT NOT NULL,
|
| 15 |
-
"docpath" TEXT NOT NULL,
|
| 16 |
-
"workspaceId" INTEGER NOT NULL,
|
| 17 |
-
"metadata" TEXT,
|
| 18 |
-
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 19 |
-
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 20 |
-
CONSTRAINT "workspace_documents_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
| 21 |
-
);
|
| 22 |
-
|
| 23 |
-
-- CreateTable
|
| 24 |
-
CREATE TABLE "invites" (
|
| 25 |
-
"id" SERIAL PRIMARY KEY,
|
| 26 |
-
"code" TEXT NOT NULL,
|
| 27 |
-
"status" TEXT NOT NULL DEFAULT 'pending',
|
| 28 |
-
"claimedBy" INTEGER,
|
| 29 |
-
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 30 |
-
"createdBy" INTEGER NOT NULL,
|
| 31 |
-
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 32 |
-
);
|
| 33 |
-
|
| 34 |
-- CreateTable
|
| 35 |
CREATE TABLE "system_settings" (
|
| 36 |
"id" SERIAL PRIMARY KEY,
|
|
@@ -52,11 +28,26 @@ CREATE TABLE "users" (
|
|
| 52 |
);
|
| 53 |
|
| 54 |
-- CreateTable
|
| 55 |
-
CREATE TABLE "
|
| 56 |
"id" SERIAL PRIMARY KEY,
|
| 57 |
-
"
|
| 58 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
| 60 |
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 61 |
);
|
| 62 |
|
|
@@ -70,19 +61,28 @@ CREATE TABLE "welcome_messages" (
|
|
| 70 |
);
|
| 71 |
|
| 72 |
-- CreateTable
|
| 73 |
-
CREATE TABLE "
|
| 74 |
"id" SERIAL PRIMARY KEY,
|
| 75 |
-
"
|
| 76 |
-
"
|
| 77 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 79 |
-
"openAiTemp" REAL,
|
| 80 |
-
"openAiHistory" INTEGER NOT NULL DEFAULT 20,
|
| 81 |
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 82 |
-
"
|
| 83 |
);
|
| 84 |
|
| 85 |
-
-- CreateTable
|
| 86 |
CREATE TABLE "workspace_chats" (
|
| 87 |
"id" SERIAL PRIMARY KEY,
|
| 88 |
"workspaceId" INTEGER NOT NULL,
|
|
@@ -95,7 +95,7 @@ CREATE TABLE "workspace_chats" (
|
|
| 95 |
CONSTRAINT "workspace_chats_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
| 96 |
);
|
| 97 |
|
| 98 |
-
-- CreateTable
|
| 99 |
CREATE TABLE "workspace_users" (
|
| 100 |
"id" SERIAL PRIMARY KEY,
|
| 101 |
"user_id" INTEGER NOT NULL,
|
|
@@ -109,9 +109,6 @@ CREATE TABLE "workspace_users" (
|
|
| 109 |
-- CreateIndex
|
| 110 |
CREATE UNIQUE INDEX "api_keys_secret_key" ON "api_keys"("secret");
|
| 111 |
|
| 112 |
-
-- CreateIndex
|
| 113 |
-
CREATE UNIQUE INDEX "workspace_documents_docId_key" ON "workspace_documents"("docId");
|
| 114 |
-
|
| 115 |
-- CreateIndex
|
| 116 |
CREATE UNIQUE INDEX "invites_code_key" ON "invites"("code");
|
| 117 |
|
|
@@ -123,3 +120,6 @@ CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
|
|
| 123 |
|
| 124 |
-- CreateIndex
|
| 125 |
CREATE UNIQUE INDEX "workspaces_slug_key" ON "workspaces"("slug");
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- CreateTable (基础表 - 无外键依赖)
|
| 2 |
CREATE TABLE "api_keys" (
|
| 3 |
"id" SERIAL PRIMARY KEY,
|
| 4 |
"secret" TEXT,
|
|
|
|
| 7 |
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 8 |
);
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
-- CreateTable
|
| 11 |
CREATE TABLE "system_settings" (
|
| 12 |
"id" SERIAL PRIMARY KEY,
|
|
|
|
| 28 |
);
|
| 29 |
|
| 30 |
-- CreateTable
|
| 31 |
+
CREATE TABLE "workspaces" (
|
| 32 |
"id" SERIAL PRIMARY KEY,
|
| 33 |
+
"name" TEXT NOT NULL,
|
| 34 |
+
"slug" TEXT NOT NULL,
|
| 35 |
+
"vectorTag" TEXT,
|
| 36 |
+
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 37 |
+
"openAiTemp" REAL,
|
| 38 |
+
"openAiHistory" INTEGER NOT NULL DEFAULT 20,
|
| 39 |
+
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 40 |
+
"openAiPrompt" TEXT
|
| 41 |
+
);
|
| 42 |
+
|
| 43 |
+
-- CreateTable
|
| 44 |
+
CREATE TABLE "invites" (
|
| 45 |
+
"id" SERIAL PRIMARY KEY,
|
| 46 |
+
"code" TEXT NOT NULL,
|
| 47 |
+
"status" TEXT NOT NULL DEFAULT 'pending',
|
| 48 |
+
"claimedBy" INTEGER,
|
| 49 |
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 50 |
+
"createdBy" INTEGER NOT NULL,
|
| 51 |
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 52 |
);
|
| 53 |
|
|
|
|
| 61 |
);
|
| 62 |
|
| 63 |
-- CreateTable
|
| 64 |
+
CREATE TABLE "document_vectors" (
|
| 65 |
"id" SERIAL PRIMARY KEY,
|
| 66 |
+
"docId" TEXT NOT NULL,
|
| 67 |
+
"vectorId" TEXT NOT NULL,
|
| 68 |
+
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 69 |
+
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 70 |
+
);
|
| 71 |
+
|
| 72 |
+
-- CreateTable (有外键依赖的表 - 依赖 workspaces)
|
| 73 |
+
CREATE TABLE "workspace_documents" (
|
| 74 |
+
"id" SERIAL PRIMARY KEY,
|
| 75 |
+
"docId" TEXT NOT NULL,
|
| 76 |
+
"filename" TEXT NOT NULL,
|
| 77 |
+
"docpath" TEXT NOT NULL,
|
| 78 |
+
"workspaceId" INTEGER NOT NULL,
|
| 79 |
+
"metadata" TEXT,
|
| 80 |
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
|
| 81 |
"lastUpdatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 82 |
+
CONSTRAINT "workspace_documents_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
| 83 |
);
|
| 84 |
|
| 85 |
+
-- CreateTable (有外键依赖的表 - 依赖 workspaces 和 users)
|
| 86 |
CREATE TABLE "workspace_chats" (
|
| 87 |
"id" SERIAL PRIMARY KEY,
|
| 88 |
"workspaceId" INTEGER NOT NULL,
|
|
|
|
| 95 |
CONSTRAINT "workspace_chats_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
| 96 |
);
|
| 97 |
|
| 98 |
+
-- CreateTable (有外键依赖的表 - 依赖 workspaces 和 users)
|
| 99 |
CREATE TABLE "workspace_users" (
|
| 100 |
"id" SERIAL PRIMARY KEY,
|
| 101 |
"user_id" INTEGER NOT NULL,
|
|
|
|
| 109 |
-- CreateIndex
|
| 110 |
CREATE UNIQUE INDEX "api_keys_secret_key" ON "api_keys"("secret");
|
| 111 |
|
|
|
|
|
|
|
|
|
|
| 112 |
-- CreateIndex
|
| 113 |
CREATE UNIQUE INDEX "invites_code_key" ON "invites"("code");
|
| 114 |
|
|
|
|
| 120 |
|
| 121 |
-- CreateIndex
|
| 122 |
CREATE UNIQUE INDEX "workspaces_slug_key" ON "workspaces"("slug");
|
| 123 |
+
|
| 124 |
+
-- CreateIndex
|
| 125 |
+
CREATE UNIQUE INDEX "workspace_documents_docId_key" ON "workspace_documents"("docId");
|