Go での DynamoDB UpdateItem(AWS SDK v2)
Go での UpdateItem は素直です。厄介なのは map[string]types.AttributeValue が インターフェース のマップだという点で、そのため送る値も返ってくる値も、9 つのメンバー構造体のいずれかへのポインターになります。この 1 つの設計判断が、以下の摩擦のほとんどを説明します。まずは、このスニペットの fmt.Println があなたのアイテムを表示しないという事実から。
コード
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
log.Fatalf("load config: %v", err)
}
client := dynamodb.NewFromConfig(cfg)
out, err := client.UpdateItem(ctx, &dynamodb.UpdateItemInput{
TableName: aws.String("Music"),
Key: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "Arturo Sandoval"},
"SongTitle": &types.AttributeValueMemberS{Value: "Cubano Chant"},
},
UpdateExpression: aws.String("SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2"),
ExpressionAttributeNames: map[string]string{
"#upd0": "Genre",
"#upd1": "Year",
"#upd2": "Awards",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":updValue0": &types.AttributeValueMemberS{Value: "Latin Jazz"},
":updValue1": &types.AttributeValueMemberN{Value: "1994"},
":updValue2": &types.AttributeValueMemberN{Value: "1"},
},
ReturnValues: types.ReturnValueAllNew,
})
if err != nil {
log.Fatalf("update item: %v", err)
}
fmt.Println(out.Attributes) // the item after the update
}解説
AttributeValueMemberN.Valueは数値型ではなくstringです。attributevalue.Marshalもそのまま保ちます。int64(9007199254740993)は正確に"9007199254740993"にマーシャルされます。DynamoDB の数値は 38 桁の精度を持ち、どんな Go の float よりも大きいので、SDK は決して変換しません。パースは自分で、境界で、意図して行います。ReturnValues: types.ReturnValueAllNew— この定数の値はリテラルの"ALL_NEW"です。ドキュメントより型から読み取りたければ、types.ReturnValueAllNew.Values()が 5 つすべて(NONE、ALL_OLD、UPDATED_OLD、ALL_NEW、UPDATED_NEW)を列挙します。UpdateExpressionは自分で組み立てる*stringです。あるいは後述のexpressionパッケージに任せます。いずれにせよパーサーは DynamoDB だけです。ADD #upd2 :updValue2がアトミックなインクリメントになり、ConditionExpressionのattribute_exists(Artist)は呼び出しをアップサートではなく更新専用にします。句の文法は更新式にあります。ValidationExceptionに Go の型はありません。types/errors.goは 35 個のエラー構造体を定義しており、その中にはConditionalCheckFailedException、TransactionCanceledException、ProvisionedThroughputExceededException、TransactionConflictExceptionがあります。バリデーション失敗はその中にないので、不正な式は文字列でしか判別できない汎用のsmithy.APIErrorとして表面化します。operation error DynamoDB: UpdateItem, https response error StatusCode: 400, RequestID: 702d67f1-4f15-43dc-b3e9-cea691878801, api error ValidationException: Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Yearapi errorという接頭辞に注目してください。後述のモデル化されたConditionalCheckFailedExceptionにはこれが付きません。その存在は、ここでは具象型に対するerrors.Asが助けにならないことのそれなりの手がかりです。対照的に、失敗した条件は本物の型です。
var ccf *types.ConditionalCheckFailedException; errors.As(err, &ccf)は一致し、入力にReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOldを付けておけば、ccf.Itemに実際のアイテムがそのまま入って返ってきます。ccf.ErrorMessage()は素のThe conditional request failedで、err.Error()が前置する転送層の前置きは付きません。
結果を読み戻す
インターフェースのマップに対する fmt.Println(out.Attributes) はアドレスを表示します。
map[Artist:0x1c8cf589e060 Awards:0x1c8cf589e078 Genre:0x1c8cf589e090 SongTitle:0x1c8cf589e0c0 Year:0x1c8cf589e0a8]出口は 2 つあります。構造体にアンマーシャルする方法。ほとんどのコードはこうすべきです。
var song struct {
Artist string
Genre string
Year int
Awards int
}
err = attributevalue.UnmarshalMap(out.Attributes, &song)
// {Artist:Arturo Sandoval Genre:Latin Jazz Year:1994 Awards:1}あるいは、必要な 1 つのメンバーだけを型アサーションする方法です。.Value が文字列であることを忘れずに。
n, ok := out.Attributes["Awards"].(*types.AttributeValueMemberN)
if ok {
awards, _ := strconv.Atoi(n.Value) // "1" -> 1
fmt.Println(awards)
}expression パッケージに書かせる
このサイトで UpdateExpression を代わりに生成してくれる SDK は Go だけです。feature/dynamodb/expression が句と 2 つのマップを組み立てます。
upd := expression.Set(expression.Name("Genre"), expression.Value("Latin Jazz")).
Set(expression.Name("Year"), expression.Value(1994)).
Add(expression.Name("Awards"), expression.Value(1))
expr, _ := expression.NewBuilder().WithUpdate(upd).Build()出てくるものは、書いたものとは違います。
UpdateExpression: ADD #0 :0
SET #1 = :1, #2 = :2
Names: map[#0:Awards #1:Genre #2:Year]ビルダーは句を並べ替え、改行で区切り、プレースホルダーに自分で番号を振ったので、#0 は最初に書いた名前ではなく Awards です。下流はどれも気にしませんが、この文字列は編集をまたいで安定しないので、テストでアサートする対象としては不向きです。expr.Update()、expr.Names()、expr.Values() をそのまま UpdateItemInput に渡して、中身は見ないことです。
利点は、すべての名前に別名を付けてくれることで、予約語が出荷しうるバグの一種でなくなります。式を手書きするなら、属性名をまず DynamoDB 予約語チェッカーに通してください。AWS のリストは 573 語あり、Year、Name、Status はすべて載っています。そして、アイテムをインターフェースポインターのマップではなくデータとして眺めたいなら、DynoTable をダウンロードしてください。
関連する例
- Java での DynamoDB UpdateItem — AWS SDK for Java 2.x による同じ更新。
- Go での DynamoDB PutItem — 代わりにアイテムを丸ごと置き換えます。
- DynamoDB の更新式 —
SET、ADD、REMOVE、DELETEとイディオム。 - ReturnValues を理解する — 各
ReturnValuesオプションが返すもの。 - 「Attribute name is a reserved keyword」 — ここの別名マップが任意ではない理由。
- 「Invalid UpdateExpression」の構文エラー — よくある SET/ADD の構文ミスを解説。
参考資料
- UpdateItem — Amazon DynamoDB API Reference
- Use UpdateItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- dynamodb package — AWS SDK for Go v2 (pkg.go.dev)
- dynamodb/types package — AWS SDK for Go v2 (pkg.go.dev)
- expression package — AWS SDK for Go v2 (pkg.go.dev)
- Update expressions — Amazon DynamoDB Developer Guide
最終検証日 2026-07-28、上記にリンクした公式 AWS ドキュメントに照らして確認しました。