Firebase Data Connect предлагает несколько способов взаимодействия с вашей базой данных Cloud SQL:
- Встроенный GraphQL : Определите типы в файле
schema.gql, и Data Connect преобразует ваши операции GraphQL в SQL. Это стандартный подход, предлагающий строгую типизацию и структуры, обеспечиваемые схемой. Большая часть документации Data Connect за исключением этой страницы, посвящена именно этому варианту. По возможности следует использовать этот метод, чтобы воспользоваться преимуществами полной типобезопасности и поддержки инструментов. - Директива
@view: определяет тип GraphQL вschema.gqlподкрепленный пользовательским SQL-запросомSELECT. Это полезно для создания представлений только для чтения со строгой типизацией, основанных на сложной логике SQL. Эти типы доступны для запросов как обычные типы. См.@view. - Встроенный SQL : Встраивание SQL-запросов непосредственно в именованные операции в файлах
gqlс использованием специальных корневых полей. Это обеспечивает максимальную гибкость и прямой контроль, особенно для операций, которые сложно выразить в стандартном GraphQL, с использованием специфических функций базы данных или расширений PostgreSQL.
В этом руководстве основное внимание уделяется опции Native SQL .
Типичные сценарии использования нативного SQL
Хотя нативный GraphQL обеспечивает полную типобезопасность, а директива @view предлагает строго типизированные результаты для SQL-отчетов только для чтения, нативный SQL обеспечивает гибкость, необходимую для:
- Расширения PostgreSQL : Позволяет напрямую запрашивать и использовать любые установленные расширения PostgreSQL (например,
PostGISдля геопространственных данных) без необходимости сопоставления сложных типов в вашей схеме GraphQL. - Сложные запросы : Выполнение сложных SQL-запросов с использованием объединений, подзапросов, агрегаций, оконных функций и хранимых процедур.
- Манипулирование данными (DML) : Выполняйте операции
INSERT, UPDATE, DELETEнапрямую. (Однако не используйте нативный SQL для команд языка определения данных (DDL). Необходимо продолжать вносить изменения на уровне схемы с помощью GraphQL, чтобы поддерживать синхронизацию бэкэнда и сгенерированных SDK.) - Особенности, специфичные для базы данных : Используйте функции, операторы или типы данных, уникальные для PostgreSQL.
- Оптимизация производительности : ручная настройка SQL-запросов для критически важных участков кода.
Корневые поля нативного SQL
Для написания операций с использованием SQL применяйте одно из следующих корневых полей типа query или mutation :
поля query
| Поле | Описание |
|---|---|
_select | Выполняет SQL-запрос, возвращающий ноль или более строк. Аргументы :
Возвращает : массив JSON ( |
_selectFirst | Выполняет SQL-запрос, который, как ожидается, вернет ноль или одну строку. Аргументы :
Возвращает : JSON-объект ( |
поля mutation
| Поле | Описание |
|---|---|
_execute | Выполняет оператор DML ( Аргументы :
Возвращает : При формировании результата условия |
_executeReturning | Выполняет оператор DML с предложением Аргументы :
Возвращает : массив JSON ( |
_executeReturningFirst | Выполняет оператор DML с предложением Аргументы :
Возвращает : JSON-объект ( |
Примечания:
Операции выполняются с использованием прав доступа, предоставленных учетной записи службы Data Connect.
Если вы явно указываете имя таблицы с помощью директивы
@table(@table(name: "ExampleTable")), вам также необходимо заключить имя таблицы в кавычки в ваших SQL-запросах (SELECT field FROM "ExampleTable" ...).Без кавычек Data Connect преобразует имя таблицы в формат snake case (
example_table).
Правила и ограничения синтаксиса
Встроенный SQL применяет строгие правила синтаксического анализа для обеспечения безопасности и предотвращения SQL-инъекций. Следует учитывать следующие ограничения:
- Комментарии : Используйте блочные комментарии (
/* ... */). Строковые комментарии (--) запрещены, поскольку они могут обрезать последующие условия (например, фильтры безопасности) во время конкатенации запросов. - Параметры : Используйте позиционные параметры (
$1,$2), соответствующие порядку в массивеparams. Именованные параметры ($id,:name) не поддерживаются. - Строки : Расширенные строковые литералы (
E'...') и строки в долларовых кавычках ($$...$$Поддерживаются. Экранирование символов Unicode в PostgreSQL (U&'...') не поддерживается.
Параметры в комментариях
Парсер игнорирует все, что находится внутри блочного комментария. Если вы закомментируете строку, содержащую параметр (например, /* WHERE id = $1 */ ), вы также должны удалить этот параметр из списка params , иначе операция завершится ошибкой " unused parameter: $1 .
Примеры
Пример 1: Базовый оператор SELECT с псевдонимами полей.
Вы можете присвоить псевдоним корневому полю (например, movies: _select ), чтобы сделать ответ клиента более понятным ( data.movies вместо 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]
)
}
После выполнения запроса с помощью клиентского SDK результат будет находиться в data.movies .
Пример 2: Базовое обновление
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]
)
}
После выполнения мутации с помощью клиентского SDK количество затронутых строк будет указано в data._execute .
Пример 3: Базовая агрегация
queries.gql :
query GetTotalReviewCount @auth(level: PUBLIC) {
stats: _selectFirst(
sql: "SELECT COUNT(*) as total_reviews FROM \"Reviews\""
)
}
После выполнения запроса с помощью клиентского SDK результат будет находиться в data.stats.total_reviews .
Пример 4: Расширенная агрегация с использованием 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: []
)
}
После выполнения запроса с помощью клиентского SDK результат будет находиться в data._select .
Пример 5: ОБНОВЛЕНИЕ с ВОЗВРАЩАЮЩИМСЯ И КОНТЕКСТОМ АВТОРИЗАЦИИ
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" }]
)
}
После выполнения мутации с помощью клиентского SDK обновленные данные записи будут находиться в data.updatedReview .
Пример 6: Расширенное CTE с операциями upsert (атомарные get или create)
Этот шаблон полезен для обеспечения существования зависимых записей (например, пользователей или фильмов) перед добавлением дочерней записи (например, отзыва), и все это в рамках одной транзакции с базой данных.
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]
)
}
Пример 7: Использование расширений Postgres
Использование нативного SQL позволяет применять расширения Postgres, такие как PostGIS, без необходимости сопоставлять сложные типы геометрии со схемой GraphQL или изменять базовые таблицы.
В этом примере предположим, что в вашем приложении для ресторана есть таблица, которая хранит данные о местоположении в столбце метаданных JSON (например, {"latitude": 37.3688, "longitude": -122.0363} ). Если у вас включено расширение PostGIS , вы можете использовать стандартные операторы Postgres JSON ( ->> ) для извлечения этих значений на лету и передачи их в функцию PostGIS ST_MakePoint .
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]
)
}
После выполнения запроса с помощью клиентского SDK результат будет находиться в data.nearby .
Рекомендации по обеспечению безопасности: динамический SQL и хранимые процедуры.
Data Connect обеспечивает безопасную параметризацию всех входных данных на границе GraphQL и базы данных, полностью защищая ваши стандартные SQL-запросы от SQL-инъекций первого порядка. Однако, если вы используете SQL для вызова пользовательских хранимых процедур или функций Postgres, выполняющих динамический SQL, вы должны убедиться, что ваш внутренний код PL/pgSQL безопасно обрабатывает эти параметры.
Если ваша хранимая процедура напрямую объединяет пользовательский ввод в строку EXECUTE , она обходит параметризацию и создает уязвимость SQL-инъекции второго порядка:
-- 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;
$$;
Чтобы этого избежать, следуйте этим рекомендациям:
- Используйте предложение
USING: При написании динамического SQL-запроса в хранимых процедурах всегда используйте предложениеUSINGдля безопасной привязки параметров данных. - Используйте функцию
format()для идентификаторов: используйтеformat()с флагом%Iдля безопасного внедрения идентификаторов в базу данных (например, имен таблиц). - Строго разрешайте использование идентификаторов: не позволяйте клиентским приложениям произвольно выбирать идентификаторы базы данных. Если ваша процедура требует динамических идентификаторов, перед выполнением проверяйте входные данные на соответствие жестко заданному списку разрешенных идентификаторов внутри вашей логики PL/pgSQL.
-- 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;
$$;