├── .clang-format ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── build.zig ├── build.zig.zon ├── flake.lock ├── flake.nix ├── src ├── bridge.cpp ├── include │ └── bridge.h └── root.zig └── templates └── multi └── flake.nix /.clang-format: -------------------------------------------------------------------------------- 1 | ColumnLimit: 110 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/* 2 | zig-out/* 3 | .clangd 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Alex Kwiatkowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test build 2 | 3 | .PHONY: test 4 | test: 5 | zig build test 6 | test.summary: 7 | zig build test --summary all 8 | 9 | build: build.fast 10 | build.fast: 11 | zig build -Doptimize=ReleaseFast 12 | build.small: 13 | zig build -Doptimize=ReleaseSmall 14 | build.safe: 15 | zig build -Doptimize=ReleaseSafe 16 | 17 | clean: 18 | rm -rf zig-* 19 | 20 | run: 21 | duckdb -unsigned 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # duckdb-extension-template-zig 2 | 3 | A Zig & Nix toolkit template for building extensions against multiple versions of DuckDB 4 | using Zig, C or C++. 5 | 6 | ## Usage 7 | 8 | ```shell 9 | > nix develop -c $SHELL 10 | > duckdb -unsigned 11 | D LOAD 'zig-out/lib/quack.duckdb_extension'; 12 | D FROM duckdb_extensions(); 13 | ┌──────────────────┬─────────┬───────────┬──────────────┬────────────────────────────────────────────────────────────────────────────────────┬───────────────────┐ 14 | │ extension_name │ loaded │ installed │ install_path │ description │ aliases │ 15 | │ varchar │ boolean │ boolean │ varchar │ varchar │ varchar[] │ 16 | ├──────────────────┼─────────┼───────────┼──────────────┼────────────────────────────────────────────────────────────────────────────────────┼───────────────────┤ 17 | │ arrow │ false │ false │ │ A zero-copy data integration between Apache Arrow and DuckDB │ [] │ 18 | ... 19 | │ quack │ true │ │ │ │ [] │ 20 | ... 21 | │ visualizer │ true │ │ │ Creates an HTML-based visualization of the query plan │ [] │ 22 | ├──────────────────┴─────────┴───────────┴──────────────┴────────────────────────────────────────────────────────────────────────────────────┴───────────────────┤ 23 | │ 24 rows 6 columns │ 24 | └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 25 | D FROM duckdb_extensions(); 26 | > WHERE function_name ILIKE '%quack%'; 27 | ┌───────────────┬─────────────┬───────────────┬───────────────┬─────────────┬─────────────┬───┬─────────┬──────────────────┬──────────────────┬──────────┬──────────────┬─────────┐ 28 | │ database_name │ schema_name │ function_name │ function_type │ description │ return_type │ … │ varargs │ macro_definition │ has_side_effects │ internal │ function_oid │ example │ 29 | │ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │ │ varchar │ varchar │ boolean │ boolean │ int64 │ varchar │ 30 | ├───────────────┼─────────────┼───────────────┼───────────────┼─────────────┼─────────────┼───┼─────────┼──────────────────┼──────────────────┼──────────┼──────────────┼─────────┤ 31 | │ system │ main │ quack │ scalar │ │ VARCHAR │ … │ │ │ false │ true │ 1473 │ │ 32 | ├───────────────┴─────────────┴───────────────┴───────────────┴─────────────┴─────────────┴───┴─────────┴──────────────────┴──────────────────┴──────────┴──────────────┴─────────┤ 33 | │ 1 rows 14 columns (12 shown) │ 34 | └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ 35 | D SELECT quack('howdy'); 36 | ┌────────────────┐ 37 | │ quack('howdy') │ 38 | │ varchar │ 39 | ├────────────────┤ 40 | │ Quack howdy 🐥 │ 41 | └────────────────┘ 42 | ``` 43 | 44 | ## How it Works 45 | 46 | DuckDB is a fast in-process analytical database written in C++ that can be extended by 47 | creating and loading a dynamically linked library using the [extension API](https://duckdb.org/docs/extensions/overview.html). 48 | Typically extensions are written in C++ using the officially supported [extension template](https://github.com/duckdb/extension-template). 49 | 50 | But you're one of the cool kids and want to write your extension in Zig! Fortunately the [Zig build system](https://ziglang.org/learn/build-system/) 51 | ships with a Zig, C & C++ compiler. 52 | 53 | ### 1. Create a project directory initialized with the multi flake template 54 | 55 | The Nix environment generated by the `flake.nix` [template](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/templates/multi/flake.nix) 56 | provides a self contained Linux & MacOS development toolchain: 57 | 58 | - Clang (16.0.6) 59 | - libcxx headers (16.0.6) 60 | - Zig `master` (0.12.0-dev.3247+26e895e3d) 61 | - Multiple `duckdb` CLI & `libduckdb` versions linked to the same versions of `libc` & `libcxx` as the Zig compiler (v0.10.0, v0.9.2 & main) 62 | 63 | ```shell 64 | > mkdir myextension && cd myextension 65 | > nix flake init -t github:rupurt/duckdb-extension-template-zig#multi 66 | > nix develop -c $SHELL 67 | ``` 68 | 69 | ### 2. Implement 2 extension loader functions 70 | 71 | When a DuckDB extension is loaded via `LOAD 'myextension.duckdb_extension';` it requires [2 symbols](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/src/root.zig#L7C1-L13C2) 72 | to be defined (`myextension_version` & `myextension_init`). The value returned from `*_init` must 73 | match the version of DuckDB loading the extension. 74 | 75 | We create a [simple header file](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/src/include/bridge.h) to 76 | expose these 2 symbols in our built extension. 77 | 78 | ### 3. Create a C++ bridge that calls `DuckDB::ExtensionUtil` 79 | 80 | The extension utils helper plugs into DuckDB internals such as: 81 | 82 | - scalar functions 83 | - table functions 84 | - custom catalogs 85 | - much more... 86 | 87 | The example in this repository [registers a simple scalar function](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/src/bridge.cpp#L26) called `quack` 88 | 89 | ### 4. Configure the Zig build system and compile the extension 90 | 91 | The Zig build system is configured in [build.zig](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/build.zig). 92 | 93 | - We'll need to add a [shared library](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/build.zig#L12) exposing 94 | the DuckDB extension hooks defined in `root.zig`. 95 | - Add the [include](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/build.zig#L16) path for the 96 | [C header file](https://github.com/rupurt/duckdb-extension-template-zig/tree/main/src/include) exposing these hooks. 97 | - Don't forget the [C++ bridge](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/build.zig#L19) 98 | - By convention DuckDB extensions use the file suffix `.duckdb_extension`. Zig writes the dynamic library using 99 | the format `libmyextension.[so|dylib|dll]`. Add a custom install step to use the DuckDB naming convention 100 | for the [extension filename](https://github.com/rupurt/duckdb-extension-template-zig/blob/main/build.zig#L29). 101 | 102 | ## Limitations 103 | 104 | Currently this template can only build extensions using versions of `duckdb` provided by the [duckdb-nix](https://github.com/rupurt/duckdb-nix) 105 | flake. The derivation built by the flake includes header files for `duckdb` [third_party](https://github.com/duckdb/duckdb/tree/main/third_party) 106 | dependencies. 107 | 108 | I have opened a [Github issue](https://github.com/NixOS/nixpkgs/issues/292855) to include those libraries 109 | in the `nixpkgs` derivation. 110 | 111 | ## Development 112 | 113 | This repository assumes you have Nix [installed](https://determinate.systems/posts/determinate-nix-installer) 114 | 115 | ```shell 116 | > nix develop -c $SHELL 117 | > nix develop .#v0-10-0 -c $SHELL 118 | > nix develop .#v0-9-2 -c $SHELL 119 | > nix develop .#main -c $SHELL 120 | ``` 121 | 122 | ```shell 123 | > make 124 | ``` 125 | 126 | Run the Zig test suite 127 | 128 | ```shell 129 | > make test 130 | ``` 131 | 132 | Delete artifacts from previous builds 133 | 134 | ```shell 135 | > make clean 136 | ``` 137 | 138 | Build extension binary with Zig 139 | 140 | ```shell 141 | > make build 142 | ``` 143 | 144 | Run `duckdb` cli allowing `-unsigned` extensions 145 | 146 | ```shell 147 | > make run 148 | ``` 149 | 150 | ## License 151 | 152 | `duckdb-extension-template-zig` is released under the [MIT license](./LICENSE) 153 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) !void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 8 | const allocator = gpa.allocator(); 9 | defer _ = gpa.deinit(); 10 | 11 | const duckdb_third_party_path = std.os.getenv("DUCKDB_THIRD_PARTY_PATH"); 12 | const re2_include_path = try std.fmt.allocPrint(allocator, "{s}/re2", .{duckdb_third_party_path.?}); 13 | defer allocator.free(re2_include_path); 14 | 15 | // ---------------------------- 16 | // Library 17 | // ---------------------------- 18 | const lib = b.addSharedLibrary(.{ 19 | .name = "quack", 20 | .root_source_file = .{ .path = "src/root.zig" }, 21 | .target = target, 22 | .optimize = optimize, 23 | }); 24 | // DuckDB third_party headers from Nix derivation 25 | lib.addIncludePath(.{ .path = re2_include_path }); 26 | // Allow Zig to find and parse the bridge C ABI 27 | lib.addIncludePath(.{ .path = "src/include" }); 28 | // Allow Zig to compile the C++ bridge 29 | lib.addCSourceFiles(.{ 30 | .files = &.{ 31 | "src/bridge.cpp", 32 | }, 33 | }); 34 | // We can link against libc & libc++ provided in the Nix build environment 35 | lib.linkLibC(); 36 | // Footguns linking libcxx with Zig 37 | // https://github.com/ziglang/zig/blob/e1ca6946bee3acf9cbdf6e5ea30fa2d55304365d/build.zig#L369 38 | lib.linkSystemLibrary("c++"); 39 | // Link against the version of libduckdb built by the Nix derivation 40 | lib.linkSystemLibrary("duckdb"); 41 | 42 | // Use the DuckDB filename convention `myextension.duckdb_extension` 43 | const install_lib = b.addInstallArtifact( 44 | lib, 45 | .{ .dest_sub_path = "quack.duckdb_extension" }, 46 | ); 47 | b.getInstallStep().dependOn(&install_lib.step); 48 | 49 | // ---------------------------- 50 | // Tests 51 | // ---------------------------- 52 | const lib_unit_tests = b.addTest(.{ 53 | .root_source_file = .{ .path = "src/root.zig" }, 54 | .target = target, 55 | .optimize = optimize, 56 | }); 57 | lib_unit_tests.addIncludePath(.{ .path = re2_include_path }); 58 | lib_unit_tests.addIncludePath(.{ .path = "src/include" }); 59 | lib_unit_tests.addCSourceFiles(.{ 60 | .files = &.{ 61 | "src/bridge.cpp", 62 | }, 63 | }); 64 | lib_unit_tests.linkLibC(); 65 | lib_unit_tests.linkSystemLibrary("c++"); 66 | lib_unit_tests.linkSystemLibrary("duckdb"); 67 | const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); 68 | 69 | const test_step = b.step("test", "Run unit tests"); 70 | test_step.dependOn(&run_lib_unit_tests.step); 71 | } 72 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "duckdb-extension-template-zig", 3 | // This is a [Semantic Version](https://semver.org/). 4 | // In a future version of Zig it will be used for package deduplication. 5 | .version = "0.0.0", 6 | 7 | // This field is optional. 8 | // This is currently advisory only; Zig does not yet do anything 9 | // with this value. 10 | //.minimum_zig_version = "0.12.0", 11 | 12 | // This field is optional. 13 | // Each dependency must either provide a `url` and `hash`, or a `path`. 14 | // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. 15 | // Once all dependencies are fetched, `zig build` no longer requires 16 | // internet connectivity. 17 | .dependencies = .{ 18 | // See `zig fetch --save ` for a command-line interface for adding dependencies. 19 | //.example = .{ 20 | // // When updating this field to a new URL, be sure to delete the corresponding 21 | // // `hash`, otherwise you are communicating that you expect to find the old hash at 22 | // // the new URL. 23 | // .url = "https://example.com/foo.tar.gz", 24 | // 25 | // // This is computed from the file contents of the directory of files that is 26 | // // obtained after fetching `url` and applying the inclusion rules given by 27 | // // `paths`. 28 | // // 29 | // // This field is the source of truth; packages do not come from a `url`; they 30 | // // come from a `hash`. `url` is just one of many possible mirrors for how to 31 | // // obtain a package matching this `hash`. 32 | // // 33 | // // Uses the [multihash](https://multiformats.io/multihash/) format. 34 | // .hash = "...", 35 | // 36 | // // When this is provided, the package is found in a directory relative to the 37 | // // build root. In this case the package's hash is irrelevant and therefore not 38 | // // computed. This field and `url` are mutually exclusive. 39 | // .path = "foo", 40 | //}, 41 | }, 42 | 43 | // Specifies the set of files and directories that are included in this package. 44 | // Only files and directories listed here are included in the `hash` that 45 | // is computed for this package. 46 | // Paths are relative to the build root. Use the empty string (`""`) to refer to 47 | // the build root itself. 48 | // A directory listed here means that all files within, recursively, are included. 49 | .paths = .{ 50 | // This makes *all* files, recursively, included in this package. It is generally 51 | // better to explicitly list the files and directories instead, to insure that 52 | // fetching from tarballs, file system paths, and version control all result 53 | // in the same contents hash. 54 | "", 55 | // For example... 56 | //"build.zig", 57 | //"build.zig.zon", 58 | //"src", 59 | //"LICENSE", 60 | //"README.md", 61 | }, 62 | } 63 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "duckdb-nix": { 4 | "inputs": { 5 | "flake-utils": "flake-utils", 6 | "nixpkgs": [ 7 | "nixpkgs" 8 | ] 9 | }, 10 | "locked": { 11 | "lastModified": 1710067096, 12 | "narHash": "sha256-0MajsrkoN0Kpm75nnWyOKtNlmAoA/tWFerV51r4Q6nk=", 13 | "owner": "rupurt", 14 | "repo": "duckdb-nix", 15 | "rev": "bcd237aa269a745299035eade7b344d4d75f9afa", 16 | "type": "github" 17 | }, 18 | "original": { 19 | "owner": "rupurt", 20 | "repo": "duckdb-nix", 21 | "type": "github" 22 | } 23 | }, 24 | "flake-compat": { 25 | "flake": false, 26 | "locked": { 27 | "lastModified": 1673956053, 28 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 29 | "owner": "edolstra", 30 | "repo": "flake-compat", 31 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "edolstra", 36 | "repo": "flake-compat", 37 | "type": "github" 38 | } 39 | }, 40 | "flake-utils": { 41 | "inputs": { 42 | "systems": "systems" 43 | }, 44 | "locked": { 45 | "lastModified": 1709126324, 46 | "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", 47 | "owner": "numtide", 48 | "repo": "flake-utils", 49 | "rev": "d465f4819400de7c8d874d50b982301f28a84605", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "numtide", 54 | "repo": "flake-utils", 55 | "type": "github" 56 | } 57 | }, 58 | "flake-utils_2": { 59 | "inputs": { 60 | "systems": "systems_2" 61 | }, 62 | "locked": { 63 | "lastModified": 1710146030, 64 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 65 | "owner": "numtide", 66 | "repo": "flake-utils", 67 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 68 | "type": "github" 69 | }, 70 | "original": { 71 | "owner": "numtide", 72 | "repo": "flake-utils", 73 | "type": "github" 74 | } 75 | }, 76 | "flake-utils_3": { 77 | "locked": { 78 | "lastModified": 1659877975, 79 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", 80 | "owner": "numtide", 81 | "repo": "flake-utils", 82 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", 83 | "type": "github" 84 | }, 85 | "original": { 86 | "owner": "numtide", 87 | "repo": "flake-utils", 88 | "type": "github" 89 | } 90 | }, 91 | "nixpkgs": { 92 | "locked": { 93 | "lastModified": 1710252211, 94 | "narHash": "sha256-hQChQpB4LDBaSrNlD6DPLhU9T+R6oyxMCg2V+S7Y1jg=", 95 | "owner": "nixos", 96 | "repo": "nixpkgs", 97 | "rev": "7eeacecff44e05a9fd61b9e03836b66ecde8a525", 98 | "type": "github" 99 | }, 100 | "original": { 101 | "owner": "nixos", 102 | "ref": "nixpkgs-unstable", 103 | "repo": "nixpkgs", 104 | "type": "github" 105 | } 106 | }, 107 | "root": { 108 | "inputs": { 109 | "duckdb-nix": "duckdb-nix", 110 | "flake-utils": "flake-utils_2", 111 | "nixpkgs": "nixpkgs", 112 | "zig-overlay": "zig-overlay" 113 | } 114 | }, 115 | "systems": { 116 | "locked": { 117 | "lastModified": 1681028828, 118 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 119 | "owner": "nix-systems", 120 | "repo": "default", 121 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 122 | "type": "github" 123 | }, 124 | "original": { 125 | "owner": "nix-systems", 126 | "repo": "default", 127 | "type": "github" 128 | } 129 | }, 130 | "systems_2": { 131 | "locked": { 132 | "lastModified": 1681028828, 133 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 134 | "owner": "nix-systems", 135 | "repo": "default", 136 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 137 | "type": "github" 138 | }, 139 | "original": { 140 | "owner": "nix-systems", 141 | "repo": "default", 142 | "type": "github" 143 | } 144 | }, 145 | "zig-overlay": { 146 | "inputs": { 147 | "flake-compat": "flake-compat", 148 | "flake-utils": "flake-utils_3", 149 | "nixpkgs": [ 150 | "nixpkgs" 151 | ] 152 | }, 153 | "locked": { 154 | "lastModified": 1710289318, 155 | "narHash": "sha256-FBRDgGTJi8npOyUcS+oXMj6PPGIEDS7xiXbod0c8II8=", 156 | "owner": "mitchellh", 157 | "repo": "zig-overlay", 158 | "rev": "072768b20bde522707988dba9ffbaeb58339df1d", 159 | "type": "github" 160 | }, 161 | "original": { 162 | "owner": "mitchellh", 163 | "repo": "zig-overlay", 164 | "type": "github" 165 | } 166 | } 167 | }, 168 | "root": "root", 169 | "version": 7 170 | } 171 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "DuckDB extension template for Zig"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | zig-overlay = { 8 | url = "github:mitchellh/zig-overlay"; 9 | inputs.nixpkgs.follows = "nixpkgs"; 10 | }; 11 | duckdb-nix = { 12 | url = "github:rupurt/duckdb-nix"; 13 | inputs.nixpkgs.follows = "nixpkgs"; 14 | }; 15 | }; 16 | 17 | outputs = { 18 | flake-utils, 19 | nixpkgs, 20 | zig-overlay, 21 | duckdb-nix, 22 | ... 23 | }: let 24 | systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; 25 | outputs = flake-utils.lib.eachSystem systems (system: let 26 | pkgs = import nixpkgs { 27 | inherit system; 28 | overlays = [ 29 | zig-overlay.overlays.default 30 | duckdb-nix.overlay 31 | ]; 32 | }; 33 | buildInputs = 34 | [ 35 | pkgs.pkg-config 36 | pkgs.gnumake 37 | pkgs.zigpkgs.master 38 | ] 39 | ++ pkgs.lib.optionals (pkgs.stdenv.isLinux) [ 40 | pkgs.libcxx 41 | ]; 42 | devShellPackages = 43 | [ 44 | ] 45 | ++ pkgs.lib.optionals (pkgs.stdenv.isLinux) [ 46 | pkgs.strace 47 | pkgs.valgrind 48 | ]; 49 | in { 50 | # packages exported by the flake 51 | packages = {}; 52 | 53 | # nix run 54 | apps = {}; 55 | 56 | # nix fmt 57 | formatter = pkgs.alejandra; 58 | 59 | # nix develop -c $SHELL 60 | devShells = rec { 61 | # nix develop .#main -c $SHELL 62 | main = pkgs.mkShell.override {stdenv = pkgs.libcxxStdenv;} { 63 | name = "main dev shell"; 64 | 65 | buildInputs = buildInputs; 66 | 67 | packages = 68 | [ 69 | pkgs.duckdb-pkgs.main 70 | ] 71 | ++ devShellPackages; 72 | 73 | DUCKDB_THIRD_PARTY_PATH = "${pkgs.duckdb-pkgs.main}/third_party"; 74 | }; 75 | 76 | # nix develop .#v0-10-0 -c $SHELL 77 | v0-10-0 = pkgs.mkShell.override {stdenv = pkgs.libcxxStdenv;} { 78 | name = "v0.10.0 dev shell"; 79 | 80 | buildInputs = buildInputs; 81 | 82 | packages = 83 | [ 84 | pkgs.duckdb-pkgs.v0_10_0 85 | ] 86 | ++ devShellPackages; 87 | 88 | DUCKDB_THIRD_PARTY_PATH = "${pkgs.duckdb-pkgs.v0_10_0}/third_party"; 89 | }; 90 | 91 | # nix develop .#v0-9-2 -c $SHELL 92 | v0-9-2 = pkgs.mkShell.override {stdenv = pkgs.libcxxStdenv;} { 93 | name = "v0.9.2 dev shell"; 94 | 95 | buildInputs = buildInputs; 96 | 97 | packages = 98 | [ 99 | pkgs.duckdb-pkgs.v0_9_2 100 | ] 101 | ++ devShellPackages; 102 | 103 | DUCKDB_THIRD_PARTY_PATH = "${pkgs.duckdb-pkgs.v0_9_2}/third_party"; 104 | }; 105 | 106 | # nix develop -c $SHELL 107 | default = v0-10-0; 108 | }; 109 | }); 110 | in 111 | outputs 112 | // { 113 | # nix flake init -t github:rupurt/duckdb-extension-template-nix#multi 114 | templates = rec { 115 | multi = { 116 | description = "Multi version DuckDB template"; 117 | path = ./templates/multi; 118 | }; 119 | default = multi; 120 | }; 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /src/bridge.cpp: -------------------------------------------------------------------------------- 1 | #define DUCKDB_EXTENSION_MAIN 2 | 3 | #include "duckdb.hpp" 4 | #include "duckdb/common/exception.hpp" 5 | #include "duckdb/common/string_util.hpp" 6 | #include "duckdb/function/scalar_function.hpp" 7 | #include "duckdb/main/extension_util.hpp" 8 | #include 9 | 10 | namespace duckdb { 11 | inline void QuackScalarFun(DataChunk &args, ExpressionState &state, Vector &result) { 12 | auto &name_vector = args.data[0]; 13 | UnaryExecutor::Execute(name_vector, result, args.size(), [&](string_t name) { 14 | return StringVector::AddString(result, "Quack " + name.GetString() + " 🐥"); 15 | }); 16 | } 17 | 18 | static void LoadInternal(DatabaseInstance &instance) { 19 | // Register a scalar function 20 | auto quack_scalar_function = 21 | ScalarFunction("quack", {LogicalType::VARCHAR}, LogicalType::VARCHAR, QuackScalarFun); 22 | ExtensionUtil::RegisterFunction(instance, quack_scalar_function); 23 | } 24 | 25 | class QuackExtension : public Extension { 26 | public: 27 | void Load(DuckDB &db) override; 28 | std::string Name() override; 29 | }; 30 | 31 | void duckdb::QuackExtension::Load(duckdb::DuckDB &db) { LoadInternal(*db.instance); } 32 | 33 | std::string duckdb::QuackExtension::Name() { return "quack"; } 34 | } // namespace duckdb 35 | 36 | // 37 | // We will call these extern functions via the C ABI from Zig. 38 | // 39 | 40 | // DuckDB requires the version returned from the extension to match the version 41 | // calling it. Here we use the linked version reported by `libduckdb`. 42 | extern "C" char const *extension_version(void) { return duckdb::DuckDB::LibraryVersion(); } 43 | 44 | // This function is responsible for bootstrapping the extension into the DuckDB 45 | // internals. The `quack` extension is trivial and only registers a single scalar 46 | // function. 47 | extern "C" void extension_init(duckdb::DatabaseInstance &db) { 48 | duckdb::DuckDB db_wrapper(db); 49 | db_wrapper.LoadExtension(); 50 | } 51 | 52 | #ifndef DUCKDB_EXTENSION_MAIN 53 | #error DUCKDB_EXTENSION_MAIN not defined 54 | #endif 55 | -------------------------------------------------------------------------------- /src/include/bridge.h: -------------------------------------------------------------------------------- 1 | char const *extension_version(); 2 | void extension_init(void *); 3 | -------------------------------------------------------------------------------- /src/root.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // Import the bridge header file. Zig can parse and call C directly! 4 | pub const c_bridge = @cImport({ 5 | @cInclude("bridge.h"); 6 | }); 7 | 8 | // Given our extension will build an artifact called `quack.duckdb_extension` define 2 symbols 9 | // that DuckDB will call after it loads the extension with `dlopen`. 10 | 11 | export fn quack_version() [*c][*c]u8 { 12 | return @ptrCast(@alignCast(@constCast(c_bridge.extension_version()))); 13 | } 14 | 15 | export fn quack_init(db: *anyopaque) void { 16 | c_bridge.extension_init(db); 17 | } 18 | -------------------------------------------------------------------------------- /templates/multi/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "DuckDB extension template for Zig"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | zig-overlay = { 8 | url = "github:mitchellh/zig-overlay"; 9 | inputs.nixpkgs.follows = "nixpkgs"; 10 | }; 11 | duckdb-nix = { 12 | url = "github:rupurt/duckdb-nix"; 13 | inputs.nixpkgs.follows = "nixpkgs"; 14 | }; 15 | }; 16 | 17 | outputs = { 18 | flake-utils, 19 | nixpkgs, 20 | zig-overlay, 21 | duckdb-nix, 22 | ... 23 | }: let 24 | systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; 25 | outputs = flake-utils.lib.eachSystem systems (system: let 26 | pkgs = import nixpkgs { 27 | inherit system; 28 | overlays = [ 29 | zig-overlay.overlays.default 30 | duckdb-nix.overlay 31 | ]; 32 | }; 33 | buildInputs = 34 | [ 35 | pkgs.pkg-config 36 | pkgs.gnumake 37 | pkgs.zigpkgs.master 38 | ] 39 | ++ pkgs.lib.optionals (pkgs.stdenv.isLinux) [ 40 | pkgs.libcxx 41 | ]; 42 | devShellPackages = 43 | [ 44 | ] 45 | ++ pkgs.lib.optionals (pkgs.stdenv.isLinux) [ 46 | pkgs.strace 47 | pkgs.valgrind 48 | ]; 49 | in { 50 | # packages exported by the flake 51 | packages = {}; 52 | 53 | # nix run 54 | apps = {}; 55 | 56 | # nix fmt 57 | formatter = pkgs.alejandra; 58 | 59 | # nix develop -c $SHELL 60 | devShells = rec { 61 | # nix develop .#main -c $SHELL 62 | main = pkgs.mkShell.override {stdenv = pkgs.libcxxStdenv;} { 63 | name = "main dev shell"; 64 | 65 | buildInputs = buildInputs; 66 | 67 | packages = 68 | [ 69 | pkgs.duckdb-pkgs.main 70 | ] 71 | ++ devShellPackages; 72 | }; 73 | 74 | # nix develop .#v0-10-0 -c $SHELL 75 | v0-10-0 = pkgs.mkShell.override {stdenv = pkgs.libcxxStdenv;} { 76 | name = "v0.10.0 dev shell"; 77 | 78 | buildInputs = buildInputs; 79 | 80 | packages = 81 | [ 82 | pkgs.duckdb-pkgs.duckdb-v0_10_0 83 | ] 84 | ++ devShellPackages; 85 | }; 86 | 87 | # nix develop .#v0-9-2 -c $SHELL 88 | v0-9-2 = pkgs.mkShell.override {stdenv = pkgs.libcxxStdenv;} { 89 | name = "v0.9.2 dev shell"; 90 | 91 | buildInputs = buildInputs; 92 | 93 | packages = 94 | [ 95 | pkgs.duckdb-pkgs.duckdb-v0_9_2 96 | ] 97 | ++ devShellPackages; 98 | }; 99 | 100 | # nix develop .#nixpkgs-duckdb -c $SHELL 101 | nixpkgs-duckdb = pkgs.mkShell.override {stdenv = pkgs.libcxxStdenv;} { 102 | name = "nixpkgs duckdb dev shell"; 103 | 104 | buildInputs = buildInputs; 105 | 106 | packages = 107 | [ 108 | pkgs.duckdb 109 | ] 110 | ++ devShellPackages; 111 | }; 112 | 113 | # nix develop -c $SHELL 114 | default = v0-9-2; 115 | }; 116 | }); 117 | in 118 | outputs; 119 | } 120 | --------------------------------------------------------------------------------