DynamoDB 中的 Expression Attribute Name 與 Value
DynamoDB expression 是模板:你寫佔位符,然後在兩個側邊 map 中
供應真實的屬性名稱與值。#name 是一個名稱佔位符;
:value 是一個值佔位符。把這兩個搞混,DynamoDB 就拒絕整個
呼叫。
DynamoDB 中的 #name 與 :value 有什麼差別?
#name 是屬性名稱的佔位符,透過 ExpressionAttributeNames 供應;:value 是屬性值的佔位符,透過 ExpressionAttributeValues 供應。用 #name 來閃避保留字、點或空格,用 :value 來表示每個字面量 — DynamoDB 從不把值內嵌進去。兩者不可互換;把它們互調會丟出一個 ValidationException。
#name透過ExpressionAttributeNames替換一個屬性名稱 — 每當 一個屬性與保留字衝突、或含有一個點/空格時就用它。:value透過ExpressionAttributeValues替換一個值 — DynamoDB 從不把字面量內嵌進 expression 文字,所以每個值都是一個佔位符。- 它們不可互換。 在該放
:的地方放一個#是一個ValidationException,而非一個安靜的無操作。
從 SQL 過來,你把兩者都內嵌 — WHERE status = 'published'。DynamoDB
兩者都不內嵌。那個切分,正是絆倒每個新手的東西。
為什麼存在兩個 map
在 SQL 中,查詢字串承載一切:欄位名稱、字面量、運算子。 DynamoDB 刻意把 expression 的 形狀 與它的 資料 分開。
值放進它們自己的 map,這樣 DynamoDB 才能為每個型別化(S、N、BOOL、…),
也讓解析器永遠不必猜一個字串在哪結束 — 沒有引號
或逸出可以弄錯。完整的型別標籤清單請見 DynamoDB 中的資料型別。
名稱出於另一個原因得到同樣的待遇:DynamoDB 有一長串 保留字,而任何匹配其中之一的屬性都不能在一個 expression 中以 光禿禿的名稱出現。佔位符徹底閃避了那個保留。
保留字陷阱
這裡是一個 CMS 文章表 — partition key BLOG#<blog>、sort key
ARTICLE#<slug> — 它的屬性讀起來很自然,卻恰好與
保留字衝突:
| 屬性 | 保留? | 它存什麼 |
|---|---|---|
status | 是 | draft / published |
name | 是 | 作者顯示名稱 |
size | 是 | 渲染後位元組長度 |
ttl | 是 | 封存到期(epoch) |
slug | 否 | URL slug |
status、name、size 與 ttl 全都在 AWS 的保留字清單上,所以這個
過濾在第一個字就失敗:
FilterExpression status = :s
DynamoDB 回傳一個 ValidationException — 「Attribute name is a reserved
keyword; reserved keyword: status」。修正是一個名稱佔位符,絕非
改名那個屬性:
FilterExpression #status = :s
ExpressionAttributeNames { "#status": "status" }
ExpressionAttributeValues { ":s": { "S": "published" } }
陷阱:slug 不是 保留的,所以一個你對 slug 測過的查詢可行,
於是你假設下一個也會。然後 status 弄壞它。完整清單
會變動,所以別去背它 — 對每個名稱用佔位符,你就永遠不會被
咬到。
永遠對映每個值
值是不可妥協的:沒有內嵌字面量的語法。即使是一個普通的 數字也得到一個佔位符。這個更新把一篇文章標記為已發布、戳上它的 大小,並設定一個 30 天的封存 TTL:
UpdateExpression: SET #status = :s, #size = :sz, #ttl = :exp
ExpressionAttributeNames: { "#status": "status", "#size": "size", "#ttl": "ttl" }
ExpressionAttributeValues: {
":s": { "S": "published" },
":sz": { "N": "20480" },
":exp": { "N": "1719792000" }
}注意 :sz 與 :exp 以 N 字串送出 — DynamoDB 的數字型別是以
字串做線上編碼。值 map 也是你跨子句重用一個值的
地方:定義 :s 一次,在一個 ConditionExpression 與一個
FilterExpression 中都參照它。
手寫這兩個 map 正是打字錯誤藏身之處。 Expression Builder 一併產生 expression 字串與兩個 map,並填好型別標籤,這樣佔位符就不會彼此 失去同步。
巢狀與彆扭路徑的名稱
# 佔位符不只是閃避保留字。document-path 語法用
點與方括號,所以一個字面上含有一個點的屬性 — 例如一個中繼資料
key og.title — 沒有佔位符就無法定址:
ProjectionExpression #og
ExpressionAttributeNames { "#og": "og.title" }
沒有它,DynamoDB 把 og.title 讀成「og map 內的 title 欄位」
— 完全不同的東西。含空格或開頭數字的名稱也是同樣的故事。
對於巢狀,你對每個區段用佔位符:#meta.#author,並把 #meta 與
#author 兩者都定義。
名稱 vs 值,並排
#name | :value | |
|---|---|---|
| 替換 | 一個屬性 **名稱 | 一個屬性 值** |
| Map | ExpressionAttributeNames | ExpressionAttributeValues |
| 前綴 | # | : |
| 何時需要 | 保留字、點、空格 | 永遠 — 沒有內嵌字面量 |
| 用錯時出錯 | ValidationException | ValidationException |
如果一個值被型別化成一個名稱,DynamoDB 會去找一個叫
published 的屬性,而你的條件絕不會以你本意的方式匹配 — 所以 API
大聲失敗。那種嚴格是一個特性:沒有安靜的錯誤答案。
陷阱與下一步
- 宣告一個你不用的佔位符 — DynamoDB 拒絕任一 map 中未使用的 條目。從 expression 建那些 map,而非在它之前。
- 編輯 expression 後重用
:v— 丟掉一個子句,它的值可能 滯留,觸發未使用條目錯誤。builder 把它們保持同步。 - 因為一個名稱可行過一次就假設它安全 — 保留字衝突是 每個屬性各自的。一致地用佔位符,並停止猜測。
這些 map 出現在每條寫入路徑上,所以它們自然與 single-table design 配對,並與在你曾附加一個過濾之前就知道 何時 Query vs Scan 配對。
用 Expression Builder 產生 expression 加兩個 map,然後 試試 DynoTable 對你自己的表執行它們,並看著 佔位符解析。