O Firebase Data Connect oferece várias maneiras de interagir com seu banco de dados do Cloud SQL:
- GraphQL nativo: define tipos em
schema.gqle Data Connect e traduz suas operações GraphQL para SQL. Essa é a abordagem padrão, que oferece tipagem forte e estruturas com esquema aplicado. A maior parte da documentação do Data Connect fora desta página discute essa opção. Quando possível, use esse método para aproveitar a segurança de tipo completa e o suporte a ferramentas. - A diretiva
@view: define um tipo do GraphQL emschema.gqlcom suporte de uma instrução SQLSELECTpersonalizada. Isso é útil para criar visualizações somente leitura e fortemente tipadas com base em uma lógica SQL complexa. Esses tipos podem ser consultados como os tipos regulares. Consulte@view. - SQL nativo: incorpore instruções SQL diretamente em operações
nomeadas em .
gqlusando campos raiz especiais. Isso oferece máxima flexibilidade e controle direto, especialmente para operações que não são facilmente expressas em GraphQL padrão, aproveitando recursos específicos do banco de dados ou usando extensões do PostgreSQL.
Este guia se concentra na opção SQL nativo.
Casos de uso comuns do SQL nativo
Embora o GraphQL nativo ofereça segurança de tipos total, e a diretiva @view forneça resultados fortemente tipados para relatórios SQL somente leitura, o SQL nativo oferece a flexibilidade necessária para:
- Extensões do PostgreSQL: consulte e use diretamente qualquer extensão instalada do PostgreSQL (como
PostGISpara dados geoespaciais) sem precisar mapear tipos complexos no seu esquema GraphQL. - Consultas complexas: execute SQL complexo com junções, subconsultas, agregações, funções de janela e procedimentos armazenados.
- Manipulação de dados (DML): execute operações
INSERT, UPDATE, DELETEdiretamente. No entanto, não use SQL nativo para comandos da linguagem de definição de dados (DDL). Você precisa continuar fazendo alterações no nível do esquema usando GraphQL para manter o back-end e os SDKs gerados sincronizados. - Recursos específicos do banco de dados: use funções, operadores ou tipos de dados exclusivos do PostgreSQL.
- Otimização de performance: ajuste manual de instruções SQL para caminhos críticos.
Campos raiz do SQL nativo
Para gravar operações com SQL, use um dos seguintes campos raiz dos tipos
query ou mutation:
query campos
| Campo | Descrição |
|---|---|
_select |
Executa uma consulta SQL que retorna zero ou mais linhas. Argumentos:
Retorna: uma matriz JSON ( |
_selectFirst |
Executa uma consulta SQL que deve retornar zero ou uma linha. Argumentos:
Retorna: um objeto JSON ( |
mutation campos
| Campo | Descrição |
|---|---|
_execute |
Executa uma instrução DML ( Argumentos:
Retorna: um As cláusulas |
_executeReturning |
Executa uma instrução DML com uma cláusula Argumentos:
Retorna: uma matriz JSON ( |
_executeReturningFirst |
Executa uma instrução DML com uma cláusula Argumentos:
Retorna: um objeto JSON ( |
Observações:
As operações são executadas usando as permissões concedidas à conta de serviço do Data Connect.
Se você definir explicitamente o nome da tabela usando a diretiva
@table(@table(name: "ExampleTable")), também será necessário colocar o nome da tabela entre aspas nas instruções SQL (SELECT field FROM "ExampleTable" ...).Sem as aspas, Data Connect vai converter o nome da tabela em snake case (
example_table).
Regras e limitações de sintaxe
O SQL nativo aplica regras de análise estritas para garantir a segurança e evitar a injeção de SQL. Atente-se às seguintes restrições:
- Comentários: use comentários de bloco (
/* ... */). Os comentários de linha (--) são proibidos porque podem truncar cláusulas subsequentes (como filtros de segurança) durante a concatenação de consultas. - Parâmetros: use parâmetros posicionais (
$1,$2) que correspondam à ordem da matrizparams. Parâmetros nomeados ($id,:name) não são compatíveis. - Strings: literais de string estendidos (
E'...') e strings entre cifrões ($$...$$) são compatíveis. Os escapes Unicode do PostgreSQL (U&'...') não são compatíveis.
Parâmetros em comentários
O analisador ignora tudo o que está dentro de um comentário em bloco. Se você comentar uma linha
que contém um parâmetro (por exemplo, /* WHERE id = $1 */), também será necessário
remover esse parâmetro da lista params. Caso contrário, a operação vai falhar com
o erro unused parameter: $1.
Exemplos
Exemplo 1: SELECT básico com alias de campo
É possível criar um alias para o campo raiz (por exemplo, movies: _select) para deixar a resposta do cliente mais limpa (data.movies em vez de data._select).
queries.gql:
query GetMoviesByGenre($genre: String!, $limit:Int!) @auth(level: PUBLIC) {
movies: _select(
sql: """
SELECT id, title, release_year, rating
FROM movie
WHERE genre = $1
ORDER BY release_year DESC
LIMIT $2
""",
params: [$genre, $limit]
)
}
Depois de executar a consulta usando um SDK do cliente, o resultado estará em data.movies.
Exemplo 2: UPDATE básico
mutations.gql:
mutation UpdateMovieRating($movieId: UUID!, $newRating: Float!) @auth(level: NO_ACCESS) {
_execute(
sql: """
UPDATE movie
SET rating = $2
WHERE id = $1
""",
params: [$movieId, $newRating]
)
}
Depois de executar a mutação usando um SDK do cliente, o número de linhas afetadas vai
estar em data._execute.
Exemplo 3: agregação básica
queries.gql:
query GetTotalReviewCount @auth(level: PUBLIC) {
stats: _selectFirst(
sql: "SELECT COUNT(*) as total_reviews FROM \"Reviews\""
)
}
Depois de executar a consulta usando um SDK do cliente, o resultado estará em
data.stats.total_reviews.
Exemplo 4: agregação avançada com RANK
queries.gql:
query GetMoviesRankedByRating @auth(level: PUBLIC) {
_select(
sql: """
SELECT
id,
title,
rating,
RANK() OVER (ORDER BY rating DESC) as rank
FROM movie
WHERE rating IS NOT NULL
LIMIT 20
""",
params: []
)
}
Depois de executar a consulta usando um SDK do cliente, o resultado estará em
data._select.
Exemplo 5: UPDATE com RETURNING e contexto de autenticação
mutations.gql:
mutation UpdateMyReviewText($movieId: UUID!, $newText: String!) @auth(level: USER) {
updatedReview: _executeReturningFirst(
sql: """
UPDATE "Reviews"
SET review_text = $2
WHERE movie_id = $1 AND user_id = $3
RETURNING movie_id, user_id, rating, review_text
""",
params: [$movieId,$newText,{_expr: "auth.uid" }]
)
}
Depois de executar a mutação usando um SDK do cliente, os dados atualizados da postagem estarão em
data.updatedReview.
Exemplo 6: CTE avançado com upserts (get-or-create atômico)
Esse padrão é útil para garantir que registros dependentes (como usuários ou filmes) existam antes de inserir um registro filho (como uma avaliação), tudo em uma única transação de banco de dados.
mutations.gql:
mutation CreateMovieCTE($movieId: UUID!, $userId: UUID!, $reviewId: UUID!) {
_execute(
sql: """
WITH
new_user AS (
INSERT INTO "user" (id, username)
VALUES ($2, 'Auto-Generated User')
ON CONFLICT (id) DO NOTHING
RETURNING id
),
movie AS (
INSERT INTO movie (id, title, image_url, release_year, genre)
VALUES ($1, 'Auto-Generated Movie', 'https://placeholder.com', 2025, 'Sci-Fi')
ON CONFLICT (id) DO NOTHING
RETURNING id
)
INSERT INTO "Reviews" (id, movie_id, user_id, rating, review_text, review_date)
VALUES (
$3,
$1,
$2,
5,
'Good!',
NOW()
)
""",
params: [$movieId, $userId, $reviewId]
)
}
Exemplo 7: como usar extensões do Postgres
O SQL nativo permite usar extensões do Postgres, como o PostGIS, sem precisar mapear tipos de geometria complexos no esquema do GraphQL ou alterar as tabelas subjacentes.
Neste exemplo, suponha que o app de restaurante tenha uma tabela que armazena dados de localização em uma coluna JSON de metadados (por exemplo, {"latitude": 37.3688, "longitude": -122.0363}). Se você ativou a extensão PostGIS, use operadores JSON padrão do Postgres (->>) para extrair esses valores na hora e transmiti-los para a função ST_MakePoint do PostGIS.
query GetNearbyActiveRestaurants($userLong: Float!, $userLat: Float!, $maxDistanceMeters: Float!) @auth(level: USER) {
nearby: _select(
sql: """
SELECT
id,
name,
tags,
ST_Distance(
ST_MakePoint((metadata->>'longitude')::float, (metadata->>'latitude')::float)::geography,
ST_MakePoint($1, $2)::geography
) as distance_meters
FROM restaurant
WHERE active = true
AND metadata ? 'longitude' AND metadata ? 'latitude'
AND ST_DWithin(
ST_MakePoint((metadata->>'longitude')::float, (metadata->>'latitude')::float)::geography,
ST_MakePoint($1, $2)::geography,
$3
)
ORDER BY distance_meters ASC
LIMIT 10
""",
params: [$userLong, $userLat, $maxDistanceMeters]
)
}
Depois de executar a consulta usando um SDK do cliente, o resultado estará em data.nearby.
Práticas recomendadas de segurança: SQL dinâmico e procedimentos armazenados
O Data Connect parametriza com segurança todas as entradas no limite de GraphQL para banco de dados, protegendo totalmente suas consultas SQL padrão contra injeção de SQL de primeira ordem. No entanto, se você usar SQL para chamar procedimentos ou funções armazenados personalizados do Postgres que executam SQL dinâmico, verifique se o código PL/pgSQL interno processa esses parâmetros com segurança.
Se o procedimento armazenado concatenar diretamente as entradas do usuário em uma string EXECUTE, ele vai ignorar a parametrização e criar uma vulnerabilidade de injeção de SQL de segunda ordem:
-- INSECURE: Do not concatenate parameters into dynamic strings!
CREATE OR REPLACE PROCEDURE unsafe_update(user_input TEXT)
LANGUAGE plpgsql AS $$
BEGIN
-- A malicious user_input (e.g., "val'; DROP TABLE users; --") will execute as code.
EXECUTE 'UPDATE target_table SET status = ''' || user_input || '''';
END;
$$;
Para evitar isso, siga estas práticas recomendadas:
- Use a cláusula
USING: ao escrever SQL dinâmico em procedimentos armazenados, use sempre a cláusulaUSINGpara vincular parâmetros de dados com segurança. - Use
format()para identificadores: useformat()com a flag%Ipara injeção segura de identificadores de banco de dados (como nomes de tabelas). - Permita apenas identificadores: não deixe que os aplicativos cliente escolham identificadores de banco de dados de forma arbitrária. Se o procedimento exigir identificadores dinâmicos, valide a entrada em relação a uma lista de permissão fixa na lógica PL/pgSQL antes da execução.
-- SECURE: Use format() for identifiers and USING for data values
CREATE OR REPLACE PROCEDURE secure_update(target_table TEXT, new_value TEXT, row_id INT)
LANGUAGE plpgsql AS $$
BEGIN
-- Validate the dynamic table name against an allowlist
IF target_table NOT IN ('orders', 'users', 'inventory') THEN
RAISE EXCEPTION 'Invalid table name';
END IF;
-- Execute securely
EXECUTE format('UPDATE %I SET status = $1 WHERE id = $2', target_table)
USING new_value, row_id;
END;
$$;