Implementar consultas do Data Connect

Com o Firebase Data Connect, é possível criar conectores para suas instâncias do PostgreSQL gerenciadas com o Google Cloud SQL. Esses conectores são combinações de consultas e mutações para usar os dados do seu esquema.

O guia de início rápido apresentou um esquema de app de avaliação de filmes para PostgreSQL.

Esse guia também apresentou operações administrativas implantáveis e ad hoc, incluindo consultas.

  • As consultas implantáveis são aquelas que você implementa para chamar de apps clientes, com endpoints de API definidos por você. Você os agrupa em um conector implantado no servidor. As ferramentas do Data Connect geram SDKs de cliente com base na sua API. As consultas implantadas não são protegidas pela política do IAM. Portanto, proteja-as usando a diretiva Data Connect @auth.
  • As consultas administrativas ad hoc são executadas em ambientes privilegiados para ler dados. É possível criar e executar essas consultas no console do Firebase ou em ambientes de desenvolvimento local usando a extensão Data Connect do VS Code.

Este guia analisa mais detalhadamente as consultas implantáveis.

Recursos das consultas Data Connect

Com o Data Connect, você pode fazer consultas básicas de todas as maneiras esperadas em um banco de dados PostgreSQL.

Mas com as extensões do Data Connect para GraphQL, é possível implementar consultas avançadas para apps mais rápidos e eficientes:

  • Use escalares de chave retornados por muitas operações para simplificar operações repetidas em registros.
  • Realize consultas no decorrer de operações de mutação de várias etapas para pesquisar dados, economizando linhas de código e viagens de ida e volta ao servidor.

Usar campos gerados para criar consultas

Suas operações Data Connect vão estender um conjunto de campos Data Connect gerados automaticamente 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.

É possível usar campos gerados para implementar consultas cada vez mais complexas, desde a recuperação de registros individuais ou múltiplos de tabelas únicas até vários registros de tabelas relacionadas.

Suponha que seu esquema contenha um tipo Movie e um tipo Actor associado. O Data Connect gera 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 este 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 de 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 este campo para consultar todos os atores associados a um determinado filme.

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

Elementos essenciais de uma consulta

As consultas do Data Connect são consultas GraphQL com extensões do Data Connect. Assim como em uma consulta GraphQL normal, você pode definir um nome de operação e uma lista de variáveis GraphQL.

O Data Connect estende as consultas GraphQL com diretivas personalizadas, como @auth.

Portanto, a consulta a seguir tem:

  • Uma definição de tipo query
  • Um nome de operação (consulta) ListMoviesByGenre
  • Um único argumento de consulta, aqui uma variável $genre do tipo String
  • Uma única diretiva, @auth.
  • Um único campo, movies.
query ListMoviesByGenre($genre: String!) @auth(level: PUBLIC) {
  movies(where: { genre: { eq: $genre } }) {
    id
    title
  }
}

Todo argumento de consulta exige uma declaração de tipo, um tipo integrado como String ou um tipo personalizado definido pelo esquema, como Movie.

Este guia vai analisar a assinatura de consultas cada vez mais complexas. Você vai terminar apresentando expressões de relacionamento poderosas e concisas que podem ser usadas para criar consultas implantáveis.

Escalares de chave em consultas

Mas primeiro, uma observação sobre escalares importantes.

Data Connect define um escalar de chave especial para representar chaves primárias de cada tabela, identificadas por {TableType}_Key. É um objeto JSON de valores de chave primária.

Você recupera escalares de chave como uma resposta retornada pela maioria dos campos de leitura gerados automaticamente ou, é claro, de consultas em que você recuperou todos os campos necessários para criar a chave escalar.

Consultas automáticas singulares, como movie no nosso exemplo em execução, oferecem suporte a um argumento de chave que aceita um escalar de chave.

Você pode transmitir um escalar de chave como um literal. No entanto, é possível definir variáveis para transmitir escalares de chave como entrada.

Consulta

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

Resposta

{
  "data": {
    "movie": {
      "title": "Example Movie Title"
    }
  }
}
    

Eles podem ser fornecidos em JSON de solicitação assim (ou em outros formatos de serialização):

{
  # 
  "variables": {
    "myKey": {"id": "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"}
  }
}

Graças à análise escalar personalizada, um Movie_Key também pode ser construído usando a sintaxe de objeto, que pode conter variáveis. Isso é útil principalmente quando você quer dividir componentes individuais em variáveis diferentes por algum motivo.

Escrever consultas básicas

Você pode começar a escrever consultas para receber registros individuais do seu banco de dados ou listar registros de uma tabela com a opção de limitar e ordenar os resultados.

Recuperar registros individuais

A consulta mais simples recebe um único registro por ID. Sua consulta vai usar o campo movie gerado automaticamente.

Consulta

query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    id
    title
    imageUrl
    genre
  }
}
    

Resposta

{
  "data": {
    "movie": {
      "id": "some-uuid",
      "title": "Example Movie Title",
      "imageUrl": "https://example.com/movie.jpg",
      "genre": "Action"
    }
  }
}
    

Recuperar todos os registros em uma tabela

Para recuperar um subconjunto de campos da lista completa de filmes da tabela Movies, sua consulta vai usar o campo movies gerado automaticamente, e sua implementação pode ser semelhante a esta:

Consulta

query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    genre
  }
}
    

Resposta

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title",
        "imageUrl": "https://example.com/movie.jpg",
        "genre": "Action"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title",
        "imageUrl": "https://example.com/another-movie.jpg",
        "genre": "Comedy"
      }
    ]
  }
}
    

Usar os operadores orderBy, limit e offset

É claro que listar todos os registros de uma tabela tem utilidade limitada.

É possível ordenar e paginar os resultados. Esses argumentos são aceitos, mas não retornados nos resultados.

Aqui, a consulta recebe os títulos dos 10 melhores filmes por classificação.

Consulta

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

Resposta

{
  "data": {
    "movies": [
      { "title": "Top Movie 1" },
      { "title": "Top Movie 2" },
      { "title": "Top Movie 3" }
      // ... other 7 movies
    ]
  }
}
    

Você pode ter um caso de uso para buscar linhas de um deslocamento, como filmes de 11 a 20 ordenados por classificação.

Consulta

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

Resposta

{
  "data": {
    "movies": [
      { "title": "Movie 11" },
      { "title": "Movie 12" },
      { "title": "Movie 13" }
      // ... other 7 movies
    ]
  }
}
    

Usar aliases em consultas

O Data Connect oferece suporte a alias do GraphQL em consultas. Com os aliases, você renomeia os dados retornados nos resultados de uma consulta. Uma única consulta Data Connect pode aplicar vários filtros ou outras operações de consulta em uma solicitação eficiente ao servidor, emitindo várias "subconsultas" de uma só vez. Para evitar conflitos de nomes no conjunto de dados retornado, use aliases para distinguir as subconsultas.

Confira uma consulta em que uma expressão usa os aliases mostPopular e leastPopular.

Consulta

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

Resposta

{
  "data": {
    "mostPopular": [
      { "popularity": 9 }
    ],
    "leastPopular": [
      { "popularity": 1 }
    ]
  }
}
    

Usar filtros de consulta

As consultas Data Connect são mapeadas para todos os filtros e operações de ordenação comuns do SQL.

Filtrar com operadores where e orderBy

Retorna todas as linhas correspondentes da tabela (e associações aninhadas). Retorna uma matriz vazia se nenhum registro corresponder ao filtro.

Consulta

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

Resposta

{
  "data": {
    "mostPopular": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title",
        "genre": "Action",
        "description": "A great movie"
      }
    ]
  }
}
    

Filtrar testando valores nulos

É possível testar valores null usando o operador isNull.

Consulta

query ListMoviesWithoutDescription {
  movies(where: { description: { isNull: true }}) {
    id
    title
  }
}
    

Resposta

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title"
      }
    ]
  }
}
    

Para mais operadores, consulte o guia de referência de tipos de objetos de entrada.

Filtrar com comparações de valores

Você pode usar operadores como lt (menor que) e ge (maior ou igual a) para comparar valores nas consultas.

Consulta

query ListMoviesByRating($minRating: Int!, $maxRating: Int!) {
  movies(where: { rating: { ge: $minRating, lt: $maxRating }}) {
    id
    title
  }
}
    

Resposta

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title"
      }
    ]
  }
}
    

Filtrar com operadores includes e excludes para campos de matriz

Você pode testar se um campo de matriz inclui um item especificado.

O exemplo a seguir ilustra o operador includes.

O Data Connect é compatível com includesAll, excludes, excludesAll e muito mais. Confira todos esses operadores para números inteiros, strings, datas e outros tipos de dados nos cabeçalhos _ListFilter da documentação de referência.

Consulta

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

Resposta

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      }
    ]
  }
}
    

Filtrar com operações de string e expressões regulares

Suas consultas podem usar operações típicas de pesquisa e comparação de strings, incluindo expressões regulares. Para eficiência, você está agrupando várias operações aqui e desambiguando-as com aliases.

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

Filtrar com a lógica dos operadores _or, _and e _not

Use _or para uma lógica mais complexa. O Data Connect também é compatível com os operadores _and e _not.

Consulta

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

Resposta

{
  "data": {
    "movies": [
      { "title": "Movie Title 1" },
      { "title": "Movie Title 2" }
    ]
  }
}
    

Criar consultas relacionais

As consultas Data Connect podem acessar dados com base nas relações entre tabelas. Você pode usar as relações de objeto (um para um) ou matriz (um para muitos) definidas no seu esquema para fazer consultas aninhadas, ou seja, buscar dados de um tipo junto com dados de um tipo aninhado ou relacionado.

Essas consultas usam a sintaxe mágica Data Connect _on_ e _via em campos de leitura gerados.

Não se esqueça de analisar o esquema de exemplo.

Muitos para um

Agora vamos analisar uma consulta para ilustrar a sintaxe _on_.

Consulta

query MyReviews @auth(level: USER) {
  user(key: {id_expr: "auth.uid"}) {
    reviews: reviews_on_user {
      movie { name }
      rating
    }
  }
}
    

Resposta

{
  "data": {
    "user": {
      "reviews": [
        {
          "movie": { "name": "Movie Title" },
          "rating": 5
        }
      ]
    }
  }
}
    

Um para um

É possível escrever uma consulta individual usando a sintaxe _on_.

Consulta

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

Resposta

{
  "data": {
    "movie": {
      "movieMetadatas_on_movie": {
        "director": "Some Director"
      }
    }
  }
}
    

Muitos para muitos

As consultas de muitos para muitos usam a sintaxe _via_. Uma consulta de muitos para muitos pode recuperar atores de um filme específico.

Consulta

query MoviesActors($id: UUID!) @auth(level: USER) {
  movie(id: $id) {
     actors: actors_via_MovieActors {
        name
     }
  }
}
    

Resposta

{
  "data": {
    "movie": {
      "actors": [
        {
          "name": "Actor Name"
        }
      ]
    }
  }
}
    

Mas podemos escrever uma consulta mais complexa, usando aliases, para filtrar com base em role e recuperar atores e filmes associados nos resultados mainActors e supportingActors. Como é uma relação de muitos para muitos, a sintaxe _via_ é usada.

Consulta

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

Resposta

{
  "data": {
    "movie": {
      "mainActors": [
        {
          "name": "Main Actor Name"
        }
      ],
      "supportingActors": [
        {
          "name": "Supporting Actor Name"
        }
      ]
    },
    "actor": {
      "mainRoles": [
        {
          "title": "Main Role Movie Title"
        }
      ],
      "supportingRoles": [
        {
          "title": "Supporting Role Movie Title"
        }
      ]
    }
  }
}
    

Consultas de agregação

O que são agregações e por que usá-las?

Com os campos de agregação, é possível fazer cálculos em uma lista de resultados. Com os campos de agregação, você pode fazer o seguinte:

  • Encontrar a pontuação média de uma avaliação
  • Encontrar o custo total dos itens em um carrinho de compras
  • Encontrar o produto com a melhor ou pior avaliação
  • Contar o número de produtos na sua loja

As agregações são realizadas no servidor, o que oferece vários benefícios em relação ao cálculo do lado do cliente:

  • Performance mais rápida do app (já que você evita cálculos do lado do cliente)
  • Redução dos custos de saída de dados, já que você envia apenas os resultados agregados em vez de todas as entradas.
  • Segurança aprimorada (já que você pode dar aos clientes acesso a dados agregados em vez de todo o conjunto de dados)

Exemplo de esquema para agregações

Nesta seção, vamos mudar para um exemplo de esquema de vitrine, que é bom para explicar como usar agregações:

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

Agregações simples

_count para todos os campos

O campo de agregação mais simples é _count, que retorna quantas linhas correspondem à sua consulta. Para cada campo no seu tipo, o Data Connect gera campos agregados correspondentes, dependendo do tipo de campo.

Consulta

query CountProducts {
  products {
    _count
  }
}

Resposta
one

Por exemplo, se você tiver cinco produtos no banco de dados, o resultado será:

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

Todos os campos têm um campo <field>_count, que conta quantas linhas têm um valor não nulo nesse campo.

Consulta

query CountProductsWithExpirationDate {
  products {
    expirationDate_count
  }
}

Resposta
field_count

Por exemplo, se você tiver três produtos com uma data de validade, o resultado será:

{
  "products": [
    {
    "expirationDate_count": 3
    }
  ]
}
_min, _max, _sum e _avg para campos numéricos

Os campos numéricos (int, float, int64) também têm <field>_min, <field>_max, <field>_sum e <field>_avg.

Consulta

query NumericAggregates {
  products {
  quantityInStock_max
  price_min
  price_avg
  quantityInStock_sum
  }
}

Resposta
_min _max _sum _avg

Por exemplo, se você tiver os seguintes produtos:

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

O resultado seria:

{
  "products": [
    {
    "quantityInStock_max": 20,
    "price_min": 1.99,
    "price_avg": 3.6566666666666666,
    "quantityInStock_sum": 35
    }
  ]
}
_min e _max para datas e carimbos de data/hora

Os campos de data e carimbo de data/hora têm <field>_min e <field>_max.

Consulta

query DateAndTimeAggregates {
  products {
  expirationDate_max
  expirationDate_min
  }
}

Resposta
_min _maxdatetime

Por exemplo, se você tiver as seguintes datas de expiração:

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

O resultado seria:

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

Distinto

O argumento distinct permite extrair todos os valores exclusivos de um campo (ou combinação de campos). Exemplo:

Consulta

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

Resposta
distinct

Por exemplo, se você tiver os seguintes fabricantes:

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

O resultado seria:

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

Também é possível usar o argumento distinct em campos agregados para agregar os valores distintos. Exemplo:

Consulta

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

Resposta
distinctonaggregate

Por exemplo, se você tiver os seguintes fabricantes:

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

O resultado seria:

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

Agregações agrupadas

Para fazer uma agregação agrupada, selecione uma combinação de campos agregados e não agregados em um tipo. Isso agrupa todas as linhas correspondentes que têm o mesmo valor para os campos não agregados e calcula os campos agregados para esse grupo. Exemplo:

Consulta

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

Resposta
groupedaggregates

Por exemplo, se você tiver os seguintes produtos:

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

O resultado seria:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}
having e where com agregados agrupados

Também é possível usar o argumento having e where para retornar apenas grupos que atendam a um critério fornecido.

  • having permite filtrar grupos pelos campos agregados.
  • Com where, é possível filtrar as linhas com base em campos não agregados.

Consulta

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

Resposta
havingwhere

Por exemplo, se você tiver os seguintes produtos:

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

O resultado seria:

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

Agrega em várias tabelas

Os campos de agregação podem ser usados em conjunto com os campos de relacionamento um-para-muitos gerados para responder a perguntas complexas sobre seus dados. Confira um esquema modificado, com uma tabela separada, Manufacturer, que podemos usar nos exemplos:

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

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

Agora podemos usar campos agregados para fazer coisas como descobrir quantos produtos um fabricante produz:

Consulta

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

Resposta
aggregatesacrosstables

Por exemplo, se você tiver os seguintes fabricantes:

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

O resultado seria:

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

Escrever consultas avançadas: usar campos query para ler dados em operações de várias etapas

Há muitas situações em que você pode querer ler seu banco de dados durante a execução de uma mutação para pesquisar e verificar dados existentes antes de realizar, por exemplo, inserções ou atualizações. Essas opções economizam operações de ida e volta e, portanto, custos.

O Data Connect é compatível com essa funcionalidade. Consulte operações de várias etapas.

Próximas etapas

Talvez você se interesse por: