diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..1809787 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,40 @@ +name: "docker build and push" + +on: + pull_request: + paths: + - .github/workflows/docker.yml + - proto/**.proto + - src/** + - Cargo.toml + - Cargo.lock + push: + tags: + - '*' + +env: + PUBLISH: ${{ github.ref_type == 'tags' }} + IMAGE: saint1991/gduck + TAG: ${{ github.ref_type == 'tags' && github.ref_name || github.sha }} + +jobs: + docker_build: + runs-on: ubuntu-latest + steps: + - name: check publish + run: echo '${{ env.PUBLISH }}' + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + - uses: docker/build-push-action@v6 + with: + context: . + push: ${{ env.PUBLISH == 'true' }} + cache-from: ${{ format('type=registry,ref={0}:{1}', env.IMAGE, 'buildcache') }} + cache-to: ${{ format('type=registry,ref={0}:{1},mode=max', env.IMAGE, 'buildcache') }} + tags: | + ${{ format('{0}:{1}', env.IMAGE, 'latest') }} + ${{ format('{0}:{1}', env.IMAGE, env.TAG) }} diff --git a/.github/workflows/lint-rust.yml b/.github/workflows/lint-rust.yml new file mode 100644 index 0000000..f6fe43c --- /dev/null +++ b/.github/workflows/lint-rust.yml @@ -0,0 +1,21 @@ +name: check-rust + +on: + pull_request: + paths: + - .github/workflows/lint-rust.yml + - **.rs + - Cargo.toml + - Cargo.lock + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Rust format check + run: cargo fmt --check diff --git a/Cargo.lock b/Cargo.lock index e042649..641166d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.90" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" +checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" dependencies = [ "backtrace", ] @@ -317,7 +317,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -328,7 +328,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -458,7 +458,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", "syn_derive", ] @@ -545,6 +545,46 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "colorchoice" version = "1.0.2" @@ -766,12 +806,13 @@ dependencies = [ ] [[package]] -name = "gduck-rpc" +name = "gduck" version = "0.1.0" dependencies = [ "anyhow", "async-stream", "chrono", + "clap", "duckdb", "env_logger", "futures-core", @@ -1356,7 +1397,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1393,7 +1434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1464,7 +1505,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.77", + "syn 2.0.86", "tempfile", ] @@ -1478,7 +1519,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1701,7 +1742,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1759,6 +1800,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.25.0" @@ -1784,7 +1831,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1797,7 +1844,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1813,9 +1860,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", @@ -1831,7 +1878,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1878,22 +1925,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1922,9 +1969,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -1944,7 +1991,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -2029,7 +2076,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -2083,7 +2130,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -2174,7 +2221,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", "wasm-bindgen-shared", ] @@ -2196,7 +2243,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2345,5 +2392,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] diff --git a/Cargo.toml b/Cargo.toml index 086fce3..66e646c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,21 @@ [package] -name = "gduck-rpc" +name = "gduck" version = "0.1.0" edition = "2021" [dependencies] -anyhow = { version = "1.0.90", features = ["backtrace", "std"] } +anyhow = { version = "1.0.92", features = ["backtrace", "std"] } async-stream = { version = "0.3.6" } chrono = { version = "0.4.38" } +clap = { version = "4.5.20", features = ["derive"] } duckdb = { version = "1.1.1", features = ["bundled", "chrono"] } env_logger = { version = "0.11.5" } futures-core = { version = "0.3.31" } log = { version = "0.4.22" } prost = { version = "0.13.3" } prost-types = { version = "0.13.3" } -thiserror = { version = "1.0.64" } -tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "sync" ] } +thiserror = { version = "1.0.66" } +tokio = { version = "1.41.0", features = ["rt-multi-thread", "macros", "sync" ] } tokio-stream = { version = "0.1.16" } tonic = { version = "0.12.3" } diff --git a/Dockerfile b/Dockerfile index 90f6f38..7d0fcdd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ # syntax=docker/dockerfile:1 -FROM rust:1.82-slim-bookworm AS builder +ARG RUST_VERSION=1.82 +FROM rust:${RUST_VERSION}-slim-bookworm AS builder WORKDIR /home/gduck RUN apt-get update -y \ @@ -11,12 +12,12 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/home/gduck/target \ cargo build --release \ && mkdir /home/gduck/dist \ - && cp /home/gduck/target/release/gduck-rpc /home/gduck/dist + && cp /home/gduck/target/release/gduck /home/gduck/dist FROM debian:bookworm-slim -COPY --from=builder /home/gduck/dist/gduck-rpc /gduck-rpc +COPY --from=builder /home/gduck/dist/gduck /gduck ENV RUST_LOG=info RUST_BACKTRACE=1 -ENTRYPOINT ["/gduck-rpc"] +ENTRYPOINT ["/gduck"] diff --git a/README.md b/README.md index 07e3b6d..78487b1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,36 @@ # gduck-rpc -PoC implementation that uses DuckDB feature via gRPC API +[![Build](https://github.com/saint1991/gduck-rpc/actions/workflows/docker.yml/badge.svg)](https://github.com/saint1991/gduck-rpc/actions/workflows/docker.yml) + +A server implementation to utilize [DuckDB](https://github.com/duckdb/duckdb) features via gRPC API. + +## Build server + +This project uses bundled DuckDB. +To build it, please follow its [instruction](https://duckdb.org/docs/dev/building/build_instructions.html). + +Then building gduck server by cargo as follows: + +```bash +cargo build +``` + +## Usage + +Starting the server by executing a binary or by cargo run. +This server uses [env_logger](https://docs.rs/env_logger/latest/env_logger/) for logging. +Setting `RUST_LOG` is required to see server messages. +In default, it listens 0.0.0.0:50051 but it can be configured by options. + +```bash +$ RUST_LOG=INFO cargo run +[2024-11-02T12:56:13Z INFO gduck] Start listening on 0.0.0.0:50051 +``` + +## Design + +gduck server has a single gRPC bidirectional streaming API `Transaction` as defined in [service.proto](./proto/service.proto). + +First message must be a `Connect` message then DuckDB connection is established according to it and then you can send any number of Query to query DuckDB. +Connection alives until gRPC connection is closed. + +Python clinet implementation is available under [client](./client/) diff --git a/src/main.rs b/src/main.rs index 1d72d6f..70b3fe6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,28 @@ mod uri; use std::net::SocketAddr; +use clap::Parser; use tonic::transport::Server; +/// gRPC server for duckdb service +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Bind address + #[arg(short, long, default_value = "0.0.0.0")] + bind_address: String, + + /// Port number to listen to + #[arg(short, long, default_value_t = 50051)] + port: i32, +} + #[tokio::main] async fn main() -> Result<(), Box> { + let args = Args::parse(); + env_logger::init(); - let addr: SocketAddr = "0.0.0.0:50051".parse()?; + let addr: SocketAddr = format!("{}:{}", args.bind_address, args.port).parse()?; let service = service::DuckDbService::new_server();