Servidor pgwire de Provisa¶
Provisa expone un endpoint del protocolo de cable de PostgreSQL (pgwire). Cualquier herramienta que hable el protocolo de cliente de PostgreSQL — psycopg2, asyncpg, DBeaver, Tableau, JDBC — puede conectarse y consultar datos de Provisa a través del mismo pipeline de gobierno que gobierna la API HTTP. (REQ-266)
Las consultas pasan por el stack de gobierno completo: aplicación de RLS, reglas de enmascaramiento, guardas de relación, verificaciones de acceso a dominio. (REQ-001, REQ-002, REQ-263) La interfaz pgwire no es un mecanismo de evasión. (REQ-002, REQ-266)
Detalles de conexión¶
El servidor se inicia cuando PROVISA_PGWIRE_PORT está configurado con un entero distinto de cero. Está deshabilitado de forma predeterminada. (REQ-527) [tool-verified: app.py:1739]
TLS. Configure PROVISA_PGWIRE_CERT y PROVISA_PGWIRE_KEY con las rutas de un certificado y una clave PEM. Cuando ambos están presentes, el servidor envuelve las conexiones entrantes en TLS. Cuando están ausentes, TLS está desactivado y el servidor responde N a las solicitudes de negociación SSL. (REQ-530) [tool-verified: server.py:1746-1750]
Versión de servidor reportada. Los clientes ven 14.0.provisa. Las herramientas que activan funciones según el número de versión pueden comportarse como si estuvieran conectadas a PostgreSQL 14. (REQ-579) [tool-verified: server.py:208]
Autenticación¶
El paquete de inicio lleva un nombre de usuario y un único campo secreto, y ningún esquema que indique qué es ese secreto. Provisa lo decide a partir del propio secreto, de modo que un cliente no necesita más configuración que user y password:
| El secreto es | Se reconoce por | Se resuelve a |
|---|---|---|
| Un token de acceso personal | su prefijo provisa_pat_ |
el propietario y el rol del token (REQ-1263) |
| Un token bearer de OIDC / del proveedor | que el proveedor configurado sea un proveedor de tokens | la identidad que afirma el token (REQ-890) |
| Una contraseña | cualquier otra cosa | la cuenta en el proveedor configurado (basic o simple) |
La decisión se toma una sola vez. Una credencial que el validador elegido rechaza no se reintenta contra otro, así que un rechazo no se convierte en un segundo intento.
El modo trust (provider: none, o middleware de autenticación inactivo) es la excepción: el nombre de usuario se usa directamente como role_id y el secreto se ignora. No lo use sobre una conexión sin cifrar.
SCRAM-SHA-256. Con provider: basic y auth.scram: true, el servidor anuncia SASL (código de autenticación 10) con SCRAM-SHA-256 y la contraseña se demuestra en lugar de enviarse. (REQ-1394) SCRAM-SHA-256-PLUS no se ofrece. A un usuario cuyo verificador aún no se ha escrito — los verificadores no pueden derivarse de hashes bcrypt — se le responde con un intercambio simulado, de modo que el cable no revela quién ha migrado; ese usuario se autentica con contraseña en texto plano sobre TLS hasta que su siguiente introducción de contraseña escriba uno. Con auth.scram desactivado, el servidor usa el tipo de autenticación 3 de PG (contraseña en texto plano). MD5 no se admite en ninguno de los dos casos.
Certificados de cliente. Defina PROVISA_MTLS_CLIENT_CA y el servidor verificará un certificado de cliente durante el handshake, antes de examinar cualquier credencial. (REQ-1228) Con PROVISA_MTLS_BIND_PRINCIPAL, el common name del certificado debe coincidir con el user con el que la conexión se autentica después. Consulte Configuración.
Los intentos fallidos se cuentan. Cinco fallos en cinco minutos bloquean la cuenta durante quince minutos, y el contador se comparte con HTTP y Bolt: un bloqueo conseguido en cualquier superficie rige en todas. (REQ-1393)
Elegir una organización. En un despliegue multiorganización, conéctese a <org>.<su-dominio> y pgwire leerá la organización del nombre de host en el ClientHello de TLS, igual que HTTP la lee de la cabecera Host. (REQ-1234) El nombre de host solicita una organización; no la concede, y un principal sin pertenencia a ella queda rechazado. Conectarse por dirección IP no solicita ninguna organización.
Qué funciona¶
SELECT¶
Todas las sentencias SELECT pasan por el pipeline de gobierno (_pipeline.py). (REQ-001, REQ-262, REQ-266) El pipeline:
- Reescribe SQL semántico a SQL físico (
rewrite_semantic_to_physical) - Aplica el gobierno (RLS, enmascaramiento, acceso a dominio) (REQ-263)
- Valida contra el esquema registrado (REQ-011)
- Enruta a Trino o al pool directo del origen (REQ-027, REQ-028)
Se admiten consultas simples de múltiples sentencias. Las sentencias separadas por punto y coma se dividen y se ejecutan en orden. (REQ-580) [tool-verified: server.py:318-381]
Las consultas parametrizadas ($1, $2, ...) se admiten tanto en el modo de consulta simple como en el modo de consulta extendida (Bind/Execute). Los parámetros se sustituyen como literales antes de la ejecución. (REQ-581) [tool-verified: server.py:78-85]
SELECT * FROM fn(args) y SELECT fn(args) — donde fn nombra una función registrada y rastreada — se interceptan antes del pipeline de gobierno y se enrutan a través del único ejecutor gobernado (invoke_tracked_function). El resultado es un conjunto de filas tipado idéntico al que devuelve cualquier otra superficie para ese comando. writable_by y las reglas de gobierno se aplican dentro del ejecutor. (REQ-1156) [tool-verified: provisa/pgwire/function_call.py:74-88]
DDL¶
Las sentencias DDL se detectan mediante la expresión regular en server.py y se despachan a DdlHandler. El rol debe tener la capacidad "ddl". (REQ-042) Sin ella, la sentencia se rechaza con SQLSTATE 42501. [tool-verified: ddl_handler.py:82-83]
Las formas de DDL reconocidas son:
CREATE TABLE / VIEW / INDEX / UNIQUE INDEX / SEQUENCE / SCHEMA
ALTER TABLE / INDEX / SEQUENCE / VIEW
DROP TABLE / VIEW / INDEX / SEQUENCE / SCHEMA
[tool-verified: server.py:56-61]
Existen dos rutas de ejecución según ddl_catalog: (REQ-582)
Ruta Trino — se usa cuando ddl_catalog es un catálogo Trino de Iceberg, Hive u otro no registrado (por ejemplo, iceberg, hive, otel, results). En esta ruta solo se admiten CREATE TABLE y CREATE VIEW. Intentar ALTER, DROP o CREATE INDEX genera un error. El nombre de la tabla se califica completamente como catalog.schema.table. [tool-verified: ddl_handler.py:92-100]
Ruta directa — se usa cuando ddl_catalog coincide con un ID de origen registrado. Se admite DDL completo: CREATE, ALTER, DROP, índices, secuencias. CREATE TABLE y CREATE VIEW se califican por esquema como schema.table. El resto del DDL (ALTER, DROP, CREATE INDEX) se transmite tal cual después de establecer el contexto de esquema. Para orígenes PostgreSQL y SQLite, el contexto se establece con SET search_path TO schema. Para MySQL y MariaDB, el contexto se establece con USE schema. [tool-verified: ddl_handler.py:139-170, ddl_handler.py:207-213]
Después del DDL en cualquiera de las dos rutas, la nueva tabla se registra en el contexto de compilación del rol para que quede consultable de inmediato. (REQ-583) [tool-verified: ddl_handler.py:216-250]
Resolución del destino de escritura. El catálogo y el esquema de DDL provienen de los campos ddl_catalog y ddl_schema del dominio. Si ddl_catalog no está configurado, el sistema usa por defecto el catálogo Iceberg. Si ddl_schema no está configurado, usa por defecto el ID del dominio. El dominio se resuelve a través de la lista domain_access del rol. (REQ-584) [tool-verified: app.py:804-811, ddl_handler.py:104-115]
COPY¶
Se admiten tanto COPY ... TO STDOUT como COPY ... FROM STDIN. (REQ-585) [tool-verified: copy_handler.py:231-257]
COPY TO STDOUT — exporta los resultados de la consulta en el formato de cable COPY de PG. Funcionan dos formas:
-- 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)
Formatos admitidos: text (delimitado por tabulaciones, predeterminado) y csv. El formato binario no se admite en la salida de COPY. [tool-verified: copy_handler.py:36-52]
COPY FROM STDIN — inserta filas en una tabla de destino. Restringido a orígenes de tipo postgresql, mysql, sqlite o mariadb. (REQ-586) Intentar COPY FROM contra un origen exclusivo de Trino (por ejemplo, Iceberg) genera un error de permisos. [tool-verified: copy_handler.py:65, copy_handler.py:351-356]
Si no se proporciona una lista de columnas, estas se infieren a partir del esquema registrado. [tool-verified: copy_handler.py:357]
Transacciones y comandos de sesión¶
SET, BEGIN, COMMIT, ROLLBACK, SAVEPOINT, RELEASE, DISCARD, RESET y DEALLOCATE se interceptan y devuelven una respuesta de éxito vacía. (REQ-587) El servidor no mantiene estado respecto a las transacciones — no hay aislamiento de transacciones ni soporte de reversión. (REQ-587) [tool-verified: catalog.py:27-31, catalog.py:1129-1132]
Intercepción de catálogo¶
Las consultas contra information_schema y pg_catalog se responden localmente sin un viaje de ida y vuelta a Trino. (REQ-532) La capa de intercepción construye una base de datos DuckDB en memoria por solicitud, poblada a partir del contexto de compilación del rol. (REQ-532) [tool-verified: catalog.py:210-213]
Tablas interceptadas:
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 se puebla con datos reales de PK y FK derivados de los campos pk_columns y joins del modelo de dominio. (REQ-392, REQ-399) Las herramientas de BI que inspeccionan relaciones de clave foránea (Tableau, DBeaver, etc.) verán el grafo de joins que Provisa conoce. [tool-verified: catalog.py:551-632] Los joins de una sola columna entre el mismo par origen/destino cuyas columnas de destino forman en conjunto la clave primaria compuesta del destino se colapsan en una sola fila de FK con arreglos conkey/confkey de varios elementos. (REQ-1094) [tool-verified: catalog_constraints.py]
pg_index se puebla con una fila por cada restricción de clave primaria y UNIQUE (indrelid = oid de la tabla, indkey = attnums de clave ordenados, indisprimary/indisunique establecidos). Los clientes que resuelven columnas clave mediante pg_index.indkey en lugar de pg_constraint — DataGrip, por ejemplo — descubren las columnas correctas a través del join estándar pg_index → pg_attribute. (REQ-1095) [tool-verified: catalog_constraints.py:340-384]
También se interceptan las siguientes expresiones escalares: (REQ-588)
current_user,session_user→ elrole_idautenticadocurrent_database()→"provisa"current_schema()→"public"version()→"PostgreSQL 14.0 on Provisa"pg_backend_pid()→0current_setting(...)→ devuelve el valor de una tabla de configuración fijaSHOW <setting>→ devuelve el valor de la misma tabla de configuración
[tool-verified: catalog.py:168-207, catalog.py:1076-1120]
Codificación binaria de parámetros¶
El protocolo de consulta extendida (Bind/Execute) admite parámetros codificados en binario. (REQ-589) Los siguientes OID de tipo se decodifican desde binario: [tool-verified: postgres.py:69-97]
| OID | Tipo PG | Tipo 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 |
Cualquier OID que no esté en esta tabla genera "Unsupported binary parameter type: <oid>". (REQ-589) [tool-verified: postgres.py:579]
Las columnas de resultado también se envían en binario cuando el cliente lo solicita, para el mismo conjunto de tipos más ARRAY, JSON, INTERVAL y BIGINT. (REQ-589) [tool-verified: postgres.py:191-244]
Recomendaciones de driver¶
Drivers nativos de Python (psycopg2, asyncpg). Estos negocian el protocolo de consulta extendida de forma predeterminada y usan codificación binaria para la mayoría de los tipos. La fidelidad de tipos es máxima aquí — las columnas NUMERIC llegan como Decimal, TIMESTAMP como datetime, y así sucesivamente. Úselos para ETL basado en Python, scripts o integración directa.
JDBC (driver JDBC de PostgreSQL). Úselo para herramientas del ecosistema Java: DBeaver, Tableau, Power BI, Metabase, operadores JDBC de Airflow. JDBC usa por defecto el protocolo de consulta simple, lo que evita complicaciones de codificación binaria. Cadena de conexión:
Algunas herramientas de BI basadas en JDBC envían una ráfaga de consultas a information_schema y pg_catalog al conectarse para poblar su explorador de esquemas. Todas se responden mediante la capa de intercepción de catálogo — no se genera tráfico a Trino durante la inspección de esquemas. (REQ-532)
Cuándo preferir uno sobre otro. Si el cliente es Python, use psycopg2 o asyncpg para un mejor manejo de tipos. Si el cliente es una herramienta de BI o cualquier aplicación JVM, use JDBC. Evite mezclar expectativas de protocolo binario y de texto en la misma conexión si observa sorpresas de conversión de tipos — el comportamiento en modo texto de JDBC es más simple de razonar.
Advertencias y restricciones¶
Solo SQL; sin mutaciones DML. El listener pgwire analiza y ejecuta únicamente SQL — no se aceptan cadenas de GraphQL ni de Cypher. (REQ-614) INSERT, UPDATE y DELETE planos no se enrutan a una ruta de escritura. (REQ-615) Escriba datos mediante COPY FROM STDIN (orígenes con capacidad de escritura) o CREATE TABLE AS; las mutaciones a nivel de fila deben pasar en cambio por las rutas de escritura de GraphQL, Cypher o Trino.
COPY y DDL requieren la capacidad ddl. Tanto COPY (en cualquier dirección) como DDL están controlados por la capacidad ddl del rol; los roles que no la tienen reciben SQLSTATE 42501. (REQ-616)
Sin soporte real de transacciones. BEGIN/COMMIT/ROLLBACK se aceptan y se ignoran silenciosamente. Cada sentencia se ejecuta de forma independiente. (REQ-587) [tool-verified: server.py:146-158 — in_transaction() siempre devuelve False]
Tiempo de espera de 60 segundos para DDL, 120 segundos para consultas. Estos valores están codificados de forma fija en los hilos del manejador. (REQ-590) El DDL de larga duración contra orígenes remotos (cambios de esquema en tablas grandes) puede agotar el tiempo de espera. [tool-verified: ddl_handler.py:136, server.py:186]
COPY FROM solo funciona con orígenes con capacidad de escritura. Iceberg, Hive, orígenes exclusivos de Trino y tipos de origen de solo lectura no aceptan COPY FROM. El error es SQLSTATE 42501. (REQ-586) [tool-verified: copy_handler.py:65]
El formato de salida de COPY es text o csv. El formato binario COPY de PG (FORMAT binary) no está implementado. [inferred: solo existen las ramas text y csv en _rows_to_copy_text / _rows_to_copy_csv]
El DDL en la ruta Trino es solo CREATE. ALTER, DROP y CREATE INDEX contra catálogos Iceberg o Hive no se admiten. Use un origen SQL registrado como ddl_catalog si necesita DDL completo. (REQ-582) [tool-verified: ddl_handler.py:92-100]
La sustitución de parámetros es literal. Los parámetros $1, $2, ... se sustituyen como literales SQL antes de la ejecución, no se envían como parámetros de enlace al motor subyacente. Esto significa que el motor subyacente nunca ve una sentencia preparada. Para Trino esto no tiene impacto práctico; para orígenes de pool directo, evita el almacenamiento en caché de sentencias preparadas. (REQ-581) [tool-verified: server.py:78-85]
pg_stat_activity, pg_stat_user_tables, pg_extension, pg_enum, pg_attrdef, pg_proc. Estas tablas existen en la capa de catálogo pero son stubs vacíos. Las herramientas de monitoreo que las consultan recibirán cero filas en lugar de errores. (REQ-532) [tool-verified: catalog.py:519-535, catalog.py:639-934] (pg_index sí está poblada — vea Intercepción de catálogo.)