Skip to main content

Description

pg_clickhouse is a PostgreSQL extension that enables remote query execution on ClickHouse databases, including a foreign data wrapper. It supports PostgreSQL 14 and higher and ClickHouse 23.3 and higher.

Getting started

The simplest way to try pg_clickhouse is the Docker image, which contains the standard PostgreSQL Docker image with the pg_clickhouse and [re2][re2 extension] extensions:
See the tutorial to get started importing ClickHouse tables and pushing down queries.

Usage

Versioning policy

pg_clickhouse adheres to Semantic Versioning for its public releases.
  • The major version increments for API changes
  • The minor version increments for backward compatible SQL changes
  • The patch version increments for binary-only changes
Once installed, PostgreSQL tracks two variations of the version:
  • The library version (defined by PG_MODULE_MAGIC on PostgreSQL 18 and higher) includes the full semantic version, visible in the output of the pgch_version() function or the Postgres pg_get_loaded_modules() function.
  • The extension version (defined in the control file) includes only the major and minor versions, visible in the pg_catalog.pg_extension table, the output of the pg_available_extension_versions() function, and \dx pg_clickhouse.
In practice this means that a release that increments the patch version, e.g. from v0.1.0 to v0.1.1, benefits all databases that have loaded v0.1 and don’t need to run ALTER EXTENSION to benefit from the upgrade. A release that increments the minor or major versions, on the other hand, will be accompanied by SQL upgrade scripts, and all existing database that contain the extension must run ALTER EXTENSION pg_clickhouse UPDATE to benefit from the upgrade.

DDL SQL reference

The following SQL DDL expressions use pg_clickhouse.

CREATE EXTENSION

Use CREATE EXTENSION to add pg_clickhouse to a database:
Use WITH SCHEMA to install it into a specific schema (recommended):

ALTER EXTENSION

Use ALTER EXTENSION to change pg_clickhouse. Examples:
  • After installing a new release of pg_clickhouse, use the UPDATE clause:
  • Use SET SCHEMA to move the extension to a new schema:

DROP EXTENSION

Use DROP EXTENSION to remove pg_clickhouse from a database:
This command fails if there are any objects that depend on pg_clickhouse. Use the CASCADE clause to drop them, too:

CREATE SERVER

Use CREATE SERVER to create a foreign server that connects to a ClickHouse server. Example:
The supported options are:
  • driver: The ClickHouse connection driver to use, either “binary” or “http”. Required.
  • compression: Native-protocol compression for the “binary” driver, one of “none”, “lz4”, or “zstd”. Defaults to “lz4”. Ignored by the “http” driver.
  • dbname: The ClickHouse database to use upon connecting. Defaults to “default”.
  • host: The host name of the ClickHouse server. Defaults to “localhost”;
  • port: The port to connect to on the ClickHouse server. Defaults as follows:
    • 9440 if driver is “binary” and host is a ClickHouse Cloud host
    • 9004 if driver is “binary” and host isn’t a ClickHouse Cloud host
    • 8443 if driver is “http” and host is a ClickHouse Cloud host
    • 8123 if driver is “http” and host isn’t a ClickHouse Cloud host
  • min_tls_version: Minimum TLS protocol version to negotiate on connections that use TLS. One of TLSv1, TLSv1.1, TLSv1.2, or TLSv1.3. Defaults to the TLS library’s own minimum. Applies to both drivers.
  • secure: Controls TLS for the connection. One of:
    • auto (default): use TLS when host is a ClickHouse Cloud host or port is a secure port; plaintext otherwise.
    • on (or true/yes/1): always use TLS. Defaults port to 8443 (“http”) or 9440 (“binary”).
    • off (or false/no/0): never use TLS. Defaults port to 8123 (“http”) or 9000 (“binary”).
  • encoding_check: Defines how to to handle invalid characters under the database encoding when converting ClickHouse String and JSON values. One of:
    • fail (default): raise an error
    • remove removes invalid bytes
    • replace: under the UTF-8 encoding, replaces invalid bytes with the Unicode replacement character (�); same as remove for other encodings
    • truncate truncates the text at the first invalid byte

ALTER SERVER

Use ALTER SERVER to change a foreign server. Example:
The options are the same as for CREATE SERVER.

DROP SERVER

Use DROP SERVER to remove a foreign server:
This command fails if any other objects depend on the server. Use CASCADE to also drop those dependencies:

CREATE USER MAPPING

Use CREATE USER MAPPING to map a PostgreSQL user to a ClickHouse user. For example, to map the current PostgreSQL user to the remote ClickHouse user when connecting with the taxi_srv foreign server:
The supported options are:
  • user: The name of the ClickHouse user. Defaults to “default”.
  • password: The password of the ClickHouse user.

ALTER USER MAPPING

Use ALTER USER MAPPING to change the definition of a user mapping:
The options are the same as for CREATE USER MAPPING.

DROP USER MAPPING

Use DROP USER MAPPING to remove a user mapping:

IMPORT FOREIGN SCHEMA

Use IMPORT FOREIGN SCHEMA to import all the tables defines in a ClickHouse database as foreign tables into a PostgreSQL schema:
Use LIMIT TO to limit the import to specific tables:
Use EXCEPT to exclude tables:
pg_clickhouse will fetch a list of all the tables in the specified ClickHouse database (“demo” in the above examples), fetch column definitions for each, and execute CREATE FOREIGN TABLE commands to create the foreign tables. Columns will be defined using the supported data types and, were detectable, the options supported by CREATE FOREIGN TABLE. Some details to keep in mind:
  • Imported columns keep the type modifier of the ClickHouse type, including those within Arrays. In other words, Array(Decimal(12,6)) imports as numeric(12,6)[].
  • Nullable columns import without NOT NULL; Nullable inside an Array does not, because PostgreSQL arrays always allow NULL elements.
  • AggregateFunction and SimpleAggregateFunction columns import without NOT NULL, whatever the ClickHouse declaration, see State Columns and NOT NULL.
  • Tuple columns import as text[]. Map and Nested columns columns created with flatten_nested=0 import as text[][]. Each conversion emits a NOTICE. PostgreSQL’s record pseudotype cannot define a table column, so each tuple becomes an array of values, each map becomes an two-dimensional array of key-value pairs, and each Nested value becomes an two-dimensional array of rows. To read fields as records, alter the column to an array of a matching composite type, see Manual Type Mappings for an example.
  • DateTime64 and Time64 columns with precision greater than 6 (microseconds) also trigger a NOTICE, since PostgreSQL caps precision at timestamp(6) and time(6).
  • Column types without a PostgreSQL counterpart, including the legacy Object('json') type that predates ClickHouse JSON, trigger an error.
Imported Identifier Case PreservationIMPORT FOREIGN SCHEMA runs quote_identifier() on the table and column names it imports, which double-quotes identifiers with uppercase characters or blank spaces. Such table and column names thus must be double-quoted in PostgreSQL queries. Names with all lowercase and no blank space characters don’t need to be quoted.For example, given this ClickHouse table:
IMPORT FOREIGN SCHEMA creates this foreign table:
Queries therefore must quote appropriately, e.g.,
To create objects with different names or all lowercase (and therefore case-insensitive) names, use CREATE FOREIGN TABLE.

CREATE FOREIGN TABLE

Use CREATE FOREIGN TABLE to create a foreign table that can query data from a ClickHouse database:
The supported table options are:
  • database: The name of the remote database. Defaults to the database defined for the foreign server.
  • table_name: The name of the remote table. Default to the name specified for the foreign table.
  • engine: The table engine used by the ClickHouse table. For CollapsingMergeTree() and AggregatingMergeTree(), pg_clickhouse automatically applies the parameters to function expressions executed on the table.
Use the data type appropriate for the remote ClickHouse data type of each column. The supported column options are:
  • column_name: The name of the column on the ClickHouse side, used in preference to the PostgreSQL attribute name when deparsing queries and inserts. Useful for mapping unquoted lowercase PostgreSQL column names to case-sensitive ClickHouse columns, e.g.,
  • AggregateFunction: The name of the aggregate function applied to an AggregateFunction Type column. Map the data type to the ClickHouse type passed to the function and specify the name of the aggregate function via the appropriate column option and pg_clickhouse will automatically append Merge to an aggregate function evaluating the column. Declare the column nullable to avoid issues described in State Columns and NOT NULL.
  • SimpleAggregateFunction: The name of the aggregate function applied to an SimpleAggregateFunction Type column. Map the data type to the ClickHouse type passed to the function and specify the name of the aggregate function via the appropriate column option.

State Columns and NOT NULL

Keep AggregateFunction and SimpleAggregateFunction columns nullable. PostgreSQL 19 changes count(column) to count(*) for NOT NULL state columns, which counts rows instead of merging states. Take a ClickHouse table holding one count state per user, each state built from 1,000 events:
With NOT NULL, PostgreSQL counts states, which produces an invalid count:
Without NOT NULL, pg_clickhouse uses countMerge() to merge states, yielding the proper 1000 count:
Use ALTER FOREIGN TABLE to drop the constraint from an existing foreign table:
NOT NULL only affects PostgreSQL. IMPORT FOREIGN SCHEMA therefore imports state columns as nullable.

ALTER FOREIGN TABLE

Use ALTER FOREIGN TABLE to change the definition of a foreign table:
The supported table and column options are the same as for CREATE FOREIGN TABLE.

DROP FOREIGN TABLE

Use DROP FOREIGN TABLE to remove a foreign table:
This command fails if there are any objects that depend on the foreign table. Use the CASCADE clause to drop them, too:

DML SQL reference

The SQL DML expressions below may use pg_clickhouse. Examples depend on these ClickHouse tables:

EXPLAIN

The EXPLAIN command works as expected, but the VERBOSE option triggers the ClickHouse “Remote SQL” query to be emitted:
This query pushes down to ClickHouse via a “Foreign Scan” plan node, the remote SQL.

SELECT

Use the SELECT statement to execute queries on pg_clickhouse tables just like any other tables:
pg_clickhouse works to push query execution down to ClickHouse as much as possible, including aggregate functions. Use EXPLAIN to determine the pushdown extent. For the above query, for example, all execution is pushed down to ClickHouse
pg_clickhouse also pushes down JOINs to tables that are from the same remote server:
Joining with a local table will generate less efficient queries without careful tuning. In this example, we make a local copy of the nodes table and join to it instead of the remote table:
In this case, we can push more of the aggregation down to ClickHouse by grouping on node_id instead of the local column, and then join to the lookup table later:
The “Foreign Scan” node now pushes down aggregation by node_id, reducing the number of rows that must be pulled back into Postgres from 1000 (all of them) to just 8, one for each node.

Partitioned Tables

A PostgreSQL partitioned table can mix local partitions with foreign partitions backed by ClickHouse. A common layout offloads older data to ClickHouse while recent data stays in PostgreSQL:
For example on how to move data from local to foreign partitions, see offload-partition.sql. Aggregates spanning both local and foreign partitions need partitionwise aggregation, which PostgreSQL disables by default:
With enable_partitionwise_aggregate enabled, PostgreSQL computes a partial aggregate below Append, then a finalize aggregate above combines those partials into result. pg_clickhouse pushes the foreign partition’s partial down to ClickHouse:

When partial aggregates push down

PostgreSQL represents a partial aggregate as a transition state that the finalize step combines across partitions. pg_clickhouse can push a partition’s partial down only when it can express it as a ClickHouse value:
  • Decomposable aggregates whose transition state is already the final value, push down directly: count, sum, min, max, bool_and/every, bool_or, bit_and, bit_or, and bit_xor.
  • avg over integers pushes its {count, sum} state as an array.
  • avg, var_pop, var_samp, stddev_pop, and stddev_samp over floating point push their {N, sum, sum of squared deviations} state as an array.
FILTER (WHERE …) pushes down with these aggregate functions.

When they fall back

Aggregates whose transition state is PostgreSQL’s opaque internal type have no portable representation, so the foreign partition instead fetches its rows and aggregates them locally. This covers anything over numeric, plus avg(bigint) and avg(interval). DISTINCT, ordered-set, and variadic aggregates also fall back.

PREPARE, EXECUTE, DEALLOCATE

As of v0.1.2, pg_clickhouse supports parameterized queries, mainly created by the PREPARE command:
Use EXECUTE as usual to execute a prepared statement:
pg_clickhouse pushes down the aggregations, as usual, as seen in the EXPLAIN verbose output:
Note that it has sent the full date values, not the parameter placeholders. This holds for the first five requests, as described in the PostgreSQL PREPARE notes. On the sixth execution, it sends ClickHouse {param:type}-style query parameters:
Use DEALLOCATE to deallocate a prepared statement:

INSERT

Use the INSERT command to insert values into a remote ClickHouse table:

COPY

Use the COPY command to insert a batch of rows into a remote ClickHouse table:
⚠️ Batch API Limitations pg_clickhouse hasn’t yet implemented support for the PostgreSQL FDW batch insert API. Thus COPY currently uses INSERT statements to insert records. This will be improved in a future release.

LOAD

Use LOAD to load the pg_clickhouse shared library:
It’s not normally necessary to use LOAD, as Postgres will automatically load pg_clickhouse the first time any of its features (functions, foreign tables, etc.) are used. The one time it may be useful to LOAD pg_clickhouse is to SET pg_clickhouse parameters before executing queries that depend on them.

SET

Use SET to set the pg_clickhouse custom configuration parameters.

pg_clickhouse.session_settings

The pg_clickhouse.session_settings parameter configures ClickHouse settings to be set on subsequent queries. Example:
The default is
Set it to an empty string to fall back on the ClickHouse server’s settings — but note that pushdown correctness depends on some of these defaults: join_use_nulls for outer joins and transform_null_in for the IN family (see IN and NULL Semantics).
The syntax is a comma-delimited list of key/value pairs separated by one or more spaces. Keys must correspond to ClickHouse settings. Escape spaces, commas, and backslashes in values with a backslash:
Or use single quoted values to avoid escaping spaces and commas; consider using dollar quoting to avoid the need to double-quote:
If you care about legibility and need to set many settings, use multiple lines, for example:
Some settings will be ignored in cases where they would interfere with the operation of pg_clickhouse itself. These include:
  • date_time_output_format: the http driver requires it to be “iso”
  • format_tsv_null_representation: the http driver requires the default
  • output_format_tsv_crlf_end_of_line the http driver requires the default
Otherwise, pg_clickhouse does not validate the settings, but passes them on to ClickHouse for every query. It thus supports all settings for each ClickHouse version. Note that pg_clickhouse must be loaded before setting pg_clickhouse.session_settings; either use shared library preloading or simply use one of the objects in the extension to ensure it loads.

pg_clickhouse.pushdown_regex

The pg_clickhouse.pushdown_regex parameter controls whether pg_clickhouse pushes down regular expression functions and operators. It does so by default; set this parameter to false to prevent them from being pushed down:
See Regular Expressions for details.

ALTER ROLE

Use ALTER ROLE’s SET command to preload pg_clickhouse and/or SET its parameters for specific roles:
Use the ALTER ROLE’s RESET command to reset pg_clickhouse preloading and/or parameters:

Preloading

If every or nearly every Postgres connection needs to use pg_clickhouse, consider using shared library preloading to automatically load it:

session_preload_libraries

Loads the shared library for every new connection to PostgreSQL:
Useful to take advantage of updates without restarting the server: just reconnect. May also be set for specific users or roles via ALTER ROLE.

shared_preload_libraries

Loads the shared library into the PostgreSQL parent process at startup time:
Useful to save memory and load overhead for every session, but requires the cluster to be restart when the library is updated.

Data Types

This table presents the preferred mapping of ClickHouse to PostgreSQL data types. IMPORT FOREIGN SCHEMA uses these mappings and derives the appropriate type modifiers and array dimensions. Use CREATE FOREIGN TABLE to declare alternate PostgreSQL types. Additional read targets list conversions supported by binary driver beyond PostgreSQL explicit casts. Empty cells still allow those casts. Values must fit target types. These targets describe reads; writes follow separate conversion rules. Any column also reads into text, varchar, or another string type. The value takes the PostgreSQL type above, then renders through that type’s output function. A ClickHouse string read into a text type is validated against the database encoding, so bytes PostgreSQL cannot read raise an error. Declare a String, FixedString, Enum, or JSON column BYTEA to read its bytes as ClickHouse wrote them. Input-compatible types can read strings using their PostgreSQL input function. Composite types must have matching fields in matching order. To read a tuple as an array, each field must convert to the array’s element type, and no field can itself be an array. When read as arrays, Map uses one row per key-value pair and Nested uses one row per nested record. To read a tuple as box, provide two points; for circle, provide a point and radius; for line, provide three coefficients. Additional notes and details follow.

Type Coercion

With the binary driver, alternate scalar types use PostgreSQL’s explicit casts where available, and array elements are converted to the declared element type. Unsupported conversions and out-of-range values raise errors. With either driver, map ClickHouse Interval types to smallint, integer, or bigint to read and write counts of their units. For example, an IntervalDay value of 3 maps to the integer 3. Mapping IntervalNanosecond to bigint preserves nanoseconds, while mapping to interval truncates to microseconds.

BYTEA

ClickHouse does not provide the equivalent of the PostgreSQL BYTEA type, but allows any bytes to be stored in [String] type. In general ClickHouse strings should be mapped to the PostgreSQL [TEXT], but when using binary data, map it to BYTEA. Example:
That final SELECT query will output:
A foreign table using [TEXT] columns generally cannot read these values into text, because pg_clickhouse validates ClickHouse bytes against the database encoding:
Will output:
A digest holds arbitrary bytes, which seldom form valid text. PostgreSQL also reserves nul, so a [TEXT] column never reads a ClickHouse string holding one. Attempting to insert binary values into [TEXT] columns will succeed and work as expected:
The text columns will be correct:
But reading them as BYTEA will not:
A FixedString(N) column pads short values with nul bytes. A text column drops that trailing padding, where a BYTEA column keeps every byte.
As a rule, only use [TEXT] columns for encoded strings and use BYTEA columns only for binary data, and never switch between them.

Composite Types

Array

ClickHouse [Array]s map directly to PostgreSQL arrays, with equivalent semantics. The main differences is that Postgres multidimensional arrays must have array expressions with matching dimensions. An attempt to read a ClickHouse array with different dimensions, such as [[1], [2,3]], triggers an error. Array index access pushes down as appropriate, including multidimensional index access. Examples:
Array slices also push down using the [arraySlice] function, although multidimensional slice access is not yet supported:

Map

PostgreSQL provides no type corresponding to the ClickHouse [Map] type. pg_clickhouse therefore maps [Map] columns to text[][], with each key-value pair as its own array. IMPORT FOREIGN SCHEMA uses this mapping. One can INSERT maps as arrays, as well. An example:
Each nested array must contain exactly two text values. Values incompatible with corresponding ClickHouse key or value types trigger an error.
Inserting a Map requires the binary driver, which derives column types from the ClickHouse sever. The http driver does not, so lacks the information to format and insert the appropriate value.

Tuple

Similarly, a ClickHouse [Tuple] columns map to text[] and supports INSERTs via the binary driver. IMPORT FOREIGN SCHEMA emits a NOTICE when it makes such a mapping.

Nested

By default, ClickHouse splits a Nested column into one Array column per field (flatten_nested=1). IMPORT FOREIGN SCHEMA reads these from ClickHouse’s system.columns catalog and creates separate PostgreSQL array columns, preserving dotted names such as items.a and items.b. For example, given this foreign table:
It’s imported schema has three columns rather than two:
Query the nested columns by double-quoting the column names, e.g.,
And filter values in a WHERE clause using the usual array features, including array subscript syntax:
A [Nested] column created with flatten_nested=0 maps to an array with one item per nested row. Each array item contains that row’s values:
Now the resulting schema is:
Compose nested records in a two-dimensional array with text values formatted for each type defined by the ClickHouse Column. For c2 Nested(id Int64, name String) in this example, it would be:
Pushdown of text[][] columns over Nested types fails, however:
This is because pg_clickhouse cannot tell a text array column over a ClickHouse array column from one over a Nested column, so cannot rewrite it in ClickHouse’s Nested syntax. However, an array of a matching composite type can also map these items (see Manual Type Mappings for details), in which case pushdown works as long as the composite field names are identical to the Nested field names:

Manual Type Mappings

IMPORT FOREIGN SCHEMA uses general-purpose PostgreSQL types. For example, given a ClickHouse table using [Enum], [Tuple], Map, and unflattened [Nested] (flatten_nested = 0) columns, such as:
To retain structure or constrain values, define PostgreSQL composite or enum types and create a foreign table manually:
Always make the enum labels and composite type field names identical to the ClickHouse enum labels and Nested or Tuple field names to ensure that pushdown specifies the proper names.
Match composite field order and types to ClickHouse declarations. [Map] keys and values become first and second fields, respectively (key and value in this case). Nested and Map become arrays of composites, while Tuple becomes one composite value:
Of course you can use the composite type field names, too, both in a SELECT list:
And, for [Nested] types in a WHERE clause --- as long as the field names are identical:
INSERT using such composites is not yet supported.

Function and operator reference

Functions

These functions provide the interface to query a ClickHouse database.

clickhouse_server_version

Report the ClickHouse server version, as major.minor.patch, for the named foreign server, connecting if necessary using the server’s options and the current user’s user mapping:
Reads the version from the native-protocol connection handshake, or over HTTP from a single SELECT version() query, and caches it for the life of the connection.

clickhouse_query

Execute a query against an already-configured foreign server and return its rows as a relation, mapping each ClickHouse result column to the PostgreSQL type named in the column definition list. It reuses the server’s driver, credentials, database, and the connection cache. The first argument is the name of a server created with CREATE SERVER. A column definition list (AS name(col type, ...)) is required: PostgreSQL needs the result shape before fetching rows, and it must match the columns the query returns. Values are converted from ClickHouse to the declared types the same way a foreign table column would be. Statements that return no results, such as DDL, have nothing to declare; run them with clickhouse_perform instead. No role has EXECUTE access by default; GRANT to a role to allow it to use the function.

clickhouse_perform

Execute a statement against an already-configured foreign server and discard any result. Use it for statements that return no rows, such as DDL, where clickhouse_query has no result shape to declare. It resolves the server the same way clickhouse_query does, reusing its driver, credentials, database, and the connection cache. As a procedure it must be invoked with CALL, not SELECT, and it returns no rows. No role has EXECUTE access by default; GRANT to a role to allow it to use the procedure.

Pushdown functions

pg_clickhouse pushes down a subset of the PostgreSQL builtin functions used in conditionals (HAVING and WHERE clauses). That subset maps to ClickHouse equivalents as follows:

Pushdown operators

IN and NULL semantics

ClickHouse evaluates IN under two-valued logic: when the probe finds no match it returns 0 even if a NULL is involved, where PostgreSQL computes NULL. To preserve PostgreSQL semantics, pg_clickhouse pushes down the IN family over a constant list or array (IN, NOT IN, = ANY, = ALL, <> ANY, <> ALL) unconditionally: the native or cheap form where it can prove neither the probe nor an array element can be NULL, or a guarded CASE form otherwise that checks for NULL values at runtime instead, computing PostgreSQL’s exact three-valued answer (TRUE, FALSE, NULL) in every context, including value positions like a SELECT list or GROUP BY. A NOT IN (SELECT ...) filter over nullable columns also pushes down, deparsed with compensating guards that keep PostgreSQL’s behavior: a set containing a NULL disqualifies every row, and a NULL probe passes only against an empty set. Each guard is omitted when a NOT NULL declaration proves it unnecessary. Unlike the array forms above, this guard only applies in a plain filter condition (or under NOT); we still do not push down IN (SELECT ...) (in a value position) nor grouped/aggregated subquery bodies. Declaring columns NOT NULL maximizes pushdown by letting the cheaper unguarded form ship instead; IMPORT FOREIGN SCHEMA does so automatically for non-Nullable ClickHouse columns. The proof follows non-NULL constants, NOT NULL columns, and basic arithmetic (+, -, *, unary -) over them. These rules assume ClickHouse’s default transform_null_in = 0, which pg_clickhouse sets on every query through the default value of the pg_clickhouse.session_settings parameter so that a ClickHouse server profile cannot silently change it. Setting transform_null_in = 1 breaks the semantics of every pushed IN.

Custom functions

These custom functions created by pg_clickhouse provide foreign query pushdown for select ClickHouse functions with no PostgreSQL equivalents. If any of these functions can’t be pushed down they will raise an exception.

Extension pushdown

pg_clickhouse recognizes functions from select core and third-party extensions, pushing them down to their ClickHouse equivalents.

re2

All [re2 extension] operators and functions push down 1:1 to ClickHouse:

intarray

One [intarray] function pushes down to ClickHouse:

fuzzystrmatch

Two [fuzzystrmatch] functions push down to ClickHouse:

pgcyrpto

  • digest(text, text) and digest(bytea, text): Corresponding ClickHouse hash function when the algorithm is a constant md5, sha1, sha224, sha256, sha384, or sha512 (matched case-insensitively).

Pushdown casts

pg_clickhouse pushes down casts such as CAST(x AS bigint) for compatible data types. For incompatible types the pushdown will fail; if x in this example is a ClickHouse UInt64, ClickHouse will refuse to cast the value. In order to push down casts to incompatible data types, pg_clickhouse provides the following functions. They raise an exception in PostgreSQL if they’re not pushed down.

Pushdown aggregates

These PostgreSQL aggregate functions pushdown to ClickHouse.

Custom aggregates

These custom aggregate functions created by pg_clickhouse provide foreign query pushdown for select ClickHouse aggregate functions with no PostgreSQL equivalents. If any of these functions can’t be pushed down they will raise an exception.

Pushdown ordered set aggregates

These ordered-set aggregate functions map to ClickHouse parametric aggregate functions by passing their direct argument as a parameter and their ORDER BY expressions as arguments. For example, this PostgreSQL query:
Maps to this ClickHouse query:
Note that the non-default ORDER BY suffixes DESC and NULLS FIRST aren’t supported and will raise an error.

Custom Ordered Set Aggregates

These custom ordered-set aggregate functions created by pg_clickhouse provide foreign query pushdown for select ClickHouse parametric aggregate functions. If any of these functions cannot be pushed down they will raise an exception.

Custom Ordered Set Aggregates

These custom ordered-set aggregate functions created by pg_clickhouse provide foreign query pushdown for select ClickHouse parametric aggregate functions. If any of these functions cannot be pushed down they will raise an exception.

Pushdown window functions

These PostgreSQL [window functions] push down to ClickHouse with OVER (PARTITION BY ... ORDER BY ...) clauses, including frame specifications where applicable. Ranking functions (row_number, rank, dense_rank, ntile, cume_dist, percent_rank) omit their frame clause during pushdown because ClickHouse rejects frame specifications on these functions.

Compatibility notes

Regular expressions

While pg_clickhouse pushes down regular expressions to ClickHouse equivalents when pg_clickhouse.pushdown_regex is true (the default), and makes an effort to ensure a basic level of compatibility, be aware of the differences between the two and how pg_clickhouse handles them.
  • PostgreSQL supports [POSIX Regular Expressions] while ClickHouse supports [RE2 Regular Expressions][RE2]. Beware of differences in behavior: write RE2 when the regular expression will be evaluated by ClickHouse (e.g., in a WHERE clause) and POSIX when it will be evaluated by Postgres (e.g., in a SELECT clause).
  • pg_clickhouse pushes down the [Postgres flags] by prepending them to ClickHouse regular expression inside (?). For example:
    Becomes
  • The only flags both support, and therefore can be used when evaluated by ClickHouse, are: RE2 supports only these flags; don’t use any other [Postgres flags].
  • This table summarizes the affects of the various flags (and no flag, which is the same as s) when matching newlines and line endings. Note that in Postgres, m and p prevent negated character classes ([^xyz]) from matching a newline, while the ClickHouse equivalents do not. Otherwise, the behaviors are the same in ClickHouse as in Postgres:
  • Any other flags passed to regular expression functions will prevent pushdown of the function.
  • The exception is regexp_replace(), which also supports the g flag. When g is set, pg_clickhouse uses replaceRegexpAll() instead of replaceRegexpOne() and removes the flag before prepending other flags.
  • The replacement argument to Postgres regexp_replace() supports \& to refer to the entire match, while in ClickHouse supports \0 for the entire match. Be sure to use \0 when the function pushes down to ClickHouse.
  • Postgres regexp_match returns NULL when there are no matches, while the expressions it pushes down to return an empty array. Use COALESCE() to return an empty array instead of NULL to compare return values compatibly. For example:
To avoid all ambiguity, consider setting pg_clickhouse.pushdown_regex to prevent Postgres regular expression from pushing down to ClickHouse, and using the [re2 extension], for which pg_clickhouse supports direct pushdown of ClickHouse-compatible [RE2] regular expressions.

to_char()

PostgreSQL [to_char()] for timestamp and timestamp with time zone pushes down to ClickHouse [formatDateTime] only when the format argument is a non-NULL string constant whose every PostgreSQL keyword has a byte-for-byte identical ClickHouse equivalent. If the format is dynamic or contains any unsupported keyword or modifier, the call falls back to local evaluation in PostgreSQL. pg_clickhouse never pushes down a partial translation, so output remains compatible. Two-argument to_char() forms over numeric, interval, and other non-timestamp types never push down; ClickHouse [formatDateTime] only formats date-time values.

Translated keywords

Quoted text and literals

Text wrapped in "..." passes through verbatim, with any literal % doubled to %% to escape ClickHouse’s specifier prefix. A \" outside quotes also passes through as a literal ". Inside "...", backslash only escapes "; other backslash sequences are treated as literal text.

Authors

David E. Wheeler Copyright (c) 2025-2026, ClickHouse [Array] https://clickhouse.com/docs/reference/data-types/array “ClickHouse Docs: Array(T)” [Map]: https://clickhouse.com/docs/sql-reference/data-types/map “ClickHouse Docs: Map” [Tuple]: https://clickhouse.com/docs/reference/data-types/tuple “ClickHouse Docs: Tuple(T1, T2, …)” [Nested]: https://clickhouse.com/docs/sql-reference/data-types/nested-data-structures/nested “ClickHouse Docs: Nested” [Enum]: https://clickhouse.com/docs/reference/data-types/enum “ClickHouse Docs: Enum” [String]: /reference/data-types/string “ClickHouse Docs: String” [TEXT]: https://www.postgresql.org/docs/current/datatype-character.html “PostgreSQL Docs: Character Types” [window functions]: https://www.postgresql.org/docs/current/functions-window.html “PostgreSQL Docs: Window Functions” [POSIX Regular Expressions]: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-POSIX-REGEXP “PostgreSQL Docs: POSIX Regular Expressions” [Postgres flags]: https://www.postgresql.org/docs/current/functions-matching.html#POSIX-EMBEDDED-OPTIONS-TABLE “PostgreSQL Docs: ARE Embedded-Option Letters” [RE2]: https://github.com/google/re2/wiki/Syntax “RE2 Syntax” [re2 extension]: https://github.com/ClickHouse/pg_re2 “pg_re2: ClickHouse-compatible regex functions using RE2” [intarray]: https://www.postgresql.org/docs/current/intarray.html “PostgreSQL Docs: intarray” [fuzzystrmatch]: https://www.postgresql.org/docs/current/fuzzystrmatch.html “PostgreSQL Docs: fuzzystrmatch” [to_char()]: https://www.postgresql.org/docs/current/functions-formatting.html “PostgreSQL Docs: Data Type Formatting Functions” [formatDateTime]: /reference/functions/regular-functions/date-time-functions#formatDateTime “ClickHouse Docs: formatDateTime”
Last modified on October 2, 2026