Удалённые схемы (Remote Schemas)¶
Источник удалённой схемы подключает внешний API, будь то GraphQL (включая GitHub), gRPC или REST (OpenAPI), к модели Provisa. Добавление источника не регистрирует ни одной таблицы. Источник предлагает таблицы, и стюард регистрирует каждую нужную в выборе Register Table; эта регистрация и есть шаг курирования. Зарегистрированная таблица является полноценной таблицей Provisa, поэтому к ней автоматически применяются все правила управления, интерфейсы запросов и уровни безопасности. Удалённый сервис никогда не видит правил управления Provisa.
Три типа источников¶
Удалённая схема GraphQL¶
Как добавить источник. Отправьте POST на /admin/sources/graphql-remote с URL эндпоинта, пространством имён и опциональной аутентификацией. Provisa выполняет стандартный запрос интроспекции __schema к удалённому эндпоинту, чтобы подтвердить эндпоинт и учётные данные.
Добавление источника не регистрирует ни таблицу, ни command. Каждый тип удалённого источника отвечает на добавление и обновление одними и теми же счётчиками: tables (зарегистрированные или приведённые в актуальное состояние таблицы; при добавлении 0), available_tables (предлагаемые таблицы), mutations (всегда 0) и available_mutations (предлагаемые commands).
Регистрация таблиц. Откройте Tables, затем Register Table, выберите источник и схему graphql и отметьте нужные таблицы и столбцы. Через административный GraphQL API: availableTables(sourceId, schemaName) перечисляет предлагаемые таблицы, availableColumns перечисляет столбцы таблицы, а registerTable(input: TableInput) регистрирует таблицу с выбранными столбцами. Зарегистрированная таблица затем подпадает под governance.
То, как читается зарегистрированная таблица (корневое поле, путь к строкам, обязательные аргументы, аргументы страниц), хранится в sources.mapping["tables"], поэтому перезапущенный процесс читает её, не запрашивая у удалённого сервиса его схему.
{
"source_id": "petstore-gql",
"url": "https://api.example.com/graphql",
"namespace": "petstore",
"domain_id": "veterinary",
"auth": { "type": "bearer", "token": "..." },
"cache_ttl": 300,
"field_overrides": { "createPet": "query" },
"relationships": [
{ "source_table": "petstore__pets", "source_column": "owner_id",
"target_table": "owners__users", "target_column": "id" }
]
}
Варианты аутентификации: none, bearer (заголовок Authorization), basic (Base64 username:password).
Переопределения полей. field_overrides — это карта {fieldName: "query" | "mutation"}, применяемая после интроспекции. Она имеет приоритет над структурной классификацией. Только поля типа query могут быть переклассифицированы как мутации; поля типа mutation не имеют пути переопределения в GraphQL.
Связи на момент регистрации. relationships объявляет пути соединения FK/PK между таблицами на момент регистрации. Они сохраняются как связи, объявленные вручную (без флага remote_managed). При обновлении автоматически обнаруженные связи (те, что с remote_managed: True) выполняются заново и могут измениться; вручную объявленные связи не затрагиваются.
Что предлагает источник. Каждое поле удалённого типа Query, возвращающее объект или список объектов, предлагается как таблица, как и каждое соединение Relay под полем одиночного объекта (см. ниже). Регистрация предложенной таблицы делает её таблицей. Каждое поле удалённого типа Mutation — это предлагаемая команда, учитываемая в available_mutations; добавление источника не регистрирует ни одной. Нужные регистрируйте как команды; см. Операция записи удалённого источника.
Именование таблиц. Таблицы называются {namespace}__{field_name}. С пространством имён petstore и полем запроса pets: имя таблицы — petstore__pets.
Соединения Relay. Многие API возвращают списки в виде соединений Relay: объекта с nodes (или edges { node }) рядом с pageInfo. Provisa сопоставляет соединение с таблицей его узлов и читает его постранично.
- Корневое поле, возвращающее соединение (
securityAdvisories), становится одной таблицей его узлов. - Соединение на одиночном объекте, который возвращает корневое поле, становится отдельной таблицей. Таблица берёт обязательные аргументы корневого поля. Для
repository(owner, name)и соединенияissuesнаRepositoryтаблица называетсяrepositoryIssues, имя в SQL —gh__repository_issuesв пространстве имёнgh. Фильтруйте её по столбцам_nf_ownerи_nf_name:WHERE _nf_owner = 'acme' AND _nf_name = 'widgets'. - Соединение никогда не бывает столбцом. Иначе строка несла бы чтение, которое удалённый сервис вычисляет для каждой строки, по каждому соединению её типа.
- Соединение является таблицей, только если его поле принимает
firstиafter, чтобы его можно было читать постранично. Соединение, которому нужен собственный аргумент, таблицей не является. Не является ею и соединение объединения (union), и любое соединение под корневым полем, возвращающим список.
Отображение типов. Скалярные поля отображаются на типы Provisa напрямую. Поля OBJECT разделяются на два случая в зависимости от того, управляется ли целевой тип (см. «Управляемые таблицы» ниже).
| Тип GraphQL | Тип Provisa |
|---|---|
String |
text |
ID |
text |
Int |
integer |
Float |
numeric |
Boolean |
boolean |
OBJECT (неуправляемый встроенный тип, например ContactInfo) |
столбец-блоб jsonb |
| OBJECT (управляемый целевой тип) | полностью исключается из SDL и выборки |
| Любой ENUM | jsonb |
| Пользовательский скаляр | text (запасной вариант) |
Управляемые таблицы. Тип GQL управляется, когда он появляется как корневое поле Query в удалённой схеме. _collect_queryable_types собирает их во время регистрации, предпочитая поля без обязательных аргументов, чтобы их можно было массово выбирать как цели соединений.
Когда столбец типа OBJECT на управляемой таблице указывает на другой управляемый тип, этот столбец подчиняется трём правилам одновременно:
- Исключён из выборки GQL — поле не запрашивается при получении строк родительской таблицы.
- Исключён из SDL — поле не появляется на родительском типе в сгенерированной схеме.
- Доступен только через объявленную связь — куратор должен зарегистрировать JOIN между двумя материализованными управляемыми таблицами. Без этого поле просто отсутствует; запасного блоба нет.
Типы OBJECT, недостижимые как корневые поля Query (встроенные типы, такие как ContactInfo или Address), следуют другим правилам: они выбираются как столбцы-блобы jsonb и появляются в SDL как поля вложенных объектов. Доступ к подполям осуществляется через извлечение ->> в SQL.
Поля, которым нужен аргумент, не являются столбцами. Поле с обязательным аргументом нельзя выбрать «как есть», поэтому оно исключается из столбцов таблицы и из вложенных выборок.
Обязательные аргументы. Когда корневое поле запроса имеет не-null аргументы без значения по умолчанию, они становятся столбцами native_filter_type: query_param на таблице (с префиксом _nf_ на момент внедрения). Исполнитель передаёт их как переменные GraphQL.
Связи, найденные автоматически. Provisa просматривает столбцы типа OBJECT каждой зарегистрированной таблицы. Когда тип GQL, на который ссылается столбец, тоже является зарегистрированной таблицей того же источника, а столбец, на котором держится связь, входит в зарегистрированные, связь сохраняется. Ещё не зарегистрированная таблица связей не получает. Связи many-to-one выводят исходный и целевой столбцы из соглашений об именах (breedName в исходном типе → name в целевом типе Breed). Поля one-to-many (LIST) дают связи с пустыми ссылками на столбцы, потому что FK находится на стороне цели.
Мутации. Поле мутации регистрируется по одному как команда вида source_operation. Её аргументы имеют тип json каждый и передаются удалённому сервису как типизированные переменные; ответ — это JSON, который возвращает удалённый сервис, без return_schema. См. Операция записи удалённого источника.
Обновление. Отправьте POST на /admin/sources/graphql-remote/{id}/refresh. Повторно интроспектирует удалённую схему и приводит уже зарегистрированные таблицы в соответствие с ней. Она не добавляет ни таблицу, ни столбец: таблица или столбец, появившиеся в схеме, остаются в предложении, а столбец, исчезнувший из схемы, удаляется. Существующие правила governance (RLS, маскирование) сохраняются.
Ограничения.
- Скалярные и ENUM корневые поля запроса (тип возврата не OBJECT) становятся отслеживаемыми функциями, а не виртуальными таблицами. Их
return_schema— это единственный столбецvalueотображённого скалярного типа. - Вложенность объектов разрешается на момент регистрации до
graphql_remote.max_object_depth(по умолчанию: 5). И выборка при удалённой загрузке, и метаданные подполей строятся до этой глубины; поля за пределом не загружаются и недоступны для извлечения в SQL. Тип входит в обход только один раз вдоль любого пути: поле, тип которого уже находится на пути вниз, исключается, так что схема, типы которой ссылаются друг на друга, обходится один раз на тип, а не один раз на уровень глубины. - Вложенные поля OBJECT типа LIST (например,
breed.awards: [Award]) включаются в выборку при загрузке доgraphql_remote.max_list_depthуровней вложенности (по умолчанию: 2). В этом пределе список загружается как массивjsonbв родительском столбце. Когда поле списка объявляет аргументfirst(Relay, PostGraphile, pg_graphql) или аргументlimit(Hasura), выборка передаёт его какfirst: Nилиlimit: N, где N —graphql_remote.max_list_items(по умолчанию: 100). Поле списка, не объявляющее ни того, ни другого, аргумента не получает, потому что удалённый сервис отклоняет аргумент, который поле не объявляет. За пределомmax_list_depthполе LIST полностью исключается, чтобы предотвратить неограниченное разрастание данных. В SQL массив доступен черезjson_array_elements(column_name)или извлечение по индексу с->>. Если у типа элементов списка есть собственный корневой запрос, зарегистрируйте его как отдельную таблицу и создайте связь. Путь через join эффективнее и обходит блоб. - Для SQL-запросов неуправляемые столбцы типа OBJECT выбираются полностью из удалённого источника (все подполя до настроенной глубины) и кешируются как
jsonb. Доступ к подполям в SQL обрабатывается через извлечение->>из блоба; удалённый запрос не сужается только до полей, которые выбирает SQL-запрос. Когда тип элемента LIST не имеет корневого запроса, а представление блоба недостаточно, пишите запрос напрямую в GraphQL SDL. Provisa точно воспроизводит выборку полей GQL, так что удалённая сторона видит ровно запрошенные поля. - Если удалённый сервер отклоняет поле типа OBJECT, потому что оно требует выбора подполей (что не должно происходить, когда доступна
gql_selection), исполнитель повторяет попытку один раз с удалением этих полей, чтобы скалярные столбцы всё же вернулись. Это относится к таблицам, читаемым с корневого поля. Таблица соединения этот путь не проходит.
Постраничное чтение. Таблица соединения читается по курсору. Каждая страница запрашивает first: N, after: $pageCursor с pageInfo { hasNextPage endCursor }, и чтение следует за endCursor, пока удалённый сервис не сообщит, что следующей страницы нет.
| Параметр | По умолчанию | Действие |
|---|---|---|
graphql_remote.max_list_items |
100 |
Строк на страницу. |
graphql_remote.max_rows |
10000 |
Наибольшее число строк, которое берёт одно чтение таблицы соединения. Чтение, достигшее его, останавливается и записывает предупреждение в журнал. |
Два ответа заставляют исполнитель повторять запрос:
- Страница слишком тяжёлая. Когда удалённый сервис отвечает 502 или 504, та же страница запрашивается снова вполовину меньшего размера, вплоть до одной строки.
- Ограничение частоты с временем ожидания. Когда удалённый сервис отвечает 403 или 429 с
Retry-Afterне более 120 секунд, исполнитель ждёт это время и отправляет запрос снова, до трёх попыток. Отказ безRetry-Afterили с требованием более долгого ожидания вызывается как ошибка. Это относится к каждому чтению, соединение это или нет.
Любая другая ошибка в ответе приводит к сбою чтения, если тип источника не определяет иного (см. GitHub ниже). У соединения, родитель которого вернулся null, нет строк.
GitHub¶
GitHub — обычный тип источника. Его API — GraphQL, поэтому его таблицы ведут себя так, как описано выше, включая таблицы соединений, такие как gh__repository_issues.
Добавление источника.
- Откройте Sources и добавьте источник типа GitHub.
- Введите токен доступа GitHub. При желании введите пространство имён — префикс имён таблиц; по умолчанию
gh. - Сохраните. Provisa проверяет токен в GitHub. Токен, который GitHub отклоняет, приводит к сбою добавления с сообщением GitHub.
Добавление источника не регистрирует таблиц.
Регистрация таблиц. Откройте Tables, затем Register Table. Выберите источник GitHub, выберите схему graphql, затем выберите нужные таблицы. Перечисляется каждая таблица, которую предлагает GitHub; регистрация — это ваш выбор того, что открыть.
Области действия (scopes) токена. Когда вы регистрируете таблицу, Provisa один раз проверяет её в GitHub с вашим токеном.
- Поле, которое области действия токена не покрывают, исключается из таблицы. Результат называет каждое исключённое поле:
Left out, because the source's credential may not read them: projectsV2. - Таблица, которую токен не может прочитать вовсе, отклоняется с указанием причины от GitHub:
GitHub does not let this source's credential read gh__repository_issues: ....
Строки, которые токен не может видеть. GitHub отвечает FORBIDDEN для поля, которое токен не может видеть в конкретной строке, например для соавторов репозитория без права записи, и NOT_ORG_OWNED_REPO для поля, существующего только в репозиториях, принадлежащих организации. Это поле в данной строке равно null, остальное чтение продолжается, а Provisa записывает предупреждение в журнал. Ошибка, относящаяся к самой таблице, приводит к сбою чтения.
Тяжёлые страницы. Когда GitHub отвечает RESOURCE_LIMITS_EXCEEDED, потому что вычисление страницы обходится слишком дорого, страница запрашивается снова вполовину меньшего размера.
Вложенные объекты. Таблицы GitHub используют собственную глубину вложенности, 0 (max_object_depth=0 для этого типа источника), вместо graphql_remote.max_object_depth. Столбец вложенного объекта выбирается только со своими скалярными полями, а объекты внутри него отображаются как __typename.
Хранение токена. Токен помещается в хранилище секретов, а строка источника хранит ссылку, поэтому после перезапуска источник читается снова без повторного ввода токена.
Как это работает (для операторов). Схема GitHub поставляется с Provisa, поэтому добавление источника не выполняет вызова интроспекции, а большая схема ничего не стоит при регистрации. Таблицы отображаются из неё по одной по мере регистрации. Эндпоинт refresh отклоняет этот тип источника; новая схема GitHub приходит с выпуском Provisa.
GitLab¶
GitLab — обычный тип источника, и он добавляется и регистрируется так же, как GitHub: добавьте источник типа GitLab с токеном доступа, затем зарегистрируйте нужные таблицы из схемы graphql. Префикс имён таблиц по умолчанию — gl. Источник обращается к gitlab.com.
Выбор столбцов. GitLab оценивает каждый запрос и отклоняет слишком дорогой: 200 баллов для анонимного вызывающего, 250 с токеном. Широкая таблица со всеми выбранными столбцами превышает эту цену, поэтому регистрируйте таблицу GitLab с нужными вам столбцами.
- Когда вы регистрируете таблицу, Provisa один раз спрашивает GitLab, обслужит ли он выборку при размере страницы, который используют чтения. Если GitLab отвечает, что запрос слишком сложный или слишком большой, таблица не регистрируется, а результат содержит сообщение GitLab:
Table 'gl__project_issues' was not registered with the columns selected: Query has complexity of 1733, which exceeds max complexity of 200. Choose fewer columns. - Цена столбца зависит от его вида. Простое значение стоит около одного балла; столбец вложенного объекта стоит во много раз больше. Отказ от столбцов вложенных объектов освобождает больше всего.
- Размер страницы входит в цену. Это
graphql_remote.max_list_items.
Проверка токена. GitLab отвечает на нераспознанный токен пустым результатом, а не ошибкой. Provisa считает это отклонённым токеном и не добавляет источник.
Удалённая схема gRPC¶
Как добавить источник. Отправьте POST на /admin/grpc-remote/register с адресом сервера, путём или URL к файлу .proto и необязательной конфигурацией TLS. Добавление источника не регистрирует таблиц.
{
"source_id": "orders-grpc",
"proto_path": "https://api.example.com/orders.proto",
"server_address": "grpc.example.com:443",
"namespace": "orders",
"domain_id": "commerce",
"tls": true,
"cache_ttl": 300,
"method_overrides": { "CreateOrder": "query" },
"relationships": [
{ "source_table": "orders__OrderService__ListOrders", "source_column": "customer_id",
"target_table": "customers__CustomerService__GetCustomer", "target_column": "id" }
]
}
Provisa получает proto, разбирает его чисто текстовым парсером (без внешних зависимостей proto на этапе разбора), компилирует Python-заглушки через grpc_tools.protoc и открывает постоянный grpc.aio.Channel.
Файлы proto также могут быть локальными путями. Пути импорта для хорошо известных типов (google/protobuf/timestamp.proto) сохраняются на момент регистрации и переиспользуются при обновлении.
Что предлагает источник. Каждый метод rpc в proto классифицируется как query или mutation по трём сигналам в порядке приоритета:
method_overridesв полезной нагрузке регистрации —{"MethodName": "query"}или{"MethodName": "mutation"}переопределяет всё остальное.server_streaming: true— сервер отправляет поток сообщений; всегда виртуальная таблица (если только вывод не скаляр).- Выходное сообщение имеет повторяющееся поле типа message — например,
ListOrdersResponse { repeated Order items; }рассматривается как обёртка списка и становится виртуальной таблицей. Повторяющиеся скалярные поля (например,repeated string tags) это не вызывают, потому что являются свойствами-массивами одной сущности, а не источниками строк.
Методы, не соответствующие ни одному из этих сигналов (унарный RPC, возвращающий одно сообщение-сущность, или любой скалярный вывод), становятся отслеживаемыми функциями.
Регистрация таблиц. Каждый метод query предлагается как одна таблица с именем {namespace}__{Service}__{Method} в схеме выбора grpc_remote. Зарегистрируйте нужные через выбор Register Table (availableTables, availableColumns, registerTable, как для источников GraphQL), выбрав столбцы ответа. Поля запроса становятся столбцами нативного фильтра _nf_*, и они включаются всегда.
Методы мутации — это предлагаемые команды, учитываемые в available_mutations; добавление источника не регистрирует ни одной. Мутация gRPC регистрируется на странице «Команды»: выбираются источник, затем метод с именем Service.Method; вид команды — source_operation. См. Операция записи удалённого источника.
Именование таблиц. Имя по умолчанию — {namespace}__{ServiceName}__{MethodName}. Без пространства имён имена сервиса и метода соединяются напрямую. Любой зарегистрированной таблице можно задать alias; когда он установлен, алиас — это имя, используемое повсюду (запросы, SDL, связи). Автоматически сгенерированное имя — это ключ регистрации, который никогда не меняется.
Отображение типов. Скалярные типы proto отображаются на типы SQL следующим образом.
| Тип Proto | Тип SQL |
|---|---|
string, bytes |
text |
int32 / uint32 / sint32 / fixed32 / sfixed32 |
integer |
int64 / uint64 / sint64 / fixed64 / sfixed64 |
bigint |
float |
real |
double |
numeric |
bool |
boolean |
repeated <T> |
jsonb |
| Вложенное message | jsonb |
| Enum | text |
Связи при регистрации. relationships работает так же, как в адаптере GQL: объявляет пути соединения FK/PK, сохраняемые как вручную объявленные связи (без флага remote_managed). Обновление оставляет их без изменений.
Методы Query. Поля выходного сообщения становятся столбцами таблицы. Поля входного сообщения одновременно становятся аргументами GraphQL, передаваемыми в удалённый вызов, и регистрируются как столбцы с префиксом _nf_ и native_filter_type: "grpc_input"; это тот же механизм, что используют GQL и OpenAPI для внедрения нативных фильтров.
Подполя вложенного message. Для методов query неповторяющиеся поля типа message на глубине 0 (прямые выходные столбцы) имеют свои подполя, разрешённые на один уровень вглубь и сохранённые как object_fields на ColumnDef. Эти метаданные используются для извлечения подполей jsonb в SQL и для документации схемы. Поля, вложенные глубже уровня 1, не раскрываются рекурсивно.
Методы с серверным стримингом собирают все переданные потоком сообщения в список перед возвратом строк.
Методы мутации. Зарегистрированный метод мутации — это команда, аргументы которой — поля входного сообщения, каждое типа json и передаётся без изменений. Ответ удалённого сервиса возвращается как строки; отклонённый вызов — это 422, functions.remote_refused. См. Операция записи удалённого источника.
Управление каналами. Один grpc.aio.Channel на зарегистрированный источник хранится в состоянии приложения и переиспользуется между запросами. Старый канал закрывается до открытия нового при обновлении.
Обновление. Отправьте POST на /admin/grpc-remote/refresh/{source_id}. Заново загружает proto по сохранённому пути, перекомпилирует заглушки и приводит уже зарегистрированные таблицы в соответствие с proto, с теми столбцами, с которыми каждая была зарегистрирована. Она не регистрирует новых таблиц; метод query, добавленный в proto, остаётся в предложении. Либо отправьте PUT на /admin/grpc-remote/{source_id}/proto с новым proto_text, чтобы обновить proto встроенно.
Ограничения.
- Извлечение подполей объекта — на один уровень вглубь. Поля вложенного message глубже уровня 1 не раскрываются рекурсивно.
OpenAPI / REST¶
Как добавить источник. Отправьте POST на /admin/openapi/register с ID источника и спецификацией, загруженной из локального файла или по URL. Спецификация разбирается и хранится вместе с источником; ни таблица, ни command не регистрируются. Ответ сообщает tables: 0 и mutations: 0, а предлагаемые количества указаны в available_tables и available_mutations.
Регистрация таблиц. Зарегистрируйте каждую нужную операцию GET через выбор Register Table (availableTables, availableColumns, registerTable), выбрав столбцы. Каждую операцию, отличную от GET, регистрируйте по отдельности как команду на странице «Команды»; они перечисляются availableFunctions; см. Операция записи удалённого источника. PUT /admin/openapi/spec/{source_id} сохраняет спецификацию, отредактированную вручную, ничего не регистрирует и возвращает available_tables и available_mutations.
Полезная нагрузка регистрации. Эндпоинт /admin/openapi/register принимает два дополнительных поля наряду с source_id, spec_path и т. д.:
{
"operation_overrides": { "createPet": "query", "listOrders": "mutation" },
"relationships": [
{ "source_table": "pets__listPets", "source_column": "owner_id",
"target_table": "owners__listOwners", "target_column": "id" }
]
}
Что предлагает источник. Каждая операция GET в спецификации предлагается как таблица, если только её схема ответа не скалярный тип (string, number, boolean, integer); GET, возвращающий скаляр, является функцией с единственным столбцом value. Любая другая операция (POST, PUT, PATCH, DELETE) предлагается как команда с именем по её operationId. После регистрации она принимает параметры пути операции и аргумент body для тела запроса, каждый типа json, а любой другой аргумент уходит в строку запроса. См. Операция записи удалённого источника.
Приоритет классификации: operation_overrides (полезная нагрузка) переопределяет x-provisa-kind (расширение спецификации), которое переопределяет эвристику GET. operation_overrides — рекомендуемый путь переопределения; x-provisa-kind — для случаев, когда сама спецификация должна нести классификацию.
Связи при регистрации. relationships работает так же, как в других адаптерах: сохраняются как вручную объявленные связи и не меняются при обновлении.
Именование таблиц. Таблицы используют operationId операции. Если operationId не определён, Provisa образует слаг {method}_{path}. Алиас выводится удалением ведущего сегмента глагола и приведением существительного к единственному числу (findPetsByStatus → pet_by_status).
Отображение типов. Типы JSON Schema отображаются на типы Provisa следующим образом.
| Тип JSON Schema | Тип Provisa |
|---|---|
string |
string |
integer |
integer |
number |
number |
boolean |
boolean |
array |
jsonb |
object |
jsonb |
Параметры как столбцы нативных фильтров. Параметры пути и запроса, которые ещё не являются полями ответа, становятся столбцами с native_filter_type, равным path_param или query_param, и префиксом _nf_. Когда имя параметра совпадает с полем ответа, его метаданные объединяются с записью этого столбца, и дубликат не создаётся.
Разрешение схемы ответа. Маппер проверяет responses.200, затем responses.2xx, затем responses.default. Ответы типа массив разворачиваются в схему их элемента. Ссылки $ref разрешаются на один уровень вглубь.
Подполя объекта. Свойства ответа с type: object и собственными properties сохраняются как object_fields на столбце. Эти подполя видны в SDL и используются для извлечения jsonb в запросах.
Кеширование ответов. Результаты операций GET кешируются в PostgreSQL через pg_cache.py. Каждая комбинация параметров запроса получает собственную группу _params_hash. Строки для данного хеша заменяются при истечении TTL. Эндпоинты с параметрами пути (/pets/{id}) пропускают первоначальную массовую выборку. Таблица кеша создаётся пустой для интроспекции схемы, затем заполняется по PK по мере поступления запросов.
Обновление. Отправьте POST на /admin/openapi/refresh/{source_id}. Заново разбирает спецификацию через _load_and_register, который ничего не регистрирует: он не добавляет ни таблицу, ни столбец. Существующие правила governance сохраняются. Зарегистрированная таблица сохраняет свои столбцы; она читается через операцию обновлённой спецификации.
Ограничения.
- Извлечение подполей объекта — на один уровень вглубь. Свойства, вложенные внутри
object_fields, не раскрываются рекурсивно. - Параметры заголовков и cookie игнорируются; регистрируются только параметры
pathиquery. - Разрешение
$refна уровне спецификации — на один уровень вглубь для схем свойств; глубоко вложенные ссылки на компоненты могут не разрешиться.
Влияние регистрации удалённой таблицы¶
Таблица, зарегистрированная из любого источника удалённой схемы, — это полноценная таблица Provisa. Во время выполнения она ничем не отличается от локально подключённой реляционной таблицы.
Интерфейсы запросов. Таблица немедленно доступна для запросов через GraphQL, SQL (pgwire или прямой), Cypher (GQL), JSON:API и Arrow Flight. Генерация схемы синтезирует ColumnMetadata для удалённых таблиц, поскольку у них нет каталога; отображение типов применяется на этапе построения схемы.
Модель безопасности. Применяются все пять слоёв governance:
- Контроль доступа к домену —
domain_idтаблицы ограничивает, какие роли могут её видеть. - Безопасность на уровне строк (RLS) — фильтры строк, настроенные на таблице, внедряются в каждый запрос независимо от интерфейса.
- Видимость столбцов — список
visible_toна каждом столбце управляет раскрытием поля по ролям. - Маскирование столбцов — правила маскирования применяются на Этапе 2 конвейера governance.
- Защита предикатов — замаскированные столбцы отклоняются в предложениях WHERE и HAVING.
Произвольные запросы к удалённым таблицам разрешены на одних правах пользователя. Доступ везде следует одной модели на основе прав (права на таблицы и столбцы плюс одобренные связи), без отдельного режима управления для каждой таблицы.
Управление связями (V002). Условие JOIN по удалённым таблицам в запросе SQL или Cypher должно соответствовать зарегистрированной одобренной связи. Запросы GraphQL пропускают проверку V002, потому что связи, определённые в SDL, одобрены по самому устройству. См. docs/security.md.
Столбцы типа OBJECT. Когда столбец отображается на неуправляемый встроенный GQL OBJECT или объектный тип OpenAPI, его тип Provisa — jsonb. Столбец хранит полный вложенный блоб JSON. Когда подполя объявлены (gql_object_fields или object_fields), карта gql_object_columns заполняется на этапе построения схемы. Генератор SQL использует эту карту для выдачи выражений извлечения ->> для подполей, когда запрос их выбирает.
Обязательные аргументы как параметры нативного фильтра. Корневые поля запроса с не-null аргументами без значения по умолчанию внедряют дополнительные столбцы в зарегистрированную таблицу. Эти столбцы несут native_filter_type: query_param. Транслятор Cypher переписывает WHERE n.id = $val в WHERE n._nf_id = $val, а исполнитель GraphQL подбирает их как переменные для передачи удалённому эндпоинту.
Влияние создания покрывающей связи¶
Когда куратор регистрирует связь между двумя удалёнными таблицами (или между удалённой таблицей и локальной таблицей), связь становится путём соединения, используемым во время выполнения запроса.
Как побеждает соединение. На этапе компиляции запроса Provisa разрешает путь соединения через зарегистрированную связь. source_column и target_column на связи становятся условием соединения в сгенерированном SQL. Соединение заменяет любой удалённый вызов на таблицу, который иначе потребовался бы для связанного типа.
Сырой блоб никогда не открывается в SQL. Столбец breed таблицы petstore__pets нельзя выбрать в SQL-запросе как сырое значение jsonb. Как только между petstore__pets и petstore__breeds зарегистрирована связь, SQL-запросы идут через соединение: SELECT breed.name FROM petstore__pets разрешается через соединение по FK. Если связь не зарегистрирована, но у столбца объявлены подполя (gql_object_fields), ссылки SQL на подполя переписываются в извлечение ->> из сохранённого блоба. Этот путь есть только для неуправляемых встроенных типов; поля с управляемой целью вообще отсутствуют в SDL, и извлекать их не из чего. Сам сырой блоб никогда не возвращается как голое значение столбца.
В SDL GraphQL неуправляемое встроенное поле типа OBJECT имеет тип вложенного объекта. Обслуживает ли его во время выполнения соединение или извлечение из блоба, форма SDL от этого не меняется. Как только дочерний тип зарегистрирован как отдельная таблица и тем самым стал управляемым, к нему независимо применяются все пять уровней управления: собственные правила RLS, видимость столбцов, правила маскирования, защита предикатов и контроль доступа по доменам. Извлечение из блоба всё это обходит, потому что дочерние данные приходят встроенными в родительскую строку и подчиняются только правилам родительской таблицы. Чтобы детально управлять дочерним типом, зарегистрируйте его как таблицу и создайте связь.
graphql_alias на связи. Поле graphql_alias называет поле SDL, которое связь раскрывает на родительском типе. Когда оно отсутствует, имя выводится из field_name целевой таблицы и кардинальности связи через rel_field_name(target.field_name, cardinality).
V002 на пути соединения. Запросы SQL и Cypher, проходящие через связь, подчиняются governance связей V002. Связь должна быть зарегистрирована и одобрена, чтобы соединение было разрешено. Обход через поле связи SDL в GraphQL всегда предварительно одобрен.
Флаг remote-managed. Связи, автоматически обнаруженные во время регистрации удалённой схемы GraphQL, сохраняются с remote_managed: True. Это метаданный-маркер; он не изменяет поведение governance.
Поведение только-определение-типа (type-def-only)¶
Не каждый тип в удалённой схеме должен быть таблицей, доступной для запросов.
Когда на SchemaInput установлен root_table_ids, таблицы, чьи ID отсутствуют в этом наборе, исключаются из корневых полей запроса в сгенерированном SDL. Они остаются присутствующими как типы GraphQL и достижимы через поля связей на таблицах, которые действительно имеют корневые записи.
Тот же механизм действует, когда схема строится с фильтрацией по доменам. Таблицы доменов, к которым у роли нет доступа, существуют только как определения типов: их определение типа есть в SDL для обхода связей, но корневое поле запроса для них не создаётся.
Таблица только-определение-типа:
- Не имеет корневого поля запроса, поэтому клиенты не могут запросить её напрямую по имени.
- Достижима через поля связей на таблицах, которые имеют корневые записи.
- По-прежнему появляется в интроспекции схемы как именованный тип.
- По-прежнему имеет все правила governance, применяемые при доступе к данным через связь.
Таблица вместе с определением типа полностью уходит из схемы, только когда удаляется её регистрация. Если сделать таблицу только определением типа (убрав её ID из root_table_ids или через фильтр доступа к доменам), тип не удаляется.
Так стюарды могут открывать навигируемые графы объектов, в которых до некоторых типов можно добраться только обходом, а запросить их отдельно нельзя.