Time for another update. Since the last post the project moved to Mojo 1.0 and DuckDB 1.5.5, gained prepared statements, a more mature extension story, and an experimental GPU acceleration path based on MAX.
Prepared statements and a more Python-like API
Parameterized queries: bind positionally (? / $1) or by name ($name), and Optional[T] binds SQL NULL for None. executemany prepares once and re-binds per row, and con.prepare(...) returns a statement you can re-bind and re-run.
The rest of the client API also aligns more closely with DuckDB’s Python API: DB-API-style fetchone[T]/fetchmany[T] on results, read_csv and friends, a module-level duckdb.sql, and a lazy, composable Relation API that mirrors DuckDB’s Python relational API (con.sql(...).filter(...).aggregate(...) only runs at a terminal like show or get[T]).
DuckDB 1.5.5
Updating DuckDB is now simpler. The Mojo FFI layer is auto-generated from the same declarative JSON descriptors that DuckDB uses to generate its duckdb.h header (the C API function definitions plus the stable/unstable extension API descriptors).
The typed API gains the new VARIANT and GEOMETRY types (Map support landed in the typed API and the appender since the last update).
Mojo 1.0
The update to Mojo 1.0 brought type-level and lifecycle improvements that the typed API benefits from, and much better FFI. The abi("C") function effect fixed struct-by-value passing, so the C shim the project originally needed is removed, and the generated FFI wrappers now track pointer origins instead of erasing them.
Lifecycle improvements: Data access on chunks and vectors is parametric over origin and mutability (following the stdlib’s List.unsafe_ptr pattern), so a pointer into a Chunk can no longer outlive it, that is a compile error now, and writing requires a mutable binding. This means no every unsafe_mut_cast[True]() in the codebase. For UDF authors the visible change is that callbacks now take mut output: Chunk.
Extensions written in Mojo
The extension development path is now more than a POC. An extension is a Mojo init function that receives a Connection and registers your functions, plus a small C entry point:
fn add_numbers(a: Int64, b: Int64) -> Int64:
return a + b
fn init(conn: Connection[ApiLevel.EXT_STABLE]) raises:
ScalarFunction.from_function[
"mojo_add", DType.int64, DType.int64, DType.int64, add_numbers
](conn)
@export("my_ext_init_c_api")
fn my_ext_init_c_api(
info: duckdb_extension_info,
access: UnsafePointer[duckdb_extension_access, MutExternalOrigin],
) abi("C") -> Bool:
return Extension.run[init](info, access)
Built as a shared library, it loads with the usual LOAD (with allow_unsigned_extensions for local builds). DuckDB’s Extension C API is split into a stable and an unstable level. In this exampleExtension.run targets the stable struct, which is append-only, so a compiled extension stays forward-compatible with future DuckDB releases of the same API major version, but you can also target unstable or (with shims), the C++ API which allows you to access more parts of DuckDB at the cost of being tied to a single DuckDB version.
This is what the accelerators below are built on: they are themselves extensions written in Mojo.
Experimental GPU acceleration
In addition to the existing (and improved) SIMD support, DuckDB operators can now be accelerated on the GPU. The compute kernels are written in Mojo on top of MAX (max.gpu, max.algorithm.parallelize) and hooked in via a DuckDB OptimizerExtension. Supported plans, like aggregation over filter/join and vector-search top-k, route to the GPU transparently, so unchanged SQL just gets faster, and EXPLAIN shows the GPU operator. Anything unsupported, or any GPU runtime error, falls back to stock DuckDB with decimal-exact results. Tested on NVIDIA and Apple GPUs.
The SIMD path got a similar extension: it rewrites builtins in place (sqrt/sin/cos/ln/exp, sum/avg/min/max, vector distances, and a fused one-pass kernel for sum/avg of transcendentals), so existing queries speed up without renaming functions. It links the kernels straight in, so its .so is self-contained with no Mojo runtime dependency.
Conda packaging
The bindings are now a conda package (duckdb-mojo), with the SIMD kernels precompiled in. Submitting it to the modular-community channel is the next step.