DynamoDB UpdateItem in Go (AWS SDK v2)

UpdateItem è lineare in Go; la parte scomoda è che map[string]types.AttributeValue è una mappa di interfacce, quindi sia i valori che invii sia quelli che ricevi sono puntatori a una di nove struct membro. Quella singola scelta di design spiega gran parte degli attriti qui sotto, a partire dal fatto che il fmt.Println in questo snippet non stampa il tuo Item.

Codice

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
}

Spiegazione

  • AttributeValueMemberN.Value è una string, non un tipo numerico, e attributevalue.Marshal la lascia tale: int64(9007199254740993) viene marshallato in "9007199254740993" esattamente. I numeri DynamoDB portano 38 cifre di precisione, più di qualsiasi float Go, quindi l'SDK non converte mai. Il parsing lo fai tu, al confine, deliberatamente.

  • ReturnValues: types.ReturnValueAllNew — il valore della costante è il letterale "ALL_NEW". types.ReturnValueAllNew.Values() elenca tutte e cinque (NONE, ALL_OLD, UPDATED_OLD, ALL_NEW, UPDATED_NEW) se preferisci leggerle dal tipo invece che dalla documentazione.

  • UpdateExpression è un *string che costruisci tu, o che deleghi al pacchetto expression più sotto. In entrambi i casi DynamoDB è l'unico parser: ADD #upd2 :updValue2 dà l'incremento atomico, attribute_exists(Artist) in una ConditionExpression rende la chiamata di solo aggiornamento invece che un upsert, e la grammatica delle clausole vive in update expressions.

  • ValidationException non ha un tipo Go. types/errors.go definisce 35 struct di errore, tra cui ConditionalCheckFailedException, TransactionCanceledException, ProvisionedThroughputExceededException e TransactionConflictException. I fallimenti di validazione non sono tra questi, quindi un'espressione sbagliata emerge come un generico smithy.APIError che puoi riconoscere solo dalla stringa:

    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: Year

    Nota il prefisso api error, che la modellata ConditionalCheckFailedException qui sotto non riceve. La sua presenza è un buon segnale che errors.As contro un tipo concreto non ti aiuterà qui.

  • Una condizione fallita, al contrario, è un tipo vero. var ccf *types.ConditionalCheckFailedException; errors.As(err, &ccf) corrisponde, e con ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOld sull'input, ccf.Item arriva popolato con l'Item com'era davvero. ccf.ErrorMessage() è il nudo The conditional request failed, senza il preambolo di trasporto che err.Error() antepone.

Rileggere il risultato

fmt.Println(out.Attributes) su una mappa di interfacce stampa indirizzi:

map[Artist:0x1c8cf589e060 Awards:0x1c8cf589e078 Genre:0x1c8cf589e090 SongTitle:0x1c8cf589e0c0 Year:0x1c8cf589e0a8]

Due vie d'uscita. Fai l'unmarshal in una struct, che è ciò che la maggior parte del codice dovrebbe fare:

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}

Oppure fai un'asserzione sull'unico membro che ti interessa, e ricorda che .Value è una stringa:

n, ok := out.Attributes["Awards"].(*types.AttributeValueMemberN)
if ok {
	awards, _ := strconv.Atoi(n.Value) // "1" -> 1
	fmt.Println(awards)
}

Lascia che sia il pacchetto expression a scriverla

Go è l'unico SDK su questo sito che genererà l'UpdateExpression al posto tuo. feature/dynamodb/expression compone le clausole ed entrambe le mappe:

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()

Quello che esce non è quello che hai scritto:

UpdateExpression: ADD #0 :0
SET #1 = :1, #2 = :2

Names: map[#0:Awards #1:Genre #2:Year]

Il builder ha riordinato le clausole, le ha separate con un a capo e ha numerato da sé i placeholder, quindi #0 è Awards e non il primo nome che hai menzionato. A valle non importa a nessuno, ma le stringhe non sono stabili tra una modifica e l'altra, il che le rende una pessima cosa su cui fare asserzioni nei test. Passa expr.Update(), expr.Names() ed expr.Values() direttamente in UpdateItemInput e non guardarle mai.

Il lato positivo è che mette un alias su ogni nome, quindi le parole riservate smettono di essere una classe di bug che puoi spedire. Se invece scrivi l'espressione a mano, passa prima i nomi degli attributi per il controllo delle parole riservate DynamoDB — la lista AWS ha 573 voci e Year, Name e Status ci sono tutte. E se preferisci guardare un Item come dati invece che come una mappa di puntatori a interfacce, scarica DynoTable.

Esempi correlati

Riferimenti

Ultima verifica 2026-07-28 rispetto alla documentazione ufficiale AWS collegata sopra.

Lavora con DynamoDB senza la Console

Un client desktop veloce per DynamoDB che esegue il vero SQL che DynamoDB non può — JOINs, GROUP BY, aggregazioni — con modifica visuale e un agente AI sulle tue chiavi Bedrock.

Prova gratuita di 30 giorni, senza carta di credito — poi il piano Free senza limiti di tempo.