use sea_orm_migration::prelude::*;
use sea_orm_migration::sea_orm::ConnectionTrait;
pub const FIRST_EPOCH: i64 = 1;
pub const STATE_ACTIVE: &str = "active";
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.get_connection()
.execute_unprepared(
r"
CREATE TABLE IF NOT EXISTS embedding_space (
tenant_id UUID NOT NULL,
epoch BIGINT NOT NULL,
identity_hash TEXT NOT NULL,
model_artifact TEXT NOT NULL,
tokenizer_artifact TEXT NOT NULL,
preprocessing JSONB NOT NULL DEFAULT '{}'::jsonb,
pooling JSONB NOT NULL DEFAULT '{}'::jsonb,
normalization JSONB NOT NULL DEFAULT '{}'::jsonb,
dimension INTEGER NOT NULL,
state TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
activated_at TIMESTAMPTZ,
PRIMARY KEY (tenant_id, epoch)
);
-- At most one active space per deployment. The single-embedding-space
-- constraint is a database fact, not a convention the boot path remembers to
-- uphold: two active rows would make `node.embedding_epoch` ambiguous and
-- similarity search would rank across incomparable vectors.
CREATE UNIQUE INDEX IF NOT EXISTS idx_embedding_space_one_active
ON embedding_space (tenant_id) WHERE state = 'active';
",
)
.await?;
manager
.get_connection()
.execute_unprepared(
r"
DROP INDEX IF EXISTS idx_node_embedding;
CREATE INDEX IF NOT EXISTS idx_node_embedding
ON node USING hnsw (embedding vector_cosine_ops)
WHERE deleted_at IS NULL
AND embedding IS NOT NULL
AND embedding_epoch IS NOT NULL;
",
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.get_connection()
.execute_unprepared(
r"
DROP TABLE IF EXISTS embedding_space;
DROP INDEX IF EXISTS idx_node_embedding;
CREATE INDEX IF NOT EXISTS idx_node_embedding
ON node USING hnsw (embedding vector_cosine_ops)
WHERE deleted_at IS NULL AND embedding IS NOT NULL;
",
)
.await?;
Ok(())
}
}