Функции и RPC
POST https://data.layero.ru/<база>/rest/v1/rpc/<функция> форма PostgREST
GET https://data.layero.ru/<база>/rest/v1/rpc/<функция> только STABLE/IMMUTABLE
POST https://data.layero.ru/<база>/rpc/<функция> своя форма
Функция — это способ дать посетителю сайта одно конкретное действие, не открывая ни одной таблицы. Приём заявки, счётчик, начисление бонуса: снаружи доступен вызов, а что он делает с данными, решает код внутри базы.
CREATE FUNCTION api.submit(site_token text, payload jsonb)
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER AS $$
BEGIN
INSERT INTO public.entries (site_token, payload) VALUES (site_token, payload);
RETURN jsonb_build_object('ok', true);
END $$;
GRANT EXECUTE ON FUNCTION api.submit(text, jsonb) TO layero.role('anon');
await db.rpc("submit", { site_token: "abc", payload: { name: "Аня" } });
Функции ищутся только в схеме api
Таблицы шлюз ищет в api, public и app, функции — только в api.
Заголовки Accept-Profile и Content-Profile на это не влияют.
Несимметрично это осознанно: api и есть публичная витрина. Функция в
public — внутренняя механика приложения, и делать её вызываемой из интернета
по факту существования нельзя.
Если функция лежит не там, отказ так и скажет:
{
"error": "unknown_function",
"message": "функция api.submit не найдена или не открыта по API; одноимённая есть в public — по HTTP видна только схема api"
}
Имена аргументов — публичный контракт
Шлюз раскладывает ключи JSON по одноимённым аргументам функции. Значит, имя аргумента видно снаружи: оно попадает в код каждого клиента, а в случае формы на чужом сайте — ещё и в чужие сниппеты.
Отсюда две вещи:
- Переименование аргумента ломает клиентов — ровно как переименование поля в JSON. Планируйте имена как часть API, а не как локальную деталь.
- Внутри функции имя аргумента конфликтует с именем колонки. PL/pgSQL
подставит аргумент туда, где вы имели в виду колонку, и запрос упадёт с
column reference "token" is ambiguous— в том числе внутриON CONFLICT (token, …), куда локальная переменная тоже подставляется.
Лечится это не переименованием аргумента (он публичный), а квалификацией имён внутри функции:
CREATE FUNCTION api.submit(token text, payload jsonb) RETURNS jsonb
LANGUAGE plpgsql AS $$
#variable_conflict use_column -- колонка важнее переменной
BEGIN
INSERT INTO public.counters AS c (token, minute, hits)
VALUES (api.submit.token, date_trunc('minute', now()), 1) -- аргумент — по полному имени
ON CONFLICT (token, minute) DO UPDATE SET hits = c.hits + 1;
RETURN jsonb_build_object('ok', true);
END $$;
Изменяющая функция и GET
Функция с VOLATILE (умолчание) по GET недоступна и отвечает
volatile_by_get. Решает отметка волатильности в каталоге. GET кешируют и
повторяют — браузер, прокси, предзагрузка ссылок, — и «создай заказ» по GET
однажды создаст два заказа.
Функцию для чтения помечайте STABLE, тогда GET заработает.
SECURITY DEFINER и права
SECURITY DEFINER выполняет тело от владельца базы — так публичная функция
пишет в закрытую таблицу, не открывая её. Правило одно: проверяйте вход
внутри, потому что вызвать её сможет любой посетитель сайта.
Право на вызов выдаётся отдельно: свежесозданная функция в схеме api
недоступна никому, даже если схема открыта — платформа снимает EXECUTE у
PUBLIC с каждой новой функции.