Firebase Data Connect vous permet de créer des connecteurs pour vos instances PostgreSQL gérées avec Google Cloud SQL. Ces connecteurs sont des combinaisons d'un schéma, de requêtes et de mutations pour utiliser vos données.
Le guide de démarrage a présenté un schéma d'application d'avis sur les films pour PostgreSQL. Ce guide explique plus en détail comment concevoir des schémas Data Connect pour PostgreSQL.
Ce guide associe les requêtes et mutations Data Connect au schéma exemples. Pourquoi parler des requêtes (et des mutations) dans ce guide Data Connect schémas ? Comme pour les autres plates-formes basées sur GraphQL, Firebase Data Connect est une plate-forme de développement axée sur les requêtes. développeur, vous allez réfléchir aux données que vos clients besoin, ce qui aura une grande influence sur le schéma de données que vous développerez pour votre projet.
Ce guide commence par un nouveau schéma pour les critiques de films, puis nous aborderons les requêtes et les mutations dérivé de ce schéma et fournit une liste SQL équivalente au schéma Data Connect de base.
Schéma d'une application d'avis sur les films
Imaginez que vous souhaitiez créer un service qui permet aux utilisateurs d'envoyer et de regarder des films. avis.
Vous avez besoin d'un schéma initial pour une telle application. Vous allez ensuite étendre ce schéma pour créer des requêtes relationnelles complexes.
Tableau des films
Le schéma des films contient des directives de base, comme :
@table
, qui permet de définir des noms d'opérations à l'aide desingular
etplural
arguments@col
pour définir explicitement les noms des colonnes@default
pour autoriser la définition de valeurs par défaut.
# 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")
}
Valeurs de serveur et scalaires de clé
Avant de nous pencher sur l'application d'évaluation de films, examinons Data Connect. valeurs serveur et valeurs scalaires de clé.
Grâce aux valeurs du serveur, vous pouvez permettre au serveur d'être renseigné de manière dynamique.
dans vos tables à l'aide de valeurs stockées ou faciles à calculer en fonction
des expressions spécifiques côté serveur. Par exemple, vous pouvez définir un champ avec un code temporel appliqué lorsque le champ est consulté à l'aide de l'expression updatedAt: Timestamp! @default(expr: "request.time")
.
Les scalaires de clé sont des identifiants d'objet concis que Data Connect assemble automatiquement à partir des champs de clé de vos schémas. Les scalaires de clé sont axés sur l'efficacité, ce qui vous permet de trouver en un seul appel des informations sur l'identité et la structure de vos données. Ils sont particulièrement utiles lorsque vous souhaitez effectuer des actions séquentielles sur les nouveaux enregistrements et nécessitent un identifiant unique à transmettre à les opérations à venir, mais aussi pour accéder à des clés relationnelles d'effectuer d'autres opérations plus complexes.
Table des métadonnées de film
Voyons maintenant comment suivre les réalisateurs de films et configurer une relation individuelle avec Movie
.
Ajoutez la directive @ref
pour définir des relations.
# 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")
}
Acteur et MovieActor
Ensuite, vous voulez que les acteurs jouent dans vos films et, comme vous avez entre les films et les acteurs, créer une table de jointure.
# 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(name: "Actors", singular: "actor", plural: "actors") {
id: UUID! @col(name: "actor_id") @default(expr: "uuidV4()")
name: String! @col(name: "name", dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary key(s) of this table
# In this case, the keys are [movieId, actorId], the generated fields of the reference types [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
# @ref creates a field in the current table (MovieActor) 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, see: implicit.gql
actor: Actor! @ref
# actorId: UUID! <- this is created by the above @ref, see: implicit.gql
role: String! @col(name: "role") # "main" or "supporting"
# optional other fields
}
Utilisateur
Enfin, les utilisateurs de votre application.
# Users
# Suppose a user can leave reviews for movies
# user:reviews is a one to many relationship, movie:reviews is a one to many relationship, movie:user is a many to many relationship
type User
@table(name: "Users", singular: "user", plural: "users", key: ["id"]) {
id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
auth: String @col(name: "user_auth") @default(expr: "auth.uid")
username: String! @col(name: "username", dataType: "varchar(30)")
# The following are generated from the @ref in the Review table
# reviews_on_user
# movies_via_Review
}
Types de données acceptés
Data Connect est compatible avec les types de données scalaires suivants, avec des attributions à des types PostgreSQL à l'aide de @col(dataType:)
.
Type de Data Connect | Type intégré GraphQL ou type personnalisé Data Connect |
Type PostgreSQL par défaut | Types PostgreSQL compatibles (alias entre parenthèses) |
---|---|---|---|
Chaîne | GraphQL | texte | text bit(n), varbit(n) char(n), varchar(n) |
Int | GraphQL | int | Int2 (smallint, smallserial), int4 (entier, entier, série) |
Float | GraphQL | float8 | float4 (réel) float8 (double précision) numeric (décimal) |
Booléen | GraphQL | booléen | booléen |
UUID | Personnalisé | UID | uuid |
Int64 | Personnalisé | bigint | int8 (bigint, bigserial) numérique (décimal) |
Date | Personnalisé | date | date |
Horodatage | Personnalisé | timestamptz | timestamptz Remarque:Les informations de fuseau horaire local ne sont pas stockées. |
Vecteur | Personnalisé | vecteur | vecteur Consultez Effectuer une recherche de similarité vectorielle avec Vertex AI. |
List
GraphQL correspond à un tableau à une dimension.- Par exemple,
[Int]
correspond àint5[]
,[Any]
correspond àjsonb[]
. - Data Connect n'est pas compatible avec les tableaux imbriqués.
- Par exemple,
Requêtes et mutations implicites et prédéfinies
Vos requêtes et mutations Data Connect étendront un ensemble d'éléments implicites de requêtes et les mutations implicites générées par Data Connect en fonction les types et les relations de types dans votre schéma. Les requêtes et les mutations implicites sont générées par les outils locaux chaque fois que vous modifiez votre schéma.
Dans le processus de développement, vous allez implémenter des requêtes prédéfinies et des mutations prédéfinies basées sur ces opérations implicites.
Requête implicite et dénomination des mutations
Data Connect infère des noms appropriés pour les requêtes et les mutations implicites à partir de vos déclarations de type de schéma. Par exemple, si vous travaillez avec une source PostgreSQL et que vous définissez une table nommée Movie
, le serveur génère des éléments implicites :
- Les requêtes pour des cas d'utilisation de table unique avec les noms descriptifs
movie
(singulier, permettant de récupérer des résultats individuels en transmettant des arguments tels queeq
) etmovies
(pluriel, pour récupérer des listes de résultats en transmettant des arguments commegt
et des opérations commeorderby
). Data Connect génère également des requêtes pour les tables des opérations relationnelles avec des noms explicites tels queactors_on_movies
ouactors_via_actormovie
- Mutations nommées
movie_insert
,movie_upsert
...
Le langage de définition de schéma vous permet aussi de définir explicitement
à l'aide des arguments de directive singular
et plural
.
Requêtes concernant la base de données des critiques de films
Vous définissez une requête Data Connect avec une déclaration de type d'opération de requête, un nom d'opération, zéro ou plusieurs arguments d'opération, et zéro ou plusieurs directives avec des arguments.
Dans le guide de démarrage rapide, l'exemple de requête listEmails
ne comportait aucun paramètre. Bien entendu,
bien souvent, les données transmises aux champs de requête sont dynamiques. Vous pouvez utiliser
$variableName
pour utiliser des variables comme l'un des composants d'une
définition d'une requête.
La requête suivante comporte donc :
- Définition de type
query
- Nom d'une opération (requête)
ListMoviesByGenre
- Un seul argument d'opération
$genre
à variable - Une seule directive,
@auth
.
query ListMoviesByGenre($genre: String!) @auth(level: USER)
Chaque argument de requête nécessite une déclaration de type, un élément intégré tel que String
ou un type personnalisé défini par le schéma tel que Movie
.
Examinons la signature des requêtes de plus en plus complexes. Vous terminerez par en introduisant des expressions de relations concises et puissantes disponibles dans des que vous pouvez utiliser dans vos requêtes prédéfinies.
Clés scalaires dans les requêtes
Mais d'abord, un mot sur les scalaires de clé.
Data Connect définit un type spécial pour les scalaires de clé, identifiés par _Key
. Par exemple, le type d'un scalaire de clé pour notre table Movie
est Movie_Key
.
Vous récupérez les valeurs scalaires clés sous la forme d'une réponse renvoyée par la plupart des mutations implicites. des requêtes pour lesquelles vous avez récupéré tous les champs nécessaires la clé scalaire.
Les requêtes automatiques singulières, comme movie
dans notre exemple, acceptent une clé
qui accepte une clé scalaire.
Vous pouvez transmettre une clé scalaire en tant que littéral. Toutefois, vous pouvez définir des variables pour transmettre des scalaires clés en entrée.
query GetMovie($myKey: Movie_Key!) {
movie(key: $myKey) { title }
}
Vous pouvez les fournir dans la requête JSON comme suit (ou dans d'autres formats de sérialisation) :
{
# …
"variables": {
"myKey": {"foo": "some-string-value", "bar": 42}
}
}
Grâce à l'analyse scalaire personnalisée, vous pouvez également construire un Movie_Key
à l'aide de la méthode
qui peut contenir des variables. Ceci est surtout utile
lorsque vous souhaitez
décomposer les composants individuels en
différentes variables pour une raison quelconque.
Création d'alias dans les requêtes
Data Connect est compatible avec les alias GraphQL dans les requêtes. Avec les alias, vous renommer les données qui sont renvoyées dans les résultats d'une requête. Une seule La requête Data Connect peut appliquer plusieurs filtres ou une autre requête des opérations en une seule demande efficace vers le serveur, en émettant plusieurs "sous-requêtes" en même temps. Pour éviter les conflits au niveau des noms dans l'ensemble de données renvoyé, vous utiliser des alias pour distinguer les sous-requêtes.
Voici une requête dans laquelle une expression utilise l'alias mostPopular
.
query ReviewTopPopularity($genre: String) {
mostPopular: review(first: {
where: {genre: {eq: $genre}},
orderBy: {popularity: DESC}
}) { … }
}
Requêtes simples avec filtres
Les requêtes Data Connect correspondent à tous les filtres et à tous les filtres SQL courants opérations.
Opérateurs where
et orderBy
(requêtes au singulier et au pluriel)
Renvoie toutes les lignes correspondantes de la table (et les associations imbriquées). Renvoie une Un tableau vide si aucun enregistrement ne correspond au filtre.
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}]) { … }
}
Opérateurs limit
et offset
(requêtes au singulier et au pluriel)
Vous pouvez effectuer la pagination des résultats. Ces arguments sont acceptés, mais ne sont pas renvoyés dans les résultats.
query MoviesTop10 {
movies(orderBy: [{ rating: DESC }], limit: 10) {
# graphql: list the fields from the results to return
title
}
}
inclut pour les champs de tableau
Vous pouvez vérifier qu'un champ de tableau inclut un élément spécifié.
# 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
}
}
Opérations sur des chaînes et expressions régulières
Vos requêtes peuvent utiliser des opérations classiques de recherche et de comparaison de chaînes, y compris les expressions régulières. Notez que pour plus d'efficacité, vous regroupez plusieurs opérations ici et les clarifiez à l'aide d'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
et and
pour les filtres composés
Utilisez or
et and
pour une logique plus complexe.
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
}
}
Requêtes complexes
Les requêtes Data Connect peuvent accéder aux données en fonction des relations entre tableaux. Vous pouvez utiliser les relations d'objet (un à un) ou de tableau (un à plusieurs). définies dans votre schéma pour créer des requêtes imbriquées, c'est-à-dire extraire des données pour un type ainsi que les données d'un type imbriqué ou associé.
Ces requêtes utilisent la syntaxe magique Data Connect _on_
et _via
dans les requêtes implicites générées.
Vous allez modifier le schéma à partir de notre version initiale.
Plusieurs à un
Ajoutons des avis à notre application, avec une table Review
et des modifications dans User
.
# Users
# Suppose a user can leave reviews for movies
# user:reviews is a one to many relationship,
# movie:reviews is a one to many relationship,
# movie:user is a many to many relationship
type User
@table(name: "Users", singular: "user", plural: "users", key: ["id"]) {
id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
auth: String @col(name: "user_auth") @default(expr: "auth.uid")
username: String! @col(name: "username", dataType: "varchar(30)")
# The following are generated from the @ref in the Review table
# reviews_on_user
# movies_via_Review
}
# Reviews
type Review @table(name: "Reviews", key: ["movie", "user"]) {
id: UUID! @col(name: "review_id") @default(expr: "uuidV4()")
user: User! @ref
movie: Movie! @ref
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
Requête de plusieurs à un
Voyons maintenant une requête avec un alias pour illustrer la syntaxe _via_
.
query UserMoviePreferences($username: String!) @auth(level: USER) {
users(where: { username: { eq: $username } }) {
likedMovies: movies_via_review(where: { rating: { ge: 4 } }) {
title
genre
description
}
dislikedMovies: movies_via_review(where: { rating: { le: 2 } }) {
title
genre
description
}
}
}
En face à face
Vous pouvez voir le schéma. Ci-dessous, le schéma est modifié à des fins d'illustration.
# 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
}
Requête pour une session individuelle
Vous pouvez effectuer des requêtes à l'aide de la syntaxe _on_
.
# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
movie(id: $id) {
movieMetadatas_on_movie {
director
}
}
}
Plusieurs à plusieurs
Les films ont besoin d'acteurs et les acteurs ont besoin de films. Ils ont un grand nombre
que vous pouvez modéliser avec une table de jointure 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!]!
}
Requête de type "plusieurs à plusieurs"
Examinons une requête avec alias pour illustrer la syntaxe _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
}
}
}
Mutations pour la base de données des avis sur les films
Comme indiqué précédemment, lorsque vous définissez une table dans votre schéma, Data Connect générera des mutations implicites de base pour chaque table.
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!
}
Vous pouvez ainsi implémenter des cas CRUD de base de plus en plus complexes. Disons que cinq fois plus vite !
Créer
Faisons les créations de 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
})
}
ou une mise à jour.
# 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"
})
}
Effectuer des mises à jour
Voici les informations. Les producteurs et les réalisateurs espèrent certainement que ces notes moyennes sont à la hausse.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id, data: {
genre: $genre
rating: $rating
description: $description
})
}
# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $ratingIncrement: Int!) {
movie_updateMany(
where: { genre: { eq: $genre } },
update: { rating: { inc: $ratingIncrement } }
)
}
Effectuer des suppressions
Vous pouvez bien sûr supprimer les données du film. Les conservateurs de films viendront certainement veulent que les films physiques soient conservés aussi longtemps que possible.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
Ici, vous pouvez utiliser _deleteMany
.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
Écrire des mutations sur des relations
Observez comment utiliser la mutation _upsert
implicite sur une relation.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Schéma SQL équivalent
-- 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);
Étape suivante
- Découvrez comment appeler vos requêtes et mutations à partir d'une requête SDK Web, SDK Android, SDK iOS et SDK Flutter.