DynamoDB Expression Attribute Names und Values
DynamoDB-Expressions sind Vorlagen: Du schreibst Platzhalter und lieferst die echten
und -werte in zwei Begleit-Maps nach. #name ist ein Namensplatzhalter,
:value ein Wertplatzhalter. Verwechselst du die beiden, lehnt DynamoDB den
ganzen Aufruf ab.
Was ist der Unterschied zwischen #name und :value in DynamoDB?
#name ist ein Platzhalter für einen Attributnamen, geliefert über ExpressionAttributeNames; :value ist ein Platzhalter für einen Attributwert, geliefert über ExpressionAttributeValues. Nimm #name, um reservierten Wörtern, Punkten oder Leerzeichen auszuweichen, und :value für jedes Literal — DynamoDB setzt Werte nie inline ein. Austauschbar sind sie nicht; vertauschst du sie, gibt es eine ValidationException.
#nameersetzt einen Attributnamen überExpressionAttributeNames— nimm es immer dann, wenn ein Attribut mit einem reservierten Wort kollidiert oder einen Punkt bzw. ein Leerzeichen enthält.:valueersetzt einen Wert überExpressionAttributeValues— DynamoDB setzt nie Literale in den Expression-Text ein, jeder Wert ist also ein Platzhalter.- Austauschbar sind sie nicht. Ein
#, wo ein:hingehört, ist eineValidationException, kein stilles No-op.
Aus SQL kommend setzt du beides inline — WHERE status = 'published'. DynamoDB setzt
keins von beidem inline. Genau diese Trennung bringt jeden Neuling zum Stolpern.
Warum es zwei Maps gibt
In SQL trägt der Query-String alles: Spaltennamen, Literale, Operatoren. DynamoDB trennt die Form der Expression bewusst von ihren Daten.
Werte bekommen eine eigene Map, damit DynamoDB jeden einzeln typisieren kann (S, N, BOOL, …)
und damit der Parser nie raten muss, wo ein String endet — es gibt kein Quoting
und kein Escaping, das schiefgehen könnte. Die vollständige Typ-Tag-Liste steht unter
Datentypen in DynamoDB.
Namen bekommen dieselbe Behandlung, aus einem anderen Grund: DynamoDB hat eine lange Liste reservierter Wörter, und jedes Attribut, das eines davon trifft, darf in einer Expression nicht als nackter Name auftauchen. Der Platzhalter umgeht die Reservierung komplett.
Die Falle der reservierten Wörter
Hier eine CMS-Artikeltabelle — Partitionsschlüssel BLOG#<blog>, Sortierschlüssel
ARTICLE#<slug> —, deren Attribute sich natürlich lesen, aber zufällig mit
reservierten Wörtern kollidieren:
| Attribut | Reserviert? | Was es enthält |
|---|---|---|
status | ja | draft / published |
name | ja | Anzeigename des Autors |
size | ja | gerenderte Bytelänge |
ttl | ja | Archivablauf (Epoch) |
slug | nein | URL-Slug |
status, name, size und ttl stehen alle auf der Liste reservierter Wörter von AWS,
dieser Filter scheitert also schon am ersten Wort:
FilterExpression status = :s
DynamoDB antwortet mit einer ValidationException — "Attribute name is a reserved
keyword; reserved keyword: status". Die Lösung ist ein Namensplatzhalter, niemals ein
Umbenennen des Attributs:
FilterExpression #status = :s
ExpressionAttributeNames { "#status": "status" }
ExpressionAttributeValues { ":s": { "S": "published" } }
slug ist nicht reserviert, eine gegen slug getestete Abfrage funktioniert also, und du
nimmst an, die nächste tut es auch. Dann bricht status sie. Die Liste ändert sich, also
lern sie nicht auswendig — setz jeden Namen als Platzhalter, dann erwischt es dich nie.
Jeden Wert abbilden, immer
Bei Werten gibt es nichts zu verhandeln: Es existiert keine Syntax für ein Inline-Literal. Selbst eine schlichte Zahl bekommt einen Platzhalter. Dieses Update markiert einen Artikel als veröffentlicht, hält seine Größe fest und setzt eine 30-Tage-Archiv-:
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: :sz und :exp gehen als N-Strings raus — der Zahlentyp von DynamoDB wird auf
der Leitung als String kodiert. Die Wert-Map ist auch der Ort, an dem du einen Wert über
Klauseln hinweg wiederverwendest: :s einmal definieren, in ConditionExpression und
FilterExpression referenzieren.
Diese beiden Maps von Hand zu bauen ist genau die Stelle, an der sich Tippfehler verstecken. Der Expression Builder erzeugt den Expression-String und beide Maps gemeinsam, mit ausgefüllten Typ-Tags, damit die Platzhalter nicht auseinanderlaufen können.
Der Builder unten filtert auf status — ein reserviertes Wort —, du kannst also zusehen, wie
er in der ExpressionAttributeNames-Map automatisch auf #status aliast:
Namen für verschachtelte und sperrige Pfade
Der #-Platzhalter kann mehr, als reservierten Wörtern auszuweichen. Die Dokumentpfad-Syntax
nutzt Punkte und Klammern, ein Attribut, das buchstäblich einen Punkt enthält — etwa ein
Metadaten-Schlüssel og.title — ist ohne Platzhalter also gar nicht adressierbar:
ProjectionExpression #og
ExpressionAttributeNames { "#og": "og.title" }
Ohne ihn liest DynamoDB og.title als „das Feld title innerhalb der Map og"
— etwas völlig anderes. Dasselbe gilt für Namen mit Leerzeichen oder führenden Ziffern.
Bei Verschachtelung setzt du jedes Segment als Platzhalter: #meta.#author, mit #meta und
#author beide definiert.
Namen vs. Werte, nebeneinander
#name | :value | |
|---|---|---|
| Ersetzt | einen Attributnamen | einen Attributwert |
| Map | ExpressionAttributeNames | ExpressionAttributeValues |
| Präfix | # | : |
| Nötig für | reservierte Wörter, Punkte, Leerzeichen | immer — keine Inline-Literale |
| Falscher Typ ergibt | ValidationException | ValidationException |
Wäre ein Wert als Name getippt, würde DynamoDB nach einem Attribut namens published suchen,
und deine Bedingung träfe nie so zu, wie du es gemeint hast — deshalb scheitert die API laut.
Diese Strenge ist ein Feature: Es gibt keine stille falsche Antwort.
Fallstricke und nächste Schritte
- Einen Platzhalter deklarieren, den du nicht benutzt — DynamoDB lehnt ungenutzte Einträge in beiden Maps ab. Bau die Maps aus der Expression heraus, nicht im Voraus.
:vnach dem Umbauen der Expression weiterverwenden — lässt du eine Klausel weg, kann ihr Wert übrig bleiben und den Ungenutzt-Fehler auslösen. Der Builder hält beides im Gleichschritt.- Annehmen, ein Name sei sicher, weil er einmal funktioniert hat — Kollisionen mit reservierten Wörtern gelten pro Attribut. Setz einheitlich Platzhalter und hör auf zu raten.
Diese Maps tauchen in jedem Schreibpfad auf, sie passen also natürlich zu Single-Table Design und dazu, zu wissen, wann Query und wann Scan, bevor du überhaupt einen Filter anhängst.
Lass dir die Expression samt beider Maps vom Expression Builder erzeugen und probier DynoTable aus, um sie gegen deine eigenen Tabellen laufen zu lassen und den Platzhaltern beim Auflösen zuzusehen.