date_interval_exclusion 規則
date_interval_exclusion 讓日期期間在同一個分割(partition)內不會重疊:同一時間每個產品只有一個價格、每位員工只有一份合約、每個資源只有一個指派。期間從必填的起始日起算,到結束日(不含)為止,結束日可以是 null,代表沒有終點。分割由一或兩個基數為 one 的 link 決定。它是 no_overlap 的「只限日期、更嚴格」版本:每次新增都必須明確帶出結束日,沒有終點的期間是真正永不結束的期間,而且每一條寫入路徑——REST、bulk、匯入、IaC、Command、callback、trigger、agent 與 MCP——都在同一個資料表 guard 下受它約束。
Payload
{
"type": "date_interval_exclusion",
"name": "同一時間每個產品只有一個價格",
"start_column": "生效日",
"end_column": "失效日",
"scope_columns": ["產品"]
}| 欄位 | 契約 |
|---|---|
start_column | 本資料表上必須是 required 的儲存型 date 欄位。 |
end_column | 另一個不是 required 的儲存型 date 欄位:null 代表期間永不結束。 |
scope_columns | 本資料表上一或兩個基數為 one 的原生 link 欄位。只有每個 scope link 依所列順序指向相同目標的資料列才會互相競爭。 |
引用可用顯示名稱或內部 key。when 只能省略、為 null 或 [],id 必須符合 rule_<8–32 hex>,name 最長 128 個字元,未知 key 會被拒絕;request schema 把這些回報為 422 Value error, rules[<i>]: …。這條規則算進 10 條規則上限,不算進 unique 上限。GET 會把 start_column、end_column 與 scope_columns 以顯示名稱回傳。
設定錯誤是 400,detail 為單純字串(後面可能接一段列出候選欄位的提示):
detail | 原因 |
|---|---|
column '<name>' not found in '<table>' | 引用的欄位不存在 |
column '<name>' is type <type> — start_column requires one of: date | 起始欄位(結束欄位則是 end_column)不是 date 欄位 |
column '<name>' is type <type> — rules may only reference stored scalar columns (and link columns where documented) | 用 link、rollup、lookup 或 formula 欄位當作邊界 |
start and end columns must be distinct | start_column 與 end_column 是同一個欄位 |
date_interval_exclusion start column must be required | 起始欄位允許 null |
date_interval_exclusion end column must allow null | 結束欄位是 required |
'<name>' must be a link column of '<table>' | scope_columns 的項目不是本資料表的 link 欄位 |
date_interval_exclusion requires cardinality-one links | scope link 的基數是 many |
date_interval_exclusion scope columns must be distinct | 同一個 link 列了兩次 |
伺服器會強制什麼
每一個存活資料列,都是它 scope link 所屬分割中的一個期間 [start, end):
start是合法的YYYY-MM-DD日期,end是null或較晚的YYYY-MM-DD日期——結束日等於或早於起始日會被拒絕;- 每個 scope link 都必須剛好指向一個目標,所以沒有分割、或有兩個分割的資料列會被拒絕;
- 同一個分割內,沒有任何兩個存活資料列重疊。期間是左閉右開的,所以一個期間可以在另一個結束的當天開始;
null的結束日代表期間永不結束:它會與該分割內所有「在它的起始日之前尚未結束」的期間重疊。軟刪除的資料列不算。
被拒絕的寫入是不含任何值的 409:
{"detail": {"error": "date_interval_exclusion_overlap", "message": "Date period overlaps another period in this partition."}}error | message | 意義 |
|---|---|---|
date_interval_exclusion_invalid_period | Date period is invalid. | 建立時省略了 end key、結束日為空字串、結束日不晚於起始日,或 scope link 沒有剛好指向一個目標(為 null 或格式錯誤的起始日、欄位沒有預設值時缺漏的起始日,以及格式錯誤的結束日,會被欄位檢查以 400 拒絕,訊息依寫入者而異;若建立同時省略了 end key,則先得到這個 409) |
date_interval_exclusion_overlap | Date period overlaps another period in this partition. | 同一分割內另一個存活資料列涵蓋了這個期間的一部分 |
date_interval_exclusion_retry | Date period rules changed during this write; retry. | 寫入等待 guard 期間資料表的規則改變了;沒有寫入任何東西,請重送 |
任何 body 都不會指出衝突的資料列、它的日期或 scope 值;但拒絕訊息仍會讓呼叫者知道,該分割中有某筆存活資料列(可能是他們讀不到的)與這段期間重疊。
寫入資料列
建立資料列時必須帶出結束日,值是日期或 JSON null:省略這個 key 是 date_interval_exclusion_invalid_period,不是沒有終點;欄位預設值也不算,因為檢查針對的是呼叫者送來的內容。部分更新時,省略的起始、結束與 scope 欄位保留原本儲存的值,只有明確的 null 結束日才會把期間改為沒有終點。每次更新都會重新檢查完整的新期間與分割,包含只改動 scope link 的更新。
規則在每一次寫入之後檢查,而不是等到 commit。要換掉一個期間,請先關閉舊期間、再插入接續的新期間——在同一個原子 Command 或同一個 bulk 批次中依此順序進行;先插入接續的期間,只要舊期間仍涵蓋它的起始日就會被拒絕。帶有這條規則的資料表,每個寫入者在讀取或鎖定資料列之前,都會以排他方式取得該資料表的寫入 guard,所以對該表的寫入會序列化,兩個寫入者不可能並行放進重疊的期間。bulk 批次與匯入會逐列 flush,讓每一列都看得到它之前的資料列。
啟用規則
PUT 在儲存之前會檢查每一個既有的存活資料列:每列都必須是合法分割中的合法期間,而且任兩列不得重疊,否則整份清單以 409 date_interval_exclusion_invalid_period 或 409 date_interval_exclusion_overlap 被拒絕,什麼都不儲存。沒有儲存 end key 的資料列視為不合法。這項檢查在資料表寫入 guard 之下執行,所以也會和一般寫入者序列化。rules.preview 只驗證候選規則的格式,不會掃描既有資料列,所以請在發布前先自行稽核資料表。
相關欄位與資料表的保護
| 操作 | 回應 |
|---|---|
刪除 start_column、end_column 或 scope_columns 的 link,或變更起訖欄位的型別 | 409 衝突 {"type": "dependent_rule", …},指出該規則:請先刪除規則(link 欄位本身完全不能改型別:400 Only name/description can be updated on link columns (got: type)) |
| 把起始欄位改為可選,或把結束欄位改為必填 | 409 {"error": "date_interval_exclusion_dependency", "message": "Remove the date interval rule before changing this bound column requirement."} |
| Purge scope link 所指向的資料表 | Purge preview blocker {"code": "date_interval_exclusion_dependency", "table_id": "<source table>", "message": "remove the dependent date interval exclusion rule before purging its link target"};若資料表 purge 仍然執行到那裡,則回 400 並帶同一則訊息,tag purge 則回 409 purge_blocked 並列出該 blocker |
其他寫入路徑
- 還原與 revert。 還原垃圾桶中的資料列,或把資料列 revert 到較早版本,都和任何寫入一樣:會對照存活資料列檢查,期間被占用或不合法時,以相同的錯誤碼被拒絕。
- 審批。 被
require_approval規則攔下的寫入不會保留它的期間。Apply 時會對照存活資料列重新檢查,發生衝突就讓 staged change 以apply_failed結束。 - Bulk 與匯入。 同步 bulk 請求會整個回滾。非同步的 bulk 或匯入 ticket 會讓每一列各自在 savepoint 內 flush,所以被拒絕的期間是該列的 ticket 錯誤,其他列仍會寫入。
- Publish session。 暫存 chunk 時會對照存活資料列執行建議性檢查,而不是對照
replace_filter的結果:期間與「replace 會刪除的存活資料列」衝突的資料列,會在暫存時以400 invalid_rows被拒絕。請把這樣的資料列改放到空閒的區間,或先更動存活資料列。互相重疊的暫存列在暫存時不會彼此比對:兩筆都會通過暫存,commit 時才以rule_violation在較後那一列的seq與row_index失敗。commit 時會在 guard 之下再檢查一次。 - Command。 每個寫入 step 都會立即檢查。拒絕會讓整個 command 回滾,並把規則自己的
409body 原樣傳出,agent 工具會回報為custom_table_command_failed,並帶http_status: 409。若呼叫者並非管理每一張被引用的資料表,而執行的是 definer authority 的 command,得到的則是遮蔽過的command_failedbody。見寫入型 steps。 - IaC。 record 行會把起始日、結束日與每個基數為 one 的 link 在一次寫入中完成(見
record行);拒絕會以相同的錯誤碼作為IacLineError.code。rule行接受相同的 payload(IaC rule 行)。 - Agent。 資料表頁面會帶
Visible date interval exclusion policies JSON——最多 10 組{"start_column","end_column","scope_columns"},只有起始、結束與每個 scope 欄位都可見時才會出現。任何一部分被隱藏,整條政策就不會出現在頁面上,但規則仍然會被強制執行。協定教模型送出YYYY-MM-DD的起始日、較晚的日期或null的結束日,以及每個 scope link 一個被允許的目標,而且絕不推測或繞過衝突的資料列。
兩者怎麼選
no_overlap | date_interval_exclusion | |
|---|---|---|
| 邊界 | 數值、decimal、日期、日期時間、類字串,或單一 interval 儲存格 | 兩個 date 欄位,起始必填、結束可為 null |
| 沒有終點 | 缺少邊界的資料列會被豁免 | null 結束日是永不結束的期間;新增時省略結束日會被拒絕 |
| 分割 | scope_columns 的 link 加上可選的 date_column | 一或兩個基數為 one 的原生 link;每列對每個 link 都必須剛好有一個目標 |
| 最小間隔 | min_gap | 無:相鄰的期間可以相接 |