Deshabilite todas las restricciones y comprobaciones de tabla mientras restaura un volcado

19

He obtenido un volcado de mi base de datos PostgreSQL con:

pg_dump -U user-name -d db-name -f dumpfile

que luego procedo a restaurar en otra base de datos con:

psql X -U postgres  -d db-name-b -f dumpfile

Mi problema es que la base de datos contiene restricciones referenciales, comprobaciones y disparadores y algunos de estos (comprobaciones que parecerían en particular) fallan durante la restauración, ya que la información no se carga en el orden que haría que se respetaran esas comprobaciones. Por ejemplo, insertar una fila en una tabla puede estar asociado con un CHECKque llama a una plpgsqlfunción que verifica si una condición se mantiene en alguna otra tabla no relacionada. Si esa última tabla no se carga psqlantes que la anterior, se produce un error.

El siguiente es un SSCCE que produce una base de datos de este tipo que una vez vertida pg_dumpno se puede restaurar:

CREATE OR REPLACE FUNCTION fail_if_b_empty () RETURNS BOOLEAN AS $$
    SELECT EXISTS (SELECT 1 FROM b)
$$ LANGUAGE SQL;

CREATE TABLE IF NOT EXISTS a (
     i              INTEGER                    NOT NULL
);

INSERT INTO a(i) VALUES (0),(1);
CREATE TABLE IF NOT EXISTS b (
    i  INTEGER NOT NULL
);
INSERT INTO b(i) VALUES (0);

ALTER TABLE a ADD CONSTRAINT a_constr_1 CHECK (fail_if_b_empty());

¿Hay alguna manera de deshabilitar (desde la línea de comando) todas esas restricciones durante la restauración del volcado y habilitarlas nuevamente nuevamente después? Estoy ejecutando PostgreSQL 9.1.

Marcus Junius Brutus
fuente
Me pregunto, AFAIK no hay opciones -Xy -dpara pg_dump. pg_dumpproduce un volcado que se puede restaurar en una base de datos vacía.
dezso
1
@dezso bien, estos fueron errores tipográficos, he actualizado la pregunta. El volcado lamentablemente, no se puede restaurar en un DB vacío debido a las razones que estoy citando.
Marcus Junius Brutus
La pregunta necesita con urgencia tu versión de Postgres. Esto debería ser obvio sin que yo lo señale.
Erwin Brandstetter
@ErwinBrandstetter Puedo reproducir el mismo problema en 9.6, vea bugs.debian.org/cgi-bin/bugreport.cgi?bug=859033 para otro ejemplo (más mundo real, un poco más grande, MWE)
mirabilos
1
@mirabilos: Yo diría: si usa una función que hace referencia a otras tablas en una CHECKrestricción, todas las garantías se anulan, porque eso no se admite oficialmente, solo se tolera. Pero declarar la CHECKrestricción lo NOT VALIDhizo funcionar para mí en todos los aspectos. Puede haber casos de esquina que nunca toqué ...
Erwin Brandstetter

Respuestas:

17

Entonces busca otras tablas en una CHECKrestricción .

CHECKSe supone que las restricciones ejecutan IMMUTABLEcomprobaciones. Lo que pasa OK para una fila a la vez debe pasar OK en cualquier momento. Así es como CHECKse definen las restricciones en el estándar SQL. Esa es también la razón de esta restricción ( según la documentación ):

Actualmente, las CHECKexpresiones no pueden contener subconsultas ni hacer referencia a variables que no sean columnas de la fila actual.

Ahora, las expresiones en CHECKrestricciones pueden usar funciones, incluso funciones definidas por el usuario. Esas deberían estar restringidas a IMMUTABLEfunciones, pero Postgres actualmente no aplica esto. De acuerdo con esta discusión relacionada sobre pgsql-hackers , una razón es permitir referencias a la hora actual, que no es IMMUTABLEpor naturaleza.

Pero está buscando filas de otra tabla, lo que viola por completo cómo CHECKse supone que funcionan las restricciones. No me sorprende que pg_dumpno pueda prever esto.

Mueva su cheque en otra tabla a un disparador (que es la herramienta correcta), y debería funcionar con versiones modernas de Postgres.

PostgreSQL 9.2 o posterior

Si bien lo anterior es cierto para cualquier versión de Postgres, se han introducido varias herramientas con Postgres 9.2 para ayudar con su situación:

Opción pg_dump --exclude-table-data

Una solución simple sería volcar el db sin datos para la tabla infractora con:

--exclude-table-data=my_schema.my_tbl

Luego agregue solo los datos para esta tabla al final del volcado con:

--data-only --table=my_schema.my_tbl

Pero pueden surgir complicaciones con otras restricciones en la misma tabla. Hay una solución aún mejor :

NOT VALID

Existe el NOT VALIDmodificador para las restricciones. Solo disponible para la restricción FK en v9.1, pero esto se extendió a las CHECKrestricciones en 9.2. Por documentación:

Si se marca NOT VALIDla restricción, se omite la verificación inicial potencialmente larga para verificar que todas las filas de la tabla cumplan la restricción. La restricción aún se aplicará contra inserciones o actualizaciones posteriores [...]

Un archivo de volcado simple de postgres consta de tres "secciones":

  • pre_data
  • data
  • post-data

Postgres 9.2 también introdujo una opción para volcar secciones por separado -- section=sectionname, pero eso no ayuda con el problema en cuestión.

Aquí es donde se pone interesante. Por documentación:

Los elementos posteriores a los datos incluyen definiciones de índices, disparadores, reglas y restricciones distintas de las restricciones de verificación validadas . Los elementos previos a los datos incluyen todos los demás elementos de definición de datos.

El énfasis en negrita es mío.
Puede cambiar la CHECKrestricción ofensiva a NOT VALID, que mueve la restricción a la post-datasección. Suelta y recrea:

ALTER TABLE a DROP CONSTRAINT a_constr_1;
ALTER TABLE a ADD  CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()) NOT VALID;

Esto debería solucionar tu problema. Incluso puede dejar la restricción en ese estado , ya que eso refleja mejor lo que realmente hace: verificar nuevas filas, pero no ofrece garantías para los datos existentes. No hay nada malo con una NOT VALIDrestricción de verificación. Si lo prefiere, puede validarlo más tarde:

ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;

Pero luego vuelves al status quo ante.

Erwin Brandstetter
fuente
He enriquecido la pregunta con un SSCCE que muestra una base de datos que no se puede restaurar. Entiendo lo que estás diciendo, sin embargo, no veo por qué la situación problemática que muestro en mi SSCCE no puede reproducirse también con disparadores en lugar de cheques.
Marcus Junius Brutus
1
@MarcusJuniusBrutus: Porque las restricciones de verificación se evalúan para todas las filas que ya están en la tabla en la creación, mientras que los disparadores solo se ejecutan en eventos definidos.
Erwin Brandstetter
Reproduje la lógica del esquema exacto usando disparadores. Al usar desencadenadores, la restauración realmente tiene éxito, pero parece que esto se debe solo al hecho de que pg_dumpagrega los desencadenantes al final del archivo de volcado, mientras que crea la CHECKs como parte del CREATE TABLEcomando. Por lo tanto, la restauración también podría haber tenido éxito para el caso de verificación si la pg_dumpherramienta utilizara un enfoque diferente. No entiendo por qué mi DDL está bien si uso desencadenantes, pero no está bien si uso comprobaciones ya que se implementa exactamente la misma lógica en ambos casos (puede ver la versión del script que usa desencadenantes en mi propia respuesta).
Marcus Junius Brutus
1
@MarcusJuniusBrutus: si cree que pg_dumpdebe generar diferentes DDL para verificar las restricciones (por ejemplo, agregarlas todas al final), debe publicarlo en la lista de correo de Postgres como una solicitud de mejora. Pero sí estoy de acuerdo con Erwin en que estás usando mal las restricciones de verificación para algo para lo que no fueron diseñadas. Por lo tanto, no esperaría que esa solicitud de cambio se implemente en un futuro próximo. Por cierto: su SSCCE se modelaría mejor usando una clave foránea entre las dos tablas.
a_horse_with_no_name
@MarcusJuniusBrutus: Hay soluciones para Postgres 9.2 o posterior. Es por eso que tu versión de Postgres es crucial. ¿Quizás la actualización es una opción para ti? Postgres 9.1 está envejeciendo de todos modos ...
Erwin Brandstetter
2

Parece que esto se debe a la forma en que pg_dumpcrea el volcado. Al observar el volcado real, vi que la CHECKrestricción estaba presente en el archivo de volcado utilizando la sintaxis que forma parte del CREATE TABLEcomando:

CREATE TABLE a (
    i integer NOT NULL,
    CONSTRAINT a_constr_1 CHECK (fail_if_b_empty())
);      

Esto crea la falla tras la restauración de la base de datos, ya que la verificación se realiza antes de que la tabla ao la tabla btengan datos. Sin embargo, si el archivo de volcado se edita y CHECKse agrega utilizando la siguiente sintaxis, al final del archivo de volcado:

ALTER TABLE a ADD CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()); 

... entonces no hay problema en la restauración.

La misma lógica exacta se puede implementar usando un TRIGGERcomo en el siguiente script:

CREATE OR REPLACE FUNCTION fail_if_b_empty (
    ) RETURNS BOOLEAN AS $$
    SELECT EXISTS (SELECT 1 FROM b)
$$ LANGUAGE SQL;

DROP TABLE IF EXISTS a;

CREATE TABLE IF NOT EXISTS a (
    i   INTEGER   NOT NULL
);

INSERT INTO a(i) VALUES (0),(1);

CREATE TABLE IF NOT EXISTS b (
    i  INTEGER NOT NULL
);

INSERT INTO b(i) VALUES (0);

CREATE TRIGGER tr1 AFTER INSERT OR UPDATE ON a
FOR EACH ROW
EXECUTE PROCEDURE fail_if_b_empty();  

Sin embargo, en este caso, pg_dumpcrea (de forma predeterminada) el desencadenador al final del archivo de volcado (y no en la CREATE TABLEdeclaración como en el caso de una comprobación) y, por lo tanto, la restauración se realiza correctamente.

Marcus Junius Brutus
fuente
no veo ningún desencadenante en su ejemplo
Sam Watkins
1
Error de copiar y pegar de @SamWatkins, lo arregló.
Marcus Junius Brutus