# SQL Generator 實作前確認

## 1. 文件目的

本文件用於整理進入程式 repo 實作 `MySqlSchemaSqlGenerator` 前的確認項目。

目前已完成：

- Schema Attribute 第一版。
- Schema Model 第一版。
- SchemaDefinitionParser 第一版。
- Schema Attribute 使用與 SQL 產生器設計文件。

本文件只確認「SQL 字串產生器」的第一批實作範圍，不代表已經同意實際連線 MySQL、執行 DDL、建立資料表、修改專案啟動方式或導入 WebApi / ServiceHost。

## 2. 建議結論

建議下一步先做：

> `MySqlSchemaSqlGenerator` 第一版，只產生 `CREATE TABLE IF NOT EXISTS` SQL 字串。

第一版先不做：

- 不連線 MySQL。
- 不執行 SQL。
- 不做 `ALTER TABLE`。
- 不做 `SchemaInitializer`。
- 不讀取 `information_schema`。
- 不安裝 MySQL 套件。
- 不改變 ConsoleHost / API 啟動方式。

理由：

- 目前 Attribute / Parser 已完成，下一個最小可驗證節點是從 `TableDefinition` 產出穩定 SQL。
- 先不接 DB 可以降低風險，測試只需驗證 SQL 字串與錯誤處理。
- MySQL 5.6.2 語法需要保守處理，先把 DDL 產生規則固定，再進入實際連線更穩。

## 3. 第一批允許範圍

| 類別 | 允許內容 |
|---|---|
| SQL Generator | `MySqlSchemaSqlGenerator` |
| Options | `MySqlSchemaSqlGeneratorOptions` |
| 輸入 | `TableDefinition` 或 `SchemaDefinition` |
| 輸出 | `CREATE TABLE IF NOT EXISTS` SQL 字串 |
| 驗證 | table / column / index 命名、type 白名單、comment escaping |
| 測試 | xUnit 單元測試 |
| 文件 | 更新實作紀錄與下一步 |

## 4. 第一批禁止範圍

| 類別 | 不做內容 | 原因 |
|---|---|---|
| DB 連線 | 不連線 MySQL | 尚未進入連線驗證節點 |
| DDL 執行 | 不執行 `CREATE TABLE` | 避免誤改資料庫 |
| Schema Initializer | 不做啟動時自動建表 | 需等 SQL Generator 驗收後 |
| ALTER TABLE | 不補欄位、不補索引 | 需先有 schema diff 設計 |
| 套件 | 不新增 MySQL 套件 | 純字串產生器不需要 DB driver |
| 啟動方式 | 不改 ConsoleHost / API 啟動流程 | 避免擴大範圍 |
| 資料模型 | 不新增 TaskStore record 欄位 | 先沿用既有 schema class 草案 |

## 5. 建議放置位置

目前 Schema Attribute 與 Parser 位於：

```text
src/HS.DeviceControl.Core/Schema
```

第一版 SQL Generator 建議先放在同一個 `Schema` 命名空間下，理由：

- 目前尚未建立 `Infrastructure` 或 DB 子模組。
- SQL Generator 第一版不連線 DB，只是 schema 字串產生器。
- 放在同一處可降低專案切分成本。

後續若進入 MySQL 連線、TaskStore、SchemaInitializer，才再評估搬到：

```text
src/HS.DeviceControl.Infrastructure
```

或新增 DB 專案。

## 6. 建議類別清單

| 類別 | 責任 |
|---|---|
| `MySqlSchemaSqlGenerator` | 將 `TableDefinition` 產成 MySQL `CREATE TABLE` SQL |
| `MySqlSchemaSqlGeneratorOptions` | 控制 Engine、Charset、是否使用反引號 |
| `MySqlSchemaTypeValidator` | 驗證 MySQL 5.6.2 型別白名單 |
| `MySqlIdentifierValidator` | 驗證 table / column / index 命名 |
| `MySqlSqlEscaper` | 處理 identifier 包裝與 comment escaping |

若第一批想再縮小，可先只建立：

- `MySqlSchemaSqlGenerator`
- `MySqlSchemaSqlGeneratorOptions`

其他 helper 可先做成 private method，但測試要覆蓋行為。

## 7. Options 建議

```csharp
public sealed class MySqlSchemaSqlGeneratorOptions
{
    public string Engine { get; set; } = "InnoDB";
    public string Charset { get; set; } = "utf8";
    public bool QuoteIdentifiers { get; set; } = true;
}
```

第一版不建議開放：

- Collation。
- Row format。
- Default value SQL。
- Table partition。
- Foreign key。

## 8. SQL 產出規則

### 8.1 Table

輸出格式：

```sql
CREATE TABLE IF NOT EXISTS `task_executions` (
  ...
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='任務執行紀錄';
```

規則：

- 一律使用 `CREATE TABLE IF NOT EXISTS`。
- Engine 預設 `InnoDB`。
- Charset 預設 `utf8`。
- Table description 轉成 `COMMENT='...'`。

### 8.2 Column

輸出格式：

```sql
`task_id` varchar(64) NOT NULL COMMENT '任務識別碼'
```

規則：

- `IsRequired = true` 時產出 `NOT NULL`。
- `IsPrimaryKey = true` 時即使 `IsRequired = false`，也要產出 `NOT NULL`。
- 非必填欄位產出 `NULL`。
- Description 轉成 `COMMENT '...'`。
- Type 字串只允許白名單，不接受任意 SQL 片段。

### 8.3 Primary Key

輸出格式：

```sql
PRIMARY KEY (`task_id`)
```

規則：

- 第一版至少要有一個 primary key。
- 多欄位 primary key 可支援，但 TaskStore 第一版建議先以單一 id 為主。

### 8.4 Index

輸出格式：

```sql
INDEX `idx_task_executions_status` (`status`)
```

唯一索引：

```sql
UNIQUE INDEX `uk_task_executions_task_id` (`task_id`)
```

規則：

- Index 指向的 column 必須存在。
- Index name 必須符合 identifier 規則。
- Index columns 需保留宣告順序。

## 9. Identifier 規則

建議第一版規則：

```text
^[A-Za-z][A-Za-z0-9_]*$
```

適用：

- Table name。
- Column name。
- Index name。

禁止：

- 空白。
- 破折號。
- 點號。
- 反引號。
- 分號。
- 中文 identifier。

說明：

欄位描述可以中文，但 DB identifier 第一版先固定英文與底線，方便跨工具、跨系統維護。

## 10. Type 白名單

第一版建議允許：

| 類型 | 允許格式 |
|---|---|
| 字串 | `varchar(n)`，n 建議 1 到 255 |
| 長文字 | `text`、`longtext` |
| 整數 | `int`、`bigint` |
| 小數 | `decimal(p,s)` |
| 時間 | `datetime` |
| 布林 | `tinyint(1)` |

第一版禁止：

- `json`
- `enum`
- `set`
- `blob`
- `timestamp`
- `default`
- `auto_increment`
- `foreign key`
- `generated column`
- 任意含分號的 type 字串

## 11. 錯誤處理

建議沿用既有錯誤碼：

```text
SchemaDefinitionInvalid
```

需能辨識：

| 錯誤情境 | 預期 |
|---|---|
| table name 空白 | 回傳錯誤，不產 SQL |
| table name 格式不合法 | 回傳錯誤，不產 SQL |
| column name 空白 | 回傳錯誤，不產 SQL |
| column type 空白 | 回傳錯誤，不產 SQL |
| column type 不在白名單 | 回傳錯誤，不產 SQL |
| primary key 缺失 | 回傳錯誤，不產 SQL |
| index 指向不存在欄位 | 回傳錯誤，不產 SQL |

若既有 Parser 已擋下部分錯誤，SQL Generator 仍需做防呆，避免未來被其他來源直接呼叫。

## 12. 測試清單

| 測試項目 | 預期結果 |
|---|---|
| 產出單一 table | SQL 包含 `CREATE TABLE IF NOT EXISTS` |
| 產出 primary key | SQL 包含 `PRIMARY KEY` |
| required column | SQL 包含 `NOT NULL` |
| nullable column | SQL 包含 `NULL` |
| 一般 index | SQL 包含 `INDEX` |
| unique index | SQL 包含 `UNIQUE INDEX` |
| comment 含單引號 | SQL 正確 escape |
| table name 不合法 | 回傳錯誤 |
| type 不合法 | 回傳錯誤 |
| index 指向不存在欄位 | 回傳錯誤 |
| MySQL 5.6.2 不支援型別 | 回傳錯誤 |

## 13. 驗收標準

完成 SQL Generator 第一版後，需達成：

- 不需要 MySQL 服務即可跑完測試。
- 不需要 MySQL 套件即可跑完測試。
- 能從 TaskStore schema class 產出穩定的 `CREATE TABLE` SQL。
- 錯誤情境可回傳標準錯誤，不吞 exception。
- 不改變現有 ConsoleHost 啟動方式。

## 14. 仍需使用者確認

進入程式 repo 前，建議確認：

| 確認項目 | 建議值 | 是否可先採用 |
|---|---|---|
| 第一版是否只做 `CREATE TABLE` | 是 | 可先採用 |
| 是否先不連線 MySQL | 是 | 可先採用 |
| 是否先不做 `ALTER TABLE` | 是 | 可先採用 |
| Engine | `InnoDB` | 可先採用 |
| Charset | `utf8` | 可先採用 |
| Identifier 是否只允許英文 / 數字 / 底線 | 是 | 可先採用 |
| Type 白名單是否採用本文件 | 是 | 可先採用 |

## 15. 建議下一步

建議下一步：

> 進行 `MySqlSchemaSqlGenerator 第一版實作前 commit/push 前確認`

確認後再切換到程式 repo 實作：

1. `MySqlSchemaSqlGeneratorOptions`
2. `MySqlSchemaSqlGenerator`
3. SQL escaping / validation helper
4. xUnit 測試

完成後再回文件 repo 更新進度與驗證紀錄。
