DynamoDBでは新規アイテム1の作成はputItemで行います。DynamoDBの各アイテムは必ずユニークになるプライマリキー2(以下単にキーと表記)を持っている必要があり、アイテム作成時も必ずキーの指定が必要です。アイテム作成時に既存のアイテムとキーが衝突したときに、RDBの感覚だと一意性制約でエラーになるのを期待しますが、DynamoDBではなんと!上書きしてしまいます。RDBに親しんだ脳みそだと、それじゃ困るでしょ、ありえないでしょ、と考えますよね。もちろん上書き禁止の方法は用意されていて、ConditionExpressionというオプションを使うことで実現できます。キー重複以外にも様々な条件が指定できます。
ConditionExpressionについての公式ドキュメント
http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.SpecifyingConditions.html
ConditionExpressionは、処理対象のアイテムに条件を適用します。書き込み処理の場合は、条件を満たしていれば処理が実行され、満たしていなければ処理を実行せずエラーを返します。読み込み系のオペレーションで使用した場合はフィルターとして機能します。今回のサンプルコードは全てputItemですが他のオペレーションでも使えます。
上書き禁止のputItemをPHPで書くと以下のようになります。attribute_not_exists関数を使います。キーが重複した時にfatal errorになるので、処理を続けたい場合はtry〜catchでエラーハンドリングします。$dynamodbは生成済みのDynamoDbClientインスタンスです。
try {
$dynamodb->putItem([
'TableName' => 'SampleTable',
'Item' => [
'Id' => ['N' => '1'],//パーティションキー
'Attr' => ['S' => 'ABC'],
],
'ConditionExpression' => 'attribute_not_exists(Id)',
]);
} catch (Exception $e) {
echo $e->getMessage();
}
公式ドキュメントを読んでちょっと戸惑ったのが、attribute_not_existsの説明が2つあって内容が違うように見えてしまうこと。なんか分かりづらいと思うんですがそんなことないですか。キーの重複をチェックしているのはattribute_not_exists関数ではなく、putItemオペレーションだと理解しましたがよいでしょうか。
上記の理由で、プライマリキーがパーティションキーとソートキーで構成されるテーブルでも、パーティションキーの指定だけで上書き禁止になります。下記の例だとattribute_not_existsで指定しているのはIdのみですが、キーの組み合わせが1-bのアイテムが存在しても、書き込みは成功します。書き込みが失敗するのは1-aのアイテムが存在する時だけです。
$dynamodb->putItem([
'TableName' => 'SampleTable',
'Item' => [
'Id' => ['N' => '1'],//パーティションキー
'SortKey' => ['S' => 'a'],//ソートキー
'Attr' => ['S' => 'ABC'],
],
'ConditionExpression' => 'attribute_not_exists(Id)',
]);
属性の有無ではなく属性の値を条件にする場合は、プレースホルダーを使って下記のように書きます。下記の例だとIdが1のアイテムが存在し、そのアイテムのAttrがXYZの時だけ失敗します。それ以外の場合、アイテムが存在すればAttrをABCに更新し、アイテムが存在しなければ新規アイテムを作成します。
$dynamodb->putItem([
'TableName' => 'SampleTable',
'Item' => [
'Id' => ['N' => '1'],//パーティションキー
'Attr' => ['S' => 'ABC'],
],
'ConditionExpression' => 'Attr <> :val',
'ExpressionAttributeValues' => [':val' => ['S' => 'XYZ']],
]);
複数の条件を指定する場合はAND、ORでつなげます。NOTも使えます。
'ConditionExpression' => 'attribute_not_exists(Id) AND attribute_not_exists(SortKey)'
batchWriteItemでConditionExpressionは使えない
batchWriteItemでもConditionExpressionが使えるかなと思って、下記のようなコードを試したのですが、ダメでした。batchWriteItemではConditionExpressionを指定しても無視されるようです。
$dynamodb->batchWriteItem([
'RequestItems' => [
'SampleTable' => [
[
'PutRequest' => [
'Item' => [
'Id' => ['N' => '1'],
'Attr' => ['S' => 'ABC'],
],
'ConditionExpression' => 'attribute_not_exists(Id)'
],
],
]
]
]);