Data Connect-Schemas, -Abfragen und -Mutationen

Mit Firebase Data Connect können Sie Connectors für Ihre PostgreSQL-Instanzen erstellen, die mit Google Cloud SQL verwaltet werden. Diese Connectors sind Kombinationen aus einem Schema, Abfragen und Mutationen für die Verwendung Ihrer Daten.

In der Anleitung für die ersten Schritte wurde ein E-Mail-App-Schema für PostgreSQL vorgestellt. In dieser Anleitung wird genauer darauf eingegangen, wie Data Connect-Schemas für PostgreSQL entworfen werden. Als Beispiel dient eine Filmbewertungsdatenbank.

In diesem Leitfaden werden Data Connect-Abfragen und ‑Mutationen mit Schema-Beispielen kombiniert. Warum werden Abfragen (und Mutationen) in einem Leitfaden zu Data Connect-Schemas behandelt? Wie andere GraphQL-basierte Plattformen ist Firebase Data Connect eine Query-First-Entwicklungsplattform. Als Entwickler müssen Sie bei der Datenmodellierung also darüber nachdenken, welche Daten Ihre Kunden benötigen. Das hat großen Einfluss auf das Datenschema, das Sie für Ihr Projekt entwickeln.

In dieser Anleitung wird zuerst ein neues Schema für Filmbesprechungen vorgestellt. Anschließend werden die Abfragen und Mutationen behandelt, die aus diesem Schema abgeleitet werden. Zum Schluss wird eine SQL-Auflistung bereitgestellt, die dem Data Connect-Kernschema entspricht.

Das Schema für eine Filmkritik-App

Angenommen, Sie möchten einen Dienst entwickeln, mit dem Nutzer Filmrezensionen einreichen und ansehen können.

Sie benötigen ein anfängliches Schema für eine solche App. Sie erweitern dieses Schema später, um komplexe relationale Abfragen zu erstellen.

Filmtabelle

Das Schema für Filme enthält wichtige Richtlinien wie:

  • @table, mit der wir Vorgangsnamen mithilfe der Argumente singular und plural festlegen können
  • @col, um Spaltennamen explizit festzulegen
  • @default, damit Standardeinstellungen festgelegt werden können.
# 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")
}

Serverwerte und Schlüsselskalarwerte

Bevor wir uns die App für Filmbesprechungen ansehen, stellen wir Data Connect-Serverwerte und wichtige Skalare vor.

Mit Serverwerten können Sie Felder in Ihren Tabellen dynamisch mit gespeicherten oder leicht berechenbaren Werten gemäß bestimmten serverseitigen Ausdrücken füllen lassen. Sie können beispielsweise ein Feld mit einem Zeitstempel definieren, der angewendet wird, wenn auf das Feld mit dem Ausdruck updatedAt: Timestamp! @default(expr: "request.time") zugegriffen wird.

Schlüssel-Skalare sind prägnante Objekt-IDs, die von Data Connect automatisch aus Schlüsselfeldern in Ihren Schemas zusammengestellt werden. Bei wichtigen Skalaren geht es um Effizienz. Sie können mit einem einzigen Aufruf Informationen zur Identität und Struktur Ihrer Daten abrufen. Sie sind besonders nützlich, wenn Sie sequenzielle Aktionen für neue Datensätze ausführen möchten und eine eindeutige Kennung für nachfolgende Vorgänge benötigen. Außerdem können Sie damit auf relationale Schlüssel zugreifen, um zusätzliche komplexere Vorgänge auszuführen.

ID-Typ

In GraphQL ist der Typ ID als undurchsichtiger Typ definiert, der als String serialisiert wird. GraphQL ist unabhängig vom ID-Format, erzwingt aber Strings und Ganzzahlen aus der Eingabe.

PostgreSQL-Schlüssel sind in der Regel Ganzzahlen oder UUIDs, keine Strings. Data Connect generiert solche Schlüssel automatisch aus Ihrem Schema. Sie können die Schlüsselgenerierung mit der @default-Anweisung anpassen, wie in der Felddefinition id der Tabelle „Actor“ gezeigt wird: id: ID! … @default(generate: "UUID").

Tabelle mit Filmmetadaten

Als Nächstes erfassen wir die Filmregisseure und richten eine 1:1-Beziehung zu Movie ein.

Fügen Sie die @ref-Anweisung hinzu, um Beziehungen zu definieren.

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

Actor und MovieActor

Als Nächstes möchten Sie, dass Schauspieler in Ihren Filmen mitspielen. Da Sie eine Many-to-Many-Beziehung zwischen Filmen und Schauspielern haben, erstellen Sie eine Join-Tabelle.

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

Nutzer

Und schließlich Nutzer für Ihre App.

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

Unterstützte Datentypen

Data Connect unterstützt die folgenden skalaren Datentypen, wobei die Zuweisungen zu PostgreSQL-Typen mit @col(dataType:) erfolgen.

Data Connect type Integrierter GraphQL-Typ oder
Data Connect benutzerdefinierter Typ
Standard-PostgreSQL-Typ Unterstützte PostgreSQL-Typen
(Alias in Klammern)
String GraphQL Text text
bit(n), varbit(n)
char(n), varchar(n)
Integer GraphQL int Int2 (smallint, smallserial),
int4 (integer, int, serial)
Float GraphQL float8 float4 (real)
float8 (double precision)
numeric (decimal)
Boolesch GraphQL boolean boolean
UUID Benutzerdefiniert uuid uuid
INT64 Benutzerdefiniert bigint int8 (bigint, bigserial)
numeric (decimal)
Datum Benutzerdefiniert date Datum
Zeitstempel Benutzerdefiniert timestamptz

timestamptz

Hinweis:Informationen zur lokalen Zeitzone werden nicht gespeichert.
PostgreSQL konvertiert und speichert solche Zeitstempel als UTC.

Aufzählung Benutzerdefiniert enum

enum

Vektor Benutzerdefiniert vector

Vektor

Weitere Informationen finden Sie unter Vektorähnlichkeitssuche mit Vertex AI durchführen.

  • GraphQL-List wird einem eindimensionalen Array zugeordnet.
    • Beispiel: [Int] wird int5[] zugeordnet, [Any] wird jsonb[] zugeordnet.
    • Data Connect unterstützt keine verschachtelten Arrays.

Implizite und vordefinierte Abfragen und Mutationen

Ihre Data Connect-Abfragen und ‑Mutationen erweitern eine Reihe von impliziten Abfragen und impliziten Mutationen, die von Data Connect basierend auf den Typen und Typbeziehungen in Ihrem Schema generiert werden. Implizite Abfragen und Mutationen werden von lokalen Tools generiert, wenn Sie Ihr Schema bearbeiten.

Im Entwicklungsprozess implementieren Sie vordefinierte Abfragen und vordefinierte Mutationen basierend auf diesen impliziten Vorgängen.

Implizite Benennung von Abfragen und Mutationen

Data Connect leitet geeignete Namen für implizite Abfragen und Mutationen aus Ihren Schematypdeklarationen ab. Wenn Sie beispielsweise mit einer PostgreSQL-Quelle arbeiten und eine Tabelle mit dem Namen Movie definieren, generiert der Server implizit Folgendes:

  • Abfragen für Anwendungsfälle mit einer einzelnen Tabelle mit den Anzeigenamen movie (Singular, zum Abrufen einzelner Ergebnisse mit Argumenten wie eq) und movies (Plural, zum Abrufen von Ergebnislisten mit Argumenten wie gt und Vorgängen wie orderby). Data Connect generiert auch Abfragen für relationale Vorgänge mit mehreren Tabellen und expliziten Namen wie actors_on_movies oder actors_via_actormovie.
  • Mutationen mit den Namen movie_insert, movie_upsert...

Mit der Schemadefinitionssprache können Sie auch explizit Namen für Vorgänge festlegen, indem Sie die Direktivenargumente singular und plural verwenden.

Abfragen für die Filmbewertungsdatenbank

Sie definieren eine Data Connect-Abfrage mit einer Deklaration des Abfrageoperationstyps, einem Operationsnamen, null oder mehr Operationsargumenten und null oder mehr Direktiven mit Argumenten.

In der Kurzanleitung hatte die Beispielabfrage listEmails keine Parameter. In vielen Fällen sind die Daten, die an Abfragefelder übergeben werden, natürlich dynamisch. Sie können die $variableName-Syntax verwenden, um mit Variablen als einer der Komponenten einer Abfragedefinition zu arbeiten.

Die folgende Abfrage enthält also:

  • Eine query-Typdefinition
  • Ein ListMoviesByGenre-Vorgangsname (Abfrage)
  • Ein einzelnes Variablenargument für den Vorgang $genre
  • Eine einzelne Anweisung: @auth.
query ListMoviesByGenre($genre: String!) @auth(level: USER)

Für jedes Abfrageargument ist eine Typdeklaration, ein integrierter Typ wie String oder ein benutzerdefinierter, schemadefinierter Typ wie Movie erforderlich.

Sehen wir uns die Signatur von immer komplexeren Anfragen an. Zum Schluss werden leistungsstarke, prägnante Beziehungsausdrücke vorgestellt, die in impliziten Abfragen verfügbar sind und auf die Sie in Ihren vordefinierten Abfragen aufbauen können.

Wichtige Skalare in Abfragen

Zuerst aber ein Hinweis zu wichtigen Skalaren.

Data Connect definiert einen speziellen Typ für Schlüsselskalares, der durch _Key identifiziert wird. Der Typ eines Schlüsselscalars für unsere Tabelle Movie ist beispielsweise Movie_Key.

Sie rufen wichtige Skalare als Antwort ab, die von den meisten impliziten Mutationen zurückgegeben wird, oder natürlich von Abfragen, in denen Sie alle Felder abgerufen haben, die zum Erstellen des skalaren Schlüssels erforderlich sind.

Für automatische Singular-Abfragen wie movie in unserem laufenden Beispiel wird ein Schlüsselargument unterstützt, das einen Schlüssel-Skalarwert akzeptiert.

Sie können einen Schlüssel-Skalar als Literal übergeben. Sie können jedoch Variablen definieren, um wichtige Skalare als Eingabe zu übergeben.

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

Diese können in der JSON-Anfrage so (oder in anderen Serialisierungsformaten) angegeben werden:

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

Dank des benutzerdefinierten Skalar-Parsings kann ein Movie_Key auch mit der Objektsyntax erstellt werden, die Variablen enthalten kann. Das ist vor allem dann nützlich, wenn Sie einzelne Komponenten aus irgendeinem Grund in verschiedene Variablen aufteilen möchten.

Aliasing in Abfragen

Data Connect unterstützt die Aliasierung von GraphQL in Abfragen. Mit Aliasen können Sie die Daten umbenennen, die in den Ergebnissen einer Abfrage zurückgegeben werden. Mit einer einzelnen Data Connect-Abfrage können mehrere Filter oder andere Abfragevorgänge in einer effizienten Anfrage an den Server angewendet werden. So werden effektiv mehrere „Unterabfragen“ gleichzeitig ausgeführt. Um Namenskonflikte im zurückgegebenen Dataset zu vermeiden, verwenden Sie Aliase, um die Unterabfragen zu unterscheiden.

Hier sehen Sie eine Abfrage, in der ein Ausdruck den Alias mostPopular verwendet.

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

Einfache Abfragen mit Filtern

Data Connect-Abfragen werden allen gängigen SQL-Filtern und Sortiervorgängen zugeordnet.

where- und orderBy-Operatoren (Einzahl- und Mehrzahl-Abfragen)

Gibt alle übereinstimmenden Zeilen aus der Tabelle (und den verschachtelten Zuordnungen) zurück. Gibt ein leeres Array zurück, wenn keine Datensätze dem Filter entsprechen.

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}]) {  }
}

limit- und offset-Operatoren (Einzahl- und Mehrzahl-Abfragen)

Sie können die Ergebnisse paginieren. Diese Argumente werden akzeptiert, aber nicht in den Ergebnissen zurückgegeben.

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

„includes“-Anweisungen für Array-Felder

Sie können prüfen, ob ein Arrayfeld ein bestimmtes Element enthält.

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

String-Operationen und reguläre Ausdrücke

Für Ihre Abfragen können Sie die üblichen String-Such- und Vergleichsvorgänge verwenden, einschließlich regulärer Ausdrücke. Hinweis: Aus Effizienzgründen werden hier mehrere Vorgänge gebündelt und mit Aliasen unterschieden.

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 und and für zusammengesetzte Filter

Verwenden Sie or und and für komplexere Logik.

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

Komplexe Abfragen

Mit Data Connect-Abfragen kann auf Daten zugegriffen werden, die auf den Beziehungen zwischen Tabellen basieren. Sie können die in Ihrem Schema definierten Objekt- (1:1) oder Array-Beziehungen (1:n) verwenden, um verschachtelte Abfragen zu erstellen. So können Sie Daten für einen Typ zusammen mit Daten aus einem verschachtelten oder verknüpften Typ abrufen.

In solchen Abfragen wird die magische Data Connect-Syntax _on_ und _via in generierten impliziten Abfragen verwendet.

Sie nehmen Änderungen am Schema aus unserer ersten Version vor.

Viele zu eins

Fügen wir unserer App Rezensionen hinzu. Dazu erstellen wir eine Review-Tabelle und nehmen Änderungen an User vor.

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

Many-to-One-Abfrage

Sehen wir uns nun eine Abfrage mit Aliasen an, um die _via_-Syntax zu veranschaulichen.

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

Einzelgespräch

Sie können das Muster erkennen. Unten sehen Sie das Schema zur Veranschaulichung.

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

Einzelgespräch

Sie können Abfragen mit der _on_-Syntax ausführen.

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

Viele-zu-viele

Filme brauchen Schauspieler und Schauspieler brauchen Filme. Sie stehen in einer m:n-Beziehung zueinander, die Sie mit einer MovieActors-Verknüpfungstabelle modellieren können.

# 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!]!
}

Viele-zu-viele-Beziehungen abfragen

Sehen wir uns eine Abfrage mit Alias an, um die _via_-Syntax zu veranschaulichen.

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

Mutationen für die Filmbesprechungsdatenbank

Wie bereits erwähnt, werden beim Definieren einer Tabelle in Ihrem Schema mit Data Connect für jede Tabelle grundlegende implizite Mutationen generiert.

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

Damit können Sie immer komplexere CRUD-Kernfälle implementieren. Versuchen Sie, das fünfmal schnell zu sagen!

Erstellen

Wir beginnen mit einfachen Kreationen.

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

Oder ein 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"
  })
}

Updates durchführen

Hier sind die Updates. Produzenten und Regisseure hoffen natürlich, dass diese durchschnittlichen Bewertungen im Trend liegen.

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

Löschvorgänge ausführen

Sie können Filmdaten natürlich löschen. Filmkonservatoren möchten sicherlich, dass die physischen Filme so lange wie möglich erhalten bleiben.

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

Hier können Sie _deleteMany verwenden.

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

Mutationen für Beziehungen schreiben

Sehen Sie sich an, wie die implizite _upsert-Mutation für eine Beziehung verwendet wird.

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

Entsprechendes SQL-Schema

-- 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);

Nächste Schritte