中級読了 2 分

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 は各値を型付け(SNBOOL …)でき、パーサーは 文字列がどこで終わるかを推測する必要が決してありません — 間違えるクオートやエスケープが ありません。完全な型タグのリストは DynamoDB のデータ型 を参照 してください。

名前も別の理由で同じ扱いを受けます。DynamoDB には長い 予約語 のリストがあり、その いずれかに一致する属性は、式中で素の名前として現れられません。プレースホルダーが予約を 完全に回避します。

予約語の罠

CMS の記事テーブルがあります — パーティションキー BLOG#<blog>、ソートキー ARTICLE#<slug> — その属性は自然に読めますが、たまたま予約語と衝突します。

属性予約語?保持するもの
statusはいdraft / published
nameはい著者の表示名
sizeはいレンダリング後のバイト長
ttlはいアーカイブ期限(エポック)
slugいいえURL スラグ

statusnamesizettl はすべて 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:expN 文字列として送られることに注目してください — DynamoDB の数値型は ワイヤー上で文字列としてエンコードされます。値マップは、句をまたいで値を再利用する場所 でもあります。:s を一度定義し、ConditionExpressionFilterExpression の両方で 参照します。

これら2つのマップを手で構築するのは、タイプミスが隠れる場所です。 Expression Builder は、式文字列と両方のマップを、 型タグを埋めて一緒に生成するので、プレースホルダーが互いにずれることはありません。

ネストした、扱いにくいパスのための名前

# プレースホルダーは予約語を回避する以上のことをします。ドキュメントパス構文はドットと ブラケットを使うため、文字どおりドットを含む属性 — 例えばメタデータキー og.title — は プレースホルダーなしではアドレス指定できません。

ProjectionExpression       #og
ExpressionAttributeNames   { "#og": "og.title" }

それがないと、DynamoDB は og.title を「og マップの中の title フィールド」と読みます — まったく別物です。スペースや先頭の数字を持つ名前も同じ話です。ネストには各セグメントを プレースホルダー化します。#meta.#author で、#meta#author の両方を定義します。

名前と値、並べて

#name:value
置き換えるもの属性 **名属性 値**
マップExpressionAttributeNamesExpressionAttributeValues
プレフィックス#:
必要な場面予約語、ドット、スペース常に — インラインリテラルなし
間違えるとエラーValidationExceptionValidationException

もし値が名前として型付けされたら、DynamoDB は published という属性を探し、あなたの条件は 意図したようには決してマッチしません — だから API は静かにではなく大声で失敗します。その 厳格さは機能です。静かに間違った答えはありません。

落とし穴と次のステップ

  • 使わないプレースホルダーを宣言する — DynamoDB はどちらのマップでも未使用のエントリを 拒否します。マップは式の先回りではなく、式から構築しましょう。
  • 式を編集した後に :v を再利用する — 句を削るとその値が残り、未使用エントリのエラーを 引き起こします。ビルダーはそれらを歩調を合わせて保ちます。
  • 一度動いたから名前が安全と思い込む — 予約語の衝突は属性ごとです。一律に プレースホルダー化し、当て推量をやめましょう。

これらのマップはあらゆる書き込み経路に現れるため、シングルテーブル設計 と、フィルタを付ける前に Query と Scan のどちらにするか を知ることと、 自然に対になります。

式と両方のマップを Expression Builder で生成し、 それから DynoTable を試して 自分のテーブルに対して実行し、プレースホルダーが 解決されるのを見てください。

更新日