配套③:人类与 Agent 共用同一条 DuckDB 查询管线。Run / Source Run / validate /
run_query的语意契约见 Prompt 与 Context 工程 §七。快取与 Source Run 操作见 使用者指南 §7–8。
目前实作补充(0.3.3)
统一查询入口以 DuckDB SQL 为输入方言,保留反引号相容。来源稳定 ID 与位置版本分离:可恢复的位置变更可同步目前 tab 引用,但不改历史 SQL。GUI 与 Agent snapshot reader 动态附目前来源对应;同来源不代表内容相同,快照衍生的新 tab 仍须重新执行。恢复不扩张原来源范围,也不保证跨磁碟或网路储存追踪。
系列位置
完整导读见 系列导读与产品定位。
| 顺序 | 文章 | 本题 |
|---|---|---|
| 0 | 系列导读与产品定位 | 系列导读与产品定位 |
| 1–2 | Agent 时代的数据分析工作流、可治理的 Agent Memory | 工作流、记忆 |
| 3–4 | Prompt 与 Context 工程、AI Agent 架构 | Context、Agent |
| 5 | 本文 | 查询层 |
一、背景:为什么需要统一查询层
使用者与 Agent 在 SQL 里引用的是逻辑表名(档名、快取分页名、MCP 物化表、ATTACH 后的 alias.schema.table),执行时由后端改写为 DuckDB 可读路径或 TEMP TABLE。统一查询层的目标很单纯:人类与 Agent 写同一种 SQL、走同一条管线、受同一套唯读与物化规则约束。具体而言:
- 编辑器与对话里的 SQL 语法一致;
- 权限与唯读规则一处实施;
- Agent 建立的快取与使用者手动 Run 的快取同一套血缘。
SQL-first 的方法论(为何口径与取数以可审阅 SQL 为主契约)见 Agent 时代的数据分析工作流 §三。本篇说明上述原则如何以统一查询层承载(逻辑表名、物化、血缘、Source Run)。
二、逻辑表名 vs 实际来源
使用者 / Agent 撰寫
SELECT … FROM "monthly_revenue"
│
▼
sqlglot 解析 + 改寫
│
┌────────┼────────┐
▼ ▼ ▼
本機檔案 TEMP 快取 ATTACH / MCP
表名必须双引号引用(档名含 .csv 等)。Excel 为 "file.xlsx"."SheetName"。外部库为 alias.schema.table 或 alias.table。
三、查询管线总览
典型路径:
- 解析 SQL(sqlglot AST)。
- 替换表名为路径或保留快取/ATTACH 名称。
- 唯读守卫(拒绝 DDL/DML)。
- DuckDB 执行。
- 可选:物化为 TEMP TABLE 并注册
TabCacheRegistry;或写入ResultManager供结果面板分页。
§七展开分析就绪层;§八展开快取与 Source Run;§十一展开 ResultManager 生命周期。
四、sqlglot 改写
字串替换无法安全处理引号、子查询与多表 JOIN。AST 遍历可:
- 辨识表名是否为已注册快取 → 跳过路径改写;
- 辨识 ATTACH
catalog/db前缀 → 交给 DuckDB; - 将本机档映射为
read_csv_auto/read_parquet等。
五、唯读安全
使用者 SQL 与 Agent SQL 皆不得透过查询管线修改原始档或外部库内容。CREATE TABLE 等仅能出现在「物化快取」的受控路径,而非使用者自由撰写的 DDL。
六、本机文件映射
工作区 data.json 登记的文件与 Excel 工作表,在解析阶段改写为 DuckDB 可读表达式。侧边栏 schema 与 show_tables 输出与此登记一致。
七、分析就绪层(工程定义)
在 系列导读 §一「分析师的一天」 的 Medallion 隐喻中,Lantide 的 Silver 对应工作区内的分析就绪/中间结果——本篇定义其工程实作,非企业级 ETL。
| 能力 | 实作 |
|---|---|
| 可引用的中间表 | 逻辑表名 + TabCacheRegistry |
| 可重跑 | Source Run DAG、拓扑排序 |
| 可审阅 SQL | source_sql、GET /cache/.../source、get_cache_source |
| 人类与 Agent 一致 | 同一条 DuckDB 管线(§十二) |
与 Julius 类「会话内 transform」的差异在于:中间结果是具名、可查、可 Source Run 的 artifact,而非沙箱内隐式变数(对照见 系列导读 §一 与 工作流 §三)。
但分析就绪层不是每次分析的必经路径。一次性、口径清楚的多表 JOIN 可以直接执行;只有当中间结果需要重用、审阅或重跑时,persist tab / Source Run 才是更好的选择。
八、快取表与 Source Run
8.1 单次 Run
持久分页执行查询(POST /query 或工具 run_sql_tab)时,结果物化为 DuckDB TEMP TABLE,并在 TabCacheRegistry 注册,名称通常与分页标题一致。其他 SQL 可直接:
SELECT category, SUM(amount)
FROM "clean_orders"
GROUP BY category;
快取存在目前工作区的 DuckDB 连线记忆体中;切换工作区或重启应用会清除 — 符合「分析中间结果」定位,非长期仓储。
Agent 快取(run_query 条件物化)亦注册为可查表,但语意上为代理分析用,不参与 Source Run DAG(见 Prompt 与 Context 工程(Query Execution Model))。
条件物化策略(run_query)
| 条件 | 物化 | reason_code |
|---|---|---|
显式 materialize=true |
是 | explicit_true |
单表探索(单依赖、LIMIT、无 JOIN/GROUP)且显式 false |
否 | exploratory_single_table |
| 对话 ledger 已有 ≥1 成功步 | 强制 | multi_step_conversation |
| SQL 引用既有快取表名 | 强制 | references_cached_table |
| JOIN 表 ≥2 或 CTE ≥3 | 强制(含首步) | complex_query |
| 预设 | 否 | default_ephemeral |
推断与 LLM 传入 materialize=false 冲突时采 auto_warn:仍物化、status=ok,并附 warnings。
这里的「条件物化」是 run_query 的数据重用策略,不等同于阻挡查询。前景 run_sql_tab 的阻挡型 complexity gate 目前只保留给过度复杂、难以审阅的形态(例如 3+ CTE);纯多实体表 JOIN 允许执行,最多回传非阻挡的 optimization_hint,提醒可视情况改用 persist tab。
source_sql 与查看 API
TabCacheRegistry 的 CachedTableState 可存 source_sql、result_id、source_kind(persist | agent)。agent 物化时写入 SQL;persist 可由分页内容懒载入。
GET /api_v1/cache/{tab_id}/source
解析顺序:registry → persist 分页 content → ResultManager → workspace ledger 扫描。回传 sql_source(registry | tab_content | result_manager | ledger | unavailable)。UI 右键 查看 SQL 不依赖 conversation_id。
8.2 依赖解析与 DAG
当 SQL 引用其他持久分页的快取名,系统解析依赖边,组成 DAG,并做循环侦测与拓扑排序。实作集中于 tab cache 的 dependency 模组:从 sqlglot 撷取 FROM 中引号包裹的持久表名,与已注册快取比对。
规则摘要:
- 依赖边 = 被引用的持久快取表名;
- 禁止自引用自己的快取名;
- 临时分页不进 Source Run。
8.3 Source Run 与 SSE
Source Run 对目标持久分页:先依 DAG 依序重算所有上游,再执行目标 SQL。透过 POST /source_run/stream 以 SSE 推送 plan、node_start、node_done、complete 等事件;前端显示流程图进度。失败时清理相关快取状态。
Agent 对应工具 source_run_sql_tab(UICommand + 后端适配),与人类按工具列 Source Run 同源。操作见 §8。
8.4 Deep lineage
Check Lineage 可不执行查询,仅建深度血缘图:Physical(文件)、Cached(上游分页快取)、Target(目前分页)、Unknown。用于理解数据从哪来,再决定是否 Source Run。
8.5 run_id 与 abort
进行中的互动查询与 Source Run 可带 run_id 登录;使用者 Abort 时对 DuckDB 连线 interrupt(),并以统一方式辨识取消错误,使 Tab/结果状态收敛。互动查询与 Source Run 中止语意对齐。
九、外部数据源:ATTACH、Cloud folder 与 ODBC staged
9.1 ATTACH 原生 DB(PostgreSQL / MySQL / SQLite)
PostgreSQL / MySQL / SQLite 透过 DuckDB ATTACH 挂载,跨源 JOIN 与本机档在同一 SQL 中完成。解析器对已 ATTACH 的 alias 不做「拉回本机路径」改写。连线生命周期由 Connection Manager 管理(试连、Connect/Disconnect、schema 列举)。
9.2 Cloud folder
S3/OSS/ADLS Gen2 等云端 prefix 以独立 alias 注册;引用格式与 Additional folder 相同("alias"."file.parquet")。传输层由 DuckDB httpfs/azure extension 承载;Connect 会 list prefix 并建索引(有硬上限)。Sidebar Select 100 rows 走 Probe 路径 POST /probe/describe,不经主库 /query。状态 degraded 表示可用但索引可能不完整。
9.3 SQL Server / Azure SQL(ODBC staged)
不 ATTACH。每条 upstream SQL 须单源(仅引用该 ODBC alias);系统将可下推的筛选/聚合转 T-SQL 在远端执行,结果物化为本机 cache,再与本地档、agent/persist 快取或已 ATTACH DB 做第二步 JOIN。单一 statement 透明跨源 JOIN 会被拒绝(SQLSERVER_CROSS_SOURCE_REQUIRES_MATERIALIZATION)。
大结果 preflight: 远端估计超过 500,000 列 时,validate_query/run_query 回传 warning_required + warning_id;须 ask_user 后以 large_remote_warning_id 重试。
9.4 Probe vs 主库执行
| Tier | 用途 | 典型入口 |
|---|---|---|
| Probe(次要 DuckDB) | metadata、validate_query、describe、Sidebar 预览 |
/probe/describe |
| 主 connector | 正式 /query、物化、Source Run |
run_query / run_sql_tab |
分离可避免 metadata 扫描与 heavy execute 互抢同一 in-process 连线。
9.5 Connections lifecycle
每笔绑定可 Connect/Disconnect/Remove/Edit。Disconnect 保留设定但 Sidebar 隐藏 alias。Remove、Edit Save 或变更 folder 路径若影响 SQL tab 引用,会先显示 impact 确认。状态:connected(DB ATTACH)、available(folder/cloud 已挂载)、degraded(cloud 索引不完整)、disconnected、error 等。
十、MCP 物化 Parquet
MCP 工具回传结果物化为工作区内 Parquet,以 mcp_alias__table 形式注册进查询层。Stale 时由 MCP 工具刷新,而非在 SQL 内隐式连外网。这是把 Bronze(远端/API 原始源)拉入工作区 Silver(本机可查表)的典型路径。
十一、ResultManager 生命周期
互动查询的结果面板绑定 query_result_id:支援分页浏览、排序、汇出。与 TEMP 快取表分工不同 — 快取服务下游 SQL 引用;ResultManager 服务人类阅读与汇出。Agent sql_tab_result 可读持久分页对应的快取结果。Run 路径可能物化进 Registry(供下游 FROM)或写入 ResultManager(供结果面板),依执行入口而定;对照 §三管线图的物化分支。
十二、人类与 Agent 共用管线
| 动作 | 人类 | Agent(典型工具) |
|---|---|---|
| 单分页执行 | Run | run_sql_tab |
| 上游链重算 | Source Run | source_run_sql_tab |
validate_query |
编辑器执行前 | validate_query(SQL Server 大结果可回 warning_required) |
| 预览列 | 结果面板 | run_query(≤200 行;大结果须 large_remote_warning_id) |
| 读快取 | SQL FROM "tab" |
同上 + sql_tab_result |
| 查看 SQL | 右键 查看 SQL / GET /cache/{tab_id}/source |
get_cache_source(与 API 同源,见 AI Agent 架构 §6.2) |
Prompt 层的 Query Execution Model(Prompt 与 Context 工程 §七)与工具矩阵(AI Agent 架构)必须与本层语意一致,否则模型会写出无法 Source Run 的 SQL 或混淆 agent 快取。
十三、错误设计
错误讯息指向可修正项(表名未加引号、依赖分页未执行、循环依赖、唯读违规)。查询失败时,Context 可把错误 + 触发 SQL 一并送给 Agent(见 §12.11)。
十四、工程决策总览
| 决策 | 取舍 |
|---|---|
| DuckDB in-process | 低延迟;快取随程序 |
| sqlglot | 安全改写 vs 正则 |
| TEMP TABLE 快取 | 速度 vs 持久化 |
| Source Run DAG | 正确顺序 vs 单次 Run 简单 |
| SSE 进度 | 可观测长链执行 |
| 多表 JOIN 不硬挡 | 实务分析可完成;cache 以 hint 引导 |
十五、结语
统一查询层让「分析师写的 SQL」与「Agent 写的 SQL」落在同一套物化与血缘规则上——是 SQL-first 与 分析就绪层 的工程落点。
方法论见 工作流 §三;Execute 与持久分页的产品故事见同篇 §四、§七;模型何时可呼叫 source_run_sql_tab,见 AI Agent 架构。