Projetar esquemas do SQL Connect

Com o Firebase SQL Connect, você projeta um esquema do GraphQL que representa o modelo de dados necessário para seu aplicativo. O SQL Connect converte esse esquema na instância do Cloud SQL para PostgreSQL que dá suporte ao app. Em seguida, você cria consultas e mutações para interagir com o back-end e agrupa essas operações em conectores para usar os dados do código do cliente.

O SQL Connect oferece ferramentas de IA para ajudar você a projetar e implementar seus esquemas. Este guia apresenta conceitos importantes de design de esquema para apoiar e complementar seus fluxos de trabalho padrão e assistidos por IA quando você começar a desenvolver um app e além disso.

O guia para iniciantes apresentou um esquema de app de resenhas de filmes para o PostgreSQL.

Este guia desenvolve ainda mais esse esquema e fornece uma lista SQL equivalente ao esquema final do app de resenhas de filmes.

O esquema de um app de avaliação de filmes

Imagine que você queira criar um serviço que permita aos usuários enviar e ver avaliações de filmes.

Você precisa de um esquema inicial para que esse app seja compatível com consultas básicas. Você vai estender esse esquema mais tarde para criar consultas relacionais complexas.

No SQL Connect, você vai definir tipos do GraphQL para definir o formato dos dados que os clientes podem consultar e manipular. Quando você escreve seu esquema, os tipos são traduzidos para tabelas do Cloud SQL para PostgreSQL, na maioria das vezes em uma relação direta entre tipos do GraphQL e tabelas de banco de dados, embora outros mapeamentos sejam possíveis. Este guia mostra alguns exemplos, do básico ao mais avançado.

Defina um tipo básico de Movie.

Você pode começar com um tipo Movie.

O esquema de Movie contém diretivas principais, como:

  • @table(name) e @col(name) para personalizar os nomes da tabela e da coluna SQL. SQL Connect gera nomes snake_case se não forem especificados.
  • @col(dataType) para personalizar os tipos de coluna SQL.
  • @default para configurar valores padrão de colunas SQL durante a inserção.

Para mais detalhes, confira os documentos de referência de @table, @col e @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
}

Armazenar dados importantes do usuário automaticamente em um tipo User

Seu app vai acompanhar os usuários, então você precisa de um tipo User.

A diretiva @default é especialmente útil nesse caso. O campo id aqui pode capturar automaticamente o ID do usuário na autenticação: observe o uso de @default(expr: "auth.uid") no exemplo a seguir.

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

Escalares de chave e valores de servidor

Antes de analisar o app de resenhas de filmes, é importante apresentar os escalares de chave e os valores do servidor do SQL Connect.

Escalares de chave são identificadores de objeto concisos que o SQL Connect monta automaticamente com base nos campos de chave dos seus esquemas. Os escalares de chave são sobre eficiência, permitindo que você encontre em uma única chamada informações sobre a identidade e a estrutura dos seus dados. Eles são especialmente úteis quando você quer realizar ações sequenciais em novos registros e precisa de um identificador exclusivo para transmitir para as próximas operações, e também quando quer acessar chaves relacionais para realizar outras operações mais complexas.

Com os valores do servidor, é possível permitir que o servidor preencha dinamicamente os campos nas tabelas usando valores armazenados ou facilmente computáveis de acordo com expressões CEL específicas do lado do servidor no argumento expr. Por exemplo, é possível definir um campo com um carimbo de data/hora aplicado quando o campo é acessado usando o horário armazenado em uma solicitação de operação, updatedAt: Timestamp! @default(expr: "request.time").

Processar relações de muitos para muitos nos tipos Actor e MovieActor

Agora que os usuários foram tratados, você pode voltar a modelar os dados de filmes.

Depois, você precisa de atores para estrelar seus filmes.

A tabela Actor é bem simples.

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

Se você quiser que os atores estejam em vários filmes e que os filmes tenham vários atores, será necessário usar uma "tabela de junção".

A tabela MovieActor processa a relação de muitos para muitos, e a chave primária dela é uma combinação de [movie, actor] (os campos de chave externa de movie e actor).

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

Quando você define uma relação SQL na tabela com uma restrição de chave externa, o SQL Connect gera automaticamente o campo correspondente no outro lado. Não é necessário definir o campo de mapeamento reverso (por exemplo, de Actor de volta para MovieActor).

Processar relações de um para um em um tipo MovieMetadata

Agora, acompanhe os diretores de filmes e configure uma relação um para um com Movie.

Use a diretiva @ref para personalizar as restrições de chave externa:

  • @ref(fields) especifica quais campos de chave externa usar.
  • @ref(references) especifica os campos referenciados na tabela de destino (o padrão é a chave primária, mas os campos @unique também funcionam). Essa é uma opção mais avançada. O SQL Connect geralmente pode inferir isso para você.

Para mais detalhes, confira os documentos de referência do @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
}

Usar campos gerados do esquema para criar operações

Suas operações SQL Connect vão estender um conjunto de campos gerados automaticamente pelo SQL Connect com base nos tipos e nas relações de tipo no seu esquema. Esses campos são gerados por ferramentas locais sempre que você edita o esquema.

Suponha que seu esquema contenha um tipo Movie e um tipo Actor associado. O SQL Connect gera os campos movie, movies, actors_on_movies e muito mais.

Consultar com o campo
movie

O campo movie representa um único registro na tabela Movie.

Use esse campo para consultar um único filme pela chave.

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

Consultar com o campo
movies

O campo movies representa uma lista de registros na tabela Movie.

Use esse campo para consultar vários filmes, por exemplo, todos os filmes com um determinado ano.

query GetMovies($myYear: Int!) {
  movies(where: { year: { eq: $myYear } }) { title }
}

Consultar com o campo
actors_on_movies

O campo actors_on_movies representa uma lista de registros que conectam as tabelas Actor e Movie. Use esse campo para consultar todos os atores associados a um determinado filme.

Use esse campo para consultar todos os atores associados a um determinado filme.

  query GetActorsOnMovie($myKey: Movie_Key!) {
    actors_on_movies(where: { movie: { key: { eq: $myKey } } }) {
      actor { name }
    }
  }

Com isso em mente, leia como implementar operações usando esses campos no guia para implementar consultas e no guia para implementar mutações.

Conceitos de esquema mais avançados

Campos de enumeração

O SQL Connect é compatível com campos de enumeração que são mapeados para tipos enumerados do PostgreSQL. Com os tipos enumerados, é possível definir rapidamente uma lista de valores estáticos e predefinidos com uma ordem específica.

Para adicionar um tipo enumerado ao esquema, declare o tipo enumerado e os valores predefinidos dele e faça referência a ele no seu tipo.

enum AspectRatio {
   ACADEMY
   WIDESCREEN
   ANAMORPHIC
   IMAX
   "No information available on aspect ratio"
   UNAVAILABLE
}

type Movie
  @table {
  title: String! 
  genre: String
  description: String
  originalAspectRatio: AspectRatio! @default(value: WIDESCREEN)
  otherAspectRatios: [AspectRatio!]
  tags: [String]
  rating: Float
  imageUrl: String!
  releaseYear: Int
}

No tipo Movie, adicionamos um campo de tipo enumerado originalAspectRatio para a proporção em que o filme foi gravado e outro campo otherAspectRatios para uma lista de outras proporções disponíveis.

Gerenciar mudanças em campos de enumeração

É possível adicionar novos valores ao tipo enumerado, mas a ordem da lista é muito importante. Portanto, insira os novos valores com cuidado. A única mudança totalmente compatível com versões anteriores em um tipo enumerado é adicionar um novo valor ao final da lista de valores. Em especial, inserir um novo valor entre tipos enumerados publicados anteriormente ou reordenar os valores atuais altera a ordem relativa quando operadores relativos, como "menor que", são usados em consultas. Remover ou renomear valores é sempre uma mudança incompatível com versões anteriores.

Nunca reordene os valores na lista de valores do tipo enumerado. A ordenação é importante porque muda a forma como a filtragem é aplicada.

Os ajustes nos valores de tipo enumerado precisam ser feitos com cuidado para não corromper versões mais antigas da operação ou do código do cliente. Ao remover ou renomear um valor de tipo enumerado, verifique se não há instâncias restantes no banco de dados atual.

Como usar os campos de tipo enumerado em operações e no código do cliente

Agora que você adicionou um campo de tipo enumerado ao esquema, use esse campo em consultas e no código do cliente.

Saiba mais sobre como escrever consultas usando tipos enumerados e como escrever um cliente para permitir ajustes nos tipos enumerados no guia de consultas.

Outros conceitos avançados

Para ir além dos tipos e relacionamentos básicos, mas úteis, consulte os exemplos na documentação de referência.

Tipos de dados compatíveis

O SQL Connect é compatível com os seguintes tipos de dados escalares, com atribuições a tipos do PostgreSQL usando @col(dataType:).

Tipo SQL Connect Tipo integrado do GraphQL ou tipo personalizado
SQL Connect
Tipo padrão do PostgreSQL Tipos do PostgreSQL compatíveis
(alias entre parênteses)
String GraphQL texto text
bit(n), varbit(n)
char(n), varchar(n)
Int GraphQL int Int2 (smallint, smallserial),
int4 (integer, int, serial)
Ponto flutuante GraphQL float8 float4 (real)
float8 (precisão dupla)
numeric (decimal)
Booleano GraphQL booleano booleano
UUID Personalizado uuid uuid
Int64 Personalizado bigint int8 (bigint, bigserial)
numeric (decimal)
Data Personalizado date data
Carimbo de data/hora Personalizado timestamptz

timestamptz

Observação: as informações de fuso horário local não são armazenadas.
O PostgreSQL converte e armazena esses carimbos de data/hora como UTC.

Enumeração Personalizado enum

tipo enumerado

Vetor Personalizado vector

vetor

Consulte Realizar pesquisa de similaridade vetorial com a Vertex AI.

  • O GraphQL List é mapeado para uma matriz unidimensional.
    • Por exemplo, [Int] é mapeado para int5[], e [Any] é mapeado para jsonb[].
    • SQL Connect não é compatível com matrizes aninhadas.

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

Próximas etapas

Talvez você se interesse por: