use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { // 分区表必须用 raw SQL,SeaORM schema builder 不支持 PARTITION BY let sql = r#" CREATE TABLE IF NOT EXISTS device_readings ( id UUID NOT NULL DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, patient_id UUID NOT NULL, device_id VARCHAR(64), device_type VARCHAR(32) NOT NULL, device_model VARCHAR(64), raw_value JSONB NOT NULL, measured_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ ) PARTITION BY RANGE (measured_at); "#; manager.get_connection().execute_unprepared(sql).await?; // 分区表主键必须包含分区键 manager .get_connection() .execute_unprepared("ALTER TABLE device_readings ADD PRIMARY KEY (id, measured_at);") .await?; // 核心查询索引 manager.get_connection().execute_unprepared( "CREATE INDEX idx_dr_tenant_patient ON device_readings (tenant_id, patient_id, measured_at DESC);" ).await?; manager.get_connection().execute_unprepared( "CREATE INDEX idx_dr_device_type ON device_readings (tenant_id, device_type, measured_at DESC);" ).await?; // 创建初始分区(当前月 + 未来 3 个月) for (suffix, start, end) in [ ("2026_05", "2026-05-01", "2026-06-01"), ("2026_06", "2026-06-01", "2026-07-01"), ("2026_07", "2026-07-01", "2026-08-01"), ("2026_08", "2026-08-01", "2026-09-01"), ] { let partition_sql = format!( "CREATE TABLE IF NOT EXISTS device_readings_{suffix} PARTITION OF device_readings FOR VALUES FROM ('{start}') TO ('{end}');" ); manager .get_connection() .execute_unprepared(&partition_sql) .await?; } Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { for suffix in ["2026_05", "2026_06", "2026_07", "2026_08"] { manager .get_connection() .execute_unprepared(&format!("DROP TABLE IF EXISTS device_readings_{suffix};")) .await .ok(); } manager .get_connection() .execute_unprepared("DROP TABLE IF EXISTS device_readings;") .await?; Ok(()) } }