Files
Alfred/drizzle/0004_db_schema_standardization.sql
lanyuanxiaoyao db40d04dc5 refactor(db): 统一数据库 schema — 软删除、命名规范、约束标准化
- 全表新增 deleted_at 列,统一软删除替代硬删除+archived_at
- models.model_id 重命名为 external_id,消除语义混淆
- conversations.model_id 改为可空(模型为建议而非绑定)
- messages 新增 updated_at,移除 CASCADE 改为 DAO 层级联
- 移除 DB 层 UNIQUE 约束,改为应用层检查(配合软删除)
- 新增 helpers.ts(baseColumns + 构造层防御)、ESLint 规则、契约测试
- 迁移 0004 补全 CHECK 约束(providers.type/materials.status/messages.role)
- DAO 层全面重写:级联软删除、应用层唯一、provider 删除保护
- 路由/前端/测试全量适配 externalId 重命名及类型变更
2026-06-05 01:02:23 +08:00

110 lines
4.7 KiB
SQL

-- DB schema standardization migration
-- 1. Rename columns
ALTER TABLE `projects` RENAME COLUMN `archived_at` TO `deleted_at`;
ALTER TABLE `models` RENAME COLUMN `model_id` TO `external_id`;
-- 2. Add deleted_at to remaining tables
ALTER TABLE `providers` ADD COLUMN `deleted_at` text;
ALTER TABLE `models` ADD COLUMN `deleted_at` text;
ALTER TABLE `conversations` ADD COLUMN `deleted_at` text;
ALTER TABLE `materials` ADD COLUMN `deleted_at` text;
ALTER TABLE `messages` ADD COLUMN `deleted_at` text;
-- 3. Add updated_at to messages
ALTER TABLE `messages` ADD COLUMN `updated_at` text NOT NULL DEFAULT '';
-- 4. Drop unique indexes (enforcement moves to app layer)
DROP INDEX IF EXISTS `projects_name_unique`;
DROP INDEX IF EXISTS `providers_name_unique`;
DROP INDEX IF EXISTS `models_provider_id_model_id_unique`;
-- 5. Rebuild messages table (FK cascade → no action, add updated_at + deleted_at in-table, add CHECK on role)
CREATE TABLE `messages_new` (
`id` text PRIMARY KEY NOT NULL,
`conversation_id` text NOT NULL,
`role` text NOT NULL CHECK (`role` IN ('assistant', 'system', 'user')),
`content` text NOT NULL DEFAULT '',
`parts` text,
`created_at` text NOT NULL,
`updated_at` text NOT NULL DEFAULT '',
`deleted_at` text,
FOREIGN KEY (`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
INSERT INTO `messages_new` (`id`, `conversation_id`, `role`, `content`, `parts`, `created_at`, `updated_at`, `deleted_at`)
SELECT `id`, `conversation_id`, `role`, `content`, `parts`, `created_at`, '', NULL FROM `messages`;
--> statement-breakpoint
DROP TABLE `messages`;
--> statement-breakpoint
ALTER TABLE `messages_new` RENAME TO `messages`;
--> statement-breakpoint
CREATE INDEX `messages_conversation_id_idx` ON `messages` (`conversation_id`);
--> statement-breakpoint
-- 6. Rebuild conversations table (model_id nullable, add deleted_at in-table)
CREATE TABLE `conversations_new` (
`id` text PRIMARY KEY NOT NULL,
`project_id` text NOT NULL,
`model_id` text,
`title` text NOT NULL DEFAULT '新会话',
`created_at` text NOT NULL,
`updated_at` text NOT NULL,
`deleted_at` text,
FOREIGN KEY (`model_id`) REFERENCES `models`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
INSERT INTO `conversations_new` (`id`, `project_id`, `model_id`, `title`, `created_at`, `updated_at`, `deleted_at`)
SELECT `id`, `project_id`, `model_id`, `title`, `created_at`, `updated_at`, NULL FROM `conversations`;
--> statement-breakpoint
DROP TABLE `conversations`;
--> statement-breakpoint
ALTER TABLE `conversations_new` RENAME TO `conversations`;
--> statement-breakpoint
CREATE INDEX `conversations_project_id_idx` ON `conversations` (`project_id`);
--> statement-breakpoint
CREATE INDEX `conversations_model_id_idx` ON `conversations` (`model_id`);
--> statement-breakpoint
-- 7. Rebuild providers table (add deleted_at in-table, add CHECK on type)
CREATE TABLE `providers_new` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`type` text NOT NULL DEFAULT 'openai-compatible' CHECK (`type` IN ('anthropic', 'openai', 'openai-compatible')),
`api_key` text NOT NULL,
`base_url` text NOT NULL,
`created_at` text NOT NULL,
`updated_at` text NOT NULL,
`deleted_at` text
);
--> statement-breakpoint
INSERT INTO `providers_new` (`id`, `name`, `type`, `api_key`, `base_url`, `created_at`, `updated_at`, `deleted_at`)
SELECT `id`, `name`, `type`, `api_key`, `base_url`, `created_at`, `updated_at`, NULL FROM `providers`;
--> statement-breakpoint
DROP TABLE `providers`;
--> statement-breakpoint
ALTER TABLE `providers_new` RENAME TO `providers`;
--> statement-breakpoint
-- 8. Rebuild materials table (add deleted_at in-table, add CHECK on status)
CREATE TABLE `materials_new` (
`id` text PRIMARY KEY NOT NULL,
`project_id` text NOT NULL,
`associated_date` text NOT NULL,
`description` text NOT NULL,
`status` text NOT NULL DEFAULT 'pending' CHECK (`status` IN ('pending', 'approved', 'discarded')),
`created_at` text NOT NULL,
`updated_at` text NOT NULL,
`deleted_at` text,
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
INSERT INTO `materials_new` (`id`, `project_id`, `associated_date`, `description`, `status`, `created_at`, `updated_at`, `deleted_at`)
SELECT `id`, `project_id`, `associated_date`, `description`, `status`, `created_at`, `updated_at`, NULL FROM `materials`;
--> statement-breakpoint
DROP TABLE `materials`;
--> statement-breakpoint
ALTER TABLE `materials_new` RENAME TO `materials`;
--> statement-breakpoint
CREATE INDEX `materials_project_id_idx` ON `materials` (`project_id`);