Implementa mutaciones de SQL Connect

Firebase SQL Connect te permite crear conectores para tus instancias de PostgreSQL administradas con Google Cloud SQL. Estos conectores son combinaciones de consultas y mutaciones para usar tus datos desde tu esquema.

En la guía de introducción, se presentó un esquema de app de opinión sobre películas para PostgreSQL.

En esa guía, también se presentaron operaciones administrativas implementables y ad hoc, incluidas las mutaciones.

  • Las mutaciones implementables son las que implementas para llamar desde apps cliente en un conector, con extremos de API que defines. SQL Connect integra la autenticación y la autorización en estas mutaciones, y genera SDKs de cliente basados en tu API.
  • Las mutaciones administrativas ad hoc se ejecutan desde entornos con privilegios para propagar y administrar tablas. Puedes crearlas y ejecutarlas en la Firebase consola, desde entornos con privilegios con la Firebase Admin SDK, y en entornos de desarrollo locales con nuestra extensión de SQL Connect para VS Code.

En esta guía, se analiza en detalle las mutaciones implementables.

Funciones de las mutaciones de SQL Connect

SQL Connect te permite realizar mutaciones básicas de todas las formas que esperarías dada una base de datos de PostgreSQL:

  • Realiza operaciones CRUD.
  • Administra operaciones de varios pasos con transacciones.

Sin embargo, con las extensiones de SQL Connect a GraphQL, puedes implementar mutaciones avanzadas para apps más rápidas y eficientes:

  • Usa escalares clave que muestran muchas operaciones para simplificar las operaciones repetidas en los registros.
  • Usa valores del servidor para propagar datos con operaciones proporcionadas por el servidor.
  • Realiza consultas en el transcurso de operaciones de mutación de varios pasos para buscar datos, guardar líneas de código y viajes de ida y vuelta al servidor.

Usa campos generados para implementar mutaciones

Tus SQL Connect operaciones extenderán un conjunto de campos generados automáticamente por SQL Connect en función de los tipos y las relaciones de tipos en tu esquema. Estas herramientas locales generan estos campos cada vez que editas tu esquema.

Puedes usar campos generados para implementar mutaciones, desde crear, actualizar y borrar registros individuales en tablas únicas hasta actualizaciones más complejas de varias tablas.

Supongamos que tu esquema contiene un tipo Movie y un tipo Actor asociado. SQL Connect genera campos movie_insert, movie_update, movie_delete y mucho más.

Mutación con el
movie_insert campo

El campo movie_insert representa una mutación para crear un solo registro en la tabla Movie.

Usa este campo para crear una sola película.

mutation CreateMovie($data: Movie_Data!) {
  movie_insert(data: $data) { key }
}

Mutación con el
movie_update campo

El campo movie_update representa una mutación para actualizar un solo registro en la tabla Movie.

Usa este campo para actualizar una sola película por su clave.

mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) {
  movie_update(key: $myKey, data: $data) { key }
}

Mutación con el
movie_delete campo

El campo movie_delete representa una mutación para borrar un solo registro en la tabla Movie.

Usa este campo para borrar una sola película por su clave.

  mutation DeleteMovie($myKey: Movie_Key!) {
    movie_delete(key: $myKey) { key }
  }

Elementos esenciales de una mutación

SQL Connect mutaciones son mutaciones de GraphQL con SQL Connect extensiones. Al igual que con una mutación de GraphQL normal, puedes definir un nombre de operación y una lista de variables de GraphQL.

SQL Connect extiende las consultas de GraphQL con directivas personalizadas como @auth y @transaction.

Por lo tanto, la siguiente mutación tiene lo siguiente:

  • Una definición de tipo mutation
  • Un nombre de operación (mutación) SignUp
  • Un solo argumento de operación $username
  • Una sola directiva, @auth
  • Un solo campo user_insert
mutation SignUp($username: String!) @auth(level: USER) {
  user_insert(data: {
    id_expr: "auth.uid"
    username: $username
  })
}

Cada argumento de mutación requiere una declaración de tipo, un tipo integrado como String o un tipo personalizado definido por el esquema como Movie.

Escribe mutaciones básicas

Puedes comenzar a escribir mutaciones para crear, actualizar y borrar registros individuales de tu base de datos.

Crear

Hagamos creaciones básicas.

# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
  movie_insert(data: {
    title: $title
    releaseYear: $releaseYear
    genre: $genre
    rating: $rating
  })
}

# Create a movie with default values
mutation CreateMovie2 {
  movie_insert(data: {
    title: "Sherlock Holmes"
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
  })
}

O una inserción o actualización.

# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
  movie_upsert(data: {
    title: $title
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
    genre: "Mystery/Thriller"
  })
}

Realiza actualizaciones

Aquí tienes actualizaciones. Los productores y directores seguramente esperan que esas calificaciones promedio estén en tendencia.

El campo movie_update contiene un argumento id esperado para identificar un registro y un campo data que puedes usar para establecer valores en esta actualización.

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id,
    data: {
      genre: $genre
      rating: $rating
      description: $description
    })
}

Para realizar varias actualizaciones, usa el campo movie_updateMany.

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    data:
      {
        rating: $rating
      })
}

Usa operaciones de incremento, decremento, anexión y anteposición con _update

Si bien en las mutaciones _update y _updateMany puedes establecer valores de forma explícita en data:, a menudo es más conveniente aplicar un operador como el incremento para actualizar valores.

Para modificar el ejemplo de actualización anterior, supongamos que deseas aumentar la calificación de una película en particular. Puedes usar la sintaxis rating_update con el operador inc.

mutation UpdateMovie(
  $id: UUID!,
  $ratingIncrement: Int!
) {
  movie_update(id: $id, data: {
    rating_update: {
      inc: $ratingIncrement
    }
  })
}

SQL Connect admite los siguientes operadores para las actualizaciones de campos:

  • inc para aumentar los tipos de datos Int, Int64, Float, Date y Timestamp
  • dec para disminuir los tipos de datos Int, Int64, Float, Date y Timestamp

Para las listas, también puedes actualizar con valores individuales o listas de valores con lo siguiente:

  • add para anexar elementos si aún no están presentes en los tipos de lista, excepto las listas de vectores
  • remove para quitar todos los elementos, si están presentes, de los tipos de lista, excepto las listas de vectores
  • append para anexar elementos a los tipos de lista, excepto las listas de vectores
  • prepend para anteponer elementos a los tipos de lista, excepto las listas de vectores

Realiza eliminaciones

Por supuesto, puedes borrar datos de películas. Los conservacionistas de películas seguramente querrán que las películas físicas se mantengan durante el mayor tiempo posible.

# Delete by key
mutation DeleteMovie($id: UUID!) {
  movie_delete(id: $id)
}

Aquí puedes usar _deleteMany.

# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
  movie_deleteMany(where: { rating: { le: $minRating } })
}

Escribe mutaciones en relaciones

Observa cómo usar la mutación _upsert implícita en una relación.

# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
  movieMetadata_upsert(
    data: { movie: { id: $movieId }, director: $director }
  )
}

Diseña esquemas para mutaciones eficientes

SQL Connect proporciona dos funciones importantes que te permiten escribir mutaciones más eficientes y guardar operaciones de ida y vuelta.

Los escalares clave son identificadores de objetos concisos que SQL Connect ensambla automáticamente a partir de campos clave en tus esquemas. Los escalares clave son sobre la eficiencia, lo que te permite encontrar en una sola llamada información sobre la identidad y la estructura de tus datos. Son especialmente útiles cuando deseas realizar acciones secuenciales en registros nuevos y necesitas un identificador único para pasar a las próximas operaciones, y también cuando deseas acceder a claves relacionales para realizar operaciones adicionales más complejas.

Con los valores del servidor, puedes permitir que el servidor propague de forma eficaz los campos de tus tablas de forma dinámica con valores almacenados o fácilmente computables según expresiones CEL particulares del servidor en el argumento expr. Por ejemplo, puedes definir un campo con una marca de tiempo aplicada cuando se accede al campo con la hora almacenada en una solicitud de operación, updatedAt: Timestamp! @default(expr: "request.time").

Escribe mutaciones avanzadas: permite que SQL Connect proporcione valores con la sintaxis field_expr

Como se explicó en los escalares clave y los valores del servidor, puedes diseñar tu esquema de modo que el servidor propague valores para campos comunes como ids y fechas en respuesta a las solicitudes del cliente.

Además, puedes usar datos, como IDs de usuario, que se envían en SQL Connect request objetos desde apps cliente.

Cuando implementes mutaciones, usa la sintaxis field_expr para activar actualizaciones generadas por el servidor o acceder a datos de solicitudes. Por ejemplo, para pasar la autorización uid almacenada en una solicitud a una operación _upsert, pasa "auth.uid" en el campo userId_expr.

# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

O bien, en una app de lista de tareas pendientes familiar, cuando crees una nueva lista de tareas pendientes, puedes pasar id_expr para indicarle al servidor que genere automáticamente un UUID para la lista.

mutation CreateTodoListWithFirstItem(
  $listName: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
}

Para obtener más información, consulta los _Expr escalares en la referencia de escalares.

Escribe mutaciones avanzadas: operaciones de varios pasos

Hay muchas situaciones en las que es posible que desees incluir varios campos de escritura (como inserciones) en una mutación. Es posible que también desees leer tu base de datos durante la ejecución de una mutación para buscar y verificar los datos existentes antes de realizar, por ejemplo, inserciones o actualizaciones. Estas opciones guardan operaciones de ida y vuelta y, por lo tanto, costos.

SQL Connect te permite realizar lógica de varios pasos en tus mutaciones, ya que admite lo siguiente:

  • Varios campos de escritura

  • Varios campos de lectura en tus mutaciones (con la palabra clave del campo query)

  • La directiva @transaction, que proporciona compatibilidad con transacciones familiares de bases de datos relacionales

  • La directiva @check, que te permite evaluar el contenido de las lecturas con expresiones CEL y, según los resultados de dicha evaluación, hacer lo siguiente:

    • Continuar con las creaciones, actualizaciones y eliminaciones definidas por una mutación
    • Continuar para mostrar los resultados de un campo de consulta
    • Usar mensajes devueltos para realizar la lógica adecuada en tu código de cliente
  • La directiva @redact, que te permite omitir los resultados del campo de consulta de los resultados del protocolo de transmisión

  • La vinculación response de CEL, que almacena los resultados acumulados de todas las mutaciones y consultas realizadas en una operación compleja de varios pasos Puedes acceder a la vinculación response de la siguiente manera:

    • En las directivas @check, a través del argumento expr:
    • Con valores del servidor, con la sintaxis field_expr

La directiva @transaction

La compatibilidad con mutaciones de varios pasos incluye el manejo de errores con transacciones.

La directiva @transaction exige que una mutación, ya sea con un solo campo de escritura (por ejemplo, _insert o _update) o con varios campos de escritura, siempre se ejecute en una transacción de base de datos.

  • Las mutaciones sin @transaction ejecutan cada campo raíz uno tras otro en secuencia. La operación muestra los errores como errores de campo parciales, pero no los impactos de las ejecuciones posteriores.

  • Se garantiza que las mutaciones con @transaction se completarán o fallarán por completo. Si falla alguno de los campos dentro de la transacción, se revierte toda la transacción.

Las directivas @check y @redact

La directiva @check verifica que los campos especificados estén presentes en los resultados de la consulta. Se usa una expresión de Common Expression Language (CEL) para probar los valores de los campos. El comportamiento predeterminado de la directiva es verificar y rechazar los nodos cuyo valor es null o [] (listas vacías).

La directiva @redact redacta una parte de la respuesta del cliente. Los campos redactados aún se evalúan para detectar efectos secundarios (incluidos los cambios de datos y @check), y los resultados aún están disponibles para los pasos posteriores en las expresiones CEL.

Usa @check, @check(message:) y @redact

Un uso importante de @check y @redact es buscar datos relacionados para decidir si se deben autorizar ciertas operaciones, usar la búsqueda en la lógica, pero ocultarla a los clientes. Tu consulta puede mostrar mensajes útiles para el manejo correcto en el código de cliente.

query GetMovieEditors($movieId: UUID!) @auth(level: USER) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

Para obtener más información sobre las directivas @check y @redact en las verificaciones de autorización, consulta el análisis de la búsqueda de datos de autorización.

Usa @check para validar claves

Es posible que algunos campos de mutación, como _update, no funcionen si no existe un registro con una clave especificada. Del mismo modo, las búsquedas pueden mostrar valores nulos o una lista vacía. Estos no se consideran errores y, por lo tanto, no activarán reversiones.

Para evitar este resultado, prueba si se pueden encontrar claves con la directiva @check.

# Delete by key, error if not found
mutation MustDeleteMovie($id: UUID!) @transaction {
  movie_delete(id: $id) @check(expr: "this != null", message: "Movie not found, therefore nothing is deleted")
}

Usa la vinculación response para encadenar mutaciones de varios pasos

El enfoque básico para crear registros relacionados, por ejemplo, un Movie nuevo y una entrada MovieMetadata asociada, es el siguiente:

  1. Llama a una mutación _insert para Movie.
  2. Almacena la clave devuelta de la película creada.
  3. Luego, llama a una segunda mutación _insert para crear el registro MovieMetadata.

Sin embargo, con SQL Connect, puedes controlar este caso común en una sola operación de varios pasos si accedes a los resultados de la primera _insert en la segunda _insert.

Crear una app de opinión sobre películas exitosa requiere mucho trabajo. Hagamos un seguimiento de nuestra lista de tareas pendientes con un ejemplo nuevo.

Usa response para establecer campos con valores del servidor

En la siguiente mutación de lista de tareas pendientes:

  • La vinculación response representa el objeto de respuesta parcial hasta el momento, que incluye todos los campos de mutación de nivel superior antes del actual.
  • Se accede más tarde a los resultados de la operación inicial todoList_insert, que muestra el id (clave), en response.todoList_insert.id para que podamos insertar de inmediato un nuevo elemento de tarea pendiente.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1:
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

Usa response para validar campos con @check

response también está disponible en @check(expr: "..."), por lo que puedes usarlo para compilar una lógica del servidor aún más complicada. En combinación con los pasos query { … } en las mutaciones, puedes lograr mucho más sin ningún viaje de ida y vuelta adicional entre el cliente y el servidor.

En el siguiente ejemplo, ten en cuenta que @check ya tiene acceso a response.query porque un @check siempre se ejecuta después del paso al que está conectado.

mutation CreateTodoInNamedList(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1: Look up List.id by its name
  query
  @check(expr: "response.query.todoLists.size() > 0", message: "No such TodoList with the name!")
  @check(expr: "response.query.todoLists.size() < 2", message: "Ambiguous listName!") {
    todoLists(where: { name: $listName }) {
      id
    }
  }
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoLists[0].id" # <-- Now we have the parent list ID to insert to
    content: $itemContent,
  })
}

Para obtener más información sobre la vinculación response, consulta la referencia de CEL.

Comprende las operaciones interrumpidas con @transaction y query @check

Las mutaciones de varios pasos pueden generar errores:

  • Es posible que fallen las operaciones de la base de datos.
  • Es posible que la lógica de query @check finalice las operaciones.

SQL Connect recomienda que uses la directiva @transaction con tus mutaciones de varios pasos. Esto da como resultado una base de datos más coherente y resultados de mutación que son más fáciles de controlar en el código de cliente:

  • En el primer error o @check fallido, la operación finalizará, por lo que no es necesario administrar la ejecución de ningún campo posterior ni la evaluación de CEL.
  • Las reversiones se realizan en respuesta a errores de la base de datos o a la lógica de @check, lo que genera un estado coherente de la base de datos.
  • Siempre se muestra un error de reversión al código de cliente.

Es posible que haya algunos casos de uso en los que decidas no usar @transaction: puedes optar por la coherencia eventual si, por ejemplo, necesitas una mayor capacidad de procesamiento, escalabilidad o disponibilidad. Sin embargo, debes administrar tu base de datos y tu código de cliente para permitir los resultados:

  • Si un campo falla debido a las operaciones de la base de datos, los campos posteriores seguirán ejecutándose. Sin embargo, los @check fallidos aún finalizan toda la operación.
  • No se realizan reversiones, lo que significa un estado de base de datos mixto con algunas actualizaciones exitosas y otras fallidas.
  • Tus operaciones con @check pueden dar resultados más incoherentes si tu lógica @check usa los resultados de lecturas o escrituras en un paso anterior.
  • El resultado que se muestra al código de cliente contendrá una combinación más compleja de respuestas de éxito y falla que se deben controlar.

Directivas para mutaciones SQL Connect

Además de las directivas que usas para definir tipos y tablas, SQL Connect proporciona las directivas @auth, @check, @redact y @transaction para aumentar el comportamiento de las operaciones.

Directiva Se aplica a Descripción
@auth Consultas y mutaciones Define la política de autorización para una consulta o mutación. Consulta la guía de autorización y certificación.
@check Campos query en operaciones de varios pasos Verifica que los campos especificados estén presentes en los resultados de la consulta. Se usa una expresión de Common Expression Language (CEL) para probar los valores de los campos. Consulta Operaciones de varios pasos.
@redact Consultas Redacta una parte de la respuesta del cliente. Consulta Operaciones de varios pasos.
@transaction Mutaciones Exige que una mutación siempre se ejecute en una transacción de base de datos. Consulta Operaciones de varios pasos.

Próximos pasos

Tal vez te interese lo siguiente: