⭐⭐⭐ F·1 · CONSULTAR SIN COPIAR — la federación, por la cara
Estado: 🏁
f1-gate-federacion.tsVERDE en vivo contra un PostgreSQL real.
Regla de la casa: cada hecho lleva el comando que lo demuestra.
SELECT * FROM pg_ventas.public.ventas WHERE anio = 2025
SELECT v.cliente, c.segmento
FROM pg_ventas.public.ventas v
JOIN main.default.clientes c ON c.id = v.cliente_id
1 · Por qué esto es federación GOBERNADA y un FOREIGN CATALOG no
Un catálogo federado de verdad es un catálogo que el motor ve directamente. Spark
habla con la cara (spark.sql.catalog.carbon.uri), así que un catálogo así quedaría
fuera del punto de aplicación: la federación no se debilitaría, desaparecería.
Una vista TEMPORAL no es un catálogo: la crea la puerta después de autorizar, vive en la sesión del inquilino y muere con ella. No queda nada persistente con lo que esquivarnos — y aun así el motor lee del origen sin intermediarios.
⭐⭐ Y la primitiva ya existía: la construyó C·5 para copiar. Resultó ser la misma
pieza para consultar, para el metadata-only (LIKE) y para las vistas — en los cuatro
casos lo que hace falta es que el motor pueda leer del origen bajo una autorización
nuestra; lo único que cambia es qué se hace después. ⇒ vive en
lib/governance/jdbc-vista.ts, una sola vez: dos copias serían dos sitios donde
olvidar connectionProvider o fetchsize.
2 · ⛔⛔ El rewriting, y la cicatriz que hereda
M2c ya pagó esto: sustituir un nombre de tabla reescribía también dentro de los
literales — SELECT 'ventas' FROM ventas devolvía la coordenada física en el literal.
No fallaba: MENTÍA, y se descubrió en producción.
⇒ Aquí las referencias se localizan sobre el texto ENMASCARADO (maskNoise
neutraliza literales y comentarios conservando las posiciones) y se reemplazan por
RANGO, de atrás hacia delante. No hay ninguna búsqueda de texto, que es exactamente
lo que hace imposible aquel fallo.
⭐ Los tests que valen son los negativos (refs-cualificadas.test.ts, 13): un
literal, un comentario, una columna cualificada y una comilla escapada no se tocan.
⚠️ Y no sirve scanTableRefs: colapsa al último segmento (pg.public.ventas →
ventas) porque corre después de rewriteDottedFromRefs. La federación necesita justo
lo contrario — el PRIMER segmento, que es el que puede ser una conexión.
3 · La desambiguación, por TERCERA vez
SELECT * FROM main.default.ventas es una consulta legítima del warehouse con la misma
forma exacta. ⇒ se mira si el primer segmento es una conexión declarada; si no lo
es, noEsFederado y el flujo sigue.
⭐ Es la tercera vez (C·4, C·5, F·1) y ya no es casualidad: en una puerta que habla el mismo dialecto que el motor, la desambiguación nunca puede ser sintáctica.
4 · ⭐⭐⭐ Lo que el gate mide, y cuál decide
| sonda | resultado | |
|---|---|---|
| ① | sin CONNECTION_USE no lee, y el motivo lo da el plano de permisos | ✅ |
| ② | una consulta del warehouse no se secuestra | ✅ |
| ② | ⭐ una coordenada dentro de un literal no la convierte en federada | ✅ |
| ③ | lee del origen | ✅ 5 filas |
| ④ | ⭐⭐⭐ ZERO-COPY · no nació ni un dataset | ✅ 152 → 152 |
| ⑤ | el WHERE se resuelve en el ORIGEN | ✅ 84 de 250 |
| ⑥ | ⭐⭐ JOIN mixto: origen externo × Warehouse | ✅ |
| ⑦ | el secreto no aparece ni en el resultado ni en el ledger | ✅ |
⭐⭐⭐ La que decide es la ④. Que devuelva filas sólo prueba que hay un tubo; si tras
un SELECT federado apareciera un dataset, esto sería una copia con otro nombre — y
peor, una que el usuario no pidió.
⚠️ Y la ⑥ es la que separa «puedo leer de fuera» de «puedo cruzar lo de fuera con lo mío», que es el caso por el que alguien paga.
5 · ⭐⭐ Lo que hubo que abrir en la puerta, y por qué no abre un agujero
Una consulta puramente federada no tiene ninguna tabla del Warehouse, y la puerta
la rechazaba con «No se identificó ninguna tabla del Warehouse». Eso dejaba la
federación a medias de la peor manera: el JOIN mixto funcionaba (hay una tabla que
identificar) y el SELECT a secas no.
⇒ soloFederado, hermano de soloMetadata — que ya admitía las consultas de
information_schema por el mismo motivo estructural.
⛔ Y no abre un agujero de tenencia, que es lo que esa guarda protege: el token del
motor sigue siendo el del principal, las vistas viven en la sesión de su
inquilino, y la conexión de la que salen ya pasó por CONNECTION_USE. Lo que la guarda
impide es que el motor conteste con las tablas de todos los inquilinos; aquí no hay
ninguna tabla del motor que contestar.
6 · ⏭️ Lo que NO está
- Escribir en el origen. Sólo se federan lecturas (
SELECT/WITH), y no por descuido: escribir en el sistema de otro es una decisión que no está tomada. - Orígenes que no sean PostgreSQL. El mecanismo es el mismo; cada tipo es un adaptador que hay que escribir, y la lista se queda corta a propósito.
- Una VISTA persistente sobre el origen. Hoy la relación vive en la sesión. Una vista guardada exigiría decidir cuándo se re-autoriza — y ésa es la pregunta interesante.