Implementa le mutazioni di SQL Connect

Firebase SQL Connect ti consente di creare connettori per le istanze PostgreSQL gestite con Google Cloud SQL. Questi connettori sono combinazioni di query e mutazioni per l'utilizzo dei dati dallo schema.

La guida introduttiva ha presentato uno schema di app per le recensioni di film per PostgreSQL.

Questa guida ha anche introdotto operazioni amministrative sia implementabili sia ad hoc, incluse le mutazioni.

  • Le mutazioni implementabili sono quelle che implementi per chiamare dalle app client in un connettore, con gli endpoint API che definisci. SQL Connect integra l'autenticazione e l'autorizzazione in queste mutazioni e genera SDK client basati sulla tua API.
  • Le mutazioni amministrative ad hoc vengono eseguite da ambienti con privilegi per popolare e gestire le tabelle. Puoi crearle ed eseguirle nella Firebase console, da ambienti con privilegi utilizzando la Firebase Admin SDK, e in ambienti di sviluppo locali utilizzando la nostra estensione SQL Connect VS Code.

Questa guida esamina più da vicino le mutazioni implementabili.

Funzionalità delle SQL Connect mutazioni

SQL Connect ti consente di eseguire mutazioni di base in tutti i modi che ti aspetteresti da un database PostgreSQL:

  • Eseguire operazioni CRUD
  • Gestire operazioni multi-step con le transazioni

Tuttavia, con le estensioni di SQL Connect a GraphQL, puoi implementare mutazioni avanzate per app più veloci ed efficienti:

  • Utilizza gli scalari chiave restituiti da molte operazioni per semplificare le operazioni ripetute sui record
  • Utilizza i valori del server per popolare i dati con le operazioni fornite dal server
  • Esegui query nel corso di operazioni di mutazione multi-step per cercare i dati, risparmiando righe di codice e round trip al server.

Utilizzare i campi generati per implementare le mutazioni

Le operazioni SQL Connect estenderanno un insieme di campi generati automaticamente da SQL Connect in base ai tipi e alle relazioni tra i tipi nello schema. Questi campi vengono generati dagli strumenti locali ogni volta che modifichi lo schema.

Puoi utilizzare i campi generati per implementare le mutazioni, dalla creazione, all'aggiornamento e all'eliminazione di singoli record in singole tabelle, fino ad aggiornamenti più complessi di più tabelle.

Supponiamo che lo schema contenga un tipo Movie e un tipo Actor associato. SQL Connect genera i campi movie_insert, movie_update, movie_delete e altri ancora.

Mutazione con il
movie_insert campo

Il campo movie_insert rappresenta una mutazione per creare un singolo record nella tabella Movie.

Utilizza questo campo per creare un singolo film.

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

Mutazione con il
movie_update campo

Il campo movie_update rappresenta una mutazione per aggiornare un singolo record nella tabella Movie.

Utilizza questo campo per aggiornare un singolo film in base alla sua chiave.

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

Mutazione con il
movie_delete campo

Il campo movie_delete rappresenta una mutazione per eliminare un singolo record nella tabella Movie.

Utilizza questo campo per eliminare un singolo film in base alla sua chiave.

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

Elementi essenziali di una mutazione

Le mutazioni SQL Connect sono mutazioni GraphQL con SQL Connect estensioni. Come per una normale mutazione GraphQL, puoi definire un nome di operazione e un elenco di variabili GraphQL.

SQL Connect estende le query GraphQL con istruzioni personalizzate come @auth e @transaction.

Pertanto, la seguente mutazione ha:

  • Una definizione del tipo mutation
  • Un nome di operazione (mutazione) SignUp
  • Un singolo argomento di operazione $username
  • Una singola istruzione, @auth
  • Un singolo campo user_insert.
mutation SignUp($username: String!) @auth(level: USER) {
  user_insert(data: {
    id_expr: "auth.uid"
    username: $username
  })
}

Ogni argomento di mutazione richiede una dichiarazione di tipo, un tipo integrato come String o un tipo personalizzato definito dallo schema come Movie.

Scrivere mutazioni di base

Puoi iniziare a scrivere mutazioni per creare, aggiornare ed eliminare singoli record dal database.

Creare

Eseguiamo le creazioni di base.

# 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
  })
}

Oppure un upsert.

# 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"
  })
}

Eseguire aggiornamenti

Ecco gli aggiornamenti. I produttori e i registi sperano sicuramente che queste valutazioni medie siano in linea con le tendenze.

Il campo movie_update contiene un argomento id previsto per identificare un record e un campo data che puoi utilizzare per impostare i valori in questo aggiornamento.

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

Per eseguire più aggiornamenti, utilizza il 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
      })
}

Utilizzare le operazioni di incremento, decremento, aggiunta e anteposizione con _update

Nelle mutazioni _update e _updateMany puoi impostare in modo esplicito i valori in data:, ma spesso è più logico applicare un operatore come increment per aggiornare i valori.

Per modificare l'esempio di aggiornamento precedente, supponiamo che tu voglia incrementare la valutazione di un determinato film. Puoi utilizzare la sintassi rating_update con l'operatore inc.

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

SQL Connect supporta i seguenti operatori per gli aggiornamenti dei campi:

  • inc per incrementare i tipi di dati Int, Int64, Float, Date e Timestamp
  • dec per decrementare i tipi di dati Int, Int64, Float, Date e Timestamp

Per gli elenchi, puoi anche eseguire l'aggiornamento con singoli valori o elenchi di valori utilizzando:

  • add per aggiungere elementi se non sono già presenti nei tipi di elenco, ad eccezione degli elenchi di vettori
  • remove per rimuovere tutti gli elementi, se presenti, dai tipi di elenco, ad eccezione degli elenchi di vettori
  • append per aggiungere elementi ai tipi di elenco, ad eccezione degli elenchi di vettori
  • prepend per anteporre elementi ai tipi di elenco, ad eccezione degli elenchi di vettori

Eseguire eliminazioni

Naturalmente, puoi eliminare i dati dei film. I conservatori di film vorranno sicuramente che i film fisici vengano mantenuti il più a lungo possibile.

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

Qui puoi utilizzare _deleteMany.

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

Scrivere mutazioni sulle relazioni

Osserva come utilizzare la mutazione _upsert implicita su una relazione.

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

Progettare schemi per mutazioni efficienti

SQL Connect fornisce due funzionalità importanti che ti consentono di scrivere mutazioni più efficienti e di risparmiare operazioni di round trip.

Gli scalari chiave sono identificatori di oggetti concisi che SQL Connect assembla automaticamente dai campi chiave negli schemi. Gli scalari chiave sono incentrati sull'efficienza e ti consentono di trovare in una singola chiamata informazioni sull'identità e sulla struttura dei dati. Sono particolarmente utili quando vuoi eseguire azioni sequenziali su nuovi record e hai bisogno di un identificatore univoco da passare alle operazioni imminenti, nonché quando vuoi accedere alle chiavi relazionali per eseguire operazioni aggiuntive più complesse.

Utilizzando i valori del server, puoi consentire al server di popolare dinamicamente i campi nelle tabelle utilizzando valori archiviati o facilmente calcolabili in base a espressioni CEL lato server specifiche nell'argesso expr. Ad esempio, puoi definire un campo con un timestamp applicato quando si accede al campo utilizzando l'ora memorizzata in una richiesta di operazione, updatedAt: Timestamp! @default(expr: "request.time").

Scrivere mutazioni avanzate: consentire a SQL Connect di fornire valori utilizzando la sintassi field_expr

Come discusso in Scalari chiave e valori del server, puoi progettare lo schema in modo che il server popoli i valori per i campi comuni come ids e date in risposta alle richieste del client.

Inoltre, puoi utilizzare i dati, come gli ID utente, inviati in SQL Connect request oggetti dalle app client.

Quando implementi le mutazioni, utilizza la sintassi field_expr per attivare gli aggiornamenti generati dal server o accedere ai dati dalle richieste. Ad esempio, per passare l'autorizzazione uid memorizzata in una richiesta a un'operazione _upsert, passa "auth.uid" nel 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 })
}

In alternativa, in un'app di elenco di attività da svolgere, quando crei un nuovo elenco, puoi passare id_expr per indicare al server di generare automaticamente un UUID per l'elenco.

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,
  })
}

Per ulteriori informazioni, consulta gli scalari _Expr nel riferimento agli scalari.

Scrivere mutazioni avanzate: operazioni multi-step

Esistono molte situazioni in cui potresti voler includere più campi di scrittura (come gli inserimenti) in una mutazione. Potresti anche voler leggere il database durante l'esecuzione di una mutazione per cercare e verificare i dati esistenti prima di eseguire, ad esempio, inserimenti o aggiornamenti. Queste opzioni consentono di risparmiare operazioni di andata e ritorno e quindi costi.

SQL Connect ti consente di eseguire la logica multi-step nelle mutazioni supportando:

  • Più campi di scrittura

  • Più campi di lettura nelle mutazioni (utilizzando la parola chiave del campo query).

  • L'istruzione @transaction direttiva, che fornisce il supporto per le transazioni familiare dai database relazionali.

  • L'istruzione @check direttiva, che ti consente di valutare i contenuti delle letture utilizzando le espressioni CEL e, in base ai risultati di questa valutazione:

    • Procedi con le creazioni, gli aggiornamenti e le eliminazioni definiti da una mutazione
    • Procedi per restituire i risultati di un campo query
    • Utilizza i messaggi restituiti per eseguire la logica appropriata nel codice client
  • L'istruzione @redactdirettiva, che ti consente di omettere i risultati del campo query dai risultati del protocollo di rete.

  • Il binding response CEL, che memorizza i risultati accumulati di tutte le mutazioni e le query eseguite in un'operazione complessa multi-step. Puoi accedere al binding response:

    • Nelle istruzioni @check, tramite l'argomento expr:
    • Con i valori del server, utilizzando la sintassi field_expr

L'istruzione @transaction

Il supporto per le mutazioni multi-step include la gestione degli errori tramite le transazioni.

L'istruzione @transaction impone che una mutazione, con un singolo campo di scrittura (ad esempio _insert o _update) o con più campi di scrittura, venga sempre eseguita in una transazione di database.

  • Le mutazioni senza @transaction eseguono ogni campo radice uno dopo l'altro in sequenza. L'operazione mostra eventuali errori come errori di campo parziali, ma non gli impatti delle esecuzioni successive.

  • Le mutazioni con @transaction hanno la garanzia di riuscire completamente o di non riuscire completamente. Se uno dei campi all'interno della transazione non riesce, viene eseguito il rollback dell'intera transazione.

Le istruzioni @check e @redact

L'istruzione @check verifica che i campi specificati siano presenti nei risultati della query. Per testare i valori dei campi viene utilizzata un'espressione Common Expression Language (CEL). Il comportamento predefinito dell'istruzione è controllare e rifiutare i nodi il cui valore è null o [] (elenchi vuoti).

L'istruzione @redact redige una parte della risposta dal client. I campi redatti vengono comunque valutati per gli effetti collaterali (incluse le modifiche dei dati e @check) e i risultati sono comunque disponibili per i passaggi successivi nelle espressioni CEL.

Utilizzare @check, @check(message:) e @redact

Un utilizzo importante di @check e @redact è la ricerca di dati correlati per decidere se determinate operazioni devono essere autorizzate, utilizzando la ricerca nella logica ma nascondendola ai client. La query può restituire messaggi utili per la gestione corretta nel codice client.

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
    }
  }
}

Per saperne di più sulle istruzioni @check e @redact nei controlli di autorizzazione, consulta la discussione sulla ricerca dei dati di autorizzazione.

Utilizzare @check per convalidare le chiavi

Alcuni campi di mutazione, come _update, potrebbero non eseguire alcuna operazione se non esiste un record con una chiave specificata. Allo stesso modo, le ricerche potrebbero restituire null o un elenco vuoto. Questi non sono considerati errori e pertanto non attiveranno i rollback.

Per evitare questo risultato, verifica se è possibile trovare le chiavi utilizzando l'istruzione @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")
}

Utilizzare il binding response per concatenare le mutazioni multi-step

L'approccio di base per la creazione di record correlati, ad esempio una nuova Movie e una voce MovieMetadata associata, è:

  1. Chiama una mutazione _insert per Movie
  2. Memorizza la chiave restituita del film creato
  3. Quindi, chiama una seconda mutazione _insert per creare il record MovieMetadata.

Tuttavia, con SQL Connect, puoi gestire questo caso comune in una singola operazione multi-step accedendo ai risultati del primo _insert nel secondo _insert.

Creare un'app per le recensioni di film di successo è un lavoro impegnativo. Monitoriamo il nostro elenco di attività da svolgere con un nuovo esempio.

Utilizzare response per impostare i campi con i valori del server

Nella seguente mutazione dell'elenco di attività da svolgere:

  • Il binding response rappresenta l'oggetto di risposta parziale finora, che include tutti i campi di mutazione di primo livello prima di quello corrente.
  • I risultati dell'operazione todoList_insert iniziale, che restituisce il id (chiave), vengono accessibili in un secondo momento in response.todoList_insert.id, in modo da poter inserire immediatamente una nuova attività da svolgere.
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,
  })
}

Utilizzare response per convalidare i campi utilizzando @check

response è disponibile anche in @check(expr: "..."), quindi puoi utilizzarlo per creare una logica lato server ancora più complessa. In combinazione con i passaggi query { … } nelle mutazioni, puoi ottenere molto di più senza ulteriori round trip client-server.

Nell'esempio seguente, tieni presente che @check ha già accesso a response.query perché un @check viene sempre eseguito dopo il passaggio a cui è collegato.

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,
  })
}

Per ulteriori informazioni sul binding response, consulta il riferimento CEL.

Comprendere le operazioni interrotte con @transaction e query @check

Le mutazioni multi-step possono riscontrare errori:

  • Le operazioni del database potrebbero non riuscire.
  • La logica `query @check` potrebbe terminare le operazioni.

SQL Connect consiglia di utilizzare l'istruzione @transaction con le mutazioni multi-step. In questo modo, si ottengono risultati di database e mutazioni più coerenti e più facili da gestire nel codice client:

  • Al primo errore o @check non riuscito, l'operazione terminerà, quindi non è necessario gestire l'esecuzione di eventuali campi successivi o la valutazione di CEL.
  • I rollback vengono eseguiti in risposta agli errori del database o alla logica @check, producendo uno stato del database coerente.
  • Al codice client viene sempre restituito un errore di rollback.

Potrebbero esistere alcuni casi d'uso in cui scegli di non utilizzare @transaction: potresti optare per la coerenza finale se, ad esempio, hai bisogno di una velocità effettiva, una scalabilità o una disponibilità maggiori. Tuttavia, devi gestire il database e il codice client per consentire i risultati:

  • Se un campo non riesce a causa delle operazioni del database, i campi successivi continueranno a essere eseguiti. Tuttavia, i @check non riusciti terminano comunque l'intera operazione.
  • Non vengono eseguiti rollback, il che significa uno stato del database misto con alcuni aggiornamenti riusciti e alcuni non riusciti.
  • Le operazioni con @check potrebbero fornire risultati più incoerenti se la logica @check utilizza i risultati di letture e/o scritture in un passaggio precedente.
  • Il risultato restituito al codice client conterrà un mix più complesso di risposte di successo e di errore da gestire.

Istruzioni per le mutazioni SQL Connect

Oltre alle istruzioni utilizzate per definire tipi e tabelle, SQL Connect fornisce le @auth, @check, @redact e @transaction istruzioni per aumentare il comportamento delle operazioni.

Istruzione Applicabile a Descrizione
@auth Query e mutazioni Definisce la policy di autorizzazione per una query o una mutazione. Consulta la guida all'autorizzazione e all'attestazione.
@check Campi query nelle operazioni multi-step Verifica che i campi specificati siano presenti nei risultati della query. Per testare i valori dei campi viene utilizzata un'espressione Common Expression Language (CEL). Consulta Operazioni multi-step.
@redact Query Redige una parte della risposta dal client. Consulta Operazioni multi-step.
@transaction Mutazioni Impone che una mutazione venga sempre eseguita in una transazione di database. Consulta Operazioni multi-step.

Passaggi successivi

Potrebbe interessarti: