Pular para o conteúdo principal

Descrição

pg_clickhouse é uma extensão do PostgreSQL que permite executar consultas remotamente em bancos de dados ClickHouse, incluindo um foreign data wrapper. Ela é compatível com PostgreSQL 13 ou superior e ClickHouse 23 ou superior.

Primeiros passos

A forma mais simples de experimentar o pg_clickhouse é a [imagem Docker], que inclui a imagem Docker padrão do PostgreSQL com as extensões pg_clickhouse e re2:
docker run --name pg_clickhouse -e POSTGRES_PASSWORD=my_pass \
       -d ghcr.io/clickhouse/pg_clickhouse:18
docker exec -it pg_clickhouse psql -U postgres
Consulte o tutorial para começar a importar tabelas do ClickHouse e aplicar pushdown de consultas.

Uso

CREATE EXTENSION pg_clickhouse;
CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
       OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');
CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
       OPTIONS (user 'default');
CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA taxi FROM SERVER taxi_srv INTO taxi;

Política de versionamento

pg_clickhouse segue o [Versionamento Semântico] em seus lançamentos públicos.
  • A versão major é incrementada em caso de mudanças na API
  • A versão minor é incrementada em caso de mudanças de SQL compatíveis com versões anteriores
  • A versão patch é incrementada em caso de mudanças apenas no binário
Depois de instalada, o PostgreSQL rastreia duas variações da versão:
  • A versão da biblioteca (definida por PG_MODULE_MAGIC no PostgreSQL 18 e superior) inclui a versão semântica completa, visível na saída da função pgch_version() ou da função pg_get_loaded_modules() do Postgres.
  • A versão da extensão (definida no arquivo de controle) inclui apenas as versões major e minor, visíveis na tabela pg_catalog.pg_extension, na saída da função pg_available_extension_versions() e em \dx pg_clickhouse.
Na prática, isso significa que uma versão que incrementa a versão patch, por exemplo, de v0.1.0 para v0.1.1, beneficia todos os bancos de dados que carregaram v0.1 e não precisam executar ALTER EXTENSION para aproveitar a atualização. Por outro lado, uma versão que incrementa as versões minor ou major será acompanhada de scripts de atualização de SQL, e todos os bancos de dados existentes que contêm a extensão devem executar ALTER EXTENSION pg_clickhouse UPDATE para aproveitar a atualização.

Referência de SQL DDL

As expressões SQL DDL a seguir usam o pg_clickhouse.

CREATE EXTENSION

Use o CREATE EXTENSION para adicionar pg_clickhouse a um banco de dados:
CREATE EXTENSION pg_clickhouse;
Use WITH SCHEMA para instalá-la em um esquema específico (recomendado):
CREATE SCHEMA ch;
CREATE EXTENSION pg_clickhouse WITH SCHEMA ch;

ALTER EXTENSION

Use ALTER EXTENSION para alterar a extensão pg_clickhouse. Exemplos:
  • Após instalar uma nova versão do pg_clickhouse, use a cláusula UPDATE:
    ALTER EXTENSION pg_clickhouse UPDATE;
    
  • Use SET SCHEMA para mover a extensão para um novo esquema:
    CREATE SCHEMA ch;
    ALTER EXTENSION pg_clickhouse SET SCHEMA ch;
    

DROP EXTENSION

Use DROP EXTENSION para remover o pg_clickhouse de um banco de dados:
DROP EXTENSION pg_clickhouse;
Este comando falha se houver objetos que dependam de pg_clickhouse. Use a cláusula CASCADE para removê-los também:
DROP EXTENSION pg_clickhouse CASCADE;

CREATE SERVER

Use CREATE SERVER para criar um servidor externo que se conecta ao servidor ClickHouse. Exemplo:
CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
       OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');
As opções compatíveis são:
  • driver: O driver de conexão do ClickHouse a ser usado, “binary” ou “http”. Obrigatório.
  • dbname: O banco de dados do ClickHouse a ser usado na conexão. O padrão é “default”.
  • fetch_size: Tamanho aproximado do lote em bytes para HTTP streaming. Os lotes são divididos nos limites das linhas. O padrão é 50000000 (50 MB). 0 desabilita o streaming e armazena a resposta completa em buffer. Tabelas externas podem substituir esse valor.
  • host: O nome do host do servidor ClickHouse. O padrão é “localhost”;
  • port: A porta à qual se conectar no servidor ClickHouse. Os padrões são os seguintes:
    • 9440 se driver for “binary” e host for um host do ClickHouse Cloud
    • 9004 se driver for “binary” e host não for um host do ClickHouse Cloud
    • 8443 se driver for “http” e host for um host do ClickHouse Cloud
    • 8123 se driver for “http” e host não for um host do ClickHouse Cloud

ALTER SERVER

Use ALTER SERVER para modificar um servidor externo. Exemplo:
ALTER SERVER taxi_srv OPTIONS (SET driver 'http');
As opções são as mesmas de CREATE SERVER.

DROP SERVER

Use o DROP SERVER para remover um servidor externo:
DROP SERVER taxi_srv;
Este comando falha se houver outros objetos que dependam do servidor. Use CASCADE para também remover essas dependências:
DROP SERVER taxi_srv CASCADE;

CREATE USER MAPPING

Use CREATE USER MAPPING para associar um usuário do PostgreSQL a um usuário do ClickHouse. Por exemplo, para associar o usuário atual do PostgreSQL ao usuário remoto do ClickHouse ao se conectar usando o servidor externo taxi_srv:
CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
       OPTIONS (user 'demo');
As opções compatíveis são:
  • user: O nome do usuário do ClickHouse. O valor padrão é “default”.
  • password: A senha do usuário do ClickHouse.

ALTER USER MAPPING

Use ALTER USER MAPPING para alterar a definição de um mapeamento de usuário:
ALTER USER MAPPING FOR CURRENT_USER SERVER taxi_srv
       OPTIONS (SET user 'default');
As opções são as mesmas que em CREATE USER MAPPING.

DROP USER MAPPING

Use DROP USER MAPPING para excluir um mapeamento de usuário:
DROP USER MAPPING FOR CURRENT_USER SERVER taxi_srv;

IMPORT FOREIGN SCHEMA

Use IMPORT FOREIGN SCHEMA para importar todas as tabelas definidas em um banco de dados do ClickHouse como tabelas externas para um esquema do PostgreSQL:
CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA demo FROM SERVER taxi_srv INTO taxi;
Use LIMIT TO para limitar a importação a tabelas específicas:
IMPORT FOREIGN SCHEMA demo LIMIT TO (trips) FROM SERVER taxi_srv INTO taxi;
Use EXCEPT para excluir tabelas:
IMPORT FOREIGN SCHEMA demo EXCEPT (users) FROM SERVER taxi_srv INTO taxi;
pg_clickhouse obterá uma lista de todas as tabelas do banco de dados ClickHouse especificado (“demo” nos exemplos acima), obterá as definições das colunas de cada uma e executará comandos CREATE FOREIGN TABLE para criar as tabelas externas. As colunas serão definidas usando os tipos de dados suportados e, quando isso puder ser detectado, as opções suportadas por CREATE FOREIGN TABLE.
Preservação de maiúsculas/minúsculas em identificadores importadosIMPORT FOREIGN SCHEMA executa quote_identifier() nos nomes de tabelas e colunas que importa, o que coloca aspas duplas em identificadores com caracteres maiúsculos ou espaços em branco. Assim, esses nomes de tabelas e colunas devem ser colocados entre aspas duplas em consultas do PostgreSQL. Nomes compostos apenas por letras minúsculas e sem espaços em branco n’o precisam ser colocados entre aspas.Por exemplo, dada esta tabela do ClickHouse:
CREATE OR REPLACE TABLE test
(
    id UInt64,
    Name TEXT,
    updatedAt DateTime DEFAULT now()
)
ENGINE = MergeTree
ORDER BY id;
IMPORT FOREIGN SCHEMA cria esta tabela externa:
CREATE TABLE test
(
    id          BIGINT      NOT NULL,
    "Name"      TEXT        NOT NULL,
    "updatedAt" TIMESTAMPTZ NOT NULL
);
Portanto, as consultas devem usar aspas corretamente, por exemplo:
SELECT id, "Name", "updatedAt" FROM test;
Para criar objetos com nomes diferentes ou totalmente em minúsculas (e, portanto, sem diferenciar maiúsculas de minúsculas), use CREATE FOREIGN TABLE.

CREATE FOREIGN TABLE

Use CREATE FOREIGN TABLE para criar uma tabela externa capaz de consultar dados de um banco de dados ClickHouse:
CREATE FOREIGN TABLE acts (
    user_id    bigint NOT NULL,
    page_views int,
    duration   smallint,
    sign       smallint
) SERVER taxi_srv OPTIONS(
    table_name 'acts'
    engine 'CollapsingMergeTree'
);
As opções de tabela compatíveis são:
  • database: O nome do banco de dados remoto. O padrão é o banco de dados definido para o servidor externo.
  • fetch_size: Tamanho aproximado do lote em bytes para HTTP streaming. Substitui o fetch_size no nível do servidor. O padrão é 50000000 (50 MB). 0 desabilita o streaming e mantém a resposta completa em buffer.
  • table_name: O nome da tabela remota. O padrão é o nome especificado para a tabela externa.
  • engine: O table engine usado pela tabela do ClickHouse. Para CollapsingMergeTree() e AggregatingMergeTree(), o pg_clickhouse aplica automaticamente os parâmetros às expressões de função executadas na tabela.
Use o tipo de dado apropriado para o tipo de dado remoto do ClickHouse de cada coluna. As opções de coluna compatíveis são:
  • column_name: O nome da coluna no lado do ClickHouse, usado em vez do nome do atributo do PostgreSQL ao reconstruir consultas e inserções. Útil para mapear nomes de colunas do PostgreSQL em minúsculas e sem aspas para colunas do ClickHouse sensíveis a maiúsculas e minúsculas, por exemplo:
    CREATE FOREIGN TABLE hits (
        watchid    bigint   OPTIONS(column_name 'WatchID'),
        javaenable smallint OPTIONS(column_name 'JavaEnable'),
        title      text     OPTIONS(column_name 'Title')
    ) SERVER taxi_srv OPTIONS(table_name 'hits');
    
  • AggregateFunction: O nome da função de agregação aplicada a uma coluna do tipo AggregateFunction Type. Mapeie o tipo de dado para o tipo do ClickHouse passado à função e especifique o nome da função de agregação por meio da opção de coluna apropriada; o pg_clickhouse acrescentará automaticamente Merge à função de agregação que avalia a coluna.
    CREATE FOREIGN TABLE test (
        column1 bigint  OPTIONS(AggregateFunction 'uniq'),
        column2 integer OPTIONS(AggregateFunction 'anyIf'),
        column3 bigint  OPTIONS(AggregateFunction 'quantiles(0.5, 0.9)')
    ) SERVER clickhouse_srv;
    
  • SimpleAggregateFunction: O nome da função de agregação aplicada a uma coluna do tipo SimpleAggregateFunction Type. Mapeie o tipo de dado para o tipo do ClickHouse passado à função e especifique o nome da função de agregação por meio da opção de coluna apropriada.

ALTER FOREIGN TABLE

Use ALTER FOREIGN TABLE para alterar a definição de uma tabela externa:
ALTER TABLE table ALTER COLUMN b OPTIONS (SET AggregateFunction 'count');
As opções compatíveis de tabela e coluna são as mesmas de CREATE FOREIGN TABLE.

DROP FOREIGN TABLE

Use o DROP FOREIGN TABLE para remover uma tabela externa:
DROP FOREIGN TABLE acts;
Este comando falha se houver objetos que dependam da tabela externa. Use a cláusula CASCADE para removê-los também:
DROP FOREIGN TABLE acts CASCADE;

Referência de SQL DML

As expressões SQL DML abaixo podem usar pg_clickhouse. Os exemplos dependem das seguintes tabelas do ClickHouse:
CREATE TABLE logs (
    req_id    Int64 NOT NULL,
    start_at   DateTime64(6, 'UTC') NOT NULL,
    duration  Int32 NOT NULL,
    resource  Text  NOT NULL,
    method    Enum8('GET' = 1, 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH', 'QUERY') NOT NULL,
    node_id   Int64 NOT NULL,
    response  Int32 NOT NULL
) ENGINE = MergeTree
  ORDER BY start_at;

CREATE TABLE nodes (
    node_id Int64 NOT NULL,
    name    Text  NOT NULL,
    region  Text  NOT NULL,
    arch    Text  NOT NULL,
    os      Text  NOT NULL
) ENGINE = MergeTree
  PRIMARY KEY node_id;

EXPLAIN

O comando EXPLAIN funciona como esperado, mas a opção VERBOSE faz com que a consulta “Remote SQL” do ClickHouse seja exibida:
try=# EXPLAIN (VERBOSE)
       SELECT resource, avg(duration) AS average_duration
         FROM logs
        GROUP BY resource;
                                     QUERY PLAN
------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=64)
   Output: resource, (avg(duration))
   Relations: Aggregate on (logs)
   Remote SQL: SELECT resource, avg(duration) FROM "default".logs GROUP BY resource
(4 rows)
Esta consulta é enviada ao ClickHouse por meio de um nó de plano “Foreign Scan”, o SQL remoto.

SELECT

Use a instrução SELECT para executar consultas em tabelas pg_clickhouse assim como em quaisquer outras tabelas:
try=# SELECT start_at, duration, resource FROM logs WHERE req_id = 4117909262;
          start_at          | duration |    resource
----------------------------+----------+----------------
 2025-12-05 15:07:32.944188 |      175 | /widgets/totem
(1 row)
pg_clickhouse busca delegar ao ClickHouse a execução da consulta o máximo possível, incluindo funções de agregação. Use EXPLAIN para determinar o grau de pushdown. Para a consulta acima, por exemplo, toda a execução é delegada ao ClickHouse
try=# EXPLAIN (VERBOSE, COSTS OFF)
       SELECT start_at, duration, resource FROM logs WHERE req_id = 4117909262;
                                             QUERY PLAN
-----------------------------------------------------------------------------------------------------
 Foreign Scan on public.logs
   Output: start_at, duration, resource
   Remote SQL: SELECT start_at, duration, resource FROM "default".logs WHERE ((req_id = 4117909262))
(3 rows)
o pg_clickhouse também faz pushdown de JOINs para tabelas do mesmo servidor remoto:
try=# EXPLAIN (ANALYZE, VERBOSE)
       SELECT name, count(*), round(avg(duration))
         FROM logs
         LEFT JOIN nodes on logs.node_id = nodes.node_id
        GROUP BY name;
                                                                                  QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=72) (actual time=3.201..3.221 rows=8.00 loops=1)
   Output: nodes.name, (count(*)), (round(avg(logs.duration), 0))
   Relations: Aggregate on ((logs) LEFT JOIN (nodes))
   Remote SQL: SELECT r2.name, count(*), round(avg(r1.duration), 0) FROM  "default".logs r1 ALL LEFT JOIN "default".nodes r2 ON (((r1.node_id = r2.node_id))) GROUP BY r2.name
   FDW Time: 0.086 ms
 Planning Time: 0.335 ms
 Execution Time: 3.261 ms
(7 rows)
Fazer JOIN com uma tabela local gerará queries menos eficientes sem um ajuste cuidadoso. Neste exemplo, fazemos uma cópia local da tabela nodes e fazemos o JOIN com ela em vez da tabela remota:
try=# CREATE TABLE local_nodes AS SELECT * FROM nodes;
SELECT 8

try=# EXPLAIN (ANALYZE, VERBOSE)
       SELECT name, count(*), round(avg(duration))
         FROM logs
         LEFT JOIN local_nodes on logs.node_id = local_nodes.node_id
        GROUP BY name;
                                                             QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=147.65..150.65 rows=200 width=72) (actual time=6.215..6.235 rows=8.00 loops=1)
   Output: local_nodes.name, count(*), round(avg(logs.duration), 0)
   Group Key: local_nodes.name
   Batches: 1  Memory Usage: 32kB
   Buffers: shared hit=1
   ->  Hash Left Join  (cost=31.02..129.28 rows=2450 width=36) (actual time=2.202..5.125 rows=1000.00 loops=1)
         Output: local_nodes.name, logs.duration
         Hash Cond: (logs.node_id = local_nodes.node_id)
         Buffers: shared hit=1
         ->  Foreign Scan on public.logs  (cost=10.00..20.00 rows=1000 width=12) (actual time=2.089..3.779 rows=1000.00 loops=1)
               Output: logs.req_id, logs.start_at, logs.duration, logs.resource, logs.method, logs.node_id, logs.response
               Remote SQL: SELECT duration, node_id FROM "default".logs
               FDW Time: 1.447 ms
         ->  Hash  (cost=14.90..14.90 rows=490 width=40) (actual time=0.090..0.091 rows=8.00 loops=1)
               Output: local_nodes.name, local_nodes.node_id
               Buckets: 1024  Batches: 1  Memory Usage: 9kB
               Buffers: shared hit=1
               ->  Seq Scan on public.local_nodes  (cost=0.00..14.90 rows=490 width=40) (actual time=0.069..0.073 rows=8.00 loops=1)
                     Output: local_nodes.name, local_nodes.node_id
                     Buffers: shared hit=1
 Planning:
   Buffers: shared hit=14
 Planning Time: 0.551 ms
 Execution Time: 6.589 ms
Neste caso, podemos delegar mais da agregação ao ClickHouse agrupando por node_id em vez da coluna local e, em seguida, fazer join com a tabela de referência mais tarde:
try=# EXPLAIN (ANALYZE, VERBOSE)
       WITH remote AS (
           SELECT node_id, count(*), round(avg(duration))
             FROM logs
            GROUP BY node_id
       )
       SELECT name, remote.count, remote.round
         FROM remote
         JOIN local_nodes
           ON remote.node_id = local_nodes.node_id
        ORDER BY name;
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=65.68..66.91 rows=490 width=72) (actual time=4.480..4.484 rows=8.00 loops=1)
   Output: local_nodes.name, remote.count, remote.round
   Sort Key: local_nodes.name
   Sort Method: quicksort  Memory: 25kB
   Buffers: shared hit=4
   ->  Hash Join  (cost=27.60..43.79 rows=490 width=72) (actual time=4.406..4.422 rows=8.00 loops=1)
         Output: local_nodes.name, remote.count, remote.round
         Inner Unique: true
         Hash Cond: (local_nodes.node_id = remote.node_id)
         Buffers: shared hit=1
         ->  Seq Scan on public.local_nodes  (cost=0.00..14.90 rows=490 width=40) (actual time=0.010..0.016 rows=8.00 loops=1)
               Output: local_nodes.node_id, local_nodes.name, local_nodes.region, local_nodes.arch, local_nodes.os
               Buffers: shared hit=1
         ->  Hash  (cost=15.10..15.10 rows=1000 width=48) (actual time=4.379..4.381 rows=8.00 loops=1)
               Output: remote.count, remote.round, remote.node_id
               Buckets: 1024  Batches: 1  Memory Usage: 9kB
               ->  Subquery Scan on remote  (cost=1.00..15.10 rows=1000 width=48) (actual time=4.337..4.360 rows=8.00 loops=1)
                     Output: remote.count, remote.round, remote.node_id
                     ->  Foreign Scan  (cost=1.00..5.10 rows=1000 width=48) (actual time=4.330..4.349 rows=8.00 loops=1)
                           Output: logs.node_id, (count(*)), (round(avg(logs.duration), 0))
                           Relations: Aggregate on (logs)
                           Remote SQL: SELECT node_id, count(*), round(avg(duration), 0) FROM "default".logs GROUP BY node_id
                           FDW Time: 0.055 ms
 Planning:
   Buffers: shared hit=5
 Planning Time: 0.319 ms
 Execution Time: 4.562 ms
O nó “Foreign Scan” agora delega a agregação por node_id, reduzindo o número de linhas que precisam ser trazidas de volta ao Postgres de 1000 (todas elas) para apenas 8, uma para cada nó.

PREPARE, EXECUTE, DEALLOCATE

A partir da v0.1.2, o pg_clickhouse oferece suporte a consultas parametrizadas, criadas principalmente com o comando PREPARE:
try=# PREPARE avg_durations_between_dates(date, date) AS
       SELECT date(start_at), round(avg(duration)) AS average_duration
         FROM logs
        WHERE date(start_at) BETWEEN $1 AND $2
        GROUP BY date(start_at)
        ORDER BY date(start_at);
PREPARE
Use EXECUTE, como de costume, para executar uma instrução preparada:
try=# EXECUTE avg_durations_between_dates('2025-12-09', '2025-12-13');
    date    | average_duration
------------+------------------
 2025-12-09 |              190
 2025-12-10 |              194
 2025-12-11 |              197
 2025-12-12 |              190
 2025-12-13 |              195
(5 linhas)
A execução parametrizada impede que o driver HTTP converta corretamente os fusos horários de DateTime em versões do ClickHouse anteriores à 25.8, quando o [bug subjacente] foi [corrigido]. Observe que, às vezes, o PostgreSQL usa um plano de consulta parametrizado mesmo sem usar PREPARE. Para consultas que exijam uma conversão precisa de fuso horário e para as quais atualizar para a versão 25.8 ou posterior não seja uma opção, use o driver binário.
pg_clickhouse faz o pushdown das agregações, como de costume, como pode ser visto na saída detalhada de EXPLAIN:
try=# EXPLAIN (VERBOSE) EXECUTE avg_durations_between_dates('2025-12-09', '2025-12-13');
                                                                                                            QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=36)
   Output: (date(start_at)), (round(avg(duration), 0))
   Relations: Aggregate on (logs)
   Remote SQL: SELECT date(start_at), round(avg(duration), 0) FROM "default".logs WHERE ((date(start_at) >= '2025-12-09')) AND ((date(start_at) <= '2025-12-13')) GROUP BY (date(start_at)) ORDER BY date(start_at) ASC NULLS LAST
(4 rows)
Observe que ele enviou os valores completos de data, não os placeholders de parâmetros. Isso vale para as cinco primeiras solicitações, conforme descrito nas [PREPARE notes] do PostgreSQL. Na sexta execução, ele envia os [query parameters] do ClickHouse no estilo {param:type}: parâmetros:
                                                                                                         QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=36)
   Output: (date(start_at)), (round(avg(duration), 0))
   Relations: Aggregate on (logs)
   Remote SQL: SELECT date(start_at), round(avg(duration), 0) FROM "default".logs WHERE ((date(start_at) >= {p1:Date})) AND ((date(start_at) <= {p2:Date})) GROUP BY (date(start_at)) ORDER BY date(start_at) ASC NULLS LAST
(4 rows)
Use DEALLOCATE para liberar uma instrução preparada:
try=# DEALLOCATE avg_durations_between_dates;
DEALLOCATE

INSERT

Use o comando INSERT para inserir valores em uma tabela remota do ClickHouse:
try=# INSERT INTO nodes(node_id, name, region, arch, os)
VALUES (9,  'Augustin Gamarra', 'us-west-2', 'amd64', 'Linux')
     , (10, 'Cerisier', 'us-east-2', 'amd64', 'Linux')
     , (11, 'Dewalt', 'use-central-1', 'arm64', 'macOS')
;
INSERT 0 3

COPY

Use o comando COPY para inserir um lote de linhas em uma tabela remota do ClickHouse:
try=# COPY logs FROM stdin CSV;
4285871863,2025-12-05 11:13:58.360760,206,/widgets,POST,8,401
4020882978,2025-12-05 11:33:48.248450,199,/users/1321945,HEAD,3,200
3231273177,2025-12-05 12:20:42.158575,220,/search,GET,2,201
\.
>> COPY 3
⚠️ Limitações da API de batch O pg_clickhouse ainda não implementou suporte à API de insert em batch do PostgreSQL FDW. Assim, COPY atualmente usa instruções INSERT para inserir registros. Isso será melhorado em uma versão futura.

LOAD

Use LOAD para carregar a biblioteca compartilhada pg_clickhouse:
try=# LOAD 'pg_clickhouse';
LOAD
Normalmente, não é necessário usar LOAD, pois o Postgres carregará pg_clickhouse automaticamente na primeira vez que qualquer uma de suas funcionalidades (funções, tabelas externas etc.) for usada. A única situação em que pode ser útil usar LOAD pg_clickhouse é para SET os parâmetros do pg_clickhouse antes de executar consultas que dependem deles.

SET

Use SET para definir os parâmetros personalizados de configuração do pg_clickhouse.

pg_clickhouse.session_settings

O parâmetro pg_clickhouse.session_settings configura as configurações do ClickHouse a serem aplicadas às consultas subsequentes. Exemplo:
SET pg_clickhouse.session_settings = 'join_use_nulls 1, final 1';
O padrão é join_use_nulls 1, group_by_use_nulls 1, final 1. Defina como uma string vazia para voltar às configurações do servidor do ClickHouse.
SET pg_clickhouse.session_settings = '';
A sintaxe é uma lista de pares chave/valor delimitada por vírgulas, separados por um ou mais espaços. As chaves devem corresponder às configurações do ClickHouse. Use barra invertida para escapar espaços, vírgulas e barras invertidas nos valores:
SET pg_clickhouse.session_settings = 'join_algorithm grace_hash\,hash';
Ou use valores entre aspas simples para evitar escapar espaços e vírgulas; considere usar [quotação com cifrão] para evitar a necessidade de usar aspas duplas:
SET pg_clickhouse.session_settings = $$join_algorithm 'grace_hash,hash'$$;
Se a legibilidade for importante e você precisar definir muitas configurações, use várias linhas, por exemplo:
SET pg_clickhouse.session_settings TO $$
    connect_timeout 2,
    count_distinct_implementation uniq,
    final 1,
    group_by_use_nulls 1,
    join_algorithm 'prefer_partial_merge',
    join_use_nulls 1,
    log_queries_min_type QUERY_FINISH,
    max_block_size 32768,
    max_execution_time 45,
    max_result_rows 1024,
    metrics_perf_events_list 'this,that',
    network_compression_method ZSTD,
    poll_interval 5,
    totals_mode after_having_auto
$$;
Algumas configurações serão ignoradas nos casos em que interferirem na operação do próprio pg_clickhouse. Entre elas estão:
  • date_time_output_format: o driver HTTP exige que seja “iso”
  • format_tsv_null_representation: o driver HTTP exige o valor padrão
  • output_format_tsv_crlf_end_of_line o driver HTTP exige o valor padrão
Fora isso, o pg_clickhouse não valida as configurações, mas as repassa ao ClickHouse em todas as consultas. Assim, ele oferece suporte a todas as configurações de cada versão do ClickHouse. Observe que o pg_clickhouse deve ser carregado antes de definir pg_clickhouse.session_settings; use [pré-carregamento de biblioteca compartilhada] ou simplesmente use um dos objetos da extensão para garantir que ele seja carregado.

pg_clickhouse.pushdown_regex

O parâmetro pg_clickhouse.pushdown_regex controla se o pg_clickhouse faz pushdown de funções e operadores de expressão regular. Isso ocorre por padrão; defina esse parâmetro como false para impedir esse pushdown:
SET pg_clickhouse.pushdown_regex = 'false';
Consulte Expressões regulares para ver mais detalhes.

ALTER ROLE

Use o comando SET de ALTER ROLE para pré-carregar pg_clickhouse e/ou SET seus parâmetros para roles específicos:
try=# ALTER ROLE CURRENT_USER SET session_preload_libraries = pg_clickhouse;
ALTER ROLE

try=# ALTER ROLE CURRENT_USER SET pg_clickhouse.session_settings = 'final 1';
ALTER ROLE
Use o comando RESET de ALTER ROLE para redefinir o pré-carregamento e/ou os parâmetros do pg_clickhouse:
try=# ALTER ROLE CURRENT_USER RESET session_preload_libraries;
ALTER ROLE

try=# ALTER ROLE CURRENT_USER RESET pg_clickhouse.session_settings;
ALTER ROLE

Pré-carregamento

Se todas — ou quase todas — as conexões do Postgres precisarem usar o pg_clickhouse, considere usar o [pré-carregamento de biblioteca compartilhada] para carregá-lo automaticamente:

session_preload_libraries

Carrega a biblioteca compartilhada para cada nova conexão ao PostgreSQL:
session_preload_libraries = pg_clickhouse
Útil para aproveitar as atualizações sem reiniciar o servidor: basta se reconectar. Também pode ser definido para usuários ou roles específicos por meio de ALTER ROLE.

shared_preload_libraries

Carrega a biblioteca compartilhada no processo principal do PostgreSQL no momento da inicialização:
shared_preload_libraries = pg_clickhouse
Útil para economizar memória e reduzir a sobrecarga de carregamento a cada sessão, mas exige que o cluster seja reiniciado quando a biblioteca for atualizada.

Tipos de dados

pg_clickhouse mapeia os seguintes tipos de dados do ClickHouse para tipos de dados do PostgreSQL. IMPORT FOREIGN SCHEMA usa o primeiro tipo da coluna do PostgreSQL ao importar colunas; tipos adicionais podem ser usados em instruções CREATE FOREIGN TABLE:
ClickHousePostgreSQLObservações
Boolboolean
Datedate
Date32date
DateTimetimestamptz
Decimalnumeric
Float32real
Float64double precision
IPv4inet
IPv6inet
Int16smallint
Int32integer
Int64bigint
Int8smallint
JSONjsonb, json
Stringtext, bytea
UInt16integer
UInt32bigint
UInt64bigintErros para valores > BIGINT max
UInt8smallint
UUIDuuid
Mais observações e detalhes a seguir.

BYTEA

O ClickHouse não oferece um equivalente ao tipo BYTEA do PostgreSQL, mas permite que quaisquer bytes sejam armazenados no tipo String. Em geral, strings do ClickHouse devem ser mapeadas para o tipo TEXT do PostgreSQL, mas ao trabalhar com dados binários, mapeie-as para BYTEA. Exemplo:
-- Cria tabela no ClickHouse com colunas String.
SELECT clickhouse_raw_query($$
    CREATE TABLE bytes (
        c1 Int8, c2 String, c3 String
    ) ENGINE = MergeTree ORDER BY (c1);
$$);

-- Cria tabela estrangeira com colunas BYTEA.
CREATE FOREIGN TABLE bytes (
    c1 int,
    c2 BYTEA,
    c3 BYTEA
) SERVER ch_srv OPTIONS( table_name 'bytes' );

-- Insere dados binários na tabela estrangeira.
INSERT INTO bytes
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
  FROM generate_series(1, 4) n;

-- Exibe os resultados.
SELECT * FROM bytes;
Essa consulta SELECT final produzirá:
c1 |                             c2                             |                 c3
----+------------------------------------------------------------+------------------------------------
  1 | \x1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | \xae3b28cde02542f81acce8783245430d
  2 | \x5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | \x23e7c6cacb8383f878ad093b0027d72b
  3 | \x53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | \x7e969132fc656148b97b6a2ee8bc83c1
  4 | \x4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | \x8ef30f44c65480d12b650ab6b2b04245
(4 rows)
Observe que, se houver bytes nulos nas colunas do ClickHouse, uma tabela estrangeira que utilize colunas TEXT não exibirá os valores corretos:
-- Cria tabela estrangeira com colunas TEXT.
CREATE FOREIGN TABLE texts (
    c1 int,
    c2 TEXT,
    c3 TEXT
) SERVER ch_srv OPTIONS( table_name 'bytes' );

-- Codifica dados binários como hex.
SELECT c1, encode(c2::bytea, 'hex'), encode(c3::bytea, 'hex') FROM texts ORDER BY c1;
Saída:
c1 |                          encode                          |              encode
----+----------------------------------------------------------+----------------------------------
  1 | 1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | ae3b28cde02542f81acce8783245430d
  2 | 5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | 23e7c6cacb8383f878ad093b
  3 | 53ac2c1fa83c8f64603fe9568d883331                         | 7e969132fc656148b97b6a2ee8bc83c1
  4 | 4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | 8ef30f44c65480d12b650ab6b2b04245
(4 rows)
Observe que as linhas dois e três contêm valores truncados. Isso ocorre porque o PostgreSQL depende de strings terminadas em nul e não suporta nuls em suas strings. Tentar inserir valores binários em colunas TEXT funcionará com sucesso e conforme esperado:
-- Inserir via colunas de texto:
TRUNCATE texts;
INSERT INTO texts
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
  FROM generate_series(1, 4) n;

-- Visualizar os dados.
SELECT c1, encode(c2::bytea, 'hex'), encode(c3::bytea, 'hex') FROM texts ORDER BY c1;
As colunas de texto estarão corretas:

 c1 |                          encode                          |              encode
----+----------------------------------------------------------+----------------------------------
  1 | 1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | ae3b28cde02542f81acce8783245430d
  2 | 5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | 23e7c6cacb8383f878ad093b0027d72b
  3 | 53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | 7e969132fc656148b97b6a2ee8bc83c1
  4 | 4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | 8ef30f44c65480d12b650ab6b2b04245
(4 linhas)
Mas lê-los como BYTEA não funcionará:
# SELECT * FROM bytes;
 c1 |                                                           c2                                                           |                                   c3
----+------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------
  1 | \x5c783162663766306363383231643331313738363136613535613865306335323637373733353339376364646536663431353361396664336437 | \x5c786165336232386364653032353432663831616363653837383332343534333064
  2 | \x5c783566366539653132636438353932373132653633383031366634623161326537333233306565343064623439386330663062316463383431 | \x5c783233653763366361636238333833663837386164303933623030323764373262
  3 | \x5c783533616332633166613833633866363436303366653935363864383833333331303037643632383164653333306134623565373238663965 | \x5c783765393639313332666336353631343862393762366132656538626338336331
  4 | \x5c783465336332653463623735343261343531373361386461633933396464633462633735323032653334326562633736396230663564613266 | \x5c783865663330663434633635343830643132623635306162366232623034323435
(4 linhas)
Em regra, use colunas TEXT apenas para strings codificadas e colunas BYTEA apenas para dados binários, e nunca alterne entre elas.

Referência de funções e operadores

Funções

Estas funções servem como interface para consultar um banco de dados ClickHouse.

clickhouse_raw_query

SELECT clickhouse_raw_query(
    'CREATE TABLE t1 (x String) ENGINE = Memory',
    'host=localhost port=8123'
);
Conecte-se a um serviço ClickHouse por meio da interface HTTP, execute uma única consulta e desconecte-se. O segundo argumento opcional especifica uma string de conexão cujo padrão é host=localhost port=8123. Os parâmetros de conexão compatíveis são:
  • host: O host ao qual se conectar; obrigatório.
  • port: A porta HTTP à qual se conectar; o padrão é 8123, a menos que host seja um host do ClickHouse Cloud, caso em que o padrão é 8443
  • dbname: O nome do banco de dados ao qual se conectar.
  • username: O nome de usuário com o qual se conectar; o padrão é default
  • password: A senha usada para autenticação; o padrão é não usar senha
Por padrão, nenhum role tem acesso EXECUTE a esta função; considere conceder GRANT acesso apenas a roles que realmente precisem executar consultas ad hoc no ClickHouse, por exemplo, um role de administrador dedicado do ClickHouse: Útil para consultas que não retornam registros, mas consultas que retornam valores são retornadas como um único valor de texto:
SELECT clickhouse_raw_query(
    'SELECT schema_name, schema_owner from information_schema.schemata',
    'host=localhost port=8123'
);
      clickhouse_raw_query
---------------------------------
 INFORMATION_SCHEMA      default+
 default default                +
 git     default                +
 information_schema      default+
 system  default                +

(1 row)

Funções de pushdown

pg_clickhouse faz pushdown de um subconjunto das funções nativas do PostgreSQL usadas em condições (cláusulas HAVING e WHERE). Esse subconjunto tem os seguintes equivalentes no ClickHouse:

Operadores de pushdown

  • Fatia de array (arr[L:U]): arraySlice
  • @> (array contém): hasAll
  • <@ (array contido em): hasAll
  • && (sobreposição entre arrays): hasAny
  • ~ (correspondência com regexp): match
  • !~ (sem correspondência com regexp): match
  • ~* (correspondência com regexp sem diferenciar maiúsculas de minúsculas): match
  • !~* (sem correspondência com regexp sem diferenciar maiúsculas de minúsculas): match
  • ->> (JSON/JSONB extrai elemento como texto): sintaxe de subcoluna
  • -> (JSON/JSONB extrai): toJSONString + sintaxe de subcoluna

Funções personalizadas

Essas funções personalizadas criadas por pg_clickhouse fornecem pushdown de consultas externas para determinadas funções do ClickHouse sem equivalentes no PostgreSQL. Se alguma dessas funções não puder passar por pushdown, ela gerará uma exceção.

Pushdown de extensões

O pg_clickhouse reconhece funções de algumas extensões principais e de terceiros e faz o pushdown delas para os equivalentes no ClickHouse.

re2

Todas as funções da extensão re2 são convertidas 1:1 para o ClickHouse:

intarray

Uma função intarray pode ser executada no ClickHouse:

fuzzystrmatch

Duas funções fuzzystrmatch são aplicadas via pushdown no ClickHouse:

Casts com pushdown

O pg_clickhouse faz pushdown de casts como CAST(x AS bigint) para tipos de dados compatíveis. Para tipos incompatíveis, o pushdown falhará; se x, neste exemplo, for um UInt64 do ClickHouse, o ClickHouse se recusará a converter o valor. Para fazer pushdown de casts para tipos de dados incompatíveis, o pg_clickhouse fornece as seguintes funções. Elas geram uma exceção no PostgreSQL se não forem executadas via pushdown.

Funções agregadas com pushdown

Estas funções de agregação do PostgreSQL têm pushdown para o ClickHouse.

Agregações personalizadas

Estas funções de agregação personalizadas criadas por pg_clickhouse fornecem pushdown de consultas externas para algumas funções de agregação do ClickHouse sem equivalentes no PostgreSQL. Se alguma dessas funções não puder ter o pushdown aplicado, será gerada uma exceção.

Agregações de conjunto ordenado com pushdown

Estas funções de agregação de conjunto ordenado são mapeadas para as funções de agregação paramétricas do ClickHouse, passando seu argumento direto como parâmetro e suas expressões ORDER BY como argumentos. Por exemplo, esta consulta PostgreSQL:
SELECT percentile_cont(0.25) WITHIN GROUP (ORDER BY a) FROM t1;
Equivale a esta consulta do ClickHouse:
SELECT quantile(0.25)(a) FROM t1;
Observe que os sufixos não padrão de ORDER BY, DESC e NULLS FIRST, não são compatíveis e gerarão um erro.

Funções de janela com pushdown

Estas funções de janela do PostgreSQL são submetidas a pushdown para o ClickHouse com cláusulas OVER (PARTITION BY ... ORDER BY ...), incluindo especificações de frame quando aplicável. As funções de classificação (row_number, rank, dense_rank, ntile, cume_dist, percent_rank) omitem a cláusula de frame durante o pushdown porque o ClickHouse rejeita especificações de frame nessas funções.

Observações sobre compatibilidade

Expressões regulares

Embora o pg_clickhouse faça pushdown de expressões regulares para equivalentes no ClickHouse quando pg_clickhouse.pushdown_regex é true (o padrão), e se esforce para garantir um nível básico de compatibilidade, esteja ciente das diferenças entre ambos e de como o pg_clickhouse lida com elas.
  • O PostgreSQL oferece suporte a [POSIX Regular Expressions], enquanto o ClickHouse oferece suporte a RE2 Regular Expressions. Fique atento às diferenças de comportamento: escreva RE2 quando a expressão regular for avaliada pelo ClickHouse (por exemplo, em uma cláusula WHERE) e POSIX quando ela for avaliada pelo Postgres (por exemplo, em uma cláusula SELECT).
  • O pg_clickhouse faz pushdown das [Regex flags] do Postgres, prefixando-as à expressão regular do ClickHouse dentro de (?). Por exemplo:
    regexp_like(val, '^VAL\d', 'i')
    
    Torna-se
    match(val, concat('(?i-s)', '^VAL\\d'))
    
    Observe a inclusão de -s; isso alinha o comportamento com o das expressões regulares do Postgres ao desabilitar s, que o ClickHouse habilita por padrão. O pg_clickhouse não incluirá -s se as flags na chamada da função do Postgres incluírem s. Infelizmente, esse comportamento quebra a compatibilidade de algumas expressões regulares no Postgres 24 e versões anteriores.
  • As únicas flags compatíveis com ambos e que, portanto, podem ser usadas quando avaliadas pelo ClickHouse são:
    • i: sem diferenciar maiúsculas de minúsculas
    • m: modo multilinha:
    • s: faz . corresponder a \n
    • p: correspondência parcial sensível a nova linha (tratada da mesma forma que s)
    • t: sintaxe restrita (o padrão, removida pelo pg_clickhouse)
    O RE2 oferece suporte apenas a essas flags; não use nenhuma outra [Postgres flags]
  • Quaisquer outras flags passadas para funções de expressão regular farão com que a função não seja enviada por pushdown.
  • A exceção é regexp_replace(), que também oferece suporte à flag g. Quando g está definida, o pg_clickhouse usa replaceRegexpAll() em vez de replaceRegexpOne() e remove a flag antes de prefixar as demais.
  • O argumento de substituição de regexp_replace() no Postgres aceita \& para se referir à correspondência inteira, enquanto no ClickHouse \0 representa a correspondência inteira. Certifique-se de usar \0 quando a função fizer pushdown para o ClickHouse.
Para evitar qualquer ambiguidade, considere configurar pg_clickhouse.pushdown_regex para impedir que expressões regulares do Postgres façam pushdown para o ClickHouse e usar a [re2 extension], para a qual o pg_clickhouse oferece suporte a pushdown direto de expressões regulares RE2 compatíveis com o ClickHouse.

to_char()

O to_char() do PostgreSQL para timestamp e timestamp with time zone só faz pushdown para o ClickHouse formatDateTime quando o argumento de formato é uma constante de string não NULL em que cada palavra-chave do PostgreSQL tem um equivalente byte a byte idêntico no ClickHouse. Se o formato for dinâmico (não for um Const) ou contiver qualquer palavra-chave ou modificador sem suporte, a chamada volta para a avaliação local no PostgreSQL — o pushdown nunca é tentado com uma tradução parcial, para que a saída permaneça compatível com o PG. As formas de to_char() com dois argumentos para numeric, interval e outros tipos que não sejam timestamp nunca fazem pushdown; o formatDateTime do ClickHouse apenas formata valores de data e hora.

Palavras-chave traduzidas

PostgreSQLClickHouseSignificado
YYYY, yyyy%Yano com 4 dígitos
YY, yy%yano com 2 dígitos
MM, mm%mmês com zero à esquerda (01–12)
DD, dd%ddia do mês com zero à esquerda (01–31)
DDD, ddd%jdia do ano com zero à esquerda (001–366)
HH24, hh24%Hhora em formato de 24 horas com zero à esquerda (00–23)
HH, hh, HH12, hh12%Ihora em formato de 12 horas com zero à esquerda (01–12)
MI, mi%iminuto com zero à esquerda (00–59)
SS, ss%Ssegundo com zero à esquerda (00–59)
Q, q%Qtrimestre (1–4)
Mon%bnome abreviado do mês, por exemplo, Oct
Dy%anome abreviado do dia da semana, por exemplo, Mon
AM, PM%pindicador AM/PM, sempre em maiúsculas

Texto entre aspas e literais

Texto entre "..." é passado literalmente, com qualquer % literal duplicado para %% para escapar o prefixo de especificador do ClickHouse. Um \" fora das aspas também é passado como um " literal. Dentro de "...", a barra invertida escapa apenas "; outras sequências com barra invertida são tratadas como texto literal.

Autores

David E. Wheeler Copyright (c) 2025-2026, ClickHouse
Última modificação em 10 de junho de 2026