DynamoDB の式属性名と値
DynamoDB の式はテンプレートです。プレースホルダーを書き、それから実際の属性名と値を2つの
サイドマップで供給します。#name は名前のプレースホルダー、:value は値のプレースホルダー
です。この2つを取り違えると、DynamoDB は呼び出し全体を拒否します。
DynamoDB における #name と :value の違いは何ですか?
#name は属性名のプレースホルダーで、ExpressionAttributeNames 経由で供給します。:value は属性値のプレースホルダーで、ExpressionAttributeValues 経由で供給します。予約語、ドット、スペースを回避するには #name を使い、リテラルにはすべて :value を使います — DynamoDB は値を決してインライン化しません。両者は交換可能ではなく、取り違えると ValidationException がスローされます。
#nameは属性名を置き換える —ExpressionAttributeNames経由で。属性が予約語と 衝突するとき、あるいはドットやスペースを含むときに常に使います。:valueは値を置き換える —ExpressionAttributeValues経由で。DynamoDB は式の テキストにリテラルを決してインライン化しないため、すべての値がプレースホルダーです。- 両者は交換可能ではない。
:があるべき場所の#は、静かな no-op ではなくValidationExceptionです。
SQL から来ると、両方をインライン化します — WHERE status = 'published'。DynamoDB は
どちらもインライン化しません。その分割こそが、あらゆる新参者をつまずかせるものです。
なぜ2つのマップが存在するのか
SQL ではクエリ文字列がすべてを運びます。列名、リテラル、演算子。DynamoDB は式の 形 を その データ から意図的に分離します。
値は自身のマップに入るので、DynamoDB は各値を型付け(S、N、BOOL …)でき、パーサーは
文字列がどこで終わるかを推測する必要が決してありません — 間違えるクオートやエスケープが
ありません。完全な型タグのリストは DynamoDB のデータ型 を参照
してください。
名前も別の理由で同じ扱いを受けます。DynamoDB には長い 予約語 のリストがあり、その いずれかに一致する属性は、式中で素の名前として現れられません。プレースホルダーが予約を 完全に回避します。
予約語の罠
CMS の記事テーブルがあります — パーティションキー BLOG#<blog>、ソートキー
ARTICLE#<slug> — その属性は自然に読めますが、たまたま予約語と衝突します。
| 属性 | 予約語? | 保持するもの |
|---|---|---|
status | はい | draft / published |
name | はい | 著者の表示名 |
size | はい | レンダリング後のバイト長 |
ttl | はい | アーカイブ期限(エポック) |
slug | いいえ | URL スラグ |
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 がそれを壊します。リストは動くので、覚えようとせず — すべての
名前をプレースホルダー化すれば、決して噛まれません。
すべての値を、常にマップする
値は譲れません。インラインリテラルの構文はありません。素の数値でさえプレースホルダーを 得ます。この更新は、記事を published に設定し、そのサイズを刻印し、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 の数値型は
ワイヤー上で文字列としてエンコードされます。値マップは、句をまたいで値を再利用する場所
でもあります。:s を一度定義し、ConditionExpression と FilterExpression の両方で
参照します。
これら2つのマップを手で構築するのは、タイプミスが隠れる場所です。 Expression Builder は、式文字列と両方のマップを、 型タグを埋めて一緒に生成するので、プレースホルダーが互いにずれることはありません。
ネストした、扱いにくいパスのための名前
# プレースホルダーは予約語を回避する以上のことをします。ドキュメントパス構文はドットと
ブラケットを使うため、文字どおりドットを含む属性 — 例えばメタデータキー og.title — は
プレースホルダーなしではアドレス指定できません。
ProjectionExpression #og
ExpressionAttributeNames { "#og": "og.title" }
それがないと、DynamoDB は og.title を「og マップの中の title フィールド」と読みます
— まったく別物です。スペースや先頭の数字を持つ名前も同じ話です。ネストには各セグメントを
プレースホルダー化します。#meta.#author で、#meta と #author の両方を定義します。
名前と値、並べて
#name | :value | |
|---|---|---|
| 置き換えるもの | 属性 **名 | 属性 値** |
| マップ | ExpressionAttributeNames | ExpressionAttributeValues |
| プレフィックス | # | : |
| 必要な場面 | 予約語、ドット、スペース | 常に — インラインリテラルなし |
| 間違えるとエラー | ValidationException | ValidationException |
もし値が名前として型付けされたら、DynamoDB は published という属性を探し、あなたの条件は
意図したようには決してマッチしません — だから API は静かにではなく大声で失敗します。その
厳格さは機能です。静かに間違った答えはありません。
落とし穴と次のステップ
- 使わないプレースホルダーを宣言する — DynamoDB はどちらのマップでも未使用のエントリを 拒否します。マップは式の先回りではなく、式から構築しましょう。
- 式を編集した後に
:vを再利用する — 句を削るとその値が残り、未使用エントリのエラーを 引き起こします。ビルダーはそれらを歩調を合わせて保ちます。 - 一度動いたから名前が安全と思い込む — 予約語の衝突は属性ごとです。一律に プレースホルダー化し、当て推量をやめましょう。
これらのマップはあらゆる書き込み経路に現れるため、シングルテーブル設計 と、フィルタを付ける前に Query と Scan のどちらにするか を知ることと、 自然に対になります。
式と両方のマップを Expression Builder で生成し、 それから DynoTable を試して 自分のテーブルに対して実行し、プレースホルダーが 解決されるのを見てください。