Referência de função do resolvedor de JavaScript do AWS AppSync para Amazon RDS - AWS AppSync GraphQL

Referência de função do resolvedor de JavaScript do AWS AppSync para Amazon RDS

A função e o resolvedor do AWS AppSync RDS permitem que os desenvolvedores enviem consultas SQL a um banco de dados de cluster do Amazon Aurora usando a API de dados do RDS e recuperem o resultado dessas consultas. É possível redigir declarações SQL que são enviadas à API de dados usando o modelo marcado com sql do módulo do rds do AWS AppSync ou usando as funções auxiliadores select, insert, update e remove do módulo do rds. O AWS AppSync utiliza a ação ExecuteStatement do RDS Data Service para executar declarações SQL no banco de dados.

Modelo marcado com SQL

O modelo marcado com sql do AWS AppSync permite criar uma declaração estática que possa receber valores dinâmicos em runtime usando expressões de modelo. O AWS AppSync cria um mapa variável pelos valores da expressão para criar uma consulta SqlParameterized que é enviada à a API de dados sem servidor do Amazon Aurora. Com esse método, não é possível que valores dinâmicos transmitidos em runtime modifiquem a declaração original, o que pode causar uma execução não intencional. Todos os valores dinâmicos são transmitidos como parâmetros, não podem modificar a declaração original e não são executados pelo banco de dados. Isso torna a consulta menos vulnerável a ataques de injeção de SQL.

nota

Em todos os casos, ao redigir declarações SQL, é necessário seguir as diretrizes de segurança para lidar adequadamente com os dados recebidos como entrada.

nota

O modelo marcado com sql só aceita a transmissão de valores de variáveis. Não é possível usar uma expressão para especificar dinamicamente nomes de colunas ou de tabelas. No entanto, é possível usar funções de utilitário para criar declarações dinâmicas.

No exemplo a seguir, criamos uma consulta que filtra com base no valor do argumento col definido dinamicamente na consulta do GraphQL em runtime. O valor só pode ser adicionado à declaração usando a expressão de tag:

import { sql, createMySQLStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { const query = sql` SELECT * FROM table WHERE column = ${ctx.args.col}` ; return createMySQLStatement(query); }

Ao transmitir todos os valores dinâmicos pelo mapa de variáveis, contamos com o mecanismo de banco de dados para processar e higienizar os valores com segurança.

Criar declarações

Funções e resolvedores podem interagir com bancos de dados MySQL e PostgreSQL. Use createMySQLStatement e createPgStatement, respectivamente, para criar declarações. Por exemplo, createMySQLStatement pode criar uma consulta MySQL. Essas funções aceitam até duas declarações, o que é útil quando uma solicitação deve recuperar os resultados imediatamente. Com MySQL, é possível fazer o seguinte:

import { sql, createMySQLStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { const { id, text } = ctx.args; const s1 = sql`insert into Post(id, text) values(${id}, ${text})`; const s2 = sql`select * from Post where id = ${id}`; return createMySQLStatement(s1, s2); }
nota

createPgStatement e createMySQLStatement não inserem caracteres de escape nem citam declarações criadas com o modelo marcado com sql.

Recuperação de dados

O resultado da declaração SQL executada está disponível no manipulador de respostas, no objeto context.result. O resultado é uma string JSON com os elementos de resposta da ação ExecuteStatement. Quando analisado, o resultado tem o seguinte formato:

type SQLStatementResults = { sqlStatementResults: { records: any[]; columnMetadata: any[]; numberOfRecordsUpdated: number; generatedFields?: any[] }[] }

É possível usar o utilitário toJsonObject para transformar o resultado em uma lista de objetos JSON representando as linhas exibidas. Por exemplo:

import { toJsonObject } from '@aws-appsync/utils/rds'; export function response(ctx) { const { error, result } = ctx; if (error) { return util.appendError( error.message, error.type, result ) } return toJsonObject(result)[1][0] }

Observe que o toJsonObject exibe uma matriz de resultados das declarações. Se você forneceu uma declaração, o tamanho da matriz será 1. Se você forneceu duas declarações, o tamanho da matriz será 2. Cada resultado na matriz contém 0 ou mais linhas. toJsonObject exibirá null se o valor do resultado for inválido ou inesperado.

Funções do utilitário

É possível usar os auxiliares utilitários do módulo do AWS AppSync RDS para interagir com o banco de dados.

O utilitário select cria uma declaração SELECT para consultar o banco de dados relacional.

Uso básico

Na forma básica, é possível especificar a tabela que deseja consultar:

import { select, createPgStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { // Generates statement: // "SELECT * FROM "persons" return createPgStatement(select({table: 'persons'})); }

Observe também que é possível especificar o esquema no identificador da tabela:

import { select, createPgStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { // Generates statement: // SELECT * FROM "private"."persons" return createPgStatement(select({table: 'private.persons'})); }

Especificar colunas

É possível especificar colunas com a propriedade columns. Se isso não for definido como um valor, o padrão será *:

export function request(ctx) { // Generates statement: // SELECT "id", "name" // FROM "persons" return createPgStatement(select({ table: 'persons', columns: ['id', 'name'] })); }

Também é possível especificar a tabela de uma coluna:

export function request(ctx) { // Generates statement: // SELECT "id", "persons"."name" // FROM "persons" return createPgStatement(select({ table: 'persons', columns: ['id', 'persons.name'] })); }

Limites e deslocamentos

É possível aplicar limit e offset à consulta:

export function request(ctx) { // Generates statement: // SELECT "id", "name" // FROM "persons" // LIMIT :limit // OFFSET :offset return createPgStatement(select({ table: 'persons', columns: ['id', 'name'], limit: 10, offset: 40 })); }

Ordenar por

É possível classificar os resultados com a propriedade orderBy. Forneça uma matriz de objetos especificando a coluna e uma propriedade dir opcional:

export function request(ctx) { // Generates statement: // SELECT "id", "name" FROM "persons" // ORDER BY "name", "id" DESC return createPgStatement(select({ table: 'persons', columns: ['id', 'name'], orderBy: [{column: 'name'}, {column: 'id', dir: 'DESC'}] })); }

Filtros

É possível criar filtros usando o objeto de condição especial:

export function request(ctx) { // Generates statement: // SELECT "id", "name" // FROM "persons" // WHERE "name" = :NAME return createPgStatement(select({ table: 'persons', columns: ['id', 'name'], where: {name: {eq: 'Stephane'}} })); }

Também é possível combinar filtros:

export function request(ctx) { // Generates statement: // SELECT "id", "name" // FROM "persons" // WHERE "name" = :NAME and "id" > :ID return createPgStatement(select({ table: 'persons', columns: ['id', 'name'], where: {name: {eq: 'Stephane'}, id: {gt: 10}} })); }

Também é possível criar declarações OR:

export function request(ctx) { // Generates statement: // SELECT "id", "name" // FROM "persons" // WHERE "name" = :NAME OR "id" > :ID return createPgStatement(select({ table: 'persons', columns: ['id', 'name'], where: { or: [ { name: { eq: 'Stephane'} }, { id: { gt: 10 } } ]} })); }

Também é possível negar uma condição com not:

export function request(ctx) { // Generates statement: // SELECT "id", "name" // FROM "persons" // WHERE NOT ("name" = :NAME AND "id" > :ID) return createPgStatement(select({ table: 'persons', columns: ['id', 'name'], where: { not: [ { name: { eq: 'Stephane'} }, { id: { gt: 10 } } ]} })); }

Também é possível usar os seguintes operadores para comparar valores:

Operador Descrição Tipos de valores possíveis
eq Equal number, string, boolean
ne Not equal number, string, boolean
le Less than or equal number, string
lt Less than number, string
ge Greater than or equal number, string
gt Greater than number, string
contains Like string
notContains Not like string
beginsWith Starts with prefix string
between Between two values number, string
attributeExists The attribute is not null number, string, boolean
size checks the length of the element string

O utilitário insert oferece uma maneira simples de inserir itens de linha única no banco de dados com a operação INSERT.

Inserções de item único

Para inserir um item, especifique a tabela e, depois, transmita o objeto de valores. As chaves do objeto são associadas às colunas da tabela. São inseridos automaticamente caracteres de escape nos nomes das colunas e os valores são enviados ao banco de dados usando o mapa de variáveis:

import { insert, createMySQLStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { const { input: values } = ctx.args; const insertStatement = insert({ table: 'persons', values }); // Generates statement: // INSERT INTO `persons`(`name`) // VALUES(:NAME) return createMySQLStatement(insertStatement) }

Caso de uso do MySQL

É possível combinar um insert seguido por um select para recuperar a linha inserida:

import { insert, select, createMySQLStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { const { input: values } = ctx.args; const insertStatement = insert({ table: 'persons', values }); const selectStatement = select({ table: 'persons', columns: '*', where: { id: { eq: values.id } }, limit: 1, }); // Generates statement: // INSERT INTO `persons`(`name`) // VALUES(:NAME) // and // SELECT * // FROM `persons` // WHERE `id` = :ID return createMySQLStatement(insertStatement, selectStatement) }

Caso de uso do Postgres

Com o Postgres, é possível usar returning para obter dados da linha que você inseriu. Ele aceita * ou uma matriz de nomes de colunas:

import { insert, createPgStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { const { input: values } = ctx.args; const insertStatement = insert({ table: 'persons', values, returning: '*' }); // Generates statement: // INSERT INTO "persons"("name") // VALUES(:NAME) // RETURNING * return createPgStatement(insertStatement) }

O utilitário update permite atualizar as linhas existentes. É possível usar o objeto de condição para aplicar alterações às colunas especificadas em todas as linhas que atendam à condição. Por exemplo, digamos que temos um esquema que nos permita fazer essa mutação. Queremos atualizar o name de Person com o valor id de 3, mas somente se os conhecermos (known_since) desde o ano 2000:

mutation Update { updatePerson( input: {id: 3, name: "Jon"}, condition: {known_since: {ge: "2000"}} ) { id name } }

Nosso resolvedor de atualização é semelhante a:

import { update, createPgStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { const { input: { id, ...values }, condition } = ctx.args; const where = { ...condition, id: { eq: id }, }; const updateStatement = update({ table: 'persons', values, where, returning: ['id', 'name'], }); // Generates statement: // UPDATE "persons" // SET "name" = :NAME, "birthday" = :BDAY, "country" = :COUNTRY // WHERE "id" = :ID // RETURNING "id", "name" return createPgStatement(updateStatement) }

Podemos adicionar uma verificação à nossa condição para garantir que somente a linha que tem a chave primária id igual a 3 seja atualizada. Da mesma forma, para Postgres inserts, é possível usar returning para exibir os dados modificados.

O utilitário remove permite excluir as linhas existentes. É possível usar o objeto de condição em todas as linhas que atendam à condição. Observe que delete é uma palavra-chave reservada em JavaScript. remove deve ser usado:

import { remove, createPgStatement } from '@aws-appsync/utils/rds'; export function request(ctx) { const { input: { id }, condition } = ctx.args; const where = { ...condition, id: { eq: id } }; const deleteStatement = remove({ table: 'persons', where, returning: ['id', 'name'], }); // Generates statement: // DELETE "persons" // WHERE "id" = :ID // RETURNING "id", "name" return createPgStatement(updateStatement) }

Conversão

Em alguns casos, convém ter maior especificidade sobre o tipo de objeto correto a ser usado na declaração. É possível usar as dicas de tipo fornecidas para especificar o tipo dos parâmetros. AWS AppSync é compatível com os mesmos tipos de dica da API de dados. É possível converter parâmetros usando as funções typeHint do módulo do AWS AppSync rds.

O exemplo a seguir permite enviar uma matriz como um valor que é convertido como um objeto JSON. Usamos o operador -> para recuperar o elemento index 2 na matriz JSON:

import { sql, createPgStatement, toJsonObject, typeHint } from '@aws-appsync/utils/rds'; export function request(ctx) { const arr = ctx.args.list_of_ids const statement = sql`select ${typeHint.JSON(arr)}->2 as value` return createPgStatement(statement) } export function response(ctx) { return toJsonObject(ctx.result)[0][0].value }

A conversão também é útil ao processar e comparar DATE, TIME e TIMESTAMP:

import { select, createPgStatement, typeHint } from '@aws-appsync/utils/rds'; export function request(ctx) { const when = ctx.args.when const statement = select({ table: 'persons', where: { createdAt : { gt: typeHint.DATETIME(when) } } }) return createPgStatement(statement) }

Veja outro exemplo de como enviar a data e a hora atuais:

import { sql, createPgStatement, typeHint } from '@aws-appsync/utils/rds'; export function request(ctx) { const now = util.time.nowFormatted('YYYY-MM-dd HH:mm:ss') return createPgStatement(sql`select ${typeHint.TIMESTAMP(now)}`) }

Dicas de tipo disponíveis

  • typeHint.DATE: o parâmetro correspondente é enviado como objeto do tipo DATE ao banco de dados. O formato aceito é YYYY-MM-DD.

  • typeHint.DECIMAL: o parâmetro correspondente é enviado como objeto do tipo DECIMAL ao banco de dados.

  • typeHint.JSON: o parâmetro correspondente é enviado como objeto do tipo JSON ao banco de dados.

  • typeHint.TIME: o valor de parâmetro de string correspondente é enviado como objeto do tipo TIME ao banco de dados. O formato aceito é HH:MM:SS[.FFF].

  • typeHint.TIMESTAMP: o valor de parâmetro de string correspondente é enviado como objeto do tipo TIMESTAMP ao banco de dados. O formato aceito é YYYY-MM-DD HH:MM:SS[.FFF].

  • typeHint.UUID: o valor de parâmetro de string correspondente é enviado como objeto do tipo UUID ao banco de dados.