Schemi, query e mutazioni di Data Connect

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

La guida introduttiva ha introdotto uno schema dell'app di recensione di film per PostgreSQL e questa guida esamina più da vicino come progettare schemi Data Connect per PostgreSQL.

Questa guida abbina query e mutazioni Data Connect a esempi di schema. Perché parlare di query (e di mutazioni) in una guida sugli schemiData Connect? Come altre piattaforme basate su GraphQL, Firebase Data Connect è una piattaforma di sviluppo incentrata sulle query, pertanto, come sviluppatore, nella definizione del modello di dati dovrai tenere conto dei dati di cui i tuoi clienti hanno bisogno, che influenzeranno notevolmente lo schema di dati che sviluppi per il tuo progetto.

Questa guida inizia con un nuovo schema per le recensioni di film, poi illustra le query e le mutazioni derivate da questo schema e infine fornisce un elenco SQL equivalente allo schema Data Connect di base.

Lo schema di un'app di recensioni di film

Immagina di voler creare un servizio che consenta agli utenti di inviare e visualizzare recensioni di film.

Per un'app di questo tipo è necessario uno schema iniziale, che verrà esteso in un secondo momento per creare query relazionali complesse.

Tabella Film

Lo schema per i film contiene direttive di base come:

  • @table(name) e @col(name) per personalizzare i nomi delle tabelle e delle colonne SQL. Se non specificato, Data Connect genera nomi in snake_case.
  • @col(dataType) per personalizzare i tipi di colonne SQL.
  • @default per configurare i valori predefiniti delle colonne SQL durante l'inserimento.

Per ulteriori dettagli, consulta la documentazione di riferimento per @table, @col, @default.

# Movies
type Movie @table(name: "movie", key: "id") {
  id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
  title: String!
  releaseYear: Int
  genre: String @col(dataType: "varchar(20)")
  rating: Int
  description: String
}

Valori scalari chiave e del server

Prima di esaminare ulteriormente l'app di recensione di film, introduciamo Data Connect scalari chiave e valori del server.

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

Utilizzando i valori del server, puoi consentire al server di compilare dinamicamente i campi delle tabelle utilizzando valori memorizzati o facilmente calcolabili in base a determinate espressioni CEL lato server nell'argomento expr. Ad esempio, puoi definire un campo con un timestamp applicato quando si accede al campo utilizzando il tempo memorizzato in una richiesta di operazione, updatedAt: Timestamp! @default(expr: "request.time").

Tabella dei metadati dei film

Ora teniamo traccia dei registi dei film e stabiliamo una relazione uno a uno con Movie.

Aggiungi il campo di riferimento per definire una relazione.

Puoi utilizzare la direttiva @ref per personalizzare il vincolo della chiave esterna.

  • @ref(fields) per specificare i campi della chiave esterna.
  • @ref(references) per specificare i campi a cui si fa riferimento nella tabella di destinazione. Per impostazione predefinita, questo riferimento è la chiave principale, ma sono supportati anche i campi con @unique.

Per ulteriori dettagli, consulta la documentazione di riferimento per @ref.

# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
  # @unique ensures that each Movie only has one MovieMetadata.
  movie: Movie! @unique
  # Since it references to another table type, it adds a foreign key constraint.
  #  movie: Movie! @unique @ref(fields: "movieId", references: "id")
  #  movieId: UUID! <- implicitly added foreign key field
  director: String
}

Actor e MovieActor

Poi vuoi che gli attori recitino nei tuoi film e, poiché hai una relazione molti-a-molti tra film e attori, crea una tabella di join.

# Actors
# Suppose an actor can participate in multiple movies and movies can have multiple actors
# Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
  id: UUID! @default(expr: "uuidV4()")
  name: String! @col(dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary keys of this table
# In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
  movie: Movie!
  # movieId: UUID! <- implicitly added foreign key field
  actor: Actor!
  # actorId: UUID! <- implicitly added foreign key field
  role: String! # "main" or "supporting"
  # optional other fields
}

Utente

Infine, gli utenti della tua app.

# Users
# Suppose a user can leave reviews for movies
type User @table {
  id: String! @default(expr: "auth.uid")
  username: String! @col(dataType: "varchar(50)")
}

Tipi di dati supportati

Data Connect supporta i seguenti tipi di dati scalari, con assegnazioni ai tipi PostgreSQL utilizzando @col(dataType:).

Tipo Data Connect Tipo integrato di GraphQL o
Data Connect tipo personalizzato
Tipo PostgreSQL predefinito Tipi PostgreSQL supportati
(alias tra parentesi)
Stringa GraphQL testo text
bit(n), varbit(n)
char(n), varchar(n)
Int GraphQL int Int2 (smallint, smallserial),
int4 (integer, int, serial)
In virgola mobile GraphQL float8 float4 (real)
float8 (precisione doppia)
numeric (decimale)
Booleano GraphQL booleano booleano
UUID Personalizzato uuid UUID
Int64 Personalizzato bigint int8 (bigint, bigserial)
numeric (decimale)
Data Personalizzato date data
Timestamp Personalizzato timestamptz

timestamptz

Nota:le informazioni sul fuso orario locale non vengono memorizzate.
PostgreSQL converte e memorizza questi timestamp come UTC.

Vettoriale Personalizzato vector

vettoriale

Consulta Eseguire ricerche di somiglianza vettoriale con Vertex AI.

  • List di GraphQL viene mappato a un array unidimensionale.
    • Ad esempio, [Int] corrisponde a int5[] e [Any] a jsonb[].
    • Data Connect non supporta gli array nidificati.

Utilizzare i campi generati per creare query e mutazioni

Le query e le mutazioni Data Connect estenderanno un insieme di campi Data Connect generati automaticamente 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.

  • Come hai scoperto nella guida Per iniziare, la console Firebase e i nostri strumenti di sviluppo locale utilizzano questi campi generati automaticamente per fornirti query e mutazioni amministrative ad hoc che puoi utilizzare per eseguire il seeding dei dati e verificare i contenuti delle tabelle.

  • Durante il processo di sviluppo, implementerai query di deployment e mutazioni di deployment raggruppate nei connettori in base a questi campi generati automaticamente.

Nomi dei campi generati automaticamente

Data Connect deducono nomi adatti per i campi generati automaticamente in base alle dichiarazioni di tipo dello schema. Ad esempio, se utilizzi un'origine PostgreSQL e definisci una tabella denominata Movie, il server genererà:

  • Campi per la lettura dei dati nei casi d'uso con una singola tabella con i nomi facili da ricordare movie (singolare, per il recupero di singoli risultati passando argomenti come eq) e movies (plurale, per il recupero di elenchi di risultati passando argomenti come gt e operazioni come orderby). Data Connect genera anche campi per operazioni relazionali multi-tabella con nomi espliciti come actors_on_movies o actors_via_actormovie.
  • Campi per la scrittura di dati con nomi familiari come movie_insert, movie_upsert

Il linguaggio di definizione dello schema ti consente inoltre di controllare esplicitamente il modo in cui vengono generati i nomi per i campi utilizzando gli argomenti delle istruzioni singular e plural.

Direttive per query e mutazioni

Oltre alle direttive utilizzate per definire tipi e tabelle, Data Connect fornisce le direttive @auth, @check, @redact e @transaction per migliorare il comportamento di query e mutazioni.

Direttiva Applicabile a Descrizione
@auth Query e mutazioni Definisce il criterio di autenticazione per una query o una mutazione. Consulta la guida all'autorizzazione e all'attestazione.
@check Query di ricerca dei dati di autorizzazione Verifica che i campi specificati siano presenti nei risultati della query. Un'espressione Common Expression Language (CEL) viene utilizzata per testare i valori dei campi. Consulta la guida all'autorizzazione e all'attestazione.
@redact Query Oscura una parte della risposta del cliente. Consulta la guida all'autorizzazione e all'attestazione.
@transaction Mutazioni Impone che una mutazione venga sempre eseguita in una transazione di database. Consulta gli esempi di mutazioni delle app di film.

Query per il database delle recensioni di film

Definisci una query Data Connect con una dichiarazione del tipo di operazione di query, il nome dell'operazione, zero o più argomenti dell'operazione e zero o più direttive con argomenti.

Nella guida rapida, la query di esempio listEmails non ha richiesto parametri. Ovviamente, in molti casi, i dati passati ai campi di query saranno dinamici. Puoi utilizzare la sintassi $variableName per lavorare con le variabili come uno dei componenti di una definizione di query.

Pertanto, la seguente query ha:

  • Una definizione di tipo query
  • Un nome di operazione (query) ListMoviesByGenre
  • Un argomento dell'operazione $genre con una singola variabile
  • Una singola direttiva, @auth.
query ListMoviesByGenre($genre: String!) @auth(level: USER)

Ogni argomento della query richiede una dichiarazione di tipo, un valore predefinito come String o un tipo personalizzato definito dallo schema come Movie.

Vediamo la firma di query sempre più complesse. Al termine, introdurrai espressioni di relazioni efficaci e concise che puoi utilizzare per creare le tue query di deployment.

Scalari chiave nelle query

Ma prima, una nota sugli scalari principali.

Data Connect definisce un tipo speciale per gli scalari chiave, identificati da _Key. Ad esempio, il tipo di una chiave scalare per la nostra tabella Movie è Movie_Key.

Puoi recuperare gli scalari chiave come risposta restituita dalla maggior parte dei campi di lettura generati automaticamente o, naturalmente, dalle query in cui hai recuperato tutti i campi necessari per creare la chiave scalare.

Le query automatiche singolari, come movie nel nostro esempio in esecuzione, supportano un argomento chiave che accetta una chiave scalare.

Puoi passare una chiave scalare come valore letterale. Tuttavia, puoi definire variabili per passare scalari chiave come input.

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}

Questi possono essere forniti nel JSON della richiesta come questo (o in altri formati di serializzazione):

{
  # 
  "variables": {
    "myKey": {"foo": "some-string-value", "bar": 42}
  }
}

Grazie alla sintassi scalare personalizzata, un Movie_Key può essere costruito anche utilizzando la sintassi dell'oggetto, che può contenere variabili. Questa operazione è utile soprattutto se per qualche motivo vuoi suddividere i singoli componenti in variabili diverse.

Alias nelle query

Data Connect supporta l'aliasing GraphQL nelle query. Con gli alias, puoi rinominare i dati restituiti nei risultati di una query. Una singola query Data Connect può applicare più filtri o altre operazioni di query in una richiesta efficiente al server, emettendo effettivamente più "sottoquery" contemporaneamente. Per evitare conflitti di nomi nel set di dati restituito, utilizza gli alias per distinguere le sottoquery.

Ecco una query in cui un'espressione utilizza l'alias mostPopular.

query ReviewTopPopularity($genre: String) {
  mostPopular: review(first: {
    where: {genre: {eq: $genre}},
    orderBy: {popularity: DESC}
  }) {  }
}

Query semplici con filtri

Le query Data Connect si mappano a tutti i filtri e le operazioni di ordinamento SQL comuni.

Operatori where e orderBy (query singolari e plurali)

Restituisce tutte le righe corrispondenti della tabella (e le associazioni nidificate). Restituisce un array vuoto se nessun record corrisponde al filtro.

query MovieByTopRating($genre: String) {
  mostPopular: movies(
     where: { genre: { eq: $genre } }, orderBy: { rating: DESC }
  ) {
    # graphql: list the fields from the results to return
    id
    title
    genre
    description
  }
}

query MoviesByReleaseYear($min: Int, $max: Int) {
  movies(where: {releaseYear: {le: $max, ge: $min}}, orderBy: [{releaseYear: ASC}]) {  }
}

Operatori limit e offset (query singolari e plurali)

Puoi eseguire l'impaginazione dei risultati. Questi argomenti sono accettati, ma non vengono riportati nei risultati.

query MoviesTop10 {
  movies(orderBy: [{ rating: DESC }], limit: 10) {
    # graphql: list the fields from the results to return
    title
  }
}

include per i campi di array

Puoi verificare che un campo array includa un elemento specificato.

# Filter using arrays and embedded fields.
query ListMoviesByTag($tag: String!) {
  movies(where: { tags: { includes: $tag }}) {
    # graphql: list the fields from the results to return
    id
    title
  }
}

Operazioni sulle stringhe ed espressioni regolari

Le query possono utilizzare le normali operazioni di ricerca e confronto di stringhe, incluse le espressioni regolari. Tieni presente che, per motivi di efficienza, stai raggruppando diverse operazioni qui e le stai disambiguando con gli alias.

query MoviesTitleSearch($prefix: String, $suffix: String, $contained: String, $regex: String) {
  prefixed: movies(where: {title: {startsWith: $prefix}}) {...}
  suffixed: movies(where: {title: {endsWith: $suffix}}) {...}
  contained: movies(where: {title: {contains: $contained}}) {...}
  matchRegex: movies(where: {title: {pattern: {regex: $regex}}}) {...}
}

or e and per i filtri composti

Utilizza or e and per una logica più complessa.

query ListMoviesByGenreAndGenre($minRating: Int!, $genre: String) {
  movies(
    where: { _or: [{ rating: { ge: $minRating } }, { genre: { eq: $genre } }] }
  ) {
    # graphql: list the fields from the results to return
    title
  }
}

Query complesse

Le query Data Connect possono accedere ai dati in base alle relazioni tra le tabelle. Puoi utilizzare le relazioni di oggetti (one-to-one) o array (one-to-many) definite nello schema per eseguire query nidificate, ovvero recuperare i dati di un tipo insieme ai dati di un tipo nidificato o correlato.

Queste query utilizzano la sintassi magica Data Connect _on_ e _via nei campi di lettura generati.

Apportare modifiche allo schema dalla nostra versione iniziale.

Molti a uno

Aggiungiamo le recensioni alla nostra app, con una tabella Review e modifiche a User.

# User table is keyed by Firebase Auth UID.
type User @table {
  # `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
  id: String! @default(expr: "auth.uid")
  username: String! @col(dataType: "varchar(50)")
  # The `user: User!` field in the Review table generates the following one-to-many query field.
  #  reviews_on_user: [Review!]!
  # The `Review` join table the following many-to-many query field.
  #  movies_via_Review: [Movie!]!
}

# Reviews is a join table tween User and Movie.
# It has a composite primary keys `userUid` and `movieId`.
# A user can leave reviews for many movies. A movie can have reviews from many users.
# User  <-> Review is a one-to-many relationship
# Movie <-> Review is a one-to-many relationship
# Movie <-> User is a many-to-many relationship
type Review @table(name: "Reviews", key: ["movie", "user"]) {
  user: User!
  # The user field adds the following foreign key field. Feel free to uncomment and customize it.
  #  userUid: String!
  movie: Movie!
  # The movie field adds the following foreign key field. Feel free to uncomment and customize it.
  #  movieId: UUID!
  rating: Int
  reviewText: String
  reviewDate: Date! @default(expr: "request.time")
}

Query many-to-one

Ora esaminiamo una query con aliasing per illustrare la sintassi di _via_.

query UserMoviePreferences($username: String!) @auth(level: USER) {
  users(where: { username: { eq: $username } }) {
    likedMovies: movies_via_Review(where: { rating: { ge: 4 } }) {
      title
      genre
    }
    dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
      title
      genre
    }
  }
}

Uno a uno

Puoi vedere il pattern. Di seguito, lo schema è modificato per illustrazione.

# Movies
type Movie
  @table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
  id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
  title: String!
  releaseYear: Int @col(name: "release_year")
  genre: String
  rating: Int @col(name: "rating")
  description: String @col(name: "description")
  tags: [String] @col(name: "tags")
}
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
  @table(
    name: "MovieMetadata"
  ) {
  # @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
  # In this case, @ref(fields: "id") is implied
  movie: Movie! @ref
  # movieId: UUID <- this is created by the above @ref
  director: String @col(name: "director")
}


extend type MovieMetadata {
  movieId: UUID! # matches primary key of referenced type
...
}

extend type Movie {
  movieMetadata: MovieMetadata # can only be non-nullable on ref side
  # conflict-free name, always generated
  movieMetadatas_on_movie: MovieMetadata
}

Query per una sessione individuale

Puoi eseguire query utilizzando la sintassi _on_.

# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    movieMetadatas_on_movie {
      director
    }
  }
}

Molti a molti

I film hanno bisogno di attori e gli attori hanno bisogno di film. Hanno un rapporto many-to-many che puoi modellare con una tabella di join MovieActors.

# MovieActors Join Table Definition
type MovieActors @table(
  key: ["movie", "actor"] # join key triggers many-to-many generation
) {
  movie: Movie!
  actor: Actor!
}

# generated extensions for the MovieActors join table
extend type MovieActors {
  movieId: UUID!
  actorId: UUID!
}

# Extensions for Actor and Movie to handle many-to-many relationships
extend type Movie {
  movieActors: [MovieActors!]! # standard many-to-one relation to join table
  actors: [Actor!]! # many-to-many via join table

  movieActors_on_actor: [MovieActors!]!
  # since MovieActors joins distinct types, type name alone is sufficiently precise
  actors_via_MovieActors: [Actor!]!
}

extend type Actor {
  movieActors: [MovieActors!]! # standard many-to-one relation to join table
  movies: [Movie!]! # many-to-many via join table

  movieActors_on_movie: [MovieActors!]!
  movies_via_MovieActors: [Movie!]!
}

Query per molti a molti

Diamo un'occhiata a una query con aliasing per illustrare la sintassi di _via_.

query GetMovieCast($movieId: UUID!, $actorId: UUID!) @auth(level: PUBLIC) {
  movie(id: $movieId) {
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      name
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      name
    }
  }
  actor(id: $actorId) {
    mainRoles: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      title
    }
    supportingRoles: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      title
    }
  }
}

Query di aggregazione

Che cosa sono gli aggregati e perché utilizzarli?

I campi aggregati ti consentono di eseguire calcoli su un elenco di risultati. Con i campi aggregati puoi eseguire operazioni come:

  • Trovare il punteggio medio di una recensione
  • Trovare il costo totale degli articoli in un carrello degli acquisti
  • Trovare il prodotto con la valutazione più alta o più bassa
  • Contare il numero di prodotti nel tuo negozio

Le aggregazioni vengono eseguite sul server, il che offre una serie di vantaggi rispetto al calcolo lato client:

  • Rendimento più rapido dell'app (poiché vengono evitati i calcoli lato client)
  • Costi di esportazione dei dati ridotti (poiché invii solo i risultati aggregati anziché tutti gli input)
  • Maggiore sicurezza (poiché puoi concedere ai clienti l'accesso ai dati aggregati anziché all'intero set di dati)

Schema di esempio per gli aggregati

In questa sezione passeremo a uno schema di esempio di vetrina, che è utile per spiegare come utilizzare gli aggregati:

  type Product @table {
    name: String!
    manufacturer: String!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

Aggregati semplici

_count per tutti i campi

Il campo aggregato più semplice è _count: restituisce il numero di righe corrispondenti alla query. Per ogni campo del tipo, Data Connect genera i campi aggregati corrispondenti a seconda del tipo di campo.

Query

query CountProducts {
  products {
    _count
  }
}

Risposta
one

Ad esempio, se il tuo database contiene 5 prodotti, il risultato sarà:

{
  "products": [
    {
    "_count": 5
    }
  ]
}

Tutti i campi hanno un campo <field>_count, che conteggia il numero di righe con un valore non nullo in quel campo.

Query

query CountProductsWithExpirationDate {
  products {
    expirationDate_count
  }
}

Risposta
field_count

Ad esempio, se hai 3 prodotti con una data di scadenza, il risultato sarà:

{
  "products": [
    {
    "expirationDate_count": 3
    }
  ]
}
_min, _max, _sum e _avg per i campi numerici

I campi numerici (int, float, int64) hanno anche <field>_min, <field>_max, <field>_sum e <field>_avg.

Query

query NumericAggregates {
  products {
  quantityInStock_max
  price_min
  price_avg
  quantityInStock_sum
  }
}

Risposta
_min _max _sum _avg

Ad esempio, se hai i seguenti prodotti:

  • Prodotto A: quantityInStock: 10, price: 2.99
  • Prodotto B: quantityInStock: 5, price: 5.99
  • Prodotto C: quantityInStock: 20, price: 1.99

Il risultato sarà:

{
  "products": [
    {
    "quantityInStock_max": 20,
    "price_min": 1.99,
    "price_avg": 3.6566666666666666,
    "quantityInStock_sum": 35
    }
  ]
}
_min e _max per date e timestamp

I campi Data e Timestamp hanno <field>_min e <field>_max.

Query

query DateAndTimeAggregates {
  products {
  expirationDate_max
  expirationDate_min
  }
}

Risposta
_min _maxdatetime

Ad esempio, se hai le seguenti date di scadenza:

  • Prodotto A: 2024-01-01
  • Prodotto B: 2024-03-01
  • Prodotto C: 2024-02-01

Il risultato sarà:

{
  "products": [
    {
    "expirationDate_max": "2024-03-01",
    "expirationDate_min": "2024-01-01"
    }
  ]
}

Distinct

L'argomento distinct consente di ottenere tutti i valori univoci per un campo (o una combinazione di campi). Ad esempio:

Query

query ListDistinctManufacturers {
  products(distinct: true) {
    manufacturer
  }
}

Risposta
distinct

Ad esempio, se hai i seguenti produttori:

  • Prodotto A: manufacturer: "Acme"
  • Prodotto B: manufacturer: "Beta"
  • Prodotto C: manufacturer: "Acme"

Il risultato sarà:

{
  "products": [
    { "manufacturer": "Acme" },
    { "manufacturer": "Beta" }
  ]
}

Puoi anche utilizzare l'argomento distinct sui campi aggregati per aggregare i valori distinti. Ad esempio:

Query

query CountDistinctManufacturers {
  products {
    manufacturer_count(distinct: true)
  }
}

Risposta
distinctonaggregate

Ad esempio, se hai i seguenti produttori:

  • Prodotto A: manufacturer: "Acme"
  • Prodotto B: manufacturer: "Beta"
  • Prodotto C: manufacturer: "Acme"

Il risultato sarà:

{
  "products": [
    {
    "manufacturer_count": 2
    }
  ]
}

Dati aggregati raggruppati

Esegui un aggregato raggruppato selezionando una combinazione di campi aggregati e non aggregati per un tipo. Questo raggruppa tutte le righe corrispondenti che hanno lo stesso valore per i campi non aggregati e calcola i campi aggregati per quel gruppo. Ad esempio:

Query

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

Risposta
groupedaggregates

Ad esempio, se hai i seguenti prodotti:

  • Prodotto A: manufacturer: "Acme", price: 2.99
  • Prodotto B: manufacturer: "Beta", price: 5.99
  • Prodotto C: manufacturer: "Acme", price: 1.99

Il risultato sarà:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}
having e where con aggregati raggruppati

Puoi anche utilizzare l'argomento having e where per restituire solo i gruppi chesoddisfano i criteri specificati.

  • having ti consente di filtrare i gruppi in base ai relativi campi aggregati
  • where consente di filtrare le righe in base a campi non aggregati.

Query

query FilteredMostExpensiveProductByManufacturer {
  products(having: {price_max: {ge: 2.99}}) {
  manufacturer
  price_max
  }
}

Risposta
havingwhere

Ad esempio, se hai i seguenti prodotti:

  • Prodotto A: manufacturer: "Acme", price: 2.99
  • Prodotto B: manufacturer: "Beta", price: 5.99
  • Prodotto C: manufacturer: "Acme", price: 1.99

Il risultato sarà:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}

Aggregazioni in più tabelle

I campi aggregati possono essere utilizzati insieme ai campi della relazione uno a molti generati per rispondere a domande complesse sui dati. Di seguito è riportato uno schema modificato, con una tabella separata, Manufacturer, che possiamo utilizzare negli esempi:

  type Product @table {
    name: String!
    manufacturer: Manufacturer!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

  type Manufacturer @table {
    name: String!
    headquartersCountry: String!
  }

Ora possiamo utilizzare i campi aggregati per trovare, ad esempio, il numero di prodotti realizzati da un produttore:

Query

query GetProductCount($id: UUID) {
  manufacturers {
    name
    products_on_manufacturer {
      _count
    }
  }
}

Risposta
aggregatesacrosstables

Ad esempio, se hai i seguenti produttori:

  • Produttore A: name: "Acme", products_on_manufacturer: 2
  • Produttore B: name: "Beta", products_on_manufacturer: 1

Il risultato sarà:

{
  "manufacturers": [
    { "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
    { "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
  ]
}

Mutazioni per il database delle recensioni di film

Come accennato, quando definisci una tabella nello schema, Data Connect genererà mutazioni implicite di base per ogni tabella.

type Movie @table { ... }

extend type Mutation {
  # Insert a row into the movie table.
  movie_insert(...): Movie_Key!
  # Upsert a row into movie."
  movie_upsert(...): Movie_Key!
  # Update a row in Movie. Returns null if a row with the specified id/key does not exist
  movie_update(...): Movie_Key
  # Update rows based on a filter in Movie.
  movie_updateMany(...): Int!
  # Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
  movie_delete(...): Movie_Key
  # Delete rows based on a filter in Movie.
  movie_deleteMany(...): Int!
}

Con questi, puoi implementare casi CRUD di base sempre più complessi. Ripeti cinque volte velocemente.

Istruzione @transaction

Questa direttiva impone che una mutazione venga sempre eseguita in una transazione di database.

Le mutazioni con @transaction hanno la garanzia di riuscire o meno completamente. Se uno dei campi all'interno della transazione non va a buon fine, viene eseguito il rollback dell'intera transazione. Dal punto di vista del client, qualsiasi errore si comporta come se l'intera richiesta non fosse riuscita a causa di un errore di richiesta e l'esecuzione non fosse iniziata.

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

Crea

Facciamo delle 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
  })
}

O 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 gli aggiornamenti

Ecco gli aggiornamenti. Produttori e registi sperano certamente 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, accodamento e anteposizione con _update

Sebbene nelle mutazioni _update e _updateMany sia possibile impostare esplicitamente i valori in data:, spesso è più opportuno applicare un operatore come incremento per aggiornare i valori.

Per modificare l'esempio di aggiornamento precedente, supponiamo che tu voglia aumentare 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
    }
  })
}

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

  • inc per incrementare i tipi di dati Int, Int64 e Float
  • dec per decrementare i tipi di dati Int, Int64 e Float
  • append per accodare ai tipi di elenco, ad eccezione degli elenchi di vettori
  • prepend da anteporre ai tipi di elenchi, ad eccezione degli elenchi di vettori

Eseguire le eliminazioni

Ovviamente puoi eliminare i dati dei film. I conservatori di film vorranno sicuramente conservare le pellicole fisiche 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

Scopri come utilizzare la mutazione _upsert implicita in una relazione.

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

Consenti a Data Connect di fornire valori utilizzando la sintassi field_expr

Come discusso in Valori scalari chiave e del server, puoi progettare lo schema in modo che il server compili i valori per i campi comuni come id e le date in risposta alle richieste del client.

Inoltre, puoi utilizzare i dati, ad esempio gli ID utente, inviati negli oggetti Data Connect request dalle app client.

Quando implementi le mutazioni, utilizza la sintassi field_expr per attivare gli aggiornamenti generati dal server o accedere ai dati delle 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 liste di cose da fare familiare, quando crei una nuova lista di cose da fare, puoi indicare 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 i valori scalari _Expr nel riferimento ai valori scalari.

Query di ricerca dei dati di autorizzazione

Le mutazioni Data Connect possono essere autorizzate eseguendo prima una query sul database e verificando i risultati della query con le espressioni CEL. Questa operazione è utile, ad esempio, quando scrivi in una tabella e devi controllare i contenuti di una riga in un'altra tabella.

Questa funzionalità supporta:

  • La direttiva @check, che consente di valutare i contenuti dei campi e, in base ai risultati di questa valutazione:
    • Procedi con le operazioni di creazione, aggiornamento ed eliminazione definite da una mutazione
    • Procedi per restituire i risultati di una query
    • Utilizza i valori restituiti per eseguire una logica diversa nel codice client
  • L'istruzione @redact, che consente di omettere i risultati delle query dai risultati del protocollo di trasmissione.

Queste funzionalità sono utili per i flussi di autorizzazione.

Schema SQL equivalente

-- Movies Table
CREATE TABLE Movies (
    movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    release_year INT,
    genre VARCHAR(30),
    rating INT,
    description TEXT,
    tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
    movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
    director VARCHAR(255) NOT NULL,
    PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
    actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
    movie_id UUID REFERENCES Movies(movie_id),
    actor_id UUID REFERENCES Actors(actor_id),
    role VARCHAR(50) NOT NULL, # "main" or "supporting"
    PRIMARY KEY (movie_id, actor_id),
    FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
    FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
    user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_auth VARCHAR(255) NOT NULL
    username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
    review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_id UUID REFERENCES Users(user_id),
    movie_id UUID REFERENCES Movies(movie_id),
    rating INT,
    review_text TEXT,
    review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE (movie_id, user_id)
    FOREIGN KEY (user_id) REFERENCES Users(user_id),
    FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);

Quali sono i passaggi successivi?