Expression Attribute Names und Values in DynamoDB
DynamoDB-Expressions sind Templates: du schreibst Platzhalter, dann lieferst du die
echten Attributnamen und Werte in zwei Side-Maps. #name ist ein
Name-Platzhalter; :value ist ein Wert-Platzhalter. Verwechsle die beiden, und
DynamoDB lehnt den ganzen Aufruf ab.
Was ist der Unterschied zwischen #name und :value in DynamoDB?
#name ist ein Platzhalter für einen Attributnamen, der über
ExpressionAttributeNames geliefert wird; :value ist ein Platzhalter für einen
Attributwert, der über ExpressionAttributeValues geliefert wird. Verwende
#name, um reservierten Wörtern, Punkten oder Leerzeichen auszuweichen, und
:value für jedes Literal — DynamoDB inlinet Werte nie. Sie sind nicht
austauschbar; sie zu vertauschen wirft eine ValidationException.
#namesubstituiert einen Attributnamen überExpressionAttributeNames— verwende es immer, wenn ein Attribut mit einem reservierten Wort kollidiert oder einen Punkt/Leerzeichen enthält.:valuesubstituiert einen Wert überExpressionAttributeValues— DynamoDB inlinet Literale nie in den Expression-Text, also ist jeder Wert ein Platzhalter.- Sie sind nicht austauschbar. Ein
#, wo ein:hingehört, ist eineValidationException, kein stilles No-op.
Aus SQL kommend inlinest du beides — WHERE status = 'published'. DynamoDB inlinet
keines. Diese Aufteilung ist das, was jeden Neuling stolpern lässt.
Warum die zwei Maps existieren
In SQL trägt der Query-String alles: Spaltennamen, Literale, Operatoren. DynamoDB trennt absichtlich die Form der Expression von ihren Daten.
Werte kommen in ihre eigene Map, damit DynamoDB jeden typen kann (S, N,
BOOL, …) und damit der Parser nie raten muss, wo ein String endet — es gibt kein
Quoting oder Escaping, das man falsch machen könnte. Siehe
Datentypen in DynamoDB für die volle Type-Tag-Liste.
Namen bekommen dieselbe Behandlung aus einem anderen Grund: DynamoDB hat eine lange Liste reservierter Wörter, und jedes Attribut, das einem entspricht, kann nicht als bloßer Name in einer Expression erscheinen. Der Platzhalter weicht der Reservierung ganz aus.
Die Reservierte-Wort-Falle
Hier ist eine CMS-Artikel-Tabelle — Partition Key BLOG#<blog>, Sort Key
ARTICLE#<slug> — deren Attribute sich natürlich lesen, aber zufällig mit
reservierten Wörtern kollidieren:
| Attribut | Reserviert? | Was es hält |
|---|---|---|
status | ja | draft / published |
name | ja | Autor-Anzeigename |
size | ja | gerenderte Byte-Länge |
ttl | ja | Archiv-Ablauf (Epoch) |
slug | nein | URL-Slug |
status, name, size und ttl stehen alle auf AWS' Reservierte-Wörter-Liste,
also scheitert dieser Filter beim ersten Wort:
FilterExpression status = :s
DynamoDB gibt eine ValidationException zurück — "Attribute name is a reserved
keyword; reserved keyword: status". Der Fix ist ein Name-Platzhalter, nie das
Umbenennen des Attributs:
FilterExpression #status = :s
ExpressionAttributeNames { "#status": "status" }
ExpressionAttributeValues { ":s": { "S": "published" } }
Die Falle: slug ist nicht reserviert, also funktioniert eine Query, die du
gegen slug getestet hast, und du nimmst an, die nächste tut es auch. Dann macht
status sie kaputt. Die volle Liste bewegt sich, also memoriere sie nicht —
platzhaltere jeden Namen und du wirst nie gebissen.
Mappe jeden Wert, immer
Werte sind nicht verhandelbar: es gibt keine Syntax für ein Inline-Literal. Selbst eine bloße Zahl bekommt einen Platzhalter. Dieses Update markiert einen Artikel als publiziert, stempelt seine Größe und setzt eine 30-Tage-Archiv-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" }
}Beachte, dass :sz und :exp als N-Strings gesendet werden — DynamoDBs
Number-Typ ist wire-kodiert als String. Die Wert-Map ist auch, wo du einen Wert
über Klauseln hinweg wiederverwendest: definiere :s einmal, referenziere ihn in
sowohl einer ConditionExpression als auch einer FilterExpression.
Diese zwei Maps von Hand zu bauen ist, wo sich Tippfehler verstecken. Der Expression Builder generiert den Expression-String und beide Maps zusammen, mit den ausgefüllten Type-Tags, sodass die Platzhalter nicht aus dem Takt geraten können.
Namen für verschachtelte und sperrige Pfade
Der #-Platzhalter tut mehr, als reservierten Wörtern auszuweichen.
Document-Path-Syntax verwendet Punkte und Klammern, also ist ein Attribut, das
buchstäblich einen Punkt enthält — etwa ein Metadaten-Key og.title — ohne
Platzhalter nicht adressierbar:
ProjectionExpression #og
ExpressionAttributeNames { "#og": "og.title" }
Ohne ihn liest DynamoDB og.title als "das title-Feld innerhalb der og-Map" —
etwas völlig anderes. Dieselbe Geschichte für Namen mit Leerzeichen oder führenden
Ziffern. Für Nesting platzhalterst du jedes Segment: #meta.#author mit sowohl
#meta als auch #author definiert.
Namen vs. Werte, nebeneinander
#name | :value | |
|---|---|---|
| Substituiert | einen Attributnamen | einen Attributwert |
| Map | ExpressionAttributeNames | ExpressionAttributeValues |
| Präfix | # | : |
| Nötig für | reservierte Wörter, Punkte, Leerzeichen | immer — keine Inline-Literale |
| Falscher gibt Fehler | ValidationException | ValidationException |
Wäre ein Wert als Name getypt, würde DynamoDB nach einem Attribut namens
published suchen und deine Bedingung würde nie so matchen, wie du es meintest —
also scheitert die API laut statt. Diese Strenge ist ein Feature: es gibt keine
stille falsche Antwort.
Fallstricke und nächste Schritte
- Einen Platzhalter deklarieren, den du nicht verwendest — DynamoDB lehnt ungenutzte Einträge in beiden Maps ab. Baue die Maps aus der Expression, nicht ihr voraus.
:vnach dem Bearbeiten der Expression wiederverwenden — drop eine Klausel, und ihr Wert kann verweilen, was den Ungenutzter-Eintrag-Fehler auslöst. Der Builder hält sie im Gleichschritt.- Annehmen, dass ein Name sicher ist, weil er einmal funktionierte — Reservierte-Wort-Kollisionen sind pro Attribut. Platzhaltere einheitlich und hör auf zu raten.
Diese Maps tauchen in jedem Write-Pfad auf, also passen sie natürlich zu Single-Table Design und dazu, zu wissen, wann man Query vs. Scan verwendet, bevor du je einen Filter anhängst.
Generiere die Expression plus beide Maps mit dem Expression Builder, dann probier DynoTable, um sie gegen deine eigenen Tabellen auszuführen und zuzusehen, wie die Platzhalter auflösen.