A Preview of DuckDB v2.0DuckDB v2.0 前瞻

Mark Raasveldt and Hannes Mühleisen
2026-08-17 | 16 min

TL;DR: DuckDB v2.0 is coming this fall. In this post, we preview its headline features: DuckDB as a server, triggers, the VARIANT type, asynchronous I/O, a new SQL parser, a new storage format, and much more.简而言之:DuckDB v2.0 将于今年秋季发布。在这篇文章中,我们将为您预览其核心功能:DuckDB 服务器模式、触发器、VARIANT 类型、异步 I/O、全新的 SQL 解析器、全新的存储格式等等。

DuckDB v2.0 will be named “Cyanoptera” after the cinnamon teal (Anas cyanoptera), a strikingly reddish-brown duck found in the western Americas.DuckDB v2.0 将被命名为“Cyanoptera”,取名自肉桂鸭(学名:Anas cyanoptera),这是一种分布于美洲西部、羽色呈迷人红褐色的鸭子。

A major version bump is not something we do lightly, and it is not just ceremony: v2.0 ships a new SQL parser, a new default storage format, a reworked C API, and a small number of carefully chosen breaking changes. But above all, it is a feature release, built from over 10,000 commits since we released v1.5 in March. Where last year was the year of the lakehouse, this release kicks off the year of DuckDB as a server. We previewed many of these features in the “State of the Duck” talk at DuckCon #7, if you prefer to watch instead of read.进行重大版本升级并非我们草率的决定,这也不仅仅是一种仪式:v2.0 带来了全新的 SQL 解析器、默认存储格式、重构的 C API,以及少量经过深思熟虑的破坏性变更。但最重要的是,这是一个功能性版本,自 3 月份发布 v1.5 以来,我们已经提交了超过 10,000 次代码。如果说去年是“湖仓一体”之年,那么本次发布将开启“DuckDB 服务器”之年。如果您更喜欢观看视频而非阅读,我们在 DuckCon #7 的“State of the Duck”演讲中预览了许多相关功能。

DuckDB is moving rather quickly, and we can only cover a small fraction of the changes here. Condensing all new features down to a shortlist is always a fight over what gets in, and yes, we know that what follows is technically a listicle (Ten Things Coming to DuckDB v2.0, Number Eight Will Shock You). We are not proud of the format, but it works, so here it is, starting with the SQL-level features and working down into the engine.DuckDB 的发展速度非常快,我们在此仅能涵盖其中一小部分变化。将所有新功能浓缩成一份简短列表,总是要在取舍之间进行博弈。是的,我们知道接下来的内容在技术上属于“列表体”(《DuckDB v2.0 即将到来的十件事,第八件让你震惊》)。我们并不以此格式为荣,但它确实有效,所以就这样吧,让我们从 SQL 层面功能开始,逐步深入到引擎内部。

1. DuckDB as a Server: Quack and CONNECT 1. DuckDB 服务器模式:Quack 与 CONNECT

DuckDB has been an in-process database since day one. But people have asked us – very persistently – for a client/server mode, and we have finally caved. The quack extension implements DuckDB's native protocol for talking to other DuckDBs. It was released as a preview shortly before DuckCon #7, graduates to stable in v2.0, and it is a big part of where DuckDB is headed: any DuckDB process can serve its databases over the network, and any other DuckDB can attach to it and route queries there using the new CONNECT statement. For example:从第一天起,DuckDB 就是一个进程内数据库。但人们一直非常执着地要求我们提供客户端/服务器模式,最终我们妥协了。quack 扩展实现了 DuckDB 用于与其他 DuckDB 通信的本地协议。它在 DuckCon #7 前夕作为预览版发布,在 v2.0 中正式转为稳定版。这是 DuckDB 未来发展的重要方向:任何 DuckDB 进程都可以通过网络提供数据库服务,而其他任何 DuckDB 都可以通过新的 CONNECT 语句连接到它并路由查询。例如:

DuckDB server DuckDB 服务器

CALL quack_serve(
    token = 'my_token'
);
quack:quack:

DuckDB client DuckDB 客户端

ATTACH 'quack:server.example.com'
    AS qk (TOKEN 'my_token');

CONNECT qk;
SELECT count(*) FROM events;
-- executes on the server,
-- results stream back
DISCONNECT;

CONNECT is the successor to the remote.query($$...$$) workaround we showed when Quack was first revealed – we looked at that syntax and said: no, this cannot be it. And CONNECT is not limited to Quack: it points your session at any remote database that supports it, and the new remote pushdown optimizer (#22914) ships SQL directly to PostgreSQL and MySQL instead of pulling tables over the wire:CONNECT 是我们最初展示 Quack 时所使用的 remote.query($$...$$) 变通方案的继任者——当时我们审视那种语法时觉得:不,这不应该是最终形态。CONNECT 不仅限于 Quack:它可以将您的会话指向任何支持它的远程数据库,新的远程下推优化器 (#22914) 会将 SQL 直接发送到 PostgreSQL 和 MySQL,而不是通过网络拉取表数据:

CONNECT 'postgres://localhost/mydb';
SELECT count(*) FROM orders; -- runs on the PostgreSQL server
DISCONNECT;

If you have worked with analytical systems in the past, you may assume that DuckDB cannot handle transactional workloads. But DuckDB has been built as a transactional, multi-connection database with full MVCC and transaction isolation since day one. Most users just never needed that in a single-user scenario. It turns out DuckDB handles transactions well: it's fast enough to compete with general-purpose databases like PostgreSQL on quite a few workloads, and the client/server pattern finally lets that machinery shine in multi-tenant, long-running deployments.如果您过去曾使用过分析系统,可能会认为 DuckDB 无法处理事务性工作负载。但从第一天起,DuckDB 就是作为一款具备完整 MVCC 和事务隔离功能的事务型多连接数据库而构建的。大多数用户在单用户场景下从未用到这些功能。事实证明,DuckDB 处理事务的能力很强:在相当多的工作负载下,它的速度足以与 PostgreSQL 等通用数据库竞争,而客户端/服务器模式终于让这一机制在多租户、长时间运行的部署中大放异彩。

Running DuckDB long-term also comes with new challenges, which is why v2.0 pushes on better metrics, logs, and observability (see, e.g., the metrics layer rework in #22799) that let you look at a DuckDB instance and see what it is actually doing. People even built standalone clients for the Quack protocol within weeks of the preview. We thought we were extending DuckDB to talk to other DuckDBs; the world said no, no, no, and built their own clients. Who would have thought.长期运行 DuckDB 也带来了新的挑战,这就是为什么 v2.0 在指标、日志和可观测性方面进行了加强(例如 #22799 中的指标层重构),让您可以查看 DuckDB 实例的实时状态。甚至在预览版发布后的几周内,人们就为 Quack 协议构建了独立的客户端。我们本以为只是在扩展 DuckDB 以便与其他 DuckDB 通信,但世界给了我们惊喜,大家直接构建了自己的客户端。谁能想到呢。

2. VARIANT Becomes a First-Class Citizen 2. VARIANT 成为一等公民

The VARIANT type shipped in DuckDB v1.5, and the way to think about it is JSON on steroids. Basically, imagine if JSON were fast. Like JSON, a VARIANT column can store differently-shaped data in every row. Unlike JSON, it is not a text format: DuckDB automatically detects the common structure hidden in your semi-structured data and “shreds” it, so it compresses well in storage and executes fast in queries, all without you ever declaring a schema. This makes VARIANT a natural fit for real-time log ingestion, where streams of JSON-ish records share structure but evolve over time.VARIANT 类型随 DuckDB v1.5 发布,您可以将其理解为“强力版 JSON”。基本上,想象一下如果 JSON 运行得很快会怎样。像 JSON 一样,VARIANT 列可以在每一行存储不同形状的数据。与 JSON 不同的是,它不是文本格式:DuckDB 会自动检测半结构化数据中隐藏的通用结构并对其进行“切片”(shredding),因此它在存储时压缩效果极佳,在查询时执行速度极快,且无需您声明任何模式。这使得 VARIANT 非常适合实时日志摄取,因为在这种场景下,JSON 类记录流共享结构但会随时间演变。

In v2.0, this pipeline works end to end: shredded execution straight from storage (#20912), extraction pushdown into scans (#22478), shredded VARIANT reading and writing for Parquet, and a family of variant_* functions:在 v2.0 中,这一流程实现了端到端打通:直接从存储进行切片执行 (#20912)、扫描时的提取下推 (#22478)、Parquet 的切片 VARIANT 读写,以及一系列 variant_* 函数:

CREATE TABLE events (payload VARIANT);
INSERT INTO events
VALUES ('{"user": {"id": 42, "tags": ["a", "b"]}}'::JSON::VARIANT);

SELECT variant_type(payload), variant_keys(payload)
FROM events;

SELECT *
FROM events
WHERE variant_contains(payload, {'user': {'id': 42}}::VARIANT);

Longer term, likely soon after v2.0 (but don't hold us to it), we plan to back the regular JSON type with VARIANT, so existing JSON workloads get all of these benefits without changing a single query.从长远来看,很可能在 v2.0 发布后不久(但请不要以此强求我们),我们计划让常规的 JSON 类型底层支持 VARIANT,这样现有的 JSON 工作负载无需更改任何查询即可获得所有这些好处。

3. Triggers 3. 触发器

Triggers have been a long-standing feature request, and DuckDB v2.0 delivers them in full: BEFORE and AFTER triggers, FOR EACH ROW and FOR EACH STATEMENT, transition tables via REFERENCING OLD/NEW TABLE, multiple triggers per event, RETURNING on triggered tables, and DROP TRIGGER.触发器是长期以来的功能需求,DuckDB v2.0 完整地实现了它们:BEFORE 和 AFTER 触发器、FOR EACH ROW 和 FOR EACH STATEMENT、通过 REFERENCING OLD/NEW TABLE 实现的转换表、每个事件支持多个触发器、触发器表上的 RETURNING 以及 DROP TRIGGER。

The classic use case is audit tables: something happens in the system, and a trigger records what changed. For example:经典的用例是审计表:系统中发生了某些事情,触发器会记录变更内容。例如:

CREATE TABLE target (id INTEGER, val INTEGER);
CREATE TABLE audit (id INTEGER, old_val INTEGER, new_val INTEGER);

CREATE TRIGGER trg_audit AFTER UPDATE ON target
REFERENCING OLD TABLE AS o NEW TABLE AS n
FOR EACH STATEMENT
    INSERT INTO audit
    SELECT n.id, o.val, n.val
    FROM o
    JOIN n ON o.id = n.id;

INSERT INTO target VALUES (1, 10), (2, 20);
UPDATE target SET val = val * 10 WHERE id <= 2;
SELECT * FROM audit;
id old_val new_val
1 10 100
2 20 200

Triggers fit naturally with long-running DuckDB services, and we are also planning to use them internally to build several upcoming features. They are fully exposed at the SQL level too, so you can build your own cool stuff with them.触发器与长时间运行的 DuckDB 服务非常契合,我们还计划在内部使用它们来构建几个即将推出的功能。它们在 SQL 层面也是完全开放的,因此您可以利用它们构建属于自己的炫酷功能。

4. SQL Dialect Additions 4. SQL 方言新增内容

As always, DuckDB's SQL dialect keeps growing. A few favorites from this release cycle:一如既往,DuckDB 的 SQL 方言在不断壮大。本发布周期中我们最喜欢的几个功能:

With NEAREST joins (#24137), top-k similarity search becomes a join clause, handy for vector and embedding workloads:通过 NEAREST 连接 (#24137),Top-K 相似度搜索现在成为了连接子句,这对于向量和嵌入工作负载非常方便:

SELECT q.user_id, t.product_id
FROM users q
    INNER JOIN products t APPROX NEAREST 2
    BY SIMILARITY array_cosine_similarity(q.embedding, t.embedding);

DML inside CTEs (#21634, #21997, #24217) lets you use INSERT, UPDATE, DELETE, and COPY as pipeline steps:CTE 内部的 DML (#21634, #21997, #24217) 允许您将 INSERT、UPDATE、DELETE 和 COPY 作为流水线步骤:

WITH moved AS MATERIALIZED (
    DELETE FROM staging RETURNING *
)
INSERT INTO archive SELECT * FROM moved;

Nested schemas (#23492, #24222) allow schemas within schemas:嵌套模式 (#23492, #24222) 允许在模式内创建模式:

CREATE SCHEMA finance;
CREATE SCHEMA finance.reports;
CREATE TABLE finance.reports.q3 (revenue DECIMAL);

The new variable syntax (#21194) lets you write $x anywhere an expression is allowed, no more getvariable(...) verbiage:新的变量语法 (#21194) 让您可以在任何允许表达式的地方编写 $x,无需再使用冗长的 getvariable(...):

SET VARIABLE threshold = 100;
SELECT * FROM orders WHERE amount > $threshold;

The JSON mutation functions json_set, json_insert, json_replace, and json_remove (#23786) finally let you modify JSON documents in place:JSON 修改函数 json_set、json_insert、json_replace 和 json_remove (#23786) 终于让您可以就地修改 JSON 文档:

SELECT json_set('{"a":1}', '$.b', '2');
json_set('{"a":1}', '$.b', '2')
{"a":1,"b":2}

And recursive CTEs with USING KEY aggregation (#19481) enable iterative algorithms in pure SQL, backed by the rewritten recursive CTE engine described below:此外,带有 USING KEY 聚合的递归 CTE (#19481) 实现了纯 SQL 中的迭代算法,并由下文所述的重写后的递归 CTE 引擎提供支持:

WITH RECURSIVE tbl(a, b) USING KEY (a, avg(b)) AS (
    SELECT 1, 5
    UNION
    SELECT a, b - 1 FROM tbl WHERE b > 0
)
TABLE tbl;
a b
1 2.5

There is more: SQL-standard FETCH FIRST 2 ROWS ONLY (#23533), OVERLAY() (#22456), UNNEST in GROUP BY (#23644), and well-defined MERGE / UPDATE ... FROM semantics for multi-matched rows (#24058).还有更多功能:SQL 标准的 FETCH FIRST 2 ROWS ONLY (#23533)、OVERLAY() (#22456)、GROUP BY 中的 UNNEST (#23644),以及针对多匹配行定义明确的 MERGE / UPDATE ... FROM 语义 (#24058)。

5. Asynchronous I/O 5. 异步 I/O

Interacting with object stores like S3 is central to the DuckDB experience: your data has to come from somewhere, and it often sits in object storage. DuckDB has long been able to read from object stores in parallel, but synchronous access placed a limit on how fast this could go. DuckDB v2.0 introduces asynchronous I/O throughout the engine. We described the design in detail in a dedicated blog post.与 S3 等对象存储交互是 DuckDB 体验的核心:您的数据必须来自某个地方,而它通常位于对象存储中。DuckDB 很早就能并行读取对象存储,但同步访问限制了其速度上限。DuckDB v2.0 在整个引擎中引入了异步 I/O。我们在专门的博客文章中详细描述了其设计。

Thanks to asynchronous access, the I/O layer now scales independently from the query processing layer, which means far more parallelism for remote reads and dramatically faster queries on network storage. Parquet support came first (#23662), with CSV (#23961) and DuckDB's own file format (#24654) following, along with asynchronous Parquet writes (#23283) and new MMAP and DIRECT_IO modes (#22988). Local storage benefits a little too, but network storage is where you will see the big gains.得益于异步访问,I/O 层现在可以独立于查询处理层进行扩展,这意味着远程读取的并行度更高,网络存储上的查询速度也大幅提升。Parquet 支持率先实现 (#23662),随后是 CSV (#23961) 和 DuckDB 自己的文件格式 (#24654),以及异步 Parquet 写入 (#23283) 和新的 MMAP 及 DIRECT_IO 模式 (#22988)。本地存储也会受益,但网络存储的性能提升将最为显著。

6. Faster Queries Across the Board 6. 全面提升查询速度

As with every release, a lot of work went into making your existing queries faster without you doing anything. To pick some highlights: partial aggregates are now pushed below joins (#22572) and redundant aggregations are reused (#24543), the recursive CTE engine has been rewritten (#22211), aggregations now spill to disk when they outgrow memory (#24499), and the Windows CLI got approximately 2.2× faster at multi-threaded result materialization (#24036).与每次发布一样,我们投入了大量工作来提升现有查询的性能,且无需您进行任何操作。重点包括:部分聚合现在被下推到连接操作下方 (#22572),冗余聚合被复用 (#24543),递归 CTE 引擎经过重写 (#22211),聚合在超出内存时现在会溢出到磁盘 (#24499),Windows CLI 在多线程结果物化方面的速度提升了约 2.2 倍 (#24036)。

How much faster can this get? Here is a microbenchmark you can run on a laptop: single-source reachability over a graph with one million edges, written as a plain recursive CTE.速度还能提升多少?这是一个您可以在笔记本电脑上运行的微基准测试:对一个包含一百万条边的图进行单源可达性分析,代码编写为普通的递归 CTE。

CREATE TABLE edges AS
    SELECT (range % 100_000)::INTEGER AS src,
           ((range * 13 + 7) % 100_000)::INTEGER AS dst
    FROM range(1_000_000);

WITH RECURSIVE reachable(node) AS (
    SELECT 0
    UNION
    SELECT dst FROM edges, reachable WHERE src = node
)
SELECT count(*) FROM reachable;
Version Run time
DuckDB v1.5.4 4.90 s
DuckDB v2.0 (preview) 0.12 s

As you can see, DuckDB v2.0 is about 40× faster (!) for the same recursive query.如您所见,对于同一个递归查询,DuckDB v2.0 的速度快了约 40 倍(!)

Row-group pruning has been massively expanded: min-max indexes (zone maps) and Parquet Bloom filters now skip data for structs, lists, decimals, UUIDs, IN filters, and even function predicates:行组剪枝(Row-group pruning)得到了大规模扩展:最小-最大索引(区域映射)和 Parquet 布隆过滤器现在可以跳过结构体、列表、十进制数、UUID、IN 过滤器甚至函数谓词的数据:

-- these now prune row groups instead of scanning them:
SELECT * FROM logs WHERE contains(message, 'ERROR');
SELECT * FROM t WHERE substr(code, 1, 3) = 'NL-';
SELECT * FROM 'data/*.parquet' WHERE id IN (1, 5, 9);

Query planning also becomes partition-aware (#22336). Lakehouse formats (DuckLake, Iceberg and plain Hive-partitioned Parquet on S3) are all partitioned, and exploiting that partitioning is often the difference between scanning a dataset and skipping most of it. In v2.0, the planner and optimizer take full advantage of existing partitioning, and partitioned writes have been reworked as well (#22225, #22620).查询规划也变得具有分区感知能力 (#22336)。湖仓格式(DuckLake、Iceberg 以及 S3 上普通的 Hive 分区 Parquet)都是分区的,利用这种分区往往是扫描整个数据集与跳过大部分数据之间的性能差异所在。在 v2.0 中,规划器和优化器充分利用了现有分区,分区写入也进行了重构 (#22225, #22620)。

7. Storage Format v2.0 7. 存储格式 v2.0

DuckDB v2.0 bumps the default storage format version to v2.0.0 (#22875).DuckDB v2.0 将默认存储格式版本提升至 v2.0.0 (#22875)。

Column metadata is now loaded lazily (#22333), so wide tables open faster too. The DICT_FSST string compression method is enabled by default (#23733), deletes are stored compactly (#24336), and the storage layer performs much stronger corruption validation on read. In short: databases with big indexes and wide tables open faster and use far less memory. The new storage format also allows checkpoint vacuuming to compact tables with ART indexes by incrementally remapping entries whose row IDs change, avoiding a full index rebuild (#23653).列元数据现在采用惰性加载 (#22333),因此宽表打开速度更快。DICT_FSST 字符串压缩方法默认启用 (#23733),删除操作存储更紧凑 (#24336),存储层在读取时执行更严格的损坏验证。简而言之:拥有大索引和宽表的数据库打开速度更快,内存占用更少。新的存储格式还允许检查点清理(checkpoint vacuuming)通过增量重映射行 ID 发生变化的条目来压缩带有 ART 索引的表,从而避免了全索引重建 (#23653)。

Later this year, ART indexes will be buffer-managed (#21458, #23605). This will allow ART index buffers to be evicted under memory pressure, and remove the restriction on the ART index fitting completely within memory.今年晚些时候,ART 索引将实现缓冲区管理 (#21458, #23605)。这将允许 ART 索引缓冲区在内存压力下被驱逐,并取消了 ART 索引必须完全放入内存的限制。

8. A Brand New SQL Parser 8. 全新的 SQL 解析器

DuckDB has famously always used a parser derived from PostgreSQL's. We have decided that enough is enough: v2.0 ships our own modern, extensible PEG-based parser (#22194), an idea we first explored in our 2024 post on runtime-extensible parsers. This change ties into the extension ecosystem: extensions can now hook into the grammar itself, so expect extensions that expose entirely new SQL syntax. It also brings better error messages with precise source locations, and the first dialect compatibility mode:众所周知,DuckDB 一直使用源自 PostgreSQL 的解析器。我们认为已经足够了:v2.0 采用了我们自己基于 PEG 的现代化、可扩展解析器 (#22194),这个想法我们最早在 2024 年关于运行时可扩展解析器的文章中探讨过。这一变化与扩展生态系统紧密相连:扩展现在可以挂载到语法本身,因此请期待那些能够引入全新 SQL 语法的扩展。它还带来了带有精确源码位置的更好错误消息,以及第一个方言兼容模式:

SET dialect_compatibility_mode = 'spark';

You should not actually notice anything from the parser swap as we designed it to be compatible with the old one. If you do notice, please file an issue.由于我们将其设计为与旧版本兼容,您实际上不应该感觉到解析器更换带来的任何影响。如果您注意到了,请提交 issue。

9. Timezones, Calendars, and Collations Without ICU 9. 时区、日历和无需 ICU 的排序规则

Timezone-aware timestamps, calendars, and collations in DuckDB have always been powered by the ICU library. ICU is a fine library, but we only ever used a small slice of it, while still carrying it around in every DuckDB distribution. In v2.0, the ICU library is gone entirely: the icu extension now implements timezones, calendars, and collations itself (#24463, #24403), with the timezone data built directly from the IANA database and compressed down to around 45 kB. Everything keeps working exactly as before:DuckDB 中的时区感知时间戳、日历和排序规则一直由 ICU 库提供支持。ICU 是一个优秀的库,但我们只使用了其中的一小部分,却在每个 DuckDB 发行版中都携带了它。在 v2.0 中,ICU 库被完全移除:icu 扩展现在自行实现了时区、日历和排序规则 (#24463, #24403),时区数据直接从 IANA 数据库构建并压缩至约 45 kB。一切功能保持完全不变:

SELECT '2026-08-14 12:00:00'::TIMESTAMPTZ AT TIME ZONE 'Europe/Paris';
SELECT * FROM names ORDER BY name COLLATE de;

Besides being much smaller and easier to keep up to date, the new implementation is also simply faster. Here's a quick microbenchmark on a MacBook that converts 25 million timestamps to a timezone and filters 5 million strings with a German collation:除了体积更小、更易于更新外,新的实现速度也更快。这是 MacBook 上的一项快速微基准测试,将 2500 万个时间戳转换为时区并使用德语排序规则过滤 500 万个字符串:

Query v1.5.4 (ICU) v2.0 (native) Speedup
ts AT TIME ZONE 'Europe/Paris', 25 M rows 0.24 s 0.11 s 2.2×
Filter with COLLATE de, 5 M rows 0.15 s 0.06 s 2.6×

10. Write Extensions Once, Host Them Yourself 10. 编写一次扩展,自主托管

Extensions are one of the best things about DuckDB, but today, most of them, including our own, build against the unstable C++ API. Extension authors are required to rebuild and publish their extensions for every DuckDB release, even if the extension itself does not change. This is often a non-trivial operation for the extension author and a non-trivial coordination effort for the DuckDB team. Not rebuilding an extension means it can't be installed on the latest version of DuckDB.扩展是 DuckDB 最好的特性之一,但目前大多数扩展(包括我们自己的)都是针对不稳定的 C++ API 构建的。扩展作者必须为每个 DuckDB 版本重建并发布其扩展,即使扩展本身没有变化。对于扩展作者来说,这通常是一项繁琐的操作,对于 DuckDB 团队来说也是不小的协调工作。如果不重建扩展,它就无法在最新版本的 DuckDB 上安装。

DuckDB v2.0 will ship with a revamped C API (see #24702 for part 1). The API will have a versioned specification expressed in YAML that uses an also versioned specification schema, and tooling for code generation (#24135). The schema lets us tag every symbol with its lifecycle and stability guarantees (#24435). A large part of the API will be marked stable and frozen, providing a stable ABI across DuckDB versions.DuckDB v2.0 将附带一个重构的 C API(第 1 部分见 #24702)。该 API 将拥有一个用 YAML 表达的版本化规范,使用同样版本化的规范模式,并提供代码生成工具 (#24135)。该模式允许我们标记每个符号的生命周期和稳定性保证 (#24435)。API 的很大一部分将被标记为稳定且冻结,从而在不同 DuckDB 版本之间提供稳定的 ABI。

The API itself improves over the pre-2.0 C API in a number of ways. It provides coherent error handling and a unified set of conventions around naming and ownership. It covers a much larger feature surface, including scalar, aggregate, table, cast, and copy functions with named parameters and varargs, parsing and inspecting SQL statements, prepared statements, replacement scans, custom filesystems, direct access to vector buffers, values and types, and more. It supports streaming query result consumption. With this API we want to provide the primitives to access DuckDB's most powerful features.该 API 本身在多个方面优于 2.0 之前的 C API。它提供了连贯的错误处理以及围绕命名和所有权的统一约定。它涵盖了更大的功能面,包括带有命名参数和可变参数的标量、聚合、表、转换和复制函数,解析和检查 SQL 语句、预编译语句、替换扫描、自定义文件系统、直接访问向量缓冲区、值和类型等。它支持流式查询结果消费。通过这个 API,我们希望提供访问 DuckDB 最强大功能的基元。

You won't need to program against the C API directly, though. We also provide a C++ API on top of it. It is a thin layer that compiles into your extension and talks only to the stable C ABI, so the binary you ship stays independent of DuckDB versions. It gives you a convenient, typed, well-documented way to access DuckDB's API. We're also working on bindings that provide the same for extension writers using Rust.不过,您不需要直接针对 C API 进行编程。我们还在其之上提供了一个 C++ API。它是一个编译到您扩展中的薄层,仅与稳定的 C ABI 通信,因此您发布的二进制文件与 DuckDB 版本无关。它为您提供了一种便捷、类型安全且文档完善的方式来访问 DuckDB 的 API。我们也在致力于为使用 Rust 的扩展开发者提供相同的绑定。

So what will an extension using the C++ API look like? Here is a complete example, though the details may still change: a single file that registers a vectorized scalar function.那么使用 C++ API 的扩展会是什么样子呢?这是一个完整的示例,尽管细节可能会有变化:一个注册向量化标量函数的文件。

#include "duckdb_cpp.hpp"

using namespace duckdb_api;

DUCKDB_CPP_EXTENSION_ENTRYPOINT(extension) {
    // add_numbers(a BIGINT, b BIGINT DEFAULT 2): adds two BIGINTs, one vector at a time
    ScalarFunction function;
    function.SetName("add_numbers")
        .SetSignature(FunctionSignature::Create()
                        .AddParameter("a", LogicalType::BIGINT())
                        .AddParameterDefault("b", LogicalType::BIGINT(), Value::Bigint(2))
                        .SetReturnType(LogicalType::BIGINT()))
        .SetExecCallback([](ScalarFunction::ExecInput &input) {
            auto chunk = input.GetInputChunk();
            auto a = chunk.GetVector(0).GetView();
            auto b = chunk.GetVector(1).GetView();
            auto out = input.GetResultVector().GetDataMutable<int64_t>();
            for (idx_t i = 0; i < chunk.GetRowCount(); i++) {
                out[i] = a.Data<int64_t>()[a.SelAt(i)] + b.Data<int64_t>()[b.SelAt(i)];
            }
        })
        .Register(extension);
}

You can use it as follows:您可以按如下方式使用它:

LOAD add_numbers;
SELECT add_numbers(40, 2);           -- 42
SELECT add_numbers(40);              -- 42: b falls back to its default
SELECT add_numbers(b := 2, a := 40); -- 42: named arguments work too
SELECT add_numbers(40, NULL);        -- NULL, without the function doing anything

You do not need re-target or rebuild it every time a new DuckDB version comes out. And nowadays, with all the AI tooling around, building an extension has never been easier.您不需要在每次发布新 DuckDB 版本时重新定位或重建它。而且如今,随着所有 AI 工具的出现,构建扩展从未如此简单。

So you have written your extension. But how should you distribute it? Until now, DuckDB could only install extensions from the built-in repositories (core, core_nightly, community, …). In v2.0, you will be able to register your own trusted repositories (#24777, currently work-in-progress), so an organization can host and sign its own extensions and have them install and load just like the built-in ones:那么您已经编写了扩展,但应该如何分发它呢?到目前为止,DuckDB 只能从内置仓库(core, core_nightly, community 等)安装扩展。在 v2.0 中,您将能够注册自己的受信任仓库(#24777,目前正在开发中),这样组织就可以托管和签名自己的扩展,并像内置扩展一样安装和加载它们:

SET allow_extension_repositories = 'allowed';
CREATE EXTENSION REPOSITORY my_repo FROM 'https://extensions.example.org';
INSTALL my_ext FROM my_repo;
LOAD my_repo/my_ext;

A repository is a name, a URL prefix, and one or more RSA public keys that are trusted to sign the extensions served from it. The prefix can point at anything DuckDB can read: a local path, https, s3, you name it. At CREATE time, DuckDB fetches the repository's public keys and pins them into the repository definition, printing each key's SHA-256 fingerprint so you can compare it against one published out of band. If you would rather not trust the network at all, you can pass the key directly:仓库包含名称、URL 前缀以及一个或多个受信任的 RSA 公钥,用于对从中提供的扩展进行签名。前缀可以指向 DuckDB 可以读取的任何内容:本地路径、https、s3 等。在 CREATE 时,DuckDB 会获取仓库的公钥并将其固定在仓库定义中,打印出每个密钥的 SHA-256 指纹,以便您与带外发布的信息进行比对。如果您根本不想信任网络,可以直接传递密钥:

CREATE EXTENSION REPOSITORY my_repo FROM 's3://my-bucket/extensions'
    USING PUBLIC KEY '-----BEGIN PUBLIC KEY----- ...';

Pinned repositories survive restarts, support key rotation by trusting multiple keys, and can be audited at any time through the duckdb_extension_repositories() table function, or removed again with DROP EXTENSION REPOSITORY. Together with the stable C API, the extension story rounds out nicely: write your extension once, sign it, host it wherever you like, and INSTALL it anywhere.固定的仓库在重启后依然有效,支持通过信任多个密钥进行密钥轮换,并且可以随时通过 duckdb_extension_repositories() 表函数进行审计,或者通过 DROP EXTENSION REPOSITORY 删除。结合稳定的 C API,扩展体验变得非常完善:编写一次扩展,签名,随处托管,随处 INSTALL。

Bonus: DuckDB Foundation – Advisory Board 彩蛋:DuckDB 基金会——咨询委员会

Starting this fall, we will add a stakeholder advisory board to the DuckDB Foundation. The advisory board will provide input on the development roadmap of DuckDB, DuckLake, and Quack. This allows key stakeholders to have a say in the projects' direction.从今年秋季开始,我们将为 DuckDB 基金会增加一个利益相关者咨询委员会。该咨询委员会将为 DuckDB、DuckLake 和 Quack 的开发路线图提供意见。这使得关键利益相关者能够在项目方向上拥有发言权。

Final Thoughts 结语

These are only a few highlights, and this post is only a preview. Some details may still shift before the release this fall, and there are many more features and improvements that we could not cover here. DuckDB v2.0 will also come with a small set of breaking changes, including the new default storage format and the completed lambda syntax transition, which we will cover in detail in the release announcement.以上仅是部分亮点,这篇文章也只是一个预览。在今年秋季正式发布之前,一些细节可能仍会调整,还有许多我们无法在此涵盖的功能和改进。DuckDB v2.0 还将带来少量破坏性变更,包括新的默认存储格式和已完成的 lambda 语法转换,我们将在发布公告中详细说明。

There have been more than 10,000 commits by many contributors since we released v1.5. We would like to thank our community for the detailed issue reports, feedback, and contributions that shaped this release. If you want a taste before the fall, the preview builds have most of these features today, and if something breaks, you know where the issue tracker is.自 v1.5 发布以来,许多贡献者提交了超过 10,000 次代码。我们要感谢社区提供的详细问题报告、反馈和贡献,正是这些塑造了本次发布。如果您想在秋季之前先睹为快,预览版构建目前已经包含了大部分功能,如果出现问题,您知道在哪里提交 issue。