pg-clickhouse-encode.h

Encode PostgreSQL values into ClickHouse Native blocks. See setup and header.

Define PGCH_IMPLEMENTATION in exactly one translation unit. Supply your own transport and query execution.

Type mapping

These defaults come from pgch_ch_type_for for non-nullable columns. Types without a dedicated mapping use String. Domains use their base type and type modifier. Arrays wrap the element mapping in Array, with nullable elements; the array itself cannot be nullable. Nullable scalar columns use Nullable where ClickHouse supports it.

PostgreSQL Default ClickHouse Notes
boolean Bool
smallint Int16
integer Int32
bigint Int64
oid UInt32
xid8 UInt64
oid8 UInt64
real Float32 BFloat16 drops low mantissa bits
double precision Float64
numeric Decimal256(38) numeric_as_string selects String
numeric(12,6) Decimal(12,6) Precision selects Decimal width
text String low_cardinality selects LowCardinality(String)
bytea String Writes raw bytes
date Date32
time Time64(6)
timestamp DateTime64(6, ‘UTC’)
timestamptz DateTime64(6, ‘UTC’)
interval String Interval destinations require whole unit counts
uuid UUID
json String json_as_json selects JSON
jsonb String json_as_json selects JSON
inet String Override with IPv4 or IPv6 matching address family
point Point
lseg LineString Two points
path LineString Closed paths repeat the first point
polygon Ring
box Tuple(high Point, low Point)
circle Tuple(center Point, radius Float64)
line Tuple(a Float64, b Float64, c Float64)

Read conversions follow separate rules. Reading and writing may not preserve original types or values.

Explicit destination Conversion
Integer Accept compatible integer widths
String Accept source type’s text representation
Interval Interpret integers as destination unit counts
Other Accept PostgreSQL explicit casts to destination’s PostgreSQL mapping

See Append PostgreSQL Datums for structural conversions and value constraints.

Create writer

Parse same ClickHouse declarations used by destination:

chc_type *type;
chc_err err = {};

if (chc_type_parse("Array(Int32)", 12, &pgch_alloc, &type, &err) != CHC_OK)
    pgch_raise(&err, ERRCODE_INVALID_PARAMETER_VALUE, NULL, "column \"items\"");

pgch_col col = {
    .name = "items",
    .name_len = 5,
    .type = type,
};

pgch_writer *writer = pgch_writer_new(CurrentMemoryContext, &col, 1);

Writer copies column names and borrows chc_type pointers. Keep types alive until pgch_writer_free. Writer owns a child memory context under parent; freeing writer or deleting parent releases writer state.

pgch_writer_new raises ERRCODE_FDW_INVALID_DATA_TYPE when any ClickHouse type cannot be encoded. Supported composite output is Array, Tuple, Map, Nested, and the geometric types built over them; Dynamic, Variant, and unsupported LowCardinality forms are rejected. SimpleAggregateFunction(f, T) writes as T.

Append PostgreSQL Datums

Call pgch_append_datum once for every column in every row. Column order within row does not matter. Pass source type OID as valtype; writer handles conversion.

For PostgreSQL arrays, pass actual array OID. To pass pgch_array, use ANYARRAYOID. Missing conversion raises ERRCODE_DATATYPE_MISMATCH.

Pass a Tuple as a composite or an array of fields. Pass a Map as an array of composites or a two-dimensional array of key/value pairs, and Nested the same way with one element per row. Composite attributes fill Tuple fields in order, skipping dropped attributes. Text elements are parsed as destination field types.

bytea values map to ClickHouse String and FixedString without text conversion. json and jsonb map to JSON, Object, or String. FixedString pads short values with NUL and rejects longer values. Enum requires a declared label. numeric scales to destination Decimal and rejects overflow. inet family must match IPv4 or IPv6.

interval conversion rejects precision loss, such as 18 months written as IntervalYear. Float32 and Float64 accept NaN and Infinity; Decimal rejects them.

NULL requires a nullable destination, except for arrays handled by policy below.

pgch_append_slot appends one row from slot. It skips dropped and generated attributes, matching pgch_structure_from_tupdesc. Build writer from same descriptor and options to preserve column order.

NULL arrays

Default PGCH_NULL_ARRAY_ERROR rejects NULL PostgreSQL arrays because ClickHouse cannot represent a nullable array. Call pgch_writer_set_null_array(w, PGCH_NULL_ARRAY_EMPTY) to write an empty array instead.

Recover from row errors

Zero-initialize pgch_checkpoint. Call pgch_writer_checkpoint before appending each row. Saving again replaces previous position. Successful appends need no further checkpoint calls.

If append fails, call pgch_writer_rollback to discard everything written since checkpoint.

Save checkpoints only at row boundaries, with no array or tuple open. Resetting writer or rolling back invalidates all saved checkpoints. Call pgch_checkpoint_free when checkpoint is no longer needed.

Append arrays and tuples manually

Open array, append one value per element, then close array:

pgch_array_begin(writer, col);
for (size_t i = 0; i < count; i++)
    pgch_append_datum(writer, 0, values[i], INT8OID, nulls[i]);
pgch_array_end(writer);

Open tuple, append one value per field left to right, then close tuple. pgch_tuple_end requires a value for every field:

pgch_tuple_begin(writer, col);
pgch_append_datum(writer, 0, name, TEXTOID, false);
pgch_append_datum(writer, 0, Int64GetDatum(count), INT8OID, false);
pgch_tuple_end(writer);

Nest these calls as needed. Write Map(K, V) as Array(Tuple(K, V)) and Nested(fields) as Array(Tuple(fields)).

While either context is active, pgch_append_datum ignores col and targets current array element or tuple field.

Close every context before building block.

Inspect and write buffered rows

pgch_writer_rows returns first column row count. pgch_writer_bytes returns buffered bytes across all columns. Use these to choose block boundary.

pgch_writer_flush appends serialized block to pgch_buf, then resets writer after a successful write. Pass NULL options to use pgch_block_opts_local.

pgch_buf out = {};

pgch_writer_flush(writer, &out, NULL);
send_native(out.data, out.len);
pgch_buf_reset(&out);

For another output destination, pass pgch_writer_build to chc_block_write. Built block borrows writer buffers; finish writing before calling pgch_writer_reset. Reset empties buffers and keeps storage for reuse.