1. 页面概览

1.1 是什么

SQL 工作台是 LightFoundry 提供的一块「对象名直查的受限只读 SQL」查询界面(V5 Stage 1 B1-4 交付)。用户可以用业务对象名(api_name / object_ 前缀 / 物理表名)直接编写 SELECT 语句,由后端将其翻译为真实的物理 SQL 再执行,从而:

1.2 核心价值

维度说明
低门槛取数面向会写 SQL 的用户,用对象名取数,无需接触物理表
安全兜底后端强制只读、白名单、RLS/CLS 注入、LIMIT 钳制,前端不可绕过
可解释翻译预览清晰回显「对象→物理表、属性→物理列、字面量→?」的映射
编辑态可见若数据存在「编辑态叠加」(草稿修改未合入),查询结果会绿色高亮叠加值并提示

1.3 一句话总结

在 Foundry 里「用业务对象名写只读 SQL」,翻译、执行、导出 CSV,全程由后端做安全兜底,前端负责展示翻译元信息与结果。

2. 访问入口

2.1 路由与菜单

2.2 认证与权限

2.3 端口与 API 前缀

3. 界面布局

页面纵向分为 6 个板块(从上到下):

┌──────────────────────────────────────────────────────────────┐
│ ① 页头:标题「SQL 工作台」+ 一句话说明                         │
│ ② 操作结果提示条(alert,成功后绿色 / 失败后红色,可关闭)       │
│ ③ 查询编辑卡片                                                │
│    ├─ 对象下拉(点击注入模板)        ── LIMIT 输入框(可空)  │
│    ├─ Ontology SQL 多行编辑框(textarea)                      │
│    └─ [▶ 运行查询] [翻译预览] [导出 CSV] 三个按钮              │
│ ④ Tab 切换(有结果或翻译后才出现):[查询结果] [翻译预览]      │
│ ⑤ 查询结果卡片(结果表格 + 元信息注记 + 编辑态叠加高亮)        │
│ ⑥ 翻译预览卡片(物理 SQL + 翻译元信息表 + 绑定参数)           │
└──────────────────────────────────────────────────────────────┘

各板块职责:

4. 交互元素详解

4.1 对象下拉(点击注入模板)

属性
控件<select> 下拉框
数据来源GET /api/v1/ontology/objects?status=merged(页面加载时 onMounted(fetchObjects) 拉取)
加载态下拉置灰并显示「加载对象中...」,由 loadingObjects 控制
选项格式显示名(N 属性)value 为对象 api_name
空态无对象时显示占位「选择对象注入模板」

交互效果:选中任意对象后,编辑器被覆盖注入为模板 SQL:

SELECT *
FROM {api_name}
LIMIT 100

注入后下拉框自身 value 立即重置为空(e.target.value = ''),避免重复选中同一项不触发 change 事件。

注意:注入是覆盖而不是追加。若编辑器里已有 SQL,选中对象会被整体替换。若对象列表加载失败,页面不阻断,仍可手写 SQL(仅控制台输出 console.warn)。

4.2 LIMIT 输入框(可空)

属性
类型type="number"min=1,绑定 v-model.number
默认空(limitInput = null
前端处理空值时传 0 给后端(limitInput.value || 0
后端处理传 0 或未写 LIMIT 时,后端补默认 500;SQL 自带 LIMIT 超 5000 时钳制到 5000

4.3 Ontology SQL 编辑框

属性
控件<textarea rows=6>,等宽字体,spellcheck=false
写法FROM 后写对象名(api_name / object_ 前缀 / 物理表名);SELECT 写属性名(映射为物理列)
字符串自动参数化为 ?(绑定参数,防注入)
示例SELECT order_id, amount FROM order WHERE amount > 100 ORDER BY amount DESC LIMIT 50
限制单条只读 SELECT;不支持 linkName.prop 关联属性写法(需显式 JOIN 目标对象后用 别名.属性

编辑器占位提示里给出的经典示例:SELECT name, amount FROM orders LIMIT 10

4.4 三个操作按钮

按钮触发函数触发条件效果
▶ 运行查询handleRun编辑器非空且非 busyexecuteOntologySQL,成功切到「查询结果」Tab 并弹绿色提示(行数与生效 LIMIT),失败弹红色提示
翻译预览handleTranslate编辑器非空且非 busytranslateOntologySQL,切到「翻译预览」Tab 展示物理 SQL 与元信息
导出 CSVexportCSVresult.rows.length > 0纯前端把当前结果表格导出为 CSV 文件(带 BOM,Excel 可识别 UTF-8),无后端调用

4.5 Tab 切换

4.6 查询结果卡片

4.7 翻译预览卡片

区块内容
物理 SQL<pre> 深色块展示 physical_sql(参数化,字面量以 ? 占位)
翻译元信息表涉及对象 / 物理表 / 查询属性列 / 生效 LIMIT(含 limit_mode 说明)/ RLS / CLS / 编辑态叠加注记
绑定参数args JSON 数组(按 ? 顺序),有参数时才显示

limit_mode 取值(前端 limitModeLabel 映射):

含义
defaulted未写 LIMIT,后端补默认(500)
clamped超上限,已钳制到 5000
user用户明确指定

5. 后端关联

5.1 依赖的 API 客户端

action/web/src/api/queryApi.js(独立 axios 实例):

5.2 调用的后端端点

方法路径用途请求体超时
GET/ontology/objects对象列表(页面加载拉对象下拉)?status=merged60s
POST/ontology/sql/translate翻译预览(不执行){"sql": "..."}60s
POST/ontology/sql/execute翻译并执行受限只读 SQL{"sql": "...", "limit": 0}120s

5.3 响应结构

translate 成功{code:0, data:{...}}):

{
  "code": 0,
  "data": {
    "physical_sql": "SELECT \"order_id\", \"amount\" FROM \"orders\" WHERE \"amount\" > ? ORDER BY \"amount\" DESC LIMIT 50",
    "args": ["100"],
    "meta": {
      "objects": ["order"],
      "base_tables": ["orders"],
      "columns": ["order_id", "amount"],
      "limit_applied": 50,
      "limit_mode": "user",
      "rls_injected": true,
      "cls_injected": false,
      "security_note": "",
      "overlay_note": ""
    }
  }
}

execute 成功{code:0, data:{...}}):

{
  "code": 0,
  "data": {
    "columns": ["order_id", "amount"],
    "rows": [["O-1001", "259.00"]],
    "edited_fields": ["amount"],
    "row_count": 1,
    "elapsed_ms": 12,
    "meta": { "limit_applied": 50, "limit_mode": "user", "rls_injected": true, "cls_injected": false }
  }
}

失败(统一错误结构):{ "code": "<错误码>", "error": "<错误详情>" },HTTP 状态码由后端 ierr 错误映射(如 400/403/500)。

5.4 关联的后端模块与服务

模块位置职责
Ontology SQL 服务action/products/foundry/query/OntologySQLService.Translate/Execute,SQL 解析、白名单映射、安全注入、LIMIT 钳制
REST 入口action/products/foundry/query/rest.go两个 POST 端点,RegisterRoutes 挂载到鉴权路由组
语义查询层action/products/foundry/semantic/属性级安全(RLS/CLS 注入执行)、编辑态叠加(edit_overlay.goontology_edits 表)
宏解析action/products/foundry/query/translate.goV5 Stage 6 新增 MacroResolver 钩子,Execute 入口翻译前展开宏(SQL 工作台与 Notebook sql 单元格两处生效)

5.5 后端强制安全口径

规则说明
只读 SELECTDML/DDL 黑名单,非 SELECT 一律拒绝
单语句不允许 ; 分隔多语句
标识符白名单对象名/属性名须经本体映射,物理列名白名单
字符串参数化字面量转绑定参数 ?,杜绝拼接注入
RLS 注入基于当前 user_idLIMIT 之前 注入行级守卫(防「先分页后过滤」越权)
CLS 注入基于密级/列授权隐藏列(NULL AS col),属性级安全合并 Marking 隐藏
LIMIT 钳制未写补 500,超上限钳制到 5000

6. 核心流程详解

6.1 一次完整查询的主流程

页面加载
  └─ onMounted(fetchObjects)
       └─ GET /ontology/objects?status=merged → 填充对象下拉(失败仅 console.warn,不阻断)

用户操作
  1. 可选:选择对象下拉 → 注入模板 SQL `SELECT * FROM {name} LIMIT 100`
  2. 编辑 SQL(可改 LIMIT 输入框)
  3. 点击「▶ 运行查询」
       ├─ busy=true,按钮禁用,文案「执行中...」
       ├─ POST /ontology/sql/execute {sql, limit}
       │    ├─ 后端:校验只读/单语句/白名单 → 翻译 → RLS/CLS 注入 → LIMIT 钳制 → 执行 → 叠加编辑态
       │    └─ 返回 columns/rows/edited_fields/elapsed_ms/meta
       ├─ 前端:计算 editedColumns 下标集合,拼 metaNote,切「查询结果」Tab
       └─ busy=false,alert 绿色「查询完成:N 行(LIMIT X)」

6.2 翻译预览流程

点击「翻译预览」
  ├─ POST /ontology/sql/translate {sql}(不执行,纯翻译回显)
  └─ 切「翻译预览」Tab:物理 SQL / 元信息表 / 绑定参数

翻译与执行共用同一套翻译逻辑,因此「先翻译、后执行」可预判结果是否合规(如 RLS 是否注入、LIMIT 是否被钳制)。

6.3 编辑态叠加(Edited Fields)机制

6.4 CSV 导出流程(纯前端)

点击「导出 CSV」(需已有结果)
  ├─ 取当前 result.columns + result.rows(仅当前结果页,不做后端分页)
  ├─ 每个单元格转字符串;含逗号/引号/换行时用双引号包裹并转义("")
  ├─ 首行 BOM \uFEFF + 表头 + 数据行,\n 连接
  └─ Blob(text/csv;charset=utf-8) → URL.createObjectURL → <a download> 触发下载
     文件名:query_result_YYYY-MM-DD-HH-mm-ss.csv

7. 权限与安全

7.1 认证

7.2 数据级安全(查询阶段生效)

7.3 写操作防护

8. 常见问题与排错

8.1 点击「运行查询」报错,无任何结果

现象:alert 红色「查询失败:...」,结果区空。

排查步骤

  1. 先点「翻译预览」,若翻译也失败,说明 SQL 语法或对象名有问题——查看报错 error 字段(如 invalid SQLobject not found: xxx)。
  2. 若翻译成功但执行失败:确认对象状态是否 merged(草稿对象不可查询),或物理表/列是否存在。
  3. 确认后端 18081 已启动:浏览器开发者工具 Network 面板看请求是否 502/404。
  4. 查看 Foundry 后端日志定位服务层错误(query 包与 semantic 包)。

8.2 结果被截断,行数不对

现象row_count 正好是 500 或 5000。

原因:LIMIT 钳制——未写 LIMIT 补默认 500,超上限钳到 5000。这是安全设计,不是 bug。

处理:在 SQL 中显式写 LIMIT N(≤5000),或观察翻译预览「生效 LIMIT(limit_mode)」字段确认实际生效值。

8.3 某些列显示为 NULL,或整列消失

现象:翻译元信息显示 CLS 已注入,列值全为 NULL

原因:当前用户无该列权限(CLS 列级安全),或该列被 Markings 密级隐藏。

处理

8.4 查询结果里有绿色高亮列,数值与库里不一致

现象:某列绿色高亮,标题区有「编辑态叠加」tag。

原因:该对象存在未合入的草稿编辑(编辑态叠加层),查询结果展示的是叠加后的值。

处理:这是设计行为。若希望看到物理库原始值,需等待编辑态合入/回滚(见 Foundry 本体工作台版本状态机)。

8.5 导出 CSV 用 Excel 打开中文乱码

原因:CSV 已带 UTF-8 BOM,正常情况下 Excel 可识别;若仍乱码,多为编辑器/其它程序未按 UTF-8 读取。

处理:用 Excel「数据→自文本/CSV」导入并选择 UTF-8;或先用记事本另存为带 BOM 的 UTF-8。

8.6 想用 link 关联属性(linkName.prop)取数

现象:写 linkName.prop 报不支持。

原因:本版不支持 link 属性隐式解析。

处理:显式 JOIN 目标对象后用 别名.属性,例如:

SELECT o.amount, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
LIMIT 100

8.7 查询执行超过 60 秒被中断

现象:请求在 60 秒左右失败。

说明:execute 端点超时放宽到 120 秒;若仍超时,多为数据量大或物理表缺索引。

处理:缩小结果集(LIMIT、加 WHERE 过滤条件);检查物理表索引;必要时在数据管道层做预聚合。

9. 已知缺陷与边界

说明
link 关联属性linkName.prop 写法本版不支持,需显式 JOIN(见 8.6)
编辑态叠加范围叠加仅作用于返回行(分页/排序后),超大结果集下的叠加展示以后端为准
宏解析依赖 Stage 6V5 Stage 6 起 Execute 入口会经 MacroResolver 展开宏(宏表优先、未命中原样、迭代深度 ≤5);若配置了宏但未生效,检查 fcr_repos/fcr_files 宏表内容

注:对象下拉失败静默已于 2026-09-06 修复(失败改为页面级 alert-error 提示,并说明仍可手写 SQL、「对象注入模板」暂不可用)。

注:CSV 仅导出当前已加载行且无提示的问题已于 2026-09-06 修复(导出前明确提示「仅当前已加载 N 行」;如需全量请调大 LIMIT 或按范围拆分查询)。

10.2 后端相关

10.3 项目文档

10.4 相邻页面