配套③:人類與 Agent 共用同一條 DuckDB 查詢管線。Run / Source Run / validate /
run_query的語意契約見 Prompt 與 Context 工程 §七。快取與 Source Run 操作見 使用者指南 §7–8。
系列位置
完整導讀見 系列導讀與產品定位。
| 順序 | 文章 | 本題 |
|---|---|---|
| 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 架構。