# pg_synonym [![CI](https://github.com/Mazuco/pg_synonym/actions/workflows/ci.yml/badge.svg)](https://github.com/Mazuco/pg_synonym/actions/workflows/ci.yml) ![PostgreSQL 15+](https://img.shields.io/badge/PostgreSQL-15%2B-336791?logo=postgresql&logoColor=white) [![Licença: PostgreSQL](https://img.shields.io/badge/licen%C3%A7a-PostgreSQL-blue.svg)](LICENSE) **`CREATE SYNONYM` para PostgreSQL.** Dê a uma tabela ou view um segundo nome em outro schema, e continue lendo e gravando por ele enquanto a tabela de verdade muda por baixo. 🇺🇸 [Read in English](README.md) ```sql -- SQL Server: CREATE SYNONYM vendas.cliente FOR erp.clientes SELECT synonym.create('vendas.cliente', 'erp.clientes'); SELECT * FROM vendas.cliente; -- lê erp.clientes INSERT INTO vendas.cliente (nome) VALUES ('Ana'); -- grava em erp.clientes ``` ## Por quê? SQL Server e Oracle têm sinônimos. As aplicações usam esse recurso para esconder onde a tabela está de verdade, em qual schema ou com qual nome. O PostgreSQL não tem, e as migrações costumam cair em uma de duas gambiarras: - **Uma view.** Funciona até alguém rodar `ALTER TABLE` na tabela real. Colunas novas não aparecem, `DROP COLUMN` e `ALTER COLUMN TYPE` são recusados ("other objects depend on it") e o `DROP TABLE` exige `CASCADE`. - **Truques com `search_path`.** São globais, frágeis e invisíveis no código. O `pg_synonym` traz sinônimos de verdade. Cada um é uma view atualizável, e a extensão mantém essa view em dia com o destino automaticamente, qualquer que seja o `ALTER TABLE`. ## Como usar ### Criar e apagar ```sql SELECT synonym.create('vendas.cliente', 'erp.clientes'); SELECT synonym.drop('vendas.cliente'); SELECT synonym.drop('vendas.cliente', if_exists => true); ``` `DROP VIEW vendas.cliente`, ou `DROP OWNED BY` o dono do sinônimo, também apagam o sinônimo. Os nomes seguem as regras normais do PostgreSQL. Sem schema, o sinônimo vai para o primeiro schema do `search_path`, e o destino é procurado pelo `search_path`. Nomes entre aspas funcionam: `synonym.create('app."Pedidos"', 'erp."Pedidos Antigos"')`. ### Listar ```sql SELECT * FROM synonym.list; ``` ``` synonym | target | owner | status | created_at ----------------+---------------+-------+----------+------------------------------- vendas.cliente | erp.clientes | ana | ok | 2026-10-19 15:47:28.03-03 vendas.produto | erp.produtos | ana | pending | 2026-10-19 15:48:02.11-03 ``` | status | significado | |---|---| | `ok` | Funcionando. | | `pending` | O destino ainda não existe. O sinônimo passa a funcionar assim que ele for criado. | | `conflict` | Outro objeto já usa o nome do sinônimo. Renomeie ou apague esse objeto. | | `out of sync` | Não deveria acontecer. Rode `SELECT synonym.refresh();`. | ### Use como se fosse a tabela ```sql SELECT ... FROM vendas.cliente JOIN ...; INSERT INTO vendas.cliente (...) VALUES (...) RETURNING id; INSERT ... ON CONFLICT (id) DO UPDATE ...; UPDATE vendas.cliente SET ...; DELETE FROM vendas.cliente WHERE ...; ``` Os defaults, as colunas identity, as constraints, os triggers e a segurança por linha (RLS) da tabela de destino continuam valendo. O planner enxerga direto a tabela real, então uma consulta pelo sinônimo é exatamente tão rápida quanto uma consulta na tabela. ### Colinha SQL Server → PostgreSQL | SQL Server | pg_synonym | |---|---| | `CREATE SYNONYM s.x FOR s.y` | `SELECT synonym.create('s.x', 's.y')` | | `DROP SYNONYM s.x` | `SELECT synonym.drop('s.x')` | | `DROP SYNONYM IF EXISTS s.x` | `SELECT synonym.drop('s.x', if_exists => true)` | | `SELECT * FROM sys.synonyms` | `SELECT * FROM synonym.list` | | `CREATE SYNONYM x FOR outrobanco.dbo.y` | Não suportado. Crie uma foreign table com `postgres_fdw` e aponte o sinônimo para ela | ## O que ele mantém sincronizado | Você faz isso no destino | O sinônimo | |---|---| | `ADD COLUMN` | mostra a coluna nova | | `RENAME COLUMN` | usa o nome novo | | `ALTER COLUMN ... TYPE` | usa o tipo novo (uma view comum bloquearia isso) | | `DROP COLUMN` | perde a coluna (uma view comum bloquearia isso) | | `RENAME TO`, `SET SCHEMA` | continua apontando para a mesma tabela, como uma view faria | | `DROP TABLE` | continua definido e fica `pending` (sem precisar de `CASCADE`) | | criar de novo | volta a funcionar, já com as colunas novas | Também dá para criar um sinônimo **antes** do destino existir (ligação tardia, como no SQL Server). Ele fica `pending` e passa a funcionar quando o destino for criado. Views que você criar **em cima** de um sinônimo sobrevivem a mudanças inofensivas, como adicionar ou renomear colunas. Mudanças que exigem reconstruir o sinônimo (`DROP COLUMN`, `ALTER COLUMN TYPE`, `DROP TABLE`) são recusadas enquanto essas views existirem, que é o mesmo que o PostgreSQL faz com qualquer view. Use `CASCADE` quando o comando aceitar. ## Permissões Um sinônimo nunca dá a ninguém mais acesso do que a pessoa já tem no destino. - Toda view de sinônimo é criada com `security_invoker = true`. Quem **usa** o sinônimo precisa ter os privilégios no destino, e as políticas de RLS do destino se aplicam a essa pessoa. - Para criar um sinônimo você precisa de privilégio `CREATE` no schema do sinônimo, como para criar uma view, e de `USAGE` no schema do destino, para poder citá-lo. Não precisa de nenhum privilégio na tabela de destino em si. - Só o dono do sinônimo, um membro do papel (role) dono ou um superusuário pode apagá-lo. - O dono da tabela continua podendo dar `ALTER` ou `DROP` nela, mesmo que outros usuários tenham sinônimos apontando para ela. Instalar a extensão exige superusuário, porque ela usa event triggers. Usar não exige. ## Instalação Requer **PostgreSQL 15 ou mais novo**. Não é preciso mexer em `shared_preload_libraries` nem reiniciar o servidor. ### Linux (Debian / Ubuntu) ```bash sudo apt install build-essential postgresql-server-dev-17 # use a versão do seu PostgreSQL git clone https://github.com/Mazuco/pg_synonym.git cd pg_synonym make sudo make install ``` ### Linux (Rocky / RHEL / Fedora, pacotes do PGDG) ```bash sudo dnf install -y gcc make git redhat-rpm-config dnf-plugins-core sudo dnf config-manager --set-enabled crb # Rocky 9 (no Rocky 8: --set-enabled powertools) sudo dnf install -y postgresql17-devel make PG_CONFIG=/usr/pgsql-17/bin/pg_config with_llvm=no sudo make install PG_CONFIG=/usr/pgsql-17/bin/pg_config with_llvm=no ``` ### macOS (Homebrew) ```bash brew install postgresql@17 make PG_CONFIG=$(brew --prefix postgresql@17)/bin/pg_config make install PG_CONFIG=$(brew --prefix postgresql@17)/bin/pg_config ``` ### Docker (qualquer sistema, inclusive Windows) ```bash docker build -t pg_synonym . # PostgreSQL 17 por padrão docker build --build-arg PG_MAJOR=18 -t pg_synonym:18 . docker run -d --name pgsyn -e POSTGRES_PASSWORD=postgres -p 5432:5432 pg_synonym ``` > **Erro de compilação falando de `clang` ou `llvm`?** O seu pacote do PostgreSQL foi compilado com JIT e tenta > gerar um "bitcode" LLVM, que esta extensão não precisa. Compile com `make with_llvm=no` e instale com > `sudo make with_llvm=no install`. ### Ativando no banco ```sql CREATE EXTENSION pg_synonym; -- como superusuário ``` A extensão fica no seu próprio schema, `synonym`. ## Limitações - **Tabelas, views, materialized views, tabelas particionadas e foreign tables** podem ser destino. Funções, procedures e sequences ainda não. - **Não existe sintaxe nova.** Extensões não podem criar comandos SQL, então o `CREATE SYNONYM` vira uma chamada de função. - **Sem sinônimos entre bancos.** Aponte o sinônimo para uma foreign table do `postgres_fdw`. - **Sinônimo de sinônimo** não é permitido. O SQL Server também não permite. - **O que uma view não faz** também não funciona pelo sinônimo: `TRUNCATE`, `COPY sinonimo TO` (use `COPY (SELECT * FROM sinonimo) TO`) e DDL como `ALTER TABLE sinonimo`. `MERGE` num sinônimo exige PostgreSQL 17 ou mais novo. - **Renomear o destino é acompanhado.** O sinônimo continua apontando para a tabela depois de `RENAME` ou `SET SCHEMA`, como uma view faria. No SQL Server o vínculo é pelo nome. - **Gerencie sinônimos só por estas funções.** Renomear a view do sinônimo ou o schema dela, ou desligar o `security_invoker` na mão, quebra o vínculo. Quando um sinônimo é reconstruído, GRANTs feitos à mão nele são refeitos do zero. - **`DROP EXTENSION pg_synonym`** transforma os sinônimos existentes em views comuns e deixa o schema `synonym` para trás (`DROP SCHEMA synonym` remove). ## Desempenho - **Consultas:** nenhum custo extra. O sinônimo é uma view simples, que o planner elimina. - **DDL:** os event triggers acrescentam trabalho a `CREATE`, `ALTER` e `DROP` de tabelas e views. Medido no PostgreSQL 16: cerca de 0,4 ms por DDL sem sinônimos, e cerca de 11 ms por DDL com 500 sinônimos. ## Como funciona Um sinônimo é uma linha em `synonym.catalog` mais uma view `CREATE VIEW ... WITH (security_invoker = true) AS SELECT * FROM destino`. Event triggers mantêm os dois juntos: 1. **Antes** de `DROP TABLE`, `DROP COLUMN` ou `ALTER COLUMN TYPE` no destino, uma pequena função em C lê o comando, descobre quais sinônimos estão envolvidos e apaga as views deles, para o PostgreSQL não recusar o comando. 2. **Depois** de qualquer DDL em tabelas, views e schemas, uma função PL/pgSQL compara cada sinônimo com o destino e cria, estende, renomeia ou reconstrói a view conforme o necessário. 3. **Quando** alguém apaga a view de um sinônimo diretamente (`DROP VIEW`, `DROP OWNED`), o sinônimo é esquecido. Se o comando falhar, a transação inteira é desfeita, inclusive o passo 1. Tirando cerca de 200 linhas de C comentado, tudo é PL/pgSQL comum em [`pg_synonym--1.0.sql`](pg_synonym--1.0.sql). A explicação linha a linha está em [docs/COMO_FUNCIONA.md](docs/COMO_FUNCIONA.md). ## Rodando os testes ```bash make sudo make install make installcheck # precisa de um servidor rodando e de conexão como superusuário ``` ## Contribuindo Issues e pull requests são bem-vindos. Qualquer mudança de comportamento deve vir com um teste de regressão em `test/sql/` e a saída esperada em `test/expected/`. ## Licença Distribuída sob a [Licença PostgreSQL](LICENSE).