feat(db): initial migration with pgvector and the eight foundation tables
Creates the vector extension plus id_user, id_user_identity, id_consent, bl_quota_rule, bl_quota, bl_ledger, tk_task and ob_ai_call using the domain prefixes of 架构文档 §6.1, and seeds the quota rules. Reversible: verified by an upgrade/downgrade/upgrade round-trip, which CI now runs on every push.
This commit is contained in:
Executable
+79
@@ -0,0 +1,79 @@
|
||||
"""Alembic environment — async engine + pgvector.
|
||||
|
||||
NFR-MAINT-06 / 架构文档 §14.3: 迁移必须向后兼容(加列不删列、先双写后切读),
|
||||
并且必须可逆或明确标注不可逆。CI runs upgrade → downgrade → upgrade.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from logging.config import fileConfig
|
||||
|
||||
from alembic import context
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||
|
||||
from packages.shared.config import get_settings
|
||||
from packages.shared.models import Base
|
||||
|
||||
config = context.config
|
||||
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
config.set_main_option("sqlalchemy.url", get_settings().database_url)
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def _include_object(
|
||||
obj: object, name: str | None, type_: str, reflected: bool, compare_to: object
|
||||
) -> bool:
|
||||
# pgvector installs its own tables/types; never autogenerate against them.
|
||||
return not (type_ == "table" and name in {"vector", "spatial_ref_sys"})
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
context.configure(
|
||||
url=config.get_main_option("sqlalchemy.url"),
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
compare_type=True,
|
||||
include_object=_include_object,
|
||||
)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def do_run_migrations(connection: Connection) -> None:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
compare_type=True,
|
||||
include_object=_include_object,
|
||||
)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_async_migrations() -> None:
|
||||
connectable = async_engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
asyncio.run(run_async_migrations())
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
Reference in New Issue
Block a user