Expression Attribute Names e Values no DynamoDB
Expressões do DynamoDB são templates: você escreve placeholders, depois fornece os nomes de
atributo e valores reais em dois mapas auxiliares. #name é um placeholder de nome;
:value é um placeholder de valor. Confunda os dois e o DynamoDB rejeita a chamada inteira.
Qual é a diferença entre #name e :value no DynamoDB?
#name é um placeholder para um nome de atributo, fornecido através de ExpressionAttributeNames; :value é um placeholder para um valor de atributo, fornecido através de ExpressionAttributeValues. Use #name para escapar de palavras reservadas, pontos ou espaços, e :value para todo literal — o DynamoDB nunca embute valores no texto. Eles não são intercambiáveis; trocar um pelo outro dispara uma ValidationException.
#namesubstitui um nome de atributo viaExpressionAttributeNames— use-o sempre que um atributo colidir com uma palavra reservada ou contiver um ponto/espaço.:valuesubstitui um valor viaExpressionAttributeValues— o DynamoDB nunca embute literais no texto da expressão, então todo valor é um placeholder.- Eles não são intercambiáveis. Um
#onde um:pertence é umaValidationException, não um no-op silencioso.
Vindo do SQL, você embute os dois — WHERE status = 'published'. O DynamoDB não embute
nenhum. Essa divisão é a coisa que tropeça todo novato.
Por que os dois mapas existem
No SQL a string de consulta carrega tudo: nomes de coluna, literais, operadores. O DynamoDB deliberadamente separa a forma da expressão dos seus dados.
Valores vão no seu próprio mapa para que o DynamoDB possa tipar cada um (S, N, BOOL,
…) e para que o parser nunca tenha que adivinhar onde uma string termina — não há aspas nem
escaping para errar. Veja tipos de dados no DynamoDB para a lista
completa de type-tags.
Nomes recebem o mesmo tratamento por uma razão diferente: o DynamoDB tem uma longa lista de palavras reservadas, e qualquer atributo que corresponda a uma não pode aparecer como nome cru em uma expressão. O placeholder desvia da reserva por completo.
A cilada das palavras reservadas
Aqui está uma tabela de artigos de um CMS — chave de partição BLOG#<blog>, chave de
ordenação ARTICLE#<slug> — cujos atributos se leem naturalmente mas por acaso colidem com
palavras reservadas:
| Atributo | Reservado? | O que contém |
|---|---|---|
status | sim | draft / published |
name | sim | nome de exibição do autor |
size | sim | tamanho em bytes renderizado |
ttl | sim | expiração do arquivamento (epoch) |
slug | não | slug da URL |
status, name, size e ttl estão todos na lista de palavras reservadas da AWS, então
este filtro falha na primeira palavra:
FilterExpression status = :s
O DynamoDB retorna uma ValidationException — "Attribute name is a reserved keyword;
reserved keyword: status". A correção é um placeholder de nome, nunca renomear o atributo:
FilterExpression #status = :s
ExpressionAttributeNames { "#status": "status" }
ExpressionAttributeValues { ":s": { "S": "published" } }
A cilada: slug não é reservado, então uma consulta que você testou contra slug
funciona, e você assume que a próxima também vai. Aí status a quebra. A lista completa
muda, então não a memorize — coloque placeholder em todo nome e você nunca é mordido.
Mapeie todo valor, sempre
Valores são inegociáveis: não há sintaxe para um literal inline. Até um número simples ganha um placeholder. Este update marca um artigo como publicado, carimba seu tamanho, e define um TTL de arquivamento de 30 dias:
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" }
}Note que :sz e :exp são enviados como strings N — o tipo number do DynamoDB é
codificado no fio como string. O mapa de valores também é onde você reutiliza um valor entre
cláusulas: defina :s uma vez, referencie-o tanto em uma ConditionExpression quanto em
uma FilterExpression.
Construir esses dois mapas à mão é onde erros de digitação se escondem. O Expression Builder gera a string da expressão e os dois mapas juntos, com os type-tags preenchidos, para que os placeholders não saiam de sincronia.
Nomes para caminhos aninhados e estranhos
O placeholder # faz mais do que desviar de palavras reservadas. A sintaxe de
document-path usa pontos e colchetes, então um atributo que literalmente contém um ponto —
digamos uma chave de metadados og.title — é inendereçável sem um placeholder:
ProjectionExpression #og
ExpressionAttributeNames { "#og": "og.title" }
Sem ele, o DynamoDB lê og.title como "o campo title dentro do map og" — uma coisa
inteiramente diferente. Mesma história para nomes com espaços ou dígitos iniciais. Para
aninhamento, você coloca placeholder em cada segmento: #meta.#author com ambos #meta e
#author definidos.
Nomes vs valores, lado a lado
#name | :value | |
|---|---|---|
| Substitui | um nome de atributo | um valor de atributo |
| Mapa | ExpressionAttributeNames | ExpressionAttributeValues |
| Prefixo | # | : |
| Necessário para | palavras reservadas, pontos, espaços | sempre — sem literais inline |
| O errado dá erro | ValidationException | ValidationException |
Se um valor fosse tipado como nome, o DynamoDB procuraria um atributo chamado published e
sua condição nunca corresponderia do jeito que você quis — então a API falha alto em vez
disso. Essa rigidez é um recurso: não há resposta errada silenciosa.
Armadilhas e próximos passos
- Declarar um placeholder que você não usa — o DynamoDB rejeita entradas não usadas em qualquer um dos mapas. Construa os mapas a partir da expressão, não antes dela.
- Reutilizar
:vdepois de editar a expressão — remova uma cláusula e seu valor pode permanecer, disparando o erro de entrada não usada. O builder os mantém em sincronia. - Assumir que um nome é seguro porque funcionou uma vez — colisões de palavra reservada são por atributo. Coloque placeholder uniformemente e pare de adivinhar.
Esses mapas aparecem em todo caminho de escrita, então combinam naturalmente com single-table design e com saber quando usar Query vs Scan antes de você anexar um filtro.
Gere a expressão mais os dois mapas com o Expression Builder, depois experimente o DynoTable para rodá-los contra suas próprias tabelas e ver os placeholders se resolverem.