Desabilite todas as restrições e verificações de tabela ao restaurar um dump

19

Eu obtive um despejo do meu banco de dados PostgreSQL com:

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

que depois procuro restaurar em outro banco de dados com:

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

Meu problema é que o banco de dados contém restrições referenciais, verificações e gatilhos, e algumas delas (verificações ao que parece, em particular) falham durante a restauração, pois as informações não são carregadas na ordem em que essas verificações sejam respeitadas. Por exemplo, a inserção de uma linha em uma tabela pode estar associada a uma CHECKque chama uma plpgsqlfunção que verifica se uma condição está em alguma outra tabela não relacionada. Se essa última tabela não for carregada psqlantes da anterior, ocorrerá um erro.

A seguir, é apresentado um SSCCE que produz um banco de dados com o qual uma vez despejado pg_dumpnão pode ser restaurado:

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 uma maneira de desativar (na linha de comando) todas essas restrições durante a restauração de despejo e ativá-las novamente depois? Estou executando o PostgreSQL 9.1.

Marcus Junius Brutus
fonte
Gostaria de saber, AFAIK não há -Xe -dopções para pg_dump. pg_dumpproduz um despejo que é restaurável em um banco de dados vazio.
Dez
1
@ Dezso certo, estes foram erros de digitação, eu atualizei a pergunta. Infelizmente, o despejo não é restaurável em um banco de dados vazio devido aos motivos que estou citando.
Marcus Junius Brutus
A pergunta precisa muito da sua versão do Postgres. Isso deve ser óbvio sem que eu aponte.
Erwin Brandstetter
@ErwinBrandstetter Posso reproduzir o mesmo problema na versão 9.6, consulte bugs.debian.org/cgi-bin/bugreport.cgi?bug=859033 para outro exemplo ( MWE do mundo real, um pouco maior)
mirabilos
1
@mirabilos: eu diria: se você usar uma função que faça referência a outras tabelas em uma CHECKrestrição, todas as garantias serão anuladas, porque isso não é oficialmente suportado, apenas tolerado. Mas declarar a CHECKrestrição NOT VALIDfez funcionar para mim em todos os aspectos. Pode haver casos de canto eu nunca toquei ...
Erwin Brandstetter

Respostas:

17

Então, você consulta outras tabelas com uma CHECKrestrição .

CHECKas restrições devem executar IMMUTABLEverificações. O que passa OK por uma linha de uma vez deve passar OK a qualquer momento. É assim que as CHECKrestrições são definidas no padrão SQL. Essa também é a razão dessa restrição ( por documentação ):

Atualmente, as CHECKexpressões não podem conter subconsultas nem se referem a variáveis ​​que não sejam colunas da linha atual.

Agora, expressões em CHECKrestrições podem usar funções, mesmo funções definidas pelo usuário. Essas devem ser restritas a IMMUTABLEfunções, mas o Postgres atualmente não impõe isso. De acordo com essa discussão relacionada ao pgsql-hackers , uma razão é permitir referências ao horário atual, o que não é IMMUTABLEpor natureza.

Mas você está procurando linhas de outra tabela, o que viola completamente como as CHECKrestrições devem funcionar. Não estou surpreso que pg_dumpnão consiga prever isso.

Mova seu cheque em outra tabela para um gatilho (que é a ferramenta certa) e deve funcionar com versões modernas do Postgres.

PostgreSQL 9.2 ou posterior

Embora o acima seja verdadeiro para qualquer versão do Postgres, várias ferramentas foram introduzidas no Postgres 9.2 para ajudar na sua situação:

opção pg_dump --exclude-table-data

Uma solução simples seria despejar o banco de dados sem dados para a tabela violadora com:

--exclude-table-data=my_schema.my_tbl

Em seguida, acrescente apenas os dados para esta tabela no final do dump com:

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

Mas podem ocorrer complicações com outras restrições na mesma tabela. Existe uma solução ainda melhor :

NOT VALID

Existe o NOT VALIDmodificador para restrições. Disponível apenas para restrição FK na v9.1, mas isso foi estendido às CHECKrestrições na 9.2. Por documentação:

Se a restrição estiver marcada NOT VALID, a verificação inicial potencialmente longa para verificar se todas as linhas da tabela atendem à restrição foi ignorada. A restrição ainda será aplicada contra inserções ou atualizações subsequentes [...]

Um arquivo de despejo simples do postgres consiste em três "seções":

  • pre_data
  • data
  • post-data

O Postgres 9.2 também introduziu uma opção para despejar seções separadamente -- section=sectionname, mas isso não está ajudando no problema em questão.

Aqui é onde fica interessante. Por documentação:

Os itens pós-dados incluem definições de índices, gatilhos, regras e restrições que não sejam restrições de verificação validadas . Os itens de pré-dados incluem todos os outros itens de definição de dados.

Negrito ênfase minha.
Você pode alterar a CHECKrestrição incorreta para NOT VALID, que move a restrição para a post-dataseção. Soltar e recriar:

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

Isso deve resolver seu problema. Você pode até deixar a restrição nesse estado , pois isso reflete melhor o que realmente faz: verifique novas linhas, mas não dê garantias para os dados existentes. Não há nada de errado com uma NOT VALIDrestrição de cheque. Se preferir, você pode validá-lo mais tarde:

ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;

Mas então você está de volta ao status quo ante.

Erwin Brandstetter
fonte
Enriqueci a pergunta com um SSCCE que mostra um banco de dados que não pode ser restaurado. Entendo o que você está dizendo, no entanto, não vejo por que a situação problemática que mostro no meu SSCCE também não pode ser reproduzida com gatilhos em vez de verificações.
Marcus Junius Brutus
1
@MarcusJuniusBrutus: porque as restrições de verificação são avaliadas para todas as linhas que já estão na tabela na criação, enquanto os gatilhos são executados apenas em eventos definidos.
Erwin Brandstetter
Reproduzi a lógica exata do esquema usando gatilhos. Usando gatilhos, a restauração é bem-sucedida, mas parece que isso ocorre apenas porque pg_dumpos gatilhos são adicionados no final do arquivo de despejo, enquanto cria os CHECKs como parte do CREATE TABLEcomando. Portanto, a restauração também poderia ter sido bem-sucedida no caso de verificação se a pg_dumpferramenta usasse uma abordagem diferente. Não consigo entender por que meu DDL está OK se eu usar gatilhos, mas não como OK se eu usar verificações, pois a mesma lógica é implementada nos dois casos (você pode ver a versão do script usando gatilhos na minha própria resposta).
Marcus Junius Brutus
1
@MarcusJuniusBrutus: se você acha que pg_dumpdeve gerar DDL diferente para restrições de verificação (por exemplo, adicionando todas elas no final), você deve publicá-lo na lista de discussão do Postgres como uma solicitação de aprimoramento. Mas concordo com Erwin que você está usando mal as restrições de verificação para algo para o qual não foram projetadas. Portanto, eu não esperaria que essa solicitação de mudança fosse implementada em um futuro próximo. Btw: seu SSCCE seria melhor modelado usando uma chave estrangeira entre as duas tabelas.
A_horse_with_no_name
@ MarcusJuniusBrutus: Existem soluções para o Postgres 9.2 ou posterior. É por isso que sua versão do Postgres é crucial. Talvez a atualização seja uma opção para você? Postgres 9.1 é o envelhecimento de qualquer maneira ...
Erwin Brandstetter
2

Parece que isso se deve à maneira como pg_dumpcria o despejo. Observando o despejo real, vi que a CHECKrestrição estava presente no arquivo de despejo usando a sintaxe que faz parte do CREATE TABLEcomando:

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

Isso cria a falha na restauração do banco de dados, à medida que a verificação é realizada antes que a tabela aou a tabela bcontenha dados. Se, no entanto, o arquivo de despejo for editado e CHECKadicionado usando a seguinte sintaxe, no final do arquivo de despejo:

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

... então não há problema na restauração.

A mesma lógica exata pode ser implementada usando a TRIGGERcomo no script a seguir:

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

Nesse caso, no entanto, pg_dumpcria (por padrão) o gatilho no final do arquivo de despejo (e não na CREATE TABLEinstrução como no caso de uma verificação) e, portanto, a restauração é bem-sucedida.

Marcus Junius Brutus
fonte
Não vejo qualquer gatilho no seu exemplo
Sam Watkins
1
Erro de copiar e colar do @SamWatkins, corrigido.
Marcus Junius Brutus