Schema Attribute 使用與 SQL 產生器設計
1. 文件目的
本文件整理第二階段 MySQL TaskStore PoC 中,Schema Attribute 的使用方式,以及後續 SQL Generator 要如何從 C# Class 產出 MySQL 5.6.2 相容 DDL。
目前程式 repo 已完成第一版:
DbTableAttributeDbColumnAttributeDbIndexAttributeSchemaDefinitionTableDefinitionColumnDefinitionIndexDefinitionSchemaDefinitionParser
本文件是「下一步 SQL 產生器與自動建表執行器」的設計依據,不代表已經同意實際連線 MySQL、執行 DDL、安裝套件、修改啟動方式或新增 WebApi / ServiceHost。
2. 目前已完成與尚未完成
| 區塊 | 狀態 | 說明 |
|---|---|---|
| Schema Attribute | 已完成第一版 | 可在 C# Class / Property 上描述 table、column、index |
| Schema Parser | 已完成第一版 | 可將 Attribute 解析成中立 SchemaDefinition |
| SQL Generator | 尚未完成 | 尚未把 SchemaDefinition 轉成 MySQL DDL |
| Schema Initializer | 尚未完成 | 尚未連線 DB,也尚未檢查 information_schema |
| TaskStore | 尚未完成 | 尚未把任務、節點、命令、錯誤寫入 MySQL |
3. 使用邊界
3.1 Schema Attribute 負責什麼
Schema Attribute 只負責描述資料結構意圖:
- Table 名稱與描述。
- Column 名稱、MySQL 型別、主鍵、必填與描述。
- Index 名稱、欄位與唯一性。
Schema Attribute 不負責:
- 連線 MySQL。
- 建立 database。
- 執行 SQL。
- 判斷 schema 是否需要升級。
- 做資料搬移。
- 管理查詢或交易。
3.2 SQL Generator 負責什麼
SQL Generator 只負責把 SchemaDefinition 轉成 SQL 字串:
CREATE TABLE IF NOT EXISTSALTER TABLE ADD COLUMNALTER TABLE ADD INDEXALTER TABLE ADD UNIQUE INDEX
SQL Generator 不直接執行 SQL,也不持有連線字串。
3.3 Schema Initializer 負責什麼
Schema Initializer 是後續才會建立的執行器,負責:
- 讀取已註冊的 schema class。
- 呼叫
SchemaDefinitionParser取得SchemaDefinition。 - 讀取 MySQL
information_schema。 - 比對既有 table / column / index。
- 呼叫 SQL Generator 產出需要執行的 SQL。
- 依設定決定 Dry Run 或實際執行。
- 將結果寫入 Log。
4. C# Class 使用方式
4.1 Table 與 Index
[DbTable("task_executions", Description = "任務執行紀錄")]
[DbIndex("idx_task_executions_status", "status")]
[DbIndex("idx_task_executions_started_at", "started_at")]
public class TaskExecutionRecord
{
}
規則:
- 每個 schema class 必須有一個
DbTableAttribute。 - Table name 採用 snake_case。
- Index name 建議包含 table name,避免跨表重名難以追蹤。
- 第一版不在 Attribute 上設定 Engine / Charset,由 SQL Generator options 統一決定。
4.2 Column
[DbColumn("task_id", "varchar(64)", IsPrimaryKey = true, IsRequired = true, Description = "任務識別碼")]
public string TaskId { get; set; }
[DbColumn("status", "varchar(32)", IsRequired = true, Description = "任務狀態")]
public string Status { get; set; }
[DbColumn("raw_context", "longtext", Description = "任務原始上下文 JSON 字串")]
public string RawContext { get; set; }
規則:
Name必填。Type必填,直接使用 MySQL 5.6.2 相容型別字串。IsPrimaryKey = true時,SQL Generator 應視為NOT NULL。- 第一版不支援
DefaultValueSql,避免預設值 SQL 寫法先擴散。 - 原始 JSON / request / response 建議用
text或longtext,不要使用 MySQL 5.6.2 不支援的json型別。
5. Schema 轉換流程
flowchart LR
A["C# Record Class"] --> B["DbTable / DbColumn / DbIndex"]
B --> C["SchemaDefinitionParser"]
C --> D["SchemaDefinition"]
D --> E["MySqlSchemaSqlGenerator"]
E --> F["DDL SQL 字串"]
F --> G["Schema Initializer 後續執行"]
第一版已完成 A 到 D。
本文件規劃下一步 D 到 F。
6. SQL Generator 建議介面
建議先建立純字串產生器,不連線 DB。
public interface ISchemaSqlGenerator
{
IReadOnlyList<string> GenerateCreateTableSql(SchemaDefinition schema);
IReadOnlyList<string> GenerateAddColumnSql(SchemaDefinition schema, SchemaDiff diff);
IReadOnlyList<string> GenerateAddIndexSql(SchemaDefinition schema, SchemaDiff diff);
}
若第一版尚未建立 SchemaDiff,可以先只做:
public interface ICreateTableSqlGenerator
{
string GenerateCreateTableSql(TableDefinition table);
}
建議第一批先實作 GenerateCreateTableSql,等可產出完整建表 SQL 後,再補 ALTER TABLE。
7. MySQL 5.6.2 DDL 規則
7.1 CREATE TABLE
產出範例:
CREATE TABLE IF NOT EXISTS `task_executions` (
`task_id` varchar(64) NOT NULL COMMENT '任務識別碼',
`workflow_id` varchar(64) NOT NULL COMMENT 'Workflow 識別碼',
`status` varchar(32) NOT NULL COMMENT '任務狀態',
`started_at` datetime NULL COMMENT '開始時間',
`ended_at` datetime NULL COMMENT '結束時間',
`raw_context` longtext NULL COMMENT '任務原始上下文 JSON 字串',
PRIMARY KEY (`task_id`),
INDEX `idx_task_executions_status` (`status`),
INDEX `idx_task_executions_started_at` (`started_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='任務執行紀錄';
7.2 ADD COLUMN
產出範例:
ALTER TABLE `task_executions`
ADD COLUMN `raw_context` longtext NULL COMMENT '任務原始上下文 JSON 字串';
7.3 ADD INDEX
產出範例:
ALTER TABLE `task_executions`
ADD INDEX `idx_task_executions_status` (`status`);
唯一索引產出範例:
ALTER TABLE `task_executions`
ADD UNIQUE INDEX `uk_task_executions_task_id` (`task_id`);
8. Identifier 與字串安全規則
SQL Generator 必須集中處理命名與字串 escaping。
| 類型 | 規則 |
|---|---|
| Table / Column / Index 名稱 | 只允許英數字與底線,建議 regex:^[A-Za-z][A-Za-z0-9_]*$ |
| Identifier 包裝 | 使用反引號,例如 ` task_executions ` |
| COMMENT 字串 | 單引號需轉成兩個單引號,例如 patient's → patient''s |
| Type 字串 | 第一版只允許白名單型別或安全格式,不接受任意 SQL 片段 |
不建議讓 Type 可放入完整 SQL,例如:
varchar(64) NOT NULL DEFAULT 'x'
原因是第一版的 NOT NULL、主鍵與描述已由 Attribute 欄位控制,若讓 Type 混入約束,後續 Generator 很難穩定判斷。
9. MySQL 型別白名單建議
第一版建議允許:
| 類型 | 範例 |
|---|---|
| 字串 | varchar(32)、varchar(64)、varchar(128)、varchar(255) |
| 長文字 | text、longtext |
| 整數 | int、bigint |
| 小數 | decimal(18,4) |
| 時間 | datetime |
| 布林 | tinyint(1) |
第一版不建議允許:
jsongenerated columnforeign keytriggerstored procedure- 任意
defaultSQL
10. 差異比對設計
後續 Schema Initializer 需要建立 SchemaDiff,但第一批 SQL Generator 可以先不做。
建議差異分類:
| 差異 | 第一版處理方式 |
|---|---|
| Table 不存在 | 產出 CREATE TABLE IF NOT EXISTS |
| Column 不存在 | 產出 ALTER TABLE ADD COLUMN |
| Index 不存在 | 產出 ALTER TABLE ADD INDEX |
| Column 型別不同 | 回報 mismatch,不產出修改 SQL |
| Column 必填不同 | 回報 mismatch,不產出修改 SQL |
| Column 描述不同 | 第一版只警告 |
| DB 多出欄位 | 忽略或警告,不刪除 |
| DB 多出索引 | 忽略或警告,不刪除 |
11. Log 與錯誤回報
SQL Generator 與 Initializer 的錯誤需可追蹤:
| 情境 | 建議錯誤 |
|---|---|
| Table name 無效 | SchemaDefinitionInvalid |
| Column name 無效 | SchemaDefinitionInvalid |
| Column type 不在白名單 | SchemaDefinitionInvalid |
| Primary key 缺失 | SchemaDefinitionInvalid |
| Index 指向不存在欄位 | SchemaDefinitionInvalid |
| DB 權限不足 | 後續 DB 錯誤碼 |
| schema mismatch | 後續 DB / Schema mismatch 錯誤碼 |
Log 至少需包含:
- Table name
- Column name
- Index name
- SQL 動作類型
- Dry Run 或實際執行
- 錯誤代碼
- 錯誤訊息
- 耗時
12. 測試規劃
12.1 SQL Generator 單元測試
| 測試項目 | 預期結果 |
|---|---|
| 單一 table + primary key | 產出 CREATE TABLE IF NOT EXISTS |
| 多欄位 + 多索引 | 產出完整欄位與 index |
| unique index | 產出 UNIQUE INDEX |
| description 含單引號 | COMMENT 正確 escape |
| 無效 table name | 回傳錯誤 |
| 無效 column type | 回傳錯誤 |
| index 指向不存在欄位 | 回傳錯誤 |
12.2 Initializer 驗證節點
| 驗證項目 | 預期結果 |
|---|---|
| Dry Run | 只輸出 SQL,不修改 DB |
| 空 database | 建立 TaskStore table |
| 重複啟動 | 不重複建立、不報錯 |
| 缺少欄位 | 補欄位 |
| 缺少索引 | 補索引 |
| 欄位型別不一致 | 停止或警告,不自動改型別 |
13. 建議實作順序
- 建立
MySqlSchemaSqlGeneratorOptions。 - 建立 identifier / comment / type 驗證 helper。
- 實作
GenerateCreateTableSql。 - 補 SQL Generator 單元測試。
- 實作
GenerateAddColumnSql。 - 實作
GenerateAddIndexSql。 - 設計
SchemaDiff。 - 設計
SchemaInitializerDry Run。 - 等使用者確認 MySQL 連線資訊後,再做實際連線驗證。
14. 與現有文件關係
| 文件 | 關係 |
|---|---|
| MySQL Schema 自動建表設計 | 上層設計,定義整體自動建表策略 |
| MySQL schema Class 初稿 | 定義 TaskStore 四張表的欄位與索引 |
| MySQL Schema Attribute 實作前確認 | 實作 Attribute 前的範圍確認 |
| 本文件 | Attribute 已完成後,銜接 SQL Generator / Initializer 的設計 |
15. 建議下一步
建議下一步先做:
SQL Generator 實作前確認
確認項目:
- SQL Generator 是否先只做
CREATE TABLE。 - MySQL type 白名單是否足夠。
- Identifier 命名規則是否接受。
- 第一版是否先做 Dry Run,不實際連線。
確認後再進入程式 repo 實作 MySqlSchemaSqlGenerator 第一版。