Désactivez toutes les contraintes et les vérifications de table lors de la restauration d'un vidage

19

J'ai obtenu un vidage de ma base de données PostgreSQL avec:

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

que je procède ensuite à restaurer dans une autre base de données avec:

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

Mon problème est que la base de données contient des contraintes référentielles, des vérifications et des déclencheurs et certains de ces contrôles (il semblerait en particulier) échouent pendant la restauration car les informations ne sont pas chargées dans l'ordre qui entraînerait le respect de ces vérifications. Par exemple, l' insertion d' une ligne dans une table peut être associée à un CHECKqui appelle une plpgsqlfonction qui vérifie si une condition est dans une autre table sans rapport. Si cette dernière table n'est pas chargée par psqlavant la première, une erreur se produit.

Ce qui suit est un SSCCE qui produit une telle base de données qui, une fois sauvegardée avec, pg_dumpne peut pas être restaurée:

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());

Existe-t-il un moyen de désactiver (à partir de la ligne de commande) toutes ces contraintes pendant la restauration du vidage et de les réactiver par la suite? J'utilise PostgreSQL 9.1.

Marcus Junius Brutus
la source
Je me demande, AFAIK il n'y a pas -Xet d' -doptions pour pg_dump. pg_dumpproduit un vidage qui peut être restauré dans une base de données vide.
dezso
1
@dezso à droite, c'étaient des fautes de frappe, j'ai mis à jour la question. La décharge, malheureusement, n'est pas restaurable dans une base de données vide pour les raisons que je cite.
Marcus Junius Brutus du
La question a vraiment besoin de votre version de Postgres. Cela devrait être évident sans que je le souligne.
Erwin Brandstetter du
@ErwinBrandstetter Je peux reproduire le même problème sur 9.6, voir bugs.debian.org/cgi-bin/bugreport.cgi?bug=859033 pour un autre exemple (plus réel, légèrement plus grand, MWE)
mirabilos
1
@mirabilos: Je dirais: si vous utilisez une fonction qui référence d'autres tables dans une CHECKcontrainte, alors toutes les garanties sont annulées, car ce n'est pas officiellement pris en charge, juste toléré. Mais déclarer la CHECKcontrainte l'a NOT VALIDfait fonctionner à tous égards pour moi. Il y a peut-être des cas d'angle que je n'ai jamais touchés ...
Erwin Brandstetter

Réponses:

17

Vous recherchez donc d'autres tables dans une CHECKcontrainte .

CHECKles contraintes sont censées exécuter des IMMUTABLEvérifications. Ce qui passe OK pour une rangée à la fois devrait passer OK à tout moment. C'est ainsi que les CHECKcontraintes sont définies dans le standard SQL. C'est aussi la raison de cette restriction ( selon la documentation ):

Actuellement, les CHECKexpressions ne peuvent pas contenir de sous-requêtes ni faire référence à des variables autres que les colonnes de la ligne actuelle.

Désormais, les expressions dans les CHECKcontraintes sont autorisées à utiliser des fonctions, même des fonctions définies par l'utilisateur. Ceux-ci devraient être limités aux IMMUTABLEfonctions, mais Postgres ne les applique pas actuellement. Selon cette discussion connexe sur les hackers pgsql , une des raisons est de permettre les références à l'heure actuelle, qui n'est pasIMMUTABLE par nature.

Mais vous recherchez des lignes d'une autre table, ce qui est complètement en violation de la façon dont les CHECKcontraintes sont censées fonctionner. Je ne suis pas surpris que cela pg_dumpne prévoie pas cela.

Déplacez votre chèque dans une autre table vers un déclencheur (qui est le bon outil), et cela devrait fonctionner avec les versions modernes de Postgres.

PostgreSQL 9.2 ou version ultérieure

Bien que ce qui précède soit vrai pour n'importe quelle version de Postgres, plusieurs outils ont été introduits avec Postgres 9.2 pour vous aider dans votre situation:

Option pg_dump --exclude-table-data

Une solution simple serait de vider la base de données sans données pour la table violée avec:

--exclude-table-data=my_schema.my_tbl

Ajoutez ensuite uniquement les données de ce tableau à la fin du vidage avec:

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

Mais des complications avec d'autres contraintes sur la même table pourraient en résulter. Il existe une solution encore meilleure :

NOT VALID

Il y a le NOT VALIDmodificateur pour les contraintes. Disponible uniquement pour la contrainte FK dans la v9.1, mais cela a été étendu aux CHECKcontraintes dans 9.2.Par documentation:

Si la contrainte est marquée NOT VALID , la vérification initiale potentiellement longue pour vérifier que toutes les lignes de la table satisfont à la contrainte est ignorée. La contrainte sera toujours appliquée contre les insertions ou mises à jour ultérieures [...]

Un fichier de vidage postgres simple se compose de trois "sections":

  • pre_data
  • data
  • post-data

Postgres 9.2 a également introduit une option pour vider les sections séparément avec -- section=sectionname , mais cela ne résout pas le problème actuel.

Voici où cela devient intéressant. Par documentation:

Les éléments de post-données incluent des définitions d'index, de déclencheurs, de règles et de contraintes autres que les contraintes de vérification validées . Les éléments de pré-données incluent tous les autres éléments de définition de données.

Accentuation mienne.
Vous pouvez remplacer la CHECKcontrainte incriminée par NOT VALID, ce qui déplace la contrainte vers la post-datasection. Déposer et recréer:

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

Cela devrait résoudre votre problème. Vous pouvez même laisser la contrainte dans cet état , car cela reflète mieux ce qu'elle fait réellement: vérifiez les nouvelles lignes, mais ne donnez aucune garantie pour les données existantes. Il n'y a rien de mal à une NOT VALIDcontrainte de vérification. Si vous préférez, vous pouvez le valider plus tard:

ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;

Mais vous revenez au statu quo ante.

Erwin Brandstetter
la source
J'ai enrichi la question avec un SSCCE qui montre une base de données qui ne peut pas être restaurée. Je comprends ce que vous dites, mais je ne vois pas pourquoi la situation problématique que je présente dans mon SSCCE ne peut pas également être reproduite avec des déclencheurs au lieu de vérifications.
Marcus Junius Brutus
1
@MarcusJuniusBrutus: Parce que les contraintes de vérification sont évaluées pour toutes les lignes qui sont déjà dans le tableau lors de la création, alors que les déclencheurs ne s'exécutent que sur des événements définis.
Erwin Brandstetter
J'ai reproduit la logique de schéma exacte à l'aide de déclencheurs. En utilisant des déclencheurs, la restauration réussit en effet mais il semble que cela ne soit dû qu'au fait que l' pg_dumpajout des déclencheurs à la fin du fichier de vidage, alors qu'il crée le CHECKs dans le cadre de la CREATE TABLEcommande. La restauration aurait donc pu également réussir pour le cas de vérification si l' pg_dumpoutil avait utilisé une approche différente. Je ne vois pas pourquoi mon DDL est OK si j'utilise des déclencheurs mais pas OK si j'utilise des vérifications car la même logique exacte est implémentée dans les deux cas (vous pouvez voir la version du script utilisant des déclencheurs dans ma propre réponse).
Marcus Junius Brutus du
1
@MarcusJuniusBrutus: si vous pensez que vous devez pg_dumpgénérer un DDL différent pour les contraintes de vérification (par exemple, en les ajoutant toutes à la fin), vous devez le publier sur la liste de diffusion Postgres en tant que demande d'amélioration. Mais je suis d'accord avec Erwin que vous utilisez mal les contraintes de vérification pour quelque chose pour lequel elles n'ont pas été conçues. Je ne m'attendrais donc pas à ce que cette demande de changement soit mise en œuvre dans un avenir proche. Btw: votre SSCCE serait mieux modélisé en utilisant une clé étrangère entre les deux tables.
a_horse_with_no_name
@MarcusJuniusBrutus: Il existe des solutions pour Postgres 9.2 ou version ultérieure. C'est pourquoi votre version de Postgres est cruciale. Peut-être que la mise à niveau est une option pour vous? Postgres 9.1 vieillit quand même ...
Erwin Brandstetter
2

Il semble que cela soit dû à la façon dont pg_dumpcrée le vidage. En regardant le vidage réel, j'ai vu que la CHECKcontrainte était présente dans le fichier de vidage en utilisant la syntaxe qui fait partie de la CREATE TABLEcommande:

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

Cela crée l'échec lors de la restauration de la base de données car la vérification est mise en place avant que la table aou la table bcontiennent des données. Si toutefois, le fichier de vidage est modifié et CHECKest ajouté à l'aide de la syntaxe suivante à la fin du fichier de vidage:

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

... alors il n'y a pas de problème dans la restauration.

La même logique exacte peut être implémentée à l'aide de a TRIGGERcomme dans le script suivant:

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();  

Dans ce cas cependant, pg_dumpcrée (par défaut) le déclencheur à la fin du fichier de vidage (et non dans l' CREATE TABLEinstruction comme dans le cas d'une vérification) et ainsi la restauration réussit.

Marcus Junius Brutus
la source
ne voyez aucun déclencheur dans votre exemple
Sam Watkins
1
@SamWatkins erreur de copier-coller, corrigé.
Marcus Junius Brutus