Dumps do PostgreSQL entre versões podem corromper dados sem quote_all_identifiers
Um parâmetro pouco conhecido do pg_dump evita que palavras promovidas a reservadas entre versões reescrevam CHECK constraints e políticas de RLS em silêncio durante a restauração.

Um parâmetro pouco conhecido do pg_dump evita que palavras promovidas a reservadas entre versões reescrevam CHECK constraints e políticas de RLS em silêncio durante a restauração.
Um consultor PostgreSQL↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → que atua pela PGX Inc. publicou no Planet PostgreSQL uma análise detalhada de um parâmetro que a maioria dos DBAs nunca precisou acionar manualmente: quote_all_identifiers. O texto parte de um fato incômodo: um pg_dump pode gerar um arquivo sintaticamente válido, restaurá-lo sem erro nenhum e, mesmo assim, ter silenciosamente desativado uma constraint ou uma política de segurança em nível de linha. Não é um bug recente. É um comportamento que existe desde a versão 9.1 e que só aparece quando duas versões do PostgreSQL discordam sobre quais palavras são reservadas.
Duas listas de keywords, um só arquivo de dump
Um dump não é escrito por um programa só. pg_dump monta diretamente as partes estruturais, como CREATE TABLE, listas de colunas, cabeçalhos de COPY e ALTER ... OWNER TO, usando sua própria função de quoting, a fmtId(). Para o resto, como o corpo de uma view, uma definição de índice ou uma expressão de CHECK, ele pergunta ao servidor, que responde via pg_get_viewdef(), pg_get_indexdef(), pg_get_constraintdef() e funções equivalentes, todas apoiadas em quote_identifier(), dentro de ruleutils.c.
A regra de quoting é simples: um identificador só fica entre aspas se começar com algo diferente de letra minúscula ou underscore, se tiver caracteres fora de letras minúsculas, dígitos e underscore, ou se for uma keyword reservada. Caso contrário, fica sem aspas, porque SELECT id FROM orders é mais legível que SELECT "id" FROM "orders", e definições de view são lidas por humanos com mais frequência do que se imagina.
O problema é que pg_dump e o servidor carregam cada um a sua própria lista de keywords, correspondente à sua própria versão major. Numa migração, pg_dump é da versão de destino; o servidor de origem, da versão antiga. Quando as duas listas concordam, nada se nota. Quando uma palavra muda de categoria entre as duas versões, a mesma instrução CREATE TABLE do dump acaba sendo escrita por duas versões diferentes do PostgreSQL ao mesmo tempo, cada metade quotando o que achar necessário.
O exemplo que não dá erro nenhum
O autor do texto constrói o caso com a palavra system_user, promovida a reservada na versão 16. Um pg_dump da versão 18 extraindo uma tabela audit de um servidor 15 produz algo como:
CREATE TABLE public.audit (
id integer NOT NULL,
"system_user" text NOT NULL,
action text,
CONSTRAINT audit_system_user_check CHECK ((system_user <> ''::text))
);A coluna, escrita pelo próprio pg_dump 18, sai corretamente entre aspas. A expressão do CHECK, deparseada pelo servidor 15 (que nunca ouviu falar da palavra como reservada), sai sem aspas. Mesma instrução, duas origens, duas regras de quoting.
Ao restaurar esse dump num PostgreSQL 18, três coisas acontecem, e só uma delas avisa:
- O índice que usava
system_userfalha na restauração, porque a palavra bare agora é interpretada como chamada de função, e funções em expressão de índice precisam ser marcadasIMMUTABLE. Esse erro aparece no log, bloqueia a restauração e alguém vai notar. - A
CHECK constrainté reescrita silenciosamente paraCHECK (SYSTEM_USER <> ''::text), ondeSYSTEM_USERvirou a função que devolve a identidade autenticada da sessão (algo comoscram-sha-256:alice). Como essa string nunca é vazia, a constraint passa a aceitar qualquer valor, inclusive nulo. A regra de negócio que impedia a coluna de ficar em branco deixou de existir, sem erro algum. - Uma política de RLS que usava
USING ((system_user = CURRENT_USER))vira uma comparação entre a identidade autenticada completa e o nome de papel puro. Como as duas strings nunca coincidem, ninguém mais enxerga suas próprias linhas. O usuário que via seus dados na versão 15 some da visão na versão 18.
Em resumo: o sintoma visível (o índice quebrado) é o menor dos três problemas. Os dois que realmente importam, a constraint neutralizada e a política de RLS inoperante, não deixam rastro na restauração. O primeiro sinal costuma ser um chamado de suporte, semanas depois, perguntando por que um dado inválido passou ou por que um usuário não vê mais nada.
Dar o --quote-all-identifiers para o pg_dump resolve os três casos de uma vez, porque "system_user" entre aspas é um nome de coluna legítimo em qualquer versão que o PostgreSQL já lançou ou vá lançar.
Por que isso não é o padrão
O parâmetro nasceu do bug #5488, de junho de 2010: uma tabela com coluna chamada window não conseguia ser restaurada na versão 8.4, que havia acabado de reservar a palavra para funções de janela. O autor do relato pediu que pg_dump quotasse toda coluna, sempre. Tom Lane recusou a ideia de torná-la padrão:
A legibilidade do script de dump é algo ao qual atribuímos um valor não nulo.
Readability of the dump script is something that we put a nonzero value on.Tom Lane, desenvolvedor-chefe do PostgreSQL
O resultado foi o desenho opt-in que existe hoje: uma flag de linha de comando para a metade do cliente e um GUC de sessão para a metade do servidor, ambos ligados pela mesma chave. Dois bugs posteriores mostraram o lado inverso do problema, palavras que deixam de ser reservadas: o #8128, de 2013, com um schema chamado old, e o #14152, de 2016, com uma coluna chamada over.
Em ambos, um pg_dump mais novo deixava a palavra sem aspas por não considerá-la mais reservada, e um servidor mais antigo rejeitava a própria query do dump. É por isso que o pg_upgrade passa essa flag incondicionalmente desde então: ninguém se importa com a legibilidade do dump que uma rotina de upgrade gera sozinha e descarta em seguida.
Desde a versão 9.4 (bug over), nenhuma palavra voltou a ser des-reservada de um jeito que afetasse as duas pontas de um dump. Todas as mudanças recentes foram promoções, principalmente por causa de SQL/JSON: system_user e a família json_array/json_object na versão 16, json_table, json_value, json_query, json_exists e merge_action na 17.
A maior parte desses nomes é inofensiva dentro de um dump porque, desde as correções do CVE-2018-1058, pg_dump roda com search_path vazio, o que força o servidor a qualificar com schema toda função e tipo que deparsa. public.json_value(...) não é ambíguo. O problema recai especificamente sobre nomes de coluna usados soltos numa expressão, e no range de versões suportado hoje system_user é o único caso real.
O que o parâmetro não protege
quote_all_identifiers só atua sobre o que o servidor deparsa na hora do dump. Corpos de função em PL/pgSQL são armazenados como texto puro e reparseados apenas na primeira chamada, depois de restaurados. Uma função como esta se comporta normalmente na versão 15 e passa a devolver a identidade de autenticação silenciosamente na 18, com ou sem a flag:
Nota da redação: o código desta seção não foi executado em ambiente real. Valide antes de usar em produção.
create function who_did(p_id int) returns text language plpgsql stable as $$
declare v text;
begin
select system_user into v from audit where id = p_id;
return v;
end $$;Funções criadas com corpo BEGIN ATOMIC (recurso disponível desde a versão 14) escapam desse problema porque são parseadas no momento da criação e armazenadas como árvore no catálogo; pg_get_function_sqlbody() as deparsa junto com o resto, e aí a flag funciona. Já código de aplicação, enviado em tempo de execução, está na mesma posição do corpo de PL/pgSQL: nada num arquivo de dump consegue quotar uma query que ainda não foi disparada.
A recomendação prática
A orientação que fica do material publicado é objetiva: use --quote-all-identifiers em todo pg_dump ou pg_dumpall cujo destino seja uma versão major diferente da origem, e deixe desligado nos dumps que você vai de fato ler. Para um upgrade lógico, isso é sempre.
Uma ressalva importante: nunca ative isso como GUC global via postgresql.conf ou ALTER DATABASE ... SET. Fora do contexto de um dump pontual, ligar quote_all_identifiers faz \d passar a mostrar "text" e "jsonb" como tipos entre aspas, EXPLAIN exibir Seq Scan on "orders", e qualquer rotina que compare definições deparseadas entre dois servidores reportar que o banco inteiro mudou. Não há ganho nenhum nisso, porque o próprio pg_dump já liga o GUC sozinho quando precisa. O valor do parâmetro está exatamente em ser cirúrgico: aplicado na travessia entre versões, e só ali.
Fonte: Planet PostgreSQL, "All Your GUCs in a Row: quote_all_identifiers" (https://postgr.es/p/9yK)
Fonte: Planet PostgreSQL
Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
PostgreSQL: password_encryption define o hash de senhas novas, não migra as antigas
Um levantamento detalhado do parâmetro password_encryption mostra que ele só decide o algoritmo aplicado a senhas em texto claro no momento do CREATE ROLE ou ALTER ROLE, nunca converte o que já está armazenado em pg_authid, e não participa da autenticação em si.












