2026-01-25 22:34:33 +08:00
|
|
|
|
"""数据库 Schema 定义。
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
- ORM 模型:用于结构相对稳定、需要 ORM 能力的表(例如 Simulation)
|
|
|
|
|
|
- Core Table:用于时序/大数据量写入或更灵活的 SQL 操作(例如 vehicle_signals、server_metrics、RBAC 表等)
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
2026-01-25 22:34:33 +08:00
|
|
|
|
from sqlalchemy import JSON, BigInteger, Boolean, Column, DateTime, Float, ForeignKey, Index, Integer, String, Table, text
|
2026-01-19 14:27:41 +08:00
|
|
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
|
|
|
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Base(DeclarativeBase):
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""SQLAlchemy Declarative Base。"""
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-25 22:34:33 +08:00
|
|
|
|
class SimulationScene(Base):
|
|
|
|
|
|
"""仿真场景配置(非时序数据)。"""
|
|
|
|
|
|
__tablename__ = "sim_scenes"
|
|
|
|
|
|
scene_id: Mapped[str] = mapped_column(String(64), primary_key=True, comment="场景 ID")
|
|
|
|
|
|
scene_name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True, index=True, comment="场景名称")
|
|
|
|
|
|
scene_desc: Mapped[str | None] = mapped_column(String(255), nullable=True, comment="场景描述(可选)")
|
|
|
|
|
|
scene_config: Mapped[dict] = mapped_column(JSON, default=dict, comment="场景配置信息(JSON)")
|
|
|
|
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False, index=True, comment="是否启用")
|
|
|
|
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)")
|
|
|
|
|
|
updated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, comment="更新时间(UTC)")
|
|
|
|
|
|
|
2026-01-19 14:27:41 +08:00
|
|
|
|
|
2026-01-25 22:34:33 +08:00
|
|
|
|
class SimulationTask(Base):
|
|
|
|
|
|
"""仿真任务记录(非时序数据)。"""
|
|
|
|
|
|
__tablename__ = "sim_tasks"
|
|
|
|
|
|
"""以下为仿真任务相关配置"""
|
|
|
|
|
|
task_id: Mapped[str] = mapped_column(String(64), primary_key=True, comment="任务 ID")
|
|
|
|
|
|
task_name: Mapped[str | None] = mapped_column(String(255), nullable=True, comment="任务名称")
|
|
|
|
|
|
scene_id: Mapped[str | None] = mapped_column(
|
|
|
|
|
|
String(64), ForeignKey("sim_scenes.scene_id"), nullable=True, index=True, comment="仿真场景 ID(场景表中选择)"
|
|
|
|
|
|
)
|
|
|
|
|
|
scene_name: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True, comment="仿真场景名称")
|
|
|
|
|
|
scene_config: Mapped[dict] = mapped_column(JSON, default=dict, comment="仿真场景配置信息")
|
|
|
|
|
|
config_created_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True, comment="配置创建时间(UTC)")
|
|
|
|
|
|
"""以下为仿真启停操作状态相关记录信息"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True, comment="开始时间(UTC)")
|
|
|
|
|
|
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True, comment="结束时间(UTC)")
|
2026-01-25 22:34:33 +08:00
|
|
|
|
status: Mapped[str] = mapped_column(String(32), index=True, comment="仿真任务状态(wait/running/stopped/archived 等)")
|
|
|
|
|
|
operator: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True, comment="仿真操作员")
|
|
|
|
|
|
"""以下为开始时发送给Unity程序的连接及初始化配置"""
|
|
|
|
|
|
unity_host: Mapped[str | None] = mapped_column(String(64), nullable=True, comment="Unity Socket 主机")
|
|
|
|
|
|
unity_port: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="Unity Socket 端口")
|
|
|
|
|
|
sync_timestamp: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True, comment="同步基准时间戳(毫秒)")
|
|
|
|
|
|
init_config: Mapped[dict | None] = mapped_column(JSONB, nullable=True, comment="InitConfig 原始内容(主控→Unity,JSONB)")
|
|
|
|
|
|
init_sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True, comment="InitConfig 发送时间(UTC)")
|
2026-01-19 14:27:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vehicle_signals = Table(
|
|
|
|
|
|
"sim_vehicle_signals",
|
|
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="信号采样时间(UTC)"),
|
2026-01-25 22:34:33 +08:00
|
|
|
|
Column("simulation_id", String(64), nullable=False, index=True, comment="仿真任务 ID(sim_tasks.task_id)"),
|
|
|
|
|
|
Column("vehicle_id", String(64), nullable=False, index=True, comment="实物车辆ID,默认值为'0'"),
|
2026-01-19 14:27:41 +08:00
|
|
|
|
Column("seq", BigInteger, nullable=False, comment="信号序列号(单仿真内递增)"),
|
|
|
|
|
|
Column("signals", JSONB, nullable=False, comment="车辆信号载荷(JSONB)"),
|
|
|
|
|
|
Index("idx_vehicle_signals_sim_ts", "simulation_id", "ts"),
|
|
|
|
|
|
comment="车辆信号时序数据(TimescaleDB hypertable)",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-25 22:34:33 +08:00
|
|
|
|
unity_frames = Table(
|
|
|
|
|
|
"sim_unity_frames",
|
2026-01-19 14:27:41 +08:00
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="帧时间(UTC)"),
|
2026-01-25 22:34:33 +08:00
|
|
|
|
Column("simulation_id", String(64), nullable=False, index=True, comment="仿真任务 ID(sim_tasks.task_id)"),
|
2026-01-19 14:27:41 +08:00
|
|
|
|
Column("vehicle_id", String(64), nullable=False, index=True, comment="虚拟车辆 ID"),
|
|
|
|
|
|
Column("seq", BigInteger, nullable=False, comment="帧序号(单仿真单车内递增)"),
|
|
|
|
|
|
Column("pos_x", Float, nullable=False, comment="位置 X(世界坐标)"),
|
|
|
|
|
|
Column("pos_y", Float, nullable=False, comment="位置 Y(世界坐标)"),
|
|
|
|
|
|
Column("pos_z", Float, nullable=False, comment="位置 Z(世界坐标)"),
|
|
|
|
|
|
Column("rot_x", Float, nullable=False, comment="旋转四元数 X"),
|
|
|
|
|
|
Column("rot_y", Float, nullable=False, comment="旋转四元数 Y"),
|
|
|
|
|
|
Column("rot_z", Float, nullable=False, comment="旋转四元数 Z"),
|
|
|
|
|
|
Column("rot_w", Float, nullable=False, comment="旋转四元数 W"),
|
|
|
|
|
|
Column("lin_vel_x", Float, nullable=True, comment="线速度 X(可选)"),
|
|
|
|
|
|
Column("lin_vel_y", Float, nullable=True, comment="线速度 Y(可选)"),
|
|
|
|
|
|
Column("lin_vel_z", Float, nullable=True, comment="线速度 Z(可选)"),
|
|
|
|
|
|
Column("ang_vel_x", Float, nullable=True, comment="角速度 X(可选)"),
|
|
|
|
|
|
Column("ang_vel_y", Float, nullable=True, comment="角速度 Y(可选)"),
|
|
|
|
|
|
Column("ang_vel_z", Float, nullable=True, comment="角速度 Z(可选)"),
|
|
|
|
|
|
Column("controls", JSONB, nullable=True, comment="控制量(油门/刹车/方向/档位等,JSONB)"),
|
|
|
|
|
|
Column("extra", JSONB, nullable=True, comment="扩展字段(仿真引擎自定义,JSONB)"),
|
|
|
|
|
|
Index("idx_unity_frames_sim_vehicle_ts", "simulation_id", "vehicle_id", "ts"),
|
|
|
|
|
|
comment="虚拟车辆驱动仿真帧数据(用于 Unity 车辆模型运动与回放,TimescaleDB hypertable)",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
screen_recordings = Table(
|
|
|
|
|
|
"sim_screen_videos",
|
|
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("video_id", String(64), primary_key=True, comment="录制文件记录 ID"),
|
2026-01-25 22:34:33 +08:00
|
|
|
|
Column("simulation_id", String(64), nullable=False, index=True, comment="仿真任务 ID(sim_tasks.task_id)"),
|
2026-01-19 14:27:41 +08:00
|
|
|
|
Column("screen_type", String(32), nullable=False, index=True, comment="屏幕类型(big_screen/vehicle_screen 等)"),
|
|
|
|
|
|
Column("source_name", String(64), nullable=True, index=True, comment="录制源名称(可选,如设备号/通道号)"),
|
|
|
|
|
|
Column("status", String(32), nullable=False, index=True, comment="状态(recording/ready/failed 等)"),
|
|
|
|
|
|
Column("relative_path", String(1024), nullable=False, comment="相对文件根目录的路径(用于下载/归档)"),
|
|
|
|
|
|
Column("file_name", String(255), nullable=True, comment="文件名(可选)"),
|
|
|
|
|
|
Column("format", String(32), nullable=True, comment="容器格式(mp4/mkv 等)"),
|
|
|
|
|
|
Column("codec", String(64), nullable=True, comment="编码信息(H264/H265 等)"),
|
|
|
|
|
|
Column("width", BigInteger, nullable=True, comment="视频宽度(像素)"),
|
|
|
|
|
|
Column("height", BigInteger, nullable=True, comment="视频高度(像素)"),
|
|
|
|
|
|
Column("fps", Float, nullable=True, comment="帧率(可选)"),
|
|
|
|
|
|
Column("duration_ms", BigInteger, nullable=True, comment="时长(毫秒,可选)"),
|
|
|
|
|
|
Column("size_bytes", BigInteger, nullable=True, comment="文件大小(字节,可选)"),
|
|
|
|
|
|
Column("recorded_started_at", DateTime(timezone=True), nullable=True, index=True, comment="录制开始时间(UTC,可选)"),
|
|
|
|
|
|
Column("recorded_ended_at", DateTime(timezone=True), nullable=True, index=True, comment="录制结束时间(UTC,可选)"),
|
|
|
|
|
|
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="记录创建时间(UTC)"),
|
|
|
|
|
|
Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
|
|
|
|
|
|
Index("idx_screen_recordings_sim_screen_created", "simulation_id", "screen_type", "created_at"),
|
|
|
|
|
|
Index("idx_screen_recordings_sim_screen_time", "simulation_id", "screen_type", "recorded_started_at"),
|
|
|
|
|
|
comment="仿真过程屏幕录制文件元数据(显示大屏/车载屏等)",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
sys_role = Table(
|
|
|
|
|
|
"sys_role",
|
|
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("role_id", String(64), primary_key=True, comment="角色 ID"),
|
|
|
|
|
|
Column("role_name", String(64), nullable=False, unique=True, index=True, comment="角色名称(唯一)"),
|
|
|
|
|
|
Column("role_desc", String(255), nullable=True, comment="角色描述"),
|
|
|
|
|
|
Column("is_active", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否启用"),
|
|
|
|
|
|
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
|
|
|
|
|
|
Column("updated_at", DateTime(timezone=True), nullable=True, comment="更新时间(UTC)"),
|
|
|
|
|
|
Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
|
|
|
|
|
|
comment="系统角色",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
sys_permission = Table(
|
|
|
|
|
|
"sys_permission",
|
|
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("perm_code", String(128), primary_key=True, comment="权限编码(唯一)"),
|
|
|
|
|
|
Column("perm_name", String(128), nullable=False, index=True, comment="权限名称"),
|
|
|
|
|
|
Column("perm_group", String(64), nullable=True, index=True, comment="权限分组(可选)"),
|
|
|
|
|
|
Column("perm_desc", String(255), nullable=True, comment="权限描述"),
|
|
|
|
|
|
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
|
|
|
|
|
|
comment="系统功能权限",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
sys_role_permission = Table(
|
|
|
|
|
|
"sys_role_permission",
|
|
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("role_id", String(64), ForeignKey("sys_role.role_id", ondelete="CASCADE"), primary_key=True, comment="角色 ID"),
|
|
|
|
|
|
Column("perm_code", String(128), ForeignKey("sys_permission.perm_code", ondelete="CASCADE"), primary_key=True, comment="权限编码"),
|
|
|
|
|
|
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
|
|
|
|
|
|
Index("idx_sys_role_permission_role", "role_id"),
|
|
|
|
|
|
Index("idx_sys_role_permission_perm", "perm_code"),
|
|
|
|
|
|
comment="角色功能权限关联表",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
sys_user = Table(
|
|
|
|
|
|
"sys_user",
|
|
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("user_id", String(64), primary_key=True, comment="用户 ID"),
|
|
|
|
|
|
Column("username", String(64), nullable=False, unique=True, index=True, comment="登录名(唯一)"),
|
|
|
|
|
|
Column("display_name", String(64), nullable=True, index=True, comment="显示名称"),
|
|
|
|
|
|
Column("password_hash", String(255), nullable=False, comment="密码哈希"),
|
|
|
|
|
|
Column("role_id", String(64), ForeignKey("sys_role.role_id"), nullable=False, index=True, comment="所属角色 ID"),
|
|
|
|
|
|
Column("is_active", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否启用"),
|
|
|
|
|
|
Column("last_login_at", DateTime(timezone=True), nullable=True, index=True, comment="最近登录时间(UTC)"),
|
|
|
|
|
|
Column("created_at", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="创建时间(UTC)"),
|
|
|
|
|
|
Column("updated_at", DateTime(timezone=True), nullable=True, comment="更新时间(UTC)"),
|
|
|
|
|
|
Column("extra", JSONB, nullable=True, comment="扩展信息(JSONB)"),
|
|
|
|
|
|
comment="系统用户(含所属角色)",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
sys_logs = Table(
|
|
|
|
|
|
"sys_logs",
|
|
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("log_id", BigInteger, primary_key=True, comment="日志 ID"),
|
|
|
|
|
|
Column("ts", DateTime(timezone=True), nullable=False, server_default=text("now()"), index=True, comment="操作时间(UTC)"),
|
|
|
|
|
|
Column("user_id", String(64), nullable=True, index=True, comment="用户 ID(可为空,如匿名)"),
|
|
|
|
|
|
Column("username", String(64), nullable=True, index=True, comment="登录名快照(可选)"),
|
|
|
|
|
|
Column("role_id", String(64), nullable=True, index=True, comment="角色 ID 快照(可选)"),
|
|
|
|
|
|
Column("action", String(128), nullable=False, index=True, comment="操作动作(如 login/start_simulation)"),
|
|
|
|
|
|
Column("resource", String(255), nullable=True, index=True, comment="资源标识(如 URL/对象 ID)"),
|
|
|
|
|
|
Column("success", Boolean, nullable=False, server_default=text("TRUE"), index=True, comment="是否成功"),
|
|
|
|
|
|
Column("ip", String(64), nullable=True, comment="客户端 IP(可选)"),
|
|
|
|
|
|
Column("user_agent", String(512), nullable=True, comment="User-Agent(可选)"),
|
|
|
|
|
|
Column("detail", JSONB, nullable=True, comment="操作明细(JSONB,可选)"),
|
|
|
|
|
|
Index("idx_sys_logs_user_ts", "user_id", "ts"),
|
|
|
|
|
|
Index("idx_sys_logs_action_ts", "action", "ts"),
|
|
|
|
|
|
comment="系统操作日志",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server_metrics = Table(
|
|
|
|
|
|
"server_metrics",
|
|
|
|
|
|
Base.metadata,
|
|
|
|
|
|
Column("ts", DateTime(timezone=True), nullable=False, index=True, comment="采样时间(UTC)"),
|
|
|
|
|
|
Column("host_name", String(64), nullable=False, index=True, comment="主机名"),
|
|
|
|
|
|
Column("cpu_usage_percent", JSONB, nullable=False, comment="CPU 使用率(百分比,JSONB)"),
|
|
|
|
|
|
Column("memory_usage_bytes", JSONB, nullable=False, comment="内存使用情况(字节,JSONB)"),
|
|
|
|
|
|
Column("disk_usage_bytes", JSONB, nullable=True, comment="磁盘使用情况(字节,JSONB)"),
|
|
|
|
|
|
Index("idx_server_metrics_host_ts", "host_name", "ts"),
|
|
|
|
|
|
comment="服务器监控指标时序数据(TimescaleDB hypertable)",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def init_schema(engine) -> None:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""初始化数据库表结构与必要的兼容性变更。
|
|
|
|
|
|
|
|
|
|
|
|
该函数会:
|
|
|
|
|
|
- create_all:创建 Base.metadata 里声明的表
|
|
|
|
|
|
- 插入默认角色(若不存在)
|
|
|
|
|
|
- 对历史表做列/索引补齐(兼容升级)
|
|
|
|
|
|
"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(engine, AsyncEngine):
|
|
|
|
|
|
raise TypeError("engine must be AsyncEngine")
|
|
|
|
|
|
|
|
|
|
|
|
async with engine.begin() as conn:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_vehicle_signals_sim_ts"))
|
|
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_unity_frames_sim_vehicle_ts"))
|
|
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_screen_recordings_sim_screen_created"))
|
|
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_screen_recordings_sim_screen_time"))
|
|
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_sys_role_permission_role"))
|
|
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_sys_role_permission_perm"))
|
|
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_sys_logs_user_ts"))
|
|
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_sys_logs_action_ts"))
|
|
|
|
|
|
await conn.execute(text("DROP INDEX IF EXISTS idx_server_metrics_host_ts"))
|
2026-01-19 14:27:41 +08:00
|
|
|
|
await conn.run_sync(Base.metadata.create_all)
|
2026-01-25 22:34:33 +08:00
|
|
|
|
await conn.execute(
|
|
|
|
|
|
text(
|
|
|
|
|
|
"""
|
|
|
|
|
|
INSERT INTO sys_role (role_id, role_name, role_desc, is_active)
|
|
|
|
|
|
VALUES
|
|
|
|
|
|
('admin', '系统管理员', '系统管理员', TRUE),
|
|
|
|
|
|
('auditor', '审计员', '审计员', TRUE),
|
|
|
|
|
|
('teacher', '老师', '老师', TRUE),
|
|
|
|
|
|
('student', '学生', '学生', TRUE)
|
|
|
|
|
|
ON CONFLICT (role_id) DO NOTHING
|
|
|
|
|
|
"""
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
text(
|
|
|
|
|
|
"""
|
|
|
|
|
|
INSERT INTO sim_scenes (scene_id, scene_name, scene_desc, scene_config, is_active)
|
|
|
|
|
|
VALUES
|
|
|
|
|
|
('scene_01', '城市道路', '默认场景 1', '{}'::json, TRUE),
|
|
|
|
|
|
('scene_02', '高速公路', '默认场景 2', '{}'::json, TRUE),
|
|
|
|
|
|
('scene_03', '学校道路', '默认场景 3', '{}'::json, TRUE),
|
|
|
|
|
|
('scene_04', '场地训练', '默认场景 4', '{}'::json, TRUE),
|
|
|
|
|
|
('scene_05', '综合测试', '默认场景 5', '{}'::json, TRUE)
|
|
|
|
|
|
ON CONFLICT (scene_id) DO NOTHING
|
|
|
|
|
|
"""
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-01-19 14:27:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def init_timescaledb(engine) -> None:
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"""初始化 TimescaleDB 扩展与 hypertable/索引(若启用)。"""
|
2026-01-19 14:27:41 +08:00
|
|
|
|
async with engine.begin() as conn:
|
|
|
|
|
|
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS timescaledb"))
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
text(
|
|
|
|
|
|
"SELECT create_hypertable('sim_vehicle_signals', 'ts', if_not_exists => TRUE)"
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
text(
|
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_vehicle_signals_sim_ts_desc ON sim_vehicle_signals (simulation_id, ts DESC)"
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
text(
|
|
|
|
|
|
"SELECT create_hypertable('server_metrics', 'ts', if_not_exists => TRUE)"
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
text(
|
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_server_metrics_host_ts_desc ON server_metrics (host_name, ts DESC)"
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
text(
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"SELECT create_hypertable('sim_unity_frames', 'ts', if_not_exists => TRUE)"
|
2026-01-19 14:27:41 +08:00
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.execute(
|
|
|
|
|
|
text(
|
2026-01-25 22:34:33 +08:00
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_unity_frames_sim_vehicle_ts_desc ON sim_unity_frames (simulation_id, vehicle_id, ts DESC)"
|
2026-01-19 14:27:41 +08:00
|
|
|
|
)
|
|
|
|
|
|
)
|