diff --git a/.github/.codecov.yml b/.github/.codecov.yml new file mode 100644 index 00000000..35fbac03 --- /dev/null +++ b/.github/.codecov.yml @@ -0,0 +1,10 @@ +ignore: + - "libsduckdb-sys/duckdb" +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml index 51a07482..15dd69cb 100644 --- a/.github/workflows/rust.yaml +++ b/.github/workflows/rust.yaml @@ -1,33 +1,116 @@ -name: Rust build - -on: push +name: CI +on: + push: + branches: + - master + pull_request: + branches: + - master env: - CARGO_TERM_COLOR: always - + RUST_BACKTRACE: 1 jobs: - build: + test: + name: Test ${{ matrix.target }} + + strategy: + fail-fast: true + + matrix: + include: + #- { target: x86_64-pc-windows-msvc, os: windows-latest } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest } + #- { target: x86_64-apple-darwin, os: macos-latest } + #- { + #target: x86_64-pc-windows-gnu, + #os: windows-latest, + #host: -x86_64-pc-windows-gnu, + #} + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + # This has a matcher for test panics, so we use it even though elsewhere + # we use actions-rs/toolchain. + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable${{ matrix.host }} + targets: ${{ matrix.target }} + + # - run: cargo build --features bundled --workspace --all-targets --verbose + - run: cargo test --features bundled --workspace --all-targets --verbose + # - run: cargo test --features bundled --workspace --doc --verbose + + sanitizer: + name: Address Sanitizer runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # Need nightly rust. + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: nightly + components: rust-src + - name: Tests with asan + env: + RUSTFLAGS: -Zsanitizer=address + RUSTDOCFLAGS: -Zsanitizer=address + ASAN_OPTIONS: "detect_stack_use_after_return=1:detect_leaks=0" + # Work around https://github.com/rust-lang/rust/issues/59125 by + # disabling backtraces. In an ideal world we'd probably suppress the + # leak sanitization, but we don't care about backtraces here, so long + # as the other tests have them. + RUST_BACKTRACE: "0" + run: cargo -Z build-std test --features 'bundled' --target x86_64-unknown-linux-gnu + # Ensure clippy doesn't complain. + clippy: + name: Clippy + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install latest nightly - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - components: rustfmt, clippy - - name: Cache .cargo - id: cache-cargo - uses: actions/cache@v2 - with: - path: ~/.cargo - key: ${{ runner.os }}-${{ hashFiles('Cargo**') }} - - name: cargo test - uses: actions-rs/cargo@v1 - with: - command: test - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + - uses: actions/checkout@v2 + - uses: hecrj/setup-rust-action@v1 + with: + components: clippy + - run: cargo clippy --all-targets --workspace --features bundled -- -D warnings + + # Ensure patch is formatted. + fmt: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: hecrj/setup-rust-action@v1 + with: + components: rustfmt + - run: cargo fmt --all -- --check + + # Detect cases where documentation links don't resolve and such. + # doc: + # name: Docs + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v2 + # - uses: hecrj/setup-rust-action@v1 + # with: + # rust-version: nightly + # # Need to use `cargo rustdoc` to actually get it to respect -D + # # warnings... Note: this also requires nightly. + # - run: cargo rustdoc --features 'bundled' -- -D warnings + + codecov: + name: Generate code coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: hecrj/setup-rust-action@v1 + - name: Run cargo-tarpaulin + uses: actions-rs/tarpaulin@v0.1 + with: + # Intentionally omit time feature until we're on time 0.3, at which + # point it should be added to `bundled-full`. + args: '--features "bundled"' + + - name: Upload to codecov.io + uses: codecov/codecov-action@v1 diff --git a/README.md b/README.md index 67bb4cda..814c8ff2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,133 @@ -# duckdb-rs (WIP) - Ergonomic bindings to duckdb for Rust +# duckdb-rs -# TODO +[![Build Status](https://github.com/wangfenjin/duckdb-rs/workflows/CI/badge.svg)](https://github.com/wangfenjin/duckdb-rs/actions) +[![dependency status](https://deps.rs/repo/github/wangfenjin/duckdb-rs/status.svg)](https://deps.rs/repo/github/wangfenjin/duckdb-rs) +[comment]: <> ([![Latest Version](https://img.shields.io/crates/v/duckdb.svg)](https://crates.io/crates/duckdb)) +[comment]: <> ([![Docs](https://docs.rs/rusqlite/badge.svg)](https://docs.rs/rusqlite)) +[![codecov](https://codecov.io/gh/wangfenjin/duckdb-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/wangfenjin/duckdb-rs) + +duckdb-rs is an ergonomic wrapper for using [duckdb](https://github.com/duckdb/duckdb) from Rust. It attempts to expose +an interface similar to [rusqlite](https://github.com/rusqlite/rusqlite). Acctually the initial code and even this README is +forked from rusqlite as duckdb also tries to expose a sqlite3 compatible API. + +```rust +use duckdb::{params, Connection, Result}; + +#[derive(Debug)] +struct Person { + id: i32, + name: String, + data: Option>, +} + +fn main() -> Result<()> { + let conn = Connection::open_in_memory()?; + + conn.execute_batch( + r"CREATE SEQUENCE seq; + CREATE TABLE person ( + id INTEGER PRIMARY KEY DEFAULT NEXTVAL('seq'), + name TEXT NOT NULL, + data BLOB + ); + ")?; + + let me = Person { + id: 0, + name: "Steven".to_string(), + data: None, + }; + conn.execute( + "INSERT INTO person (name, data) VALUES (?, ?)", + params![me.name, me.data], + )?; + + let mut stmt = conn.prepare("SELECT id, name, data FROM person")?; + let person_iter = stmt.query_map([], |row| { + Ok(Person { + id: row.get(0)?, + name: row.get(1)?, + data: row.get(2)?, + }) + })?; + + for person in person_iter { + println!("Found person {:?}", person.unwrap()); + } + Ok(()) +} +``` + +## Notes on building rusqlite and libduckdb-sys + +`libduckdb-sys` is a separate crate from `duckdb-rs` that provides the Rust +declarations for DuckDB's C API. By default, `libduckdb-sys` attempts to find a DuckDB library that already exists on your system using pkg-config, or a +[Vcpkg](https://github.com/Microsoft/vcpkg) installation for MSVC ABI builds. + +You can adjust this behavior in a number of ways: + +* If you use the `bundled` feature, `libsqlite3-sys` will use the + [cc](https://crates.io/crates/cc) crate to compile DuckDB from source and + link against that. This source is embedded in the `libsqlite3-sys` crate and + as we are still in development, we will update it regularly. After we are more stable, + we will use the stable released version from [duckdb](https://github.com/duckdb/duckdb/releases). + This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file: + ```toml + [dependencies.duckdb] + version = "0.1" + features = ["bundled"] + ``` +* When linking against a DuckDB library already on the system (so *not* using any of the `bundled` features), you can set the `DUCKDB_LIB_DIR` environment variable to point to a directory containing the library. You can also set the `DUCKDB_INCLUDE_DIR` variable to point to the directory containing `duckdb.h`. +* Installing the duckdb development packages will usually be all that is required, but + the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs) + and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration + options. The default when using vcpkg is to dynamically link, + which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build. + + +### Binding generation + +We use [bindgen](https://crates.io/crates/bindgen) to generate the Rust +declarations from DuckDB's C header file. `bindgen` +[recommends](https://github.com/servo/rust-bindgen#library-usage-with-buildrs) +running this as part of the build process of libraries that used this. We tried +this briefly (`duckdb` 0.10.0, specifically), but it had some annoyances: + +* The build time for `libduckdb-sys` (and therefore `duckdb`) increased + dramatically. +* Running `bindgen` requires a relatively-recent version of Clang, which many + systems do not have installed by default. +* Running `bindgen` also requires the DuckDB header file to be present. + +So we try to avoid running `bindgen` at build-time by shipping +pregenerated bindings for DuckDB. + +If you use the `bundled` features, you will get pregenerated bindings for the +bundled version of DuckDB. If you want to run `bindgen` at buildtime to +produce your own bindings, use the `buildtime_bindgen` Cargo feature. + +## Contributing + +If running bindgen is problematic for you, `--features bundled` enables +bundled and all features which don't require binding generation, and can be used +instead. + +### Checklist + +- Run `cargo fmt` to ensure your Rust code is correctly formatted. +- Ensure `cargo clippy --all-targets --workspace --features bundled` passes without warnings. +- Ensure `cargo test --all-targets --workspace --features bundled` reports no failures. + +### TODOs - [x] Refactor the ErrorCode part, it's borrowed from rusqlite, we should have our own - [ ] Support more type -- [ ] Update duckdb.h +- [x] Update duckdb.h - [ ] Adjust the code examples and documentation - [x] Delete unused code / functions - [x] Add CI - [ ] Publish to crate + +## License + +DuckDB and libduckdb-sys are available under the MIT license. See the LICENSE file for more info.