Saltar a contenido

Constraints e índices

column(unique=True) y column(index=True) hablan de una columna. Lo que abarca varias va en __constraints__:

class Asignacion(Table):
    __tablename__ = 'asignacion'
    __constraints__ = [
        unique_constraint('usuario_id', 'rol_id'),
        index_constraint('usuario_id', 'creado_en'),
    ]

class Usuario(Table):
    __tablename__ = 'usuario'
    __constraints__ = unique_constraint('tenant_id', 'email')   # uno solo, sin lista
unique_constraint(*columns)
index_constraint(*columns, unique=False, method=None)

method es el tipo de índice del motor ('btree', 'hash', 'gin', 'hnsw'…), cuando el motor lo admite.

Las reglas duras

El orden de las columnas es significativo

Y se preserva. En un índice, el prefijo por la izquierda decide para qué consultas sirve: ('a','b') no es lo mismo que ('b','a').

Una sola forma de decir cada cosa

  • Una unique_constraint de una sola columna es un error: para eso está column(unique=True).
  • Un index_constraint de una sola columna solo vale si pide unique=True o method=..., que es justo lo que column(index=True) no sabe expresar.

Una lista de cadenas se rechaza

__constraints__ = ['a', 'b'] no dice si es una constraint sobre las dos columnas o dos constraints de una columna. Hay que usar los constructores.

Además:

  • Se declaran sobre columnas físicas, no sobre relaciones: usuario_id, no usuario. El análisis detecta ese caso concreto y lo dice.
  • El nombre no se declara: lo fija tai-sql de forma determinista, y es el mismo que emiten el DDL y el cliente generado.

Cómo se nombran

Objeto Patrón
Restricción de unicidad uq_<tabla>_<col1>_<col2>
Índice ix_<tabla>_<col1>_<col2>
Índice único uix_<tabla>_<col1>_<col2>

Que el nombre sea determinista es lo que permite que push sepa qué eliminar: un nombre inventado por el motor hacía que el DROP CONSTRAINT no acertara nunca.

Un índice ajeno no se elimina nunca

Si un índice no lleva esa convención de nombres, push da por hecho que lo creó alguien a mano y lo respeta, aunque el schema no lo declare. Es deliberado.

Una unique compuesta es también la clave natural

tai-sql feed hace upsert sobre la clave natural de la tabla, y esa clave es una unique_constraint. Una tabla sin ninguna, cuya clave primaria sea autoincremental, no tiene clave de conflicto: el feed corta con un error en vez de duplicar filas en cada pasada.

class Rol(Table):
    __tablename__ = 'rol'
    __constraints__ = unique_constraint('nombre', 'tenant_id')   # ← clave natural

Ver feed().

Lo que push valida antes de crear una unique

Añadir una restricción de unicidad sobre una columna que ya tiene duplicados es una operación BLOCKED: push corta y te dice cómo resolverlo, en vez de fallar a mitad. Lo mismo con una FK nueva que dejaría filas huérfanas. Ver tai-sql push.