Firebase Data Connect oferece várias maneiras de interagir com o banco de dados do Cloud SQL:
- GraphQL nativo: defina tipos no arquivo
schema.gql, e o Data Connect vai traduzir suas operações GraphQL para SQL. Essa é a abordagem padrão, que oferece estruturas com tipagem forte e aplicadas pelo esquema. A maior parte da Data Connect documentação fora desta página discute esta opção. Sempre que possível, use esse método para aproveitar a segurança de tipo completa e o suporte a ferramentas. - A diretiva
@view: defina um tipo GraphQL emschema.gqlcom suporte de uma instrução SQL personalizadaSELECT. Isso é útil para criar visualizações somente leitura e com tipagem forte com base em uma lógica SQL complexa. Esses tipos podem ser consultados como tipos normais. Consulte@view. - SQL nativo: incorpore instruções SQL diretamente em operações nomeadas
em arquivos .
gqlusando campos raiz especiais. Isso oferece flexibilidade máxima 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. Ao contrário do GraphQL e da diretiva@view, o SQL nativo não oferece saída com tipagem forte.
Este guia se concentra na opção SQL nativo.
Casos de uso comuns para SQL nativo
Embora o GraphQL nativo ofereça segurança de tipo completa e a diretiva @view ofereça resultados com tipagem forte 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 do PostgreSQL
instalada (como
PostGISpara dados geoespaciais) sem precisar mapear tipos complexos no 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, na sigla em inglês). 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 desempenho: ajuste manualmente instruções SQL para caminhos críticos.
Campos raiz de SQL nativo
Para gravar operações com SQL, use um dos seguintes campos raiz dos tipos query ou mutation:
Campos query
| 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 ( |
Campos mutation
| 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 à Data Connect conta de serviço.
Se você definir explicitamente o nome da tabela usando a
@tablediretiva (@table(name: "ExampleTable")), também precisará 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 para snake case (
example_table).
Regras e limitações de sintaxe
O SQL nativo aplica regras de análise sintática rigorosas para garantir a segurança e evitar a injeção de SQL. Esteja ciente das 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 de posição (
$1,$2) que correspondam à ordem da matrizparams. Os parâmetros nomeados ($id,:name) não são aceitos. - Strings: literais de string estendidos (
E'...') e strings entre cifrões ($$...$$) são aceitos. Os escapes Unicode do PostgreSQL (U&'...') não são aceitos.
Parâmetros em comentários
O analisador sintático ignora tudo dentro de um comentário de bloco. Se você comentar uma linha que contém um parâmetro (por exemplo, /* WHERE id = $1 */), também precisará 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 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 os 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 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ê tiver ativado a extensão PostGIS,
poderá usar operadores JSON padrão do Postgres (->>) para extrair esses valores em
tempo real 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
Data Connect parametriza com segurança todas as entradas no limite do GraphQL para o 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, será necessário garantir que o código PL/pgSQL interno processe 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 gravar SQL dinâmico em procedimentos armazenados, sempre use 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 identificadores estritamente: não permita que os aplicativos cliente escolham identificadores de banco de dados arbitrariamente. Se o procedimento exigir identificadores dinâmicos, valide a entrada em relação a uma lista de permissões codificada no seu código 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;
$$;