sql5 4.1.0

SQLite compatible database with CJK FTS5 full-text search and vector similarity
Documentation
# 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 相容性

| SQLite 功能 | sql5 v1.17 狀態 |
|-----------|---------------|
| TRIGGERs | 🔧 基本框架完成 (v1.16-1.17),多項限制待解 |
| ATTACH | ❌ 待支援 |
| VACUUM | ❌ 待支援 |