Files
dbtool-cli-v1/TUI_BACKEND_CONTRACT.md
Paperclip CTO d5f69462b0
Some checks failed
release-smoke / macos-13 / x86_64-apple-darwin (push) Has been cancelled
release-smoke / ubuntu-latest / x86_64-unknown-linux-gnu (push) Has been cancelled
release-smoke / windows-latest / x86_64-pc-windows-msvc (push) Has been cancelled
feat(usable): integrate current dbtool implementation snapshot
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-02 08:26:18 +00:00

9.7 KiB
Raw Blame History

TUI Backend Contract

日期2026-04-02
作者Senior Backend Engineer

1. 当前后端边界

  • crates/db-core:数据库无关的领域对象与基础校验。
  • crates/db-config:连接 profile、versioned profile store、active profile 语义、session-only secret 缓存、密码环境变量注入与脱敏摘要。
  • crates/db-driversPostgreSQL / MySQL / SQLite 驱动实现与差异收敛。
  • crates/db-appconnect / inspect / query / export 共享应用编排,以及 profile load / save / delete / activate / resolve 共享入口、结构化结果、统一错误对象,与 TUI worker 可复用的执行状态。
  • apps/cli参数解析、stdout/stderr 渲染、退出码。

2. 持久化边界

  • 当前 backend 新增了一条受控本地持久化边界:db_config::ConnectionProfileStore
  • store 仅持久化:
    • version
    • active_profile
    • 不含 secret 的 ConnectionProfile[]
  • 当前 store 不持久化:
    • 明文密码 / session secret
    • 查询历史
    • 导出历史
    • 后台任务状态
  • 当前写路径只有:
    • 调用方显式传入路径的 profile store JSON 文件
    • export 明确指定的输出文件
    • SQLite 目标数据库本身(由用户选择)
  • SessionSecretStore 只存在于当前进程内存;重启后不恢复。
  • 当前阶段仍不引入新的数据库、本地缓存库或后台 daemon 存储契约,也不引入 migration chain。

3. 共享请求对象

TUI worker 应直接构造共享 Rust 对象,不要拼接 CLI flags更不要解析 CLI 文本。

Connect

  • 输入:db_config::ConnectionProfile
  • 说明:
    • name 用于当前会话内的目标标识
    • target 复用 db_core::ConnectionTarget
    • password_env_var 只作为脱敏标签 / advanced fallback 保留
    • ConnectionTarget.password 可在运行态存在,但禁止进入 ConnectionProfileCatalog

Connection Management

  • profile storedb_config::ConnectionProfileStore
  • catalogdb_config::ConnectionProfileCatalog
  • session secret cachedb_config::SessionSecretStore
  • 保存请求:db_app::SaveProfileRequest
    • profile
    • set_active
    • session_secretPreserve | Set(String) | Clear
  • 共享入口:
    • db_app::load_profile_catalog
    • db_app::save_profile
    • db_app::delete_profile
    • db_app::activate_profile
    • db_app::resolve_profile
    • db_app::resolve_active_profile
    • db_app::connect_saved_profile
  • 约束:
    • catalog 内 profile 必须无 secrettarget.password 存在,视为运行态数据,只能通过 session secret 路径保留
    • active_profile 必须指向已存在 profile或为 null
    • 同名 profile 视为 upsert不引入第二份记录

Inspect

  • 输入:db_core::InspectRequest
  • 字段:
    • schema: Option<String>
    • table: Option<String>
  • 约束:
    • table.is_some() 时必须同时提供 schema

Query

  • 输入:db_core::QueryRequest
  • 字段:
    • sql
    • source
    • parameters
  • source 语义:
    • QuerySource::Inline
    • QuerySource::File

Export

  • 输入:
    • db_core::QueryRequest
    • db_core::ExportRequest
  • ExportRequest 字段:
    • format
    • output_path
    • overwrite
  • 约束:
    • 只允许导出返回结果集的查询
    • 不静默覆盖已有文件
    • 不为 TUI 增加新的导出元数据存储

4. 共享结构化结果

TUI 应优先直接调用 crates/db-app,不要解析 CLI 的人类可读文本。

Connect

  • 返回 ConnectResponse
  • 字段:
    • target.profile_name
    • target.driver
    • target.endpoint
    • target.password_env_var
    • status
  • 当前 status 固定为 connected

Connection Management

  • load_profile_catalog / save_profile / delete_profile / activate_profile 返回 ProfileCatalogResponse
  • 字段:
    • active_profile_name
    • profiles[]
      • target
      • is_active
      • secret_sourcenone | session | env_var
  • resolve_profile / resolve_active_profile 返回运行态 ConnectionProfile
    • 若 session secret 存在,优先注入该 secret
    • 否则再尝试 password_env_var
    • 解析阶段仍不把 secret 暴露到 summary / doc / 持久化内容

Inspect

  • 返回 InspectResponse
  • 字段:
    • target
    • scope.schema
    • scope.table
    • payload
  • payload.kind 为:
    • schemas
    • tables
    • columns
  • payload.kind=schemas 时,每个 schema item 额外包含:
    • name
    • availabilityready | restricted
    • note:可选;当 availability=restricted 时给出权限说明
  • payload.items 允许为空;空数组不是错误
  • PostgreSQL root inspect 现会保留 restricted schema 名称,并通过 item-level availability 暴露权限状态
  • 当显式 schema inspect 命中存在但当前连接无权访问的 PostgreSQL schema 时,返回 AppError.kind=inspect,不再把 restricted schema 误报为 empty

Query

  • 返回 QueryResponse
  • 字段:
    • target
    • source.kindinline / file
    • source.label
    • source.path
    • result.columns
    • result.rows
    • result.rows_affected
    • result.row_count
  • rows_affected != null 表示 command-style 执行成功但没有结果集

Export

  • 返回 ExportResponse
  • 字段:
    • target
    • source
    • export.format
    • export.output_path
    • export.overwrite
    • row_count

5. 错误模型

db-app::AppError.kind 当前分为:

  • validation
  • connection
  • authentication
  • inspect
  • query
  • export
  • config

这组分类是 TUI banner、QA 断言和未来 GUI 状态映射的当前稳定基础。

AppError.target 为可选字段:

  • 有目标上下文时返回脱敏后的 ConnectionSummary
  • 参数校验在目标尚未构成时可为 null

6. Worker / Channel 状态契约

crates/db-app 现提供:

  • AppOperationconnect | inspect | query | export | load_profiles | save_profile | delete_profile | activate_profile
  • OperationStaterunning | success | empty | error
  • AppEvent<T>worker / channel 推荐 envelope

推荐发送顺序:

  1. 先发 AppEvent::running(operation)
  2. 成功后发送 response.into_event()
  3. 失败时发送 error.into_event()

状态语义固定如下:

  • connect
    • running:正在测试连接
    • success:连接成功
    • error:连接失败
  • inspect
    • success:返回非空 schema / table / column 集合
    • empty:返回空集合,但请求本身成功
    • errorinspect 失败;包括 restricted schema 等显式 scope 无权访问场景
  • query
    • success:返回至少一行,或 rows_affected 非空
    • empty:返回零行且 rows_affected == null
    • error:执行失败
  • export
    • success:文件写入成功;row_count == 0 仍算成功
    • error:导出校验、查询或文件写入失败
  • load_profiles | save_profile | delete_profile | activate_profile
    • 当前先作为共享 operation 名称与 error.operation 保留,供连接管理 worker 接入
    • 若后续 channelize 这些动作,应继续沿用 running | success | error,不要另起一套状态词

示例:

use db_app::{AppEvent, AppOperation};

sender.send(AppEvent::<()>::running(AppOperation::Query))?;

match db_app::query(&profile, &request) {
    Ok(response) => sender.send(response.into_event())?,
    Err(error) => sender.send(error.into_event())?,
}

7. CLI 结构化输出

CLI 继续保留默认文本输出,同时新增:

  • --result-format text
  • --result-format json

JSON 返回 envelope

{
  "status": "success",
  "operation": "query",
  "state": "success",
  "data": {}
}
  • status 继续只区分 envelope 级成功/失败
  • state 对齐共享应用层状态:
    • connectsuccess
    • inspectsuccess | empty
    • querysuccess | empty
    • exportsuccess

错误返回 envelope

{
  "status": "error",
  "operation": "query",
  "state": "error",
  "error": {
    "operation": "query",
    "kind": "query",
    "message": "..."
  }
}

CLI JSON envelope 继续作为跨入口回归样本,但 TUI live path 不应以调用 CLI 二进制作为主集成方式。

8. Frontend 接入约束

  • TUI 直接依赖 crates/db-appdb-core,不解析 CLI stdout/stderr
  • UI 状态映射应优先使用 OperationState,不要自行发明另一套 success/empty/error 语义
  • UI 在 schema 根列表上应优先消费 item-level availability / note,不要把 restricted schema 再降级成 empty
  • ConnectionSummary.endpointpassword_env_var 仅用于展示脱敏上下文,不应用作 secret 来源
  • 连接管理 UI 应优先消费 ProfileCatalogResponse.secret_source 与 active profile 语义,不自行读取持久化文件
  • AppError.kind 必须原样进入状态区 / inspector避免把 validationconnectionquery 混成统一“失败”
  • 若未来引入异步 worker 池,仍以共享 response/error 对象作为唯一 UI 数据源

9. 当前回归边界

  • crates/db-app 回归需覆盖:
    • response -> OperationState 映射
    • AppEvent<T> / AppError 序列化
    • 空 inspect / 空 query / command query 语义
    • profile save/load/activate 的 secret 非持久化边界
  • apps/cli 回归继续覆盖:
    • --result-format json envelope
    • text/json 输出在相同 db-app 结果上的一致性

10. 演进规则

  • CLI 文本文案可小幅演进,但 db-app 结构化对象字段应保持兼容。
  • 新增数据库驱动时,优先扩展 db-driversdb-app,不要在 TUI/CLI 内复制数据库语义。
  • 若未来扩展 profile store schema例如新增 profile id、secret provider、连接历史必须先 bump version 并补迁移策略文档,再进入实现。