P·1 · El inventario de verbos — preguntado al motor
Fecha de la medición: 2026-08-12. Sonda:
scripts/bridge/p1-inventario-verbos.py,
corrida dentro del pod del puente contraspark 4.1.2-stackable26.7.0.Fase P·1 de
parser-al-motor-spec.md.Nada se ejecutó. Todo va envuelto en
EXPLAIN EXTENDEDy con nombres de tabla
inexistentes a propósito: se lee el Parsed Logical Plan, anterior a la resolución.
1 · El titular
28 formas de sentencia · planifica 28 · no planifica 0
⇒ El motor sabe planificarlas TODAS, incluidas las 23 que el editor rechaza hoy.
La lista de cinco verbos de sql-parse.ts:383
no es una limitación del lenguaje ni del motor: es nuestra.
2 · Lo medido, entero
| forma | nodo de comando | objetos → privilegio declarado por el motor |
|---|---|---|
select | Project | cat.sch.t → READ |
select-join | Project | a → READ · b → READ |
select-time-travel | Project | cat.sch.t → READ |
insert-values | InsertIntoStatement | t → INSERT |
insert-select | InsertIntoStatement | dst → INSERT · src → READ |
insert-overwrite | InsertIntoStatement | dst → INSERT,DELETE · src → READ |
update | UpdateTable | t → UPDATE |
delete | DeleteFromTable | t → DELETE |
merge | MergeIntoTable | dst → UPDATE,INSERT · src → READ |
truncate | TruncateTable | ⚠️ el motor no se pronuncia |
create-table | CreateTable | ⚠️ no se pronuncia |
ctas | CreateTableAsSelect | ⚠️ no se pronuncia · src → READ |
create-or-replace | ReplaceTable | ⚠️ no se pronuncia |
drop-table | DropTable | ⚠️ no se pronuncia |
alter-add-column | AddColumns | ⚠️ no se pronuncia |
alter-drop-column | DropColumns | ⚠️ no se pronuncia |
alter-rename-column | RenameColumn | ⚠️ no se pronuncia |
alter-alter-column | AlterColumns | ⚠️ no se pronuncia |
alter-rename-table | RenameTable | ⚠️ no se pronuncia |
alter-set-props | SetTableProperties | ⚠️ no se pronuncia |
alter-unset-props | UnsetTableProperties | ⚠️ no se pronuncia |
create-view | CreateView | ⚠️ no se pronuncia · t → READ |
drop-view | DropView | ⚠️ no se pronuncia |
create-schema | CreateNamespace | ⚠️ no se pronuncia |
drop-schema | DropNamespace | ⚠️ no se pronuncia |
describe | DescribeRelation | ⚠️ no se pronuncia |
show-tables | ShowTables | ⚠️ no se pronuncia |
call-procedure | Call | ⚠️ no se pronuncia |
⚠️ Y una trampa que la sonda tuvo primero
La primera versión pintaba de READ los tres casos —declara lectura, anotación
vacía y sin anotación— y el resultado parecía decir que Spark declara que un
DROP TABLE es una lectura. No lo declara. Hay tres estados y hay que
distinguirlos:
__required_write_privileges__=X el motor DECLARA que exige X
anotación presente y vacía declara que sólo lee
SIN anotación el motor NO DICE NADA ← el DDL entero
Un instrumento que funde «no hay» con «no supe mirar» no mide: opina.
3 · ⭐⭐ Los dos ejes — y es lo que estructura todo P·5
Eje A · escrituras de DATOS — el privilegio lo regala el motor
Seis formas, y Spark declara el privilegio por relación, compuesto cuando toca y distinguiendo destino de fuente:
INSERT INSERT
INSERT OVERWRITE INSERT,DELETE ← compuesto
UPDATE UPDATE
DELETE DELETE
MERGE UPDATE,INSERT ← compuesto, y la fuente sólo READ
Eso es exactamente lo que locateDmlCommand intenta adivinar con una regex — y el
motor lo dice sin que haya que adivinar nada. Aquí P·1 se reduce a traducir su
vocabulario (INSERT/UPDATE/DELETE/READ) al nuestro (TABLE_WRITE_DATA,
TABLE_READ_DATA…).
Eje B · DDL y metadata — el privilegio lo ponemos nosotros
22 formas donde el motor no se pronuncia. Pero da algo casi tan bueno: el nodo de
comando, que es inequívoco (AddColumns, DropColumns, RenameTable,
SetTableProperties, CreateTable, ReplaceTable, DropTable, CreateView,
DropView, CreateNamespace, DropNamespace, TruncateTable, Call…).
⇒ El trabajo del eje B es una tabla nodo → privilegio, y buena parte ya existe:
CATALOG_OPS cubre createTable, dropTable, createView, dropView,
createNamespace, dropNamespace, updateTable.
4 · ⭐⭐⭐ El hallazgo que cambia el orden del trabajo
CATALOG_OPS no está indexado por verbo SQL: está indexado por operación del
catálogo Iceberg (loadTable, updateTable, createTable…). Y un verbo SQL se
convierte, al ejecutarse, en una o varias de esas operaciones contra la cara.
⇒ ALTER TABLE … ADD COLUMN YA ESTÁ GOBERNADO. Al ejecutarse acaba en un
loadTable + updateTable contra /api/iceberg/v1, y la cara lo autoriza con
TABLE_WRITE_DATA como cualquier otra escritura. Lo único que impide hacerlo es el
gate de Node que lo rechaza antes de que llegue — la interceptación, otra vez.
No hay que construir gobernanza para los verbos nuevos. Hay que dejar de
interceptarlos antes de que lleguen a la que ya existe.
⚠️ Pero con un agujero que hay que anotar
La cara no distingue un commit que cambia FILAS de uno que cambia el ESQUEMA: los
dos son updateTable → TABLE_WRITE_DATA. Unity Catalog aquí pediría MODIFY o
propiedad para un cambio de esquema, que es más severo que escribir datos.
⇒ Conceder TABLE_WRITE_DATA hoy implica poder añadir y quitar columnas. Es una
decisión que ahora mismo está tomada por omisión, y conviene tomarla a propósito.
5 · La cuarta columna, que es la que muerde: reconciliar Index
Un verbo sin esto no es un verbo nuevo: es una mentira nueva. Medido: datasets.schema
sólo se escribe al nacer la tabla (table-materialise.ts:211)
o desde los caminos de ingesta. Ninguno es el editor.
| Verbo | Qué queda rancio en Index si no se reconcilia |
|---|---|
ADD/DROP/RENAME/ALTER COLUMN | datasets.schema · column_count · dataset_schema_versions |
INSERT · UPDATE · DELETE · MERGE · TRUNCATE | row_count · last_committed_at |
ALTER … RENAME TO | datasets.name — y con él toda resolución por nombre |
DROP TABLE | la fila del dataset (¿soft-delete de 7 d?) y sus versiones de esquema |
CREATE TABLE · CTAS | ✅ ya cubierto por table-materialise |
SET/UNSET TBLPROPERTIES | datasets.metadata — y tier, que se implementa así |
CREATE/DROP VIEW | las columnas view_* del dataset |
CREATE/DROP NAMESPACE | la tabla schemas |
⭐ El más peligroso no es el esquema: es RENAME TO. Si Index no se entera, el
nombre lógico deja de casar con el físico y se rompe la resolución de esa tabla para
todo el producto, no sólo para el editor.
5·bis · ⭐ El SEGUNDO verbo — y otro guardián con la condición caducada
Medido el 2026-08-12, en frío, tras cerrar N·5 y el primer verbo:
INSERT INTO dst SELECT * FROM src
→ WITH `src` AS (SELECT * FROM prod.w_abc.`src`) INSERT INTO dst SELECT * FROM src
INSERT OVERWRITE dst SELECT * FROM src
→ WITH `src` AS (SELECT * FROM prod.w_abc.`src`) INSERT OVERWRITE dst SELECT * FROM src
La fuente SE RESUELVE: le sale su CTE apuntando al namespace físico. Y el motivo
que junction.ts da para dejar INSERT fuera es exactamente:
«Su fuente (
SELECT … FROM …) no se resuelve: el camino de escritura cualifica el
destino pero no los nombres que lee, así que el motor recibiría un nombre lógico.»
⇒ Caducado. Era cierto antes del mecanismo de CTE; hoy el destino lo cualifica
qualifyWriteTarget —que desde P·5 también reconoce ALTER TABLE— y la fuente la
resuelve el prólogo. Es el mismo patrón que desbloqueó el ALTER: un guardián con
su condición de retirada escrita en su propio mensaje, y el mundo movido debajo (§12·43
del sustrato-06).
⚠️ Sin verificar en vivo. Esto es una medición en frío del plan; falta el canary con control negativo antes de abrirlo. Pero dice que el segundo verbo es barato, y que el coste real de cada verbo nuevo no está en el motor: está en las tres listas que todavía hay que tocar (§6).
6 · Lo que P·1 deja decidido
- La riqueza de verbos no hay que construirla: hay que dejar de bloquearla. 28 de 28 planifican.
- El eje A sale gratis — el motor declara el privilegio, compuesto y por rol.
- El eje B es una tabla
nodo → privilegio, yCATALOG_OPSya cubre la mitad. - El coste real por verbo es la reconciliación de Index, no la autorización.
- Hay una decisión de gobernanza pendiente: si un cambio de esquema debe exigir
más que
TABLE_WRITE_DATA. Hoy no lo exige, por omisión.
7 · Lo que P·1 NO cubre, y hay que medir aparte
Call(procedimientos de mantenimiento de Iceberg:rewrite_data_files,expire_snapshots) planifica — ⇒ B7 es alcanzable por la misma puerta. No medido: si el catálogo los admite tal cual.- El time travel (
VERSION AS OF) planifica. Está en P2 delsustrato-06como «producto que aún no existe»; el motor ya lo soporta. - La profundidad de coordenada: el corpus usa 3 segmentos. Con namespaces de 4
niveles medidos en
ns-profundidad.ts, falta comprobar que el plan los segmenta igual. describe/show-tablesno nombran privilegio ni son escrituras — pero hoy el editor los deja pasar por otra vía (el SQL curado del catálogo). Hay que decidir si se unifican.