Сервер Provisa pgwire¶
Provisa предоставляет эндпоинт по протоколу проводного взаимодействия PostgreSQL (pgwire). Любой инструмент, говорящий на клиентском протоколе PostgreSQL — psycopg2, asyncpg, DBeaver, Tableau, JDBC — может подключиться и запрашивать данные Provisa через тот же конвейер governance, который управляет HTTP API. (REQ-266)
Запросы проходят через полный стек governance: применение RLS, правила маскирования, защиту связей, проверки доступа к домену. (REQ-001, REQ-002, REQ-263) Интерфейс pgwire — не обходной путь. (REQ-002, REQ-266)
Детали подключения¶
Сервер запускается, когда PROVISA_PGWIRE_PORT установлен в ненулевое целое число. По умолчанию отключён. (REQ-527) [tool-verified: app.py:1739]
TLS. Установите PROVISA_PGWIRE_CERT и PROVISA_PGWIRE_KEY в пути к PEM-сертификату и ключу. Когда оба присутствуют, сервер оборачивает входящие соединения в TLS. Когда отсутствуют, TLS отключён, и сервер отвечает N на запросы согласования SSL. (REQ-530) [tool-verified: server.py:1746-1750]
Сообщаемая версия сервера. Клиенты видят 14.0.provisa. Инструменты, включающие функции по номеру версии, могут вести себя так, как будто подключены к PostgreSQL 14. (REQ-579) [tool-verified: server.py:208]
Аутентификация¶
Стартовый пакет несёт имя пользователя и одно поле с секретом, но не содержит схемы, которая сообщала бы, что это за секрет. Provisa определяет его по самому секрету, поэтому клиенту не нужна никакая настройка, кроме user и password:
| Секрет — это | Распознаётся по | Разрешается в |
|---|---|---|
| Персональный токен доступа | префиксу provisa_pat_ |
владельца токена и его роль (REQ-1263) |
| Bearer-токен OIDC / провайдера | тому, что настроенный провайдер является токен-провайдером | личность, которую утверждает токен (REQ-890) |
| Пароль | всему остальному | учётную запись в настроенном провайдере (basic или simple) |
Решение принимается один раз. Учётные данные, отклонённые выбранным валидатором, не проверяются повторно другим, поэтому один отказ не превращается во вторую попытку угадать.
Режим trust (provider: none или неактивный middleware аутентификации) — исключение: имя пользователя используется напрямую как role_id, а секрет игнорируется. Не используйте его через незашифрованное соединение.
SCRAM-SHA-256. При provider: basic и auth.scram: true сервер объявляет SASL (код аутентификации 10) с SCRAM-SHA-256, и пароль доказывается, а не передаётся. (REQ-1394) SCRAM-SHA-256-PLUS не предлагается. Пользователю, для которого верификатор ещё не записан — верификаторы нельзя вывести из bcrypt-хешей, — сервер отвечает фиктивным обменом, чтобы по сети нельзя было понять, кто уже перешёл; такой пользователь аутентифицируется паролем открытым текстом поверх TLS, пока следующий ввод пароля не запишет верификатор. При выключенном auth.scram сервер использует тип аутентификации PG 3 (пароль открытым текстом). MD5 не поддерживается ни в одном из случаев.
Клиентские сертификаты. Задайте PROVISA_MTLS_CLIENT_CA, и сервер проверит клиентский сертификат во время рукопожатия, прежде чем будут рассмотрены какие-либо учётные данные. (REQ-1228) С PROVISA_MTLS_BIND_PRINCIPAL common name сертификата должен совпадать с user, под которым соединение затем аутентифицируется. См. Конфигурация.
Неудачные попытки подсчитываются. Пять неудач за пять минут блокируют учётную запись на пятнадцать минут, и счётчик общий с HTTP и Bolt — блокировка, заработанная на любой поверхности, действует на всех. (REQ-1393)
Выбор организации. В развёртывании с несколькими организациями подключайтесь к <org>.<your-domain>, и pgwire прочитает организацию из имени хоста в TLS ClientHello — так же, как HTTP читает её из заголовка Host. (REQ-1234) Имя хоста запрашивает организацию, но не предоставляет её, и principal без членства в ней получает отказ. Подключение по IP-адресу не запрашивает никакой организации.
Что работает¶
SELECT¶
Все операторы SELECT проходят через конвейер governance (_pipeline.py). (REQ-001, REQ-262, REQ-266) Конвейер:
- Переписывает семантический SQL в физический SQL (
rewrite_semantic_to_physical) - Применяет governance (RLS, маскирование, доступ к домену) (REQ-263)
- Проверяет по зарегистрированной схеме (REQ-011)
- Маршрутизирует на Trino или прямой пул источника (REQ-027, REQ-028)
Многооператорные простые запросы поддерживаются. Операторы, разделённые точкой с запятой, разбиваются и выполняются по порядку. (REQ-580) [tool-verified: server.py:318-381]
Параметризованные запросы ($1, $2, ...) поддерживаются как в режиме простого запроса, так и в режиме расширенного запроса (Bind/Execute). Параметры подставляются как литералы перед выполнением. (REQ-581) [tool-verified: server.py:78-85]
SELECT * FROM fn(args) и SELECT fn(args) — где fn называет зарегистрированную отслеживаемую функцию — перехватываются до конвейера governance и маршрутизируются через единый управляемый исполнитель (invoke_tracked_function). Результат — типизированный набор строк, идентичный тому, что возвращает любая другая поверхность для этой команды. Назначение команды (visible_to), право write для мутации и правила governance применяются внутри исполнителя. (REQ-1156) [tool-verified: provisa/pgwire/function_call.py:74-88]
Курсоры (DECLARE / FETCH / MOVE / CLOSE)¶
Provisa обрабатывает текстовый синтаксис SQL-курсоров по протоколу простых запросов. (REQ-1862) Клиенты, которые отправляют DECLARE/FETCH/MOVE/CLOSE как обычные SQL-инструкции, получают настоящую потоковую передачу на стороне сервера вместо ошибки протокола или буфера со всем результатом в памяти клиента.
Это отличается от потоковой передачи через порталы. Протокол расширенных запросов — Bind/Execute с полем лимита строк — это другой механизм протокола передачи. asyncpg и большинство современных драйверов PostgreSQL используют его автоматически, без SQL курсоров. Если ваш драйвер уже использует порталы, этот синтаксис вам не нужен. DECLARE CURSOR — для клиентов, которые отправляют SQL курсоров буквально: именованные/серверные курсоры psycopg2 (conn.cursor(name="...")), некоторые BI-инструменты и некоторые старые драйверы JDBC.
DECLARE
DECLARE c1 CURSOR FOR SELECT id, amount FROM orders WHERE region = 'us-west';
DECLARE c2 SCROLL CURSOR FOR SELECT * FROM events ORDER BY ts;
DECLARE "CaseSensitive" NO SCROLL CURSOR FOR SELECT * FROM large_table;
server.py:1367 — ctx.execute_sql(inner_sql), the same call path as a plain SELECT] Объявление курсора не обходит governance.
Ключевое слово SCROLL управляет и допустимой навигацией, и объёмом памяти курсора:
| Объявление | Навигация назад | Затраты памяти |
|---|---|---|
DECLARE c CURSOR FOR ... (по умолчанию) |
Не разрешена | Ограничены одной партией FETCH |
DECLARE c NO SCROLL CURSOR FOR ... |
Не разрешена | Ограничены одной партией FETCH |
DECLARE c SCROLL CURSOR FOR ... |
Разрешена | Весь результат в буфере памяти Python |
Когда не указано ни SCROLL, ни NO SCROLL, по умолчанию действует NO SCROLL — как и в самом PostgreSQL. [tool-verified: server.py:1361-1362] Курсор NO SCROLL удаляет из внутреннего буфера потреблённые строки после каждого шага вперёд, поэтому проход FETCH FORWARD по большому результату никогда не держит в памяти Python больше примерно одной партии за раз. [tool-verified: server.py:443-447] Курсор SCROLL сохраняет каждую строку, которую когда-либо получил, — это цена поддержки FETCH BACKWARD, ABSOLUTE, FIRST и LAST. Прежде чем объявлять курсор SCROLL над большим результатом, заложите полный размер результата в кучу Python. [tool-verified: server.py:383-389]
Повторное объявление уже открытого имени курсора сначала закрывает прежний курсор. [tool-verified: server.py:1369-1371] WITH HOLD и WITHOUT HOLD принимаются синтаксисом; см. время жизни курсора ниже.
FETCH и MOVE
FETCH 100 FROM c1 -- next 100 rows
FETCH FORWARD 100 FROM c1 -- same
FETCH ALL FROM c1 -- all remaining rows
FETCH BACKWARD 10 FROM c1 -- previous 10 rows (SCROLL cursors only)
FETCH ABSOLUTE 50 FROM c1 -- the row at absolute position 50 (SCROLL cursors only)
FETCH RELATIVE -5 FROM c1 -- 5 positions back (SCROLL cursors only)
FETCH FIRST FROM c1 -- row 1 (SCROLL cursors only)
FETCH LAST FROM c1 -- the final row (SCROLL cursors only)
MOVE 100 FROM c1 -- advance position without returning rows
FETCH возвращает строки и отправляет тег команды FETCH <n>, где n — фактически возвращённое число, которое может быть меньше запрошенного, когда курсор доходит до конца. MOVE сдвигает позицию, не возвращая строк, и отправляет MOVE <n>. [tool-verified: server.py:1417, server.py:1419]
FROM и IN взаимозаменяемы перед именем курсора. Имена курсоров без кавычек приводятся к нижнему регистру; имена в кавычках ("CaseSensitive") сохраняют регистр. [tool-verified: test_cursor.py:74-79]
Навигация назад (BACKWARD, ABSOLUTE, FIRST, LAST, отрицательный RELATIVE) по курсору NO SCROLL возвращает SQLSTATE 55000 (object_not_in_prerequisite_state), как и отказ самого PostgreSQL. [tool-verified: server.py:1402-1406] FETCH или MOVE с именем никогда не объявленного курсора возвращает SQLSTATE 34000 (invalid_cursor_name). [tool-verified: server.py:1398-1400]
CLOSE
CLOSE c1; -- release one cursor and free its result stream
CLOSE ALL; -- release every open cursor on this connection
CLOSE освобождает нижележащий поток результатов и удаляет курсор из сеанса. [tool-verified: server.py:1421-1436] Отключение без CLOSE безопасно: ProvisaSession.close() автоматически освобождает все ещё открытые курсоры при отключении. [tool-verified: test_cursor.py:335-346]
Время жизни курсора. У сервера pgwire Provisa нет конечного автомата транзакций (ProvisaSession.in_transaction() жёстко возвращает False). Поэтому каждый курсор ведёт себя так, будто объявлен WITH HOLD: живёт всё время соединения, а не до COMMIT. [tool-verified: server.py:200 — in_transaction() always returns False] Ключевые слова WITH HOLD и WITHOUT HOLD принимаются и молча игнорируются — жизненного цикла курсоров по транзакциям, который нужно было бы применять, нет.
Определения (CREATE, ALTER, DROP)¶
Через pgwire ничего не определяется. Инструкция, которая создаёт, изменяет или удаляет объект (CREATE TABLE, CREATE TABLE … AS SELECT, CREATE VIEW, ALTER, DROP, индекс, последовательность или схема), отклоняется с SQLSTATE 0A000, что бы ни было у роли; исключение — собственная временная таблица сеанса (см. Временные таблицы). Таблица или представление — это объект модели: создавайте их в модели (страницы администратора, API администратора или конфигурация) либо создайте в источнике данных и допустите в модель. TRUNCATE ничего не определяет: это запись данных (см. Запись данных). [tool-verified: provisa/compiler/definitions.py]
Отказ выполняется один раз, в конвейере, через который проходит каждая SQL-поверхность, поэтому SQL по HTTP, Flight и MCP отклоняют те же инструкции с тем же сообщением.
Временные таблицы¶
Сеанс может создавать, записывать, читать и удалять собственные временные таблицы:
CREATE TEMP TABLE busy AS SELECT customer_id, COUNT(*) AS n FROM sales.orders GROUP BY customer_id;
INSERT INTO busy VALUES (0, 0);
UPDATE busy SET n = n + 1 WHERE customer_id = 0;
DELETE FROM busy WHERE n < 5;
SELECT c.name, b.n FROM sales.customers c JOIN busy b ON b.customer_id = c.id;
DROP TABLE busy;
CREATE TEMP TABLE name (column type, ...) или CREATE TEMP TABLE name AS SELECT .... Имя не принимает схему. SELECT, из которого она создаётся, — это такое же чтение, как любое другое: правила строк, маски, поддельные данные и лимит строк роли применяются к строкам, которые он берёт.
- Типы колонок: smallint, integer, bigint, real, double precision, boolean, date, timestamp, text, varchar и DECIMAL(precision, scale). Десятичное число сохраняет точность и масштаб. Любой другой тип отклоняется с указанием колонки и типа: значение никогда не хранится как тип, отличный от собственного. Колонка, созданная из SELECT, принимает тип своих значений; колонка без значения, по которому можно определить тип, отклоняется, поэтому примените CAST в SELECT.
- Лимит строк. Строки, которые берёт CREATE ... AS SELECT или INSERT ... SELECT, читаются так, как роль читает что угодно, поэтому действует лимит строк роли. Временная таблица, заполненная из чтения, урезанного лимитом, несёт то же предупреждение инструкции, что и любое урезанное чтение (statement.rows_cut), с указанием лимита.
- Запись — INSERT ... VALUES, INSERT ... SELECT, UPDATE и DELETE, без RETURNING и без FROM. Запись во временную таблицу — не мутация: она не достигает ни источника, ни данных окружения, поэтому работает при любой обработке мутаций в окружении и не требует права записи.
- Чтение — по её имени, отдельно или в соединении с таблицами модели. Чтение вычисляет движок.
- Время жизни. Временная таблица — не объект модели, другие сеансы её не видят, и она заканчивается вместе с сеансом. В pgwire сеанс — это соединение. На поверхности без собственного соединения (SQL по HTTP, Flight, MCP) сеанс — это запрос, поэтому временная таблица живёт для инструкций, отправленных вместе в этом запросе.
- Хранение. Строки хранятся в собственной схеме сеанса в хранилище движка и удаляются по окончании сеанса. INSERT, UPDATE или DELETE перезаписывает всю таблицу через движок, поэтому временная таблица подходит для рабочих данных, а не для больших таблиц, записываемых построчно. Процесс, который останавливается, не завершив свои сеансы, оставляет их схемы в хранилище; они удаляются, когда окружение выводится из эксплуатации.
Те же инструкции работают на каждой SQL-поверхности: они обрабатываются в конвейере, общем для всех. [inferred: verified by tests/integration/test_session_temp_tables.py over HTTP; the pgwire connection-long session is not yet covered by a test]
Запись данных (INSERT, UPDATE, DELETE, MERGE, COPY FROM STDIN)¶
Строки существующей зарегистрированной таблицы записываются через pgwire так же, как на любой другой SQL-поверхности, через единый допуск записи (provisa/compiler/write_admission.py):
- Право. У роли есть
write. (REQ-868) - Разрешение. У роли есть разрешение на запись в таблицу для операции:
INSERTтребует разрешенияinsert,UPDATE—update,DELETE—delete.MERGEтребует разрешенийinsertиupdateна все строки, аINSERT ... ON CONFLICT DO UPDATE—updateна все строки. (REQ-2074) - Колонки. Каждая колонка, которую пишет инструкция, названа в разрешении. Колонка, которую разрешение задаёт пресетом, принадлежит разрешению: инструкция не может её передать, а значение разрешения (переменная сеанса, литерал или момент записи) добавляется. (REQ-663, REQ-2074)
- Строки, которых она может касаться. Предикат
rowsразрешения добавляется кWHEREинструкцииUPDATEилиDELETE. Разрешение, не задающее его, берёт фильтр строк роли — тот же предикат, что несут её чтения. - Строки, которые она оставляет. Строки
INSERTи новые значенияUPDATE, задающего колонку, которую читаетcheckразрешения, проверяются поcheckдо того, как что-либо отправлено источнику. Разрешение, не задающееcheck, берёт фильтр строк роли.
TRUNCATE — тоже запись данных; он выполняется как есть и никогда не переписывается в DELETE. Ему нужны право write, разрешение delete на все строки таблицы и источник, принимающий удаления. Он очищает одну таблицу за раз, а роли, чьё разрешение ограничивает строки (предикат rows или фильтр строк роли, если разрешение его не задаёт), в нём отказывается, так как TRUNCATE не может применить предикат: используйте DELETE, который его применяет. (REQ-1942) [tool-verified: provisa/compiler/write_admission.py admit_write]
То, что нельзя решить, отклоняется по имени, а не допускается при сомнении (INSERT … SELECT, значение, которое не является ни литералом, ни связанным параметром, фильтр, читающий колонку, которую инструкция не предоставляет). [tool-verified: provisa/compiler/write_admission.py, provisa/pgwire/copy_handler.py]
COPY¶
Поддерживаются и COPY ... TO STDOUT, и COPY ... FROM STDIN. (REQ-585)
COPY TO STDOUT — экспортирует результаты запросов в формате передачи COPY PG. Работают две формы:
-- Table reference
COPY my_table TO STDOUT WITH (FORMAT csv)
-- Arbitrary query
COPY (SELECT col1, col2 FROM my_table WHERE ...) TO STDOUT WITH (FORMAT text)
text (с разделителями-табуляциями, по умолчанию), csv и binary — двоичный формат COPY PostgreSQL, которым читает расширение postgres в DuckDB. (REQ-883) [tool-verified: provisa/pgwire/copy_handler.py, provisa/pgwire/copy_binary.py]
COPY FROM STDIN — это запись данных в существующую зарегистрированную таблицу: она допускается тем же допущением записи, что и INSERT тех же строк, с дополнительным условием, что у разрешения insert нет пресетов (см. Запись данных). Он читает text (по умолчанию) и csv. Любой другой формат отклоняется с SQLSTATE 0A000 с указанием его имени до отправки каких-либо данных. [tool-verified: _parse_copy_data, provisa/pgwire/copy_handler.py]
Транзакции и команды сессии¶
SET, BEGIN, COMMIT, ROLLBACK, SAVEPOINT, RELEASE, DISCARD, RESET и DEALLOCATE перехватываются и возвращают пустой успешный ответ. (REQ-587) Сервер не хранит состояние транзакций — изоляция транзакций и откат не поддерживаются. (REQ-587) [tool-verified: catalog.py:27-31, catalog.py:1129-1132]
Перехват каталога¶
Запросы к information_schema и pg_catalog обрабатываются локально без обращения к Trino. (REQ-532) Слой перехвата строит базу данных DuckDB в памяти на каждый запрос, заполненную из контекста компиляции роли. (REQ-532) [tool-verified: catalog.py:210-213]
Перехватываемые таблицы:
information_schema: schemata, tables, columns, views, table_constraints, key_column_usage, referential_constraints
pg_catalog: pg_namespace, pg_class, pg_attribute, pg_type, pg_attrdef, pg_description, pg_index, pg_constraint, pg_proc, pg_roles, pg_auth_members, pg_database, pg_settings, pg_tables, pg_stat_user_tables, pg_statio_user_tables, pg_am, pg_extension, pg_enum, pg_stat_activity
[tool-verified: catalog.py:39-67]
pg_constraint заполняется реальными данными PK и FK, выведенными из полей pk_columns и joins доменной модели. (REQ-392, REQ-399) Инструменты BI, проверяющие связи по внешним ключам (Tableau, DBeaver и т. д.), увидят граф соединений, известный Provisa. [tool-verified: catalog.py:551-632] Однозначные соединения между одной и той же парой источник/цель, чьи целевые столбцы вместе образуют составной первичный ключ цели, схлопываются в одну строку FK с многоэлементными массивами conkey/confkey. (REQ-1094) [tool-verified: catalog_constraints.py]
Связь через junction (REQ-1586) не порождает строку FK. Это ребро через ассоциативную таблицу, а не пара столбцов, и у pg_constraint нет формы для двух переходов — поэтому доменная модель не включает её в joins, а сама junction-таблица выглядит как обычная таблица с собственными внешними ключами на каждый из концов. SQL-клиенты добираются до неё соединением с этой таблицей; Cypher-клиенты проходят её как одну связь. [tool-verified: provisa/compiler/schema_gen.py:302-306]
pg_index заполняется одной строкой на каждое ограничение первичного ключа и UNIQUE (indrelid = oid таблицы, indkey = упорядоченные attnum ключа, установлены indisprimary/indisunique). Клиенты, разрешающие столбцы ключа через pg_index.indkey, а не через pg_constraint — например, DataGrip — обнаруживают правильные столбцы через стандартное соединение pg_index → pg_attribute. (REQ-1095) [tool-verified: catalog_constraints.py:340-384]
Также перехватываются следующие скалярные выражения: (REQ-588)
current_user,session_user→ аутентифицированныйrole_idcurrent_database()→"provisa"current_schema()→"public"version()→"PostgreSQL 14.0 on Provisa"pg_backend_pid()→0current_setting(...)→ возвращает значение из фиксированной таблицы настроекSHOW <setting>→ возвращает значение из той же таблицы настроек
[tool-verified: catalog.py:168-207, catalog.py:1076-1120]
Бинарное кодирование параметров¶
Протокол расширенного запроса (Bind/Execute) поддерживает параметры в бинарной кодировке. (REQ-589) Следующие OID типов декодируются из бинарного вида: [tool-verified: postgres.py:69-97]
| OID | Тип PG | Тип Python |
|---|---|---|
| 16 | bool | bool |
| 17 | bytea | bytes |
| 20 | int8 | int |
| 21 | int2 | int |
| 23 | int4 | int |
| 25 | text | str |
| 700 | float4 | float |
| 701 | float8 | float |
| 1043 | varchar | str |
| 1082 | date | datetime.date |
| 1114 | timestamp | datetime.datetime |
| 1184 | timestamptz | datetime.datetime (UTC) |
| 1700 | numeric | decimal.Decimal |
| 2950 | uuid | str |
Любой OID, отсутствующий в этой таблице, вызывает ошибку "Unsupported binary parameter type: <oid>". (REQ-589) [tool-verified: postgres.py:579]
Столбцы результата также отправляются в бинарном виде, если клиент это запрашивает, для того же набора типов плюс ARRAY, JSON, INTERVAL и BIGINT. (REQ-589) [tool-verified: postgres.py:191-244]
Рекомендации по драйверам¶
Нативные драйверы Python (psycopg2, asyncpg). Они по умолчанию согласовывают протокол расширенного запроса и используют бинарную кодировку для большинства типов. Точность типов здесь наивысшая — столбцы NUMERIC приходят как Decimal, TIMESTAMP как datetime и так далее. Используйте их для ETL на Python, скриптов или прямой интеграции.
JDBC (драйвер PostgreSQL JDBC). Используйте его для инструментов Java-экосистемы: DBeaver, Tableau, Power BI, Metabase, операторов Airflow JDBC. JDBC по умолчанию использует протокол простого запроса, что избегает сложностей бинарной кодировки. Строка подключения:
Некоторые BI-инструменты на базе JDBC при подключении отправляют залп запросов к information_schema и pg_catalog, чтобы заполнить свой браузер схемы. Все они обрабатываются слоем перехвата каталога — при исследовании схемы трафик к Trino не генерируется. (REQ-532)
Когда какой предпочесть. Если клиент на Python, используйте psycopg2 или asyncpg для лучшей обработки типов. Если клиент — BI-инструмент или любое JVM-приложение, используйте JDBC. Избегайте смешивания ожиданий бинарного и текстового протокола в одном соединении, если вы наблюдаете неожиданности при преобразовании типов — поведение JDBC в текстовом режиме проще для анализа.
Оговорки и ограничения¶
Только SQL. Слушатель pgwire разбирает и выполняет только SQL — строки GraphQL и Cypher не принимаются. (REQ-614) Запись данных в существующие таблицы допускается по правам роли, её разрешениям на колонки и её фильтру строк; см. Запись данных.
Без определений. CREATE, ALTER и DROP отклоняются с SQLSTATE 0A000, что бы ни было у роли, кроме собственной временной таблицы сеанса; см. Временные таблицы. TRUNCATE — это запись данных. (REQ-615, REQ-616)
Нет реальной поддержки транзакций. BEGIN/COMMIT/ROLLBACK принимаются и молча игнорируются. Каждый оператор выполняется независимо. (REQ-587) [tool-verified: server.py:146-158 — in_transaction() always returns False]
Тайм-аут запроса. Инструкция может использовать собственный тайм-аут запроса pgwire, limits.request_timeouts.pgwire, или общий limits.request_timeout, если первый не задан; оба являются настройками оператора. (REQ-590, REQ-1905) [tool-verified: provisa/core/limits.py, server.py]
Ввод COPY — text или csv. COPY ... TO STDOUT пишет text, csv и binary; COPY ... FROM STDIN читает text и csv. [tool-verified: _parse_copy_data, provisa/pgwire/copy_handler.py]
Подстановка параметров литеральная. Параметры $1, $2, ... подставляются как литералы SQL перед выполнением, а не отправляются как связанные параметры вышестоящему движку. Это означает, что вышестоящий движок никогда не видит подготовленный оператор. Для Trino это не имеет практического значения; для источников с прямым пулом это обходит кеширование подготовленных операторов. (REQ-581) [tool-verified: server.py:78-85]
DECLARE SCROLL буферизует весь результат. Курсор SCROLL накапливает в куче Python каждую строку, полученную от движка, чтобы по запросу обслуживать FETCH BACKWARD, ABSOLUTE, FIRST и LAST. При большом результате весь результат живёт в памяти, пока курсор открыт. Используйте NO SCROLL по умолчанию для потоковой передачи только вперёд. (REQ-1862) [tool-verified: server.py:383-389]
pg_stat_activity, pg_stat_user_tables, pg_extension, pg_enum, pg_attrdef, pg_proc. Эти таблицы существуют в слое каталога, но являются пустыми заглушками. Инструменты мониторинга, запрашивающие их, получат ноль строк, а не ошибки. (REQ-532) [tool-verified: catalog.py:519-535, catalog.py:639-934] (pg_index заполняется — см. Перехват каталога.)