Giản đồ, truy vấn và dữ liệu đột biến của Data Connect

Firebase Data Connect cho phép bạn tạo trình kết nối cho các phiên bản PostgreSQL được quản lý bằng Google Cloud SQL. Các trình kết nối này là sự kết hợp của một giản đồ, truy vấn và đột biến để sử dụng dữ liệu của bạn.

Hướng dẫn bắt đầu đã giới thiệu giản đồ ứng dụng đánh giá phim cho PostgreSQL và hướng dẫn này sẽ xem xét kỹ hơn về cách thiết kế giản đồ Data Connect cho PostgreSQL.

Hướng dẫn này sẽ ghép nối các truy vấn và đột biến Data Connect với các ví dụ về giản đồ. Tại sao chúng ta lại thảo luận về truy vấn (và sự thay đổi) trong hướng dẫn về schema Data Connect? Giống như các nền tảng dựa trên GraphQL khác, Firebase Data Connect là nền tảng phát triển ưu tiên truy vấn. Vì vậy, là một nhà phát triển, khi lập mô hình dữ liệu, bạn sẽ suy nghĩ về dữ liệu mà khách hàng cần. Điều này sẽ ảnh hưởng lớn đến giản đồ dữ liệu bạn phát triển cho dự án của mình.

Hướng dẫn này bắt đầu bằng một giản đồ về bài đánh giá phim mới, sau đó đề cập đến các truy vấnđột biến lấy từ giản đồ đó, cuối cùng cung cấp một trang thông tin SQL tương đương với giản đồ Data Connect chính.

Sơ đồ cho ứng dụng đánh giá phim

Hãy tưởng tượng bạn muốn xây dựng một dịch vụ cho phép người dùng gửi và xem bài đánh giá về phim.

Bạn cần có một giản đồ ban đầu cho ứng dụng như vậy. Bạn sẽ mở rộng giản đồ này sau đó để tạo các truy vấn quan hệ phức tạp.

Bảng phim

Giản đồ cho Phim chứa các lệnh cốt lõi như:

  • @table, cho phép chúng ta đặt tên cho toán tử bằng cách sử dụng đối số singularplural
  • @col để đặt tên cột một cách rõ ràng
  • @default để cho phép đặt giá trị mặc định.
# 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")
}

Giá trị máy chủ và đại lượng vô hướng khoá

Trước khi xem ứng dụng đánh giá phim, hãy giới thiệu các giá trị máy chủđại lượng vô hướng khoá Data Connect.

Khi sử dụng giá trị máy chủ, bạn có thể cho phép máy chủ tự động điền các trường trong bảng bằng cách sử dụng các giá trị đã lưu trữ hoặc dễ dàng tính toán theo các biểu thức cụ thể phía máy chủ. Ví dụ: bạn có thể xác định một trường có dấu thời gian được áp dụng khi truy cập vào trường đó bằng biểu thức updatedAt: Timestamp! @default(expr: "request.time").

Đại lượng vô hướng chính là giá trị nhận dạng đối tượng ngắn gọn mà Data Connect tự động ghép nối từ các trường chính trong giản đồ. Các đại lượng vô hướng chính xoay quanh tính hiệu quả, cho phép bạn tìm thấy thông tin về danh tính và cấu trúc của dữ liệu trong một lệnh gọi. Các khoá này đặc biệt hữu ích khi bạn muốn thực hiện các thao tác tuần tự trên các bản ghi mới và cần một giá trị nhận dạng duy nhất để truyền đến các thao tác sắp tới, cũng như khi bạn muốn truy cập vào các khoá quan hệ để thực hiện các thao tác phức tạp hơn.

Bảng siêu dữ liệu của phim

Bây giờ, hãy theo dõi các đạo diễn phim, cũng như thiết lập mối quan hệ một với một với Movie.

Thêm lệnh @ref để xác định mối quan hệ.

# 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 và MovieActor

Tiếp theo, bạn muốn diễn viên đóng vai chính trong phim của mình và vì bạn có mối quan hệ nhiều với nhiều giữa phim và diễn viên, hãy tạo một bảng chung.

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

Người dùng

Cuối cùng là người dùng ứng dụng.

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

Các loại dữ liệu được hỗ trợ

Data Connect hỗ trợ các loại dữ liệu vô hướng sau đây, với các giá trị gán cho các loại PostgreSQL bằng @col(dataType:).

Loại Data Connect Loại tích hợp sẵn GraphQL hoặc loại tuỳ chỉnh
Data Connect
Loại PostgreSQL mặc định Các loại PostgreSQL được hỗ trợ
(email đại diện trong dấu ngoặc đơn)
Chuỗi GraphQL văn bản text
bit(n), varbit(n)
char(n), varchar(n)
Int GraphQL int Int2 (smallint, Smallserial),
int4 (số nguyên, int, sê-ri)
Nổi GraphQL float8 float4 (thực)
float8 (độ chính xác kép)
số (thập phân)
Boolean GraphQL boolean boolean
mã nhận dạng duy nhất (UUID) Tuỳ chỉnh uuid uuid
Int64 Tuỳ chỉnh bigint int8 (số lớn, số lớn)
số (thập phân)
Ngày Tuỳ chỉnh ngày ngày
Dấu thời gian Tuỳ chỉnh timestamptz

timestamptz

Lưu ý: Thông tin về múi giờ tại địa phương không được lưu trữ.
PostgreSQL chuyển đổi và lưu trữ các dấu thời gian đó dưới dạng UTC.

Vectơ Tuỳ chỉnh vectơ

vectơ

Xem phần Tìm kiếm độ tương đồng vectơ bằng Vertex AI.

  • GraphQL List liên kết đến một mảng một chiều.
    • Ví dụ: [Int] liên kết đến int5[], [Any] liên kết đến jsonb[].
    • Data Connect không hỗ trợ mảng lồng nhau.

Các truy vấn và đột biến ngầm ẩn và được xác định trước

Các truy vấn và đột biến Data Connect sẽ mở rộng một tập hợp truy vấn ngầm ẩnđột biến ngầm ẩn do Data Connect tạo ra dựa trên các loại và mối quan hệ loại trong giản đồ. Các truy vấn ngầm ẩn và các phép biến đổi được tạo bằng công cụ cục bộ mỗi khi bạn chỉnh sửa giản đồ.

Trong quá trình phát triển, bạn sẽ triển khai các truy vấn được xác định trướccác đột biến được xác định trước dựa trên các thao tác ngầm ẩn này.

Tên truy vấn và đột biến ngầm ẩn

Data Connect suy ra tên phù hợp cho các truy vấn ngầm ẩn và các trường hợp đột biến từ nội dung khai báo kiểu giản đồ. Ví dụ: khi làm việc với nguồn PostgreSQL, nếu bạn xác định một bảng có tên là Movie, máy chủ sẽ tạo ngầm ẩn:

  • Truy vấn cho các trường hợp sử dụng bảng đơn có tên thân thiện movie (số ít, để truy xuất kết quả riêng lẻ truyền các đối số như eq) và movies (số nhiều, để truy xuất danh sách kết quả truyền các đối số như gt và các thao tác như orderby). Data Connect cũng tạo truy vấn cho các thao tác đa bảng, quy chiếu có tên rõ ràng như actors_on_movies hoặc actors_via_actormovie.
  • Các đột biến có tên là movie_insert, movie_upsert...

Ngôn ngữ định nghĩa giản đồ cũng cho phép bạn đặt tên rõ ràng cho các thao tác bằng cách sử dụng đối số lệnh singularplural.

Truy vấn cho cơ sở dữ liệu bài đánh giá phim

Bạn xác định truy vấn Data Connect có nội dung khai báo loại thao tác truy vấn, tên thao tác, không hoặc nhiều đối số toán tử và không có hoặc nhiều lệnh có đối số.

Trong phần hướng dẫn nhanh, truy vấn listEmails mẫu không có tham số nào. Tất nhiên, trong nhiều trường hợp, dữ liệu được chuyển đến các trường truy vấn sẽ là dữ liệu động. Bạn có thể sử dụng cú pháp $variableName để xử lý các biến dưới dạng một trong các thành phần của định nghĩa truy vấn.

Vì vậy, truy vấn sau đây có:

  • Định nghĩa kiểu query
  • Tên toán tử (truy vấn) ListMoviesByGenre
  • Một đối số thao tác $genre biến
  • Một lệnh duy nhất, @auth.
query ListMoviesByGenre($genre: String!) @auth(level: USER)

Mỗi đối số truy vấn đều yêu cầu một nội dung khai báo kiểu, một nội dung tích hợp sẵn như String hoặc một kiểu tuỳ chỉnh, do giản đồ xác định như Movie.

Hãy xem chữ ký của những truy vấn ngày càng phức tạp. Cuối cùng, bạn sẽ giới thiệu các biểu thức mối quan hệ mạnh mẽ, súc tích có trong các truy vấn ngầm ẩn mà bạn có thể xây dựng trong các truy vấn được xác định trước.

Số thực chính trong truy vấn

Nhưng trước tiên, hãy lưu ý về đại lượng vô hướng chính.

Data Connect xác định một loại đặc biệt cho các đại lượng vô hướng khoá, được xác định bằng _Key. Ví dụ: loại của một đại lượng vô hướng khoá cho bảng MovieMovie_Key.

Bạn truy xuất các đại lượng vô hướng khoá dưới dạng phản hồi được trả về bởi hầu hết các trường hợp đột biến ngầm ẩn, hoặc tất nhiên là từ các truy vấn mà bạn đã truy xuất tất cả các trường cần thiết để tạo khoá vô hướng.

Các truy vấn tự động số ít, chẳng hạn như movie trong ví dụ đang chạy, hỗ trợ đối số khoá chấp nhận một đại lượng vô hướng khoá.

Bạn có thể truyền một đại lượng vô hướng khoá ở dạng giá trị cố định. Tuy nhiên, bạn có thể xác định các biến để truyền các đại lượng vô hướng của khoá làm dữ liệu đầu vào.

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

Bạn có thể cung cấp các thông tin này trong yêu cầu JSON như sau (hoặc các định dạng chuyển đổi tuần tự khác):

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

Nhờ khả năng phân tích cú pháp vô hướng tuỳ chỉnh, bạn cũng có thể tạo Movie_Key bằng cách sử dụng cú pháp đối tượng có thể chứa các biến. Điều này chủ yếu hữu ích khi bạn muốn chia các thành phần riêng lẻ thành các biến khác nhau vì một số lý do.

Bí danh trong truy vấn

Data Connect hỗ trợ việc gán bí danh GraphQL trong các truy vấn. Với bí danh, bạn đổi tên dữ liệu được trả về trong kết quả của truy vấn. Một truy vấn Data Connect có thể áp dụng nhiều bộ lọc hoặc các thao tác truy vấn khác trong một yêu cầu hiệu quả đến máy chủ, đồng thời phát hành một số "truy vấn phụ" một cách hiệu quả. Để tránh xung đột tên trong tập dữ liệu được trả về, bạn sử dụng bí danh để phân biệt các truy vấn phụ.

Dưới đây là một truy vấn trong đó biểu thức sử dụng bí danh mostPopular.

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

Truy vấn đơn giản có bộ lọc

Truy vấn Data Connect liên kết với tất cả bộ lọc SQL và thao tác thứ tự phổ biến.

Toán tử whereorderBy (truy vấn số ít, số nhiều)

Trả về tất cả các hàng đã so khớp trong bảng (và các mối liên kết lồng nhau). Trả về một mảng trống nếu không có bản ghi nào khớp với bộ lọc.

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

Toán tử limitoffset (truy vấn số ít, số nhiều)

Bạn có thể phân trang kết quả. Các đối số này được chấp nhận nhưng không được trả về trong kết quả.

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

bao gồm các trường mảng

Bạn có thể kiểm tra để đảm bảo rằng một trường mảng có chứa một mục được chỉ định.

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

Thao tác với chuỗi và biểu thức chính quy

Truy vấn của bạn có thể sử dụng các thao tác so sánh và tìm kiếm chuỗi thông thường, bao gồm cả biểu thức chính quy. Lưu ý để đảm bảo tính hiệu quả, bạn đang gói một số hoạt động ở đây và phân biệt các hoạt động đó bằng các bí danh.

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

orand cho bộ lọc tổng hợp

Sử dụng orand cho logic phức tạp hơn.

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

Câu lệnh truy vấn phức tạp

Truy vấn Data Connect có thể truy cập dữ liệu dựa trên mối quan hệ giữa các bảng. Bạn có thể sử dụng mối quan hệ đối tượng (một với một) hoặc mảng (một với nhiều) được xác định trong giản đồ để tạo truy vấn lồng nhau, tức là tìm nạp dữ liệu cho một loại cùng với dữ liệu từ một loại lồng nhau hoặc có liên quan.

Các truy vấn như vậy sử dụng cú pháp Data Connect _on__via thần kỳ trong các truy vấn ngầm ẩn đã tạo.

Bạn sẽ sửa đổi giản đồ từ phiên bản ban đầu của chúng tôi.

Nhiều với một

Hãy thêm bài đánh giá vào ứng dụng của chúng ta, với bảng Review và các sửa đổi đối với 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")
}

Truy vấn nhiều với một

Bây giờ, hãy xem truy vấn có bí danh để minh hoạ cú pháp _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
    }
  }
}

Một với một

Bạn có thể thấy mẫu này. Dưới đây là giản đồ được sửa đổi để minh hoạ.

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

Truy vấn một với một

Bạn có thể truy vấn bằng cú pháp _on_.

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

Nhiều với nhiều

Phim cần có diễn viên và diễn viên cần phim. Các lớp này có mối quan hệ nhiều với nhiều mà bạn có thể lập mô hình bằng bảng nối 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!]!
}

Truy vấn nhiều với nhiều

Hãy xem một truy vấn có sử dụng bí danh để minh hoạ cú pháp _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
    }
  }
}

Các đột biến cho cơ sở dữ liệu bài đánh giá phim

Như đã đề cập, khi bạn xác định một bảng trong giản đồ, Data Connect sẽ tạo các đột biến ngầm ẩn cơ bản cho mỗi bảng.

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

Với những kiến thức này, bạn có thể triển khai các trường hợp CRUD cốt lõi ngày càng phức tạp. Hãy nói nhanh năm lần!

Tạo

Hãy tạo các thành phần cơ bản.

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

Hoặc một thao tác chèn mới.

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

Thực hiện cập nhật

Sau đây là thông tin cập nhật. Nhà sản xuất và đạo diễn chắc chắn hy vọng những điểm xếp hạng trung bình đó sẽ tăng lên.

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

Thực hiện thao tác xoá

Tất nhiên, bạn có thể xoá dữ liệu phim. Những người bảo tồn phim chắc chắn sẽ muốn duy trì phim vật lý lâu nhất có thể.

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

Tại đây, bạn có thể sử dụng _deleteMany.

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

Viết đột biến về quan hệ

Quan sát cách sử dụng đột biến _upsert ngầm ẩn trên một mối quan hệ.

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

Sơ đồ SQL tương đương

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

Tiếp theo là gì?