# sql5 v1.17 版本說明(已完成)
## 版本資訊
- **版本**:1.17
- **日期**:2026-05-04
- **名稱**:TRIGGER 觸發機制完成
## 現況
v1.16 已建立 TRIGGER 基礎設施(AST、Parser、Planner、Executor Stub、Catalog CRUD),
但 `fire_triggers()` 尚未實作 — 也就是 INSERT/UPDATE/DELETE 時 trigger 不會真的被觸發。
## 待實作功能
### 1. Catalog — 觸發器查詢 API
新增 `get_triggers(table, event)` 方法:
```rust
/// 取得指定資料表的觸發器(依事件類型過濾)
pub fn get_triggers(&self, table: &str, event: &TriggerEvent) -> Vec<&TriggerMeta> {
self.trigger_cache.values()
.filter(|t| t.table == table && match (&t.event, event) {
(TriggerEvent::Delete, TriggerEvent::Delete) => true,
(TriggerEvent::Insert, TriggerEvent::Insert) => true,
(TriggerEvent::Update(a), TriggerEvent::Update(b)) =>
a.as_ref().is_none() || b.as_ref().is_none() ||
a.as_ref() == b.as_ref(),
_ => false,
})
.collect()
}
```
### 2. Executor — fire_triggers()
```rust
fn fire_triggers(
&mut self,
table: &str,
event: TriggerEvent,
new_row: Option<&Row>,
old_row: Option<&Row>,
) -> Result<(), String> {
let triggers = self.catalog.get_triggers(table, &event);
for trigger in triggers {
// WHEN 條件檢查
if let Some(ref when_expr) = trigger.when {
let cols = new_row.map(|r| r.col_names.clone())
.or_else(|| old_row.map(|r| r.col_names.clone()))
.unwrap_or_default();
let row = new_row.or(old_row).ok_or("trigger requires a row")?;
if !eval_expr(when_expr, row, &cols)?.is_truthy() {
continue;
}
}
// 解析並執行 trigger.body
let mut parser = Parser::new(&trigger.body);
let stmt = parser.parse_statement()?;
self.execute_stmt(stmt)?;
}
Ok(())
}
```
### 3. exec_insert / exec_update / exec_delete — 串接觸發
```rust
// BEFORE INSERT — 在插入前行
self.fire_triggers(&table, TriggerEvent::Insert, Some(&row), None)?;
// ... 插入資料 ...
// AFTER INSERT — 在插入後行
self.fire_triggers(&table, TriggerEvent::Insert, Some(&row), None)?;
// UPDATE 的 BEFORE/AFTER 需要傳入 old_row 和 new_row
// DELETE 的 BEFORE/AFTER 需要傳入 old_row
```
### 4. TriggerMeta 結構調整
已完成。`TriggerMeta` 現在包含完整資訊:
```rust
pub struct TriggerMeta {
pub name: String,
pub table: String,
pub timing: TriggerTiming, // Before, After, InsteadOf
pub event: TriggerEvent, // Delete, Insert, Update(Option<Vec<String>>)
pub for_each_row: bool,
pub when: Option<String>, // WHEN 條件(尚未實作評估)
pub body: String,
}
```
### 5. 當前限制
- Trigger body 只支援**單一 SQL 語句**(不含 `BEGIN...END`)
- `INSTEAD OF` trigger 暫不支援(需要視圖支援)
- `NEW.col` / `OLD.col` 變數參照暫時不支援
- `WHEN` 子句目前儲存但**不會被評估**
- `UPDATE OF col1, col2` 的欄位過濾尚未實作(所有 UPDATE 都會觸發)
- Trigger 不會在資料庫重啟後持久化(重啟後消失)
- 巢狀 trigger 呼叫時有 `triggering` flag 防護,但觸發 body 內的 DML 不會再觸發
## 架構變化
```
src/catalog/catalog.rs — trigger_cache, create_trigger, drop_trigger, get_triggers()
src/catalog/meta.rs — TriggerMeta + TriggerTiming + TriggerEvent 定義
src/planner/executor.rs — fire_triggers(), execute_sql_body(), exec_* + trigger calls
```
## 測試計畫
- [x] `CREATE TRIGGER` 基本解析(v1.16 已完成)
- [x] `DROP TRIGGER`(v1.16 已完成)
- [x] BEFORE/AFTER INSERT 觸發
- [x] BEFORE/AFTER UPDATE 觸發
- [x] BEFORE/AFTER DELETE 觸發
- [ ] `WHEN` 子句條件過濾(儲存但未評估)
- [ ] `UPDATE OF col1, col2` 只在特定欄位變更時觸發
- [ ] Trigger 持久化(重啟後仍在)
## SQLite 相容性
| TRIGGERs | 🔧 基本框架完成 (v1.16-1.17),多項限制待解 |
| ATTACH | ❌ 待支援 |
| VACUUM | ❌ 待支援 |