├── .github └── workflows │ ├── release-please.yml │ └── rust.yml ├── .gitignore ├── .gitmodules ├── .gitpod.yml ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bors.toml ├── build.rs ├── renovate.json ├── rust-toolchain.toml └── src ├── config.rs ├── connection.rs ├── constants.rs ├── data_chunk.rs ├── database.rs ├── duckly.rs ├── error.rs ├── lib.rs ├── logical_type.rs ├── table_functions ├── bind_info.rs ├── function_info.rs ├── init_info.rs ├── mod.rs ├── replacement_scan.rs ├── table_function.rs └── test_integration.rs ├── value.rs ├── vector.rs ├── wrapper.cpp └── wrapper.hpp /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: Release Please 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: google-github-actions/release-please-action@v3 11 | with: 12 | release-type: rust 13 | package-name: duckdb-extension-framework 14 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main", "staging", "trying" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUSTC_WRAPPER: sccache 12 | 13 | jobs: 14 | build: 15 | name: build 16 | runs-on: ubuntu-latest 17 | env: 18 | LD_LIBRARY_PATH: ${{ github.workspace }}/duckdb/build/debug/src/ 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | submodules: recursive 24 | - uses: hendrikmuhs/ccache-action@v1.2 25 | with: 26 | variant: sccache 27 | 28 | - name: Build 29 | run: cargo build --verbose 30 | 31 | - run: sudo apt install ninja-build build-essential 32 | - run: make debug 33 | env: 34 | GEN: ninja 35 | DISABLE_SANITIZER: 1 36 | working-directory: duckdb 37 | 38 | - name: Compile tests 39 | run: cargo test --no-run --all-features 40 | 41 | - name: Run tests 42 | run: LD_PRELOAD=$(whereis libasan.so.6 | cut -d ' ' -f 2) cargo test --verbose --all-features 43 | 44 | - name: cleanup 45 | run: | 46 | ls 47 | rm -rf duckdb/* 48 | 49 | - working-directory: duckdb 50 | run: git checkout src/include 51 | 52 | - uses: katyo/publish-crates@v2 53 | if: github.ref == 'refs/heads/main' 54 | with: 55 | registry-token: ${{ secrets.CARGO_TOKEN }} 56 | ignore-unpublished-changes: true 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | .sccache 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "duckdb"] 2 | path = duckdb 3 | url = https://github.com/duckdb/duckdb 4 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | tasks: 6 | - init: cargo build 7 | command: cargo watch -x build 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | skip: [cargo-check, clippy] 3 | 4 | repos: 5 | - repo: https://github.com/cheshirekow/cmake-format-precommit 6 | rev: v0.6.13 7 | hooks: 8 | - id: cmake-format 9 | - id: cmake-lint 10 | 11 | - repo: local 12 | hooks: 13 | - id: cargo-fmt 14 | name: Rust Formatter 15 | description: "A tool for formatting Rust code according to style guidelines." 16 | language: rust 17 | entry: rustfmt 18 | types: [rust] 19 | 20 | - repo: https://github.com/doublify/pre-commit-rust 21 | rev: v1.0 22 | hooks: 23 | - id: cargo-check 24 | - id: clippy 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.7.0](https://github.com/Mause/duckdb-extension-framework/compare/v0.6.0...v0.7.0) (2022-11-14) 4 | 5 | 6 | ### Features 7 | 8 | * add config machinery ([9d8b24d](https://github.com/Mause/duckdb-extension-framework/commit/9d8b24d75cef28cb6094881e36b7904beb0ac8f0)) 9 | 10 | ## [0.6.0](https://github.com/Mause/duckdb-extension-framework/compare/v0.5.0...v0.6.0) (2022-11-13) 11 | 12 | 13 | ### Features 14 | 15 | * add Vector#get_data_as_slice ([a2499a3](https://github.com/Mause/duckdb-extension-framework/commit/a2499a3802c81a3f9024d4adc338d10af1fefdd4)) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * Vector::get_data should return a pointer of type T ([ef6219a](https://github.com/Mause/duckdb-extension-framework/commit/ef6219a28565f6943854d28f18acc5c41cdc28f7)) 21 | 22 | ## [0.5.0](https://github.com/Mause/duckdb-extension-framework/compare/v0.4.1...v0.5.0) (2022-11-10) 23 | 24 | 25 | ### Features 26 | 27 | * add ReplacementScanInfo ([74dcc53](https://github.com/Mause/duckdb-extension-framework/commit/74dcc534c7616244a900221e9f7fc4f7cd376f2c)) 28 | * add T to Vector<T> ([d764ce4](https://github.com/Mause/duckdb-extension-framework/commit/d764ce44d2bc8622cf7ae8593a953fd2524eaa5c)) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * LogicalType::new_union_type ([4ed2a37](https://github.com/Mause/duckdb-extension-framework/commit/4ed2a376a192a57ca2f8cda64e6e32ea84c115c4)) 34 | 35 | ## [0.4.1](https://github.com/Mause/duckdb-extension-framework/compare/v0.4.0...v0.4.1) (2022-11-09) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * correct LogicalTypeId enum name ([9dbebbb](https://github.com/Mause/duckdb-extension-framework/commit/9dbebbbc52213871f3bc0b75e3b606544f758ff6)) 41 | 42 | ## [0.4.0](https://github.com/Mause/duckdb-extension-framework/compare/v0.3.0...v0.4.0) (2022-11-09) 43 | 44 | 45 | ### Features 46 | 47 | * add basic map support ([fc734aa](https://github.com/Mause/duckdb-extension-framework/commit/fc734aa81b0d38439b983d3fd3e3deea0ef9b5e4)) 48 | * add missing functions to BindInfo ([c8e7afe](https://github.com/Mause/duckdb-extension-framework/commit/c8e7afe0f78ee4fdf05a180bbe0e68f286ab7988)) 49 | * add missing functions to FunctionInfo ([468cbad](https://github.com/Mause/duckdb-extension-framework/commit/468cbad542952cd3dc1602da125e5f5c6492581d)) 50 | * add missing functions to InitInfo ([d978d6f](https://github.com/Mause/duckdb-extension-framework/commit/d978d6fb0931c491a6f4aa8800bbae975f1a59e6)) 51 | * add missing functions to LogicalType ([9136cdd](https://github.com/Mause/duckdb-extension-framework/commit/9136cdde431d9a528889cd35ee2bf32aac7a18fe)) 52 | * add missing functions to TableFunction ([cb72f2a](https://github.com/Mause/duckdb-extension-framework/commit/cb72f2a850c7b0caa2d6743cba593c026acc06f4)) 53 | 54 | ## [0.3.0](https://github.com/Mause/duckdb-extension-framework/compare/v0.2.0...v0.3.0) (2022-10-20) 55 | 56 | 57 | ### Features 58 | 59 | * add Connection#get_ptr ([3b39d45](https://github.com/Mause/duckdb-extension-framework/commit/3b39d45cefac1bad424fd5a6e59ff3b51235a8eb)) 60 | * add malloc_struct ([ee56c93](https://github.com/Mause/duckdb-extension-framework/commit/ee56c93804c9b67b64ed39ff5ac8f1557009c806)) 61 | * add ValidityMask struct ([4a7d27d](https://github.com/Mause/duckdb-extension-framework/commit/4a7d27d20dfbb52096156c7c8d119d46b085ead3)) 62 | 63 | ## [0.2.0](https://github.com/Mause/duckdb-extension-framework/compare/v0.1.3...v0.2.0) (2022-10-19) 64 | 65 | 66 | ### Features 67 | 68 | * add ability to create new database instances ([495c9dd](https://github.com/Mause/duckdb-extension-framework/commit/495c9dd849fd03b58389d85ee9dea12bd210d8dc)) 69 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bindgen" 13 | version = "0.65.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" 16 | dependencies = [ 17 | "bitflags", 18 | "cexpr", 19 | "clang-sys", 20 | "lazy_static", 21 | "lazycell", 22 | "log", 23 | "peeking_take_while", 24 | "prettyplease", 25 | "proc-macro2", 26 | "quote", 27 | "regex", 28 | "rustc-hash", 29 | "shlex", 30 | "syn 2.0.13", 31 | "which", 32 | ] 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "1.3.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 39 | 40 | [[package]] 41 | name = "build_script" 42 | version = "0.2.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "163ca3c07a6939838dfa322e1e36cd434cc2b625dacb051b8e8b893bbbb8aa62" 45 | dependencies = [ 46 | "once_cell", 47 | ] 48 | 49 | [[package]] 50 | name = "cc" 51 | version = "1.0.79" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 54 | 55 | [[package]] 56 | name = "cexpr" 57 | version = "0.6.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 60 | dependencies = [ 61 | "nom", 62 | ] 63 | 64 | [[package]] 65 | name = "cfg-if" 66 | version = "1.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 69 | 70 | [[package]] 71 | name = "clang-sys" 72 | version = "1.4.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" 75 | dependencies = [ 76 | "glob", 77 | "libc", 78 | "libloading", 79 | ] 80 | 81 | [[package]] 82 | name = "duckdb-extension-framework" 83 | version = "0.7.0" 84 | dependencies = [ 85 | "bindgen", 86 | "build_script", 87 | "cc", 88 | "num-derive", 89 | "num-traits", 90 | ] 91 | 92 | [[package]] 93 | name = "either" 94 | version = "1.8.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 97 | 98 | [[package]] 99 | name = "glob" 100 | version = "0.3.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 103 | 104 | [[package]] 105 | name = "lazy_static" 106 | version = "1.4.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 109 | 110 | [[package]] 111 | name = "lazycell" 112 | version = "1.3.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 115 | 116 | [[package]] 117 | name = "libc" 118 | version = "0.2.135" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" 121 | 122 | [[package]] 123 | name = "libloading" 124 | version = "0.7.3" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" 127 | dependencies = [ 128 | "cfg-if", 129 | "winapi", 130 | ] 131 | 132 | [[package]] 133 | name = "log" 134 | version = "0.4.17" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 137 | dependencies = [ 138 | "cfg-if", 139 | ] 140 | 141 | [[package]] 142 | name = "memchr" 143 | version = "2.5.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 146 | 147 | [[package]] 148 | name = "minimal-lexical" 149 | version = "0.2.1" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 152 | 153 | [[package]] 154 | name = "nom" 155 | version = "7.1.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 158 | dependencies = [ 159 | "memchr", 160 | "minimal-lexical", 161 | ] 162 | 163 | [[package]] 164 | name = "num-derive" 165 | version = "0.3.3" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 168 | dependencies = [ 169 | "proc-macro2", 170 | "quote", 171 | "syn 1.0.102", 172 | ] 173 | 174 | [[package]] 175 | name = "num-traits" 176 | version = "0.2.15" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 179 | dependencies = [ 180 | "autocfg", 181 | ] 182 | 183 | [[package]] 184 | name = "once_cell" 185 | version = "1.15.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" 188 | 189 | [[package]] 190 | name = "peeking_take_while" 191 | version = "0.1.2" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 194 | 195 | [[package]] 196 | name = "prettyplease" 197 | version = "0.2.4" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" 200 | dependencies = [ 201 | "proc-macro2", 202 | "syn 2.0.13", 203 | ] 204 | 205 | [[package]] 206 | name = "proc-macro2" 207 | version = "1.0.56" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 210 | dependencies = [ 211 | "unicode-ident", 212 | ] 213 | 214 | [[package]] 215 | name = "quote" 216 | version = "1.0.26" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 219 | dependencies = [ 220 | "proc-macro2", 221 | ] 222 | 223 | [[package]] 224 | name = "regex" 225 | version = "1.6.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 228 | dependencies = [ 229 | "regex-syntax", 230 | ] 231 | 232 | [[package]] 233 | name = "regex-syntax" 234 | version = "0.6.27" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 237 | 238 | [[package]] 239 | name = "rustc-hash" 240 | version = "1.1.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 243 | 244 | [[package]] 245 | name = "shlex" 246 | version = "1.1.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 249 | 250 | [[package]] 251 | name = "syn" 252 | version = "1.0.102" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" 255 | dependencies = [ 256 | "proc-macro2", 257 | "quote", 258 | "unicode-ident", 259 | ] 260 | 261 | [[package]] 262 | name = "syn" 263 | version = "2.0.13" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" 266 | dependencies = [ 267 | "proc-macro2", 268 | "quote", 269 | "unicode-ident", 270 | ] 271 | 272 | [[package]] 273 | name = "unicode-ident" 274 | version = "1.0.5" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 277 | 278 | [[package]] 279 | name = "which" 280 | version = "4.3.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" 283 | dependencies = [ 284 | "either", 285 | "libc", 286 | "once_cell", 287 | ] 288 | 289 | [[package]] 290 | name = "winapi" 291 | version = "0.3.9" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 294 | dependencies = [ 295 | "winapi-i686-pc-windows-gnu", 296 | "winapi-x86_64-pc-windows-gnu", 297 | ] 298 | 299 | [[package]] 300 | name = "winapi-i686-pc-windows-gnu" 301 | version = "0.4.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 304 | 305 | [[package]] 306 | name = "winapi-x86_64-pc-windows-gnu" 307 | version = "0.4.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 310 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "duckdb-extension-framework" 3 | version = "0.7.0" 4 | edition = "2021" 5 | description = "Purely experimental DuckDB extension framework" 6 | license-file = "LICENSE" 7 | documentation = "https://docs.rs/duckdb-extension-framework" 8 | homepage = "https://github.com/Mause/duckdb-extension-framework" 9 | repository = "https://github.com/Mause/duckdb-extension-framework" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [lib] 14 | doctest = false 15 | 16 | [features] 17 | statically_linked = [] 18 | 19 | [dependencies] 20 | num-traits = "0.2.15" 21 | num-derive = "0.3.3" 22 | 23 | [build-dependencies] 24 | bindgen = "0.65.1" 25 | build_script = "0.2.0" 26 | cc = "1.0.79" 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Elliana May 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated in favour of code located in https://docs.rs/duckdb/latest/duckdb/vtab/index.html 2 | 3 | --- 4 | 5 | ## duckdb-extension-framework 6 | 7 | Purely experimental framework 8 | 9 | Used in: 10 | * [duckdb-deltatable-extension](https://github.com/Mause/duckdb-deltatable-extension) 11 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = ["build"] 2 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use build_script::cargo_rerun_if_changed; 2 | use std::path::PathBuf; 3 | use std::{env, path::Path}; 4 | 5 | fn main() { 6 | let duckdb_root = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()) 7 | .join("duckdb") 8 | .canonicalize() 9 | .expect("duckdb source root"); 10 | 11 | let header = "src/wrapper.hpp"; 12 | 13 | #[cfg(feature = "statically_linked")] 14 | { 15 | use build_script::{cargo_rustc_link_lib, cargo_rustc_link_search}; 16 | cargo_rustc_link_lib("duckdb"); 17 | cargo_rustc_link_search(duckdb_root.join("build/debug/src")); 18 | cargo_rustc_link_search(duckdb_root.join("build/release/src")); 19 | } 20 | 21 | // Tell cargo to invalidate the built crate whenever the wrapper changes 22 | cargo_rerun_if_changed(header); 23 | 24 | // The bindgen::Builder is the main entry point 25 | // to bindgen, and lets you build up options for 26 | // the resulting bindings. 27 | let duckdb_include = duckdb_root.join("src/include"); 28 | let bindings = bindgen::Builder::default() 29 | // The input header we would like to generate 30 | // bindings for. 31 | .header(header) 32 | // .enable_cxx_namespaces() 33 | // .generate_comments(true) 34 | // .derive_default(true) 35 | // Tell bindgen we are processing c++ 36 | .clang_arg("-xc++") 37 | // .clang_arg("-std=c++11") 38 | .clang_arg("-I") 39 | .clang_arg(duckdb_include.to_string_lossy()) 40 | // .allowlist_type("duckdb::DuckDB") 41 | // .opaque_type("std::.*") 42 | .derive_debug(true) 43 | .derive_default(true) 44 | // Tell cargo to invalidate the built crate whenever any of the 45 | // included header files changed. 46 | .parse_callbacks(Box::new(bindgen::CargoCallbacks)) 47 | // Finish the builder and generate the bindings. 48 | .generate() 49 | // Unwrap the Result and panic on failure. 50 | .expect("Unable to generate bindings"); 51 | 52 | // Write the bindings to the $OUT_DIR/bindings.rs file. 53 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 54 | bindings 55 | .write_to_file(out_path.join("bindings.rs")) 56 | .expect("Couldn't write bindings!"); 57 | 58 | cc::Build::new() 59 | .include(duckdb_include) 60 | .flag_if_supported("-Wno-unused-parameter") 61 | .flag_if_supported("-Wno-redundant-move") 62 | .flag_if_supported("-std=c++14") 63 | .cpp(true) 64 | .file("src/wrapper.cpp") 65 | .compile("duckdb_extension_framework"); 66 | } 67 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "git-submodules": { 7 | "enabled": true 8 | }, 9 | "packageRules": [ 10 | { 11 | "matchPackageNames": ["duckdb"], 12 | "automerge": true 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.65.0" 3 | components = [ "rustfmt", "clippy" ] 4 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CString, 3 | ptr::{addr_of_mut, null_mut}, 4 | }; 5 | 6 | use crate::{ 7 | check, 8 | duckly::{ 9 | duckdb_config, duckdb_config_count, duckdb_create_config, duckdb_destroy_config, 10 | duckdb_get_config_flag, duckdb_set_config, 11 | }, 12 | }; 13 | 14 | pub struct Config(pub(crate) duckdb_config); 15 | 16 | pub fn get_configs() -> ConfigList { 17 | ConfigList { idx: 0 } 18 | } 19 | 20 | pub struct ConfigItem { 21 | pub name: String, 22 | pub desc: String, 23 | } 24 | 25 | pub struct ConfigList { 26 | idx: usize, 27 | } 28 | impl Iterator for ConfigList { 29 | type Item = ConfigItem; 30 | 31 | fn next(&mut self) -> Option { 32 | self.try_next().ok() 33 | } 34 | } 35 | impl ConfigList { 36 | fn try_next(&mut self) -> Result> { 37 | let name = CString::new("")?; 38 | let desc = CString::new("")?; 39 | 40 | let mut name_ptr = name.as_ptr(); 41 | let mut desc_ptr = desc.as_ptr(); 42 | check!(unsafe { 43 | duckdb_get_config_flag(self.idx, addr_of_mut!(name_ptr), addr_of_mut!(desc_ptr)) 44 | }); 45 | self.idx += 1; 46 | 47 | Ok(ConfigItem { 48 | name: name.to_str()?.to_owned(), 49 | desc: desc.to_str()?.to_owned(), 50 | }) 51 | } 52 | 53 | pub fn len(&self) -> usize { 54 | unsafe { duckdb_config_count() } 55 | } 56 | 57 | pub fn is_empty(&self) -> bool { 58 | self.len() == 0 59 | } 60 | } 61 | 62 | impl Config { 63 | pub fn new() -> Result> { 64 | let mut out_config: duckdb_config = null_mut(); 65 | check!(unsafe { duckdb_create_config(addr_of_mut!(out_config)) }); 66 | Ok(Self(out_config)) 67 | } 68 | pub fn set_flag(&mut self, name: &str, value: &str) -> Result<(), Box> { 69 | let name = CString::new(name)?; 70 | let value = CString::new(value)?; 71 | check!(unsafe { duckdb_set_config(self.0, name.as_ptr(), value.as_ptr()) }); 72 | Ok(()) 73 | } 74 | } 75 | 76 | impl Drop for Config { 77 | fn drop(&mut self) { 78 | unsafe { duckdb_destroy_config(addr_of_mut!(self.0)) }; 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod test { 84 | use std::error::Error; 85 | 86 | use crate::{get_configs, Config, ConfigItem}; 87 | 88 | #[test] 89 | fn test_config_list() { 90 | let lst = get_configs(); 91 | 92 | let lst: Vec = lst.collect(); 93 | 94 | assert!(lst.len() > 0); 95 | } 96 | 97 | #[test] 98 | fn test_config_populate() -> Result<(), Box> { 99 | let mut config = Config::new()?; 100 | 101 | config.set_flag("access_mode", "READ_ONLY")?; 102 | 103 | Ok(()) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/connection.rs: -------------------------------------------------------------------------------- 1 | use crate::check; 2 | use crate::duckly::{duckdb_connection, duckdb_disconnect, duckdb_register_table_function}; 3 | use crate::table_functions::TableFunction; 4 | 5 | /// A connection to a database. This represents a (client) connection that can 6 | /// be used to query the database. 7 | #[derive(Debug)] 8 | pub struct Connection { 9 | ptr: duckdb_connection, 10 | } 11 | 12 | impl From for Connection { 13 | fn from(ptr: duckdb_connection) -> Self { 14 | Self { ptr } 15 | } 16 | } 17 | 18 | impl Connection { 19 | /// Register the table function object within the given connection. 20 | /// 21 | /// The function requires at least a name, a bind function, an init function and a main function. 22 | /// 23 | /// If the function is incomplete or a function with this name already exists DuckDBError is returned. 24 | /// 25 | /// # Arguments 26 | /// * `function`: The function pointer 27 | /// returns: Whether or not the registration was successful. 28 | pub fn register_table_function( 29 | &self, 30 | table_function: TableFunction, 31 | ) -> Result<(), Box> { 32 | unsafe { 33 | check!(duckdb_register_table_function(self.ptr, table_function.ptr)); 34 | } 35 | Ok(()) 36 | } 37 | 38 | /// Returns the internal connection pointer 39 | pub fn get_ptr(&self) -> duckdb_connection { 40 | self.ptr 41 | } 42 | } 43 | 44 | impl Drop for Connection { 45 | fn drop(&mut self) { 46 | unsafe { 47 | duckdb_disconnect(&mut self.ptr); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | use crate::duckly::*; 2 | 3 | #[derive(Debug, Eq, PartialEq, num_derive::FromPrimitive)] 4 | pub enum LogicalTypeId { 5 | Boolean = DUCKDB_TYPE_DUCKDB_TYPE_BOOLEAN as isize, 6 | Tinyint = DUCKDB_TYPE_DUCKDB_TYPE_TINYINT as isize, 7 | Smallint = DUCKDB_TYPE_DUCKDB_TYPE_SMALLINT as isize, 8 | Integer = DUCKDB_TYPE_DUCKDB_TYPE_INTEGER as isize, 9 | Bigint = DUCKDB_TYPE_DUCKDB_TYPE_BIGINT as isize, 10 | Utinyint = DUCKDB_TYPE_DUCKDB_TYPE_UTINYINT as isize, 11 | Usmallint = DUCKDB_TYPE_DUCKDB_TYPE_USMALLINT as isize, 12 | Uinteger = DUCKDB_TYPE_DUCKDB_TYPE_UINTEGER as isize, 13 | Ubigint = DUCKDB_TYPE_DUCKDB_TYPE_UBIGINT as isize, 14 | Float = DUCKDB_TYPE_DUCKDB_TYPE_FLOAT as isize, 15 | Double = DUCKDB_TYPE_DUCKDB_TYPE_DOUBLE as isize, 16 | Timestamp = DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP as isize, 17 | Date = DUCKDB_TYPE_DUCKDB_TYPE_DATE as isize, 18 | Time = DUCKDB_TYPE_DUCKDB_TYPE_TIME as isize, 19 | Interval = DUCKDB_TYPE_DUCKDB_TYPE_INTERVAL as isize, 20 | Hugeint = DUCKDB_TYPE_DUCKDB_TYPE_HUGEINT as isize, 21 | Varchar = DUCKDB_TYPE_DUCKDB_TYPE_VARCHAR as isize, 22 | Blob = DUCKDB_TYPE_DUCKDB_TYPE_BLOB as isize, 23 | Decimal = DUCKDB_TYPE_DUCKDB_TYPE_DECIMAL as isize, 24 | TimestampS = DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_S as isize, 25 | TimestampMs = DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_MS as isize, 26 | TimestampNs = DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_NS as isize, 27 | Enum = DUCKDB_TYPE_DUCKDB_TYPE_ENUM as isize, 28 | List = DUCKDB_TYPE_DUCKDB_TYPE_LIST as isize, 29 | Struct = DUCKDB_TYPE_DUCKDB_TYPE_STRUCT as isize, 30 | Map = DUCKDB_TYPE_DUCKDB_TYPE_MAP as isize, 31 | Uuid = DUCKDB_TYPE_DUCKDB_TYPE_UUID as isize, 32 | Union = DUCKDB_TYPE_DUCKDB_TYPE_UNION as isize, 33 | Json = DUCKDB_TYPE_DUCKDB_TYPE_JSON as isize, 34 | } 35 | -------------------------------------------------------------------------------- /src/data_chunk.rs: -------------------------------------------------------------------------------- 1 | use crate::duckly::{ 2 | duckdb_create_data_chunk, duckdb_data_chunk, duckdb_data_chunk_get_column_count, 3 | duckdb_data_chunk_get_size, duckdb_data_chunk_get_vector, duckdb_data_chunk_reset, 4 | duckdb_data_chunk_set_size, duckdb_destroy_data_chunk, duckdb_logical_type, idx_t, 5 | }; 6 | use crate::{LogicalType, Vector}; 7 | 8 | /// A Data Chunk represents a set of vectors. 9 | /// 10 | /// The data chunk class is the intermediate representation used by the 11 | /// execution engine of DuckDB. It effectively represents a subset of a relation. 12 | /// It holds a set of vectors that all have the same length. 13 | /// 14 | /// DataChunk is initialized using the DataChunk::Initialize function by 15 | /// providing it with a vector of TypeIds for the Vector members. By default, 16 | /// this function will also allocate a chunk of memory in the DataChunk for the 17 | /// vectors and all the vectors will be referencing vectors to the data owned by 18 | /// the chunk. The reason for this behavior is that the underlying vectors can 19 | /// become referencing vectors to other chunks as well (i.e. in the case an 20 | /// operator does not alter the data, such as a Filter operator which only adds a 21 | /// selection vector). 22 | /// 23 | /// In addition to holding the data of the vectors, the DataChunk also owns the 24 | /// selection vector that underlying vectors can point to. 25 | #[derive(Debug)] 26 | pub struct DataChunk { 27 | ptr: duckdb_data_chunk, 28 | owned: bool, 29 | } 30 | 31 | impl DataChunk { 32 | /// Creates an empty DataChunk with the specified set of types. 33 | /// 34 | /// # Arguments 35 | /// - `types`: An array of types of the data chunk. 36 | pub fn new(types: Vec) -> Self { 37 | let types: Vec = types.iter().map(|x| x.typ).collect(); 38 | let mut types = types.into_boxed_slice(); 39 | 40 | let ptr = unsafe { 41 | duckdb_create_data_chunk(types.as_mut_ptr(), types.len().try_into().unwrap()) 42 | }; 43 | 44 | Self { ptr, owned: true } 45 | } 46 | 47 | /// Retrieves the vector at the specified column index in the data chunk. 48 | /// 49 | /// The pointer to the vector is valid for as long as the chunk is alive. 50 | /// It does NOT need to be destroyed. 51 | /// 52 | pub fn get_vector(&self, column_index: idx_t) -> Vector { 53 | Vector::from(unsafe { duckdb_data_chunk_get_vector(self.ptr, column_index) }) 54 | } 55 | /// Sets the current number of tuples in a data chunk. 56 | pub fn set_size(&self, size: idx_t) { 57 | unsafe { duckdb_data_chunk_set_size(self.ptr, size) }; 58 | } 59 | /// Resets a data chunk, clearing the validity masks and setting the cardinality of the data chunk to 0. 60 | pub fn reset(&self) { 61 | unsafe { duckdb_data_chunk_reset(self.ptr) } 62 | } 63 | /// Retrieves the number of columns in a data chunk. 64 | pub fn get_column_count(&self) -> idx_t { 65 | unsafe { duckdb_data_chunk_get_column_count(self.ptr) } 66 | } 67 | /// Retrieves the current number of tuples in a data chunk. 68 | pub fn get_size(&self) -> idx_t { 69 | unsafe { duckdb_data_chunk_get_size(self.ptr) } 70 | } 71 | } 72 | 73 | impl From for DataChunk { 74 | fn from(ptr: duckdb_data_chunk) -> Self { 75 | Self { ptr, owned: false } 76 | } 77 | } 78 | 79 | impl Drop for DataChunk { 80 | fn drop(&mut self) { 81 | if self.owned { 82 | unsafe { duckdb_destroy_data_chunk(&mut self.ptr) }; 83 | } 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod test { 89 | use crate::{DataChunk, LogicalType}; 90 | 91 | #[test] 92 | fn test_data_chunk_construction() { 93 | let dc = DataChunk::new(vec![LogicalType::new( 94 | crate::constants::LogicalTypeId::Integer, 95 | )]); 96 | 97 | assert_eq!(dc.get_column_count(), 1); 98 | 99 | drop(dc); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/database.rs: -------------------------------------------------------------------------------- 1 | use crate::database::DatabaseOwnership::{Borrowed, Owned}; 2 | use crate::duckly::{ 3 | duckdb_add_replacement_scan, duckdb_close, duckdb_connect, duckdb_connection, duckdb_database, 4 | duckdb_delete_callback_t, duckdb_open, duckdb_open_ext, duckdb_replacement_callback_t, 5 | }; 6 | use crate::Connection; 7 | use crate::{check, Config}; 8 | use std::error::Error; 9 | use std::ffi::{c_void, CString}; 10 | use std::ptr::{addr_of, addr_of_mut, null_mut}; 11 | 12 | /// Equivalent of [`DatabaseData`](https://github.com/duckdb/duckdb/blob/50951241de3d9c06fac5719dcb907eb21163dcab/src/include/duckdb/main/capi_internal.hpp#L27), wraps `duckdb::DuckDB` 13 | #[repr(C)] 14 | #[derive(Debug)] 15 | struct Wrapper { 16 | instance: *const c_void, 17 | } 18 | 19 | #[derive(Debug)] 20 | enum DatabaseOwnership { 21 | Owned(duckdb_database), 22 | Borrowed(Wrapper), 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct Database(DatabaseOwnership); 27 | 28 | impl Database { 29 | pub fn new() -> Result> { 30 | let mut db: duckdb_database = null_mut(); 31 | 32 | let filename = CString::new(":memory:")?; 33 | unsafe { 34 | check!(duckdb_open(filename.as_ptr(), &mut db)); 35 | } 36 | Ok(Self(Owned(db))) 37 | } 38 | 39 | pub fn new_with_config(config: &Config) -> Result> { 40 | let mut db: duckdb_database = null_mut(); 41 | let error = CString::new("")?; 42 | let filename = CString::new(":memory:")?; 43 | let mut out_error = error.as_ptr().cast_mut(); 44 | unsafe { 45 | check!(duckdb_open_ext( 46 | filename.as_ptr(), 47 | &mut db, 48 | config.0, 49 | addr_of_mut!(out_error) 50 | )); 51 | } 52 | Ok(Self(Owned(db))) 53 | } 54 | 55 | /// Construct a [`Database`] instance from a pointer passed to an extensions `init` function 56 | pub fn from_cpp_duckdb(ptr: *mut c_void) -> Self { 57 | Self(Borrowed(Wrapper { instance: ptr })) 58 | } 59 | 60 | pub fn connect(&self) -> Result> { 61 | let mut connection: duckdb_connection = null_mut(); 62 | 63 | let db = self.get_ptr(); 64 | 65 | unsafe { 66 | check!(duckdb_connect(db, &mut connection)); 67 | } 68 | 69 | Ok(Connection::from(connection)) 70 | } 71 | 72 | fn get_ptr(&self) -> duckdb_database { 73 | match &self.0 { 74 | Borrowed(wrapper) => addr_of!(wrapper) as duckdb_database, 75 | Owned(ptr) => *ptr, 76 | } 77 | } 78 | 79 | /// Add a replacement scan definition to the specified database 80 | /// 81 | /// # Safety 82 | /// The `extra_data` arg should live as long as the database 83 | /// 84 | /// # Arguments 85 | /// * `replacement`: The replacement scan callback 86 | /// * `extra_data`: Extra data that is passed back into the specified callback 87 | /// * `delete_callback`: The delete callback to call on the extra data, if any 88 | pub unsafe fn add_replacement_scan( 89 | &self, 90 | replacement: duckdb_replacement_callback_t, 91 | extra_data: *mut c_void, 92 | delete_callback: duckdb_delete_callback_t, 93 | ) { 94 | duckdb_add_replacement_scan(self.get_ptr(), replacement, extra_data, delete_callback); 95 | } 96 | } 97 | 98 | impl Drop for Database { 99 | fn drop(&mut self) { 100 | if let Owned(mut ptr) = self.0 { 101 | unsafe { duckdb_close(&mut ptr) } 102 | } 103 | } 104 | } 105 | 106 | #[cfg(test)] 107 | mod test { 108 | use crate::database::Database; 109 | use crate::{Config, Connection}; 110 | use std::any::{Any, TypeId}; 111 | use std::error::Error; 112 | use std::ptr::null_mut; 113 | 114 | #[test] 115 | fn test_database_creation() -> Result<(), Box> { 116 | let db = Database::new()?; 117 | let conn = db.connect()?; 118 | 119 | drop(db); 120 | 121 | assert_eq!(conn.type_id(), TypeId::of::()); 122 | 123 | drop(conn); 124 | 125 | Ok(()) 126 | } 127 | 128 | #[test] 129 | fn test_with_config() -> Result<(), Box> { 130 | let config = Config::new()?; 131 | 132 | let db = Database::new_with_config(&config)?; 133 | 134 | assert_ne!(db.get_ptr(), null_mut()); 135 | 136 | Ok(()) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/duckly.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(unused)] 5 | #![allow(improper_ctypes)] 6 | #![allow(clippy::upper_case_acronyms)] 7 | #![allow(rustdoc::broken_intra_doc_links)] 8 | 9 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 10 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /// Asserts that the given expression returns DuckDBSuccess, else panics and prints the expression 2 | #[macro_export] 3 | macro_rules! check { 4 | ($x:expr) => {{ 5 | if ($x != $crate::duckly::duckdb_state_DuckDBSuccess) { 6 | Err(format!("failed call: {}", stringify!($x)))?; 7 | } 8 | }}; 9 | } 10 | 11 | /// Returns a `*const c_char` pointer to the given string 12 | #[macro_export] 13 | macro_rules! as_string { 14 | ($x:expr) => { 15 | std::ffi::CString::new($x) 16 | .expect("c string") 17 | .as_ptr() 18 | .cast::() 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(rustdoc::bare_urls)] 2 | #![warn(rustdoc::invalid_html_tags)] 3 | #![warn(rustdoc::private_doc_tests)] 4 | #![warn(rustdoc::missing_crate_level_docs)] 5 | #![deny(rustdoc::private_intra_doc_links)] 6 | #![deny(rustdoc::broken_intra_doc_links)] 7 | #![deny(unused_unsafe)] 8 | 9 | //! This crate facilitates development of DuckDB extensions using Rust 10 | 11 | mod config; 12 | mod connection; 13 | mod constants; 14 | mod data_chunk; 15 | mod database; 16 | pub mod duckly; 17 | mod error; 18 | mod logical_type; 19 | pub mod table_functions; 20 | mod value; 21 | mod vector; 22 | 23 | use std::mem::size_of; 24 | 25 | pub use crate::config::{get_configs, Config, ConfigItem, ConfigList}; 26 | pub use crate::connection::Connection; 27 | pub use crate::constants::LogicalTypeId; 28 | pub use crate::data_chunk::DataChunk; 29 | pub use crate::database::Database; 30 | pub use crate::logical_type::LogicalType; 31 | pub use crate::value::Value; 32 | pub use crate::vector::Vector; 33 | 34 | use crate::duckly::duckdb_malloc; 35 | 36 | /// # Safety 37 | /// This function is obviously unsafe 38 | pub unsafe fn malloc_struct() -> *mut T { 39 | duckdb_malloc(size_of::()).cast::() 40 | } 41 | -------------------------------------------------------------------------------- /src/logical_type.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::LogicalTypeId; 2 | use crate::duckly::{ 3 | duckdb_create_list_type, duckdb_create_logical_type, duckdb_create_map_type, 4 | duckdb_create_struct_type, duckdb_create_union, duckdb_destroy_logical_type, 5 | duckdb_get_type_id, duckdb_logical_type, idx_t, 6 | }; 7 | use num_traits::FromPrimitive; 8 | use std::collections::HashMap; 9 | use std::ffi::{c_char, CString}; 10 | use std::ops::Deref; 11 | 12 | /// Represents a logical type in the database - the underlying physical type can differ depending on the implementation 13 | #[derive(Debug)] 14 | pub struct LogicalType { 15 | pub(crate) typ: duckdb_logical_type, 16 | } 17 | 18 | impl LogicalType { 19 | pub fn new(typ: LogicalTypeId) -> Self { 20 | unsafe { 21 | Self { 22 | typ: duckdb_create_logical_type(typ as u32), 23 | } 24 | } 25 | } 26 | /// Creates a map type from its key type and value type. 27 | /// 28 | /// # Arguments 29 | /// * `type`: The key type and value type of map type to create. 30 | /// * `returns`: The logical type. 31 | pub fn new_map_type(key: &LogicalType, value: &LogicalType) -> Self { 32 | unsafe { 33 | Self { 34 | typ: duckdb_create_map_type(key.typ, value.typ), 35 | } 36 | } 37 | } 38 | /// Creates a list type from its child type. 39 | /// 40 | /// # Arguments 41 | /// * `type`: The child type of list type to create. 42 | /// * `returns`: The logical type. 43 | pub fn new_list_type(child_type: &LogicalType) -> Self { 44 | unsafe { 45 | Self { 46 | typ: duckdb_create_list_type(child_type.typ), 47 | } 48 | } 49 | } 50 | /// Make `LogicalType` for `struct` 51 | /// 52 | /// # Argument 53 | /// `shape` should be the fields and types in the `struct` 54 | pub fn new_struct_type(shape: HashMap<&str, LogicalType>) -> Self { 55 | Self::make_meta_type(shape, duckdb_create_struct_type) 56 | } 57 | /// Make `LogicalType` for `union` 58 | /// 59 | /// # Argument 60 | /// `shape` should be the variants in the `union` 61 | pub fn new_union_type(shape: HashMap<&str, LogicalType>) -> Self { 62 | Self::make_meta_type(shape, duckdb_create_union) 63 | } 64 | 65 | fn make_meta_type( 66 | shape: HashMap<&str, LogicalType>, 67 | x: unsafe extern "C" fn( 68 | nmembers: idx_t, 69 | names: *mut *const c_char, 70 | types: *const duckdb_logical_type, 71 | ) -> duckdb_logical_type, 72 | ) -> LogicalType { 73 | let keys: Vec = shape 74 | .keys() 75 | .map(|it| CString::new(it.deref()).unwrap()) 76 | .collect(); 77 | let values: Vec = shape.values().map(|it| it.typ).collect(); 78 | let name_ptrs = keys 79 | .iter() 80 | .map(|it| it.as_ptr()) 81 | .collect::>(); 82 | 83 | unsafe { 84 | Self { 85 | typ: x( 86 | shape.len().try_into().unwrap(), 87 | name_ptrs.as_slice().as_ptr().cast_mut(), 88 | values.as_slice().as_ptr(), 89 | ), 90 | } 91 | } 92 | } 93 | 94 | /// Retrieves the type class of a `duckdb_logical_type`. 95 | /// 96 | /// # Arguments 97 | /// * `returns`: The type id 98 | pub fn type_id(&self) -> LogicalTypeId { 99 | let id = unsafe { duckdb_get_type_id(self.typ) }; 100 | 101 | FromPrimitive::from_u32(id).unwrap() 102 | } 103 | } 104 | impl Clone for LogicalType { 105 | fn clone(&self) -> Self { 106 | let type_id = self.type_id(); 107 | 108 | Self::new(type_id) 109 | } 110 | } 111 | 112 | impl From for LogicalType { 113 | fn from(ptr: duckdb_logical_type) -> Self { 114 | Self { typ: ptr } 115 | } 116 | } 117 | 118 | impl Drop for LogicalType { 119 | fn drop(&mut self) { 120 | unsafe { 121 | duckdb_destroy_logical_type(&mut self.typ); 122 | } 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod test { 128 | use crate::constants::LogicalTypeId; 129 | use crate::LogicalType; 130 | use std::collections::HashMap; 131 | #[test] 132 | fn test_logi() { 133 | let key = LogicalType::new(LogicalTypeId::Varchar); 134 | 135 | let value = LogicalType::new(LogicalTypeId::Utinyint); 136 | 137 | let map = LogicalType::new_map_type(&key, &value); 138 | 139 | assert_eq!(map.type_id(), LogicalTypeId::Map); 140 | 141 | let union_ = LogicalType::new_union_type(HashMap::from([ 142 | ("number", LogicalType::new(LogicalTypeId::Bigint)), 143 | ("string", LogicalType::new(LogicalTypeId::Varchar)), 144 | ])); 145 | assert_eq!(union_.type_id(), LogicalTypeId::Union); 146 | 147 | let struct_ = LogicalType::new_struct_type(HashMap::from([ 148 | ("number", LogicalType::new(LogicalTypeId::Bigint)), 149 | ("string", LogicalType::new(LogicalTypeId::Varchar)), 150 | ])); 151 | assert_eq!(struct_.type_id(), LogicalTypeId::Struct); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/table_functions/bind_info.rs: -------------------------------------------------------------------------------- 1 | use crate::duckly::{ 2 | duckdb_bind_add_result_column, duckdb_bind_get_extra_info, duckdb_bind_get_parameter, 3 | duckdb_bind_get_parameter_count, duckdb_bind_info, duckdb_bind_set_bind_data, 4 | duckdb_bind_set_cardinality, duckdb_bind_set_error, idx_t, 5 | }; 6 | #[allow(unused)] 7 | use crate::table_functions::TableFunction; 8 | use crate::{as_string, LogicalType, Value}; 9 | use std::ffi::c_void; 10 | use std::os::raw::c_char; 11 | 12 | /// An interface to store and retrieve data during the function bind stage 13 | #[derive(Debug)] 14 | pub struct BindInfo { 15 | ptr: *mut c_void, 16 | } 17 | 18 | impl BindInfo { 19 | /// Adds a result column to the output of the table function. 20 | /// 21 | /// # Arguments 22 | /// * `name`: The name of the column 23 | /// * `type`: The logical type of the column 24 | pub fn add_result_column(&self, column_name: &str, column_type: LogicalType) { 25 | unsafe { 26 | duckdb_bind_add_result_column(self.ptr, as_string!(column_name), column_type.typ); 27 | } 28 | } 29 | /// Report that an error has occurred while calling bind. 30 | /// 31 | /// # Arguments 32 | /// * `error`: The error message 33 | pub fn set_error(&self, error: &str) { 34 | unsafe { 35 | duckdb_bind_set_error(self.ptr, as_string!(error)); 36 | } 37 | } 38 | /// Sets the user-provided bind data in the bind object. This object can be retrieved again during execution. 39 | /// 40 | /// # Arguments 41 | /// * `extra_data`: The bind data object. 42 | /// * `destroy`: The callback that will be called to destroy the bind data (if any) 43 | /// 44 | /// # Safety 45 | /// 46 | pub unsafe fn set_bind_data( 47 | &self, 48 | data: *mut c_void, 49 | free_function: Option, 50 | ) { 51 | duckdb_bind_set_bind_data(self.ptr, data, free_function); 52 | } 53 | /// Retrieves the number of regular (non-named) parameters to the function. 54 | pub fn get_parameter_count(&self) -> u64 { 55 | unsafe { duckdb_bind_get_parameter_count(self.ptr) } 56 | } 57 | /// Retrieves the parameter at the given index. 58 | /// 59 | /// # Arguments 60 | /// * `index`: The index of the parameter to get 61 | /// 62 | /// returns: The value of the parameter 63 | pub fn get_parameter(&self, param_index: u64) -> Value { 64 | unsafe { Value::from(duckdb_bind_get_parameter(self.ptr, param_index)) } 65 | } 66 | 67 | /// Sets the cardinality estimate for the table function, used for optimization. 68 | /// 69 | /// # Arguments 70 | /// * `cardinality`: The cardinality estimate 71 | /// * `is_exact`: Whether or not the cardinality estimate is exact, or an approximation 72 | pub fn set_cardinality(&self, cardinality: idx_t, is_exact: bool) { 73 | unsafe { duckdb_bind_set_cardinality(self.ptr, cardinality, is_exact) } 74 | } 75 | /// Retrieves the extra info of the function as set in [`TableFunction::set_extra_info`] 76 | /// 77 | /// # Arguments 78 | /// * `returns`: The extra info 79 | pub fn get_extra_info(&self) -> *const T { 80 | unsafe { duckdb_bind_get_extra_info(self.ptr).cast() } 81 | } 82 | } 83 | 84 | impl From for BindInfo { 85 | fn from(ptr: duckdb_bind_info) -> Self { 86 | Self { ptr } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/table_functions/function_info.rs: -------------------------------------------------------------------------------- 1 | use crate::as_string; 2 | use crate::duckly::{ 3 | duckdb_function_get_bind_data, duckdb_function_get_extra_info, duckdb_function_get_init_data, 4 | duckdb_function_get_local_init_data, duckdb_function_info, duckdb_function_set_error, 5 | }; 6 | #[allow(unused)] 7 | use crate::table_functions::{BindInfo, InitInfo, TableFunction}; 8 | use std::os::raw::c_char; 9 | 10 | /// An interface to store and retrieve data during the function execution stage 11 | #[derive(Debug)] 12 | pub struct FunctionInfo(duckdb_function_info); 13 | 14 | impl FunctionInfo { 15 | /// Report that an error has occurred while executing the function. 16 | /// 17 | /// # Arguments 18 | /// * `error`: The error message 19 | pub fn set_error(&self, error: &str) { 20 | unsafe { 21 | duckdb_function_set_error(self.0, as_string!(error)); 22 | } 23 | } 24 | /// Gets the bind data set by [`BindInfo::set_bind_data`] during the bind. 25 | /// 26 | /// Note that the bind data should be considered as read-only. 27 | /// For tracking state, use the init data instead. 28 | /// 29 | /// # Arguments 30 | /// * `returns`: The bind data object 31 | pub fn get_bind_data(&self) -> *mut T { 32 | unsafe { duckdb_function_get_bind_data(self.0).cast() } 33 | } 34 | /// Gets the init data set by [`InitInfo::set_init_data`] during the init. 35 | /// 36 | /// # Arguments 37 | /// * `returns`: The init data object 38 | pub fn get_init_data(&self) -> *mut T { 39 | unsafe { duckdb_function_get_init_data(self.0).cast() } 40 | } 41 | /// Retrieves the extra info of the function as set in [`TableFunction::set_extra_info`] 42 | /// 43 | /// # Arguments 44 | /// * `returns`: The extra info 45 | pub fn get_extra_info(&self) -> *mut T { 46 | unsafe { duckdb_function_get_extra_info(self.0).cast() } 47 | } 48 | /// Gets the thread-local init data set by [`InitInfo::set_init_data`] during the local_init. 49 | /// 50 | /// # Arguments 51 | /// * `returns`: The init data object 52 | pub fn get_local_init_data(&self) -> *mut T { 53 | unsafe { duckdb_function_get_local_init_data(self.0).cast() } 54 | } 55 | } 56 | 57 | impl From for FunctionInfo { 58 | fn from(ptr: duckdb_function_info) -> Self { 59 | Self(ptr) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/table_functions/init_info.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | use crate::table_functions::{BindInfo, TableFunction}; 3 | use std::ffi::{c_void, CString}; 4 | 5 | use crate::duckly::{ 6 | duckdb_init_get_bind_data, duckdb_init_get_column_count, duckdb_init_get_column_index, 7 | duckdb_init_get_extra_info, duckdb_init_info, duckdb_init_set_error, duckdb_init_set_init_data, 8 | duckdb_init_set_max_threads, idx_t, 9 | }; 10 | 11 | /// An interface to store and retrieve data during the function init stage 12 | #[derive(Debug)] 13 | pub struct InitInfo(duckdb_init_info); 14 | 15 | impl From for InitInfo { 16 | fn from(ptr: duckdb_init_info) -> Self { 17 | Self(ptr) 18 | } 19 | } 20 | 21 | impl InitInfo { 22 | /// # Safety 23 | pub unsafe fn set_init_data( 24 | &self, 25 | data: *mut c_void, 26 | freeer: Option, 27 | ) { 28 | duckdb_init_set_init_data(self.0, data, freeer); 29 | } 30 | 31 | /// Returns the column indices of the projected columns at the specified positions. 32 | /// 33 | /// This function must be used if projection pushdown is enabled to figure out which columns to emit. 34 | /// 35 | /// returns: The column indices at which to get the projected column index 36 | pub fn get_column_indices(&self) -> Vec { 37 | let mut indices; 38 | unsafe { 39 | let column_count = duckdb_init_get_column_count(self.0); 40 | indices = Vec::with_capacity(column_count as usize); 41 | for i in 0..column_count { 42 | indices.push(duckdb_init_get_column_index(self.0, i)) 43 | } 44 | } 45 | indices 46 | } 47 | 48 | /// Retrieves the extra info of the function as set in [`TableFunction::set_extra_info`] 49 | /// 50 | /// # Arguments 51 | /// * `returns`: The extra info 52 | pub fn get_extra_info(&self) -> *const T { 53 | unsafe { duckdb_init_get_extra_info(self.0).cast() } 54 | } 55 | /// Gets the bind data set by [`BindInfo::set_bind_data`] during the bind. 56 | /// 57 | /// Note that the bind data should be considered as read-only. 58 | /// For tracking state, use the init data instead. 59 | /// 60 | /// # Arguments 61 | /// * `returns`: The bind data object 62 | pub fn get_bind_data(&self) -> *const T { 63 | unsafe { duckdb_init_get_bind_data(self.0).cast() } 64 | } 65 | /// Sets how many threads can process this table function in parallel (default: 1) 66 | /// 67 | /// # Arguments 68 | /// * `max_threads`: The maximum amount of threads that can process this table function 69 | pub fn set_max_threads(&self, max_threads: idx_t) { 70 | unsafe { duckdb_init_set_max_threads(self.0, max_threads) } 71 | } 72 | /// Report that an error has occurred while calling init. 73 | /// 74 | /// # Arguments 75 | /// * `error`: The error message 76 | pub fn set_error(&self, error: CString) { 77 | unsafe { duckdb_init_set_error(self.0, error.as_ptr()) } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/table_functions/mod.rs: -------------------------------------------------------------------------------- 1 | /// A table function is a function that returns a queryable table 2 | mod bind_info; 3 | mod function_info; 4 | mod init_info; 5 | mod replacement_scan; 6 | mod table_function; 7 | #[cfg(test)] 8 | mod test_integration; 9 | 10 | pub use self::bind_info::BindInfo; 11 | pub use self::function_info::FunctionInfo; 12 | pub use self::init_info::InitInfo; 13 | pub use self::replacement_scan::ReplacementScanInfo; 14 | pub use self::table_function::TableFunction; 15 | -------------------------------------------------------------------------------- /src/table_functions/replacement_scan.rs: -------------------------------------------------------------------------------- 1 | /// A replacement scan is a way to pretend that a table exists in DuckDB 2 | /// For example, you can do the following: 3 | /// ```sql 4 | /// SELECT * from "hello.csv" 5 | /// ``` 6 | /// and DuckDB will realise that you're referring to a CSV file, and read that instead 7 | use crate::{ 8 | duckly::{ 9 | duckdb_replacement_scan_add_parameter, duckdb_replacement_scan_info, 10 | duckdb_replacement_scan_set_error, duckdb_replacement_scan_set_function_name, 11 | }, 12 | Value, 13 | }; 14 | use std::ffi::CString; 15 | pub struct ReplacementScanInfo(pub(crate) duckdb_replacement_scan_info); 16 | 17 | impl ReplacementScanInfo { 18 | /// Sets the replacement function name to use. If this function is called in the replacement callback, the replacement scan is performed. If it is not called, the replacement callback is not performed. 19 | pub fn set_function_name(&mut self, function_name: &str) { 20 | unsafe { 21 | let function_name = CString::new(function_name).unwrap(); 22 | duckdb_replacement_scan_set_function_name(self.0, function_name.as_ptr()); 23 | } 24 | } 25 | /// Adds a parameter to the replacement scan function. 26 | pub fn add_parameter(&mut self, parameter: Value) { 27 | unsafe { 28 | duckdb_replacement_scan_add_parameter(self.0, parameter.0); 29 | } 30 | } 31 | /// Report that an error has occurred while executing the replacement scan. 32 | pub fn set_error(&mut self, error: &str) { 33 | unsafe { 34 | let error = CString::new(error).unwrap(); 35 | duckdb_replacement_scan_set_error(self.0, error.as_ptr()); 36 | } 37 | } 38 | } 39 | 40 | impl From for ReplacementScanInfo { 41 | fn from(value: duckdb_replacement_scan_info) -> Self { 42 | Self(value) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/table_functions/table_function.rs: -------------------------------------------------------------------------------- 1 | use crate::duckly::{ 2 | duckdb_create_table_function, duckdb_delete_callback_t, duckdb_destroy_table_function, 3 | duckdb_table_function, duckdb_table_function_add_parameter, duckdb_table_function_init_t, 4 | duckdb_table_function_set_bind, duckdb_table_function_set_extra_info, 5 | duckdb_table_function_set_function, duckdb_table_function_set_init, 6 | duckdb_table_function_set_local_init, duckdb_table_function_set_name, 7 | duckdb_table_function_supports_projection_pushdown, 8 | }; 9 | use crate::logical_type::LogicalType; 10 | #[allow(unused)] 11 | use crate::table_functions::InitInfo; 12 | use std::ffi::{c_void, CString}; 13 | 14 | /// A function that returns a queryable table 15 | #[derive(Debug)] 16 | pub struct TableFunction { 17 | pub(crate) ptr: duckdb_table_function, 18 | } 19 | 20 | impl Drop for TableFunction { 21 | fn drop(&mut self) { 22 | unsafe { 23 | duckdb_destroy_table_function(&mut self.ptr); 24 | } 25 | } 26 | } 27 | 28 | impl TableFunction { 29 | /// Sets whether or not the given table function supports projection pushdown. 30 | /// 31 | /// If this is set to true, the system will provide a list of all required columns in the `init` stage through 32 | /// the [`InitInfo::get_column_indices`] method. 33 | /// If this is set to false (the default), the system will expect all columns to be projected. 34 | /// 35 | /// # Arguments 36 | /// * `pushdown`: True if the table function supports projection pushdown, false otherwise. 37 | pub fn supports_pushdown(&self, supports: bool) -> &Self { 38 | unsafe { 39 | duckdb_table_function_supports_projection_pushdown(self.ptr, supports); 40 | } 41 | self 42 | } 43 | 44 | /// Adds a parameter to the table function. 45 | /// 46 | /// # Arguments 47 | /// * `logical_type`: The type of the parameter to add. 48 | pub fn add_parameter(&self, logical_type: &LogicalType) -> &Self { 49 | unsafe { 50 | duckdb_table_function_add_parameter(self.ptr, logical_type.typ); 51 | } 52 | self 53 | } 54 | 55 | /// Sets the main function of the table function 56 | /// 57 | /// # Arguments 58 | /// * `function`: The function 59 | pub fn set_function( 60 | &self, 61 | func: Option, 62 | ) -> &Self { 63 | unsafe { 64 | duckdb_table_function_set_function(self.ptr, func); 65 | } 66 | self 67 | } 68 | 69 | /// Sets the init function of the table function 70 | /// 71 | /// # Arguments 72 | /// * `function`: The init function 73 | pub fn set_init(&self, init_func: Option) -> &Self { 74 | unsafe { 75 | duckdb_table_function_set_init(self.ptr, init_func); 76 | } 77 | self 78 | } 79 | 80 | /// Sets the bind function of the table function 81 | /// 82 | /// # Arguments 83 | /// * `function`: The bind function 84 | pub fn set_bind(&self, bind_func: Option) -> &Self { 85 | unsafe { 86 | duckdb_table_function_set_bind(self.ptr, bind_func); 87 | } 88 | self 89 | } 90 | 91 | /// Creates a new empty table function. 92 | pub fn new() -> Self { 93 | Self { 94 | ptr: unsafe { duckdb_create_table_function() }, 95 | } 96 | } 97 | 98 | /// Sets the name of the given table function. 99 | /// 100 | /// # Arguments 101 | /// * `name`: The name of the table function 102 | pub fn set_name(&self, name: &str) -> &TableFunction { 103 | unsafe { 104 | let string = CString::from_vec_unchecked(name.as_bytes().into()); 105 | duckdb_table_function_set_name(self.ptr, string.as_ptr()); 106 | } 107 | self 108 | } 109 | 110 | /// Assigns extra information to the table function that can be fetched during binding, etc. 111 | /// 112 | /// # Arguments 113 | /// * `extra_info`: The extra information 114 | /// * `destroy`: The callback that will be called to destroy the bind data (if any) 115 | /// 116 | /// # Safety 117 | pub unsafe fn set_extra_info( 118 | &self, 119 | extra_info: *mut c_void, 120 | destroy: duckdb_delete_callback_t, 121 | ) { 122 | duckdb_table_function_set_extra_info(self.ptr, extra_info, destroy); 123 | } 124 | 125 | /// Sets the thread-local init function of the table function 126 | /// 127 | /// # Arguments 128 | /// * `init`: The init function 129 | pub fn set_local_init(&self, init: duckdb_table_function_init_t) { 130 | unsafe { duckdb_table_function_set_local_init(self.ptr, init) }; 131 | } 132 | } 133 | impl Default for TableFunction { 134 | fn default() -> Self { 135 | Self::new() 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/table_functions/test_integration.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::LogicalTypeId; 2 | use crate::database::Database; 3 | use crate::duckly::{ 4 | duckdb_bind_info, duckdb_data_chunk, duckdb_function_info, duckdb_init_info, duckdb_query, 5 | }; 6 | use crate::duckly::{ 7 | duckdb_destroy_result, duckdb_free, duckdb_result, duckdb_result_error, 8 | duckdb_state_DuckDBError, duckdb_value_varchar, 9 | }; 10 | use crate::table_functions::{BindInfo, FunctionInfo, InitInfo, TableFunction}; 11 | use crate::{malloc_struct, DataChunk, LogicalType}; 12 | use std::error::Error; 13 | use std::ffi::{CStr, CString}; 14 | use std::mem; 15 | use std::ptr::{null, null_mut}; 16 | 17 | #[repr(C)] 18 | struct TestInitInfo { 19 | done: bool, 20 | } 21 | 22 | unsafe extern "C" fn func(info: duckdb_function_info, output: duckdb_data_chunk) { 23 | let info = FunctionInfo::from(info); 24 | let output = DataChunk::from(output); 25 | 26 | let init_info = info.get_init_data::(); 27 | 28 | if (*init_info).done { 29 | output.set_size(0); 30 | } else { 31 | (*init_info).done = true; 32 | 33 | let vector = output.get_vector::<&str>(0); 34 | 35 | let string = CString::new("hello world").expect("unable to build string"); 36 | vector.assign_string_element(0, string.as_ptr()); 37 | 38 | output.set_size(1); 39 | } 40 | } 41 | 42 | unsafe extern "C" fn init(info: duckdb_init_info) { 43 | let info = InitInfo::from(info); 44 | 45 | let data = malloc_struct::(); 46 | 47 | (*data).done = false; 48 | 49 | info.set_init_data(data.cast(), Some(duckdb_free)) 50 | } 51 | 52 | unsafe extern "C" fn bind(info: duckdb_bind_info) { 53 | let info = BindInfo::from(info); 54 | 55 | info.add_result_column("column0", LogicalType::new(LogicalTypeId::Varchar)); 56 | 57 | let param = info.get_parameter(0).get_varchar(); 58 | 59 | assert_eq!("hello.json", param.to_str().unwrap()); 60 | } 61 | 62 | #[test] 63 | fn test_database_creation() -> Result<(), Box> { 64 | let db = Database::new()?; 65 | let conn = db.connect()?; 66 | 67 | let table_function = TableFunction::default(); 68 | table_function 69 | .add_parameter(&LogicalType::new(LogicalTypeId::Json)) 70 | .set_name("read_json") 71 | .supports_pushdown(false) 72 | .set_function(Some(func)) 73 | .set_init(Some(init)) 74 | .set_bind(Some(bind)); 75 | conn.register_table_function(table_function)?; 76 | 77 | let query = CString::new("select * from read_json('hello.json')")?; 78 | 79 | unsafe { 80 | let mut result: duckdb_result = mem::zeroed(); 81 | let connection = conn.get_ptr(); 82 | 83 | assert_ne!(connection, null_mut()); 84 | 85 | if duckdb_query(connection, query.as_ptr(), &mut result) == duckdb_state_DuckDBError { 86 | let error = duckdb_result_error(&mut result); 87 | assert_ne!(error, null()); 88 | let error = CStr::from_ptr(error); 89 | 90 | panic!("error: {}", error.to_str().unwrap()); 91 | } 92 | 93 | let ptr = duckdb_value_varchar(&mut result, 0, 0); 94 | assert_ne!(ptr, null_mut()); 95 | let value = CStr::from_ptr(ptr); 96 | 97 | assert_eq!(value.to_str()?, "hello world"); 98 | 99 | duckdb_free(ptr.cast()); 100 | 101 | duckdb_destroy_result(&mut result); 102 | }; 103 | 104 | drop(conn); 105 | 106 | drop(db); 107 | 108 | Ok(()) 109 | } 110 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | use crate::duckly::{duckdb_destroy_value, duckdb_get_varchar, duckdb_value}; 2 | use std::ffi::CString; 3 | 4 | /// The Value object holds a single arbitrary value of any type that can be 5 | /// stored in the database. 6 | #[derive(Debug)] 7 | pub struct Value(pub(crate) duckdb_value); 8 | 9 | impl Value { 10 | /// Obtains a string representation of the given value 11 | pub fn get_varchar(&self) -> CString { 12 | unsafe { CString::from_raw(duckdb_get_varchar(self.0)) } 13 | } 14 | } 15 | 16 | impl From for Value { 17 | fn from(ptr: duckdb_value) -> Self { 18 | Self(ptr) 19 | } 20 | } 21 | 22 | impl Drop for Value { 23 | fn drop(&mut self) { 24 | unsafe { 25 | duckdb_destroy_value(&mut self.0); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/vector.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | duckly::{ 3 | duckdb_validity_row_is_valid, duckdb_validity_set_row_invalid, 4 | duckdb_validity_set_row_valid, duckdb_validity_set_row_validity, duckdb_vector, 5 | duckdb_vector_assign_string_element, duckdb_vector_assign_string_element_len, 6 | duckdb_vector_ensure_validity_writable, duckdb_vector_get_column_type, 7 | duckdb_vector_get_data, duckdb_vector_get_validity, duckdb_vector_size, idx_t, 8 | }, 9 | LogicalType, 10 | }; 11 | use std::fmt::Debug; 12 | use std::{ffi::c_char, marker::PhantomData, slice}; 13 | 14 | /// Vector of values of a specified PhysicalType. 15 | pub struct Vector(duckdb_vector, PhantomData); 16 | 17 | impl From for Vector { 18 | fn from(ptr: duckdb_vector) -> Self { 19 | Self(ptr, PhantomData {}) 20 | } 21 | } 22 | 23 | impl Vector { 24 | /// Retrieves the data pointer of the vector. 25 | /// 26 | /// The data pointer can be used to read or write values from the vector. How to read or write values depends on the type of the vector. 27 | pub fn get_data(&self) -> *mut T { 28 | unsafe { duckdb_vector_get_data(self.0).cast() } 29 | } 30 | 31 | /// Assigns a string element in the vector at the specified location. 32 | /// 33 | /// # Arguments 34 | /// * `index` - The row position in the vector to assign the string to 35 | /// * `str` - The string 36 | /// * `str_len` - The length of the string (in bytes) 37 | /// 38 | /// # Safety 39 | pub unsafe fn assign_string_element_len( 40 | &self, 41 | index: idx_t, 42 | str_: *const c_char, 43 | str_len: idx_t, 44 | ) { 45 | duckdb_vector_assign_string_element_len(self.0, index, str_, str_len); 46 | } 47 | 48 | /// Assigns a string element in the vector at the specified location. 49 | /// 50 | /// # Arguments 51 | /// * `index` - The row position in the vector to assign the string to 52 | /// * `str` - The null-terminated string"] 53 | /// 54 | /// # Safety 55 | pub unsafe fn assign_string_element(&self, index: idx_t, str_: *const c_char) { 56 | duckdb_vector_assign_string_element(self.0, index, str_); 57 | } 58 | 59 | /// Retrieves the data pointer of the vector as a slice 60 | /// 61 | /// The data pointer can be used to read or write values from the vector. How to read or write values depends on the type of the vector. 62 | pub fn get_data_as_slice(&mut self) -> &mut [T] { 63 | let ptr = self.get_data(); 64 | unsafe { slice::from_raw_parts_mut(ptr, duckdb_vector_size() as usize) } 65 | } 66 | 67 | /// Retrieves the column type of the specified vector. 68 | pub fn get_column_type(&self) -> LogicalType { 69 | unsafe { LogicalType::from(duckdb_vector_get_column_type(self.0)) } 70 | } 71 | /// Retrieves the validity mask pointer of the specified vector. 72 | /// 73 | /// If all values are valid, this function MIGHT return NULL! 74 | /// 75 | /// The validity mask is a bitset that signifies null-ness within the data chunk. It is a series of uint64_t values, where each uint64_t value contains validity for 64 tuples. The bit is set to 1 if the value is valid (i.e. not NULL) or 0 if the value is invalid (i.e. NULL). 76 | /// 77 | /// Validity of a specific value can be obtained like this: 78 | /// 79 | /// idx_t entry_idx = row_idx / 64; idx_t idx_in_entry = row_idx % 64; bool is_valid = validity_maskentry_idx & (1 << idx_in_entry); 80 | /// 81 | /// Alternatively, the (slower) row_is_valid function can be used. 82 | /// 83 | /// returns: The pointer to the validity mask, or NULL if no validity mask is present 84 | pub fn get_validity(&self) -> ValidityMask { 85 | unsafe { ValidityMask(duckdb_vector_get_validity(self.0), duckdb_vector_size()) } 86 | } 87 | /// Ensures the validity mask is writable by allocating it. 88 | /// 89 | /// After this function is called, get_validity will ALWAYS return non-NULL. This allows null values to be written to the vector, regardless of whether a validity mask was present before. 90 | pub fn ensure_validity_writable(&self) { 91 | unsafe { duckdb_vector_ensure_validity_writable(self.0) }; 92 | } 93 | } 94 | 95 | pub struct ValidityMask(*mut u64, idx_t); 96 | 97 | impl Debug for ValidityMask { 98 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 99 | let base = (0..self.1) 100 | .map(|row| if self.row_is_valid(row) { "." } else { "X" }) 101 | .collect::>() 102 | .join(""); 103 | 104 | f.debug_struct("ValidityMask") 105 | .field("validity", &base) 106 | .finish() 107 | } 108 | } 109 | 110 | impl ValidityMask { 111 | /// Returns whether or not a row is valid (i.e. not NULL) in the given validity mask. 112 | /// 113 | /// # Arguments 114 | /// * `row`: The row index 115 | /// returns: true if the row is valid, false otherwise 116 | pub fn row_is_valid(&self, row: idx_t) -> bool { 117 | unsafe { duckdb_validity_row_is_valid(self.0, row) } 118 | } 119 | /// In a validity mask, sets a specific row to either valid or invalid. 120 | /// 121 | /// Note that ensure_validity_writable should be called before calling get_validity, to ensure that there is a validity mask to write to. 122 | /// 123 | /// # Arguments 124 | /// * `row`: The row index 125 | /// * `valid`: Whether or not to set the row to valid, or invalid 126 | pub fn set_row_validity(&self, row: idx_t, valid: bool) { 127 | unsafe { duckdb_validity_set_row_validity(self.0, row, valid) } 128 | } 129 | /// In a validity mask, sets a specific row to invalid. 130 | /// 131 | /// Equivalent to set_row_validity with valid set to false. 132 | /// 133 | /// # Arguments 134 | /// * `row`: The row index 135 | pub fn set_row_invalid(&self, row: idx_t) { 136 | unsafe { duckdb_validity_set_row_invalid(self.0, row) } 137 | } 138 | /// In a validity mask, sets a specific row to valid. 139 | /// 140 | /// Equivalent to set_row_validity with valid set to true. 141 | /// 142 | /// # Arguments 143 | /// * `row`: The row index 144 | pub fn set_row_valid(&self, row: idx_t) { 145 | unsafe { duckdb_validity_set_row_valid(self.0, row) } 146 | } 147 | } 148 | 149 | #[cfg(test)] 150 | mod test { 151 | use crate::constants::LogicalTypeId; 152 | use crate::{DataChunk, LogicalType}; 153 | 154 | #[test] 155 | fn test_vector() { 156 | let datachunk = DataChunk::new(vec![LogicalType::new(LogicalTypeId::Bigint)]); 157 | let mut vector = datachunk.get_vector::(0); 158 | let data = vector.get_data_as_slice(); 159 | 160 | data[0] = 42; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "duckdb.hpp" 2 | #include "wrapper.hpp" 3 | 4 | #include 5 | 6 | static duckdb::child_list_t 7 | getVector(idx_t n_pairs, const char *const *names, duckdb_logical_type const *types) { 8 | duckdb::child_list_t members; 9 | for (idx_t i = 0; i < n_pairs; i++) { 10 | members.emplace_back( 11 | std::string(names[i]), 12 | *(duckdb::LogicalType *) types[i] 13 | ); 14 | } 15 | return members; 16 | } 17 | 18 | extern "C" { 19 | 20 | duckdb_logical_type duckdb_create_struct_type(idx_t n_pairs, const char **names, const duckdb_logical_type *types) { 21 | auto *stype = new duckdb::LogicalType; 22 | *stype = duckdb::LogicalType::STRUCT(getVector(n_pairs, names, types)); 23 | return stype; 24 | } 25 | 26 | duckdb_logical_type duckdb_create_union(idx_t nmembers, const char **names, const duckdb_logical_type *types) { 27 | auto *utype = new duckdb::LogicalType; 28 | *utype = duckdb::LogicalType::UNION(getVector(nmembers, names, types)); 29 | return utype; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/wrapper.hpp: -------------------------------------------------------------------------------- 1 | #define DUCKDB_BUILD_LOADABLE_EXTENSION 2 | #include "duckdb.h" 3 | 4 | extern "C" { 5 | DUCKDB_EXTENSION_API duckdb_logical_type duckdb_create_union(idx_t nmembers, const char** names, const duckdb_logical_type* types); 6 | 7 | DUCKDB_EXTENSION_API duckdb_logical_type duckdb_create_struct_type(idx_t n_pairs, const char** names, const duckdb_logical_type* types); 8 | }; 9 | --------------------------------------------------------------------------------