Author: Alexey Milovidov, 2026-07-23.
1. (50 min) What's new in ClickHouse 26.7.
2. (10 min) Q&A.
— 64 new features 🌻
— 117 performance optimizations ⛱️
— 349 bug fixes 🍉
An aggregate function that formats each group's rows
with any output format and returns the result as a string:
SELECT user, groupFormat('Markdown')(action, ts) AS history
FROM events GROUP BY user;
-- a rendered Markdown table per user
SELECT id, groupFormat('JSONEachRow')(*) FROM t GROUP BY id;
— Works with all output formats: CSV, JSON, Vertical, XML, …
— Perfect for feeding grouped data to LLMs, webhooks, and reports.
Developer: Yang Hu.
Apply an aggregate function to every element of a Tuple at once:
SELECT sumTuple((clicks, views, purchases)) FROM events;
-- (1234, 56789, 42) — one sum per element
SELECT corrTuple((a1, a2), (b1, b2));
-- (corr(a1, b1), corr(a2, b2)) — arguments paired by position
-- Note: this was already possible
SELECT COLUMNS('^metric_') APPLY sum FROM table;
— Unlike -ForEach (for Arrays), elements may have different types.
— Element names and types are preserved in the result.
Developer: RinChanNOW.
A projection can now materialize only the rows matching a predicate:
ALTER TABLE hits ADD PROJECTION errors
(
SELECT ts, status, url WHERE status >= 400 ORDER BY status
);
-- reads the tiny projection: 4 granules instead of 10M rows
SELECT count() FROM hits WHERE status >= 400;
— The optimizer uses it (cost-based) when the query's WHERE
implies the projection's WHERE.
— A subset of data, that works faster, without the need of a separate table.
Developer: S Bala Vignesh.
The url table function and URL engine now dispatch
to the right backend based on the URL scheme:
SELECT * FROM url('file://data/hits.csv'); -- File
SELECT * FROM url('s3://bucket/data/*.parquet'); -- S3
SELECT * FROM url('gs://bucket/table.parquet'); -- S3 (GCS)
SELECT * FROM url('az://container/blob.csv'); -- Azure
SELECT * FROM url('hdfs://nn:8020/path'); -- HDFS
— One function to read them all.
— http(s):// works as before.
— s3, gs, etc. use the environment credentials.
Developer: Alexey Milovidov.
A table function that builds a query string from an expression and runs it:
SET allow_experimental_eval_table_function = 1;
SELECT * FROM eval(
'SELECT number FROM numbers(' || toString(2 + 1) || ')');
-- 0, 1, 2
-- the argument can also be a subquery, not wrapped in a string —
-- e.g., generate a query that sums every numeric column of a table:
SELECT * FROM eval((
SELECT 'SELECT '
|| arrayStringConcat(groupArray('sum(' || name || ')'), ', ')
|| ' FROM metrics'
FROM system.columns WHERE table = 'metrics' AND type LIKE '%Int%'));
— Dynamic SQL without external scripting.
Developers: Yue Ni, Alexey Milovidov.
The remote table function now has persistent counterparts:
CREATE TABLE events_on_prod
ENGINE = Remote('prod:9000', default, events);
-- no column list needed: the structure is inferred from the remote table
SELECT count() FROM events_on_prod; -- reads remotely
INSERT INTO events_on_prod VALUES …; -- writes remotely
-- Note: this was already possible:
CREATE TABLE events_on_prod AS remote('prod:9000', default, events);
— A permanent "symlink" to a table on another server.
— RemoteSecure — the same over TLS.
— Supports cluster addresses: Remote('host{1..3}:9000', …).
Developer: Alexey Milovidov.
mysql, postgresql, and sqlite accept a query instead of a table name
and pass it to the external database as is:
SELECT * FROM postgresql('host:5432', 'db', query($$
SELECT date_trunc('week', day) AS w, count()
FROM pageviews GROUP BY w
$$), 'user', 'pass');
-- runs inside PostgreSQL, with PostgreSQL functions
SELECT * FROM sqlite('db.sqlite', (SELECT customer, sum(amount)
FROM orders GROUP BY customer)); -- subquery form
— The result structure is inferred from the query. Read-only.
— Aggregate remotely, transfer only the result.
Developers: Denis Vidiaev, Alexey Milovidov.
The url table function expands wildcards by crawling index pages:
SET allow_experimental_url_wildcard_from_index_pages = 1;
SELECT * FROM url(
'https://dumps.wikimedia.org/other/pageviews/2026/2026-01/pageviews*.gz');
-- lists the HTML/plaintext directory pages, follows matching links
— Works with autogenerated directory listings (nginx, S3 websites, …).
— With limits on the index page size and the number of directories read.
Developer: Yue Ni.
A data type for vector embeddings,
that allows tuning the search precision at runtime.
Production-ready since 26.2:
CREATE TABLE vectors (
id UInt64, name String, ...
vec QBit(BFloat16, 1536)
) ORDER BY ();
SELECT id, name FROM vectors
ORDER BY L2DistanceTransposed(vec, target, 10)
LIMIT 10;
It uses a bit-sliced data layout:
every number is sliced by bits,
e.g., for 1536-dim vector of BFloat16,
we store 16 subcolumns with Nth (1..16th) bits from all dimensions.
At the query time, we specify, how many (most significant) bits to take.
For example, we can ask to read
only 10 out of 16 bits.
QBit now supports Int8, which allows anything from binary (1 bit) to full 8 bit quantization, chosen at runtime per query.
It can store any Int8 codes, and we also provide quantizeBFloat16ToInt8 and dequantizeInt8ToBFloat16 functions for optimal conversion from Float to Int8, minimizing the error on gaussian-distributed vector coordinates.
CREATE TABLE images (id UInt64, emb QBit(Int8, 2048)) …;
INSERT INTO images SELECT id, quantizeBFloat16ToInt8(embedding * sqrt(2048)) FROM src;
-- scale unit-normalized embeddings by sqrt(dim) and convert to Int8
SELECT id FROM images
ORDER BY cosineDistanceTransposedQuantized(emb, target, 4) LIMIT 10;
-- reads only the top 4 bit-planes — half the I/O of 8-bit
Developer: Alexey Milovidov.
The optional stride parameter slices dimensions into groups — each bit-plane of each group is its own stream on disk: QBit(Int8, 1024, 256):
| dims 1–256 | 257–512 | 513–768 | 769–1024 | |
| bit 1 (sign) | ||||
| bit 2 | ||||
| bit 3 | ||||
| bit 4 | ||||
| bit 5 … 8 |
… ORDER BY cosineDistanceTransposedQuantized(emb, target, 4, 256) …
-- 4 bits × first 256 dims: reads the 4 green streams of 32 — 3% of a Float32 column
You can request reading only first N dimensions - good for MRL (Matryoshka Representation Learning) embeddings to tune between the speed and recall.
Developer: Alexey Milovidov.
To improve the quality of vector search, it makes sense to apply random rotation of N-dimensional space before quantization of vectors.
Rotation is a multiplication to an orthogonal matrix. There are many orthogonal matrices, and matrix multiplication is computationally expensive.
Thankfully, there is a family of orthogonal matrices, that are faster to multiply with - Hadamard matrices. And it is broad enough to represent pseudorandom rotations.
Now ClickHouse has a function for pseudorandom rotations:
SELECT randomHadamardTransform(vec);
Developer: Alexey Milovidov.
A building block for vector quantization pipelines:
SELECT randomHadamardTransform([1., 2., 3., 4.]::Array(Float32));
-- [0, 2, 1, -5] — an orthogonal, norm-preserving rotation
SELECT randomHadamardTransform(v, seed); -- different rotation
SELECT randomHadamardTransform(v, seed, 256); -- truncate to 256 dims
— Spreads the information evenly across dimensions,
so scalar quantization loses less — rotate, then quantize.
— Truncated form = Johnson–Lindenstrauss random projection.
— Deterministic: the same seed gives the same rotation.
Developer: Alexey Milovidov.
Store a compact quantized companion next to full-precision vectors:
CREATE TABLE vecs (id UInt32,
v Array(BFloat16) CODEC(Quantized('rabitq', 384))) …;
-- methods: int8, rabitq (1-bit), turboquant (2-bit), mrl, pq
SET vector_search_use_quantized_codes = 1;
SELECT id FROM vecs ORDER BY L2Distance(v, […]) LIMIT 10;
— Two-stage brute-force search without an index: scan the small
quantized stream, then rescore the candidates at full precision.
— Up to 8x less I/O in the first stage (1-bit codes: 16x smaller).
Developer: Shankar Iyer.
Text indexes can now store token positions — for exact phrase search:
CREATE TABLE docs (…, INDEX idx body TYPE text(
tokenizer = 'splitByNonAlpha', support_phrase_search = 1))
… SETTINGS allow_experimental_text_index_phrase_search = 1;
SELECT count() FROM docs WHERE hasPhrase(body, 'quick brown fox');
-- 1 — but hasPhrase(body, 'brown quick fox') = 0: order matters
— The phrase test is answered directly from the index:
in our test, 124 granules → 1, and no column data was read at all.
Developer: Elmi Ahmadov.
Convert between WGS84 coordinates and the UTM / MGRS systems
used in surveying, mapping, and military grids:
Map: NASA Visible Earth / Wikimedia Commons, public domain.
SELECT geoToUTM(4.89, 52.36) AS amsterdam;
-- (628700.24, 5802760.82, 31, 'U')
-- easting, northing, zone, band
SELECT UTMToGeo(628700.24, 5802760.82, 31, true);
-- (4.89, 52.36) — and back
SELECT geoToMGRS(4.89, 52.36);
-- '31UFU2870002760'
SELECT MGRSToGeo('31UFU2870002760');
-- (4.89, 52.36)
Developer: Alexey Milovidov.
26.6 added GeoJSON input. 26.7 writes it, too:
SELECT id, (13.4050, 52.5200)::Point AS geometry, 'Berlin' AS name
FORMAT GeoJSON
{"type":"FeatureCollection","features":[
{"type":"Feature","id":1,
"geometry":{"type":"Point","coordinates":[13.405,52.52]},
"properties":{"name":"Berlin"}}, …]}
— The geo column → geometry, the id column → feature id,
everything else → properties.
— Feed query results straight to Leaflet, Mapbox, deck.gl, QGIS.
Developers: Mark Needham, Nihal Z. Miaji.
Do two shapes intersect — whatever their types are?
SELECT geometryIntersectCartesian(
[[(0., 0.), (10., 0.), (10., 10.), (0., 10.)]]::Polygon,
[(5., 5.), (15., 15.)]::LineString); -- 1
SELECT geometryIntersectSpherical(route, restricted_zone);
— Unlike polygonsIntersect*, they accept any geometry type:
Point, LineString, MultiLineString, Ring, Polygon, MultiPolygon —
including the umbrella Geometry type, and the arguments may differ.
Developer: Alexey Milovidov.
Frequent inserts produce many small manifest files.
Now ClickHouse can compact the metadata — without rewriting data:
SET allow_experimental_iceberg_compaction = 1;
OPTIMIZE TABLE ice MANIFEST;
-- 13 manifests merged into 1; a new 'replace' snapshot is committed
— Faster planning for every subsequent query on the table.
— Triggered when the manifest list exceeds
iceberg_manifest_min_count_to_compact (default 30).
— Data files are untouched; time travel keeps working.
Developer: Smita Kulkarni.
Delta Lake writes are now Beta (were experimental):
SET allow_experimental_delta_lake_writes = 1;
INSERT INTO delta_table VALUES (3, 'written'), (4, 'by clickhouse');
-- commits a new Delta log version; readable by Spark, Trino, Python
Microsoft OneLake: authentication with a bearer token:
CREATE DATABASE onelake ENGINE = DataLakeCatalog('https://onelake.…')
SETTINGS catalog_type = 'onelake', onelake_bearer_token = '…';
— No need to share a long-lived client secret.
Developers: Kseniia Sumarokova, Asya Shneerson.
The built-in /play UI now has tabs for working with multiple queries:
— Tabs, titles, parameters, and small result snapshots
are persisted across reloads.
— Integrated with browser history and the URL.
— Middle-click a tab to duplicate it; switch tabs with the mouse wheel.
— Middle-click a table in the database panel to open it in a new tab.
— Connection credentials are tucked behind a key button.
Demo
Developer: Alexey Milovidov.
The result table became much more powerful:
— Color coding per column (the 🌈 toggle in the header):
bar / heatmap / categorical — remembered in the URL.
— Pinned columns (📌): keep key columns visible while scrolling.
— Autocompletion from system.completions + a WASM SQL lexer.
— Full keyboard navigation: arrows in the database panel and
the result table, focus outlines, accessible toolbar.
— Rainbow parentheses, digit-group highlighting, Tab indentation,
syntax errors highlighted in place.
Developer: Alexey Milovidov.
The left panel of the Web UI grew up:
— Table count next to each expanded database.
— A refresh button to reload the table list.
— Expanded databases are restored when the panel reopens.
— Click a table's icon to see its columns — with type icons,
a size bar per column, and compression ratios in tooltips.
— Selecting a database makes it the default for queries.
Demo
Developer: Alexey Milovidov.
A new documentation page, embedded in the server:
http://localhost:8123/docs
— Instant search over system.documentation —
functions, settings, table engines, data types, formats, …
— Rendered reference docs with syntax highlighting, math,
and cross-links. Works offline, always matches your server version.
Demo
Developer: Alexey Milovidov.
A JOIN can use the values of the right-hand side to prune granules
of the left table via its primary key or skip indexes:
SET enable_join_runtime_filters_index_analysis = 1;
SELECT … FROM facts JOIN lookup ON facts.id = lookup.id;
-- the ids from `lookup` become an index condition on `facts`
Measured — 100M-row table joined with a 100-row table:
granules read: 12216 → 100 (122x),
time: 0.177 s → 0.022 s (8x).
Developer: Shankar Iyer.
The right-hand side of a hash join now uses a compact
8-byte index-based row reference — for every key type:
— Hash-table entries of ALL joins are now as small as those of ANY joins.
— Benchmarked on 100–300M row joins: median 12% faster,
15% less peak memory; INNER on UInt64 keys: 21% faster, 38% less memory.
A test: 100M × 150M parallel_hash INNER JOIN:
26.6: 14.4 GB, 0.84 sec.
26.7: 8.0 GB, 0.66 sec.
Developer: Harikrishnan Prabakaran.
A new JOIN-order optimization algorithm —
dynamic programming over subsets:
SET query_plan_optimize_join_order_algorithm = 'dpsub';
— Considers all JOIN orders — finds the optimal plan.
— Lower optimization overhead than dpsize.
— Supports non-inner joins, unlike dpsize and dphyp.
— Algorithms can be chained: 'dpsub,greedy' — tried in order,
with fallback for queries an algorithm cannot handle.
Developer: Fisnik Kastrati.
Simple regular expressions are now compiled to native code with LLVM:
SELECT count() FROM logs
WHERE match(line, '^[a-z]+[0-9]+@example[0-9]+\\.com$');
— Applies to match, extract, extractAll, replaceRegexpOne/All, LIKE.
— compile_regular_expressions — on by default; unsupported patterns
transparently fall back to RE2.
A test: match() over 20M strings:
26.6: 2.7 sec.
26.7: 1.1 sec.
Developer: Alexey Milovidov.
When the GROUP BY key matches the ORDER BY key and the table's
sorting key, the LIMIT is pushed into aggregation-in-order:
SELECT site, avg(dur) FROM visits
GROUP BY site ORDER BY site LIMIT 10
SETTINGS optimize_aggregation_in_order = 1;
-- stops reading as soon as 10 groups are complete
A test: 200M rows:
26.6: 0.23 sec.
26.7: 0.036 sec. (6.4 times faster!)
Also: DISTINCT in order is 2x faster, LIMIT BY in order 1.4x faster
(faster scanning of runs of equal keys in sorted streams).
Developers: Konstantin Bogdanov, Nihal Z. Miaji.
The query condition cache remembers which data ranges matched a WHERE
— now it also works for:
— Top-K queries (ORDER BY … LIMIT n) with dynamic filtering —
including queries with lazy materialization, and entries are shared
with ordinary queries that have the same WHERE.
— Local Parquet files read via the File engine
(object storage was already supported), keyed by ETag for S3.
Measured — Top-K over 100M rows, second run: 0.114 s → 0.057 s (2x).
Developers: Alexey Milovidov, Shankar Iyer.
— Arrow / ArrowStream: a native reader and writer (no Apache Arrow
library, no extra copies), now the default. Writes ~1.3x faster.
— ZSTD decompression on ARM: NEON-vectorized short-offset copies.
Scan of a repetitive UInt64 column: 3.0 s → 1.8 s (up to 2.3x pure decompression).
— GZip via libdeflate: ~1.4–1.5x faster decompression, ~1.15x faster
compression with a better ratio — for .gz, HTTP, url/s3, Parquet GZIP.
— LZ4 decompression is faster for fixed-size, low-cardinality columns.
— Delta codec decompression vectorized: 1.5–5x faster.
Developers: Alexey Milovidov, Nikita Taranov, Manuel.
A batch of improvements for those who run ClickHouse on a Mac:
— jemalloc no longer purges dirty pages eagerly — a background
purging thread, like on Linux: allocation-heavy workloads
(e.g. recursive CTEs) are ~2x faster.
— jemalloc per-CPU arenas and allocation profiling enabled.
— Asynchronous reads from remote replicas (epoll over kqueue) —
distributed queries read shards in parallel instead of serially.
— SSD cache dictionaries and the FileLog engine now work on macOS.
— STREAM queries are supported.
Developers: Alexey Milovidov, Raúl Marín.
The Mandelbrot set in pure SQL (a recursive CTE):
github.com/ClickHouse/sql-mandelbrot-benchmark
Rendered by ClickHouse.
| 1 | ClickHouse (SQL) | 518 ms ⭐ |
| 2 | NumPy (vectorized) | 688 ms |
| 3 | chDB (SQL) | 745 ms |
| 4 | CedarDB (SQL) | 835 ms |
| 5 | ArrowDatafusion (SQL) | 998 ms |
| 6 | Arc (DuckDB over HTTP) | 1,589 ms |
| 7 | DuckDB (SQL) | 1,954 ms |
| … | ||
| 11 | SQLite (SQL) | 144,421 ms |
— The only engine faster than hand-vectorized NumPy.
— MacBook Pro M3 Max — this benchmark surfaced the allocator fix from the previous slide.
Created by Thomas Zeutschler, Ulrich Ludmann, and Jakub Jirak;
continued by Alexey Milovidov.
— Faster query analysis: shared subexpressions are not rebuilt when
constructing the DAG; bounded work for giant AND-chains.
— Faster window functions: less memory for SELECT * over wide tables;
faster quantile*/uniq*/groupArray with OVER () and RANGE frames.
— Faster float parsing — and it is precise (closest-representable)
by default now: 50M floats parse 1.2x faster than 26.6.
— Faster uniq, uniqCombined: 500M rows, no keys: 0.23 s → 0.09 s (2.5x).
— Redundant logical expressions optimized: a < 3 AND a > 5 → false,
a = 3 AND a < 5 → a = 3 — contradictions detected at plan time.
— INTERSECT ALL / EXCEPT ALL are several times faster. And more…
Developers: D. Novik, A. Milovidov, N. Z. Miaji, R. Marín, A. Popov, Xiaozhe Yu.
Run the query — and see the actual execution metrics in the plan:
EXPLAIN ANALYZE SELECT site, avg(dur) FROM visits
WHERE site < 100000 GROUP BY site ORDER BY site LIMIT 10
Query summary:
Time: 76.77 ms (planning 29.38 ms · execution 47.39 ms)
Read: 20.08 million rows, 160.54 MB (423.72 million rows/s.)
Peak memory: 91.95 MiB
…
└──Aggregating
│ I/O: rows 20.00 million → 100.00 thousand (0.50%) · 160 MB → 1.2 MB
│ Stage (partial aggregation): time 24.29 ms (51.3%) · parallelism 16.29/96
└──ReadFromMergeTree (default.visits)
— Time split into planning and execution; per-step rows, bytes,
time share, and achieved parallelism. Find the bottleneck at a glance.
Developer: Kirill Kopnev.
Write a UDF in any programming language — right in SQL.
A "driver" compiles it at CREATE time into an executable UDF:
CREATE FUNCTION collatz_steps ARGUMENTS (n UInt64) RETURNS UInt64
ENGINE = GVisorC AS $$
uint64_t steps = 0;
while (n > 1) { n = (n % 2 == 0) ? n / 2 : 3 * n + 1; ++steps; }
return steps;
$$;
SELECT number, collatz_steps(number) FROM numbers(1, 10);
— The C body is compiled and runs in sandboxed Docker containers
the GVisorC, DockerC, and UnsafeC drivers are provided as examples.
— Drivers are declared by the operator; functions survive restarts.
Developers: Daniil Timižev, Alexey Milovidov.
The supported range grew from [1900, 2299]
to [0000-01-01, 9999-12-31]:
SELECT toDateTime64('9999-12-31 23:59:59.999', 3);
-- 9999-12-31 23:59:59.999 (was clamped to 2299-12-31)
SELECT age('year', toDateTime64('0079-08-24 13:00:00', 0), now());
-- 1946 years since the eruption of Vesuvius
SELECT toDateTime64('1789-07-14 12:00:00', 0) + INTERVAL 237 YEAR;
-- 2026-07-14 12:00:00
Uses Proleptic Gregorian Calendar.
Developer: Alexey Milovidov.
A new asymmetric LZ codec: compresses better than LZ4,
decompresses just as fast:
CREATE TABLE logs (line String CODEC(ZXC)) …;
| codec | compressed | ratio | full scan |
|---|---|---|---|
| LZ4 | 1.28 GiB | 3.73 | 0.21 s |
| ZXC | 1.15 GiB | 4.16 | 0.21 s |
| ZSTD | 0.67 GiB | 7.16 | 1.05 s |
— Compression is slower (~2x LZ4) — by design: pay once on write,
win on every read. A great fit for read-heavy columns.
Developer: Alexey Milovidov.
ZXC codec: Bertrand Lebonnois and contributors (BSD License) https://github.com/hellobertrand/zxc
SZ3 🧪 — error-bounded lossy compression for floats:
v Float64 CODEC(SZ3('ALGO_INTERP_LORENZO', 'ABS', 0.001))
-- sensor data, 50M rows: ZSTD 353 MiB → SZ3 22 MiB (16x)
-- with the default settings: 2.1 MiB (168x)
ALP 🧪 — now auto-selects between two schemes:
price Float64 CODEC(ALP) -- AUTO: picks STD or RD per block
m Float64 CODEC(ALP(RD)) -- force "Real Doubles"
-- decimal-like data: ALP ratio 3.74 vs ZSTD 2.42 — and it is lossless
— Both require allow_experimental_codecs = 1.
Developers: Konstantin Vedernikov, Alexey Milovidov, Nazarii Piontko.
SZ3 library: Sheng Di, Kai Zhao, Xin Liang, Jinyang Liu, Franck Cappello, and contributors (BSD License) https://github.com/szcompressor/SZ3
ALP algorithm: Azim Afroozeh, Leonardo Kuffó, and Peter Boncz — CWI (https://ir.cwi.nl/pub/33334)
A table engine that executes the queries you INSERT into it:
CREATE TABLE shadow (query String) ENGINE = QueryRunner
SETTINGS cluster = 'staging', shard = 'random', threads = 4;
INSERT INTO shadow
SELECT query FROM system.query_log WHERE type = 'QueryFinish';
-- replay production traffic on the staging cluster
— Fire-and-forget: async or sync mode, results are discarded.
— For shadow traffic, benchmarks, fuzzing, batch execution
of generated queries, and directing queries to remote clusters.
— Access control via SQL SECURITY INVOKER / DEFINER / NONE.
Developer: Miсhael Stetsyuk.
WITH
2.5 AS z0, 0.6 AS ty,
(24.0, 0.0, 6.0) AS target,
(24.0, -45.0, 12.0) AS eye,
36.0 AS fovDeg,
getSetting('output_format_image_width')::UInt64 AS W,
getSetting('output_format_image_height')::UInt64 AS H,
W::Float64 AS imgW, H::Float64 AS imgH,
4 AS maxDepth,
(9.0, -30.0, 52.0) AS lightPos,
5.0 AS lightR,
(0.42, 0.60, 0.95) AS skyColor,
(0.66, 0.78, 0.96) AS hazeColor,
35.0 AS fogStart, 120.0 AS fogRange,
(0.02, 0.02, 0.02) AS floorA,
(1.00, 0.80, 0.00) AS floorB,
5.0 AS tile, 0.30 AS floorAmb, 0.85 AS floorDiff,
(1.0, 1.0, 1.0) AS specColor,
60.0 AS shininess, 0.72 AS reflect_k,
(24.0, 12.0, 17.5) AS csgA, 6.0 AS csgAr,
(24.0, 6.6, 17.0) AS csgB, 4.4 AS csgBr,
1.15 AS exposure, 2.2 AS gamma, 0.002 AS eps,
48 AS marchSteps, 0.01 AS marchEps, 260.0 AS marchMax,
0.62 AS rodR,
[((100000.0,100000.0,100000.0),(1.0,0.0,0.0),(0.0,1.0,0.0),0.1,0.1)] AS oboxes,
[((5.75,0.0,2.5),(5.75,0.0,9.5),0.62), ((8.65,0.0,2.5),(8.65,0.0,7.5),0.62), ((16.8,0.0,2.5),(16.8,0.0,9.5),0.62), ((17.15,0.0,5.0),(20.05,0.0,7.6),0.62), ((17.35,0.0,5.1),(20.05,0.0,2.5),0.62), ((22.4,0.0,2.5),(22.4,0.0,9.5),0.62), ((26.15,0.0,2.5),(26.15,0.0,9.5),0.62), ((22.35,0.0,6.0),(26.15,0.0,6.0),0.62), ((34.82,0.0,3.5),(34.82,0.0,7.5),0.62), ((37.72,0.0,3.5),(37.72,0.0,7.5),0.62), ((45.35,0.0,5.05),(48.85,0.0,5.05),0.41333)] AS cyls,
[(100000.0,100000.0,0.1,0.05,100000000.0,100000000.0,100000000.0,100000000.0)] AS rings,
[((8.65,0.0,8.8),0.72)] AS spheres,
[(1.3,6.0,2.28,0.62,(4.425,0.0,6.0),(3.025,100.0,1.5)), (12.95,5.0,1.425,0.575,(16.45,0.0,5.0),(3.4,100.0,1.1)), (30.4,5.0,1.475,0.525,(1000000.0,0.0,1000000.0),(0.1,0.1,0.1)), (36.27,4.0,1.45,0.62,(33.95,0.0,7.75),(9.0,100.0,3.75)), (41.9,6.0,1.025,0.425,(45.375,0.0,-0.25),(3.475,100.0,6.25)), (41.9,3.9,1.025,0.425,(36.375,0.0,7.7),(5.525,100.0,3.8)), (47.25,5.0,1.585,0.465,(50.8,0.0,4.32),(3.25,100.0,0.32))] AS tori,
((a, b) -> tuplePlus(a, b)) AS va,
((a, b) -> tupleMinus(a, b)) AS vs,
((a, s) -> tupleMultiplyByNumber(a, s)) AS vm,
((a, b) -> dotProduct(a, b)) AS vd,
((a) -> L2Normalize(a)) AS vn,
((a, b) -> (a.2*b.3 - a.3*b.2, a.3*b.1 - a.1*b.3, a.1*b.2 - a.2*b.1)) AS vc,
((d, n) -> tupleMinus(d, tupleMultiplyByNumber(n, 2.0 * dotProduct(d, n)))) AS vref,
L2Normalize(tupleMinus(target, eye)) AS camFwd,
L2Normalize(vc(L2Normalize(tupleMinus(target, eye)), (0.0, 0.0, 1.0))) AS camRight,
vc(camRight, camFwd) AS camUp,
tan(fovDeg * 0.5 * pi() / 180.0) AS tanHalf,
imgW / imgH AS aspect,
((O,D,b) -> arrayMap(eu -> arrayMap(fu -> arrayMap(ev -> arrayMap(fv -> arrayMap(ew -> arrayMap(fw -> arrayMap(lou -> arrayMap(hiu -> arrayMap(lov -> arrayMap(hiv -> arrayMap(low -> arrayMap(hiw -> arrayMap(tn -> arrayMap(tf -> if(tf >= tn AND tf > eps, if(tn > eps, tn, tf), 1e9), [least(hiu, least(hiv, hiw))])[1], [greatest(lou, greatest(lov, low))])[1], [greatest((ew-ty)/fw,(ew+ty)/fw)])[1], [least((ew-ty)/fw,(ew+ty)/fw)])[1], [greatest((ev-b.5)/fv,(ev+b.5)/fv)])[1], [least((ev-b.5)/fv,(ev+b.5)/fv)])[1], [greatest((eu-b.4)/fu,(eu+b.4)/fu)])[1], [least((eu-b.4)/fu,(eu+b.4)/fu)])[1], [D.2])[1], [b.1.2 - O.2])[1], [vd(b.3,D)])[1], [vd(b.3, vs(b.1,O))])[1], [vd(b.2,D)])[1], [vd(b.2, vs(b.1,O))])[1]) AS oboxT,
((hp,b) -> arrayMap(qu -> arrayMap(qv -> arrayMap(qw -> if(abs(qu) >= abs(qv) AND abs(qu) >= abs(qw), vm(b.2, if(qu>0.0,1.0,-1.0)), if(abs(qv) >= abs(qw), vm(b.3, if(qv>0.0,1.0,-1.0)), (0.0, if(qw>0.0,1.0,-1.0), 0.0))), [(hp.2 - b.1.2)/ty])[1], [vd(b.3, vs(hp,b.1))/b.5])[1], [vd(b.2, vs(hp,b.1))/b.4])[1]) AS oboxN,
((O,D,A,B,r) -> arrayMap(ba -> arrayMap(oa -> arrayMap(baba -> arrayMap(bard -> arrayMap(baoa -> arrayMap(aa -> arrayMap(bb -> arrayMap(cc -> arrayMap(hh -> arrayMap(t -> arrayMap(y -> if(hh >= 0.0 AND aa > 1e-9 AND t > eps AND y > 0.0 AND y < baba, t, 1e9), [baoa + t*bard])[1], [(-bb - sqrt(greatest(hh,0.0)))/aa])[1], [bb*bb - aa*cc])[1], [baba*vd(oa,oa) - baoa*baoa - r*r*baba])[1], [baba*vd(D,oa) - baoa*bard])[1], [baba - bard*bard])[1], [vd(ba,oa)])[1], [vd(ba,D)])[1], [vd(ba,ba)])[1], [vs(O,A)])[1], [vs(B,A)])[1]) AS cylBodyT,
((O,D,P,ax,r) -> arrayMap(den -> arrayMap(t -> arrayMap(hp -> if(abs(den) > 1e-9 AND t > eps AND vd(vs(hp,P), vs(hp,P)) <= r*r, t, 1e9), [va(O, vm(D, t))])[1], [vd(ax, vs(P,O))/den])[1], [vd(ax, D)])[1]) AS diskT,
((O,D,A,B,r) -> least(cylBodyT(O,D,A,B,r), least(diskT(O,D,A,vs(B,A),r), diskT(O,D,B,vs(B,A),r)))) AS rodT,
((O,D,A,B,r) -> arrayMap(ba -> arrayMap(tL -> arrayMap(tA -> arrayMap(tB -> arrayMap(tm -> arrayMap(hp -> if(tm = tL, vn(vs(hp, va(A, vm(ba, vd(vs(hp,A),ba)/vd(ba,ba))))), if(tm = tA, vn(vm(ba,-1.0)), vn(ba))), [va(O, vm(D,tm))])[1], [least(tL, least(tA,tB))])[1], [diskT(O,D,B,ba,r)])[1], [diskT(O,D,A,ba,r)])[1], [cylBodyT(O,D,A,B,r)])[1], [vs(B,A)])[1]) AS rodN,
((O,D,rg) -> arrayMap(a2 -> arrayMap(hb -> arrayMap(rr -> arrayMap(dO -> arrayMap(dI -> arrayMap(toN -> arrayMap(toF -> arrayMap(tiN -> arrayMap(tiF -> arrayMap(ty0 -> arrayMap(ty1 -> arrayMap(tcN -> arrayMap(tcF -> arrayMap(aN -> arrayMap(aF -> arrayMap(e1 -> arrayMap(e2 -> arrayMap(e3 -> arrayMap(tm -> tm, [least(e1, least(e2, e3))])[1], [if(aN < tcF AND tcF < aF AND tcF > eps AND NOT (tiN < tcF AND tcF < tiF), tcF, 1e9)])[1], [if(aN < tiF AND tiF < aF AND tiF > eps AND NOT (tcN < tiF AND tiF < tcF), tiF, 1e9)])[1], [if(aN < aF AND aN > eps AND NOT (tiN < aN AND aN < tiF) AND NOT (tcN < aN AND aN < tcF), aN, 1e9)])[1], [least(ty1, toF)])[1], [greatest(ty0, toN)])[1], [least(greatest((rg.5-O.1)/D.1,(rg.7-O.1)/D.1), greatest((rg.6-O.3)/D.3,(rg.8-O.3)/D.3))])[1], [greatest(least((rg.5-O.1)/D.1,(rg.7-O.1)/D.1), least((rg.6-O.3)/D.3,(rg.8-O.3)/D.3))])[1], [greatest((-ty-O.2)/D.2, (ty-O.2)/D.2)])[1], [least((-ty-O.2)/D.2, (ty-O.2)/D.2)])[1], [if(dI > 0.0, (-hb + sqrt(dI))/a2, -1e9)])[1], [if(dI > 0.0, (-hb - sqrt(dI))/a2, 1e9)])[1], [if(dO > 0.0, (-hb + sqrt(dO))/a2, -1e9)])[1], [if(dO > 0.0, (-hb - sqrt(dO))/a2, 1e9)])[1], [hb*hb - a2*(rr - rg.4*rg.4)])[1], [hb*hb - a2*(rr - rg.3*rg.3)])[1], [(O.1-rg.1)*(O.1-rg.1) + (O.3-rg.2)*(O.3-rg.2)])[1], [(O.1-rg.1)*D.1 + (O.3-rg.2)*D.3])[1], [greatest(D.1*D.1 + D.3*D.3, 1e-12)])[1]) AS ringT,
((O,D,rg) -> arrayMap(a2 -> arrayMap(hb -> arrayMap(rr -> arrayMap(dO -> arrayMap(dI -> arrayMap(toN -> arrayMap(toF -> arrayMap(tiN -> arrayMap(tiF -> arrayMap(ty0 -> arrayMap(ty1 -> arrayMap(tcN -> arrayMap(tcF -> arrayMap(aN -> arrayMap(aF -> arrayMap(e1 -> arrayMap(e2 -> arrayMap(e3 -> arrayMap(tm -> arrayMap(hp -> if(tm = e1, if(toN >= ty0, vn((hp.1-rg.1, 0.0, hp.3-rg.2)), (0.0, if(D.2>0.0,-1.0,1.0), 0.0)), if(tm = e2, vn((rg.1-hp.1, 0.0, rg.2-hp.3)), if(greatest((rg.5-O.1)/D.1,(rg.7-O.1)/D.1) <= greatest((rg.6-O.3)/D.3,(rg.8-O.3)/D.3), (if(D.1>0.0,-1.0,1.0),0.0,0.0), (0.0,0.0,if(D.3>0.0,-1.0,1.0))))), [va(O, vm(D, tm))])[1], [least(e1, least(e2, e3))])[1], [if(aN < tcF AND tcF < aF AND tcF > eps AND NOT (tiN < tcF AND tcF < tiF), tcF, 1e9)])[1], [if(aN < tiF AND tiF < aF AND tiF > eps AND NOT (tcN < tiF AND tiF < tcF), tiF, 1e9)])[1], [if(aN < aF AND aN > eps AND NOT (tiN < aN AND aN < tiF) AND NOT (tcN < aN AND aN < tcF), aN, 1e9)])[1], [least(ty1, toF)])[1], [greatest(ty0, toN)])[1], [least(greatest((rg.5-O.1)/D.1,(rg.7-O.1)/D.1), greatest((rg.6-O.3)/D.3,(rg.8-O.3)/D.3))])[1], [greatest(least((rg.5-O.1)/D.1,(rg.7-O.1)/D.1), least((rg.6-O.3)/D.3,(rg.8-O.3)/D.3))])[1], [greatest((-ty-O.2)/D.2, (ty-O.2)/D.2)])[1], [least((-ty-O.2)/D.2, (ty-O.2)/D.2)])[1], [if(dI > 0.0, (-hb + sqrt(dI))/a2, -1e9)])[1], [if(dI > 0.0, (-hb - sqrt(dI))/a2, 1e9)])[1], [if(dO > 0.0, (-hb + sqrt(dO))/a2, -1e9)])[1], [if(dO > 0.0, (-hb - sqrt(dO))/a2, 1e9)])[1], [hb*hb - a2*(rr - rg.4*rg.4)])[1], [hb*hb - a2*(rr - rg.3*rg.3)])[1], [(O.1-rg.1)*(O.1-rg.1) + (O.3-rg.2)*(O.3-rg.2)])[1], [(O.1-rg.1)*D.1 + (O.3-rg.2)*D.3])[1], [greatest(D.1*D.1 + D.3*D.3, 1e-12)])[1]) AS ringN,
((O,D,C,r) -> if(dotProduct(vs(O,C),D)*dotProduct(vs(O,C),D) - (dotProduct(vs(O,C),vs(O,C))-r*r) > 0.0
AND -dotProduct(vs(O,C),D) - sqrt(greatest(dotProduct(vs(O,C),D)*dotProduct(vs(O,C),D)-(dotProduct(vs(O,C),vs(O,C))-r*r),0.0)) > eps,
-dotProduct(vs(O,C),D) - sqrt(greatest(dotProduct(vs(O,C),D)*dotProduct(vs(O,C),D)-(dotProduct(vs(O,C),vs(O,C))-r*r),0.0)), 1e9)) AS sphT,
((o, d, c, rad) -> if(
dotProduct(vs(o,c), d) * dotProduct(vs(o,c), d) - (dotProduct(vs(o,c), vs(o,c)) - rad*rad) > 0.0,
(-dotProduct(vs(o,c), d) - sqrt(greatest(dotProduct(vs(o,c), d) * dotProduct(vs(o,c), d) - (dotProduct(vs(o,c), vs(o,c)) - rad*rad), 0.0)),
-dotProduct(vs(o,c), d) + sqrt(greatest(dotProduct(vs(o,c), d) * dotProduct(vs(o,c), d) - (dotProduct(vs(o,c), vs(o,c)) - rad*rad), 0.0))),
(1e9, -1e9))) AS sphPair,
((p,tl) -> sqrt(pow(sqrt((p.1-tl.1)*(p.1-tl.1)+(p.3-tl.2)*(p.3-tl.2)) - tl.3, 2) + p.2*p.2) - tl.4) AS tsdf,
((p,C,H) -> sqrt(pow(greatest(abs(p.1-C.1)-H.1,0.0),2) + pow(greatest(abs(p.2-C.2)-H.2,0.0),2) + pow(greatest(abs(p.3-C.3)-H.3,0.0),2))
+ least(greatest(abs(p.1-C.1)-H.1, greatest(abs(p.2-C.2)-H.2, abs(p.3-C.3)-H.3)), 0.0)) AS bsdf,
((p) -> arrayMin(arrayMap(tl -> greatest(tsdf(p, tl), -bsdf(p, tl.5, tl.6)), tori))) AS toriSDF,
((O,D) -> arrayMap(tfin -> if(toriSDF(va(O, vm(D, tfin))) < marchEps AND tfin > eps AND tfin < marchMax, tfin, 1e9),
[arrayFold((t, i) -> t + toriSDF(va(O, vm(D, t))), range(marchSteps), 0.02)])[1]) AS marchTorus,
((O,D,t) -> arrayMap(p -> vn(va(va(vm((1.0,-1.0,-1.0), toriSDF(va(p, (0.0009,-0.0009,-0.0009)))),
vm((-1.0,-1.0,1.0), toriSDF(va(p, (-0.0009,-0.0009,0.0009))))),
va(vm((-1.0,1.0,-1.0), toriSDF(va(p, (-0.0009,0.0009,-0.0009)))),
vm((1.0,1.0,1.0), toriSDF(va(p, (0.0009,0.0009,0.0009))))))),
[va(O, vm(D, t))])[1]) AS torusN,
((o, d) -> if(d.3 < -eps AND o.3 > 0.0, -o.3 / d.3, 1e9)) AS planeT,
((o, d, maxd) -> arrayExists(b -> oboxT(o, d, b) < maxd, oboxes)
OR arrayExists(c -> rodT(o, d, c.1, c.2, c.3) < maxd, cyls)
OR arrayExists(rg -> ringT(o, d, rg) < maxd, rings)
OR arrayExists(sp -> sphT(o, d, sp.1, sp.2) < maxd, spheres)
OR (marchTorus(o, d) < maxd)) AS occluded,
((d) -> tupleMultiplyByNumber(skyColor, 0.35 + 0.65 * pow(greatest(1.0 - d.3, 0.0), 3.0))) AS sky,
((p) -> (toInt64(floor(p.1 / tile)) + toInt64(floor(p.2 / tile))) % 2) AS checker,
((a, b, c) -> (cityHash64(a, b, c) % 1048576) / 1048576.0) AS rnd,
((s, lpos) -> arrayMap(o -> arrayMap(d -> arrayMap(thr -> arrayMap(acc -> arrayMap(oboxHits -> arrayMap(cylHits -> arrayMap(ringHits -> arrayMap(sphHits -> arrayMap(tTorus -> arrayMap(tBox -> arrayMap(tCyl -> arrayMap(tRing -> arrayMap(tSph -> arrayMap(tLet -> arrayMap(tP -> arrayMap(pairA -> arrayMap(pairB -> arrayMap(cEnter -> arrayMap(cCav -> arrayMap(tC -> arrayMap(tM -> arrayMap(tHit -> arrayMap(hp -> arrayMap(letN -> arrayMap(sn -> arrayMap(isMirror -> arrayMap(isFloor -> arrayMap(isMiss -> arrayMap(nrm -> arrayMap(shOrig -> arrayMap(rdir -> arrayMap(ldir -> arrayMap(distL -> arrayMap(shadowed -> arrayMap(spec -> arrayMap(lambert -> arrayMap(fog -> arrayMap(floorCol -> (
if(isMirror, shOrig, o),
if(isMirror, rdir, d),
if(isMirror, thr * reflect_k, thr),
va(acc, va(if(isMirror, vm(specColor, thr * spec), (0.0, 0.0, 0.0)),
va(if(isFloor, vm(floorCol, thr), (0.0, 0.0, 0.0)),
if(isMiss, vm(sky(d), thr), (0.0, 0.0, 0.0))))),
if(isMirror, 1.0, 0.0)), [va(vm(vm(if(checker(hp) = 0, floorA, floorB), floorAmb + floorDiff * lambert), 1.0 - fog), vm(hazeColor, fog))])[1], [least(1.0, greatest(0.0, (tHit - fogStart) / fogRange))])[1], [greatest(vd(ldir, (0.0, 0.0, 1.0)), 0.0) * if(shadowed, 0.0, 1.0)])[1], [pow(greatest(vd(ldir, rdir), 0.0), shininess) * if(shadowed, 0.0, 1.0)])[1], [occluded(shOrig, ldir, distL - 0.05)])[1], [L2Norm(vs(lpos, hp))])[1], [vn(vs(lpos, hp))])[1], [vref(d, sn)])[1], [va(hp, vm(nrm, eps * 2.0))])[1], [if(isFloor, (0.0, 0.0, 1.0), sn)])[1], [(tM >= 1e8) AND (tP >= 1e8)])[1], [(tP <= tM) AND (tP < 1e8)])[1], [(tM < tP) AND (tM < 1e8)])[1], [if(tC < tLet, if(cEnter <= cCav, vn(vs(hp, csgA)), vn(vs(csgB, hp))), letN)])[1], [if(tTorus <= tBox AND tTorus <= tCyl AND tTorus <= tRing AND tTorus <= tSph, torusN(o, d, tTorus), if(tCyl <= tBox AND tCyl <= tRing AND tCyl <= tSph, rodN(o, d, cyls[indexOf(cylHits, tCyl)].1, cyls[indexOf(cylHits, tCyl)].2, cyls[indexOf(cylHits, tCyl)].3), if(tBox <= tRing AND tBox <= tSph, oboxN(hp, oboxes[indexOf(oboxHits, tBox)]), if(tRing <= tSph, ringN(o, d, rings[indexOf(ringHits, tRing)]), vn(vs(hp, spheres[indexOf(sphHits, tSph)].1))))))])[1], [va(o, vm(d, tHit))])[1], [least(tM, tP)])[1], [least(tLet, tC)])[1], [least(cEnter, cCav)])[1], [if(pairB.1 < 1e8 AND pairB.2 > eps AND pairA.1 <= pairB.2 AND pairB.2 <= pairA.2, pairB.2, 1e9)])[1], [if(pairA.1 > eps AND pairA.1 < 1e8 AND NOT (pairB.1 <= pairA.1 AND pairA.1 <= pairB.2), pairA.1, 1e9)])[1], [sphPair(o, d, csgB, csgBr)])[1], [sphPair(o, d, csgA, csgAr)])[1], [planeT(o, d)])[1], [least(least(tBox, tCyl), least(tRing, least(tSph, tTorus)))])[1], [arrayMin(sphHits)])[1], [arrayMin(ringHits)])[1], [arrayMin(cylHits)])[1], [arrayMin(oboxHits)])[1], [marchTorus(o, d)])[1], [arrayMap(sp -> sphT(o, d, sp.1, sp.2), spheres)])[1], [arrayMap(rg -> ringT(o, d, rg), rings)])[1], [arrayMap(c -> rodT(o, d, c.1, c.2, c.3), cyls)])[1], [arrayMap(b -> oboxT(o, d, b), oboxes)])[1], [s.4])[1], [s.3])[1], [s.2])[1], [s.1])[1]) AS oneBounce
SELECT
clamp(pow(greatest(avg(col.1), 0.0) * exposure, 1.0 / gamma), 0.0, 1.0) AS r,
clamp(pow(greatest(avg(col.2), 0.0) * exposure, 1.0 / gamma), 0.0, 1.0) AS g,
clamp(pow(greatest(avg(col.3), 0.0) * exposure, 1.0 / gamma), 0.0, 1.0) AS b,
pixel % W AS x,
intDiv(pixel, W) AS y
FROM (
SELECT pixel, va(st.4, if(st.5 > 0.5, vm(sky(st.2), st.3), (0.0,0.0,0.0))) AS col
FROM (
SELECT pixel,
arrayFold((s, i) -> if(s.5 > 0.5, oneBounce(s, lpos), s), range(maxDepth), st0) AS st
FROM (
SELECT
intDiv(number, {SAMPLES:UInt32}) AS pixel,
number % {SAMPLES:UInt32} AS sample,
va(lightPos, (lightR * (rnd(pixel, sample, 3) - 0.5),
lightR * (rnd(pixel, sample, 4) - 0.5),
lightR * (rnd(pixel, sample, 5) - 0.5))) AS lpos,
CAST((eye,
vn(va(camFwd, va(vm(camRight, (((pixel % W) + rnd(pixel, sample, 1)) / imgW * 2.0 - 1.0) * aspect * tanHalf),
vm(camUp, (1.0 - (intDiv(pixel, W) + rnd(pixel, sample, 2)) / imgH * 2.0) * tanHalf)))),
1.0, (0.0, 0.0, 0.0), 1.0) AS Tuple(Tuple(Float64,Float64,Float64), Tuple(Float64,Float64,Float64), Float64, Tuple(Float64,Float64,Float64), Float64)) AS st0
FROM numbers_mt(W * H * {SAMPLES:UInt32})
)
)
)
GROUP BY pixel
SETTINGS max_block_size = 2048
FORMAT PNG
github.com/ClickHouse/RayTracer
🇦🇺 Sydney, Aug 11 · 🇦🇺 Melbourne, Aug 13 · 🇳🇱 Amsterdam, Sep 1
🇺🇸 New York, Sep 10 · 🇮🇳 Bangalore, Sep 22 · 🇸🇬 Singapore, Sep 24
🇬🇧 London, Sep 30 · 🇩🇪 Munich, Oct 6
— 🇹🇭 Bangkok: OSS & Data Evening, Jul 23
— 🇮🇳 Mumbai: Lakes to Queries, Jul 25
— 🇸🇬 Singapore: BUILD Workshop, Jul 28
— 🇯🇵 Tokyo: Google Cloud Next, Jul 30
— 🇨🇴 Bogotá: AWS Summit, Jul 30
— 🇮🇩 Jakarta Meetup, Aug 5
— 🇺🇸 Seattle: AI Demo Night, Aug 6
— 🇨🇱 Santiago: Real-time Analytics Training, Aug 6
— 🇺🇸 San Francisco Meetup, Aug 11
— 🇦🇺 Sydney User Conference, Aug 11
— 🇦🇺 Melbourne User Conference, Aug 13
— 🇺🇸 New York: AI Demo Night, Aug 18
— A Quadrillion Rows across three Clouds: scaling LogHouse
— One driver, one format, every language: ADBC
— chDB as the Agent's Local Data Engine
— Replacing the HDB: ClickHouse for historical ticker data
— Introducing pg_re2, fast, RE2-powered regular expressions in Postgres
— How we made ClickStack 5x faster for ClickHouse observability
— ClickHouse on Docker Hardened Images
— How we scale PgBouncer in ClickHouse Managed Postgres
— @clickhouse/rowbinary: when your library is also a parser compiler