diff --git a/.github/scripts/bump-agent-versions.mjs b/.github/scripts/bump-agent-versions.mjs index e801c0225e..e58f3d572d 100644 --- a/.github/scripts/bump-agent-versions.mjs +++ b/.github/scripts/bump-agent-versions.mjs @@ -43,6 +43,7 @@ function fileContainsCommonDependency(path, moduleExists, readModuleFile) { } const nativeDriverDirectories = { + duckdb: "duckdb", oracle: "oracle-go", kingbase: "kingbase-go", }; diff --git a/.github/scripts/bump-agent-versions.test.mjs b/.github/scripts/bump-agent-versions.test.mjs new file mode 100644 index 0000000000..354c2925f6 --- /dev/null +++ b/.github/scripts/bump-agent-versions.test.mjs @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { evaluateAgentVersionBump } from "./bump-agent-versions.mjs"; + +const moduleExists = (path) => path === "agents/drivers/duckdb"; + +test("keeps the manually registered first DuckDB driver version", () => { + const result = evaluateAgentVersionBump({ + versions: { duckdb: "0.1.0" }, + prevVersions: {}, + changedFiles: ["agents/versions.json", "agents/drivers/duckdb/src/main.rs"], + moduleExists, + readModuleFile: () => "", + }); + + assert.equal(result.versions.duckdb, "0.1.0"); + assert.equal(result.changed, false); + assert.match(result.logs.join("\n"), /new module version kept at 0\.1\.0/); +}); + +test("bumps DuckDB after its initial release", () => { + const result = evaluateAgentVersionBump({ + versions: { duckdb: "0.1.0" }, + changedFiles: ["agents/drivers/duckdb/src/main.rs"], + moduleExists, + readModuleFile: () => "", + }); + + assert.equal(result.versions.duckdb, "0.1.1"); +}); diff --git a/.github/workflows/agents-release.yml b/.github/workflows/agents-release.yml index a2700199d5..a4fbe7eb09 100644 --- a/.github/workflows/agents-release.yml +++ b/.github/workflows/agents-release.yml @@ -207,6 +207,111 @@ jobs: name: kingbase-native path: "release-native/dbx-agent-kingbase-*" + build-duckdb-native: + needs: [bump-versions] + strategy: + fail-fast: false + matrix: + include: + - runner: macos-latest + target: aarch64-apple-darwin + platform: macos-aarch64 + extension: "" + - runner: macos-15-intel + target: x86_64-apple-darwin + platform: macos-x64 + extension: "" + - runner: ubuntu-22.04-arm + target: aarch64-unknown-linux-gnu + platform: linux-aarch64 + extension: "" + manylinux_image: quay.io/pypa/manylinux2014_aarch64 + - runner: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + platform: linux-x64 + extension: "" + manylinux_image: quay.io/pypa/manylinux2014_x86_64 + - runner: windows-2022 + target: aarch64-pc-windows-msvc + platform: windows-aarch64 + extension: .exe + - runner: windows-2022 + target: x86_64-win7-windows-msvc + platform: windows-x64 + extension: .exe + win7: true + runs-on: ${{ matrix.runner }} + env: + CARGO_INCREMENTAL: "0" + CARGO_TARGET_DIR: ${{ github.workspace }}/target/duckdb-driver + RUSTC_WRAPPER: sccache + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@1.97.1 + if: runner.os != 'Linux' && matrix.win7 != true + with: + targets: ${{ matrix.target }} + - name: Setup Rust for Windows 7 + if: matrix.win7 == true + uses: dtolnay/rust-toolchain@nightly + with: + toolchain: nightly-2026-07-22 + components: rust-src + - uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 + with: + version: "v0.10.0" + - name: Build DuckDB native driver + shell: bash + run: | + if [ -n "${{ matrix.manylinux_image }}" ]; then + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -e HOME=/tmp/dbx-rust-home \ + -e CARGO_INCREMENTAL=0 \ + -e CARGO_TARGET_DIR=/workspace/target/duckdb-driver \ + -v "${{ github.workspace }}:/workspace" \ + -w /workspace \ + "${{ matrix.manylinux_image }}" \ + bash -lc ' + mkdir -p "$HOME" + curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.97.1 + source "$HOME/.cargo/env" + cargo build \ + --manifest-path agents/drivers/duckdb/Cargo.toml \ + --locked \ + --release \ + --bin dbx-duckdb-driver \ + --target "${{ matrix.target }}" + ' + else + BUILD_STD=() + if [ "${{ matrix.win7 }}" = "true" ]; then + BUILD_STD=(-Z build-std=std,panic_abort) + fi + cargo build \ + --manifest-path agents/drivers/duckdb/Cargo.toml \ + --locked \ + --release \ + --bin dbx-duckdb-driver \ + --target "${{ matrix.target }}" \ + "${BUILD_STD[@]}" + fi + mkdir -p release-native + cp \ + "target/duckdb-driver/${{ matrix.target }}/release/dbx-duckdb-driver${{ matrix.extension }}" \ + "release-native/dbx-agent-duckdb-${{ matrix.platform }}${{ matrix.extension }}" + if [ -n "${{ matrix.manylinux_image }}" ]; then + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -v "${{ github.workspace }}/release-native:/driver:ro" \ + "${{ matrix.manylinux_image }}" \ + "/driver/dbx-agent-duckdb-${{ matrix.platform }}" < /dev/null + fi + - uses: actions/upload-artifact@v4 + with: + name: duckdb-native-${{ matrix.platform }} + path: "release-native/dbx-agent-duckdb-*" + build-jre: runs-on: ubuntu-latest strategy: @@ -223,12 +328,14 @@ jobs: - name: Build JRE for all platforms shell: bash run: | + sudo apt-get update + sudo apt-get install -y zstd MODULES="${{ matrix.modules }}" JLINK_OPTS="--strip-debug --no-header-files --no-man-pages --compress=zip-6" JRE_KEY="${{ matrix.jre-key }}" jlink --add-modules $MODULES $JLINK_OPTS --output dbx-jre - tar czf dbx-jre-${JRE_KEY}-linux-x64.tar.gz dbx-jre + tar cf - dbx-jre | zstd -q -19 -T0 -o dbx-jre-${JRE_KEY}-linux-x64.tar.zst rm -rf dbx-jre ADOPTIUM="https://api.adoptium.net/v3/binary/latest/${{ matrix.java-version }}/ga" @@ -269,18 +376,18 @@ jobs: CROSS_OPTS="--disable-plugin strip-native-debug-symbols" fi jlink --module-path "$JMODS" --add-modules $MODULES $JLINK_OPTS $CROSS_OPTS --output dbx-jre - tar czf dbx-jre-${JRE_KEY}-$platform.tar.gz dbx-jre + tar cf - dbx-jre | zstd -q -19 -T0 -o dbx-jre-${JRE_KEY}-$platform.tar.zst rm -rf dbx-jre jdk-$platform done - ls -lh dbx-jre-*.tar.gz + ls -lh dbx-jre-*.tar.zst - uses: actions/upload-artifact@v4 with: name: jre-${{ matrix.jre-key }} - path: "dbx-jre-*.tar.gz" + path: "dbx-jre-*.tar.zst" release: - needs: [bump-versions, build-agents, build-oracle-native, build-xugu-native, build-kingbase-native, build-jre] + needs: [bump-versions, build-agents, build-oracle-native, build-xugu-native, build-kingbase-native, build-duckdb-native, build-jre] runs-on: ubuntu-latest steps: - name: Create DBX bot release token @@ -310,7 +417,8 @@ jobs: find artifacts/oracle-native -type f -name 'dbx-agent-oracle-*' -exec cp {} release/ \; find artifacts/xugu-native -type f -name 'dbx-agent-xugu-*' -exec cp {} release/ \; find artifacts/kingbase-native -type f -name 'dbx-agent-kingbase-*' -exec cp {} release/ \; - find artifacts -name 'dbx-jre-*.tar.gz' -exec cp {} release/ \; + find artifacts/duckdb-native-* -type f -name 'dbx-agent-duckdb-*' -exec cp {} release/ \; + find artifacts -name 'dbx-jre-*.tar.zst' -exec cp {} release/ \; ls -lh release/ - name: Add versions to agent artifact filenames @@ -325,6 +433,7 @@ jobs: TAG="${GITHUB_REF_NAME}" REPO="${GITHUB_REPOSITORY}" RELEASE_VERSION="${TAG#agents-v}" + CURRENT_APP_VERSION=$(awk -F'"' '/^version = "/ { print $2; exit }' src-tauri/Cargo.toml) get_module_version() { local name="$1" @@ -349,7 +458,7 @@ jobs: "${name}": { "version": "${version}", "label": "${label}", - "min_app_version": "0.6.0", + "min_app_version": "${CURRENT_APP_VERSION}", "jre": "${jre_key}", "external_driver_required": ${external_driver}, ${native_line} @@ -359,13 +468,13 @@ jobs: } generate_native_entry() { - local name="$1" label="$2" jre_key="$3" version="$4" native_json="$5" + local name="$1" label="$2" jre_key="$3" version="$4" native_json="$5" min_app_version="$6" local legacy_jar_url="https://github.com/${REPO}/releases/download/${TAG}/dbx-agent-${name}-legacy-placeholder.jar" cat < release/agent-registry.json < /dev/null + echo "=== final agent-registry.json ===" + cat release/agent-registry.json + - name: Generate release notes env: MODULE_VERSIONS: ${{ needs.bump-versions.outputs.versions }} @@ -489,17 +610,14 @@ jobs: } for name in $(module_names); do version=$(echo "$MODULE_VERSIONS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('$name',''))") - [ -f "release/dbx-agent-${name}-${version}.jar" ] && DRIVER_NAMES+=("$name") - done - for name in oracle xugu kingbase; do - [ -f "release/dbx-agent-${name}.jar" ] && continue - compgen -G "release/dbx-agent-${name}-*" > /dev/null && DRIVER_NAMES+=("$name") + compgen -G "release/dbx-agent-${name}-${version}*.tar.zst" > /dev/null && DRIVER_NAMES+=("$name") done native_only_label() { local name="$1" case "$name" in kingbase) echo "人大金仓 KingbaseES" ;; + duckdb) echo "DuckDB" ;; oracle) echo "Oracle" ;; xugu) echo "虚谷 XuguDB" ;; *) echo "$name" ;; @@ -588,47 +706,20 @@ jobs: - name: Upload to R2 env: - MODULE_VERSIONS: ${{ needs.bump-versions.outputs.versions }} - PREV_VERSIONS: ${{ needs.bump-versions.outputs.prev_versions }} AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} R2_ENDPOINT: "https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com" R2_BUCKET: "s3://${{ secrets.R2_BUCKET_NAME }}" run: | - # Always upload agent-registry.json + # Always upload the current registry. aws s3 cp release/agent-registry.json \ "$R2_BUCKET/agents/agent-registry.json" \ --endpoint-url "$R2_ENDPOINT" echo "Uploaded agent-registry.json" - # Upload only changed driver JARs - UPLOADED=0 - SKIPPED=0 - module_names() { - echo "$MODULE_VERSIONS" | python3 -c 'import sys,json; print("\n".join(sorted(json.load(sys.stdin))))' - } - for name in $(module_names); do - old_ver=$(echo "$PREV_VERSIONS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('$name',''))" 2>/dev/null) - new_ver=$(echo "$MODULE_VERSIONS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('$name',''))" 2>/dev/null) - f="release/dbx-agent-${name}-${new_ver}.jar" - [ ! -f "$f" ] && continue - if [ "$old_ver" = "$new_ver" ] && [ -n "$old_ver" ]; then - echo "Skip $(basename $f) (unchanged $old_ver)" - SKIPPED=$((SKIPPED + 1)) - continue - fi - aws s3 cp "$f" \ - "$R2_BUCKET/agents/drivers/$(basename $f)" \ - --endpoint-url "$R2_ENDPOINT" - echo "Uploaded $(basename $f) ($old_ver → $new_ver)" - UPLOADED=$((UPLOADED + 1)) - done - echo "Drivers: $UPLOADED uploaded, $SKIPPED skipped" - - # Upload versioned native binaries and all single-driver ZIP packages. - for f in release/dbx-agent-*; do + # Upload all single-driver packages. + for f in release/dbx-agent-*.tar.zst; do [ -f "$f" ] || continue - [[ "$f" != *.jar ]] || continue aws s3 cp "$f" \ "$R2_BUCKET/agents/drivers/$(basename $f)" \ --endpoint-url "$R2_ENDPOINT" @@ -636,7 +727,7 @@ jobs: done # Upload JRE — always overwrite to ensure latest modules - for f in release/dbx-jre-*.tar.gz; do + for f in release/dbx-jre-*.tar.zst; do [ ! -f "$f" ] && continue key="agents/jre/$(basename $f)" aws s3 cp "$f" "$R2_BUCKET/$key" --endpoint-url "$R2_ENDPOINT" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4415723bc..d857141118 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,9 +141,10 @@ jobs: # sccache cannot reuse Cargo incremental artifacts, so avoid generating them in CI. CARGO_INCREMENTAL: "0" RUSTC_WRAPPER: sccache - # The fast lane skips only bundled DuckDB while retaining the other default capabilities. + # The fast lane skips only system font discovery while retaining the other default capabilities. RUST_FEATURE_MODE: ${{ github.event_name == 'pull_request' && needs.changes.outputs.rust_full != 'true' && 'fast' || 'full' }} - RUST_FAST_FEATURES: dbx/mq-admin,dbx/sqlite-sqlcipher,dbx-core/mq-admin,dbx-core/sqlite-sqlcipher,dbx-web/mq-admin,dbx-web/sqlite-sqlcipher + RUST_FAST_FEATURES: dbx/duckdb-sidecar,dbx/mq-admin,dbx/sqlite-sqlcipher,dbx-core/duckdb-sidecar,dbx-core/mq-admin,dbx-core/sqlite-sqlcipher,dbx-web/duckdb-sidecar,dbx-web/mq-admin,dbx-web/sqlite-sqlcipher + RUST_FULL_FEATURES: dbx/duckdb-sidecar,dbx/mq-admin,dbx/sqlite-sqlcipher,dbx/system-fonts,dbx-core/duckdb-sidecar,dbx-core/mq-admin,dbx-core/sqlite-sqlcipher,dbx-core/system-fonts,dbx-web/duckdb-sidecar,dbx-web/mq-admin,dbx-web/sqlite-sqlcipher,dbx-web/system-fonts # Fork PRs cannot read repository secrets, so retain the GHA backend for them. SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }} steps: @@ -203,7 +204,7 @@ jobs: if [ "$RUST_FEATURE_MODE" = "fast" ]; then cargo clippy --workspace --locked --all-targets --no-default-features --features "$RUST_FAST_FEATURES" -- -D warnings else - cargo clippy --workspace --locked --all-targets -- -D warnings + cargo clippy --workspace --locked --all-targets --no-default-features --features "$RUST_FULL_FEATURES" -- -D warnings fi - name: Show sccache stats @@ -219,9 +220,10 @@ jobs: # sccache cannot reuse Cargo incremental artifacts, so avoid generating them in CI. CARGO_INCREMENTAL: "0" RUSTC_WRAPPER: sccache - # The fast lane skips only bundled DuckDB while retaining the other default capabilities. + # The fast lane skips only system font discovery while retaining the other default capabilities. RUST_FEATURE_MODE: ${{ github.event_name == 'pull_request' && needs.changes.outputs.rust_full != 'true' && 'fast' || 'full' }} - RUST_FAST_FEATURES: dbx/mq-admin,dbx/sqlite-sqlcipher,dbx-core/mq-admin,dbx-core/sqlite-sqlcipher,dbx-web/mq-admin,dbx-web/sqlite-sqlcipher + RUST_FAST_FEATURES: dbx/duckdb-sidecar,dbx/mq-admin,dbx/sqlite-sqlcipher,dbx-core/duckdb-sidecar,dbx-core/mq-admin,dbx-core/sqlite-sqlcipher,dbx-web/duckdb-sidecar,dbx-web/mq-admin,dbx-web/sqlite-sqlcipher + RUST_FULL_FEATURES: dbx/duckdb-sidecar,dbx/mq-admin,dbx/sqlite-sqlcipher,dbx/system-fonts,dbx-core/duckdb-sidecar,dbx-core/mq-admin,dbx-core/sqlite-sqlcipher,dbx-core/system-fonts,dbx-web/duckdb-sidecar,dbx-web/mq-admin,dbx-web/sqlite-sqlcipher,dbx-web/system-fonts # Fork PRs cannot read repository secrets, so retain the GHA backend for them. SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }} steps: @@ -278,7 +280,7 @@ jobs: if [ "$RUST_FEATURE_MODE" = "fast" ]; then cargo test --workspace --locked --no-default-features --features "$RUST_FAST_FEATURES" else - cargo test --workspace --locked + cargo test --workspace --locked --no-default-features --features "$RUST_FULL_FEATURES" fi - name: Show sccache stats @@ -395,6 +397,12 @@ jobs: - 'plugins/jdbc/**' agents: - 'agents/**' + - 'crates/dbx-core/Cargo.toml' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/scripts/bump-agent-versions.mjs' + - '.github/scripts/bump-agent-versions.test.mjs' + - '.github/workflows/agents-release.yml' nix: # This fast check validates only the fixed-output pnpm dependency closure. - 'package.json' @@ -441,13 +449,6 @@ jobs: ;; esac - # Feature-gated DuckDB code must keep the full default-feature checks on its PR. - if echo "$file" | grep -Eqi 'duckdb' \ - || { [ -f "$file" ] && grep -Eqi 'duckdb-bundled|cfg[^[:cntrl:]]*duckdb|feature[^[:cntrl:]]*duckdb' "$file"; } \ - || git diff "$BASE_SHA" HEAD -- "$file" | grep -Eqi 'duckdb-bundled|cfg[^[:cntrl:]]*duckdb|feature[^[:cntrl:]]*duckdb'; then - full=true - break - fi done < <(git diff --name-only "$BASE_SHA" HEAD -- Cargo.toml Cargo.lock 'rust-toolchain*' crates src-tauri .github/workflows/ci.yml) fi @@ -476,11 +477,25 @@ jobs: with: go-version: "1.22.x" + - name: Setup Rust + uses: dtolnay/rust-toolchain@1.97.1 + - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + - name: Install packaging tools + run: | + sudo apt-get update + sudo apt-get install -y zstd + - name: Agent script tests - run: python3 -m unittest discover -s scripts -p '*_test.py' + run: | + python3 -m unittest discover -s scripts -p '*_test.py' + node --test ../.github/scripts/bump-agent-versions.test.mjs + + - name: DuckDB driver tests + working-directory: .. + run: cargo test --manifest-path agents/drivers/duckdb/Cargo.toml --locked - name: Agent validation run: python3 scripts/validate_agents.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acc69d7afc..28cc368fef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -503,7 +503,7 @@ jobs: RUSTFLAGS: ${{ matrix.rustflags }} run: | cargo zigbuild --release -p dbx-web --target ${{ matrix.target }} \ - --no-default-features --features "duckdb-bundled,mq-admin" + --no-default-features --features "duckdb-sidecar,mq-admin" - name: Package static browser runtime env: diff --git a/Cargo.lock b/Cargo.lock index 9591b00f43..037f9c4a77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,7 +96,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "const-random", "getrandom 0.3.4", "once_cell", "version_check", @@ -231,169 +230,6 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" -[[package]] -name = "arrow" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfdd0833e32a9874d2b55089333ad310c0be208aafa277385ce2461dec90be3" -dependencies = [ - "arrow-arith", - "arrow-array", - "arrow-buffer", - "arrow-cast", - "arrow-data", - "arrow-ord", - "arrow-row", - "arrow-schema", - "arrow-select", - "arrow-string", -] - -[[package]] -name = "arrow-arith" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a41203398f0eaa6f7ec8e62c0da742a21abf282c148fc157f6c35c90e29981a" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "chrono", - "num-traits", -] - -[[package]] -name = "arrow-array" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae33dad492b7df00a217563a7b0ef2874df68a0deea1b1a3acf628152f7f7a69" -dependencies = [ - "ahash 0.8.12", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "chrono", - "half", - "hashbrown 0.17.1", - "num-complex", - "num-integer", - "num-traits", -] - -[[package]] -name = "arrow-buffer" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9552f96391c005e6ab449fa941420935e7e062489b12b8b1b08879b2163f5b5" -dependencies = [ - "bytes", - "half", - "num-bigint", - "num-traits", -] - -[[package]] -name = "arrow-cast" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8a327c9649f30d8406995f27642b68df354713cca3baaaf100f076f18d5f34" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-ord", - "arrow-schema", - "arrow-select", - "atoi", - "base64 0.22.1", - "chrono", - "comfy-table", - "half", - "lexical-core", - "num-traits", - "ryu", -] - -[[package]] -name = "arrow-data" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b24852db04738907e06c04ea61e42fe7fda962a34513022dc0d0e754fb7976b" -dependencies = [ - "arrow-buffer", - "arrow-schema", - "half", - "num-integer", - "num-traits", -] - -[[package]] -name = "arrow-ord" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63a083ec750f5c043f02946b4baf05fcdbb55f4560a3277055caca5cc99f3eb0" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "arrow-select", -] - -[[package]] -name = "arrow-row" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514ba0ef0d4c5896202dae736251ce415abb43a950bed570fb7981b8716c0e4c" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "half", -] - -[[package]] -name = "arrow-schema" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ca356ad6425cecb6eb7b28e4f659f1ee7880fbb1a16127de7dd62901efee9e" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "arrow-select" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58da39eb3d8350ad4a549e5c2bc49284dac554016c69829310350f1731b0aad" -dependencies = [ - "ahash 0.8.12", - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "num-traits", -] - -[[package]] -name = "arrow-string" -version = "58.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6789b388467525e3271326b6b4915666ecfdf5142aef09779445c954b67543c" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "arrow-select", - "memchr", - "num-traits", - "regex", - "regex-syntax", -] - [[package]] name = "async-broadcast" version = "0.7.2" @@ -457,7 +293,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.4", + "rustix", "slab", "windows-sys 0.61.2", ] @@ -488,7 +324,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.1.4", + "rustix", ] [[package]] @@ -514,7 +350,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.4", + "rustix", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -595,15 +431,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - [[package]] name = "atoi_simd" version = "0.16.1" @@ -1065,7 +892,7 @@ dependencies = [ "log", "quick-xml 0.37.5", "serde", - "zip 4.6.1", + "zip", ] [[package]] @@ -1110,12 +937,6 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", ] -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cbc" version = "0.1.2" @@ -1289,17 +1110,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "comfy-table" -version = "7.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" -dependencies = [ - "crossterm", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "compression-codecs" version = "0.4.38" @@ -1574,28 +1384,6 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags 2.13.1", - "crossterm_winapi", - "parking_lot", - "rustix 0.38.44", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "crunchy" version = "0.2.4" @@ -1843,7 +1631,6 @@ dependencies = [ "csv", "dbx-core", "deadpool-postgres", - "duckdb", "font-kit", "futures", "libloading 0.8.9", @@ -1882,7 +1669,7 @@ dependencies = [ "tokio-postgres", "tokio-util", "uuid", - "zip 4.6.1", + "zip", ] [[package]] @@ -1915,7 +1702,6 @@ dependencies = [ "chrono", "csv", "deadpool-postgres", - "duckdb", "encoding_rs", "flate2", "font-kit", @@ -1966,7 +1752,8 @@ dependencies = [ "uuid", "webpki-roots 0.26.11", "winnow 1.0.4", - "zip 4.6.1", + "zip", + "zstd", ] [[package]] @@ -2341,23 +2128,6 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" -[[package]] -name = "duckdb" -version = "1.10505.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970e05eedd3f55c435194d9104f90a9b4a79a80d6e73251bc9ff43e178130c4e" -dependencies = [ - "arrow", - "cast", - "comfy-table", - "fallible-iterator 0.3.0", - "fallible-streaming-iterator", - "hashlink 0.10.0", - "libduckdb-sys", - "num-integer", - "strum", -] - [[package]] name = "dunce" version = "1.0.5" @@ -3055,7 +2825,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.4", + "rustix", "windows-link 0.2.1", ] @@ -3307,7 +3077,6 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", - "num-traits", "zerocopy", ] @@ -3369,15 +3138,6 @@ dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "heck" version = "0.4.1" @@ -4242,63 +4002,6 @@ dependencies = [ "spin", ] -[[package]] -name = "lexical-core" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" -dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", -] - -[[package]] -name = "lexical-parse-float" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" -dependencies = [ - "lexical-parse-integer", - "lexical-util", -] - -[[package]] -name = "lexical-parse-integer" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" -dependencies = [ - "lexical-util", -] - -[[package]] -name = "lexical-util" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" - -[[package]] -name = "lexical-write-float" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" -dependencies = [ - "lexical-util", - "lexical-write-integer", -] - -[[package]] -name = "lexical-write-integer" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" -dependencies = [ - "lexical-util", -] - [[package]] name = "libappindicator" version = "0.9.0" @@ -4344,23 +4047,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libduckdb-sys" -version = "1.10505.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb514dab5e271e849235c1cb98bd65a2ae107fbd619a6740219319c54a71d95" -dependencies = [ - "cc", - "flate2", - "pkg-config", - "serde", - "serde_json", - "tar", - "ureq", - "vcpkg", - "zip 6.0.0", -] - [[package]] name = "libloading" version = "0.7.4" @@ -4428,12 +4114,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -5059,15 +4739,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.2.2" @@ -5100,7 +4771,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -5929,7 +5599,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -6791,7 +6461,7 @@ dependencies = [ "bitflags 2.13.1", "fallible-iterator 0.3.0", "fallible-streaming-iterator", - "hashlink 0.9.1", + "hashlink", "libsqlite3-sys", "smallvec", ] @@ -6976,19 +6646,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.1.4" @@ -6998,7 +6655,7 @@ dependencies = [ "bitflags 2.13.1", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -7973,27 +7630,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "subtle" version = "2.6.1" @@ -8512,7 +8148,7 @@ dependencies = [ "tokio", "url", "windows-sys 0.60.2", - "zip 4.6.1", + "zip", ] [[package]] @@ -8639,7 +8275,7 @@ dependencies = [ "fastrand", "getrandom 0.4.3", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -9457,34 +9093,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" -dependencies = [ - "base64 0.22.1", - "log", - "percent-encoding", - "rustls 0.23.42", - "rustls-pki-types", - "ureq-proto", - "utf8-zero", - "webpki-roots 1.0.9", -] - -[[package]] -name = "ureq-proto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" -dependencies = [ - "base64 0.22.1", - "http", - "httparse", - "log", -] - [[package]] name = "url" version = "2.5.8" @@ -9510,12 +9118,6 @@ dependencies = [ "url", ] -[[package]] -name = "utf8-zero" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -9744,7 +9346,7 @@ checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.4", + "rustix", "smallvec", "wayland-sys", ] @@ -9756,7 +9358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" dependencies = [ "bitflags 2.13.1", - "rustix 1.1.4", + "rustix", "wayland-backend", "wayland-scanner", ] @@ -10588,7 +10190,7 @@ dependencies = [ "libc", "log", "os_pipe", - "rustix 1.1.4", + "rustix", "thiserror 2.0.19", "tree_magic_mini", "wayland-backend", @@ -10684,7 +10286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "gethostname", - "rustix 1.1.4", + "rustix", "x11rb-protocol", ] @@ -10713,7 +10315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.4", + "rustix", ] [[package]] @@ -10772,7 +10374,7 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix 1.1.4", + "rustix", "serde", "serde_repr", "tracing", @@ -10932,20 +10534,6 @@ dependencies = [ "zstd", ] -[[package]] -name = "zip" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" -dependencies = [ - "arbitrary", - "crc32fast", - "flate2", - "indexmap 2.14.0", - "memchr", - "zopfli", -] - [[package]] name = "zlib-rs" version = "0.6.6" diff --git a/Makefile b/Makefile index d93aea93f1..ffe8e2979a 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ help: @printf '%s\n' 'App:' @printf ' %-23s %s\n' 'make' 'Start the local desktop development environment' @printf ' %-23s %s\n' 'make dev' 'Start the local desktop development environment' - @printf ' %-23s %s\n' 'make dev-fast' 'Start Tauri dev without default Rust features' + @printf ' %-23s %s\n' 'make dev-fast' 'Start lightweight Tauri dev with DuckDB sidecar support' @printf ' %-23s %s\n' 'make dev-web' 'Start the web frontend development server' @printf ' %-23s %s\n' 'make dev-backend' 'Start the web backend development server' @printf ' %-23s %s\n' 'make build' 'Run type checks and build the desktop frontend' @@ -75,7 +75,7 @@ dev: node_modules/.modules.yaml check-tauri-dev-port $(PNPM) dev:tauri dev-fast: node_modules/.modules.yaml check-tauri-dev-port - $(PNPM) tauri dev -- --no-default-features + $(PNPM) tauri dev -- --no-default-features --features duckdb-sidecar dev-web: node_modules/.modules.yaml $(PNPM) dev:web diff --git a/agents/README.md b/agents/README.md index f7723bc306..f3c79c088d 100644 --- a/agents/README.md +++ b/agents/README.md @@ -49,13 +49,13 @@ Each agent runs as a standalone process and communicates with DBX via stdin/stdo ## Multi-JRE Support -Most Java agents target JRE 21. Native agents, such as `oracle`, `kingbase`, and `xugu`, do not require a JRE. DBX downloads and manages the JRE 21 installation automatically for Java agents. +Most Java agents target JRE 21. Native agents, such as `duckdb`, `oracle`, `kingbase`, and `xugu`, do not require a JRE. DBX downloads and manages the JRE 21 installation automatically for Java agents. ## Choosing a Driver Language For new agents, prefer a **native (Go or Rust) driver** over a Java/JDBC agent whenever a mature, license-compatible native driver is available. Native agents ship as a single self-contained executable with no JRE, which significantly reduces memory footprint and startup time — the JVM baseline that every Java agent pays even when idle is avoided entirely. -- **Native (Go/Rust)** — preferred when a usable native driver exists. See `drivers/oracle-go` (go-ora), `drivers/kingbase-go` (gokb), and `drivers/xugu` as reference implementations. No JRE download or management is needed. +- **Native (C++/Go/Rust)** — preferred when a usable native driver exists. See `drivers/duckdb`, `drivers/oracle-go` (go-ora), `drivers/kingbase-go` (gokb), and `drivers/xugu` as reference implementations. No JRE download or management is needed. - **Java/JDBC** — the default fallback when only a JDBC driver exists for the database, or when the native driver is immature or unmaintained. Most agents still fall in this category. Native agents implement the same JSON-RPC contract and `versions.json` registration as Java agents; they ship an `agent` executable instead of `agent.jar`. If both native and Java source implementations exist for the same database, publish only the native artifact unless the Java variant has a separately registered compatibility profile, such as `oracle-legacy` / `oracle-10g`. diff --git a/agents/docs/release-checklist.md b/agents/docs/release-checklist.md index f84b33ac34..2b36ba4e97 100644 --- a/agents/docs/release-checklist.md +++ b/agents/docs/release-checklist.md @@ -39,6 +39,8 @@ python3 scripts/validate_agent_jars.py git diff --check ``` +The package tests require the `zstd` CLI. + Expected result: - Python script tests pass. @@ -166,9 +168,11 @@ The release workflow will: - Bump changed module versions in `versions.json`. - Build all agent shadow jars. -- Build/download JRE artifacts. +- Build/download JRE `.tar.zst` artifacts. - Generate `agent-registry.json`. -- Create a GitHub release with jars, JRE archives, and registry. +- Create full offline platform ZIPs from raw staging files. +- Create one `.tar.zst` package per Java or native driver. +- Publish offline ZIPs, single-driver packages, JRE archives, and the registry. ## 9. Post-Release Verification diff --git a/src-tauri/Cargo.lock b/agents/drivers/duckdb/Cargo.lock similarity index 54% rename from src-tauri/Cargo.lock rename to agents/drivers/duckdb/Cargo.lock index 1e0064eefb..87003d87fb 100644 --- a/src-tauri/Cargo.lock +++ b/agents/drivers/duckdb/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -24,7 +24,7 @@ version = "0.6.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b657e772794c6b04730ea897b66a058ccd866c16d1967da05eeeecec39043fe" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout 0.2.2", ] @@ -41,11 +41,11 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", "cpubits", "cpufeatures 0.3.0", ] @@ -71,9 +71,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22c0c90bbe8d4f77c3ca9ddabe41a1f8382d6fc1f7cea89459d0f320371f972" dependencies = [ "aead 0.6.0-rc.10", - "aes 0.9.0", - "cipher 0.5.1", - "ctr 0.10.0", + "aes 0.9.2", + "cipher 0.5.2", + "ctr 0.10.1", "ghash 0.6.0", "subtle", ] @@ -112,44 +112,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android_log-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" - -[[package]] -name = "android_logger" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" -dependencies = [ - "android_log-sys", - "env_filter", - "log", -] - [[package]] name = "android_system_properties" version = "0.1.5" @@ -161,9 +129,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ar_archive_writer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348" +dependencies = [ + "object", +] [[package]] name = "arbitrary" @@ -174,6 +151,15 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "argon2" version = "0.5.3" @@ -188,15 +174,15 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "arrow" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "607e64bb911ee4f90483e044fe78f175989148c2892e659a2cd25429e782ec54" +checksum = "6cfdd0833e32a9874d2b55089333ad310c0be208aafa277385ce2461dec90be3" dependencies = [ "arrow-arith", "arrow-array", @@ -212,9 +198,9 @@ dependencies = [ [[package]] name = "arrow-arith" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e754319ed8a85d817fe7adf183227e0b5308b82790a737b426c1124626b48118" +checksum = "0a41203398f0eaa6f7ec8e62c0da742a21abf282c148fc157f6c35c90e29981a" dependencies = [ "arrow-array", "arrow-buffer", @@ -226,9 +212,9 @@ dependencies = [ [[package]] name = "arrow-array" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841321891f247aa86c6112c80d83d89cb36e0addd020fa2425085b8eb6c3f579" +checksum = "ae33dad492b7df00a217563a7b0ef2874df68a0deea1b1a3acf628152f7f7a69" dependencies = [ "ahash 0.8.12", "arrow-buffer", @@ -236,7 +222,7 @@ dependencies = [ "arrow-schema", "chrono", "half", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "num-complex", "num-integer", "num-traits", @@ -244,9 +230,9 @@ dependencies = [ [[package]] name = "arrow-buffer" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f955dfb73fae000425f49c8226d2044dab60fb7ad4af1e24f961756354d996c9" +checksum = "b9552f96391c005e6ab449fa941420935e7e062489b12b8b1b08879b2163f5b5" dependencies = [ "bytes", "half", @@ -256,9 +242,9 @@ dependencies = [ [[package]] name = "arrow-cast" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5e686972523798f76bef355145bc1ae25a84c731e650268d31ab763c701663" +checksum = "3a8a327c9649f30d8406995f27642b68df354713cca3baaaf100f076f18d5f34" dependencies = [ "arrow-array", "arrow-buffer", @@ -278,9 +264,9 @@ dependencies = [ [[package]] name = "arrow-data" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3b5846209775b6dc8056d77ff9a032b27043383dd5488abd0b663e265b9373" +checksum = "2b24852db04738907e06c04ea61e42fe7fda962a34513022dc0d0e754fb7976b" dependencies = [ "arrow-buffer", "arrow-schema", @@ -291,9 +277,9 @@ dependencies = [ [[package]] name = "arrow-ord" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa70d9d6b1356f1fb9f1f651b84a725b7e0abb93f188cf7d31f14abfa2f2e6f" +checksum = "63a083ec750f5c043f02946b4baf05fcdbb55f4560a3277055caca5cc99f3eb0" dependencies = [ "arrow-array", "arrow-buffer", @@ -304,9 +290,9 @@ dependencies = [ [[package]] name = "arrow-row" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faec88a945338192beffbbd4be0def70135422930caa244ac3cec0cd213b26b4" +checksum = "514ba0ef0d4c5896202dae736251ce415abb43a950bed570fb7981b8716c0e4c" dependencies = [ "arrow-array", "arrow-buffer", @@ -317,18 +303,18 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18aa020f6bc8e5201dcd2d4b7f98c68f8a410ef37128263243e6ff2a47a67d4f" +checksum = "21ca356ad6425cecb6eb7b28e4f659f1ee7880fbb1a16127de7dd62901efee9e" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.1", ] [[package]] name = "arrow-select" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a657ab5132e9c8ca3b24eb15a823d0ced38017fe3930ff50167466b02e2d592c" +checksum = "c58da39eb3d8350ad4a549e5c2bc49284dac554016c69829310350f1731b0aad" dependencies = [ "ahash 0.8.12", "arrow-array", @@ -340,9 +326,9 @@ dependencies = [ [[package]] name = "arrow-string" -version = "58.2.0" +version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6de2efbbd1a9f9780ceb8d1ff5d20421b35863b361e3386b4f571f1fc69fcb8" +checksum = "b6789b388467525e3271326b6b4915666ecfdf5142aef09779445c954b67543c" dependencies = [ "arrow-array", "arrow-buffer", @@ -355,133 +341,15 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 1.1.4", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-native-tls" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d57d4cec3c647232e1094dc013546c0b33ce785d8aeb251e1f20dfaf8a9a13fe" -dependencies = [ - "futures-util", - "native-tls", - "thiserror 1.0.69", - "url", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix 1.1.4", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "async-signal" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 1.1.4", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -497,29 +365,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "atoi" version = "2.0.0" @@ -546,15 +391,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -563,14 +408,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -591,6 +437,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9" + [[package]] name = "base64ct" version = "1.8.3" @@ -608,21 +460,6 @@ dependencies = [ "sha2 0.10.9", ] -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - [[package]] name = "bitflags" version = "1.3.2" @@ -631,18 +468,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -dependencies = [ - "serde_core", -] +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -670,9 +504,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] @@ -695,28 +529,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - [[package]] name = "blowfish" version = "0.9.1" @@ -729,9 +541,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" dependencies = [ "borsh-derive", "bytes", @@ -740,36 +552,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c" dependencies = [ "once_cell", - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "syn 2.0.119", ] [[package]] @@ -784,10 +575,10 @@ dependencies = [ "getrandom 0.2.17", "getrandom 0.3.4", "hex", - "indexmap 2.14.0", + "indexmap", "js-sys", "once_cell", - "rand 0.9.4", + "rand 0.9.5", "serde", "serde_bytes", "serde_json", @@ -796,23 +587,30 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.20.2" +name = "bstr" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" +dependencies = [ + "memchr", + "serde_core", +] [[package]] -name = "byte-unit" -version = "5.2.0" +name = "btoi" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6d47a4e2961fb8721bcfc54feae6455f2f64e7054f9bc67e875f0e77f4c58d" +checksum = "3b5ab9db53bcda568284df0fd39f6eac24ad6f7ba7ff1168b9e76eba6576b976" dependencies = [ - "rust_decimal", - "schemars 1.2.1", - "serde", - "utf8-width", + "num-traits", ] +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytecheck" version = "0.6.12" @@ -835,12 +633,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - [[package]] name = "byteorder" version = "1.5.0" @@ -849,37 +641,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.11.1", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "calamine" @@ -889,57 +653,16 @@ checksum = "b1a9acfeb1555aa8def91fe8ff208aadaea850c109968ec35ac965edbe7d210b" dependencies = [ "atoi_simd", "byteorder", + "chrono", "codepage", "encoding_rs", "fast-float2", "log", - "quick-xml 0.37.5", + "quick-xml", "serde", "zip 4.6.1", ] -[[package]] -name = "camino" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", -] - [[package]] name = "cast" version = "0.3.0" @@ -957,18 +680,18 @@ dependencies = [ [[package]] name = "cbc" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98db6aeaef0eeef2c1e3ce9a27b739218825dae116076352ac3777076aa22225" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", ] [[package]] name = "cc" -version = "1.2.61" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -976,33 +699,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -1011,9 +707,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" @@ -1028,9 +724,9 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -1039,16 +735,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1063,12 +759,12 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "block-buffer 0.12.0", - "crypto-common 0.2.1", + "block-buffer 0.12.1", + "crypto-common 0.2.2", "inout 0.2.2", ] @@ -1083,9 +779,9 @@ dependencies = [ [[package]] name = "cmov" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "codepage" @@ -1121,21 +817,23 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "connection-string" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "510ca239cf13b7f8d16a2b48f263de7b4f8c566f0af58d901031473c76afb1e3" +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1177,16 +875,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -1213,30 +901,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "core-graphics-types", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.11.1", - "core-foundation 0.10.1", - "libc", -] - [[package]] name = "cpubits" version = "0.1.1" @@ -1262,19 +926,10 @@ dependencies = [ ] [[package]] -name = "crc" -version = "3.4.0" +name = "crc16" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" +checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" [[package]] name = "crc32fast" @@ -1293,36 +948,46 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "crossbeam-deque" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crossterm" @@ -1330,7 +995,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.1", "crossterm_winapi", "parking_lot", "rustix 0.38.44", @@ -1360,7 +1025,7 @@ checksum = "96dacf199529fb801ae62a9aafdc01b189e9504c0d1ee1512a4c16bcd8666a93" dependencies = [ "cpubits", "ctutils", - "getrandom 0.4.2", + "getrandom 0.4.3", "hybrid-array", "num-traits", "rand_core 0.10.1", @@ -1376,16 +1041,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array 0.14.7", + "rand_core 0.6.4", "typenum", ] [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "hybrid-array", "rand_core 0.10.1", ] @@ -1401,29 +1067,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "cssparser" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf 0.13.1", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.117", -] - [[package]] name = "csv" version = "1.4.0" @@ -1445,22 +1088,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ctor" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" -dependencies = [ - "ctor-proc-macro", - "dtor", -] - -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" - [[package]] name = "ctr" version = "0.9.2" @@ -1472,11 +1099,11 @@ dependencies = [ [[package]] name = "ctr" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17469f8eb9bdbfad10f71f4cfddfd38b01143520c0e717d8796ccb4d44d44e42" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", ] [[package]] @@ -1489,6 +1116,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "5.0.0-pre.6" @@ -1499,7 +1141,7 @@ dependencies = [ "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.11.3", - "fiat-crypto", + "fiat-crypto 0.3.0", "rustc_version", "subtle", "zeroize", @@ -1513,7 +1155,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1536,7 +1178,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1547,7 +1189,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1557,72 +1199,125 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] -name = "dbus" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" -dependencies = [ - "libc", - "libdbus-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "dbus-secret-service" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +name = "dbx-core" +version = "0.1.0" dependencies = [ "aes 0.8.4", - "block-padding 0.3.3", + "aes-gcm 0.10.3", + "anyhow", + "arc-swap", + "argon2", + "async-trait", + "base64 0.22.1", + "bytes", + "calamine", "cbc 0.1.2", - "dbus", - "fastrand", - "hkdf 0.12.4", - "num", - "once_cell", + "chrono", + "csv", + "deadpool-postgres", + "encoding_rs", + "flate2", + "futures", + "httpdate", + "iana-time-zone", + "insta", + "json5", + "jsonwebtoken", + "log", + "minijinja", + "mongodb", + "mysql_async", + "notify", + "pageant", + "percent-encoding", + "portpicker", + "prometheus-parse", + "quick-xml", + "rayon", + "redis", + "regex", + "reqwest", + "rusqlite", + "russh", + "rust_decimal", + "rustls 0.23.42", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_yaml", + "serde_yaml_ng", "sha2 0.10.9", - "zeroize", + "sqlparser", + "ssfmt", + "sysinfo", + "tar", + "tempfile", + "tiberius", + "tokio", + "tokio-postgres", + "tokio-postgres-rustls", + "tokio-util", + "tracing", + "tracing-subscriber", + "unicode-width", + "uuid", + "webpki-roots 0.26.11", + "winnow", + "zip 4.6.1", + "zstd", ] [[package]] -name = "dbx" -version = "0.4.2" +name = "dbx-duckdb-driver" +version = "0.1.0" dependencies = [ - "anyhow", - "calamine", "chrono", - "csv", + "dbx-core", "duckdb", - "futures", - "keyring", "log", - "mongodb", - "oracle-rs", - "percent-encoding", - "portpicker", - "redis", - "reqwest 0.12.28", - "russh", - "rust_decimal", - "rustls", "serde", "serde_json", - "sqlx", - "tauri", - "tauri-build", - "tauri-plugin-dialog", - "tauri-plugin-fs", - "tauri-plugin-log", - "tauri-plugin-process", - "tauri-plugin-shell", - "tauri-plugin-updater", - "tiberius", + "sysinfo", "tokio", "tokio-util", "uuid", ] +[[package]] +name = "deadpool" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +dependencies = [ + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-postgres" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d697d376cbfa018c23eb4caab1fd1883dd9c906a8c034e8d9a3cb06a7e0bef9" +dependencies = [ + "async-trait", + "deadpool", + "getrandom 0.2.17", + "tokio", + "tokio-postgres", + "tracing", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +dependencies = [ + "tokio", +] + [[package]] name = "debug_unsafe" version = "0.1.4" @@ -1637,7 +1332,7 @@ checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1647,30 +1342,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid 0.9.6", - "pem-rfc7468 0.7.0", + "der_derive", + "flagset", "zeroize", ] [[package]] name = "der" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ "const-oid 0.10.2", "pem-rfc7468 1.0.0", "zeroize", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", - "serde_core", -] [[package]] name = "derive-syn-parse" @@ -1680,7 +1383,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1691,7 +1394,7 @@ checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1702,7 +1405,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1724,7 +1427,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.119", "unicode-xid", ] @@ -1746,154 +1449,37 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid 0.10.2", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", ] -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.11.1", - "block2", - "libc", - "objc2", -] - [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "dom_query" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" -dependencies = [ - "bit-set", - "cssparser", - "foldhash 0.2.0", - "html5ever", - "precomputed-hash", - "selectors", - "tendril", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "duckdb" -version = "1.10502.0" +version = "1.10505.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdc796383b176dd5a45353fbb5e64583c0ee4da12cb62c9e510b785324b2488" +checksum = "970e05eedd3f55c435194d9104f90a9b4a79a80d6e73251bc9ff43e178130c4e" dependencies = [ "arrow", "cast", "comfy-table", - "fallible-iterator", + "fallible-iterator 0.3.0", "fallible-streaming-iterator", - "hashlink", + "hashlink 0.10.0", "libduckdb-sys", "num-integer", - "rust_decimal", "strum", ] @@ -1903,23 +1489,17 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - [[package]] name = "ecdsa" version = "0.17.0-rc.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829" dependencies = [ - "der 0.8.0", + "der 0.8.1", "digest 0.11.3", "elliptic-curve", "rfc6979", - "signature 3.0.0", + "signature", "spki 0.8.0-rc.4", "zeroize", ] @@ -1930,8 +1510,8 @@ version = "3.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" dependencies = [ - "pkcs8 0.11.0-rc.11", - "signature 3.0.0", + "pkcs8", + "signature", ] [[package]] @@ -1940,24 +1520,21 @@ version = "3.0.0-pre.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 5.0.0-pre.6", "ed25519", "rand_core 0.10.1", "serde", "sha2 0.11.0", - "signature 3.0.0", + "signature", "subtle", "zeroize", ] [[package]] name = "either" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "elliptic-curve" @@ -1967,13 +1544,13 @@ checksum = "bde7860544606d222fd6bd6d9f9a0773321bf78072a637e1d560a058c0031978" dependencies = [ "base16ct", "crypto-bigint", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "digest 0.11.3", - "hkdf 0.13.0", + "hkdf", "hybrid-array", "once_cell", "pem-rfc7468 1.0.0", - "pkcs8 0.11.0-rc.11", + "pkcs8", "rand_core 0.10.1", "rustcrypto-ff", "rustcrypto-group", @@ -1983,24 +1560,10 @@ dependencies = [ ] [[package]] -name = "embed-resource" -version = "3.0.9" +name = "encode_unicode" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.2+spec-1.1.0", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" @@ -2011,24 +1574,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "enum_dispatch" version = "0.3.13" @@ -2038,7 +1583,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2048,7 +1593,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ "enumflags2_derive", - "serde", ] [[package]] @@ -2059,17 +1603,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "env_filter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" -dependencies = [ - "log", - "regex", + "syn 2.0.119", ] [[package]] @@ -2078,17 +1612,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - [[package]] name = "errno" version = "0.3.14" @@ -2100,36 +1623,10 @@ dependencies = [ ] [[package]] -name = "etcetera" -version = "0.8.0" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fallible-iterator" @@ -2151,27 +1648,15 @@ checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] -name = "fdeflate" -version = "0.3.7" +name = "fiat-crypto" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "fern" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" -dependencies = [ - "log", -] +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "fiat-crypto" @@ -2179,25 +1664,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -2206,6 +1680,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + [[package]] name = "flate2" version = "1.1.9" @@ -2217,23 +1697,6 @@ dependencies = [ "zlib-rs", ] -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" @@ -2246,48 +1709,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -2311,9 +1732,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -2326,9 +1747,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -2336,79 +1757,55 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", "futures-util", ] -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -2421,105 +1818,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2532,26 +1830,15 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.4.1" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab9e9188e97a93276e1fe7b56401b851e2b45a46d045ca658100c1303ada649" +checksum = "ab4e5aa225bc56696909483320f0ff9b600f1a971b52e07a17d70f3d9b43254b" dependencies = [ "generic-array 0.14.7", "rustversion", "typenum", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -2581,16 +1868,16 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", + "wasm-bindgen", ] [[package]] @@ -2609,174 +1896,20 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "polyval 0.7.1", + "polyval 0.7.3", ] [[package]] -name = "gio" -version = "0.18.4" +name = "globset" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.11.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "h2" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", ] [[package]] @@ -2800,22 +1933,54 @@ dependencies = [ "ahash 0.7.8", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", +] + [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", "foldhash 0.1.5", ] [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] [[package]] name = "hashlink" @@ -2826,12 +1991,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -2857,24 +2016,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] -name = "hickory-proto" -version = "0.25.2" +name = "hickory-net" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner", "futures-channel", "futures-io", "futures-util", + "hickory-proto", "idna", "ipnet", - "once_cell", - "rand 0.9.4", - "ring", - "thiserror 2.0.18", + "jni", + "rand 0.10.2", + "thiserror 2.0.19", "tinyvec", "tokio", "tracing", @@ -2882,33 +2040,49 @@ dependencies = [ ] [[package]] -name = "hickory-resolver" -version = "0.25.2" +name = "hickory-proto" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "moka", + "data-encoding", + "idna", + "ipnet", + "jni", "once_cell", - "parking_lot", - "rand 0.9.4", - "resolv-conf", - "smallvec", - "thiserror 2.0.18", - "tokio", + "prefix-trie", + "rand 0.10.2", + "ring", + "thiserror 2.0.19", + "tinyvec", "tracing", + "url", ] [[package]] -name = "hkdf" -version = "0.12.4" +name = "hickory-resolver" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" dependencies = [ - "hmac 0.12.1", + "cfg-if", + "futures-util", + "hickory-net", + "hickory-proto", + "ipconfig", + "ipnet", + "jni", + "moka", + "ndk-context", + "once_cell", + "parking_lot", + "rand 0.10.2", + "resolv-conf", + "smallvec", + "system-configuration", + "thiserror 2.0.19", + "tokio", + "tracing", ] [[package]] @@ -2938,41 +2112,11 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "hostname" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" -dependencies = [ - "cfg-if", - "libc", - "windows-link 0.2.1", -] - -[[package]] -name = "html5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" -dependencies = [ - "log", - "markup5ever", -] - [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -2980,9 +2124,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -2990,9 +2134,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -3008,10 +2152,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "hybrid-array" -version = "0.4.11" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "ctutils", "subtle", @@ -3021,15 +2171,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", @@ -3049,27 +2198,11 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls", + "rustls 0.23.42", "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots 1.0.7", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", + "tokio-rustls 0.26.4", "tower-service", + "webpki-roots 1.0.9", ] [[package]] @@ -3090,11 +2223,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -3121,16 +2252,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ico" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png 0.17.16", -] - [[package]] name = "icu_collections" version = "2.2.0" @@ -3213,12 +2334,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -3246,17 +2361,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -3264,18 +2368,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", - "serde", - "serde_core", + "hashbrown 0.17.1", ] [[package]] -name = "infer" -version = "0.19.0" +name = "inotify" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" dependencies = [ - "cfb", + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", ] [[package]] @@ -3298,6 +2411,30 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "insta" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" +dependencies = [ + "console", + "globset", + "once_cell", + "serde", + "similar", + "tempfile", + "walkdir", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "internal-russh-forked-ssh-key" version = "0.6.18+upstream-0.6.7" @@ -3316,11 +2453,11 @@ dependencies = [ "p384", "p521", "rand_core 0.10.1", - "rsa 0.10.0-rc.16", + "rsa", "sec1", "sha1 0.11.0", "sha2 0.11.0", - "signature 3.0.0", + "signature", "ssh-cipher", "ssh-encoding", "subtle", @@ -3335,7 +2472,7 @@ checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8" dependencies = [ "num-integer", "num-traits", - "rand 0.10.1", + "rand 0.10.2", "rand_core 0.10.1", ] @@ -3357,34 +2494,17 @@ name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ - "memchr", "serde", ] [[package]] -name = "is-docker" -version = "0.2.0" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", + "either", ] [[package]] @@ -3393,45 +2513,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - [[package]] name = "jni" version = "0.22.4" @@ -3441,12 +2522,12 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror 2.0.19", "walkdir", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -3459,16 +2540,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.117", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", + "syn 2.0.119", ] [[package]] @@ -3487,51 +2559,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] [[package]] -name = "json-patch" -version = "3.0.1" +name = "json5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" dependencies = [ - "jsonptr", + "pest", + "pest_derive", "serde", - "serde_json", - "thiserror 1.0.69", ] [[package]] -name = "jsonptr" -version = "0.6.3" +name = "jsonwebtoken" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", "serde", "serde_json", + "simple_asn1", ] [[package]] @@ -3550,36 +2625,37 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "rand_core 0.10.1", ] [[package]] -name = "keyboard-types" -version = "0.7.0" +name = "keyed_priority_queue" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" dependencies = [ - "bitflags 2.11.1", - "serde", - "unicode-segmentation", + "indexmap", ] [[package]] -name = "keyring" -version = "3.6.3" +name = "kqueue" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ - "byteorder", - "dbus-secret-service", - "linux-keyutils", - "log", - "secret-service", - "security-framework 2.11.1", - "security-framework 3.7.0", - "windows-sys 0.60.2", - "zeroize", + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.1", + "libc", ] [[package]] @@ -3591,12 +2667,6 @@ dependencies = [ "spin", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "lexical-core" version = "1.0.6" @@ -3654,72 +2724,29 @@ dependencies = [ "lexical-util", ] -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading", - "once_cell", -] - [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libdbus-sys" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" -dependencies = [ - "pkg-config", -] +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libduckdb-sys" -version = "1.10502.0" +version = "1.10505.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7401630ae2abcff642f7156294289e50f2d222e061c026ad797b01bf20c215" +checksum = "6cb514dab5e271e849235c1cb98bd65a2ae107fbd619a6740219319c54a71d95" dependencies = [ "cc", "flate2", "pkg-config", - "reqwest 0.12.28", "serde", "serde_json", "tar", + "ureq", "vcpkg", "zip 6.0.0", ] -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libm" version = "0.2.16" @@ -3728,14 +2755,11 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.11.1", "libc", - "plain", - "redox_syscall 0.7.4", ] [[package]] @@ -3749,16 +2773,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-keyutils" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" -dependencies = [ - "bitflags 2.11.1", - "libc", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -3788,11 +2802,26 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ - "value-bag", + "hashbrown 0.16.1", +] + +[[package]] +name = "lru" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +dependencies = [ + "hashbrown 0.17.1", ] [[package]] @@ -3810,7 +2839,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3824,7 +2853,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3835,7 +2864,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3846,36 +2875,42 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "markup5ever" -version = "0.38.0" +name = "manyhow" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" dependencies = [ - "log", - "tendril", - "web_atoms", + "manyhow-macros", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", ] [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest 0.11.3", ] -[[package]] -name = "md5" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d" - [[package]] name = "md5" version = "0.7.0" @@ -3884,31 +2919,26 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] -name = "memoffset" -version = "0.9.1" +name = "memo-map" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" + +[[package]] +name = "minijinja" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3d648e68cea56d9858d535ee28f9538404e2dd8cb08ed0bd05dca379477f39" dependencies = [ - "autocfg", + "memo-map", + "serde", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minisign-verify" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3921,11 +2951,12 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -3945,9 +2976,9 @@ dependencies = [ [[package]] name = "module-lattice" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc7c90d33a0dac244570c26461d761ffaeadb3bfc2b17cc625ae2185cafdffae" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" dependencies = [ "ctutils", "hybrid-array", @@ -3973,9 +3004,9 @@ dependencies = [ [[package]] name = "mongocrypt" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da0cd419a51a5fb44819e290fbdb0665a54f21dead8923446a799c7f4d26ad9" +checksum = "8426a875ded61430d4a811dbfda7633b6b8af0225c547fc6c28b8b0aa7d79a13" dependencies = [ "bson", "mongocrypt-sys", @@ -3985,18 +3016,18 @@ dependencies = [ [[package]] name = "mongocrypt-sys" -version = "0.1.5+1.15.1" +version = "0.1.6+1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224484c5d09285a7b8cb0a0c117e847ebd14cb6e4470ecf68cdb89c503b0edb9" +checksum = "851fac73f7fe22f6a3ab87f720ce509cae7c9fd08e7dd27866cc232dee07ccf4" [[package]] name = "mongodb" -version = "3.6.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef2c933617431ad0246fb5b43c425ebdae18c7f7259c87de0726d93b0e7e91b" +checksum = "b814038f367d212f55de0a630cb35102a9b8ca23785a86955d62c0087c93846d" dependencies = [ "base64 0.22.1", - "bitflags 2.11.1", + "bitflags 2.13.1", "bson", "derive-where", "derive_more", @@ -4004,161 +3035,193 @@ dependencies = [ "futures-io", "futures-util", "hex", + "hickory-net", "hickory-proto", "hickory-resolver", - "hmac 0.12.1", + "hmac 0.13.0", "macro_magic", "md-5", "mongocrypt", "mongodb-internal-macros", - "pbkdf2 0.12.2", + "pbkdf2 0.13.0", "percent-encoding", - "rand 0.9.4", + "rand 0.9.5", "rustc_version_runtime", - "rustls", - "rustversion", + "rustls 0.23.42", "serde", "serde_bytes", "serde_with", - "sha1 0.10.6", - "sha2 0.10.9", + "sha1 0.11.0", + "sha2 0.11.0", "socket2", "stringprep", "strsim", "take_mut", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-util", "typed-builder", "uuid", - "webpki-roots 1.0.7", + "webpki-roots 1.0.9", ] [[package]] name = "mongodb-internal-macros" -version = "3.6.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5758dc828eb2d02ec30563cba365609d56ddd833190b192beaee2b475a7bb3" +checksum = "f736d2fbc56e0011a341fbb9172bd822fda75c5f93b82fae1c7aab1e2613c810" dependencies = [ "macro_magic", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "muda" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb" +name = "mysql-common-derive" +version = "0.32.2" +source = "git+https://github.com/t8y2/rust_mysql_common.git?rev=77085e91e5081309d585153e3b656ce33bc1fe74#77085e91e5081309d585153e3b656ce33bc1fe74" dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png 0.18.1", + "darling", + "heck", + "manyhow", + "num-bigint", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", + "termcolor", + "thiserror 2.0.19", +] + +[[package]] +name = "mysql_async" +version = "0.37.0" +source = "git+https://github.com/t8y2/mysql_async.git?rev=2be6e392eb9b06d20dcd2d8ed8eae748d413c9ec#2be6e392eb9b06d20dcd2d8ed8eae748d413c9ec" +dependencies = [ + "bytes", + "crossbeam-queue", + "crossbeam-utils", + "flate2", + "futures-core", + "futures-sink", + "futures-util", + "keyed_priority_queue", + "lru 0.18.1", + "mysql_common", + "percent-encoding", + "rand 0.10.2", + "rustls 0.23.42", "serde", - "thiserror 2.0.18", + "socket2", + "thiserror 2.0.19", + "tokio", + "tokio-rustls 0.26.4", + "tokio-util", + "twox-hash", + "url", + "webpki-roots 1.0.9", +] + +[[package]] +name = "mysql_common" +version = "0.38.0" +source = "git+https://github.com/t8y2/rust_mysql_common.git?rev=77085e91e5081309d585153e3b656ce33bc1fe74#77085e91e5081309d585153e3b656ce33bc1fe74" +dependencies = [ + "base64 0.22.1", + "bitflags 2.13.1", + "btoi", + "byteorder", + "bytes", + "chrono", + "crc32fast", + "curve25519-dalek 4.1.3", + "flate2", + "getrandom 0.3.4", + "hex", + "mysql-common-derive", + "num-bigint", + "num-traits", + "regex", + "rust_decimal", + "saturating", + "serde", + "serde_json", + "sha1 0.10.7", + "sha2 0.10.9", + "thiserror 2.0.19", + "uuid", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.13.1", + "filetime", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework 3.7.0", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.11.1", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nix" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -4175,10 +3238,9 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.6", + "rand 0.8.7", "serde", "smallvec", - "zeroize", ] [[package]] @@ -4192,9 +3254,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -4207,22 +3269,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", "num-integer", "num-traits", ] @@ -4238,242 +3288,40 @@ dependencies = [ ] [[package]] -name = "num_enum" -version = "0.7.6" +name = "num_cpus" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ + "hermit-abi", "libc", ] -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2", - "objc2-foundation", -] - [[package]] name = "objc2-core-foundation" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.1", - "dispatch2", - "objc2", + "bitflags 2.13.1", ] [[package]] -name = "objc2-core-graphics" +name = "objc2-system-configuration" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" dependencies = [ - "bitflags 2.11.1", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.11.1", - "block2", - "libc", - "objc2", "objc2-core-foundation", ] [[package]] -name = "objc2-io-surface" -version = "0.3.2" +name = "object" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-osa-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.11.1", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.11.1", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", + "memchr", ] [[package]] @@ -4493,42 +3341,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] -name = "open" -version = "5.3.4" +name = "openssl-probe" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "openssl" -version = "0.10.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-probe" @@ -4536,91 +3352,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-sys" -version = "0.9.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "oracle-rs" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c9ca364b441f92b717658c62e85207158b30285deb01811ccd923017f61612e" -dependencies = [ - "aes 0.8.4", - "async-trait", - "bytes", - "cbc 0.1.2", - "chrono", - "hex", - "hmac 0.12.1", - "hostname", - "indexmap 2.14.0", - "md-5", - "pbkdf2 0.12.2", - "pkcs8 0.10.2", - "rand 0.8.6", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "sha1 0.10.6", - "sha2 0.10.9", - "thiserror 1.0.69", - "tokio", - "tokio-rustls", - "tracing", - "webpki-roots 0.26.11", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "os_pipe" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "osakit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" -dependencies = [ - "objc2", - "objc2-foundation", - "objc2-osa-kit", - "serde", - "serde_json", - "thiserror 2.0.18", -] - [[package]] name = "p256" version = "0.14.0-rc.7" @@ -4642,7 +3373,7 @@ checksum = "8c91df688211f5957dbe2ab599dcbcaade8d6d3cdc15c5b350d350d7d07ce423" dependencies = [ "ecdsa", "elliptic-curve", - "fiat-crypto", + "fiat-crypto 0.3.0", "primefield", "primeorder", "sha2 0.11.0", @@ -4664,54 +3395,24 @@ dependencies = [ [[package]] name = "pageant" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b537f975f6d8dcf48db368d7ec209d583b015713b5df0f5d92d2631e4ff5595" +checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5" dependencies = [ + "base16ct", "byteorder", "bytes", "delegate", "futures", "log", - "rand 0.8.6", - "sha2 0.10.9", - "thiserror 1.0.69", + "rand 0.10.2", + "sha2 0.11.0", + "thiserror 2.0.19", "tokio", "windows 0.62.2", - "windows-strings 0.5.1", + "windows-strings", ] -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" version = "0.12.5" @@ -4730,9 +3431,9 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4746,12 +3447,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - [[package]] name = "pbkdf2" version = "0.12.2" @@ -4772,6 +3467,16 @@ dependencies = [ "hmac 0.13.0", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -4797,13 +3502,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "phf" -version = "0.11.3" +name = "pest" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pest_meta" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" +dependencies = [ + "pest", ] [[package]] @@ -4812,76 +3549,10 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", - "phf_shared 0.13.1", + "phf_shared", "serde", ] -[[package]] -name = "phf_codegen" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" -dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.6", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "phf_shared" version = "0.13.1" @@ -4897,90 +3568,41 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der 0.7.10", - "pkcs8 0.10.2", - "spki 0.7.3", -] - [[package]] name = "pkcs1" version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" dependencies = [ - "der 0.8.0", + "der 0.8.1", "spki 0.8.0-rc.4", ] -[[package]] -name = "pkcs5" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" -dependencies = [ - "aes 0.8.4", - "cbc 0.1.2", - "der 0.7.10", - "pbkdf2 0.12.2", - "scrypt 0.11.0", - "sha2 0.10.9", - "spki 0.7.3", -] - [[package]] name = "pkcs5" version = "0.8.0-rc.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5a777c6e26664bc9504b3ce3f6133f8f20d9071f130a4f9fcbd3186959d8dd6" dependencies = [ - "aes 0.9.0", + "aes 0.9.2", "aes-gcm 0.11.0-rc.3", - "cbc 0.2.0", - "der 0.8.0", + "cbc 0.2.1", + "der 0.8.1", "pbkdf2 0.13.0", "rand_core 0.10.1", - "scrypt 0.12.0", + "scrypt", "sha2 0.11.0", "spki 0.8.0-rc.4", ] -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.10", - "pkcs5 0.7.1", - "rand_core 0.6.4", - "spki 0.7.3", -] - [[package]] name = "pkcs8" version = "0.11.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" dependencies = [ - "der 0.8.0", - "pkcs5 0.8.0-rc.13", + "der 0.8.1", + "pkcs5", "rand_core 0.10.1", "spki 0.8.0-rc.4", ] @@ -4991,65 +3613,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "plist" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" -dependencies = [ - "base64 0.22.1", - "indexmap 2.14.0", - "quick-xml 0.39.2", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.11.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - [[package]] name = "poly1305" version = "0.8.0" @@ -5075,9 +3638,9 @@ dependencies = [ [[package]] name = "polyval" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" dependencies = [ "cpubits", "cpufeatures 0.3.0", @@ -5086,9 +3649,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "portpicker" @@ -5096,7 +3659,40 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" dependencies = [ - "rand 0.8.6", + "rand 0.8.7", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.12" +source = "git+https://github.com/t8y2/tokio-postgres-gaussdb.git?rev=115f9fef10f0fc3669b5337955e4eb461fc349a6#115f9fef10f0fc3669b5337955e4eb461fc349a6" +dependencies = [ + "base64 0.23.0", + "byteorder", + "bytes", + "fallible-iterator 0.2.0", + "hmac 0.13.0", + "md-5", + "memchr", + "pbkdf2 0.12.2", + "rand 0.10.2", + "sha1 0.10.7", + "sha2 0.11.0", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.14" +source = "git+https://github.com/t8y2/tokio-postgres-gaussdb.git?rev=115f9fef10f0fc3669b5337955e4eb461fc349a6#115f9fef10f0fc3669b5337955e4eb461fc349a6" +dependencies = [ + "bytes", + "chrono", + "fallible-iterator 0.2.0", + "postgres-protocol", + "serde_core", + "serde_json", + "uuid", ] [[package]] @@ -5124,10 +3720,15 @@ dependencies = [ ] [[package]] -name = "precomputed-hash" -version = "0.1.1" +name = "prefix-trie" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] [[package]] name = "pretty-hex" @@ -5135,16 +3736,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "primefield" version = "0.14.0-rc.7" @@ -5152,7 +3743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93401c13cc7ff24684571cfca9d3cf9ebabfaf3d4b7b9963ade41ec54da196b5" dependencies = [ "crypto-bigint", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "rand_core 0.10.1", "rustcrypto-ff", "subtle", @@ -5168,68 +3759,57 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-utils" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" dependencies = [ "proc-macro2", "quote", - "version_check", + "smallvec", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] +[[package]] +name = "prometheus-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "811031bea65e5a401fb2e1f37d802cca6601e204ac463809a3189352d13b78a5" +dependencies = [ + "chrono", + "itertools", + "once_cell", + "regex", +] + +[[package]] +name = "psm" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" +dependencies = [ + "ar_archive_writer", + "cc", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -5260,20 +3840,11 @@ dependencies = [ "memchr", ] -[[package]] -name = "quick-xml" -version = "0.39.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" -dependencies = [ - "memchr", -] - [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -5281,9 +3852,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", + "rustls 0.23.42", "socket2", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tracing", "web-time", @@ -5291,20 +3862,21 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "bytes", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.4", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", - "rustls", + "rustls 0.23.42", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror 2.0.19", "tinyvec", "tracing", "web-time", @@ -5312,23 +3884,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -5353,22 +3925,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.7.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -5377,9 +3936,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -5387,25 +3946,15 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "chacha20 0.10.0", - "getrandom 0.4.2", + "chacha20 0.10.1", + "getrandom 0.4.3", "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -5426,15 +3975,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -5460,19 +4000,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rand_pcg" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "rand_core 0.5.1", + "rand_core 0.10.1", ] [[package]] -name = "raw-window-handle" -version = "0.6.2" +name = "rayon" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn 2.0.119", +] [[package]] name = "redis" @@ -5483,18 +4057,22 @@ dependencies = [ "bytes", "cfg-if", "combine", + "crc16", + "futures-sink", "futures-util", "itoa", + "log", "num-bigint", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-native-certs", + "rand 0.9.5", + "rustls 0.23.42", + "rustls-native-certs 0.8.4", "ryu", "sha1_smol", "socket2", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-util", "url", ] @@ -5505,54 +4083,14 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", -] - -[[package]] -name = "redox_syscall" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" -dependencies = [ - "bitflags 2.11.1", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "bitflags 2.13.1", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -5562,9 +4100,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -5573,9 +4111,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rend" @@ -5594,34 +4132,27 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", - "encoding_rs", - "futures-channel", "futures-core", "futures-util", - "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "mime", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", - "rustls", + "rustls 0.23.42", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-util", "tower", "tower-http", @@ -5629,48 +4160,9 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.4.2", - "web-sys", - "webpki-roots 1.0.7", -] - -[[package]] -name = "reqwest" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams 0.5.0", + "wasm-streams", "web-sys", + "webpki-roots 1.0.9", ] [[package]] @@ -5681,38 +4173,14 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "rfc6979" -version = "0.5.0-rc.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a3127ee32baec36af75b4107082d9bd823501ec14a4e016be4b6b37faa74ae" +checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3" dependencies = [ "hmac 0.13.0", "subtle", ] -[[package]] -name = "rfd" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" -dependencies = [ - "block2", - "dispatch2", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.60.2", -] - [[package]] name = "ring" version = "0.17.14" @@ -5756,26 +4224,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1 0.7.5", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "signature 2.2.0", - "spki 0.7.3", - "subtle", - "zeroize", -] - [[package]] name = "rsa" version = "0.10.0-rc.16" @@ -5786,40 +4234,54 @@ dependencies = [ "crypto-bigint", "crypto-primes", "digest 0.11.3", - "pkcs1 0.8.0-rc.4", - "pkcs8 0.11.0-rc.11", + "pkcs1", + "pkcs8", "rand_core 0.10.1", "sha2 0.11.0", - "signature 3.0.0", + "signature", "spki 0.8.0-rc.4", "zeroize", ] [[package]] -name = "russh" -version = "0.60.2" +name = "rusqlite" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e358980fe9b079b99da387117864ee6f0a3fd02f39e5b5fde6af9c2895374" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.13.1", + "fallible-iterator 0.3.0", + "fallible-streaming-iterator", + "hashlink 0.9.1", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "russh" +version = "0.60.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324b92f459d3e42da294e14e8eb150d2215fcfb7c966838bc1127cd68bc05a0d" dependencies = [ "aead 0.6.0-rc.10", "aes 0.8.4", - "aes 0.9.0", + "aes 0.9.2", "aes-gcm 0.11.0-rc.3", "aws-lc-rs", - "bitflags 2.11.1", + "bitflags 2.13.1", "block-padding 0.3.3", "byteorder", "bytes", "cbc 0.1.2", - "cbc 0.2.0", - "cipher 0.5.1", + "cbc 0.2.1", + "cipher 0.5.2", "crypto-bigint", - "ctr 0.10.0", + "ctr 0.10.1", "ctr 0.9.2", - "curve25519-dalek", + "curve25519-dalek 5.0.0-pre.6", "data-encoding", "delegate", - "der 0.8.0", + "der 0.8.1", "digest 0.10.7", "ecdsa", "ed25519-dalek", @@ -5827,11 +4289,11 @@ dependencies = [ "enum_dispatch", "flate2", "futures", - "generic-array 1.4.1", + "generic-array 1.4.4", "getrandom 0.2.17", "ghash 0.6.0", "hex-literal", - "hkdf 0.13.0", + "hkdf", "hmac 0.12.1", "hmac 0.13.0", "inout 0.1.4", @@ -5839,7 +4301,7 @@ dependencies = [ "internal-russh-num-bigint", "keccak", "log", - "md5 0.7.0", + "md5", "ml-kem", "module-lattice", "num-bigint", @@ -5849,28 +4311,28 @@ dependencies = [ "pageant", "pbkdf2 0.12.2", "pbkdf2 0.13.0", - "pkcs1 0.8.0-rc.4", - "pkcs5 0.8.0-rc.13", - "pkcs8 0.11.0-rc.11", - "polyval 0.7.1", - "rand 0.10.1", + "pkcs1", + "pkcs5", + "pkcs8", + "polyval 0.7.3", + "rand 0.10.2", "rand_core 0.10.1", - "rsa 0.10.0-rc.16", + "rsa", "russh-cryptovec", "russh-util", - "salsa20 0.11.0", - "scrypt 0.12.0", + "salsa20", + "scrypt", "sec1", - "sha1 0.10.6", + "sha1 0.10.7", "sha1 0.11.0", "sha2 0.10.9", "sha2 0.11.0", "sha3", - "signature 3.0.0", + "signature", "spki 0.8.0-rc.4", "ssh-encoding", "subtle", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "typenum", "universal-hash 0.6.1", @@ -5879,12 +4341,12 @@ dependencies = [ [[package]] name = "russh-cryptovec" -version = "0.59.0" +version = "0.60.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36140e8a20297bc2e8338807c3d9ca911f7fa49d7539cbcd6d48d3befd70efd8" +checksum = "37cb4d0360bdd8935392a306d8b5edb539cc455b30e8bf13dd213a0cf7879b40" dependencies = [ "log", - "nix 0.31.2", + "nix", "ssh-encoding", "windows-sys 0.61.2", ] @@ -5903,15 +4365,16 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.41.0" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", - "rand 0.8.6", + "postgres-types", + "rand 0.8.7", "rkyv", "serde", "serde_json", @@ -5920,9 +4383,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -5970,7 +4433,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -5983,7 +4446,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -5992,32 +4455,65 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ - "openssl-probe", + "openssl-probe 0.1.6", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe 0.2.1", "rustls-pki-types", "schannel", "security-framework 3.7.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -6029,41 +4525,24 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", ] [[package]] -name = "rustls-platform-verifier" -version = "0.7.0" +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni 0.22.4", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework 3.7.0", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", + "ring", + "untrusted 0.9.0", ] -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" version = "0.103.13" @@ -6078,9 +4557,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -6088,15 +4567,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher 0.4.4", -] - [[package]] name = "salsa20" version = "0.11.0" @@ -6104,7 +4574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" dependencies = [ "cfg-if", - "cipher 0.5.1", + "cipher 0.5.2", ] [[package]] @@ -6116,6 +4586,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "saturating" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" + [[package]] name = "schannel" version = "0.1.29" @@ -6125,74 +4601,12 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.117", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scrypt" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" -dependencies = [ - "pbkdf2 0.12.2", - "salsa20 0.10.2", - "sha2 0.10.9", -] - [[package]] name = "scrypt" version = "0.12.0" @@ -6201,10 +4615,20 @@ checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" dependencies = [ "cfg-if", "pbkdf2 0.13.0", - "salsa20 0.11.0", + "salsa20", "sha2 0.11.0", ] +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted 0.9.0", +] + [[package]] name = "seahash" version = "4.1.0" @@ -6219,38 +4643,19 @@ checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct", "ctutils", - "der 0.8.0", + "der 0.8.1", "hybrid-array", "subtle", "zeroize", ] -[[package]] -name = "secret-service" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" -dependencies = [ - "aes 0.8.4", - "cbc 0.1.2", - "futures-util", - "generic-array 0.14.7", - "hkdf 0.12.4", - "num", - "once_cell", - "rand 0.8.6", - "serde", - "sha2 0.10.9", - "zbus", -] - [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -6263,7 +4668,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -6280,57 +4685,22 @@ dependencies = [ "libc", ] -[[package]] -name = "selectors" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" -dependencies = [ - "bitflags 2.11.1", - "cssparser", - "derive_more", - "log", - "new_debug_unreachable", - "phf 0.13.1", - "phf_codegen", - "precomputed-hash", - "rustc-hash", - "servo_arc", - "smallvec", -] - [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", ] -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - [[package]] name = "serde_bytes" version = "0.11.19" @@ -6343,42 +4713,31 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap 2.14.0", + "indexmap", "itoa", "memchr", "serde", @@ -6386,35 +4745,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6429,81 +4759,67 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.19.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.0", - "schemars 0.9.0", - "schemars 1.2.1", "serde_core", - "serde_json", "serde_with_macros", - "time", ] [[package]] name = "serde_with_macros" -version = "3.19.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serde_yaml_ng" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", ] [[package]] name = "serdect" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" dependencies = [ "base16ct", "serde", ] -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "servo_arc" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" -dependencies = [ - "stable_deref_trait", -] - [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -6560,42 +4876,19 @@ dependencies = [ ] [[package]] -name = "shared_child" -version = "1.1.1" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "libc", - "sigchld", - "windows-sys 0.60.2", + "lazy_static", ] [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "sigchld" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" -dependencies = [ - "libc", - "os_pipe", - "signal-hook", -] - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -6607,16 +4900,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "3.0.0" @@ -6629,15 +4912,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -6650,10 +4933,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] -name = "siphasher" -version = "1.0.2" +name = "similar" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.19", + "time", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -6663,79 +4964,25 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", ] -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall 0.5.18", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" [[package]] name = "spki" @@ -6754,207 +5001,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" dependencies = [ "base64ct", - "der 0.8.0", + "der 0.8.1", ] [[package]] -name = "sqlx" -version = "0.8.6" +name = "sqlparser" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "13c6d1b651dc4edf07eead2a0c6c78016ce971bc2c10da5266861b13f25e7cec" dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "base64 0.22.1", - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap 2.14.0", "log", - "memchr", - "native-tls", - "once_cell", - "percent-encoding", - "rust_decimal", - "serde", - "serde_json", - "sha2 0.10.9", - "smallvec", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "url", - "uuid", + "recursive", + "sqlparser_derive", ] [[package]] -name = "sqlx-macros" -version = "0.8.6" +name = "sqlparser_derive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "a6dd45d8fc1c79299bfbb7190e42ccbbdf6a5f52e4a6ad98d92357ea965bd289" dependencies = [ "proc-macro2", "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "sqlx-macros-core" -version = "0.8.6" +name = "ssfmt" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "8cafd85a137485737308ec6b94c19639833ca96bb83cbad2bc94a0e0eb763410" dependencies = [ - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2 0.10.9", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.117", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.11.1", - "byteorder", - "bytes", - "chrono", - "crc", - "digest 0.10.7", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array 0.14.7", - "hex", - "hkdf 0.12.4", - "hmac 0.12.1", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.6", - "rsa 0.9.10", - "rust_decimal", - "serde", - "sha1 0.10.6", - "sha2 0.10.9", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.11.1", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf 0.12.4", - "hmac 0.12.1", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.6", - "rust_decimal", - "serde", - "serde_json", - "sha2 0.10.9", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.18", - "tracing", - "url", - "uuid", + "lru 0.16.4", + "thiserror 2.0.19", ] [[package]] @@ -6993,33 +5072,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "static_assertions" -version = "1.1.0" +name = "stacker" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "string_cache" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.13.1", - "precomputed-hash", -] - -[[package]] -name = "string_cache_codegen" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" -dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", - "proc-macro2", - "quote", + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", ] [[package]] @@ -7054,10 +5116,10 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -7066,17 +5128,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - [[package]] name = "syn" version = "1.0.109" @@ -7090,9 +5141,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -7116,7 +5178,21 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "sysinfo" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", ] [[package]] @@ -7125,7 +5201,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -7140,19 +5216,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - [[package]] name = "tagptr" version = "0.2.0" @@ -7165,57 +5228,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" -[[package]] -name = "tao" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cf65722394c2ac443e80120064987f8914ee1d4e4e36e63cdf10f2990f01159" -dependencies = [ - "bitflags 2.11.1", - "block2", - "core-foundation 0.10.1", - "core-graphics", - "crossbeam-channel", - "dbus", - "dispatch2", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni 0.21.1", - "libc", - "log", - "ndk", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "once_cell", - "parking_lot", - "percent-encoding", - "raw-window-handle", - "tao-macros", - "unicode-segmentation", - "url", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "tap" version = "1.0.1" @@ -7224,378 +5236,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", "xattr", ] -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d059f2527558d9dba6f186dec4772610e1aecfd3f94002397613e7e648752b66" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni 0.21.1", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest 0.13.3", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.18", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows 0.61.3", -] - -[[package]] -name = "tauri-build" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9aa8c59a894f76c29a002501c589de5eb4987a5913d62a6e0a47f320901988" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e4e8230d565106aa19dfbaa01a7ed01abf78047fe0577a83377224bd1bf20e" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png 0.17.16", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2 0.10.9", - "syn 2.0.117", - "tauri-utils", - "thiserror 2.0.18", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc8de2cddbbc33dbdf4c84f170121886595efdbcc9cb4b3d76342b79d082cedc" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d5f58bfd0cdcfdbc0a68dc08b354eea2afc551b421de91b07b69e0dd769d57" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "walkdir", -] - -[[package]] -name = "tauri-plugin-dialog" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" -dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.18", - "url", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" -dependencies = [ - "anyhow", - "dunce", - "glob", - "log", - "objc2-foundation", - "percent-encoding", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.18", - "toml 1.1.2+spec-1.1.0", - "url", -] - -[[package]] -name = "tauri-plugin-log" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7545bd67f070a4500432c826e2e0682146a1d6712aee22a2786490156b574d93" -dependencies = [ - "android_logger", - "byte-unit", - "fern", - "log", - "objc2", - "objc2-foundation", - "serde", - "serde_json", - "serde_repr", - "swift-rs", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "tauri-plugin-process" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a" -dependencies = [ - "tauri", - "tauri-plugin", -] - -[[package]] -name = "tauri-plugin-shell" -version = "2.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b" -dependencies = [ - "encoding_rs", - "log", - "open", - "os_pipe", - "regex", - "schemars 0.8.22", - "serde", - "serde_json", - "shared_child", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "tauri-plugin-updater" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" -dependencies = [ - "base64 0.22.1", - "dirs", - "flate2", - "futures-util", - "http", - "infer", - "log", - "minisign-verify", - "osakit", - "percent-encoding", - "reqwest 0.13.3", - "rustls", - "semver", - "serde", - "serde_json", - "tar", - "tauri", - "tauri-plugin", - "tempfile", - "thiserror 2.0.18", - "time", - "tokio", - "url", - "windows-sys 0.60.2", - "zip 4.6.1", -] - -[[package]] -name = "tauri-runtime" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e42bbcb76237351fbaa02f08d808c537dc12eb5a6eabbf3e517b50056334d95" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni 0.21.1", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cadb13dad0c681e1e0a2c49ae488f0e2906ded3d57e7a0017f4aaf46e387117" -dependencies = [ - "gtk", - "http", - "jni 0.21.1", - "log", - "objc2", - "objc2-app-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f61d2bf7188fbcf2b0ed095b67a6bc498f713c939314bb19eb700118a573b7" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dom_query", - "dunce", - "glob", - "http", - "infer", - "json-patch", - "log", - "memchr", - "phf 0.11.3", - "plist", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.18", - "toml 1.1.2+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" -dependencies = [ - "dunce", - "embed-resource", - "toml 1.1.2+spec-1.1.0", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -7603,20 +5252,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", ] [[package]] -name = "tendril" -version = "0.5.0" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "new_debug_unreachable", - "utf-8", + "winapi-util", ] [[package]] @@ -7630,11 +5278,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -7645,18 +5293,27 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", ] [[package]] @@ -7665,7 +5322,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1446cb4198848d1562301a3340424b4f425ef79f35ef9ee034769a9dd92c10d" dependencies = [ - "async-native-tls", "async-trait", "asynchronous-codec", "byteorder", @@ -7680,23 +5336,24 @@ dependencies = [ "pin-project-lite", "pretty-hex", "rust_decimal", + "rustls-native-certs 0.6.3", + "rustls-pemfile 1.0.4", "thiserror 1.0.69", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", "tracing", "uuid", - "winauth", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", - "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde_core", "time-core", @@ -7705,15 +5362,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -7740,9 +5397,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -7754,10 +5411,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "tokio" -version = "1.52.1" +name = "tls_codec" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -7772,22 +5450,62 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +name = "tokio-postgres" +version = "0.7.18" +source = "git+https://github.com/t8y2/tokio-postgres-gaussdb.git?rev=115f9fef10f0fc3669b5337955e4eb461fc349a6#115f9fef10f0fc3669b5337955e4eb461fc349a6" dependencies = [ - "native-tls", + "async-trait", + "byteorder", + "bytes", + "fallible-iterator 0.2.0", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.10.2", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + +[[package]] +name = "tokio-postgres-rustls" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d684bad428a0f2481f42241f821db42c54e2dc81d8c00db8536c506b0a0144" +dependencies = [ + "const-oid 0.9.6", + "ring", + "rustls 0.23.42", + "tokio", + "tokio-postgres", + "tokio-rustls 0.26.4", + "x509-cert", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", "tokio", ] @@ -7797,95 +5515,26 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", + "rustls 0.23.42", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", + "futures-util", + "libc", "pin-project-lite", "tokio", ] -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.14.0", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" -dependencies = [ - "indexmap 2.14.0", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 1.0.2", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -7897,55 +5546,25 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "indexmap 2.14.0", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.14.0", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.25.11+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime 1.1.1+spec-1.1.0", + "indexmap", + "toml_datetime", "toml_parser", - "winnow 1.0.2", + "winnow", ] [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow 1.0.2", + "winnow", ] -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - [[package]] name = "tower" version = "0.5.3" @@ -7963,20 +5582,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.1", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -8011,7 +5630,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -8021,28 +5640,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", ] [[package]] -name = "tray-icon" -version = "0.23.1" +name = "tracing-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", + "log", "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.18", - "windows-sys 0.61.2", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -8051,6 +5674,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "twox-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" + [[package]] name = "typed-builder" version = "0.22.0" @@ -8068,72 +5697,20 @@ checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "uds_windows" -version = "1.2.1" +name = "ucd-trie" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" -dependencies = [ - "memoffset", - "tempfile", - "windows-sys 0.61.2", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-bidi" @@ -8164,9 +5741,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -8196,10 +5773,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.7.1" @@ -8212,6 +5795,34 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64 0.22.1", + "log", + "percent-encoding", + "rustls 0.23.42", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots 1.0.9", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -8222,32 +5833,13 @@ dependencies = [ "idna", "percent-encoding", "serde", - "serde_derive", ] [[package]] -name = "urlpattern" -version = "0.3.0" +name = "utf8-zero" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" [[package]] name = "utf8_iter" @@ -8257,21 +5849,21 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", ] [[package]] -name = "value-bag" -version = "1.12.0" +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -8279,38 +5871,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -8330,12 +5896,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -8343,34 +5903,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" +name = "wasi" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ - "wit-bindgen 0.57.1", + "wasip2", ] [[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +name = "wasip2" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasite" -version = "0.1.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" +dependencies = [ + "wasi 0.14.7+wasi-0.2.4", +] [[package]] name = "wasm-bindgen" -version = "0.2.120" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -8382,9 +5945,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.70" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -8392,9 +5955,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.120" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8402,48 +5965,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - [[package]] name = "wasm-streams" version = "0.4.2" @@ -8457,36 +5998,11 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.97" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -8502,133 +6018,35 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web_atoms" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" -dependencies = [ - "phf 0.13.1", - "phf_codegen", - "string_cache", - "string_cache_codegen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.7", + "webpki-roots 1.0.9", ] [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webview2-com" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" -dependencies = [ - "thiserror 2.0.18", - "windows 0.61.3", - "windows-core 0.61.2", -] - [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" dependencies = [ + "libc", "libredox", + "objc2-system-configuration", "wasite", + "web-sys", ] [[package]] @@ -8668,45 +6086,14 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winauth" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f820cd208ce9c6b050812dc2d724ba98c6c1e9db5ce9b3f58d925ae5723a5e6" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "md5 0.6.1", - "rand 0.7.3", - "winapi", -] - -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - [[package]] name = "windows" -version = "0.61.3" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", + "windows-core 0.57.0", + "windows-targets", ] [[package]] @@ -8715,19 +6102,10 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", + "windows-collections", "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-future", + "windows-numerics", ] [[package]] @@ -8741,15 +6119,14 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.2" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets", ] [[package]] @@ -8758,22 +6135,11 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", + "windows-strings", ] [[package]] @@ -8783,8 +6149,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -8795,7 +6172,18 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -8806,31 +6194,15 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" @@ -8838,7 +6210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core 0.62.2", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -8847,18 +6219,18 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-strings", ] [[package]] name = "windows-result" -version = "0.3.4" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-link 0.1.3", + "windows-targets", ] [[package]] @@ -8867,16 +6239,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -8885,25 +6248,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-link", ] [[package]] @@ -8912,7 +6257,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -8921,16 +6266,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -8939,37 +6275,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] [[package]] @@ -8978,40 +6284,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -9020,376 +6300,78 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" -version = "0.5.40" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - -[[package]] -name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" -[[package]] -name = "wry" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3013fd6116aac351dd2e18f349b28b2cfef3a5ff3253a9d0ce2d7193bb1b4429" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dom_query", - "dpi", - "dunce", - "gdkx11", - "gtk", - "http", - "javascriptcore-rs", - "jni 0.21.1", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2 0.10.9", - "soup3", - "tao-macros", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - [[package]] name = "wyz" version = "0.5.1" @@ -9400,24 +6382,15 @@ dependencies = [ ] [[package]] -name = "x11" -version = "2.21.0" +name = "x509-cert" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", + "const-oid 0.9.6", + "der 0.7.10", + "spki 0.7.3", + "tls_codec", ] [[package]] @@ -9430,21 +6403,11 @@ dependencies = [ "rustix 1.1.4", ] -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -9459,91 +6422,35 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] -[[package]] -name = "zbus" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" -dependencies = [ - "async-broadcast", - "async-process", - "async-recursion", - "async-trait", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.29.0", - "ordered-stream", - "rand 0.8.6", - "serde", - "serde_repr", - "sha1 0.10.6", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant", -] - [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -9556,28 +6463,28 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -9610,7 +6517,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -9622,7 +6529,7 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.14.0", + "indexmap", "memchr", "zopfli", ] @@ -9636,22 +6543,22 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.14.0", + "indexmap", "memchr", "zopfli", ] [[package]] name = "zlib-rs" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zopfli" @@ -9666,38 +6573,29 @@ dependencies = [ ] [[package]] -name = "zvariant" -version = "4.2.0" +name = "zstd" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "zvariant_derive", + "zstd-safe", ] [[package]] -name = "zvariant_derive" -version = "4.2.0" +name = "zstd-safe" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", - "zvariant_utils", + "zstd-sys", ] [[package]] -name = "zvariant_utils" -version = "2.1.0" +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "cc", + "pkg-config", ] diff --git a/agents/drivers/duckdb/Cargo.toml b/agents/drivers/duckdb/Cargo.toml new file mode 100644 index 0000000000..777e42ab32 --- /dev/null +++ b/agents/drivers/duckdb/Cargo.toml @@ -0,0 +1,50 @@ +[workspace] + +[package] +name = "dbx-duckdb-driver" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +publish = false + +[[bin]] +name = "duckdb-worker-hanging-connect-test-host" +path = "tests/support/duckdb_worker_hanging_connect_test_host.rs" +test = false + +[[bin]] +name = "duckdb-worker-pid-test-host" +path = "tests/support/duckdb_worker_pid_test_host.rs" +test = false + +[[bin]] +name = "duckdb-worker-file-lock-owner" +path = "tests/support/duckdb_worker_file_lock_owner.rs" +test = false + +[dependencies] +chrono = "0.4" +duckdb = { version = "1.3.2", features = ["bundled", "parquet"] } +log = "0.4" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["full"] } + +[dev-dependencies] +dbx-core = { path = "../../../crates/dbx-core", default-features = false, features = ["duckdb-sidecar"] } +sysinfo = "0.32" +tokio-util = "0.7" +uuid = { version = "1", features = ["v4"] } + +[patch.crates-io] +tokio-postgres = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", rev = "115f9fef10f0fc3669b5337955e4eb461fc349a6" } +postgres-types = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", rev = "115f9fef10f0fc3669b5337955e4eb461fc349a6" } +postgres-protocol = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", rev = "115f9fef10f0fc3669b5337955e4eb461fc349a6" } +mysql_async = { git = "https://github.com/t8y2/mysql_async.git", rev = "2be6e392eb9b06d20dcd2d8ed8eae748d413c9ec" } + +[profile.release] +panic = "abort" +strip = true +lto = true +codegen-units = 1 +opt-level = "s" diff --git a/agents/drivers/duckdb/README.md b/agents/drivers/duckdb/README.md new file mode 100644 index 0000000000..c24550cbeb --- /dev/null +++ b/agents/drivers/duckdb/README.md @@ -0,0 +1,42 @@ +# DBX DuckDB Standalone Driver + +This directory contains the standalone Rust DuckDB sidecar. It reuses DBX's +existing newline-delimited JSON worker runtime, while keeping DuckDB and +`libduckdb-sys` outside the main application dependency graph. + +## Build + +```bash +cargo build --manifest-path agents/drivers/duckdb/Cargo.toml --release --bin dbx-duckdb-driver +``` + +Point DBX at the resulting executable with: + +```bash +DBX_DUCKDB_DRIVER_PATH=/absolute/path/to/dbx-duckdb-driver \ + cargo run -p dbx --no-default-features --features duckdb-sidecar +``` + +Release builds publish this driver through the DBX driver registry. Driver +Manager installs it as `~/.dbx/agents/drivers/duckdb/agent` (or `agent.exe` on +Windows). `DBX_DUCKDB_DRIVER_PATH` remains available for local development. + +## Release package + +Each platform is published as a self-contained `.tar.zst` package used by both +online Driver Manager installation and manual single-driver import: + +```text +agent-registry.json +drivers/dbx-agent-duckdb--[.exe] +``` + +DBX decompresses the package itself, so users do not need to install `zstd`, +DuckDB, or a separate database driver. The existing aggregate offline `.zip` +packages remain supported for backward compatibility. + +## Current scope + +The driver implements connect, execute, database/schema/table/column metadata, +table DDL, view source, completion assistance, attach, cancel, and shutdown over +the DBX sidecar protocol. diff --git a/crates/dbx-core/src/db/duckdb_driver.rs b/agents/drivers/duckdb/src/connection.rs similarity index 90% rename from crates/dbx-core/src/db/duckdb_driver.rs rename to agents/drivers/duckdb/src/connection.rs index c64cd18159..9981b69838 100644 --- a/crates/dbx-core/src/db/duckdb_driver.rs +++ b/agents/drivers/duckdb/src/connection.rs @@ -1,21 +1,17 @@ -#![cfg(feature = "duckdb-bundled")] - use std::fs; use std::path::Path; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::sync::Mutex; pub struct DuckDbConnection { connection: Mutex, interrupt_handle: Arc, - draining: AtomicBool, } impl DuckDbConnection { pub fn new(connection: duckdb::Connection) -> Self { let interrupt_handle = connection.interrupt_handle(); - Self { connection: Mutex::new(connection), interrupt_handle, draining: AtomicBool::new(false) } + Self { connection: Mutex::new(connection), interrupt_handle } } pub fn lock(&self) -> std::sync::LockResult> { @@ -26,19 +22,8 @@ impl DuckDbConnection { self.interrupt_handle.clone() } - pub fn mark_draining(&self) { - self.draining.store(true, Ordering::SeqCst); - } - - pub fn clear_draining(&self) { - self.draining.store(false, Ordering::SeqCst); - } - - pub fn is_draining(&self) -> bool { - self.draining.load(Ordering::SeqCst) - } - // Preserve PoisonError ownership so close_connection can still release the contained DuckDB handle. + #[cfg(test)] #[allow(clippy::result_large_err)] fn into_inner(self) -> std::sync::LockResult { self.connection.into_inner() @@ -69,8 +54,8 @@ pub fn connect_path(path: &str) -> Result, String> { /// Runs a connection init script statement-by-statement so a failure can /// point at the offending statement instead of the whole batch. pub fn run_init_script(con: &duckdb::Connection, script: &str) -> Result<(), String> { - for (index, statement) in crate::db::duckdb_sql::split_sql_statements(script).iter().enumerate() { - if crate::db::duckdb_sql::strip_leading_comments(statement).is_empty() { + for (index, statement) in crate::sql::split_sql_statements(script).iter().enumerate() { + if crate::sql::strip_leading_comments(statement).is_empty() { continue; } con.execute_batch(statement) @@ -131,7 +116,8 @@ pub fn is_memory_database_path(path: &str) -> bool { /// Unlike relying on Drop, this calls `duckdb_disconnect` synchronously /// so the file handle is released before this function returns. /// On Windows this prevents "file already in use" errors when reconnecting. -pub fn close_connection(con: Arc) { +#[cfg(test)] +fn close_connection(con: Arc) { match Arc::try_unwrap(con) { Ok(handle) => match handle.into_inner() { Ok(conn) => { diff --git a/agents/drivers/duckdb/src/lib.rs b/agents/drivers/duckdb/src/lib.rs new file mode 100644 index 0000000000..62e292f476 --- /dev/null +++ b/agents/drivers/duckdb/src/lib.rs @@ -0,0 +1,8 @@ +mod connection; +mod query; +mod runtime; +mod schema; +mod sql; +mod wire; + +pub use runtime::run_stdio_worker; diff --git a/agents/drivers/duckdb/src/main.rs b/agents/drivers/duckdb/src/main.rs new file mode 100644 index 0000000000..3611a14878 --- /dev/null +++ b/agents/drivers/duckdb/src/main.rs @@ -0,0 +1,7 @@ +#[tokio::main] +async fn main() { + if let Err(error) = dbx_duckdb_driver::run_stdio_worker().await { + eprintln!("DuckDB driver failed: {error}"); + std::process::exit(1); + } +} diff --git a/agents/drivers/duckdb/src/query.rs b/agents/drivers/duckdb/src/query.rs new file mode 100644 index 0000000000..48147455bb --- /dev/null +++ b/agents/drivers/duckdb/src/query.rs @@ -0,0 +1,436 @@ +#![allow(clippy::items_after_test_module)] + +use chrono::{DateTime, Duration as ChronoDuration, NaiveDate, NaiveDateTime, NaiveTime, Utc}; +use duckdb::types::{TimeUnit, Value, ValueRef}; + +use crate::wire as db; + +const MAX_ROWS: usize = 10000; + +fn query_result_row_limit(max_rows: Option) -> usize { + max_rows.unwrap_or(MAX_ROWS).max(1) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn duckdb_execute_preserves_double_precision() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + let result = duckdb_execute( + &con, + "SELECT 12.34567::DOUBLE AS sample, 0.5::DOUBLE AS half, 99.99::DOUBLE AS price, 1.0::DOUBLE AS one", + ) + .expect("execute double query"); + + assert_eq!(result.columns, vec!["sample", "half", "price", "one"]); + let row = &result.rows[0]; + assert_eq!(row[0], serde_json::json!(12.34567)); + assert_eq!(row[1], serde_json::json!(0.5)); + assert_eq!(row[2], serde_json::json!(99.99)); + assert_eq!(row[3], serde_json::json!(1.0)); + } + + #[test] + fn duckdb_execute_create_insert_select_double() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + con.execute_batch("CREATE TABLE tmp1 (tmp_double DOUBLE)").expect("create table"); + con.execute_batch("INSERT INTO tmp1 VALUES (45.678), (12.345), (99.999)").expect("insert"); + + let result = duckdb_execute(&con, "SELECT tmp_double FROM tmp1 ORDER BY tmp_double").expect("select doubles"); + + assert_eq!(result.rows.len(), 3); + assert_eq!(result.rows[0][0], serde_json::json!(12.345)); + assert_eq!(result.rows[1][0], serde_json::json!(45.678)); + assert_eq!(result.rows[2][0], serde_json::json!(99.999)); + } + + #[test] + fn duckdb_execute_returns_rows_for_from_first_query() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + con.execute_batch("CREATE TABLE users (id INTEGER, name VARCHAR)").expect("create table"); + con.execute_batch("INSERT INTO users VALUES (2, 'Grace'), (1, 'Ada')").expect("insert"); + + let result = duckdb_execute(&con, "FROM users ORDER BY id").expect("execute from-first query"); + + assert_eq!(result.columns, vec!["id", "name"]); + assert_eq!(result.rows.len(), 2); + assert_eq!(result.rows[0], vec![serde_json::json!(1), serde_json::json!("Ada")]); + assert_eq!(result.rows[1], vec![serde_json::json!(2), serde_json::json!("Grace")]); + } + + #[test] + fn duckdb_execute_returns_rows_for_summarize_query() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + con.execute_batch("CREATE TABLE metrics (value INTEGER)").expect("create table"); + con.execute_batch("INSERT INTO metrics VALUES (1), (2), (NULL)").expect("insert"); + + let result = duckdb_execute(&con, "SUMMARIZE metrics").expect("execute summarize query"); + + assert!(!result.columns.is_empty()); + assert!(!result.rows.is_empty()); + } + + #[test] + fn duckdb_execute_handles_various_types() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + let result = duckdb_execute( + &con, + "SELECT 42 AS int_val, true AS bool_val, 'hello' AS text_val, 3.14::FLOAT AS float_val, 123456789012345::BIGINT AS big_val", + ) + .expect("execute mixed types query"); + + let row = &result.rows[0]; + assert_eq!(row[0], serde_json::json!(42)); + assert_eq!(row[1], serde_json::json!(true)); + assert_eq!(row[2], serde_json::Value::String("hello".to_string())); + assert!(row[3].is_number()); + assert_eq!(row[4], serde_json::json!(123456789012345_i64)); + } + + #[test] + fn duckdb_execute_returns_list_values_as_json_arrays() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + let result = duckdb_execute(&con, "SELECT ['a','b','c','d'];").expect("execute list query"); + + assert_eq!(result.rows, vec![vec![serde_json::json!(["a", "b", "c", "d"])]]); + } + + #[test] + fn duckdb_execute_preserves_nulls_inside_list_values() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + let result = duckdb_execute(&con, "SELECT [1, NULL, 3] AS items;").expect("execute nullable list query"); + + assert_eq!(result.columns, vec!["items"]); + assert_eq!(result.rows, vec![vec![serde_json::json!([1, null, 3])]]); + } + + #[test] + fn duckdb_execute_returns_nested_complex_values_as_json() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + let result = duckdb_execute( + &con, + "SELECT {'name': 'Ada', 'scores': [10, 20]} AS profile, MAP(['x', 'y'], [1, 2]) AS lookup, [1, 2, 3]::INTEGER[3] AS fixed_items", + ) + .expect("execute complex values query"); + + assert_eq!(result.columns, vec!["profile", "lookup", "fixed_items"]); + assert_eq!( + result.rows, + vec![vec![ + serde_json::json!({ "name": "Ada", "scores": [10, 20] }), + serde_json::json!([ + { "key": "x", "value": 1 }, + { "key": "y", "value": 2 }, + ]), + serde_json::json!([1, 2, 3]), + ]] + ); + } + + #[test] + fn duckdb_execute_formats_temporal_values_by_column_type() { + let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); + let result = duckdb_execute( + &con, + "SELECT DATE '2026-05-14' AS d, TIME '16:58:15' AS t, TIMESTAMP '2026-05-14 16:58:15.0' AS ts, NULL::TIMESTAMP AS nts", + ) + .expect("execute temporal query"); + + assert_eq!(result.columns, vec!["d", "t", "ts", "nts"]); + assert_eq!( + result.rows, + vec![vec![ + serde_json::Value::String("2026-05-14".to_string()), + serde_json::Value::String("16:58:15".to_string()), + serde_json::Value::String("2026-05-14 16:58:15".to_string()), + serde_json::Value::Null, + ]] + ); + } +} + +#[cfg(test)] +pub fn duckdb_execute(con: &duckdb::Connection, sql: &str) -> Result { + duckdb_execute_with_max_rows(con, sql, None) +} + +fn duckdb_value_to_json(row: &duckdb::Row<'_>, idx: usize) -> serde_json::Value { + let Ok(value_ref) = row.get_ref(idx) else { + return serde_json::Value::Null; + }; + match value_ref { + ValueRef::Null => serde_json::Value::Null, + ValueRef::Boolean(b) => serde_json::Value::Bool(b), + ValueRef::TinyInt(i) => serde_json::Value::Number((i as i64).into()), + ValueRef::SmallInt(i) => serde_json::Value::Number((i as i64).into()), + ValueRef::Int(i) => serde_json::Value::Number((i as i64).into()), + ValueRef::BigInt(i) => serde_json::Value::Number(i.into()), + ValueRef::HugeInt(i) => serde_json::Value::String(i.to_string()), + ValueRef::UTinyInt(i) => serde_json::Value::Number((i as u64).into()), + ValueRef::USmallInt(i) => serde_json::Value::Number((i as u64).into()), + ValueRef::UInt(i) => serde_json::Value::Number((i as u64).into()), + ValueRef::UBigInt(i) => serde_json::Value::Number(i.into()), + ValueRef::Float(f) => { + serde_json::Number::from_f64(f as f64).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null) + } + ValueRef::Double(f) => { + serde_json::Number::from_f64(f).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null) + } + ValueRef::Decimal(d) => serde_json::Value::String(d.to_string()), + ValueRef::Date32(days) => { + duckdb_date32_to_string(days).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) + } + ValueRef::Time64(unit, value) => { + duckdb_time64_to_string(unit, value).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) + } + ValueRef::Timestamp(unit, value) => { + duckdb_timestamp_to_string(unit, value).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) + } + ValueRef::Text(bytes) => std::str::from_utf8(bytes) + .map(|s| serde_json::Value::String(s.to_string())) + .unwrap_or(serde_json::Value::Null), + ValueRef::Blob(bytes) => { + let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect(); + serde_json::Value::String(format!("\\x{hex}")) + } + ValueRef::Interval { months, days, nanos } => { + serde_json::Value::String(duckdb_interval_to_string(months, days, nanos)) + } + ValueRef::List(..) + | ValueRef::Array(..) + | ValueRef::Struct(..) + | ValueRef::Map(..) + | ValueRef::Enum(..) + | ValueRef::Union(..) => duckdb_owned_value_to_json(&value_ref.to_owned()), + _ => duckdb_owned_value_to_json(&value_ref.to_owned()), + } +} + +fn duckdb_owned_value_to_json(value: &Value) -> serde_json::Value { + match value { + Value::Null => serde_json::Value::Null, + Value::Boolean(b) => serde_json::Value::Bool(*b), + Value::TinyInt(i) => serde_json::Value::Number((*i as i64).into()), + Value::SmallInt(i) => serde_json::Value::Number((*i as i64).into()), + Value::Int(i) => serde_json::Value::Number((*i as i64).into()), + Value::BigInt(i) => serde_json::Value::Number((*i).into()), + Value::HugeInt(i) => serde_json::Value::String(i.to_string()), + Value::UTinyInt(i) => serde_json::Value::Number((*i as u64).into()), + Value::USmallInt(i) => serde_json::Value::Number((*i as u64).into()), + Value::UInt(i) => serde_json::Value::Number((*i as u64).into()), + Value::UBigInt(i) => serde_json::Value::Number((*i).into()), + Value::Float(f) => { + serde_json::Number::from_f64(*f as f64).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null) + } + Value::Double(f) => { + serde_json::Number::from_f64(*f).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null) + } + Value::Decimal(d) => serde_json::Value::String(d.to_string()), + Value::Timestamp(unit, value) => { + duckdb_timestamp_to_string(*unit, *value).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) + } + Value::Text(text) | Value::Enum(text) => serde_json::Value::String(text.clone()), + Value::Blob(bytes) => { + let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect(); + serde_json::Value::String(format!("\\x{hex}")) + } + Value::Date32(days) => { + duckdb_date32_to_string(*days).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) + } + Value::Time64(unit, value) => { + duckdb_time64_to_string(*unit, *value).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) + } + Value::Interval { months, days, nanos } => { + serde_json::Value::String(duckdb_interval_to_string(*months, *days, *nanos)) + } + Value::List(values) | Value::Array(values) => { + serde_json::Value::Array(values.iter().map(duckdb_owned_value_to_json).collect()) + } + Value::Struct(entries) => serde_json::Value::Object( + entries.iter().map(|(key, value)| (key.clone(), duckdb_owned_value_to_json(value))).collect(), + ), + Value::Map(entries) => serde_json::Value::Array( + entries + .iter() + .map(|(key, value)| { + serde_json::json!({ + "key": duckdb_owned_value_to_json(key), + "value": duckdb_owned_value_to_json(value), + }) + }) + .collect(), + ), + Value::Union(value) => duckdb_owned_value_to_json(value), + _ => serde_json::Value::Null, + } +} + +fn duckdb_interval_to_string(months: i32, days: i32, nanos: i64) -> String { + let mut parts = Vec::new(); + if months != 0 { + let years = months / 12; + let rem = months % 12; + if years != 0 { + parts.push(format!("{} year{}", years, if years.abs() != 1 { "s" } else { "" })); + } + if rem != 0 { + parts.push(format!("{} mon{}", rem, if rem.abs() != 1 { "s" } else { "" })); + } + } + if days != 0 { + parts.push(format!("{} day{}", days, if days.abs() != 1 { "s" } else { "" })); + } + if nanos != 0 { + let total_secs = nanos / 1_000_000_000; + let hours = total_secs / 3600; + let mins = (total_secs % 3600) / 60; + let secs = total_secs % 60; + let sub_nanos = (nanos % 1_000_000_000).unsigned_abs(); + if sub_nanos > 0 { + parts.push(format!( + "{:02}:{:02}:{:02}.{}", + hours, + mins, + secs, + format_temporal_without_empty_fraction(format!("0.{:09}", sub_nanos)).trim_start_matches("0.") + )); + } else { + parts.push(format!("{:02}:{:02}:{:02}", hours, mins, secs)); + } + } + if parts.is_empty() { + "00:00:00".to_string() + } else { + parts.join(" ") + } +} + +fn duckdb_date32_to_string(days: i32) -> Option { + let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)?; + epoch.checked_add_signed(ChronoDuration::days(i64::from(days))).map(|date| date.to_string()) +} + +fn duckdb_time64_to_string(unit: TimeUnit, value: i64) -> Option { + let nanos = duckdb_time_unit_to_nanos(unit, value)?; + let seconds = nanos.div_euclid(1_000_000_000); + let nanos_remainder = nanos.rem_euclid(1_000_000_000) as u32; + if !(0..86_400).contains(&seconds) { + return None; + } + let time = NaiveTime::from_num_seconds_from_midnight_opt(seconds as u32, nanos_remainder)?; + Some(format_temporal_without_empty_fraction(time.to_string())) +} + +fn duckdb_timestamp_to_string(unit: TimeUnit, value: i64) -> Option { + let nanos = duckdb_time_unit_to_nanos(unit, value)?; + let seconds = nanos.div_euclid(1_000_000_000); + let nanos_remainder = nanos.rem_euclid(1_000_000_000) as u32; + let dt: DateTime = DateTime::from_timestamp(seconds, nanos_remainder)?; + Some(format_naive_datetime(dt.naive_utc())) +} + +fn duckdb_time_unit_to_nanos(unit: TimeUnit, value: i64) -> Option { + match unit { + TimeUnit::Second => value.checked_mul(1_000_000_000), + TimeUnit::Millisecond => value.checked_mul(1_000_000), + TimeUnit::Microsecond => value.checked_mul(1_000), + TimeUnit::Nanosecond => Some(value), + } +} + +fn format_naive_datetime(value: NaiveDateTime) -> String { + if value.and_utc().timestamp_subsec_nanos() == 0 { + value.format("%Y-%m-%d %H:%M:%S").to_string() + } else { + format_temporal_without_empty_fraction(value.to_string()) + } +} + +fn format_temporal_without_empty_fraction(value: String) -> String { + if !value.contains('.') { + return value; + } + let trimmed = value.trim_end_matches('0').trim_end_matches('.'); + trimmed.to_string() +} + +pub fn duckdb_execute_with_max_rows( + con: &duckdb::Connection, + sql: &str, + max_rows: Option, +) -> Result { + let start = std::time::Instant::now(); + let row_limit = query_result_row_limit(max_rows); + + if crate::sql::starts_with_duckdb_result_sql_keyword(sql) { + let mut stmt = con.prepare(sql).map_err(|e| e.to_string())?; + let mut rows = stmt.query([]).map_err(|e| e.to_string())?; + let stmt_ref = rows.as_ref().ok_or("DuckDB statement unavailable")?; + let col_count = stmt_ref.column_count(); + let columns: Vec = (0..col_count) + .map(|i| stmt_ref.column_name(i).map(|s| s.to_string()).unwrap_or_else(|_| "?".to_string())) + .collect(); + + let mut result_rows = Vec::new(); + while let Some(row) = rows.next().map_err(|e| e.to_string())? { + let vals: Vec = (0..col_count).map(|i| duckdb_value_to_json(row, i)).collect(); + result_rows.push(vals); + if result_rows.len() > row_limit { + break; + } + } + + let truncated = result_rows.len() > row_limit; + if truncated { + result_rows.truncate(row_limit); + } + Ok(db::QueryResult { + columns, + column_types: Vec::new(), + column_sortables: vec![], + rows: result_rows, + affected_rows: 0, + execution_time_ms: start.elapsed().as_millis(), + truncated, + session_id: None, + has_more: false, + elasticsearch_raw_body: None, + }) + } else { + let affected = con.execute(sql, []).map_err(|e| e.to_string())?; + Ok(db::QueryResult { + columns: vec![], + column_types: Vec::new(), + column_sortables: vec![], + rows: vec![], + affected_rows: affected as u64, + execution_time_ms: start.elapsed().as_millis(), + truncated: false, + session_id: None, + has_more: false, + elasticsearch_raw_body: None, + }) + } +} +pub(crate) fn duckdb_execute_for_database( + con: &duckdb::Connection, + attached_names: &[String], + database: Option<&str>, + sql: &str, + max_rows: Option, +) -> Result { + if let Some(database) = database.map(str::trim).filter(|database| !database.is_empty()) { + let catalog = if database == "main" { + crate::schema::duckdb_primary_catalog(con, attached_names)? + } else { + database.to_string() + }; + con.execute_batch(&format!("USE {}", duckdb_quote_ident(&catalog))).map_err(|e| e.to_string())?; + } + duckdb_execute_with_max_rows(con, sql, max_rows) +} + +fn duckdb_quote_ident(value: &str) -> String { + format!("\"{}\"", value.replace('"', "\"\"")) +} diff --git a/crates/dbx-core/src/db/duckdb_worker_runtime.rs b/agents/drivers/duckdb/src/runtime.rs similarity index 89% rename from crates/dbx-core/src/db/duckdb_worker_runtime.rs rename to agents/drivers/duckdb/src/runtime.rs index 84bdbc3510..e6d426ab52 100644 --- a/crates/dbx-core/src/db/duckdb_worker_runtime.rs +++ b/agents/drivers/duckdb/src/runtime.rs @@ -1,28 +1,24 @@ -#![cfg(feature = "duckdb-bundled")] - use std::sync::{Arc, Mutex}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Stdout}; -use crate::db; -use crate::db::duckdb_worker_protocol::{ - DuckDbWorkerColumnParams, DuckDbWorkerConnectParams, DuckDbWorkerDatabaseParams, DuckDbWorkerError, - DuckDbWorkerExecuteParams, DuckDbWorkerMethod, DuckDbWorkerObjectSourceParams, DuckDbWorkerRequest, - DuckDbWorkerResponse, DuckDbWorkerTableParams, +use crate::wire as db; +use crate::wire::{ + AttachedDatabaseConfig, DuckDbWorkerColumnParams, DuckDbWorkerConnectParams, DuckDbWorkerDatabaseParams, + DuckDbWorkerError, DuckDbWorkerExecuteParams, DuckDbWorkerMethod, DuckDbWorkerObjectSourceParams, + DuckDbWorkerRequest, DuckDbWorkerResponse, DuckDbWorkerTableParams, }; -use crate::models::connection::AttachedDatabaseConfig; -use crate::path_utils::expand_tilde; #[derive(Default)] pub struct DuckDbWorkerSession { - connection: Option>, + connection: Option>, attached_names: Vec, } impl DuckDbWorkerSession { pub fn connect(&mut self, params: DuckDbWorkerConnectParams) -> Result<(), String> { let path = expand_tilde(¶ms.path); - let connection = db::duckdb_driver::connect_path(&path)?; + let connection = crate::connection::connect_path(&path)?; let mut attached_names = Vec::new(); { let locked = connection.lock().map_err(|e| e.to_string())?; @@ -36,7 +32,7 @@ impl DuckDbWorkerSession { // instead of parsing the SQL, so ATTACH without an alias, // dynamic statements, and extension-specific syntax all count. let before = duckdb_catalog_list(&locked)?; - db::duckdb_driver::run_init_script(&locked, script)?; + crate::connection::run_init_script(&locked, script)?; for name in duckdb_catalog_list(&locked)? { if !before.iter().any(|existing| existing.eq_ignore_ascii_case(&name)) && !attached_names.iter().any(|attached| attached.eq_ignore_ascii_case(&name)) @@ -61,7 +57,7 @@ impl DuckDbWorkerSession { ¶ms.sql, params.max_rows, )?; - if let Some(name) = crate::db::duckdb_sql::attached_name_from_attach_sql(¶ms.sql) { + if let Some(name) = crate::sql::attached_name_from_attach_sql(¶ms.sql) { if !self.attached_names.iter().any(|attached| attached.eq_ignore_ascii_case(&name)) { self.attached_names.push(name); } @@ -104,6 +100,27 @@ impl DuckDbWorkerSession { ) } + pub fn get_table_ddl(&self, params: DuckDbWorkerColumnParams) -> Result { + let connection = self.connection.as_ref().ok_or("DuckDB worker is not connected")?.clone(); + let locked = connection.lock().map_err(|e| e.to_string())?; + crate::schema::duckdb_table_ddl_with_attached( + &locked, + ¶ms.database, + ¶ms.schema, + ¶ms.table, + &self.attached_names, + ) + } + + pub fn completion_assistant( + &self, + request: db::CompletionAssistantRequest, + ) -> Result { + let connection = self.connection.as_ref().ok_or("DuckDB worker is not connected")?.clone(); + let locked = connection.lock().map_err(|e| e.to_string())?; + crate::schema::duckdb_completion_assistant_search(&locked, &request, &self.attached_names) + } + pub fn get_object_source(&self, params: DuckDbWorkerObjectSourceParams) -> Result { let connection = self.connection.as_ref().ok_or("DuckDB worker is not connected")?.clone(); let locked = connection.lock().map_err(|e| e.to_string())?; @@ -153,6 +170,23 @@ impl DuckDbWorkerSession { } } +fn expand_tilde(path: &str) -> String { + if !path.starts_with('~') { + return path.to_string(); + } + if path.len() == 1 { + return home_dir().unwrap_or_else(|| path.to_string()); + } + if path.as_bytes().get(1) == Some(&b'/') { + return home_dir().map(|home| home + &path[1..]).unwrap_or_else(|| path.to_string()); + } + path.to_string() +} + +fn home_dir() -> Option { + std::env::var("HOME").or_else(|_| std::env::var("USERPROFILE")).ok() +} + fn duckdb_catalog_list(con: &duckdb::Connection) -> Result, String> { let mut stmt = con.prepare("SHOW DATABASES").map_err(|e| e.to_string())?; let rows = stmt.query_map([], |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; @@ -196,6 +230,14 @@ impl DuckDbWorkerRuntime { let params = request.parse_params()?; Ok(DuckDbWorkerResponse::ok(request.id, session.list_columns(params)?)) }), + DuckDbWorkerMethod::GetTableDdl => self.handle_session_request(request, |session, request| { + let params = request.parse_params()?; + Ok(DuckDbWorkerResponse::ok(request.id, session.get_table_ddl(params)?)) + }), + DuckDbWorkerMethod::CompletionAssistant => self.handle_session_request(request, |session, request| { + let params = request.parse_params()?; + Ok(DuckDbWorkerResponse::ok(request.id, session.completion_assistant(params)?)) + }), DuckDbWorkerMethod::GetObjectSource => self.handle_session_request(request, |session, request| { let params = request.parse_params()?; Ok(DuckDbWorkerResponse::ok(request.id, session.get_object_source(params)?)) @@ -422,7 +464,10 @@ mod tests { .expect("connect"); session .execute(DuckDbWorkerExecuteParams { - sql: "CREATE TABLE users (id INTEGER PRIMARY KEY, name VARCHAR)".to_string(), + sql: "CREATE TABLE users (id INTEGER PRIMARY KEY, name VARCHAR); \ + CREATE SCHEMA analytics; \ + CREATE TABLE analytics.items (id INTEGER)" + .to_string(), database: None, max_rows: None, }) @@ -440,10 +485,18 @@ mod tests { table: "users".to_string(), }) .expect("list columns"); + let analytics_columns = session + .list_columns(DuckDbWorkerColumnParams { + database: "main".to_string(), + schema: "analytics".to_string(), + table: "items".to_string(), + }) + .expect("list analytics columns"); assert!(schemas.iter().any(|schema| schema == "main")); assert!(tables.iter().any(|table| table.name == "users")); assert!(columns.iter().any(|column| column.name == "id" && column.is_primary_key)); + assert_eq!(analytics_columns.iter().map(|column| column.name.as_str()).collect::>(), vec!["id"]); } #[test] diff --git a/agents/drivers/duckdb/src/schema.rs b/agents/drivers/duckdb/src/schema.rs new file mode 100644 index 0000000000..096c3121aa --- /dev/null +++ b/agents/drivers/duckdb/src/schema.rs @@ -0,0 +1,792 @@ +use std::collections::HashMap; + +use crate::wire as db; + +#[cfg(test)] +pub fn duckdb_query_tables_in_database( + con: &duckdb::Connection, + database: &str, + schema: &str, +) -> Result, String> { + duckdb_query_tables_in_database_with_attached(con, database, schema, &[]) +} + +pub fn duckdb_query_tables_in_database_with_attached( + con: &duckdb::Connection, + database: &str, + schema: &str, + attached_names: &[String], +) -> Result, String> { + let database = duckdb_catalog_name(con, database, attached_names)?; + let mut stmt = con.prepare( + "SELECT table_name, table_type FROM information_schema.tables WHERE table_catalog = ? AND table_schema = ? ORDER BY table_name" + ).map_err(|e| e.to_string())?; + let rows = stmt + .query_map((database.as_str(), schema), |row| { + Ok(db::TableInfo { + name: row.get::<_, String>(0)?, + table_type: row.get::<_, String>(1)?, + comment: None, + parent_schema: None, + parent_name: None, + }) + }) + .map_err(|e| e.to_string())?; + let tables: Vec = rows.filter_map(|r| r.ok()).collect(); + if tables.is_empty() && duckdb_is_quack_catalog(con, &database) { + if let Some(remote) = duckdb_quack_remote_tables(con, &database, schema) { + return Ok(remote); + } + } + Ok(tables) +} + +pub fn duckdb_attach_database(con: &duckdb::Connection, name: &str, path: &str) -> Result<(), String> { + let name = name.trim(); + let path = path.trim(); + if name.is_empty() || path.is_empty() { + return Err("DuckDB attached database name and path are required".to_string()); + } + let sql = format!("ATTACH {} AS {}", duckdb_quote_string(path), duckdb_quote_ident(name)); + con.execute_batch(&sql).map_err(|e| format!("Failed to attach database \"{name}\": {e}")) +} + +#[cfg(test)] +pub fn duckdb_list_databases(con: &duckdb::Connection) -> Result, String> { + duckdb_list_databases_with_attached(con, &[]) +} + +pub fn duckdb_list_databases_with_attached( + con: &duckdb::Connection, + attached_names: &[String], +) -> Result, String> { + let primary = duckdb_primary_catalog(con, attached_names)?; + let mut stmt = con.prepare("SHOW DATABASES").map_err(|e| e.to_string())?; + let rows = stmt + .query_map([], |row| { + let name = row.get::<_, String>(0)?; + Ok(db::DatabaseInfo { name: if name == primary { "main".to_string() } else { name } }) + }) + .map_err(|e| e.to_string())?; + Ok(rows.filter_map(|row| row.ok()).collect()) +} + +pub fn duckdb_list_schemas_with_attached( + con: &duckdb::Connection, + database: &str, + attached_names: &[String], +) -> Result, String> { + let database = duckdb_catalog_name(con, database, attached_names)?; + let mut stmt = con + .prepare( + "SELECT schema_name FROM information_schema.schemata WHERE catalog_name = ? AND schema_name NOT IN ('information_schema', 'pg_catalog') ORDER BY schema_name", + ) + .map_err(|e| e.to_string())?; + let rows = stmt.query_map([database.as_str()], |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; + let schemas: Vec = rows.filter_map(|r| r.ok()).collect(); + if schemas.is_empty() && duckdb_is_quack_catalog(con, &database) { + if let Some(remote) = duckdb_quack_remote_schemas(con, &database) { + return Ok(remote); + } + } + Ok(schemas) +} + +pub fn duckdb_object_source_with_attached( + con: &duckdb::Connection, + database: &str, + schema: &str, + name: &str, + object_type: &db::ObjectSourceKind, + attached_names: &[String], +) -> Result { + if !matches!(object_type, db::ObjectSourceKind::View) { + return Err("DuckDB object source only supports views".to_string()); + } + + let database = duckdb_catalog_name(con, database, attached_names)?; + let mut stmt = con + .prepare("SELECT sql FROM duckdb_views() WHERE database_name = ? AND schema_name = ? AND view_name = ?") + .map_err(|e| e.to_string())?; + let mut rows = + stmt.query_map((database.as_str(), schema, name), |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; + + match rows.next() { + Some(row) => row.map_err(|e| e.to_string()), + None => Err(format!("DuckDB view source not found: {database}.{schema}.{name}")), + } +} + +pub fn duckdb_table_ddl_with_attached( + con: &duckdb::Connection, + database: &str, + schema: &str, + table: &str, + attached_names: &[String], +) -> Result { + let database = duckdb_catalog_name(con, database, attached_names)?; + let mut stmt = con + .prepare("SELECT sql FROM duckdb_tables() WHERE database_name = ? AND schema_name = ? AND table_name = ?") + .map_err(|e| e.to_string())?; + let mut rows = + stmt.query_map((database.as_str(), schema, table), |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; + + match rows.next() { + Some(row) => row.map_err(|e| e.to_string()), + None => Err(format!("DuckDB table not found: {database}.{schema}.{table}")), + } +} + +/// Identifies quack catalogs by the storage-extension `type` that +/// `duckdb_databases()` reports, rather than by the attach aliases parsed from +/// the init script: `ATTACH 'quack:host:port'` without an `AS` alias gets a +/// derived catalog name (e.g. `localhost:9494`) that no parser sees. +fn duckdb_is_quack_catalog(con: &duckdb::Connection, database: &str) -> bool { + con.query_row("SELECT type FROM duckdb_databases() WHERE lower(database_name) = lower(?)", [database], |row| { + row.get::<_, String>(0) + }) + .map(|catalog_type| catalog_type.eq_ignore_ascii_case("quack")) + .unwrap_or(false) +} + +/// Quack-attached catalogs expose no metadata on the client side (the beta +/// extension implements name resolution and streaming scans, but not catalog +/// enumeration: `information_schema`, `duckdb_tables()` and `SHOW ALL TABLES` +/// all skip the remote catalog). The extension's `quack_query_by_name` table +/// function runs SQL on the remote server, so when a metadata query for an +/// attached catalog comes back empty we ask the remote for its own +/// information_schema. For non-quack catalogs (or when the extension is not +/// loaded) the function call errors and the fallback quietly yields `None`. +fn duckdb_quack_remote_query( + con: &duckdb::Connection, + alias: &str, + remote_sql: &str, + map_row: impl Fn(&duckdb::Row<'_>) -> Result, +) -> Option> { + let sql = format!( + "SELECT * FROM quack_query_by_name({}, {})", + duckdb_quote_string(alias), + duckdb_quote_string(remote_sql) + ); + let mut stmt = match con.prepare(&sql) { + Ok(stmt) => stmt, + Err(e) => { + log::debug!("quack metadata fallback unavailable for catalog {alias}: {e}"); + return None; + } + }; + let rows = match stmt.query_map([], |row| map_row(row)) { + Ok(rows) => rows, + Err(e) => { + log::debug!("quack metadata fallback query failed for catalog {alias}: {e}"); + return None; + } + }; + Some(rows.filter_map(|r| r.ok()).collect()) +} + +fn duckdb_quack_remote_schemas(con: &duckdb::Connection, alias: &str) -> Option> { + duckdb_quack_remote_query( + con, + alias, + "SELECT DISTINCT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema', 'pg_catalog') ORDER BY schema_name", + |row| row.get::<_, String>(0), + ) +} + +fn duckdb_quack_remote_tables(con: &duckdb::Connection, alias: &str, schema: &str) -> Option> { + let remote_sql = format!( + "SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = {} ORDER BY table_name", + duckdb_quote_string(schema) + ); + duckdb_quack_remote_query(con, alias, &remote_sql, |row| { + Ok(db::TableInfo { + name: row.get::<_, String>(0)?, + table_type: row.get::<_, String>(1)?, + comment: None, + parent_schema: None, + parent_name: None, + }) + }) +} + +fn duckdb_quack_remote_columns( + con: &duckdb::Connection, + alias: &str, + schema: &str, + table: &str, +) -> Option> { + let pk_sql = format!( + "SELECT kcu.column_name + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + AND tc.table_name = kcu.table_name + WHERE tc.constraint_type = 'PRIMARY KEY' + AND tc.table_schema = {schema} + AND tc.table_name = {table} + ORDER BY kcu.ordinal_position", + schema = duckdb_quote_string(schema), + table = duckdb_quote_string(table), + ); + let primary_keys: std::collections::HashSet = + duckdb_quack_remote_query(con, alias, &pk_sql, |row| row.get::<_, String>(0)) + .map(|names| names.into_iter().collect()) + .unwrap_or_default(); + + let columns_sql = format!( + "SELECT column_name, data_type, is_nullable, column_default + FROM information_schema.columns + WHERE table_schema = {schema} AND table_name = {table} + ORDER BY ordinal_position", + schema = duckdb_quote_string(schema), + table = duckdb_quote_string(table), + ); + duckdb_quack_remote_query(con, alias, &columns_sql, |row| { + let name = row.get::<_, String>(0)?; + Ok(db::ColumnInfo { + is_primary_key: primary_keys.contains(&name), + name, + data_type: row.get::<_, String>(1)?, + is_nullable: row.get::<_, String>(2).unwrap_or_default() == "YES", + column_default: row.get::<_, Option>(3)?, + extra: None, + comment: None, + numeric_precision: None, + numeric_scale: None, + character_maximum_length: None, + enum_values: None, + ..Default::default() + }) + }) +} + +fn duckdb_catalog_name(con: &duckdb::Connection, database: &str, attached_names: &[String]) -> Result { + if database.trim().is_empty() || database == "main" { + return duckdb_primary_catalog(con, attached_names); + } + Ok(database.to_string()) +} + +pub fn duckdb_primary_catalog(con: &duckdb::Connection, attached_names: &[String]) -> Result { + if attached_names.is_empty() { + return duckdb_current_database(con); + } + let attached: std::collections::HashSet = attached_names.iter().map(|name| name.to_lowercase()).collect(); + let mut stmt = con.prepare("SHOW DATABASES").map_err(|e| e.to_string())?; + let rows = stmt.query_map([], |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; + for row in rows { + let name = row.map_err(|e| e.to_string())?; + if !attached.contains(&name.to_lowercase()) { + return Ok(name); + } + } + duckdb_current_database(con) +} + +fn duckdb_current_database(con: &duckdb::Connection) -> Result { + con.query_row("SELECT current_database()", [], |row| row.get::<_, String>(0)).map_err(|e| e.to_string()) +} + +fn duckdb_quote_ident(value: &str) -> String { + format!("\"{}\"", value.replace('"', "\"\"")) +} + +fn duckdb_quote_string(value: &str) -> String { + format!("'{}'", value.replace('\'', "''")) +} + +#[cfg(test)] +pub fn duckdb_query_columns(con: &duckdb::Connection, table: &str) -> Result, String> { + duckdb_query_columns_in_database(con, "main", "main", table) +} + +#[cfg(test)] +pub fn duckdb_query_columns_in_database( + con: &duckdb::Connection, + database: &str, + schema: &str, + table: &str, +) -> Result, String> { + duckdb_query_columns_in_database_with_attached(con, database, schema, table, &[]) +} + +pub fn duckdb_query_columns_in_database_with_attached( + con: &duckdb::Connection, + database: &str, + schema: &str, + table: &str, + attached_names: &[String], +) -> Result, String> { + let database = duckdb_catalog_name(con, database, attached_names)?; + let mut pk_stmt = con + .prepare( + "SELECT kcu.column_name + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + AND tc.table_name = kcu.table_name + WHERE tc.constraint_type = 'PRIMARY KEY' + AND tc.table_catalog = ? + AND tc.table_schema = ? + AND tc.table_name = ? + ORDER BY kcu.ordinal_position", + ) + .map_err(|e| e.to_string())?; + let pk_rows = pk_stmt + .query_map((database.as_str(), schema, table), |row| row.get::<_, String>(0)) + .map_err(|e| e.to_string())?; + let primary_keys: std::collections::HashSet = pk_rows.filter_map(|r| r.ok()).collect(); + let column_comments = duckdb_column_comments(con, &database, schema, table); + + let mut stmt = con + .prepare( + "SELECT column_name, data_type, is_nullable, column_default + FROM information_schema.columns + WHERE table_catalog = ? AND table_schema = ? AND table_name = ? + ORDER BY ordinal_position", + ) + .map_err(|e| e.to_string())?; + let rows = stmt + .query_map((database.as_str(), schema, table), |row| { + let name = row.get::<_, String>(0)?; + let comment = column_comments.get(&name).cloned().flatten(); + Ok(db::ColumnInfo { + is_primary_key: primary_keys.contains(&name), + name, + data_type: row.get::<_, String>(1)?, + is_nullable: row.get::<_, String>(2).unwrap_or_default() == "YES", + column_default: row.get::<_, Option>(3)?, + extra: None, + comment, + numeric_precision: None, + numeric_scale: None, + character_maximum_length: None, + enum_values: None, + ..Default::default() + }) + }) + .map_err(|e| e.to_string())?; + let columns: Vec = rows.filter_map(|r| r.ok()).collect(); + if columns.is_empty() && duckdb_is_quack_catalog(con, &database) { + if let Some(remote) = duckdb_quack_remote_columns(con, &database, schema, table) { + return Ok(remote); + } + } + Ok(columns) +} + +fn duckdb_column_comments( + con: &duckdb::Connection, + database: &str, + schema: &str, + table: &str, +) -> HashMap> { + let Ok(mut stmt) = con.prepare( + "SELECT column_name, comment FROM duckdb_columns() \ + WHERE database_name = ? AND schema_name = ? AND table_name = ?", + ) else { + // Older DuckDB versions may not expose the comment column; keep metadata browsing functional. + return HashMap::new(); + }; + let Ok(rows) = stmt + .query_map((database, schema, table), |row| Ok((row.get::<_, String>(0)?, row.get::<_, Option>(1)?))) + else { + return HashMap::new(); + }; + rows.filter_map(Result::ok).collect() +} + +pub fn duckdb_completion_assistant_search( + con: &duckdb::Connection, + request: &db::CompletionAssistantRequest, + attached_names: &[String], +) -> Result { + let limit = request.max_results.unwrap_or(100).clamp(1, 1000); + let kinds = if request.object_kinds.is_empty() { + vec![db::CompletionAssistantObjectKind::Table, db::CompletionAssistantObjectKind::View] + } else { + request.object_kinds.clone() + }; + let mut candidates = Vec::new(); + + if kinds.iter().any(|kind| matches!(kind, db::CompletionAssistantObjectKind::Schema)) { + candidates.extend(duckdb_completion_schemas(con, request, attached_names, limit)?); + if candidates.len() >= limit { + return Ok(db::CompletionAssistantResponse { candidates, incomplete: true, fallback_used: false }); + } + } + + if kinds.iter().any(db::CompletionAssistantObjectKind::is_table_like) { + candidates.extend(duckdb_completion_tables(con, request, &kinds, attached_names, limit - candidates.len())?); + if candidates.len() >= limit { + return Ok(db::CompletionAssistantResponse { candidates, incomplete: true, fallback_used: false }); + } + } + + if kinds.iter().any(|kind| matches!(kind, db::CompletionAssistantObjectKind::Column)) { + candidates.extend(duckdb_completion_columns(con, request, attached_names, limit - candidates.len())?); + if candidates.len() >= limit { + return Ok(db::CompletionAssistantResponse { candidates, incomplete: true, fallback_used: false }); + } + } + + Ok(db::CompletionAssistantResponse { candidates, incomplete: false, fallback_used: false }) +} + +fn duckdb_completion_schemas( + con: &duckdb::Connection, + request: &db::CompletionAssistantRequest, + attached_names: &[String], + limit: usize, +) -> Result, String> { + if limit == 0 { + return Ok(Vec::new()); + } + let database = duckdb_catalog_name(con, &request.database, attached_names)?; + let pattern = duckdb_completion_like_pattern(request); + let mut stmt = con + .prepare( + "SELECT schema_name + FROM information_schema.schemata + WHERE catalog_name = ? + AND schema_name NOT IN ('information_schema', 'pg_catalog') + AND lower(schema_name) LIKE lower(?) ESCAPE '\\' + ORDER BY schema_name + LIMIT ?", + ) + .map_err(|e| e.to_string())?; + let rows = stmt + .query_map((database.as_str(), pattern.as_str(), limit as i64), |row| { + let schema = row.get::<_, String>(0)?; + Ok(db::CompletionAssistantCandidate { + name: schema.clone(), + kind: db::CompletionAssistantCandidateKind::Schema, + database: Some(request.database.clone()), + schema: Some(schema), + parent_schema: None, + parent_name: None, + comment: None, + data_type: None, + signature: None, + }) + }) + .map_err(|e| e.to_string())?; + Ok(rows.filter_map(|row| row.ok()).collect()) +} + +fn duckdb_completion_tables( + con: &duckdb::Connection, + request: &db::CompletionAssistantRequest, + kinds: &[db::CompletionAssistantObjectKind], + attached_names: &[String], + limit: usize, +) -> Result, String> { + if limit == 0 { + return Ok(Vec::new()); + } + let database = duckdb_catalog_name(con, &request.database, attached_names)?; + let schema = request.parent_schema.as_deref().or(request.schema.as_deref()).unwrap_or("main"); + let include_tables = kinds.iter().any(|kind| matches!(kind, db::CompletionAssistantObjectKind::Table)); + let include_views = kinds.iter().any(|kind| matches!(kind, db::CompletionAssistantObjectKind::View)); + let pattern = duckdb_completion_like_pattern(request); + let mut stmt = con + .prepare( + "SELECT table_name, table_type + FROM information_schema.tables + WHERE table_catalog = ? + AND table_schema = ? + AND ((? AND table_type = 'BASE TABLE') OR (? AND table_type = 'VIEW')) + AND lower(table_name) LIKE lower(?) ESCAPE '\\' + ORDER BY table_name + LIMIT ?", + ) + .map_err(|e| e.to_string())?; + let rows = stmt + .query_map((database.as_str(), schema, include_tables, include_views, pattern.as_str(), limit as i64), |row| { + let table_type = row.get::<_, String>(1)?; + Ok(db::CompletionAssistantCandidate { + name: row.get(0)?, + kind: if table_type.eq_ignore_ascii_case("VIEW") { + db::CompletionAssistantCandidateKind::View + } else { + db::CompletionAssistantCandidateKind::Table + }, + database: Some(request.database.clone()), + schema: Some(schema.to_string()), + parent_schema: None, + parent_name: None, + comment: None, + data_type: None, + signature: None, + }) + }) + .map_err(|e| e.to_string())?; + Ok(rows.filter_map(|row| row.ok()).collect()) +} + +fn duckdb_completion_columns( + con: &duckdb::Connection, + request: &db::CompletionAssistantRequest, + attached_names: &[String], + limit: usize, +) -> Result, String> { + if limit == 0 { + return Ok(Vec::new()); + } + let Some(table) = request.parent_name.as_deref().filter(|table| !table.trim().is_empty()) else { + return Ok(Vec::new()); + }; + let database = duckdb_catalog_name(con, &request.database, attached_names)?; + let schema = request.parent_schema.as_deref().or(request.schema.as_deref()).unwrap_or("main"); + let pattern = duckdb_completion_like_pattern(request); + let mut stmt = con + .prepare( + "SELECT column_name, data_type + FROM information_schema.columns + WHERE table_catalog = ? + AND table_schema = ? + AND table_name = ? + AND lower(column_name) LIKE lower(?) ESCAPE '\\' + ORDER BY ordinal_position + LIMIT ?", + ) + .map_err(|e| e.to_string())?; + let rows = stmt + .query_map((database.as_str(), schema, table, pattern.as_str(), limit as i64), |row| { + Ok(db::CompletionAssistantCandidate { + name: row.get(0)?, + kind: db::CompletionAssistantCandidateKind::Column, + database: Some(request.database.clone()), + schema: Some(schema.to_string()), + parent_schema: Some(schema.to_string()), + parent_name: Some(table.to_string()), + comment: None, + data_type: Some(row.get(1)?), + signature: None, + }) + }) + .map_err(|e| e.to_string())?; + Ok(rows.filter_map(|row| row.ok()).collect()) +} + +fn duckdb_completion_like_pattern(request: &db::CompletionAssistantRequest) -> String { + let mask = request.mask.trim().trim_matches('%'); + let escaped = mask.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_"); + match request.match_mode.as_ref().unwrap_or(&db::CompletionAssistantMatchMode::Prefix) { + db::CompletionAssistantMatchMode::Prefix => format!("{escaped}%"), + db::CompletionAssistantMatchMode::Contains => format!("%{escaped}%"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use dbx_core::models::connection::DatabaseType; + #[test] + fn duckdb_list_databases_includes_attached_database() { + let unique = uuid::Uuid::new_v4(); + let path = std::env::temp_dir().join(format!("dbx-attached-{unique}.duckdb")); + let _ = std::fs::remove_file(&path); + let con = duckdb::Connection::open_in_memory().unwrap(); + + duckdb_attach_database(&con, "analytics", path.to_str().unwrap()).unwrap(); + let databases = duckdb_list_databases(&con).unwrap(); + + assert!(databases.iter().any(|database| database.name == "main")); + assert!(databases.iter().any(|database| database.name == "analytics")); + + let _ = std::fs::remove_file(path); + } + + #[test] + fn duckdb_query_tables_filters_by_attached_database() { + let unique = uuid::Uuid::new_v4(); + let path = std::env::temp_dir().join(format!("dbx-attached-tables-{unique}.duckdb")); + let _ = std::fs::remove_file(&path); + let con = duckdb::Connection::open_in_memory().unwrap(); + + con.execute_batch("CREATE TABLE main_table(id INTEGER);").unwrap(); + duckdb_attach_database(&con, "analytics", path.to_str().unwrap()).unwrap(); + con.execute_batch("CREATE TABLE analytics.attached_table(id INTEGER);").unwrap(); + + let main_tables = duckdb_query_tables_in_database(&con, "main", "main").unwrap(); + let attached_tables = duckdb_query_tables_in_database(&con, "analytics", "main").unwrap(); + + assert!(main_tables.iter().any(|table| table.name == "main_table")); + assert!(!main_tables.iter().any(|table| table.name == "attached_table")); + assert!(attached_tables.iter().any(|table| table.name == "attached_table")); + assert!(!attached_tables.iter().any(|table| table.name == "main_table")); + + let _ = std::fs::remove_file(path); + } + + #[test] + fn duckdb_query_columns_includes_column_comments() { + let con = duckdb::Connection::open_in_memory().unwrap(); + con.execute_batch( + "CREATE TABLE users (id INTEGER, name VARCHAR); \ + COMMENT ON COLUMN users.name IS 'Display name';", + ) + .unwrap(); + + let columns = duckdb_query_columns(&con, "users").unwrap(); + let name = columns.iter().find(|column| column.name == "name").unwrap(); + + assert_eq!(name.comment.as_deref(), Some("Display name")); + } + + #[test] + fn duckdb_table_ddl_filters_by_database_schema_and_table_name() { + let unique = uuid::Uuid::new_v4(); + let path = std::env::temp_dir().join(format!("dbx-attached-ddl-{unique}.duckdb")); + let _ = std::fs::remove_file(&path); + let con = duckdb::Connection::open_in_memory().unwrap(); + + con.execute_batch( + "CREATE SCHEMA reporting; \ + CREATE TABLE main.orders(main_id INTEGER); \ + CREATE TABLE reporting.orders(reporting_id INTEGER);", + ) + .unwrap(); + duckdb_attach_database(&con, "analytics", path.to_str().unwrap()).unwrap(); + con.execute_batch("CREATE TABLE analytics.orders(attached_id INTEGER);").unwrap(); + + let main_ddl = + duckdb_table_ddl_with_attached(&con, "main", "main", "orders", &["analytics".to_string()]).unwrap(); + let reporting_ddl = + duckdb_table_ddl_with_attached(&con, "main", "reporting", "orders", &["analytics".to_string()]).unwrap(); + let attached_ddl = + duckdb_table_ddl_with_attached(&con, "analytics", "main", "orders", &["analytics".to_string()]).unwrap(); + + assert!(main_ddl.contains("main_id")); + assert!(reporting_ddl.contains("reporting_id")); + assert!(attached_ddl.contains("attached_id")); + + let _ = std::fs::remove_file(path); + } + + #[test] + fn duckdb_completion_assistant_searches_catalog_metadata_with_limit() { + let con = duckdb::Connection::open_in_memory().unwrap(); + con.execute_batch( + "CREATE TABLE account(id INTEGER, display_name VARCHAR); \ + CREATE VIEW account_view AS SELECT id FROM account;", + ) + .unwrap(); + + let request = db::CompletionAssistantRequest { + connection_id: "c1".to_string(), + database: "main".to_string(), + schema: Some("main".to_string()), + object_kinds: vec![db::CompletionAssistantObjectKind::Table, db::CompletionAssistantObjectKind::View], + mask: "account".to_string(), + case_sensitive: false, + global_search: false, + max_results: Some(1), + search_in_comments: false, + search_in_definitions: false, + parent_schema: Some("main".to_string()), + parent_name: None, + match_mode: Some(db::CompletionAssistantMatchMode::Prefix), + }; + + let tables = duckdb_completion_assistant_search(&con, &request, &[]).unwrap(); + assert_eq!(tables.candidates.len(), 1); + assert!(tables.incomplete); + assert!(!tables.fallback_used); + assert_eq!(tables.candidates[0].name, "account"); + + let columns = duckdb_completion_assistant_search( + &con, + &db::CompletionAssistantRequest { + object_kinds: vec![db::CompletionAssistantObjectKind::Column], + mask: "name".to_string(), + max_results: Some(10), + parent_name: Some("account".to_string()), + match_mode: Some(db::CompletionAssistantMatchMode::Contains), + ..request + }, + &[], + ) + .unwrap(); + assert_eq!(columns.candidates.len(), 1); + assert_eq!(columns.candidates[0].name, "display_name"); + } + + #[test] + fn duckdb_object_source_filters_by_schema_and_view_name() { + let con = duckdb::Connection::open_in_memory().unwrap(); + con.execute_batch( + "CREATE SCHEMA reporting; \ + CREATE VIEW main.active_orders AS SELECT 'main' AS origin; \ + CREATE VIEW reporting.active_orders AS SELECT 'reporting' AS origin;", + ) + .unwrap(); + + let main_source = + duckdb_object_source_with_attached(&con, "main", "main", "active_orders", &db::ObjectSourceKind::View, &[]) + .unwrap(); + let reporting_source = duckdb_object_source_with_attached( + &con, + "main", + "reporting", + "active_orders", + &db::ObjectSourceKind::View, + &[], + ) + .unwrap(); + + assert!(main_source.contains("'main'")); + assert!(reporting_source.contains("'reporting'")); + } + + #[test] + fn duckdb_object_source_rejects_unsupported_or_missing_objects() { + let con = duckdb::Connection::open_in_memory().unwrap(); + + let unsupported = duckdb_object_source_with_attached( + &con, + "main", + "main", + "calculate_total", + &db::ObjectSourceKind::Function, + &[], + ) + .unwrap_err(); + let missing = + duckdb_object_source_with_attached(&con, "main", "main", "missing_view", &db::ObjectSourceKind::View, &[]) + .unwrap_err(); + + assert_eq!(unsupported, "DuckDB object source only supports views"); + assert!(missing.contains("DuckDB view source not found")); + } + + #[test] + fn duckdb_catalog_view_source_can_replace_existing_view() { + let con = duckdb::Connection::open_in_memory().unwrap(); + con.execute_batch("CREATE VIEW active_orders AS SELECT 1 AS value;").unwrap(); + let source = + duckdb_object_source_with_attached(&con, "main", "main", "active_orders", &db::ObjectSourceKind::View, &[]) + .unwrap(); + let edited_source = source.replace("SELECT 1", "SELECT 2"); + assert_ne!(edited_source, source); + + let statements = dbx_core::object_source_sql::build_executable_object_source_statements( + dbx_core::object_source_sql::EditableObjectSourceSqlInput { + database_type: DatabaseType::DuckDb, + object_type: dbx_core::db::ObjectSourceKind::View, + schema: Some("main".to_string()), + name: "active_orders".to_string(), + source: edited_source, + }, + ) + .unwrap(); + con.execute_batch(&statements.join("\n")).unwrap(); + + let value = con.query_row("SELECT value FROM active_orders", [], |row| row.get::<_, i32>(0)).unwrap(); + assert_eq!(value, 2); + } +} diff --git a/agents/drivers/duckdb/src/sql.rs b/agents/drivers/duckdb/src/sql.rs new file mode 100644 index 0000000000..2354ad7b70 --- /dev/null +++ b/agents/drivers/duckdb/src/sql.rs @@ -0,0 +1,274 @@ +pub fn split_sql_statements(script: &str) -> Vec { + let bytes = script.as_bytes(); + let mut statements = Vec::new(); + let mut start = 0; + let mut index = 0; + let mut in_single = false; + let mut in_double = false; + let mut in_escape_single = false; + while index < bytes.len() { + match bytes[index] { + b'\\' if in_single && in_escape_single => { + index = (index + 2).min(bytes.len()); + continue; + } + b'\'' if !in_double => { + if in_single && index + 1 < bytes.len() && bytes[index + 1] == b'\'' { + index += 2; + continue; + } + if !in_single { + in_escape_single = is_escape_string_quote(bytes, index); + } + in_single = !in_single; + if !in_single { + in_escape_single = false; + } + } + b'"' if !in_single => { + if in_double && index + 1 < bytes.len() && bytes[index + 1] == b'"' { + index += 2; + continue; + } + in_double = !in_double; + } + b'-' if !in_single && !in_double && bytes.get(index + 1) == Some(&b'-') => { + while index < bytes.len() && bytes[index] != b'\n' { + index += 1; + } + continue; + } + b'/' if !in_single && !in_double && bytes.get(index + 1) == Some(&b'*') => { + index += 2; + while index < bytes.len() && !(bytes[index] == b'*' && bytes.get(index + 1) == Some(&b'/')) { + index += 1; + } + index = (index + 2).min(bytes.len()); + continue; + } + b'$' if !in_single && !in_double => { + if let Some(end) = dollar_quote_end(bytes, index) { + index = end; + continue; + } + } + b';' if !in_single && !in_double => { + push_statement(&mut statements, &script[start..index]); + start = index + 1; + } + _ => {} + } + index += 1; + } + push_statement(&mut statements, &script[start..]); + statements +} + +fn is_escape_string_quote(bytes: &[u8], quote_index: usize) -> bool { + if quote_index == 0 || !matches!(bytes[quote_index - 1], b'E' | b'e') { + return false; + } + quote_index == 1 || !(bytes[quote_index - 2].is_ascii_alphanumeric() || bytes[quote_index - 2] == b'_') +} + +fn push_statement(statements: &mut Vec, fragment: &str) { + let trimmed = fragment.trim(); + if !trimmed.is_empty() { + statements.push(trimmed.to_string()); + } +} + +fn dollar_quote_end(bytes: &[u8], start: usize) -> Option { + let mut tag_end = start + 1; + while tag_end < bytes.len() && (bytes[tag_end] == b'_' || bytes[tag_end].is_ascii_alphanumeric()) { + tag_end += 1; + } + if tag_end >= bytes.len() || bytes[tag_end] != b'$' { + return None; + } + if tag_end > start + 1 && bytes[start + 1].is_ascii_digit() { + return None; + } + let tag = &bytes[start..=tag_end]; + let mut index = tag_end + 1; + while index + tag.len() <= bytes.len() { + if &bytes[index..index + tag.len()] == tag { + return Some(index + tag.len()); + } + index += 1; + } + Some(bytes.len()) +} + +pub fn attached_name_from_attach_sql(sql: &str) -> Option { + let trimmed = strip_leading_comments(sql); + let first_word = trimmed.split(|character: char| character.is_whitespace() || character == ';').next()?; + if !first_word.eq_ignore_ascii_case("ATTACH") { + return None; + } + + let as_index = find_as_keyword_outside_quotes(trimmed)?; + parse_identifier_after_as(&trimmed[as_index + 2..]) +} + +pub fn strip_leading_comments(sql: &str) -> &str { + let mut rest = sql.trim_start(); + loop { + if let Some(after) = rest.strip_prefix("--") { + rest = after.split_once('\n').map(|(_, tail)| tail).unwrap_or("").trim_start(); + } else if let Some(after) = rest.strip_prefix("/*") { + rest = after.split_once("*/").map(|(_, tail)| tail).unwrap_or("").trim_start(); + } else { + return rest; + } + } +} + +fn find_as_keyword_outside_quotes(sql: &str) -> Option { + let bytes = sql.as_bytes(); + let mut index = 0; + let mut in_single = false; + let mut in_double = false; + while index < bytes.len() { + match bytes[index] { + b'\'' if !in_double => { + if in_single && index + 1 < bytes.len() && bytes[index + 1] == b'\'' { + index += 2; + continue; + } + in_single = !in_single; + } + b'"' if !in_single => { + if in_double && index + 1 < bytes.len() && bytes[index + 1] == b'"' { + index += 2; + continue; + } + in_double = !in_double; + } + b'a' | b'A' + if !in_single + && !in_double + && index + 1 < bytes.len() + && matches!(bytes[index + 1], b's' | b'S') + && is_sql_word_boundary(bytes.get(index.wrapping_sub(1)).copied()) + && is_sql_word_boundary(bytes.get(index + 2).copied()) => + { + return Some(index); + } + _ => {} + } + index += 1; + } + None +} + +fn is_sql_word_boundary(byte: Option) -> bool { + !matches!(byte, Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')) +} + +fn parse_identifier_after_as(input: &str) -> Option { + let input = input.trim_start(); + if input.is_empty() { + return None; + } + if let Some(rest) = input.strip_prefix('"') { + let mut name = String::new(); + let mut characters = rest.chars().peekable(); + while let Some(character) = characters.next() { + if character == '"' { + if characters.peek() == Some(&'"') { + name.push('"'); + characters.next(); + continue; + } + return (!name.trim().is_empty()).then_some(name); + } + name.push(character); + } + return None; + } + + let name = + input.split(|character: char| character.is_whitespace() || character == ';').next().unwrap_or_default().trim(); + (!name.is_empty()).then(|| name.to_string()) +} + +pub fn starts_with_duckdb_result_sql_keyword(sql: &str) -> bool { + let Some(token) = first_executable_sql_token(sql) else { + return false; + }; + ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN", "WITH", "PRAGMA", "FROM", "SUMMARIZE", "SUMMARISE", "PIVOT", "UNPIVOT"] + .iter() + .any(|keyword| { + token.eq_ignore_ascii_case(keyword) || (*keyword == "DESCRIBE" && token.eq_ignore_ascii_case("DESC")) + }) +} + +fn first_executable_sql_token(sql: &str) -> Option<&str> { + let bytes = sql.as_bytes(); + let mut index = 0; + + while index < bytes.len() { + while index < bytes.len() && (bytes[index].is_ascii_whitespace() || bytes[index] == b'(') { + index += 1; + } + if index >= bytes.len() { + break; + } + if index + 1 < bytes.len() && bytes[index] == b'-' && bytes[index + 1] == b'-' { + index += 2; + while index < bytes.len() && bytes[index] != b'\n' { + index += 1; + } + continue; + } + if index + 1 < bytes.len() && bytes[index] == b'/' && bytes[index + 1] == b'*' { + index += 2; + while index + 1 < bytes.len() && !(bytes[index] == b'*' && bytes[index + 1] == b'/') { + index += 1; + } + index = (index + 2).min(bytes.len()); + continue; + } + break; + } + + let start = index; + while index < bytes.len() && (bytes[index].is_ascii_alphabetic() || bytes[index] == b'_') { + index += 1; + } + (index > start).then_some(&sql[start..index]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn splits_quoted_and_commented_statements() { + let script = "SET s3_endpoint='host;port'; -- comment;\nATTACH 'db;x.duckdb' AS a; SELECT $$a;b$$"; + assert_eq!( + split_sql_statements(script), + vec![ + "SET s3_endpoint='host;port'".to_string(), + "-- comment;\nATTACH 'db;x.duckdb' AS a".to_string(), + "SELECT $$a;b$$".to_string(), + ] + ); + } + + #[test] + fn detects_attach_alias_after_comments() { + assert_eq!( + attached_name_from_attach_sql("-- note\nATTACH 'sales.duckdb' AS \"sales db\";"), + Some("sales db".to_string()) + ); + } + + #[test] + fn detects_result_statements_after_comments() { + assert!(starts_with_duckdb_result_sql_keyword("/* note */ WITH rows AS (SELECT 1) SELECT * FROM rows")); + assert!(starts_with_duckdb_result_sql_keyword("DESC SELECT 1")); + assert!(!starts_with_duckdb_result_sql_keyword("INSERT INTO items VALUES (1)")); + } +} diff --git a/agents/drivers/duckdb/src/wire.rs b/agents/drivers/duckdb/src/wire.rs new file mode 100644 index 0000000000..995f724784 --- /dev/null +++ b/agents/drivers/duckdb/src/wire.rs @@ -0,0 +1,302 @@ +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DatabaseInfo { + pub name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TableInfo { + pub name: String, + pub table_type: String, + pub comment: Option, + pub parent_schema: Option, + pub parent_name: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ObjectSourceKind { + View, + MaterializedView, + Procedure, + Function, + Trigger, + Sequence, + Package, + PackageBody, + Type, + TypeBody, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct ColumnInfo { + pub name: String, + pub data_type: String, + pub is_nullable: bool, + pub column_default: Option, + pub is_primary_key: bool, + pub extra: Option, + pub comment: Option, + pub numeric_precision: Option, + pub numeric_scale: Option, + pub character_maximum_length: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enum_values: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub character_set: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub collation: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum CompletionAssistantObjectKind { + Database, + Schema, + Table, + View, + Routine, + Procedure, + Function, + Column, +} + +impl CompletionAssistantObjectKind { + pub fn is_table_like(&self) -> bool { + matches!(self, Self::Table | Self::View) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum CompletionAssistantCandidateKind { + Database, + Schema, + Table, + View, + Procedure, + Function, + Column, + Object, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum CompletionAssistantMatchMode { + Prefix, + Contains, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CompletionAssistantRequest { + pub connection_id: String, + pub database: String, + pub schema: Option, + #[serde(default)] + pub object_kinds: Vec, + #[serde(default)] + pub mask: String, + #[serde(default)] + pub case_sensitive: bool, + #[serde(default)] + pub global_search: bool, + pub max_results: Option, + #[serde(default)] + pub search_in_comments: bool, + #[serde(default)] + pub search_in_definitions: bool, + pub parent_schema: Option, + pub parent_name: Option, + pub match_mode: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CompletionAssistantCandidate { + pub name: String, + pub kind: CompletionAssistantCandidateKind, + pub database: Option, + pub schema: Option, + pub parent_schema: Option, + pub parent_name: Option, + pub comment: Option, + pub data_type: Option, + pub signature: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CompletionAssistantResponse { + pub candidates: Vec, + pub incomplete: bool, + pub fallback_used: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QueryResult { + pub columns: Vec, + #[serde(default)] + pub column_types: Vec, + #[serde(default)] + pub column_sortables: Vec, + pub rows: Vec>, + pub affected_rows: u64, + pub execution_time_ms: u128, + #[serde(default)] + pub truncated: bool, + #[serde(default)] + pub session_id: Option, + #[serde(default)] + pub has_more: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub elasticsearch_raw_body: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct AttachedDatabaseConfig { + pub name: String, + pub path: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct DuckDbWorkerRequest { + pub id: String, + pub method: DuckDbWorkerMethod, + #[serde(default)] + pub params: serde_json::Value, +} + +impl DuckDbWorkerRequest { + #[cfg(test)] + pub fn new(id: impl Into, method: DuckDbWorkerMethod, params: impl Serialize) -> Result { + let params = serde_json::to_value(params).map_err(|error| error.to_string())?; + Ok(Self { id: id.into(), method, params }) + } + + pub fn parse_params(&self) -> Result { + serde_json::from_value(self.params.clone()).map_err(|error| { + DuckDbWorkerError::new("invalid_params", format!("Invalid params for {:?}: {error}", self.method)) + }) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum DuckDbWorkerMethod { + Connect, + Execute, + ListDatabases, + ListSchemas, + ListTables, + ListColumns, + GetTableDdl, + CompletionAssistant, + GetObjectSource, + AttachDatabase, + Cancel, + Shutdown, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DuckDbWorkerResponse { + pub id: String, + pub ok: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +impl DuckDbWorkerResponse { + pub fn ok(id: impl Into, result: impl Serialize) -> Self { + match serde_json::to_value(result) { + Ok(result) => Self { id: id.into(), ok: true, result: Some(result), error: None }, + Err(error) => Self::err(id, DuckDbWorkerError::new("serialization_failed", error.to_string())), + } + } + + pub fn ok_empty(id: impl Into) -> Self { + Self { id: id.into(), ok: true, result: None, error: None } + } + + pub fn err(id: impl Into, error: DuckDbWorkerError) -> Self { + Self { id: id.into(), ok: false, result: None, error: Some(error) } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct DuckDbWorkerError { + pub code: String, + pub message: String, +} + +impl DuckDbWorkerError { + pub fn new(code: impl Into, message: impl Into) -> Self { + Self { code: code.into(), message: message.into() } + } + + pub fn from_message(code: &'static str, error: impl ToString) -> Self { + Self::new(code, error.to_string()) + } +} + +impl From for DuckDbWorkerError { + fn from(message: String) -> Self { + Self::new("duckdb_worker_error", message) + } +} + +impl From<&str> for DuckDbWorkerError { + fn from(message: &str) -> Self { + Self::new("duckdb_worker_error", message) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DuckDbWorkerConnectParams { + pub path: String, + #[serde(default)] + pub attached_databases: Vec, + #[serde(default)] + pub init_script: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DuckDbWorkerExecuteParams { + pub sql: String, + #[serde(default)] + pub database: Option, + #[serde(default)] + pub max_rows: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DuckDbWorkerDatabaseParams { + pub database: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DuckDbWorkerTableParams { + pub database: String, + pub schema: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DuckDbWorkerColumnParams { + pub database: String, + pub schema: String, + pub table: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DuckDbWorkerObjectSourceParams { + pub database: String, + pub schema: String, + pub name: String, + pub object_type: ObjectSourceKind, +} diff --git a/crates/dbx-core/tests/duckdb_worker_process.rs b/agents/drivers/duckdb/tests/duckdb_worker_process.rs similarity index 91% rename from crates/dbx-core/tests/duckdb_worker_process.rs rename to agents/drivers/duckdb/tests/duckdb_worker_process.rs index 415418a3e7..63388b4b28 100644 --- a/crates/dbx-core/tests/duckdb_worker_process.rs +++ b/agents/drivers/duckdb/tests/duckdb_worker_process.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "duckdb-bundled")] - use std::path::PathBuf; use std::process::Stdio; use std::sync::atomic::{AtomicU64, Ordering}; @@ -21,7 +19,7 @@ static TEMP_DB_COUNTER: AtomicU64 = AtomicU64::new(0); #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn worker_process_reads_view_source() { let _guard = duckdb_worker_process_test_guard().await; - let executable = PathBuf::from(env!("CARGO_BIN_EXE_duckdb-worker-test-host")); + let executable = PathBuf::from(env!("CARGO_BIN_EXE_dbx-duckdb-driver")); let db_path = temp_duckdb_path(); let _ = std::fs::remove_file(&db_path); @@ -57,10 +55,45 @@ async fn worker_process_reads_view_source() { let _ = std::fs::remove_file(&db_path); } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn worker_process_reads_table_ddl() { + let _guard = duckdb_worker_process_test_guard().await; + let executable = PathBuf::from(env!("CARGO_BIN_EXE_dbx-duckdb-driver")); + let db_path = temp_duckdb_path(); + let _ = std::fs::remove_file(&db_path); + + let client = + DuckDbWorkerClient::open_with_executable(executable, db_path.to_string_lossy().to_string(), Vec::new(), None) + .await + .expect("worker process connects"); + client + .execute( + None, + "CREATE TABLE orders(order_id BIGINT, amount DECIMAL(18, 2))".to_string(), + None, + None, + Some(Duration::from_secs(5)), + ) + .await + .expect("create table"); + + let ddl = client + .get_table_ddl("main".to_string(), "main".to_string(), "orders".to_string()) + .await + .expect("get table ddl"); + + assert!(ddl.starts_with("CREATE TABLE")); + assert!(ddl.contains("order_id")); + assert!(ddl.contains("DECIMAL(18,2)")); + + client.shutdown().await; + let _ = std::fs::remove_file(&db_path); +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn worker_process_recovers_immediately_after_cancelled_long_query() { let _guard = duckdb_worker_process_test_guard().await; - let executable = PathBuf::from(env!("CARGO_BIN_EXE_duckdb-worker-test-host")); + let executable = PathBuf::from(env!("CARGO_BIN_EXE_dbx-duckdb-driver")); let db_path = temp_duckdb_path(); let _ = std::fs::remove_file(&db_path); @@ -111,7 +144,7 @@ async fn worker_process_recovers_immediately_after_cancelled_long_query() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn worker_process_recovers_after_registered_cancel_interrupt() { let _guard = duckdb_worker_process_test_guard().await; - let executable = PathBuf::from(env!("CARGO_BIN_EXE_duckdb-worker-test-host")); + let executable = PathBuf::from(env!("CARGO_BIN_EXE_dbx-duckdb-driver")); let db_path = temp_duckdb_path(); let _ = std::fs::remove_file(&db_path); @@ -174,7 +207,7 @@ async fn worker_process_recovers_after_registered_cancel_interrupt() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn worker_process_recovers_after_parser_error() { let _guard = duckdb_worker_process_test_guard().await; - let executable = PathBuf::from(env!("CARGO_BIN_EXE_duckdb-worker-test-host")); + let executable = PathBuf::from(env!("CARGO_BIN_EXE_dbx-duckdb-driver")); let db_path = temp_duckdb_path(); let _ = std::fs::remove_file(&db_path); @@ -224,7 +257,7 @@ async fn worker_process_recovers_after_parser_error() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn worker_process_keeps_session_state_after_benign_error() { let _guard = duckdb_worker_process_test_guard().await; - let executable = PathBuf::from(env!("CARGO_BIN_EXE_duckdb-worker-test-host")); + let executable = PathBuf::from(env!("CARGO_BIN_EXE_dbx-duckdb-driver")); let db_path = temp_duckdb_path(); let _ = std::fs::remove_file(&db_path); @@ -272,7 +305,7 @@ async fn worker_process_keeps_session_state_after_benign_error() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn worker_process_exits_when_stdin_closes_during_active_query() { let _guard = duckdb_worker_process_test_guard().await; - let executable = PathBuf::from(env!("CARGO_BIN_EXE_duckdb-worker-test-host")); + let executable = PathBuf::from(env!("CARGO_BIN_EXE_dbx-duckdb-driver")); let db_path = temp_duckdb_path(); let _ = std::fs::remove_file(&db_path); let mut child = Command::new(executable) @@ -339,6 +372,7 @@ async fn worker_process_is_killed_after_connect_timeout() { std::env::set_var("DBX_DUCKDB_HANGING_CONNECT_PID_FILE", &pid_file); let client = DuckDbWorkerClient::new_unconnected_with_timeouts( executable, + Vec::new(), db_path.to_string_lossy().to_string(), Vec::new(), None, @@ -374,6 +408,7 @@ async fn worker_process_is_killed_after_connect_error() { std::env::set_var("DBX_DUCKDB_PID_TEST_HOST_PID_FILE", &pid_file); let client = DuckDbWorkerClient::new_unconnected_with_timeouts( executable, + Vec::new(), db_path.to_string_lossy().to_string(), Vec::new(), None, @@ -405,7 +440,7 @@ async fn worker_process_is_killed_after_connect_error() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn worker_process_retries_connect_after_transient_file_lock() { let _guard = duckdb_worker_process_test_guard().await; - let executable = PathBuf::from(env!("CARGO_BIN_EXE_duckdb-worker-test-host")); + let executable = PathBuf::from(env!("CARGO_BIN_EXE_dbx-duckdb-driver")); let lock_owner_executable = PathBuf::from(env!("CARGO_BIN_EXE_duckdb-worker-file-lock-owner")); let db_path = temp_duckdb_path(); let _ = std::fs::remove_file(&db_path); @@ -429,6 +464,7 @@ async fn worker_process_retries_connect_after_transient_file_lock() { let client = DuckDbWorkerClient::new_unconnected_with_timeouts( executable, + Vec::new(), db_path.to_string_lossy().to_string(), Vec::new(), None, diff --git a/crates/dbx-core/tests/support/duckdb_worker_file_lock_owner.rs b/agents/drivers/duckdb/tests/support/duckdb_worker_file_lock_owner.rs similarity index 93% rename from crates/dbx-core/tests/support/duckdb_worker_file_lock_owner.rs rename to agents/drivers/duckdb/tests/support/duckdb_worker_file_lock_owner.rs index 25ac9890b9..e70521604c 100644 --- a/crates/dbx-core/tests/support/duckdb_worker_file_lock_owner.rs +++ b/agents/drivers/duckdb/tests/support/duckdb_worker_file_lock_owner.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "duckdb-bundled")] - use std::io::Write; use std::path::PathBuf; use std::time::Duration; diff --git a/crates/dbx-core/tests/support/duckdb_worker_hanging_connect_test_host.rs b/agents/drivers/duckdb/tests/support/duckdb_worker_hanging_connect_test_host.rs similarity index 92% rename from crates/dbx-core/tests/support/duckdb_worker_hanging_connect_test_host.rs rename to agents/drivers/duckdb/tests/support/duckdb_worker_hanging_connect_test_host.rs index 2919239274..1f9b22359a 100644 --- a/crates/dbx-core/tests/support/duckdb_worker_hanging_connect_test_host.rs +++ b/agents/drivers/duckdb/tests/support/duckdb_worker_hanging_connect_test_host.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "duckdb-bundled")] - use std::io::{self, BufRead}; use std::time::Duration; diff --git a/crates/dbx-core/tests/support/duckdb_worker_pid_test_host.rs b/agents/drivers/duckdb/tests/support/duckdb_worker_pid_test_host.rs similarity index 73% rename from crates/dbx-core/tests/support/duckdb_worker_pid_test_host.rs rename to agents/drivers/duckdb/tests/support/duckdb_worker_pid_test_host.rs index 4ce0fc6726..22b9aae2ed 100644 --- a/crates/dbx-core/tests/support/duckdb_worker_pid_test_host.rs +++ b/agents/drivers/duckdb/tests/support/duckdb_worker_pid_test_host.rs @@ -1,12 +1,10 @@ -#![cfg(feature = "duckdb-bundled")] - fn main() { if let Ok(path) = std::env::var("DBX_DUCKDB_PID_TEST_HOST_PID_FILE") { let _ = std::fs::write(path, std::process::id().to_string()); } let runtime = tokio::runtime::Runtime::new().expect("Failed to create DuckDB worker test runtime"); - if let Err(err) = runtime.block_on(dbx_core::db::duckdb_worker_runtime::run_stdio_worker()) { + if let Err(err) = runtime.block_on(dbx_duckdb_driver::run_stdio_worker()) { eprintln!("DuckDB worker test host failed: {err}"); std::process::exit(1); } diff --git a/agents/scripts/build_driver_zips.py b/agents/scripts/build_driver_zips.py index ccaa1c2c82..8c6ff9f3e5 100644 --- a/agents/scripts/build_driver_zips.py +++ b/agents/scripts/build_driver_zips.py @@ -1,8 +1,12 @@ #!/usr/bin/env python3 import argparse import copy +import hashlib +import io import json -import zipfile +import subprocess +import tarfile +import tempfile from pathlib import Path from urllib.parse import urlparse @@ -11,10 +15,51 @@ def artifact_filename(url: str) -> str: return Path(urlparse(url).path).name -def write_driver_zip(output: Path, registry: dict, source: Path) -> None: - with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive: - archive.writestr("agent-registry.json", json.dumps(registry, ensure_ascii=False, indent=2) + "\n") - archive.write(source, f"drivers/{source.name}") +def write_driver_tar_zstd(output: Path, registry: dict, source: Path, *, executable: bool) -> None: + registry_bytes = (json.dumps(registry, ensure_ascii=False, indent=2) + "\n").encode("utf-8") + with tempfile.TemporaryDirectory() as temp_dir: + tar_path = Path(temp_dir) / "driver.tar" + with tarfile.open(tar_path, "w", format=tarfile.PAX_FORMAT) as archive: + registry_info = tarfile.TarInfo("agent-registry.json") + registry_info.size = len(registry_bytes) + registry_info.mode = 0o644 + registry_info.mtime = 0 + archive.addfile(registry_info, io.BytesIO(registry_bytes)) + + driver_info = archive.gettarinfo(str(source), arcname=f"drivers/{source.name}") + driver_info.mode = 0o755 if executable else 0o644 + driver_info.mtime = 0 + driver_info.uid = 0 + driver_info.gid = 0 + driver_info.uname = "" + driver_info.gname = "" + with source.open("rb") as driver_file: + archive.addfile(driver_info, driver_file) + + subprocess.run( + ["zstd", "-q", "-19", "--force", str(tar_path), "-o", str(output)], + check=True, + ) + + +def release_url_with_filename(url: str, filename: str) -> str: + prefix, separator, _ = url.rpartition("/") + return f"{prefix}{separator}{filename}" if separator else filename + + +def packaged_artifact(artifact: dict, source: Path) -> dict: + packaged = copy.deepcopy(artifact) + packaged["url"] = source.name + packaged["size"] = source.stat().st_size + packaged.pop("format", None) + return packaged + + +def update_release_artifact(artifact: dict, output: Path) -> None: + artifact["url"] = release_url_with_filename(artifact["url"], output.name) + artifact["size"] = output.stat().st_size + artifact["sha256"] = hashlib.sha256(output.read_bytes()).hexdigest() + artifact["format"] = "tar_zstd" def build_driver_zips(release_dir: Path) -> list[Path]: @@ -33,10 +78,11 @@ def build_driver_zips(release_dir: Path) -> list[Path]: package_driver = copy.deepcopy(driver) package_driver.pop("native", None) - package_driver["jar"] = {"url": source.name, "size": source.stat().st_size} + package_driver["jar"] = packaged_artifact(jar_artifact, source) package_registry = {"jres": {}, "drivers": {driver_name: package_driver}} - output = release_dir / f"dbx-agent-{driver_name}-{version}.zip" - write_driver_zip(output, package_registry, source) + output = release_dir / f"dbx-agent-{driver_name}-{version}.tar.zst" + write_driver_tar_zstd(output, package_registry, source, executable=False) + update_release_artifact(jar_artifact, output) outputs.append(output) for platform, artifact in driver.get("native", {}).items(): @@ -47,22 +93,38 @@ def build_driver_zips(release_dir: Path) -> list[Path]: package_driver = copy.deepcopy(driver) package_driver.pop("jar", None) - package_driver["native"] = {platform: {"url": source.name, "size": source.stat().st_size}} + package_driver["native"] = {platform: packaged_artifact(artifact, source)} package_registry = {"jres": {}, "drivers": {driver_name: package_driver}} - output = release_dir / f"dbx-agent-{driver_name}-{version}-{platform}.zip" - write_driver_zip(output, package_registry, source) + output = release_dir / f"dbx-agent-{driver_name}-{version}-{platform}.tar.zst" + write_driver_tar_zstd(output, package_registry, source, executable=True) + update_release_artifact(artifact, output) outputs.append(output) + registry_path.write_text(json.dumps(registry, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") return outputs +def remove_raw_driver_artifacts(release_dir: Path) -> list[Path]: + removed: list[Path] = [] + for path in sorted(release_dir.glob("dbx-agent-*")): + if path.name.endswith(".tar.zst") or not path.is_file(): + continue + path.unlink() + removed.append(path) + return removed + + def main() -> None: - parser = argparse.ArgumentParser(description="Build offline ZIPs for individual DBX agents") + parser = argparse.ArgumentParser(description="Build tar.zst packages for individual DBX agents") parser.add_argument("release_dir", type=Path) + parser.add_argument("--cleanup-sources", action="store_true") args = parser.parse_args() for path in build_driver_zips(args.release_dir): print(f"Created {path.name} ({path.stat().st_size} bytes)") + if args.cleanup_sources: + for path in remove_raw_driver_artifacts(args.release_dir): + print(f"Removed intermediate {path.name}") if __name__ == "__main__": diff --git a/agents/scripts/build_offline_zip.sh b/agents/scripts/build_offline_zip.sh index 90393240dc..919240c3f0 100755 --- a/agents/scripts/build_offline_zip.sh +++ b/agents/scripts/build_offline_zip.sh @@ -3,7 +3,7 @@ set -euo pipefail # Build offline ZIP bundles for each platform. # Usage: ./scripts/build_offline_zip.sh -# The release-dir must contain agent-registry.json, dbx-agent-*.jar, and dbx-jre-*.tar.gz +# The release-dir must contain agent-registry.json, raw driver artifacts, and dbx-jre-*.tar.zst RELEASE_DIR="$(cd "${1:?Usage: build_offline_zip.sh }" && pwd)" @@ -32,7 +32,7 @@ for platform in "${PLATFORMS[@]}"; do # Copy all JRE archives for this platform JRE_COUNT=0 - for jre_file in "$RELEASE_DIR"/dbx-jre-*-"$platform".tar.gz; do + for jre_file in "$RELEASE_DIR"/dbx-jre-*-"$platform".tar.zst; do [ -f "$jre_file" ] || continue cp "$jre_file" "$WORK/jre/" JRE_COUNT=$((JRE_COUNT + 1)) diff --git a/agents/scripts/driver_release_packages_test.py b/agents/scripts/driver_release_packages_test.py index 498d408651..3ecd33398d 100644 --- a/agents/scripts/driver_release_packages_test.py +++ b/agents/scripts/driver_release_packages_test.py @@ -1,29 +1,40 @@ #!/usr/bin/env python3 +import io import json import subprocess +import tarfile import tempfile import unittest import zipfile from pathlib import Path -from build_driver_zips import build_driver_zips +from build_driver_zips import build_driver_zips, remove_raw_driver_artifacts from version_agent_artifacts import version_agent_artifacts class DriverReleasePackagesTest(unittest.TestCase): - def test_builds_java_and_platform_specific_native_driver_zips(self) -> None: + def test_builds_java_and_platform_specific_native_driver_tar_zstd_packages(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: release_dir = Path(temp_dir) native_source = release_dir / "dbx-agent-kingbase-windows-x64.exe" native_source.write_bytes(b"MZtest-agent") + duckdb_source = release_dir / "dbx-agent-duckdb-macos-aarch64" + duckdb_source.write_bytes(b"\xcf\xfa\xed\xfetest-duckdb-agent") java_source = release_dir / "dbx-agent-h2.jar" java_source.write_bytes(b"test-jar") - versions = {"h2": "0.2.5", "oracle": "0.1.10", "xugu": "0.1.20", "kingbase": "0.1.34"} + versions = { + "h2": "0.2.5", + "oracle": "0.1.10", + "xugu": "0.1.20", + "kingbase": "0.1.34", + "duckdb": "0.1.0", + } renamed = version_agent_artifacts(release_dir, versions) versioned_java = release_dir / "dbx-agent-h2-0.2.5.jar" versioned_native = release_dir / "dbx-agent-kingbase-0.1.34-windows-x64.exe" - self.assertEqual(renamed, [versioned_java, versioned_native]) + versioned_duckdb = release_dir / "dbx-agent-duckdb-0.1.0-macos-aarch64" + self.assertEqual(renamed, [versioned_java, versioned_native, versioned_duckdb]) registry = { "jres": {"21": {"version": "21", "platforms": {}}}, @@ -48,6 +59,19 @@ class DriverReleasePackagesTest(unittest.TestCase): } }, }, + "duckdb": { + "version": "0.1.0", + "label": "DuckDB", + "min_app_version": "0.6.0", + "jre": "21", + "jar": {"url": "https://example.com/legacy-placeholder.jar", "size": 0}, + "native": { + "macos-aarch64": { + "url": f"https://example.com/{versioned_duckdb.name}", + "size": versioned_duckdb.stat().st_size, + } + }, + }, }, } (release_dir / "agent-registry.json").write_text(json.dumps(registry), encoding="utf-8") @@ -57,26 +81,51 @@ class DriverReleasePackagesTest(unittest.TestCase): self.assertEqual( outputs, [ - release_dir / "dbx-agent-h2-0.2.5.zip", - release_dir / "dbx-agent-kingbase-0.1.34-windows-x64.zip", + release_dir / "dbx-agent-h2-0.2.5.tar.zst", + release_dir / "dbx-agent-kingbase-0.1.34-windows-x64.tar.zst", + release_dir / "dbx-agent-duckdb-0.1.0-macos-aarch64.tar.zst", ], ) - with zipfile.ZipFile(outputs[0]) as archive: - self.assertEqual(set(archive.namelist()), {"agent-registry.json", f"drivers/{versioned_java.name}"}) - package_registry = json.loads(archive.read("agent-registry.json")) - self.assertEqual(set(package_registry["drivers"]), {"h2"}) - self.assertNotIn("native", package_registry["drivers"]["h2"]) - self.assertEqual(package_registry["drivers"]["h2"]["jar"], {"url": versioned_java.name, "size": 8}) - with zipfile.ZipFile(outputs[1]) as archive: - self.assertEqual(set(archive.namelist()), {"agent-registry.json", f"drivers/{versioned_native.name}"}) - package_registry = json.loads(archive.read("agent-registry.json")) - kingbase = package_registry["drivers"]["kingbase"] - self.assertNotIn("jar", kingbase) - self.assertEqual(set(kingbase["native"]), {"windows-x64"}) - self.assertEqual( - kingbase["native"]["windows-x64"], - {"url": versioned_native.name, "size": versioned_native.stat().st_size}, - ) + package_cases = [ + (outputs[0], "h2", versioned_java, "jar", None), + (outputs[1], "kingbase", versioned_native, "native", "windows-x64"), + (outputs[2], "duckdb", versioned_duckdb, "native", "macos-aarch64"), + ] + for output, driver_name, source, artifact_type, platform in package_cases: + tar_bytes = subprocess.run( + ["zstd", "-q", "-dc", str(output)], + check=True, + capture_output=True, + ).stdout + with tarfile.open(fileobj=io.BytesIO(tar_bytes), mode="r:") as archive: + self.assertEqual(set(archive.getnames()), {"agent-registry.json", f"drivers/{source.name}"}) + package_registry = json.load(archive.extractfile("agent-registry.json")) + driver = package_registry["drivers"][driver_name] + if artifact_type == "jar": + self.assertNotIn("native", driver) + self.assertEqual(driver["jar"], {"url": source.name, "size": source.stat().st_size}) + else: + self.assertNotIn("jar", driver) + self.assertEqual( + driver["native"][platform], + {"url": source.name, "size": source.stat().st_size}, + ) + + final_registry = json.loads((release_dir / "agent-registry.json").read_text(encoding="utf-8")) + release_artifacts = [ + (final_registry["drivers"]["h2"]["jar"], outputs[0]), + (final_registry["drivers"]["kingbase"]["native"]["windows-x64"], outputs[1]), + (final_registry["drivers"]["duckdb"]["native"]["macos-aarch64"], outputs[2]), + ] + for artifact, output in release_artifacts: + self.assertEqual(artifact["url"], f"https://example.com/{output.name}") + self.assertEqual(artifact["size"], output.stat().st_size) + self.assertEqual(artifact["format"], "tar_zstd") + self.assertEqual(len(artifact["sha256"]), 64) + + removed = remove_raw_driver_artifacts(release_dir) + self.assertEqual(removed, [versioned_duckdb, versioned_java, versioned_native]) + self.assertTrue(all(output.is_file() for output in outputs)) def test_full_offline_bundle_includes_supported_windows_artifacts(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: @@ -85,8 +134,8 @@ class DriverReleasePackagesTest(unittest.TestCase): kafka_filename = "dbx-agent-kafka-0.1.0.jar" (release_dir / filename).write_bytes(b"MZtest-agent") (release_dir / kafka_filename).write_bytes(b"test-kafka-agent") - (release_dir / "dbx-jre-21-windows-x64.tar.gz").write_bytes(b"test-jre") - (release_dir / "dbx-jre-21-windows-aarch64.tar.gz").write_bytes(b"test-jre") + (release_dir / "dbx-jre-21-windows-x64.tar.zst").write_bytes(b"test-jre") + (release_dir / "dbx-jre-21-windows-aarch64.tar.zst").write_bytes(b"test-jre") (release_dir / "agent-registry.json").write_text('{"jres":{},"drivers":{}}', encoding="utf-8") result = subprocess.run( @@ -105,7 +154,7 @@ class DriverReleasePackagesTest(unittest.TestCase): self.assertIn(f"drivers/{filename}", archive.namelist()) self.assertIn(f"drivers/{kafka_filename}", archive.namelist()) with zipfile.ZipFile(arm64_bundle) as archive: - self.assertIn("jre/dbx-jre-21-windows-aarch64.tar.gz", archive.namelist()) + self.assertIn("jre/dbx-jre-21-windows-aarch64.tar.zst", archive.namelist()) self.assertIn(f"drivers/{kafka_filename}", archive.namelist()) diff --git a/agents/scripts/validate_agents.py b/agents/scripts/validate_agents.py index 3df80e844f..195fb7fa84 100644 --- a/agents/scripts/validate_agents.py +++ b/agents/scripts/validate_agents.py @@ -13,10 +13,12 @@ KOTLIN_SCAN_EXCLUDED_PARTS = {".git", ".gradle", "build"} DEFAULT_AGENT_JRE_KEY = "21" NON_JDBC_AGENT_MODULES = {"mongodb", "etcd", "zookeeper", "kafka", "rocketmq", "rabbitmq"} NATIVE_ONLY_AGENT_MODULES = { + "duckdb": "drivers/duckdb", "oracle": "drivers/oracle-go", "kingbase": "drivers/kingbase-go", "xugu": "drivers/xugu", } +AUTO_VERSIONED_NATIVE_MODULES = {"duckdb"} JDBC_ARCHITECTURE_ALLOWLIST = { "h2-legacy": "reuses the H2 agent implementation with an isolated legacy driver version", "access": "custom Access metadata and URL behavior pending migration", @@ -90,7 +92,10 @@ def validate_versions(root: Path) -> list[str]: included = agent_modules(root) versions = set(json.loads((root / "versions.json").read_text(encoding="utf-8"))) return ( - [f"included module missing version: {name}" for name in sorted(included - versions)] + [ + f"included module missing version: {name}" + for name in sorted(included - versions - AUTO_VERSIONED_NATIVE_MODULES) + ] + [f"versions key not included: {name}" for name in sorted(versions - included)] ) diff --git a/agents/scripts/validate_agents_test.py b/agents/scripts/validate_agents_test.py index 9608d644f8..2d48788a64 100644 --- a/agents/scripts/validate_agents_test.py +++ b/agents/scripts/validate_agents_test.py @@ -136,7 +136,7 @@ class ValidateAgentsTest(unittest.TestCase): "include(*(infrastructureModules + driverModules))\n", encoding="utf-8", ) - for driver in ("oracle-go", "kingbase-go", "xugu"): + for driver in ("oracle-go", "kingbase-go", "xugu", "duckdb"): (root / "drivers" / driver).mkdir(parents=True) (root / "versions.json").write_text( json.dumps({"h2": "0.1.0", "oracle": "0.1.0", "kingbase": "0.1.0", "xugu": "0.1.0"}), diff --git a/agents/scripts/version_agent_artifacts.py b/agents/scripts/version_agent_artifacts.py index 80948d354e..f02e2f88f5 100644 --- a/agents/scripts/version_agent_artifacts.py +++ b/agents/scripts/version_agent_artifacts.py @@ -4,7 +4,7 @@ import json from pathlib import Path -NATIVE_DRIVERS = ("oracle", "xugu", "kingbase") +NATIVE_DRIVERS = ("oracle", "xugu", "kingbase", "duckdb") PLATFORMS = ( "macos-aarch64", "macos-x64", diff --git a/agents/versions.json b/agents/versions.json index addd583e4c..1c5466a7f7 100644 --- a/agents/versions.json +++ b/agents/versions.json @@ -6,6 +6,7 @@ "databend": "0.1.22", "databricks": "0.1.29", "db2": "0.1.34", + "duckdb": "0.1.0", "exasol": "0.1.29", "firebird": "0.1.29", "gbase8a": "0.1.29", diff --git a/apps/desktop/src/components/config/DriverStoreDialog.vue b/apps/desktop/src/components/config/DriverStoreDialog.vue index b8339e304d..d6e794fa19 100644 --- a/apps/desktop/src/components/config/DriverStoreDialog.vue +++ b/apps/desktop/src/components/config/DriverStoreDialog.vue @@ -552,6 +552,11 @@ async function uninstallDriver(dbType: string) { toast(t("driverStore.driverUninstallSuccess", { label })); return; } + const blockers = await api.checkAgentUpdateBlockers([dbType]); + if (blockers.length > 0) { + toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") })); + return; + } await api.uninstallAgent(dbType); await refreshAgents(); toast(t("driverStore.driverUninstallSuccess", { label })); @@ -561,12 +566,14 @@ async function uninstallDriver(dbType: string) { } const importingZip = ref(false); +const importingDriver = ref(null); +const agentImportBusy = computed(() => importingZip.value || importingDriver.value !== null); function chooseWebOfflineZip(): Promise { return new Promise((resolve) => { const input = document.createElement("input"); input.type = "file"; - input.accept = ".zip"; + input.accept = ".zip,.tar.zst"; input.onchange = () => resolve(input.files?.[0] ?? null); input.click(); }); @@ -601,7 +608,7 @@ function chooseWebFile(accept: string): Promise { } async function importOfflineZip() { - if (importingZip.value) return; + if (agentImportBusy.value) return; let selected: string | File | null = null; if (isWeb) { selected = await chooseWebOfflineZip(); @@ -610,7 +617,7 @@ async function importOfflineZip() { const path = await open({ title: t("driverStore.chooseOfflineDriverPackage"), multiple: false, - filters: [{ name: "ZIP", extensions: ["zip"] }], + filters: [{ name: "Driver package", extensions: ["zip", "zst"] }], }); selected = typeof path === "string" ? path : null; } @@ -632,6 +639,7 @@ async function importOfflineZip() { } async function importDriverFile(driver: AgentDriverInfo) { + if (agentImportBusy.value) return; const dbType = driver.db_type; if (isPrestoSqlBuiltinDriver(dbType)) { await importJdbcDrivers(); @@ -663,30 +671,32 @@ async function importDriverFile(driver: AgentDriverInfo) { toast(t("driverStore.driverImportSuccess", { label })); } }; + const runSelectedFileImport = async (selected: string | File) => { + importingDriver.value = dbType; + try { + await installSelectedFile(selected); + } catch (e: any) { + toast(t("driverStore.driverImportFailed", { label, error: backendError(e) })); + } finally { + importingDriver.value = null; + } + }; if (isWeb) { // Native release assets have no extension on macOS/Linux, so do not apply // a browser accept filter that would make the correct file unselectable. const file = await chooseWebFile(webDriverImportAccept(requiresJavaRuntime, isWindows)); if (!file) return; - try { - await installSelectedFile(file); - } catch (e: any) { - toast(t("driverStore.driverImportFailed", { label, error: backendError(e) })); - } + await runSelectedFileImport(file); return; } const { open } = await import("@tauri-apps/plugin-dialog"); const selected = await open({ title: t("driverStore.chooseDriverJar"), multiple: false, - filters: requiresJavaRuntime ? [{ name: "Driver package or JAR", extensions: ["zip", "jar"] }] : isWindows ? [{ name: "Driver package or executable", extensions: ["zip", "exe"] }] : undefined, + filters: requiresJavaRuntime ? [{ name: "Driver package or JAR", extensions: ["zip", "zst", "jar"] }] : isWindows ? [{ name: "Driver package or executable", extensions: ["zip", "zst", "exe"] }] : undefined, }); if (typeof selected !== "string") return; - try { - await installSelectedFile(selected); - } catch (e: any) { - toast(t("driverStore.driverImportFailed", { label, error: backendError(e) })); - } + await runSelectedFileImport(selected); } async function reinstallJre(jreKey: string) { @@ -1337,9 +1347,10 @@ watch(driverStoreTab, (tab) => { - - - @@ -1417,7 +1428,7 @@ watch(driverStoreTab, (tab) => {
{{ t("driverStore.updatesAvailableTitle") }} ({{ globalUpdatableDrivers.length }})

{{ t("driverStore.updatesAvailableDescription") }}

- - @@ -1465,11 +1476,12 @@ watch(driverStoreTab, (tab) => { size="sm" variant="ghost" class="h-7 w-7 rounded-md text-xs text-muted-foreground" - :title="t('driverStore.importLocalJar')" - :disabled="upgradingAll || installing !== null || importingZip" + :title="importingDriver === driver.db_type ? t('driverStore.importing') : t('driverStore.importLocalJar')" + :disabled="upgradingAll || installing !== null || agentImportBusy" @click="importDriverFile(driver)" > - + + - - @@ -1566,14 +1578,14 @@ watch(driverStoreTab, (tab) => { size="sm" variant="outline" class="h-7 rounded-md border-green-500/30 bg-green-500/10 text-xs text-green-700 hover:bg-green-500/15" - :disabled="upgradingAll || importingZip" + :disabled="upgradingAll || agentImportBusy" @click="removeQueuedDriverInstall(driver.db_type)" > {{ t("driverStore.queued") }} - @@ -1582,11 +1594,12 @@ watch(driverStoreTab, (tab) => { size="sm" variant="ghost" class="h-7 w-7 rounded-md text-xs text-muted-foreground" - :title="t('driverStore.importLocalJar')" - :disabled="upgradingAll || installing !== null || importingZip" + :title="importingDriver === driver.db_type ? t('driverStore.importing') : t('driverStore.importLocalJar')" + :disabled="upgradingAll || installing !== null || agentImportBusy" @click="importDriverFile(driver)" > - + + - - @@ -1642,14 +1662,14 @@ watch(driverStoreTab, (tab) => { size="sm" variant="outline" class="h-7 rounded-md border-green-500/30 bg-green-500/10 text-xs text-green-700 hover:bg-green-500/15" - :disabled="upgradingAll || importingZip" + :disabled="upgradingAll || agentImportBusy" @click="removeQueuedDriverInstall(driver.db_type)" > {{ t("driverStore.queued") }} - @@ -1658,11 +1678,12 @@ watch(driverStoreTab, (tab) => { size="sm" variant="ghost" class="h-7 w-7 rounded-md text-xs text-muted-foreground" - :title="t('driverStore.importLocalJar')" - :disabled="upgradingAll || installing !== null || importingZip" + :title="importingDriver === driver.db_type ? t('driverStore.importing') : t('driverStore.importLocalJar')" + :disabled="upgradingAll || installing !== null || agentImportBusy" @click="importDriverFile(driver)" > - + + - - diff --git a/apps/desktop/src/lib/backend/http.ts b/apps/desktop/src/lib/backend/http.ts index 2b5a2176aa..c9813ce3b4 100644 --- a/apps/desktop/src/lib/backend/http.ts +++ b/apps/desktop/src/lib/backend/http.ts @@ -483,8 +483,8 @@ export async function upgradeAllAgents(_source?: UpdateDownloadSource, operation return post("/api/agents/upgrade-all", { operationId }); } -export async function checkAgentUpdateBlockers(_dbTypes: string[]): Promise { - return []; +export async function checkAgentUpdateBlockers(dbTypes: string[]): Promise { + return post("/api/agents/update-blockers", { dbTypes }); } export async function uninstallAgent(dbType: string): Promise { @@ -505,7 +505,7 @@ export async function invalidateAgentRegistryCache(): Promise { export async function importAgentsFromZip(fileOrPath: string | File, operationId?: string): Promise { if (typeof fileOrPath === "string") { - throw new Error("Offline ZIP import in web mode requires a File object, not a file path"); + throw new Error("Offline package import in web mode requires a File object, not a file path"); } const formData = new FormData(); if (operationId) formData.append("operationId", operationId); diff --git a/apps/desktop/src/lib/backend/tauri.ts b/apps/desktop/src/lib/backend/tauri.ts index f1abbcb9b5..3a58b2c1ea 100644 --- a/apps/desktop/src/lib/backend/tauri.ts +++ b/apps/desktop/src/lib/backend/tauri.ts @@ -1740,7 +1740,7 @@ export async function invalidateAgentRegistryCache(): Promise { export async function importAgentsFromZip(path: string | File, operationId?: string): Promise { if (typeof path !== "string") { - throw new Error("Desktop offline ZIP import requires a local file path"); + throw new Error("Desktop offline package import requires a local file path"); } return invoke("import_agents_from_zip", { path, operationId }); } diff --git a/apps/desktop/src/lib/connection/__tests__/agentDriverInstallHint.spec.ts b/apps/desktop/src/lib/connection/__tests__/agentDriverInstallHint.spec.ts index c5ed49a59a..89234c2aef 100644 --- a/apps/desktop/src/lib/connection/__tests__/agentDriverInstallHint.spec.ts +++ b/apps/desktop/src/lib/connection/__tests__/agentDriverInstallHint.spec.ts @@ -1,5 +1,13 @@ import { describe, expect, it } from "vitest"; -import { driverStoreFocusForInstallError, hasInstalledAgentVersion } from "@/lib/connection/agentDriverInstallHint"; +import { agentDriverInstallKey, driverStoreFocusForInstallError, hasInstalledAgentVersion, showAgentDriverInstallHint } from "@/lib/connection/agentDriverInstallHint"; + +describe("DuckDB driver installation", () => { + it("requires the downloadable DuckDB driver before connecting", () => { + expect(agentDriverInstallKey("duckdb")).toBe("duckdb"); + expect(showAgentDriverInstallHint("duckdb", [])).toBe(true); + expect(showAgentDriverInstallHint("duckdb", [{ db_type: "duckdb", installed: true }])).toBe(false); + }); +}); describe("driverStoreFocusForInstallError", () => { it("focuses the missing agent driver for driver-not-installed errors", () => { diff --git a/apps/desktop/src/lib/connection/__tests__/driverCategoryDefinitions.spec.ts b/apps/desktop/src/lib/connection/__tests__/driverCategoryDefinitions.spec.ts index 2a6272e743..2f1441eddb 100644 --- a/apps/desktop/src/lib/connection/__tests__/driverCategoryDefinitions.spec.ts +++ b/apps/desktop/src/lib/connection/__tests__/driverCategoryDefinitions.spec.ts @@ -10,7 +10,7 @@ describe("getCategoryForAgentDriver", () => { // domestic expect(getCategoryForAgentDriver("dameng")).toBe("domestic"); // lightweight - expect(getCategoryForAgentDriver("access")).toBe("lightweight"); + expect(getCategoryForAgentDriver("duckdb")).toBe("lightweight"); // document expect(getCategoryForAgentDriver("mongodb")).toBe("document"); // graph_ai @@ -69,8 +69,8 @@ describe("AGENT_DRIVER_CATEGORY_MAP integrity", () => { it("covers all known agent driver keys with no stale entries", () => { // Golden set — mirrors the store-visible entries in agent_catalog.rs - // plus EXTRA_DRIVER_STORE_ENTRIES (kafka, rocketmq, rabbitmq, - // sqlserver-legacy) and the PrestoSQL built-in driver. + // plus EXTRA_DRIVER_STORE_ENTRIES (duckdb, kafka, rocketmq, + // rabbitmq, sqlserver-legacy) and the PrestoSQL built-in driver. // When an Agent driver is added to the catalog its key MUST appear here; // removing a key from the map without removing it from the list below // will fail this test. @@ -112,6 +112,7 @@ describe("AGENT_DRIVER_CATEGORY_MAP integrity", () => { "yashandb", // lightweight "access", + "duckdb", "h2", "h2-legacy", // document diff --git a/apps/desktop/src/lib/connection/driver-category-definitions.ts b/apps/desktop/src/lib/connection/driver-category-definitions.ts index 7708d8b82b..6fd8c05f42 100644 --- a/apps/desktop/src/lib/connection/driver-category-definitions.ts +++ b/apps/desktop/src/lib/connection/driver-category-definitions.ts @@ -24,6 +24,7 @@ export const AGENT_DRIVER_CATEGORY_MAP: Readonly { - it("recognizes ZIP paths and uploaded files case-insensitively", () => { + it("recognizes ZIP and tar.zst packages case-insensitively", () => { expect(isOfflineDriverPackage("C:\\Downloads\\dbx-agent-h2-0.2.5.ZIP")).toBe(true); expect(isOfflineDriverPackage({ name: "dbx-agent-kingbase-0.1.34-macos-aarch64.zip" })).toBe(true); + expect(isOfflineDriverPackage({ name: "dbx-agent-duckdb-0.1.0-macos-aarch64.TAR.ZST" })).toBe(true); expect(isOfflineDriverPackage({ name: "dbx-agent-h2-0.2.5.jar" })).toBe(false); }); it("allows ZIP alongside the platform raw artifact", () => { - expect(webDriverImportAccept(true, false)).toBe(".zip,.jar"); - expect(webDriverImportAccept(false, true)).toBe(".zip,.exe"); + expect(webDriverImportAccept(true, false)).toBe(".zip,.tar.zst,.jar"); + expect(webDriverImportAccept(false, true)).toBe(".zip,.tar.zst,.exe"); expect(webDriverImportAccept(false, false)).toBe(""); }); }); diff --git a/apps/desktop/src/lib/driverStore/driverImportSelection.ts b/apps/desktop/src/lib/driverStore/driverImportSelection.ts index c11d70b268..6ff40402ee 100644 --- a/apps/desktop/src/lib/driverStore/driverImportSelection.ts +++ b/apps/desktop/src/lib/driverStore/driverImportSelection.ts @@ -2,10 +2,11 @@ export type DriverImportSelection = string | { name: string }; export function isOfflineDriverPackage(selection: DriverImportSelection): boolean { const name = typeof selection === "string" ? selection : selection.name; - return name.toLowerCase().endsWith(".zip"); + const lowerName = name.toLowerCase(); + return lowerName.endsWith(".zip") || lowerName.endsWith(".tar.zst"); } export function webDriverImportAccept(requiresJavaRuntime: boolean, isWindows: boolean): string { - if (requiresJavaRuntime) return ".zip,.jar"; - return isWindows ? ".zip,.exe" : ""; + if (requiresJavaRuntime) return ".zip,.tar.zst,.jar"; + return isWindows ? ".zip,.tar.zst,.exe" : ""; } diff --git a/crates/dbx-core/Cargo.toml b/crates/dbx-core/Cargo.toml index ad95113e43..aaa7117fc2 100644 --- a/crates/dbx-core/Cargo.toml +++ b/crates/dbx-core/Cargo.toml @@ -4,36 +4,9 @@ version = "0.1.0" edition = "2021" license = "Apache-2.0" -[[bin]] -name = "duckdb-worker-test-host" -path = "tests/support/duckdb_worker_test_host.rs" -required-features = ["duckdb-bundled"] -test = false -bench = false - -[[bin]] -name = "duckdb-worker-hanging-connect-test-host" -path = "tests/support/duckdb_worker_hanging_connect_test_host.rs" -required-features = ["duckdb-bundled"] -test = false - -[[bin]] -name = "duckdb-worker-pid-test-host" -path = "tests/support/duckdb_worker_pid_test_host.rs" -required-features = ["duckdb-bundled"] -test = false -bench = false - -[[bin]] -name = "duckdb-worker-file-lock-owner" -path = "tests/support/duckdb_worker_file_lock_owner.rs" -required-features = ["duckdb-bundled"] -test = false -bench = false - [features] -default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher", "system-fonts"] -duckdb-bundled = ["duckdb/bundled", "duckdb/parquet"] +default = ["duckdb-sidecar", "mq-admin", "sqlite-sqlcipher", "system-fonts"] +duckdb-sidecar = [] mq-admin = [] sqlite-sqlcipher = ["rusqlite/bundled-sqlcipher-vendored-openssl"] openapi = ["dep:utoipa"] @@ -71,7 +44,6 @@ sqlparser = { version = "0.62.0", features = ["visitor"] } redis = { version = "0.32.2", features = ["tokio-comp", "tls-rustls", "tls-rustls-insecure", "tokio-rustls-comp", "sentinel", "cluster-async"] } rustls = { version = "0.23", features = ["aws-lc-rs"] } rustls-pemfile = "2.2" -duckdb = { version = "1.3.2", optional = true } tiberius = { version = "0.12.3", default-features = false, features = ["tds73", "chrono", "rust_decimal", "rustls", "sql-browser-tokio"] } reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls", "socks"] } prometheus-parse = "=0.2.5" @@ -98,6 +70,7 @@ font-kit = { version = "0.14.3", optional = true } zip = { version = "4", default-features = false, features = ["deflate"] } flate2 = "1" tar = "0.4" +zstd = "0.13" sysinfo = { version = "0.32", features = ["system"] } serde_yaml = "0.9" minijinja = "2" diff --git a/crates/dbx-core/assets/database-drivers.manifest.json b/crates/dbx-core/assets/database-drivers.manifest.json index 6d4ae2c19a..948fc14a87 100644 --- a/crates/dbx-core/assets/database-drivers.manifest.json +++ b/crates/dbx-core/assets/database-drivers.manifest.json @@ -199,7 +199,7 @@ "fieldLineage": false, "sqlExplain": false, "userAdmin": false, - "driverManagement": false + "driverManagement": true } }, { diff --git a/crates/dbx-core/src/agent_catalog.rs b/crates/dbx-core/src/agent_catalog.rs index 6dc8052d6a..4fc5af90c7 100644 --- a/crates/dbx-core/src/agent_catalog.rs +++ b/crates/dbx-core/src/agent_catalog.rs @@ -40,12 +40,14 @@ const H2_PROFILES: &[AgentDriverProfile] = &[AgentDriverProfile { profile: "h2-legacy", key: "h2-legacy", label: "H2 2.1 Legacy", store_visible: true }]; const EXTRA_AGENT_LABELS: &[(&str, &str)] = &[ + ("duckdb", "DuckDB"), ("kafka", "Apache Kafka"), ("rocketmq", "Apache RocketMQ"), ("rabbitmq", "RabbitMQ"), ("sqlserver-legacy", "SQL Server legacy compatibility component"), ]; const EXTRA_DRIVER_STORE_ENTRIES: &[(&str, &str)] = &[ + ("duckdb", "DuckDB"), ("kafka", "Apache Kafka"), ("rocketmq", "Apache RocketMQ"), ("rabbitmq", "RabbitMQ"), @@ -388,4 +390,11 @@ mod tests { assert_eq!(agent_key(&DatabaseType::H2, Some("h2-legacy")), Some("h2-legacy")); assert!(driver_store_entries().any(|(key, label)| key == "h2-legacy" && label == "H2 2.1 Legacy")); } + + #[test] + fn duckdb_is_available_in_driver_store_without_using_agent_runtime() { + assert!(driver_store_entries().any(|(key, label)| key == "duckdb" && label == "DuckDB")); + assert_eq!(label_for_key("duckdb"), Some("DuckDB")); + assert!(!is_agent_type(&DatabaseType::DuckDb)); + } } diff --git a/crates/dbx-core/src/agent_manager.rs b/crates/dbx-core/src/agent_manager.rs index 7135e7576a..ea99e1fa35 100644 --- a/crates/dbx-core/src/agent_manager.rs +++ b/crates/dbx-core/src/agent_manager.rs @@ -345,6 +345,14 @@ pub struct DriverInfo { pub struct ArtifactInfo { pub url: String, pub size: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub format: Option, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ArtifactFormat { + TarZstd, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] diff --git a/crates/dbx-core/src/agent_service.rs b/crates/dbx-core/src/agent_service.rs index 40a3efc748..83b607ffee 100644 --- a/crates/dbx-core/src/agent_service.rs +++ b/crates/dbx-core/src/agent_service.rs @@ -8,7 +8,7 @@ use futures::{stream, StreamExt}; use crate::agent_catalog; use crate::agent_manager::{ - AgentDriverInfo, AgentManager, AgentRegistry, InstalledDriver, JavaRuntimeMode, DEFAULT_JRE_KEY, + AgentDriverInfo, AgentManager, AgentRegistry, ArtifactFormat, InstalledDriver, JavaRuntimeMode, DEFAULT_JRE_KEY, }; use crate::DownloadSource; @@ -577,7 +577,7 @@ pub async fn reinstall_agent_jre_from( .platforms .get(platform) .ok_or_else(|| format!("No JRE {jre_key} available for platform: {platform}"))?; - let jre_archive = am.base_dir().join("jre-download.tar.gz"); + let jre_archive = jre_archive_download_path(am, jre_key, platform_jre.format); download_with_progress( am, &progress, @@ -600,7 +600,7 @@ pub async fn reinstall_agent_jre_from( am.stop_daemons().await; let stash = replace_old_jre_dir(&jre_dir)?; persist_pending_jre_cleanup(am, stash.as_ref()).await?; - extract_tar_gz(&jre_archive, &jre_dir)?; + extract_jre_archive(&jre_archive, &jre_dir, platform_jre.format)?; std::fs::remove_file(&jre_archive).ok(); am.mutate_state(|state| state.jre_versions.insert(jre_key.to_string(), jre_info.version.clone()))?; cleanup_jre_download_cache_after_success(am, jre_key); @@ -627,6 +627,41 @@ pub async fn import_agents_from_zip( .await } +pub fn inspect_offline_package(package_path: &Path) -> Result { + if is_tar_zstd_package(package_path) { + inspect_tar_zstd_driver_package(package_path) + } else { + inspect_offline_zip(package_path) + } +} + +pub async fn import_agents_from_package( + am: &AgentManager, + package_path: &Path, + progress: impl Fn(AgentProgressEvent), +) -> Result { + if is_tar_zstd_package(package_path) { + import_tar_zstd_driver_package(am, package_path, |event| { + progress(AgentProgressEvent { + operation_id: None, + step: event.step, + downloaded: Some(event.current as u64), + total: Some(event.total as u64), + db_type: event.db_type, + current: Some(event.current), + total_drivers: Some(event.total), + }); + }) + .await + } else { + import_agents_from_zip(am, package_path, progress).await + } +} + +fn is_tar_zstd_package(path: &Path) -> bool { + path.file_name().and_then(|name| name.to_str()).is_some_and(|name| name.to_ascii_lowercase().ends_with(".tar.zst")) +} + async fn install_agent_driver_with_batch( am: &AgentManager, db_type: &str, @@ -734,7 +769,7 @@ async fn ensure_jre_from_registry( .platforms .get(platform) .ok_or_else(|| format!("No JRE {jre_key} available for platform: {platform}"))?; - let jre_archive = am.base_dir().join(format!("jre-{}-download.tar.gz", jre_key)); + let jre_archive = jre_archive_download_path(am, jre_key, platform_jre.format); progress(AgentProgressEvent::transfer("jre", 0, platform_jre.size).with_batch( Some(db_type), current, @@ -764,11 +799,11 @@ async fn ensure_jre_from_registry( let stash = replace_old_jre_dir(&jre_dir)?; // Persist the stash path *before* extraction so that a crash during - // extract_tar_gz (or a process kill) doesn't leave the renamed-stash + // archive extraction (or a process kill) doesn't leave the renamed-stash // directory as an orphan that never gets cleaned up. persist_pending_jre_cleanup(am, stash.as_ref()).await?; - extract_tar_gz(&jre_archive, &jre_dir)?; + extract_jre_archive(&jre_archive, &jre_dir, platform_jre.format)?; std::fs::remove_file(&jre_archive).ok(); cleanup_jre_download_cache_after_success(am, jre_key); @@ -893,6 +928,8 @@ async fn install_agent_driver_from_registry( if let Some(parent) = target_path.parent() { std::fs::create_dir_all(parent).map_err(|err| format!("Failed to create driver directory: {err}"))?; } + let artifact_kind = if is_native_artifact { DriverArtifactKind::Native } else { DriverArtifactKind::Jar }; + let download_path = driver_artifact_download_path(&target_path, artifact.format); progress(AgentProgressEvent::transfer("driver", 0, artifact.size).with_batch( Some(db_type), current, @@ -905,7 +942,7 @@ async fn install_agent_driver_from_registry( source, &artifact.url, &r2_path_with_cache_buster(&github_url_to_r2_path(&artifact.url, "driver"), &driver.version), - &target_path, + &download_path, artifact.size, Some(CacheIdentity::Driver { db_type, version: &driver.version }), Some(db_type), @@ -913,16 +950,24 @@ async fn install_agent_driver_from_registry( total_drivers, ) .await?; + install_downloaded_driver_artifact( + &download_path, + &target_path, + artifact.format, + artifact_kind, + db_type, + &driver.version, + )?; // Some drivers publish both a native agent and a legacy JAR fallback. Only // validate the artifact type that was actually installed. - if !is_native_artifact && !am.is_driver_jar_valid(db_type) { - std::fs::remove_file(&target_path).ok(); - return Err(format!("Downloaded driver jar is invalid or corrupt: {}", target_path.display())); - } if is_native_artifact { mark_executable(&target_path)?; std::fs::remove_file(am.driver_jar_path(db_type)).ok(); } else { + if !am.is_driver_jar_valid(db_type) { + std::fs::remove_file(&target_path).ok(); + return Err(format!("Downloaded driver jar is invalid or corrupt: {}", target_path.display())); + } std::fs::remove_file(am.driver_native_path(db_type)).ok(); } @@ -947,6 +992,169 @@ async fn install_agent_driver_from_registry( Ok(()) } +fn driver_artifact_download_path(target_path: &Path, format: Option) -> PathBuf { + match format { + Some(ArtifactFormat::TarZstd) => { + let file_name = target_path.file_name().and_then(|name| name.to_str()).unwrap_or("agent"); + target_path.with_file_name(format!(".{file_name}.tar.zst")) + } + None => target_path.to_path_buf(), + } +} + +fn install_downloaded_driver_artifact( + download_path: &Path, + target_path: &Path, + format: Option, + artifact_kind: DriverArtifactKind, + db_type: &str, + expected_version: &str, +) -> Result<(), String> { + let Some(format) = format else { + return Ok(()); + }; + let result = match format { + ArtifactFormat::TarZstd => { + install_driver_from_tar_zstd_package(download_path, target_path, artifact_kind, db_type, expected_version) + } + }; + if result.is_ok() { + std::fs::remove_file(download_path).ok(); + } + result +} + +fn install_driver_from_tar_zstd_package( + package_path: &Path, + target_path: &Path, + expected_kind: DriverArtifactKind, + db_type: &str, + expected_version: &str, +) -> Result<(), String> { + let info = tar_zstd_driver_package_info(package_path)?; + if info.db_type != db_type { + return Err(format!("Driver package contains {}, expected {db_type}", info.db_type)); + } + if info.version != expected_version { + return Err(format!( + "Driver package version mismatch for {db_type}: expected {expected_version}, got {}", + info.version + )); + } + if info.kind != expected_kind { + return Err(format!( + "Driver package artifact type mismatch for {db_type}: expected {}, got {}", + expected_kind.label(), + info.kind.label() + )); + } + let parent = target_path.parent().ok_or_else(|| format!("Invalid driver path: {}", target_path.display()))?; + std::fs::create_dir_all(parent).map_err(|error| format!("Failed to create driver directory: {error}"))?; + let staging_path = parent.join(format!(".agent-package-{}", uuid::Uuid::new_v4())); + let result = extract_tar_zstd_file(package_path, &info.entry_name, &staging_path, info.size).and_then(|_| { + match info.kind { + DriverArtifactKind::Jar if !is_valid_agent_jar(&staging_path) => { + return Err(format!("Packaged driver jar is invalid or corrupt: {}", info.entry_name)); + } + DriverArtifactKind::Native => { + validate_native_agent_binary(&staging_path)?; + mark_executable(&staging_path)?; + } + DriverArtifactKind::Jar => {} + } + replace_download(&staging_path, target_path) + }); + if result.is_err() { + std::fs::remove_file(&staging_path).ok(); + } + result +} + +fn read_registry_from_tar_zstd(package_path: &Path) -> Result { + const MAX_REGISTRY_BYTES: u64 = 1024 * 1024; + + let file = std::fs::File::open(package_path).map_err(|error| format!("Failed to open driver package: {error}"))?; + let decoder = zstd::stream::read::Decoder::new(file) + .map_err(|error| format!("Failed to open zstd driver package: {error}"))?; + let mut archive = tar::Archive::new(decoder); + let entries = archive.entries().map_err(|error| format!("Invalid tar.zst driver package: {error}"))?; + for entry in entries { + let mut entry = entry.map_err(|error| format!("Invalid tar.zst driver package entry: {error}"))?; + let path = entry.path().map_err(|error| format!("Invalid driver package path: {error}"))?; + let name = safe_archive_entry_name(&path)?; + if name != "agent-registry.json" { + continue; + } + if !entry.header().entry_type().is_file() { + return Err("Driver package registry is not a regular file".to_string()); + } + if entry.size() > MAX_REGISTRY_BYTES { + return Err("Driver package registry is too large".to_string()); + } + let mut json = String::new(); + entry.read_to_string(&mut json).map_err(|error| format!("Failed to read driver package registry: {error}"))?; + return serde_json::from_str(&json).map_err(|error| format!("Invalid driver package registry: {error}")); + } + Err("agent-registry.json not found in the driver package".to_string()) +} + +fn extract_tar_zstd_file( + package_path: &Path, + expected_entry: &str, + destination: &Path, + expected_size: u64, +) -> Result<(), String> { + let file = std::fs::File::open(package_path).map_err(|error| format!("Failed to open driver package: {error}"))?; + let decoder = zstd::stream::read::Decoder::new(file) + .map_err(|error| format!("Failed to open zstd driver package: {error}"))?; + let mut archive = tar::Archive::new(decoder); + let entries = archive.entries().map_err(|error| format!("Invalid tar.zst driver package: {error}"))?; + let mut extracted = false; + for entry in entries { + let mut entry = entry.map_err(|error| format!("Invalid tar.zst driver package entry: {error}"))?; + let path = entry.path().map_err(|error| format!("Invalid driver package path: {error}"))?; + let name = safe_archive_entry_name(&path)?; + if name == "agent-registry.json" || entry.header().entry_type().is_dir() { + continue; + } + if name != expected_entry { + return Err(format!("Unexpected file in driver package: {name}")); + } + if extracted || !entry.header().entry_type().is_file() { + return Err(format!("Invalid driver package entry: {name}")); + } + let mut output = + std::fs::File::create(destination).map_err(|error| format!("Failed to create staged driver: {error}"))?; + let copied = std::io::copy(&mut entry, &mut output) + .map_err(|error| format!("Failed to extract driver package: {error}"))?; + std::io::Write::flush(&mut output).map_err(|error| format!("Failed to flush staged driver: {error}"))?; + if expected_size > 0 && copied != expected_size { + return Err(format!("Packaged driver size mismatch: expected {expected_size} bytes, got {copied} bytes")); + } + extracted = true; + } + if extracted { + Ok(()) + } else { + Err(format!("Driver package entry not found: {expected_entry}")) + } +} + +fn safe_archive_entry_name(path: &Path) -> Result { + let mut parts = Vec::new(); + for component in path.components() { + match component { + std::path::Component::Normal(value) => parts.push(value.to_string_lossy().into_owned()), + std::path::Component::CurDir => {} + _ => return Err(format!("Unsafe driver package path: {}", path.display())), + } + } + if parts.is_empty() { + return Err("Driver package contains an empty path".to_string()); + } + Ok(parts.join("/")) +} + fn agent_registry_driver<'a>( registry: &'a AgentRegistry, db_type: &str, @@ -1416,10 +1624,181 @@ pub struct OfflineImportPlan { pub includes_jre: bool, } -type OfflineJreEntry = (String, String); +type OfflineJreEntry = (String, String, Option); type OfflineDriverEntry = (String, String, bool); type OfflineArchiveEntries = (Vec, Vec); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DriverArtifactKind { + Jar, + Native, +} + +impl DriverArtifactKind { + fn label(self) -> &'static str { + match self { + Self::Jar => "jar", + Self::Native => "native", + } + } +} + +#[derive(Debug, Clone)] +struct TarZstdDriverPackageInfo { + db_type: String, + version: String, + jre: String, + kind: DriverArtifactKind, + entry_name: String, + size: u64, +} + +fn inspect_tar_zstd_driver_package(package_path: &Path) -> Result { + let info = tar_zstd_driver_package_info(package_path)?; + Ok(OfflineImportPlan { driver_keys: vec![info.db_type], includes_jre: false }) +} + +async fn import_tar_zstd_driver_package( + am: &AgentManager, + package_path: &Path, + progress: impl Fn(OfflineImportProgress), +) -> Result { + let _installation_guard = am.installation_operation_lock.write().await; + let info = tar_zstd_driver_package_info(package_path)?; + let mut result = + OfflineImportResult { jre_installed: Vec::new(), drivers_installed: Vec::new(), drivers_skipped: Vec::new() }; + if let Some(installed) = am.load_state().installed_drivers.get(&info.db_type) { + if installed.version != "0.1.0-local" + && installed.version != "local" + && !crate::update::is_newer_version(&info.version, &installed.version) + { + result.drivers_skipped.push(info.db_type); + return Ok(result); + } + } + + progress(OfflineImportProgress { + step: "driver".to_string(), + current: 1, + total: 1, + label: agent_catalog::label_for_key(&info.db_type).unwrap_or(&info.db_type).to_string(), + db_type: Some(info.db_type.clone()), + }); + let target_path = match info.kind { + DriverArtifactKind::Jar => am.driver_jar_path(&info.db_type), + DriverArtifactKind::Native => am.driver_native_path(&info.db_type), + }; + install_driver_from_tar_zstd_package(package_path, &target_path, info.kind, &info.db_type, &info.version)?; + match info.kind { + DriverArtifactKind::Jar => { + std::fs::remove_file(am.driver_native_path(&info.db_type)).ok(); + } + DriverArtifactKind::Native => { + std::fs::remove_file(am.driver_jar_path(&info.db_type)).ok(); + } + } + am.mutate_state(|state| { + state.installed_drivers.insert( + info.db_type.clone(), + InstalledDriver { + version: info.version.clone(), + installed_at: chrono::Utc::now().to_rfc3339(), + jre: info.jre.clone(), + }, + ); + })?; + am.stop_daemon_by_key(&info.db_type).await; + result.drivers_installed.push(info.db_type); + Ok(result) +} + +fn tar_zstd_driver_package_info(package_path: &Path) -> Result { + let registry = read_registry_from_tar_zstd(package_path)?; + if registry.drivers.len() != 1 { + return Err("A tar.zst driver package must contain exactly one driver".to_string()); + } + let (db_type, driver) = registry.drivers.iter().next().expect("checked one driver"); + validate_offline_driver_key(db_type)?; + let platform = AgentManager::current_platform(); + let native_artifact = driver.native.get(platform); + let jar_artifact = driver.jar.as_ref(); + let (kind, artifact) = match (native_artifact, jar_artifact) { + (Some(_), Some(_)) => { + return Err("A tar.zst driver package must contain exactly one driver artifact".to_string()); + } + (Some(artifact), None) => (DriverArtifactKind::Native, artifact), + (None, Some(artifact)) => (DriverArtifactKind::Jar, artifact), + (None, None) if !driver.native.is_empty() => { + return Err(format!("Driver package does not support platform: {platform}")); + } + (None, None) => return Err("A tar.zst driver package contains no driver artifact".to_string()), + }; + if artifact.format.is_some() { + return Err("Nested driver packages are not supported".to_string()); + } + let artifact_filename = artifact + .url + .rsplit('/') + .next() + .filter(|name| !name.is_empty()) + .ok_or_else(|| format!("Invalid packaged driver URL: {}", artifact.url))?; + let entry_name = format!("drivers/{artifact_filename}"); + validate_tar_zstd_package_entries(package_path, &entry_name, artifact.size)?; + Ok(TarZstdDriverPackageInfo { + db_type: db_type.clone(), + version: driver.version.clone(), + jre: driver.jre.clone(), + kind, + entry_name, + size: artifact.size, + }) +} + +fn validate_tar_zstd_package_entries( + package_path: &Path, + expected_entry: &str, + expected_size: u64, +) -> Result<(), String> { + let file = std::fs::File::open(package_path).map_err(|error| format!("Failed to open driver package: {error}"))?; + let decoder = zstd::stream::read::Decoder::new(file) + .map_err(|error| format!("Failed to open zstd driver package: {error}"))?; + let mut archive = tar::Archive::new(decoder); + let entries = archive.entries().map_err(|error| format!("Invalid tar.zst driver package: {error}"))?; + let mut registry_seen = false; + let mut driver_seen = false; + for entry in entries { + let entry = entry.map_err(|error| format!("Invalid tar.zst driver package entry: {error}"))?; + let path = entry.path().map_err(|error| format!("Invalid driver package path: {error}"))?; + let name = safe_archive_entry_name(&path)?; + if entry.header().entry_type().is_dir() { + continue; + } + if !entry.header().entry_type().is_file() { + return Err(format!("Driver package contains a non-regular entry: {name}")); + } + match name.as_str() { + "agent-registry.json" if !registry_seen => registry_seen = true, + value if value == expected_entry && !driver_seen => { + if expected_size > 0 && entry.size() != expected_size { + return Err(format!( + "Packaged driver size mismatch: expected {expected_size} bytes, got {} bytes", + entry.size() + )); + } + driver_seen = true; + } + _ => return Err(format!("Unexpected file in driver package: {name}")), + } + } + if !registry_seen { + return Err("agent-registry.json not found in the driver package".to_string()); + } + if !driver_seen { + return Err(format!("Driver package entry not found: {expected_entry}")); + } + Ok(()) +} + pub fn inspect_offline_zip(zip_path: &Path) -> Result { let file = std::fs::File::open(zip_path).map_err(|e| format!("Failed to open ZIP file: {e}"))?; let mut archive = zip::ZipArchive::new(file).map_err(|e| format!("Invalid ZIP file: {e}"))?; @@ -1461,7 +1840,7 @@ pub async fn import_offline_zip( validate_offline_driver_entries(am, &mut archive, &driver_entries)?; let mut current: u32 = 0; - for (jre_key, entry_name) in &jre_entries { + for (jre_key, entry_name, format) in &jre_entries { current += 1; let jre_version = registry.resolve_jre(jre_key).map(|j| j.version.clone()); let existing_version = local_state.jre_versions.get(jre_key); @@ -1478,7 +1857,7 @@ pub async fn import_offline_zip( }); let mut entry = archive.by_name(entry_name).map_err(|e| format!("Failed to read {entry_name}: {e}"))?; - let tmp_archive = am.base_dir().join(format!("jre-offline-{jre_key}.tar.gz")); + let tmp_archive = am.base_dir().join(format!("jre-offline-{jre_key}{}", jre_archive_suffix(*format))); { let mut out = std::fs::File::create(&tmp_archive).map_err(|e| format!("Failed to create temp file: {e}"))?; @@ -1487,7 +1866,7 @@ pub async fn import_offline_zip( let jre_dir = am.jre_dir(jre_key); let staging_dir = am.base_dir().join(format!(".jre-offline-import-{}", uuid::Uuid::new_v4())); - if let Err(error) = extract_tar_gz(&tmp_archive, &staging_dir) { + if let Err(error) = extract_jre_archive(&tmp_archive, &staging_dir, *format) { std::fs::remove_dir_all(&staging_dir).ok(); std::fs::remove_file(&tmp_archive).ok(); return Err(error); @@ -1599,7 +1978,7 @@ fn collect_offline_entries( registry: &AgentRegistry, ) -> Result { let platform = AgentManager::current_platform(); - let mut jre_entries = Vec::new(); + let mut jres = std::collections::BTreeMap::)>::new(); let mut drivers = std::collections::BTreeMap::::new(); for index in 0..archive.len() { @@ -1608,11 +1987,21 @@ fn collect_offline_entries( return Err(format!("Offline package contains an unsafe path: {}", entry.name())); }; let name = path.to_string_lossy().replace('\\', "/"); - if name.starts_with("jre/") && name.ends_with(".tar.gz") && name.contains(platform) { + if name.starts_with("jre/") && name.contains(platform) { + let jre_format = if name.ends_with(".tar.zst") { + Some(ArtifactFormat::TarZstd) + } else if name.ends_with(".tar.gz") { + None + } else { + continue; + }; let jre_key = extract_jre_key_from_filename(&name) .ok_or_else(|| format!("Invalid JRE filename in offline package: {name}"))?; validate_offline_identifier(&jre_key, "JRE")?; - jre_entries.push((jre_key, name)); + let replace = !jres.contains_key(&jre_key) || jre_format == Some(ArtifactFormat::TarZstd); + if replace { + jres.insert(jre_key, (name, jre_format)); + } } else if name.starts_with("drivers/") && name.ends_with(".jar") { let db_type = db_type_for_jar_offline_entry(registry, &name) .or_else(|| extract_db_type_from_filename(&name)) @@ -1629,7 +2018,10 @@ fn collect_offline_entries( } } - Ok((jre_entries, drivers.into_iter().map(|(db_type, (name, is_native))| (db_type, name, is_native)).collect())) + Ok(( + jres.into_iter().map(|(jre_key, (name, format))| (jre_key, name, format)).collect(), + drivers.into_iter().map(|(db_type, (name, is_native))| (db_type, name, is_native)).collect(), + )) } fn validate_offline_driver_entries( @@ -1723,7 +2115,36 @@ fn db_type_for_jar_offline_entry(registry: &AgentRegistry, name: &str) -> Option }) } +fn jre_archive_suffix(format: Option) -> &'static str { + match format { + Some(ArtifactFormat::TarZstd) => ".tar.zst", + None => ".tar.gz", + } +} + +fn jre_archive_download_path(am: &AgentManager, jre_key: &str, format: Option) -> PathBuf { + am.base_dir().join(format!("jre-{jre_key}-download{}", jre_archive_suffix(format))) +} + +fn extract_jre_archive(archive: &Path, dest: &Path, format: Option) -> Result<(), String> { + match format { + Some(ArtifactFormat::TarZstd) => { + let file = std::fs::File::open(archive).map_err(|e| format!("Failed to open JRE archive: {e}"))?; + let decoder = + zstd::stream::read::Decoder::new(file).map_err(|e| format!("Failed to open zstd JRE archive: {e}"))?; + extract_jre_tar(tar::Archive::new(decoder), dest) + } + None => extract_tar_gz(archive, dest), + } +} + fn extract_tar_gz(archive: &Path, dest: &Path) -> Result<(), String> { + let file = std::fs::File::open(archive).map_err(|e| format!("Failed to open JRE archive: {e}"))?; + let decoder = flate2::read::GzDecoder::new(file); + extract_jre_tar(tar::Archive::new(decoder), dest) +} + +fn extract_jre_tar(mut archive: tar::Archive, dest: &Path) -> Result<(), String> { let parent = dest.parent().ok_or_else(|| format!("Invalid JRE destination: {}", dest.display()))?; std::fs::create_dir_all(parent).map_err(|e| format!("Failed to create JRE directory: {e}"))?; @@ -1731,9 +2152,7 @@ fn extract_tar_gz(archive: &Path, dest: &Path) -> Result<(), String> { .prefix(".jre-extract-") .tempdir_in(parent) .map_err(|e| format!("Failed to create JRE extraction directory: {e}"))?; - let file = std::fs::File::open(archive).map_err(|e| format!("Failed to open JRE archive: {e}"))?; - let decoder = flate2::read::GzDecoder::new(file); - tar::Archive::new(decoder).unpack(staging.path()).map_err(|e| format!("Failed to extract JRE archive: {e}"))?; + archive.unpack(staging.path()).map_err(|e| format!("Failed to extract JRE archive: {e}"))?; let mut roots = std::fs::read_dir(staging.path()) .map_err(|e| format!("Failed to inspect extracted JRE archive: {e}"))? @@ -2090,7 +2509,7 @@ mod agent_download_url_tests { #[cfg(test)] mod agent_registry_install_tests { use super::*; - use crate::agent_manager::{ArtifactInfo, DriverInfo, InstalledDriver, JavaRuntimeConfig, JreInfo}; + use crate::agent_manager::{ArtifactFormat, ArtifactInfo, DriverInfo, InstalledDriver, JavaRuntimeConfig, JreInfo}; fn test_manager(name: &str) -> AgentManager { let dir = std::env::temp_dir().join(format!("dbx-agent-registry-install-{name}-{}", uuid::Uuid::new_v4())); @@ -2124,10 +2543,11 @@ mod agent_registry_install_tests { jar: Some(ArtifactInfo { url: format!("https://example.com/dbx-agent-{db_type}-legacy-placeholder.jar"), size: 0, + format: None, }), native: [( AgentManager::current_platform().to_string(), - ArtifactInfo { url: native_url.to_string(), size: native_size }, + ArtifactInfo { url: native_url.to_string(), size: native_size, format: None }, )] .into_iter() .collect(), @@ -2145,7 +2565,7 @@ mod agent_registry_install_tests { label: db_type.to_string(), min_app_version: "0.1.0".to_string(), jre: DEFAULT_JRE_KEY.to_string(), - jar: Some(ArtifactInfo { url: url.to_string(), size }), + jar: Some(ArtifactInfo { url: url.to_string(), size, format: None }), native: std::collections::HashMap::new(), }, ); @@ -2180,6 +2600,110 @@ mod agent_registry_install_tests { cache_path } + fn current_platform_native_binary() -> Vec { + if cfg!(windows) { + let mut bytes = vec![0_u8; 0x48]; + bytes[..2].copy_from_slice(b"MZ"); + bytes[0x3c..0x40].copy_from_slice(&(0x40_u32).to_le_bytes()); + bytes[0x40..0x44].copy_from_slice(b"PE\0\0"); + let machine = if cfg!(target_arch = "aarch64") { 0xaa64_u16 } else { 0x8664_u16 }; + bytes[0x44..0x46].copy_from_slice(&machine.to_le_bytes()); + bytes + } else if cfg!(target_os = "linux") { + let mut bytes = vec![0_u8; 20]; + bytes[..4].copy_from_slice(b"\x7fELF"); + bytes[4] = 2; + bytes[5] = 1; + let machine = if cfg!(target_arch = "aarch64") { 183_u16 } else { 62_u16 }; + bytes[18..20].copy_from_slice(&machine.to_le_bytes()); + bytes + } else if cfg!(target_os = "macos") { + let mut bytes = vec![0xcf, 0xfa, 0xed, 0xfe]; + let cpu_type = if cfg!(target_arch = "aarch64") { 0x0100_000c_u32 } else { 0x0100_0007_u32 }; + bytes.extend_from_slice(&cpu_type.to_le_bytes()); + bytes + } else { + Vec::new() + } + } + + fn test_agent_jar() -> Vec { + let mut bytes = std::io::Cursor::new(Vec::new()); + { + use std::io::Write; + let mut archive = zip::ZipWriter::new(&mut bytes); + archive.start_file("META-INF/MANIFEST.MF", zip::write::SimpleFileOptions::default()).unwrap(); + archive.write_all(b"Manifest-Version: 1.0\nMain-Class: com.dbx.Agent\n").unwrap(); + archive.finish().unwrap(); + } + bytes.into_inner() + } + + fn build_tar_zstd_driver_package( + db_type: &str, + version: &str, + kind: DriverArtifactKind, + driver_bytes: &[u8], + ) -> Vec { + let platform = AgentManager::current_platform(); + let (filename, artifact) = match kind { + DriverArtifactKind::Jar => ( + format!("dbx-agent-{db_type}-{version}.jar"), + serde_json::json!({ + "jar": { + "url": format!("dbx-agent-{db_type}-{version}.jar"), + "size": driver_bytes.len() + } + }), + ), + DriverArtifactKind::Native => { + let extension = if platform.starts_with("windows-") { ".exe" } else { "" }; + let filename = format!("dbx-agent-{db_type}-{version}-{platform}{extension}"); + ( + filename.clone(), + serde_json::json!({ + "native": { + platform: { + "url": filename, + "size": driver_bytes.len() + } + } + }), + ) + } + }; + let mut driver = serde_json::json!({ + "version": version, + "label": db_type, + "min_app_version": "0.6.0", + "jre": DEFAULT_JRE_KEY + }); + driver.as_object_mut().unwrap().extend(artifact.as_object().unwrap().clone()); + let registry = serde_json::json!({ + "jres": {}, + "drivers": { + db_type: driver + } + }); + let registry_bytes = registry.to_string().into_bytes(); + let encoder = zstd::stream::write::Encoder::new(Vec::new(), 3).unwrap(); + let mut archive = tar::Builder::new(encoder); + + let mut registry_header = tar::Header::new_gnu(); + registry_header.set_size(registry_bytes.len() as u64); + registry_header.set_mode(0o644); + registry_header.set_cksum(); + archive.append_data(&mut registry_header, "agent-registry.json", registry_bytes.as_slice()).unwrap(); + + let mut driver_header = tar::Header::new_gnu(); + driver_header.set_size(driver_bytes.len() as u64); + driver_header.set_mode(if kind == DriverArtifactKind::Native { 0o755 } else { 0o644 }); + driver_header.set_cksum(); + archive.append_data(&mut driver_header, format!("drivers/{filename}"), driver_bytes).unwrap(); + + archive.into_inner().unwrap().finish().unwrap() + } + #[test] fn managed_jre_content_revision_triggers_install() { let manager = test_manager("jre-content-revision"); @@ -2208,7 +2732,7 @@ mod agent_registry_install_tests { version: version.to_string(), platforms: [( AgentManager::current_platform().to_string(), - ArtifactInfo { url: url.to_string(), size }, + ArtifactInfo { url: url.to_string(), size, format: None }, )] .into_iter() .collect(), @@ -2234,8 +2758,29 @@ mod agent_registry_install_tests { builder.into_inner().unwrap().finish().unwrap() } - fn write_cached_jre_download(am: &AgentManager, jre_key: &str, version: &str, url: &str, archive: &[u8]) { - let dest = am.base_dir().join(format!("jre-{jre_key}-download.tar.gz")); + fn build_zstd_jre_archive(am: &AgentManager, jre_key: &str) -> Vec { + let archive_root = am.base_dir().join("jre-zstd-test-archive"); + let payload = archive_root.join("payload"); + let java_path = am.jre_java_path(jre_key); + let relative_java_path = java_path.strip_prefix(am.jre_dir(jre_key)).unwrap(); + let java_path = payload.join(relative_java_path); + std::fs::create_dir_all(java_path.parent().unwrap()).unwrap(); + std::fs::write(java_path, b"java").unwrap(); + let encoder = zstd::stream::write::Encoder::new(Vec::new(), 3).unwrap(); + let mut builder = tar::Builder::new(encoder); + builder.append_dir_all("payload", &payload).unwrap(); + builder.into_inner().unwrap().finish().unwrap() + } + + fn write_cached_jre_download( + am: &AgentManager, + jre_key: &str, + version: &str, + url: &str, + format: Option, + archive: &[u8], + ) { + let dest = jre_archive_download_path(am, jre_key, format); let cache_path = cached_download_path( am, url, @@ -2284,6 +2829,81 @@ mod agent_registry_install_tests { .any(|event| event.step == "done" && event.db_type.as_deref() == Some(db_type))); } + #[tokio::test] + async fn registry_install_extracts_tar_zstd_native_driver_package() { + let manager = test_manager("tar-zstd-native-package"); + let db_type = "duckdb"; + let version = "0.1.0"; + let package_url = "https://example.com/dbx-agent-duckdb.tar.zst"; + let native_bytes = current_platform_native_binary(); + let package_bytes = build_tar_zstd_driver_package(db_type, version, DriverArtifactKind::Native, &native_bytes); + let mut registry = + registry_with_native_and_legacy_jar(db_type, version, package_url, package_bytes.len() as u64); + registry.drivers.get_mut(db_type).unwrap().native.get_mut(AgentManager::current_platform()).unwrap().format = + Some(ArtifactFormat::TarZstd); + let native_path = manager.driver_native_path(db_type); + let package_path = driver_artifact_download_path(&native_path, Some(ArtifactFormat::TarZstd)); + let cache_path = + write_cached_driver_download(&manager, db_type, version, package_url, &package_path, &package_bytes); + let progress = |_| {}; + + install_agent_driver_from_registry( + &manager, + ®istry, + DownloadSource::Official, + db_type, + &progress, + None, + None, + ) + .await + .unwrap(); + + assert_eq!(std::fs::read(&native_path).unwrap(), native_bytes); + assert!(!package_path.exists()); + assert!(!cache_path.exists()); + assert_eq!(manager.load_state().installed_drivers[db_type].version, version); + } + + #[tokio::test] + async fn registry_install_extracts_tar_zstd_java_driver_package() { + let manager = test_manager("tar-zstd-java-package"); + let db_type = "dameng"; + let version = "0.2.0"; + let package_url = "https://example.com/dbx-agent-dameng.tar.zst"; + let jar_bytes = test_agent_jar(); + let package_bytes = build_tar_zstd_driver_package(db_type, version, DriverArtifactKind::Jar, &jar_bytes); + let mut registry = registry_with_jar(db_type, version, package_url, package_bytes.len() as u64); + registry.drivers.get_mut(db_type).unwrap().jar.as_mut().unwrap().format = Some(ArtifactFormat::TarZstd); + manager + .mutate_state(|state| { + state.java_runtime = JavaRuntimeConfig { mode: JavaRuntimeMode::System, custom_java_path: None }; + }) + .unwrap(); + let jar_path = manager.driver_jar_path(db_type); + let package_path = driver_artifact_download_path(&jar_path, Some(ArtifactFormat::TarZstd)); + let cache_path = + write_cached_driver_download(&manager, db_type, version, package_url, &package_path, &package_bytes); + let progress = |_| {}; + + install_agent_driver_from_registry( + &manager, + ®istry, + DownloadSource::Official, + db_type, + &progress, + None, + None, + ) + .await + .unwrap(); + + assert_eq!(std::fs::read(&jar_path).unwrap(), jar_bytes); + assert!(!package_path.exists()); + assert!(!cache_path.exists()); + assert_eq!(manager.load_state().installed_drivers[db_type].version, version); + } + #[tokio::test] async fn batch_upgrade_preserves_successful_state_and_reports_independent_failure() { let manager = test_manager("batch-upgrade"); @@ -2367,7 +2987,7 @@ mod agent_registry_install_tests { let events = std::sync::Mutex::new(Vec::new()); let progress = |event| events.lock().unwrap().push(event); - write_cached_jre_download(&manager, jre_key, version, url, &archive); + write_cached_jre_download(&manager, jre_key, version, url, None, &archive); ensure_jre_from_registry( &manager, ®istry, @@ -2383,7 +3003,7 @@ mod agent_registry_install_tests { // The successful install cleans its cache. Re-add it so the old // implementation fails deterministically by consuming it again. - write_cached_jre_download(&manager, jre_key, version, url, &archive); + write_cached_jre_download(&manager, jre_key, version, url, None, &archive); ensure_jre_from_registry( &manager, ®istry, @@ -2410,6 +3030,26 @@ mod agent_registry_install_tests { .any(|event| event.step == "jre" && event.db_type.as_deref() == Some("dameng"))); } + #[tokio::test] + async fn managed_jre_install_extracts_tar_zstd_archive() { + let manager = test_manager("managed-zstd-jre"); + let jre_key = DEFAULT_JRE_KEY; + let version = "21.0.12"; + let url = "https://example.com/dbx-jre.tar.zst"; + let archive = build_zstd_jre_archive(&manager, jre_key); + let mut registry = registry_with_jre(jre_key, version, url, archive.len() as u64); + registry.jres.get_mut(jre_key).unwrap().platforms.get_mut(AgentManager::current_platform()).unwrap().format = + Some(ArtifactFormat::TarZstd); + write_cached_jre_download(&manager, jre_key, version, url, Some(ArtifactFormat::TarZstd), &archive); + + ensure_jre_from_registry(&manager, ®istry, DownloadSource::Official, jre_key, "dameng", &|_| {}, None, None) + .await + .unwrap(); + + assert!(manager.is_jre_installed(jre_key)); + assert_eq!(manager.load_state().jre_versions[jre_key], version); + } + #[tokio::test] async fn stash_is_recorded_before_jre_extraction() { let manager = test_manager("stash-before-extract"); diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index d445581bc0..6a6e62cb27 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -4,8 +4,6 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::{watch, Mutex, RwLock}; -#[cfg(feature = "duckdb-bundled")] -use tokio::task::JoinHandle; use mysql_async::prelude::Queryable; use mysql_async::Row as MysqlRow; @@ -42,21 +40,16 @@ const ACCESS_AGENT_CONNECT_TIMEOUT_SECS: u64 = 30; const POOL_CLOSE_TIMEOUT_SECS: u64 = 3; const HEALTH_CHECK_POOL_ACQUIRE_TIMEOUT: Duration = Duration::from_millis(500); -#[cfg(feature = "duckdb-bundled")] mod duckdb_types { + #[cfg(feature = "duckdb-sidecar")] use std::sync::Arc; - pub type DuckDbHandle = Arc; + #[cfg(feature = "duckdb-sidecar")] pub type DuckDbWorkerHandle = Arc; - pub type ExternalTabularHandle = Arc; -} -#[cfg(not(feature = "duckdb-bundled"))] -mod duckdb_types { - pub type DuckDbHandle = (); + #[cfg(not(feature = "duckdb-sidecar"))] pub type DuckDbWorkerHandle = (); - pub type ExternalTabularHandle = (); } -use duckdb_types::{DuckDbHandle, DuckDbWorkerHandle, ExternalTabularHandle}; +use duckdb_types::DuckDbWorkerHandle; #[derive(Debug, Clone, Copy, PartialEq)] pub enum MysqlMode { @@ -100,7 +93,6 @@ pub enum PoolKind { Turso(db::turso_driver::TursoClient), CloudflareD1(db::cloudflare_d1_driver::CloudflareD1Client), Redis(db::redis_driver::RedisConnection), - DuckDb(DuckDbHandle), DuckDbWorker(DuckDbWorkerHandle), MongoDb(mongodb::Client), ClickHouse(db::clickhouse_driver::ChClient), @@ -110,7 +102,6 @@ pub enum PoolKind { VectorDb(db::vector_driver::VectorClient), InfluxDb(db::influxdb_driver::InfluxdbClient), Agent(Arc>), - ExternalTabular(ExternalTabularHandle), ExternalDriver { driver_id: String, config: Arc, @@ -690,41 +681,44 @@ impl AppState { } } - #[cfg(feature = "duckdb-bundled")] - async fn create_duckdb_pool(&self, config: &ConnectionConfig) -> Result { - if self.duckdb_worker_process_isolation.load(Ordering::Relaxed) { - let attached_databases = config - .attached_databases - .iter() - .map(|attached| crate::models::connection::AttachedDatabaseConfig { - name: attached.name.clone(), - path: expand_tilde(&attached.path), - }) - .collect(); - let client = db::duckdb_worker_process::DuckDbWorkerClient::open_with_process_limit( - expand_tilde(&config.host), + #[cfg(feature = "duckdb-sidecar")] + pub async fn create_duckdb_pool(&self, config: &ConnectionConfig) -> Result { + let attached_databases = config + .attached_databases + .iter() + .map(|attached| crate::models::connection::AttachedDatabaseConfig { + name: attached.name.clone(), + path: expand_tilde(&attached.path), + }) + .collect(); + let path = expand_tilde(&config.host); + let init_script = config.init_script.clone(); + let process_limit = self.duckdb_worker_max_processes.load(Ordering::Relaxed); + let installed_driver = self.agent_manager.driver_native_path("duckdb"); + let has_driver_override = + std::env::var_os(db::duckdb_worker_process::DUCKDB_DRIVER_PATH_ENV).is_some_and(|value| !value.is_empty()); + let client = if installed_driver.is_file() && !has_driver_override { + db::duckdb_worker_process::DuckDbWorkerClient::open_with_executable_and_process_limit( + installed_driver, + path, attached_databases, - config.init_script.clone(), - self.duckdb_worker_max_processes.load(Ordering::Relaxed), + init_script, + process_limit, ) - .await?; - Ok(PoolKind::DuckDbWorker(Arc::new(client))) + .await? } else { - let con = db::duckdb_driver::connect_path(&expand_tilde(&config.host))?; - { - let locked = con.lock().map_err(|e| e.to_string())?; - for attached in &config.attached_databases { - crate::schema::duckdb_attach_database(&locked, &attached.name, &expand_tilde(&attached.path))?; - } - if let Some(script) = config.init_script.as_deref() { - db::duckdb_driver::run_init_script(&locked, script)?; - } - } - Ok(PoolKind::DuckDb(con)) - } + db::duckdb_worker_process::DuckDbWorkerClient::open_with_process_limit( + path, + attached_databases, + init_script, + process_limit, + ) + .await? + }; + Ok(PoolKind::DuckDbWorker(Arc::new(client))) } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] pub async fn test_duckdb_connection_config(&self, config: &ConnectionConfig) -> Result<(), String> { // Test the submitted form as a fresh session so unsaved ATTACH/init // changes cannot be masked by a pool created from older settings. @@ -1315,11 +1309,11 @@ impl AppState { }; PoolKind::Redis(con) } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] DatabaseType::DuckDb => self.create_duckdb_pool(&db_config).await?, - #[cfg(not(feature = "duckdb-bundled"))] + #[cfg(not(feature = "duckdb-sidecar"))] DatabaseType::DuckDb => { - return Err("DuckDB support is not compiled in this build. Rebuild with default features.".to_string()); + return Err("DuckDB support is not compiled in this build.".to_string()); } DatabaseType::MongoDb => { if mongo_uses_legacy_driver(&db_config) { @@ -2342,9 +2336,7 @@ impl AppState { } } PoolKind::Sqlite(_) - | PoolKind::DuckDb(_) | PoolKind::DuckDbWorker(_) - | PoolKind::ExternalTabular(_) | PoolKind::ExternalDriver { .. } | PoolKind::MessageQueue | PoolKind::Nacos => false, @@ -2567,123 +2559,10 @@ impl AppState { } } - #[cfg(feature = "duckdb-bundled")] - async fn remove_pool_by_key_detached(&self, pool_key: &str) -> bool { - self.stop_keepalive_task(pool_key).await; - self.pool_activity.write().await.remove(pool_key); - self.postgres_cancel_contexts.write().await.remove(pool_key); - let removed = self.connections.write().await.remove(pool_key); - if let Some(pool) = removed { - close_removed_pools_in_background(&self.task_supervisor, vec![(pool_key.to_string(), pool)]); - true - } else { - false - } - } - - #[cfg(feature = "duckdb-bundled")] - async fn remove_pool_if_duckdb_isolation_mismatch(&self, pool_key: &str) -> bool { - let isolation_enabled = self.duckdb_worker_process_isolation.load(Ordering::Relaxed); - let mismatch = { - let connections = self.connections.read().await; - match connections.get(pool_key) { - Some(PoolKind::DuckDb(_)) => isolation_enabled, - Some(PoolKind::DuckDbWorker(_)) => !isolation_enabled, - _ => false, - } - }; - if mismatch { - self.remove_pool_by_key_detached(pool_key).await - } else { - false - } - } - - #[cfg(not(feature = "duckdb-bundled"))] async fn remove_pool_if_duckdb_isolation_mismatch(&self, _pool_key: &str) -> bool { false } - #[cfg(feature = "duckdb-bundled")] - pub fn spawn_duckdb_pool_cleanup(&self, pool_key: String, con: DuckDbHandle) { - let connections = self.connections.clone(); - let supervisor = self.task_supervisor.clone(); - let pool_activity = self.pool_activity.clone(); - let postgres_cancel_contexts = self.postgres_cancel_contexts.clone(); - let task_key = format!("duckdb-cleanup:{pool_key}"); - supervisor.clone().spawn_replace(task_key, move |shutdown| async move { - while Arc::strong_count(&con) > 2 { - tokio::select! { - _ = shutdown.cancelled() => return, - _ = tokio::time::sleep(Duration::from_millis(50)) => {} - } - } - supervisor.stop(&format!("keepalive:{pool_key}")); - pool_activity.write().await.remove(&pool_key); - postgres_cancel_contexts.write().await.remove(&pool_key); - let removed = { - let mut conns = connections.write().await; - match conns.get(&pool_key) { - Some(PoolKind::DuckDb(current)) if Arc::ptr_eq(current, &con) => conns.remove(&pool_key), - _ => None, - } - }; - if let Some(pool) = removed { - // Keep the old DuckDB pool marked as draining until it is no longer - // visible in the pool map, otherwise a concurrent query could reuse it. - con.clear_draining(); - drop(con); - close_pool_kind_with_timeout(pool_key, pool).await; - } - }); - } - - #[cfg(feature = "duckdb-bundled")] - pub fn spawn_duckdb_draining_cleanup( - &self, - pool_key: String, - con: DuckDbHandle, - mut task: JoinHandle>, - ) { - let connections = self.connections.clone(); - let supervisor = self.task_supervisor.clone(); - let pool_activity = self.pool_activity.clone(); - let postgres_cancel_contexts = self.postgres_cancel_contexts.clone(); - let task_key = format!("duckdb-draining:{pool_key}"); - supervisor.clone().spawn_replace(task_key, move |shutdown| async move { - tokio::select! { - _ = shutdown.cancelled() => { - task.abort(); - return; - } - _ = &mut task => {} - } - while Arc::strong_count(&con) > 2 { - tokio::select! { - _ = shutdown.cancelled() => return, - _ = tokio::time::sleep(Duration::from_millis(50)) => {} - } - } - supervisor.stop(&format!("keepalive:{pool_key}")); - pool_activity.write().await.remove(&pool_key); - postgres_cancel_contexts.write().await.remove(&pool_key); - let removed = { - let mut conns = connections.write().await; - match conns.get(&pool_key) { - Some(PoolKind::DuckDb(current)) if Arc::ptr_eq(current, &con) => conns.remove(&pool_key), - _ => None, - } - }; - if let Some(pool) = removed { - // Keep the old DuckDB pool marked as draining until it is no longer - // visible in the pool map, otherwise a concurrent query could reuse it. - con.clear_draining(); - drop(con); - close_pool_kind_with_timeout(pool_key, pool).await; - } - }); - } - pub async fn close_database_pool(&self, connection_id: &str, database: Option<&str>) -> Result { let db_type = { let configs = self.configs.read().await; @@ -2732,6 +2611,11 @@ impl AppState { let mut keys = HashSet::new(); for (pool_key, pool) in connections.iter() { + #[cfg(feature = "duckdb-sidecar")] + if matches!(pool, PoolKind::DuckDbWorker(_)) { + keys.insert("duckdb".to_string()); + continue; + } if !matches!(pool, PoolKind::Agent(_)) { continue; } @@ -3092,9 +2976,7 @@ impl AppState { } } PoolKind::Sqlite(_) - | PoolKind::DuckDb(_) | PoolKind::DuckDbWorker(_) - | PoolKind::ExternalTabular(_) | PoolKind::ExternalDriver { .. } | PoolKind::MessageQueue | PoolKind::Nacos => true, @@ -3162,13 +3044,13 @@ impl AppState { close_removed_pools_in_background(&self.task_supervisor, removed); } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] async fn remove_duckdb_pools_detached(&self) { let removed = self.drain_duckdb_pools().await; close_removed_pools_in_background(&self.task_supervisor, removed); } - #[cfg(not(feature = "duckdb-bundled"))] + #[cfg(not(feature = "duckdb-sidecar"))] async fn remove_duckdb_pools_detached(&self) {} pub async fn remove_external_driver_pools(&self, driver_id: &str) { @@ -3209,16 +3091,16 @@ impl AppState { removed } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] async fn drain_duckdb_pools(&self) -> Vec<(String, PoolKind)> { let keys_to_remove: Vec = self .connections .read() .await .iter() - .filter_map(|(key, pool)| match pool { - PoolKind::DuckDb(_) | PoolKind::DuckDbWorker(_) => Some(key.clone()), - _ => None, + .filter_map(|(key, pool)| { + let is_duckdb = matches!(pool, PoolKind::DuckDbWorker(_)); + is_duckdb.then(|| key.clone()) }) .collect(); self.stop_keepalive_tasks(&keys_to_remove).await; @@ -3516,13 +3398,9 @@ fn clone_pool_kind(pool: &PoolKind) -> PoolKind { PoolKind::Rqlite(client) => PoolKind::Rqlite(client.clone()), PoolKind::Turso(client) => PoolKind::Turso(client.clone()), PoolKind::CloudflareD1(client) => PoolKind::CloudflareD1(client.clone()), - #[cfg(feature = "duckdb-bundled")] - PoolKind::DuckDb(con) => PoolKind::DuckDb(con.clone()), - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] PoolKind::DuckDbWorker(client) => PoolKind::DuckDbWorker(client.clone()), - #[cfg(not(feature = "duckdb-bundled"))] - PoolKind::DuckDb(_) => PoolKind::DuckDb(()), - #[cfg(not(feature = "duckdb-bundled"))] + #[cfg(not(feature = "duckdb-sidecar"))] PoolKind::DuckDbWorker(_) => PoolKind::DuckDbWorker(()), PoolKind::MongoDb(client) => PoolKind::MongoDb(client.clone()), PoolKind::ClickHouse(client) => PoolKind::ClickHouse(client.clone()), @@ -3532,10 +3410,6 @@ fn clone_pool_kind(pool: &PoolKind) -> PoolKind { PoolKind::VectorDb(client) => PoolKind::VectorDb(client.clone()), PoolKind::InfluxDb(client) => PoolKind::InfluxDb(client.clone()), PoolKind::Agent(client) => PoolKind::Agent(client.clone()), - #[cfg(feature = "duckdb-bundled")] - PoolKind::ExternalTabular(ext) => PoolKind::ExternalTabular(ext.clone()), - #[cfg(not(feature = "duckdb-bundled"))] - PoolKind::ExternalTabular(_) => PoolKind::ExternalTabular(()), PoolKind::ExternalDriver { driver_id, config, session } => { PoolKind::ExternalDriver { driver_id: driver_id.clone(), config: config.clone(), session: session.clone() } } @@ -3558,17 +3432,11 @@ pub async fn close_pool_kind(pool: PoolKind) { PoolKind::Redis(conn) => { drop(conn); } - #[cfg(feature = "duckdb-bundled")] - PoolKind::DuckDb(con) => { - crate::db::duckdb_driver::close_connection(con); - } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] PoolKind::DuckDbWorker(client) => { client.shutdown().await; } - #[cfg(not(feature = "duckdb-bundled"))] - PoolKind::DuckDb(_) => {} - #[cfg(not(feature = "duckdb-bundled"))] + #[cfg(not(feature = "duckdb-sidecar"))] PoolKind::DuckDbWorker(_) => {} PoolKind::MongoDb(client) => { drop(client); @@ -3595,7 +3463,6 @@ pub async fn close_pool_kind(pool: PoolKind) { let mut client = client.lock().await; let _ = client.disconnect().await; } - PoolKind::ExternalTabular(_) => {} PoolKind::ExternalDriver { session, .. } => { session.shutdown().await; } @@ -5108,51 +4975,6 @@ mod tests { let _ = std::fs::remove_dir_all(dir); } - #[cfg(feature = "duckdb-bundled")] - #[tokio::test] - async fn duckdb_connection_test_does_not_reuse_stale_pool_config() { - let (state, dir) = test_app_state().await; - let db_path = dir.join("app.duckdb"); - duckdb::Connection::open(&db_path).unwrap(); - let mut config = mysql_config(None); - config.id = "duckdb-conn".to_string(); - config.name = "DuckDB".to_string(); - config.db_type = DatabaseType::DuckDb; - config.host = db_path.to_string_lossy().to_string(); - config.port = 0; - - state.configs.write().await.insert(config.id.clone(), config.clone()); - state.get_or_create_pool("duckdb-conn", None).await.unwrap(); - - config.init_script = Some("SELECT definitely_invalid_syntax(".to_string()); - let error = state.test_duckdb_connection_config(&config).await.expect_err("invalid submitted script must fail"); - - assert!(error.contains("Connection init script statement 1 failed")); - - let _ = std::fs::remove_dir_all(dir); - } - - #[cfg(feature = "duckdb-bundled")] - #[tokio::test] - async fn duckdb_connection_test_validates_attached_databases() { - let (state, dir) = test_app_state().await; - let mut config = mysql_config(None); - config.id = "duckdb-conn".to_string(); - config.name = "DuckDB".to_string(); - config.db_type = DatabaseType::DuckDb; - config.host = ":memory:".to_string(); - config.port = 0; - config.attached_databases.push(AttachedDatabaseConfig { - name: "missing".to_string(), - path: dir.join("missing-parent").join("missing.duckdb").to_string_lossy().to_string(), - }); - - let error = state.test_duckdb_connection_config(&config).await.expect_err("invalid attach must fail"); - - assert!(error.to_ascii_lowercase().contains("attach")); - let _ = std::fs::remove_dir_all(dir); - } - #[tokio::test] async fn sqlite_connection_restores_attached_databases() { let (state, dir) = test_app_state().await; @@ -5189,72 +5011,6 @@ mod tests { let _ = std::fs::remove_dir_all(dir); } - #[cfg(feature = "duckdb-bundled")] - #[tokio::test] - async fn duckdb_connection_test_supports_memory_bootstrap() { - let (state, dir) = test_app_state().await; - let mut config = mysql_config(None); - config.id = "duckdb-memory".to_string(); - config.name = "DuckDB memory".to_string(); - config.db_type = DatabaseType::DuckDb; - config.host = ":memory:".to_string(); - config.port = 0; - config.init_script = Some(r#"CREATE TABLE probe AS SELECT E'it\'s;ok' AS value;"#.to_string()); - - state.test_duckdb_connection_config(&config).await.expect("memory bootstrap succeeds"); - - let _ = std::fs::remove_dir_all(dir); - } - - #[cfg(feature = "duckdb-bundled")] - #[tokio::test] - async fn applying_duckdb_worker_isolation_drops_existing_duckdb_pools() { - let (state, dir) = test_app_state().await; - let db_path = dir.join("app.duckdb"); - duckdb::Connection::open(&db_path).unwrap(); - let mut config = mysql_config(None); - config.id = "duckdb-conn".to_string(); - config.name = "DuckDB".to_string(); - config.db_type = DatabaseType::DuckDb; - config.host = db_path.to_string_lossy().to_string(); - config.port = 0; - - state.configs.write().await.insert(config.id.clone(), config); - state.get_or_create_pool("duckdb-conn", None).await.unwrap(); - assert!(matches!(state.connections.read().await.get("duckdb-conn"), Some(PoolKind::DuckDb(_)))); - - state.apply_duckdb_worker_process_isolation(true).await; - - assert!(!state.connections.read().await.contains_key("duckdb-conn")); - - let _ = std::fs::remove_dir_all(dir); - } - - #[cfg(feature = "duckdb-bundled")] - #[tokio::test] - async fn duckdb_pool_mode_mismatch_removes_existing_pool() { - let (state, dir) = test_app_state().await; - let db_path = dir.join("app.duckdb"); - duckdb::Connection::open(&db_path).unwrap(); - let mut config = mysql_config(None); - config.id = "duckdb-conn".to_string(); - config.name = "DuckDB".to_string(); - config.db_type = DatabaseType::DuckDb; - config.host = db_path.to_string_lossy().to_string(); - config.port = 0; - - state.configs.write().await.insert(config.id.clone(), config); - state.get_or_create_pool("duckdb-conn", None).await.unwrap(); - assert!(matches!(state.connections.read().await.get("duckdb-conn"), Some(PoolKind::DuckDb(_)))); - - state.set_duckdb_worker_process_isolation_enabled(true); - - assert!(state.remove_pool_if_duckdb_isolation_mismatch("duckdb-conn").await); - assert!(!state.connections.read().await.contains_key("duckdb-conn")); - - let _ = std::fs::remove_dir_all(dir); - } - #[tokio::test] async fn remove_connection_pools_clears_base_and_database_scoped_pools() { let (state, dir) = test_app_state().await; @@ -5519,33 +5275,6 @@ for line in sys.stdin: assert!(!super::is_agent_validate_connection_unsupported("Agent RPC error (-1): Unknown method: kv_put")); } - #[cfg(feature = "duckdb-bundled")] - #[tokio::test] - async fn duckdb_client_session_reuses_base_pool_to_avoid_file_locks() { - let (state, dir) = test_app_state().await; - let db_path = dir.join("session.duckdb"); - duckdb::Connection::open(&db_path).unwrap(); - let mut config = mysql_config(None); - config.id = "duckdb-conn".to_string(); - config.name = "DuckDB".to_string(); - config.db_type = DatabaseType::DuckDb; - config.host = db_path.to_string_lossy().to_string(); - config.port = 0; - - state.configs.write().await.insert(config.id.clone(), config.clone()); - let base_pool_key = state.get_or_create_pool("duckdb-conn", None).await.unwrap(); - let pool_key = state.get_or_create_pool_for_session("duckdb-conn", Some("main"), Some("tab-1")).await.unwrap(); - assert_eq!(pool_key, base_pool_key); - - assert!(!state.close_client_session_pool("duckdb-conn", Some("main"), "tab-1").await.unwrap()); - - let conns = state.connections.read().await; - assert!(conns.contains_key("duckdb-conn")); - assert!(!conns.contains_key("duckdb-conn:session:tab-1")); - - let _ = std::fs::remove_dir_all(dir); - } - #[tokio::test] async fn sqlite_memory_client_sessions_share_the_same_database() { let (state, dir) = test_app_state().await; diff --git a/crates/dbx-core/src/db/duckdb_sql.rs b/crates/dbx-core/src/db/duckdb_sql.rs index b65b98381c..27155425ae 100644 --- a/crates/dbx-core/src/db/duckdb_sql.rs +++ b/crates/dbx-core/src/db/duckdb_sql.rs @@ -1,7 +1,7 @@ //! Quote-aware helpers for inspecting DuckDB SQL snippets without a full //! parser. Shared by the DuckDB worker (execute-time ATTACH tracking) and the -//! connection layer (init-script handling), and kept free of `duckdb-bundled` -//! so config-derived metadata works in every build. +//! connection layer (init-script handling). It stays independent of the native +//! DuckDB engine so config-derived metadata remains in the main application. use crate::models::connection::ConnectionConfig; @@ -130,7 +130,7 @@ pub fn attached_name_from_attach_sql(sql: &str) -> Option { parse_identifier_after_as(&trimmed[as_index + 2..]) } -pub(crate) fn strip_leading_comments(sql: &str) -> &str { +pub fn strip_leading_comments(sql: &str) -> &str { let mut rest = sql.trim_start(); loop { if let Some(after) = rest.strip_prefix("--") { diff --git a/crates/dbx-core/src/db/duckdb_worker_process.rs b/crates/dbx-core/src/db/duckdb_worker_process.rs index ecce7e9c2e..b0b8417b30 100644 --- a/crates/dbx-core/src/db/duckdb_worker_process.rs +++ b/crates/dbx-core/src/db/duckdb_worker_process.rs @@ -1,7 +1,8 @@ -#![cfg(feature = "duckdb-bundled")] +#![cfg(feature = "duckdb-sidecar")] use std::collections::HashMap; -use std::path::PathBuf; +use std::ffi::OsString; +use std::path::{Path, PathBuf}; use std::process::Stdio; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex as StdMutex, OnceLock}; @@ -14,20 +15,21 @@ use tokio_util::sync::CancellationToken; use crate::db; use crate::db::duckdb_worker_protocol::{ - DuckDbWorkerConnectParams, DuckDbWorkerError, DuckDbWorkerExecuteParams, DuckDbWorkerMethod, - DuckDbWorkerObjectSourceParams, DuckDbWorkerRequest, DuckDbWorkerResponse, + DuckDbWorkerColumnParams, DuckDbWorkerConnectParams, DuckDbWorkerError, DuckDbWorkerExecuteParams, + DuckDbWorkerMethod, DuckDbWorkerObjectSourceParams, DuckDbWorkerRequest, DuckDbWorkerResponse, }; use crate::models::connection::AttachedDatabaseConfig; use crate::storage::{normalize_duckdb_worker_max_processes, DUCKDB_WORKER_MAX_PROCESSES_DEFAULT}; /// Error code the worker reports when a query error left the DuckDB connection poisoned /// (see the duckdb-rs Parser Error bug). The client kills the worker on this code so the -/// next request starts a fresh one. Must match the code emitted in `duckdb_worker_runtime`. +/// next request starts a fresh one. Must match the code emitted by the standalone driver runtime. const DUCKDB_WORKER_POISONED_CODE: &str = "duckdb_worker_poisoned"; const DUCKDB_WORKER_REQUEST_TIMEOUT_CODE: &str = "duckdb_worker_request_timeout"; const DEFAULT_WORKER_REQUEST_TIMEOUT: Duration = Duration::from_secs(60); const DEFAULT_WORKER_KILL_WAIT: Duration = Duration::from_secs(3); const DEFAULT_WORKER_START_WAIT: Duration = Duration::from_secs(5); +pub const DUCKDB_DRIVER_PATH_ENV: &str = "DBX_DUCKDB_DRIVER_PATH"; type PendingRequests = Arc>>; struct PendingRequest { @@ -48,6 +50,7 @@ struct DuckDbWorkerClientInner { process_limiter: Arc, process_limit: usize, executable: PathBuf, + executable_args: Vec, connect_params: DuckDbWorkerConnectParams, request_timeout: Duration, worker_start_timeout: Duration, @@ -68,8 +71,16 @@ impl DuckDbWorkerClient { attached_databases: Vec, init_script: Option, ) -> Result { - let executable = std::env::current_exe().map_err(|e| e.to_string())?; - Self::open_with_executable(executable, path, attached_databases, init_script).await + let (executable, executable_args) = resolve_duckdb_driver_command()?; + Self::open_with_command_and_process_limit( + executable, + executable_args, + path, + attached_databases, + init_script, + DUCKDB_WORKER_MAX_PROCESSES_DEFAULT, + ) + .await } pub async fn open_with_process_limit( @@ -78,9 +89,16 @@ impl DuckDbWorkerClient { init_script: Option, process_limit: usize, ) -> Result { - let executable = std::env::current_exe().map_err(|e| e.to_string())?; - Self::open_with_executable_and_process_limit(executable, path, attached_databases, init_script, process_limit) - .await + let (executable, executable_args) = resolve_duckdb_driver_command()?; + Self::open_with_command_and_process_limit( + executable, + executable_args, + path, + attached_databases, + init_script, + process_limit, + ) + .await } pub async fn open_with_executable( @@ -105,9 +123,29 @@ impl DuckDbWorkerClient { attached_databases: Vec, init_script: Option, process_limit: usize, + ) -> Result { + Self::open_with_command_and_process_limit( + executable, + Vec::new(), + path, + attached_databases, + init_script, + process_limit, + ) + .await + } + + async fn open_with_command_and_process_limit( + executable: PathBuf, + executable_args: Vec, + path: String, + attached_databases: Vec, + init_script: Option, + process_limit: usize, ) -> Result { let client = Self::new_unconnected_with_timeouts( executable, + executable_args, path, attached_databases, init_script, @@ -122,6 +160,7 @@ impl DuckDbWorkerClient { #[doc(hidden)] pub fn new_unconnected_with_timeouts( executable: PathBuf, + executable_args: Vec, path: String, attached_databases: Vec, init_script: Option, @@ -139,6 +178,7 @@ impl DuckDbWorkerClient { process_limiter, process_limit, executable, + executable_args, connect_params: DuckDbWorkerConnectParams { path, attached_databases, init_script }, request_timeout, worker_start_timeout, @@ -246,6 +286,18 @@ impl DuckDbWorkerClient { .await } + pub async fn get_table_ddl(&self, database: String, schema: String, table: String) -> Result { + self.metadata_request(DuckDbWorkerMethod::GetTableDdl, DuckDbWorkerColumnParams { database, schema, table }) + .await + } + + pub async fn completion_assistant( + &self, + request: db::CompletionAssistantRequest, + ) -> Result { + self.metadata_request(DuckDbWorkerMethod::CompletionAssistant, request).await + } + pub async fn get_object_source( &self, database: String, @@ -413,9 +465,14 @@ impl DuckDbWorkerClient { .map_err(|_| duckdb_worker_process_limit_error(self.inner.process_limit))? .map_err(|_| "DuckDB worker process limiter is closed".to_string())?; - log::info!("[duckdb-worker:start] executable={}", self.inner.executable.display()); - let mut child = crate::process::new_tokio_command(&self.inner.executable) - .arg("--duckdb-worker") + log::info!( + "[duckdb-worker:start] executable={} args={:?}", + self.inner.executable.display(), + self.inner.executable_args + ); + let mut command = crate::process::new_tokio_command(&self.inner.executable); + command.args(&self.inner.executable_args); + let mut child = command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) @@ -524,6 +581,25 @@ impl DuckDbWorkerClient { } } +fn resolve_duckdb_driver_command() -> Result<(PathBuf, Vec), String> { + if let Some(executable) = std::env::var_os(DUCKDB_DRIVER_PATH_ENV).filter(|value| !value.is_empty()) { + let executable = PathBuf::from(executable); + ensure_duckdb_driver_exists(&executable)?; + return Ok((executable, Vec::new())); + } + + Err(format!( + "DuckDB driver is not installed. Please install it from the Driver Manager or set {DUCKDB_DRIVER_PATH_ENV}." + )) +} + +fn ensure_duckdb_driver_exists(executable: &Path) -> Result<(), String> { + if executable.is_file() { + return Ok(()); + } + Err(format!("DuckDB driver configured by {DUCKDB_DRIVER_PATH_ENV} was not found: {}", executable.display())) +} + struct WorkerProcessLimiterState { limit: usize, limiter: Arc, diff --git a/crates/dbx-core/src/db/duckdb_worker_protocol.rs b/crates/dbx-core/src/db/duckdb_worker_protocol.rs index 0610f2c01e..45898bbda3 100644 --- a/crates/dbx-core/src/db/duckdb_worker_protocol.rs +++ b/crates/dbx-core/src/db/duckdb_worker_protocol.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "duckdb-bundled")] +#![cfg(feature = "duckdb-sidecar")] use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -35,6 +35,8 @@ pub enum DuckDbWorkerMethod { ListSchemas, ListTables, ListColumns, + GetTableDdl, + CompletionAssistant, GetObjectSource, AttachDatabase, Cancel, diff --git a/crates/dbx-core/src/db/mod.rs b/crates/dbx-core/src/db/mod.rs index d16b192481..cd406449de 100644 --- a/crates/dbx-core/src/db/mod.rs +++ b/crates/dbx-core/src/db/mod.rs @@ -5,14 +5,11 @@ pub mod cloudflare_d1; pub use cloudflare_d1 as cloudflare_d1_driver; pub mod document_result; pub mod doris; -pub mod duckdb_driver; pub mod duckdb_sql; -#[cfg(feature = "duckdb-bundled")] +#[cfg(feature = "duckdb-sidecar")] pub mod duckdb_worker_process; -#[cfg(feature = "duckdb-bundled")] +#[cfg(feature = "duckdb-sidecar")] pub mod duckdb_worker_protocol; -#[cfg(feature = "duckdb-bundled")] -pub mod duckdb_worker_runtime; pub mod elasticsearch_driver; pub mod elasticsearch_sql; pub mod file_validator; diff --git a/crates/dbx-core/src/db_admin_sql.rs b/crates/dbx-core/src/db_admin_sql.rs index d564170d48..647b85b1fc 100644 --- a/crates/dbx-core/src/db_admin_sql.rs +++ b/crates/dbx-core/src/db_admin_sql.rs @@ -63,7 +63,7 @@ pub enum DatabaseCreationTarget { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -#[cfg(feature = "duckdb-bundled")] +#[cfg(feature = "duckdb-sidecar")] pub struct DuckDbAttachDatabaseSqlOptions { pub path: String, pub name: String, @@ -312,7 +312,7 @@ pub fn supports_database_property_comment(database_type: Option) - ) } -#[cfg(feature = "duckdb-bundled")] +#[cfg(feature = "duckdb-sidecar")] pub fn build_duckdb_attach_database_sql(options: DuckDbAttachDatabaseSqlOptions) -> String { format!( "ATTACH {} AS {};", @@ -1096,7 +1096,7 @@ mod tests { assert!(!supports_create_database_charset(Some(DatabaseType::Postgres), None)); } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] #[test] fn builds_duckdb_attach_sql() { assert_eq!( diff --git a/crates/dbx-core/src/external/duckdb_cache.rs b/crates/dbx-core/src/external/duckdb_cache.rs deleted file mode 100644 index d5c9366c8e..0000000000 --- a/crates/dbx-core/src/external/duckdb_cache.rs +++ /dev/null @@ -1,173 +0,0 @@ -#![cfg(feature = "duckdb-bundled")] - -use std::sync::Arc; - -use super::types::{CacheState, ExternalTableRef, ExternalTableSnapshot}; - -/// Loads an external table snapshot into an in-memory DuckDB connection. -pub fn load_snapshot_to_duckdb(con: &duckdb::Connection, snapshot: &ExternalTableSnapshot) -> Result<(), String> { - let table_name = sanitize_table_name(&snapshot.table_ref.table_name); - - con.execute(&format!("DROP TABLE IF EXISTS \"{}\"", table_name), []) - .map_err(|e| format!("Failed to drop table: {e}"))?; - - let col_defs: Vec = snapshot - .columns - .iter() - .map(|c| { - format!( - "\"{}\" {}{}", - c.name.replace('"', "\"\""), - c.duckdb_type, - if c.is_nullable { "" } else { " NOT NULL" } - ) - }) - .collect(); - - let pk_cols: Vec<&str> = snapshot.columns.iter().filter(|c| c.is_primary_key).map(|c| c.name.as_str()).collect(); - - let mut create_sql = format!("CREATE TABLE \"{}\" ({}", table_name, col_defs.join(", ")); - if !pk_cols.is_empty() { - create_sql.push_str(&format!( - ", PRIMARY KEY ({})", - pk_cols.iter().map(|c| format!("\"{}\"", c.replace('"', "\"\""))).collect::>().join(", ") - )); - } - create_sql.push(')'); - - con.execute(&create_sql, []).map_err(|e| format!("Failed to create table: {e}"))?; - - if snapshot.rows.is_empty() { - return Ok(()); - } - - let placeholders: Vec<&str> = (0..snapshot.columns.len()).map(|_| "?").collect(); - let insert_sql = format!("INSERT INTO \"{}\" VALUES ({})", table_name, placeholders.join(", ")); - let mut stmt = con.prepare(&insert_sql).map_err(|e| format!("Failed to prepare insert: {e}"))?; - - for row in &snapshot.rows { - let params: Vec> = row - .iter() - .enumerate() - .map(|(i, val)| { - let col_type = snapshot.columns.get(i).map(|c| c.duckdb_type.as_str()).unwrap_or("VARCHAR"); - json_to_duckdb_param(val, col_type) - }) - .collect(); - - let param_refs: Vec<&dyn duckdb::ToSql> = params.iter().map(|p| p.as_ref()).collect(); - stmt.execute(param_refs.as_slice()).map_err(|e| format!("Failed to insert row: {e}"))?; - } - - Ok(()) -} - -fn json_to_duckdb_param(val: &serde_json::Value, col_type: &str) -> Box { - match val { - serde_json::Value::Null => Box::new(None::), - serde_json::Value::Bool(b) => Box::new(*b), - serde_json::Value::Number(n) => { - if col_type.contains("INT") { - Box::new(n.as_i64().unwrap_or(0)) - } else if col_type.contains("DOUBLE") || col_type.contains("FLOAT") || col_type.contains("DECIMAL") { - Box::new(n.as_f64().unwrap_or(0.0)) - } else { - Box::new(n.to_string()) - } - } - serde_json::Value::String(s) => Box::new(s.clone()), - _ => Box::new(val.to_string()), - } -} - -/// Sanitize a source table name into a stable DuckDB table identifier. -pub fn sanitize_table_name(name: &str) -> String { - let sanitized: String = name.chars().map(|c| if c.is_alphanumeric() || c == '_' { c } else { '_' }).collect(); - - if sanitized.is_empty() { - "_unnamed_".to_string() - } else if sanitized.chars().next().is_some_and(|c| c.is_ascii_digit()) { - format!("_{sanitized}") - } else { - sanitized - } -} - -/// Manages a DuckDB cache for one external source. -pub struct ExternalPool { - pub source: Arc, - pub cache: Arc>, - pub cache_state: std::sync::Mutex, - pub table_map: std::sync::Mutex>, -} - -impl ExternalPool { - pub fn new( - source: Arc, - cache: Arc>, - ) -> Self { - Self { - source, - cache, - cache_state: std::sync::Mutex::new(CacheState::Empty), - table_map: std::sync::Mutex::new(std::collections::HashMap::new()), - } - } - - pub async fn refresh_cache(&self) -> Result<(), String> { - { - let mut state = self.cache_state.lock().map_err(|e| e.to_string())?; - *state = CacheState::Loading; - } - - let tables = self.source.list_tables().await?; - let mut new_table_map = std::collections::HashMap::new(); - - for table_ref in &tables { - let snapshot = self.source.load_table(table_ref).await?; - let mut sanitized_name = sanitize_table_name(&table_ref.table_name); - - if new_table_map.contains_key(&sanitized_name) { - let base = sanitized_name.clone(); - let mut suffix = 2; - loop { - sanitized_name = format!("{base}_{suffix}"); - if !new_table_map.contains_key(&sanitized_name) { - break; - } - suffix += 1; - } - } - - let cache = self.cache.clone(); - tokio::task::spawn_blocking(move || { - let con = cache.lock().map_err(|e| e.to_string())?; - load_snapshot_to_duckdb(&con, &snapshot) - }) - .await - .map_err(|e| e.to_string())??; - - new_table_map.insert(sanitized_name, table_ref.clone()); - } - - { - let mut map = self.table_map.lock().map_err(|e| e.to_string())?; - *map = new_table_map; - } - { - let mut state = self.cache_state.lock().map_err(|e| e.to_string())?; - *state = CacheState::Fresh; - } - - Ok(()) - } -} - -impl std::fmt::Debug for ExternalPool { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ExternalPool") - .field("source", &self.source.display_name()) - .field("cache_state", &self.cache_state) - .finish() - } -} diff --git a/crates/dbx-core/src/external/mod.rs b/crates/dbx-core/src/external/mod.rs index 9f41a0e6dc..27a88dc18b 100644 --- a/crates/dbx-core/src/external/mod.rs +++ b/crates/dbx-core/src/external/mod.rs @@ -1,8 +1,5 @@ -pub mod duckdb_cache; pub mod traits; pub mod types; -#[cfg(feature = "duckdb-bundled")] -pub use duckdb_cache::ExternalPool; pub use traits::ExternalTabularSource; pub use types::*; diff --git a/crates/dbx-core/src/query.rs b/crates/dbx-core/src/query.rs index 78f30d7294..bad778f21e 100644 --- a/crates/dbx-core/src/query.rs +++ b/crates/dbx-core/src/query.rs @@ -1,7 +1,3 @@ -#[cfg(feature = "duckdb-bundled")] -use chrono::{DateTime, Duration as ChronoDuration, NaiveDate, NaiveDateTime, NaiveTime, Utc}; -#[cfg(feature = "duckdb-bundled")] -use duckdb::types::{TimeUnit, Value, ValueRef}; use futures::StreamExt; use mysql_async::prelude::Queryable; use serde::{Deserialize, Serialize}; @@ -18,10 +14,6 @@ use std::sync::{ Arc, }; use std::time::Duration; -#[cfg(feature = "duckdb-bundled")] -use tokio::task::JoinHandle; -#[cfg(feature = "duckdb-bundled")] -use tokio::time::sleep; use tokio::time::timeout; use tokio_util::sync::CancellationToken; @@ -30,8 +22,6 @@ use crate::database_capabilities; use crate::db; use crate::models::connection::{ConnectionConfig, DatabaseType}; use crate::query_execution_sql::is_write_sql; -#[cfg(feature = "duckdb-bundled")] -use crate::sql::starts_with_duckdb_result_sql_keyword; use crate::sql::{split_sql_batches, split_sql_statements, starts_with_executable_sql_keyword}; use crate::sql_dialect::{resolve_for_db, CAP_TRANSACTIONAL_DDL}; use crate::sql_risk::{classify_sql_risk_for_database, SqlRisk}; @@ -45,10 +35,6 @@ pub const QUERY_CANCELED: &str = "Query canceled"; const MONGO_SHELL_COMMAND_HINT: &str = "Use MongoDB shell-style commands, for example: db.collection.find({}).limit(100), db.collection.aggregate([]), db.collection.aggregate([], { explain: true }), db.version(), db.collection.countDocuments({}), db.collection.distinct(\"field\"), db.collection.getIndexes(), db.collection.createIndex({...}), or db.collection.insertOne({...})."; const SQL_OMITTED_ERROR_CONTEXT: &str = "SQL text omitted from user-facing error; enable debug SQL diagnostics for a redacted statement."; -#[cfg(feature = "duckdb-bundled")] -const DUCKDB_INTERRUPT_DRAIN_TIMEOUT: Duration = Duration::from_secs(2); -#[cfg(feature = "duckdb-bundled")] -const DUCKDB_DRAINING_MESSAGE: &str = "The previous DuckDB query is still stopping. Please try again shortly."; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PoolErrorAction { @@ -476,387 +462,6 @@ fn query_result_row_limit(max_rows: Option) -> usize { max_rows.unwrap_or(MAX_ROWS).max(1) } -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_execute(con: &duckdb::Connection, sql: &str) -> Result { - duckdb_execute_with_max_rows(con, sql, None) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_value_to_json(row: &duckdb::Row<'_>, idx: usize) -> serde_json::Value { - let Ok(value_ref) = row.get_ref(idx) else { - return serde_json::Value::Null; - }; - match value_ref { - ValueRef::Null => serde_json::Value::Null, - ValueRef::Boolean(b) => serde_json::Value::Bool(b), - ValueRef::TinyInt(i) => serde_json::Value::Number((i as i64).into()), - ValueRef::SmallInt(i) => serde_json::Value::Number((i as i64).into()), - ValueRef::Int(i) => serde_json::Value::Number((i as i64).into()), - ValueRef::BigInt(i) => serde_json::Value::Number(i.into()), - ValueRef::HugeInt(i) => serde_json::Value::String(i.to_string()), - ValueRef::UTinyInt(i) => serde_json::Value::Number((i as u64).into()), - ValueRef::USmallInt(i) => serde_json::Value::Number((i as u64).into()), - ValueRef::UInt(i) => serde_json::Value::Number((i as u64).into()), - ValueRef::UBigInt(i) => serde_json::Value::Number(i.into()), - ValueRef::Float(f) => { - serde_json::Number::from_f64(f as f64).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null) - } - ValueRef::Double(f) => { - serde_json::Number::from_f64(f).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null) - } - ValueRef::Decimal(d) => serde_json::Value::String(d.to_string()), - ValueRef::Date32(days) => { - duckdb_date32_to_string(days).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) - } - ValueRef::Time64(unit, value) => { - duckdb_time64_to_string(unit, value).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) - } - ValueRef::Timestamp(unit, value) => { - duckdb_timestamp_to_string(unit, value).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) - } - ValueRef::Text(bytes) => std::str::from_utf8(bytes) - .map(|s| serde_json::Value::String(s.to_string())) - .unwrap_or(serde_json::Value::Null), - ValueRef::Blob(bytes) => { - let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect(); - serde_json::Value::String(format!("\\x{hex}")) - } - ValueRef::Interval { months, days, nanos } => { - serde_json::Value::String(duckdb_interval_to_string(months, days, nanos)) - } - ValueRef::List(..) - | ValueRef::Array(..) - | ValueRef::Struct(..) - | ValueRef::Map(..) - | ValueRef::Enum(..) - | ValueRef::Union(..) => duckdb_owned_value_to_json(&value_ref.to_owned()), - _ => duckdb_owned_value_to_json(&value_ref.to_owned()), - } -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_owned_value_to_json(value: &Value) -> serde_json::Value { - match value { - Value::Null => serde_json::Value::Null, - Value::Boolean(b) => serde_json::Value::Bool(*b), - Value::TinyInt(i) => serde_json::Value::Number((*i as i64).into()), - Value::SmallInt(i) => serde_json::Value::Number((*i as i64).into()), - Value::Int(i) => serde_json::Value::Number((*i as i64).into()), - Value::BigInt(i) => serde_json::Value::Number((*i).into()), - Value::HugeInt(i) => serde_json::Value::String(i.to_string()), - Value::UTinyInt(i) => serde_json::Value::Number((*i as u64).into()), - Value::USmallInt(i) => serde_json::Value::Number((*i as u64).into()), - Value::UInt(i) => serde_json::Value::Number((*i as u64).into()), - Value::UBigInt(i) => serde_json::Value::Number((*i).into()), - Value::Float(f) => { - serde_json::Number::from_f64(*f as f64).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null) - } - Value::Double(f) => { - serde_json::Number::from_f64(*f).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null) - } - Value::Decimal(d) => serde_json::Value::String(d.to_string()), - Value::Timestamp(unit, value) => { - duckdb_timestamp_to_string(*unit, *value).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) - } - Value::Text(text) | Value::Enum(text) => serde_json::Value::String(text.clone()), - Value::Blob(bytes) => { - let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect(); - serde_json::Value::String(format!("\\x{hex}")) - } - Value::Date32(days) => { - duckdb_date32_to_string(*days).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) - } - Value::Time64(unit, value) => { - duckdb_time64_to_string(*unit, *value).map(serde_json::Value::String).unwrap_or(serde_json::Value::Null) - } - Value::Interval { months, days, nanos } => { - serde_json::Value::String(duckdb_interval_to_string(*months, *days, *nanos)) - } - Value::List(values) | Value::Array(values) => { - serde_json::Value::Array(values.iter().map(duckdb_owned_value_to_json).collect()) - } - Value::Struct(entries) => serde_json::Value::Object( - entries.iter().map(|(key, value)| (key.clone(), duckdb_owned_value_to_json(value))).collect(), - ), - Value::Map(entries) => serde_json::Value::Array( - entries - .iter() - .map(|(key, value)| { - serde_json::json!({ - "key": duckdb_owned_value_to_json(key), - "value": duckdb_owned_value_to_json(value), - }) - }) - .collect(), - ), - Value::Union(value) => duckdb_owned_value_to_json(value), - _ => serde_json::Value::Null, - } -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_interval_to_string(months: i32, days: i32, nanos: i64) -> String { - let mut parts = Vec::new(); - if months != 0 { - let years = months / 12; - let rem = months % 12; - if years != 0 { - parts.push(format!("{} year{}", years, if years.abs() != 1 { "s" } else { "" })); - } - if rem != 0 { - parts.push(format!("{} mon{}", rem, if rem.abs() != 1 { "s" } else { "" })); - } - } - if days != 0 { - parts.push(format!("{} day{}", days, if days.abs() != 1 { "s" } else { "" })); - } - if nanos != 0 { - let total_secs = nanos / 1_000_000_000; - let hours = total_secs / 3600; - let mins = (total_secs % 3600) / 60; - let secs = total_secs % 60; - let sub_nanos = (nanos % 1_000_000_000).unsigned_abs(); - if sub_nanos > 0 { - parts.push(format!( - "{:02}:{:02}:{:02}.{}", - hours, - mins, - secs, - format_temporal_without_empty_fraction(format!("0.{:09}", sub_nanos)).trim_start_matches("0.") - )); - } else { - parts.push(format!("{:02}:{:02}:{:02}", hours, mins, secs)); - } - } - if parts.is_empty() { - "00:00:00".to_string() - } else { - parts.join(" ") - } -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_date32_to_string(days: i32) -> Option { - let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)?; - epoch.checked_add_signed(ChronoDuration::days(i64::from(days))).map(|date| date.to_string()) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_time64_to_string(unit: TimeUnit, value: i64) -> Option { - let nanos = duckdb_time_unit_to_nanos(unit, value)?; - let seconds = nanos.div_euclid(1_000_000_000); - let nanos_remainder = nanos.rem_euclid(1_000_000_000) as u32; - if !(0..86_400).contains(&seconds) { - return None; - } - let time = NaiveTime::from_num_seconds_from_midnight_opt(seconds as u32, nanos_remainder)?; - Some(format_temporal_without_empty_fraction(time.to_string())) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_timestamp_to_string(unit: TimeUnit, value: i64) -> Option { - let nanos = duckdb_time_unit_to_nanos(unit, value)?; - let seconds = nanos.div_euclid(1_000_000_000); - let nanos_remainder = nanos.rem_euclid(1_000_000_000) as u32; - let dt: DateTime = DateTime::from_timestamp(seconds, nanos_remainder)?; - Some(format_naive_datetime(dt.naive_utc())) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_time_unit_to_nanos(unit: TimeUnit, value: i64) -> Option { - match unit { - TimeUnit::Second => value.checked_mul(1_000_000_000), - TimeUnit::Millisecond => value.checked_mul(1_000_000), - TimeUnit::Microsecond => value.checked_mul(1_000), - TimeUnit::Nanosecond => Some(value), - } -} - -#[cfg(feature = "duckdb-bundled")] -fn format_naive_datetime(value: NaiveDateTime) -> String { - if value.and_utc().timestamp_subsec_nanos() == 0 { - value.format("%Y-%m-%d %H:%M:%S").to_string() - } else { - format_temporal_without_empty_fraction(value.to_string()) - } -} - -#[cfg(feature = "duckdb-bundled")] -fn format_temporal_without_empty_fraction(value: String) -> String { - if !value.contains('.') { - return value; - } - let trimmed = value.trim_end_matches('0').trim_end_matches('.'); - trimmed.to_string() -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_execute_with_max_rows( - con: &duckdb::Connection, - sql: &str, - max_rows: Option, -) -> Result { - let start = std::time::Instant::now(); - let row_limit = query_result_row_limit(max_rows); - - if starts_with_duckdb_result_sql_keyword(sql) { - let mut stmt = con.prepare(sql).map_err(|e| e.to_string())?; - let mut rows = stmt.query([]).map_err(|e| e.to_string())?; - let stmt_ref = rows.as_ref().ok_or("DuckDB statement unavailable")?; - let col_count = stmt_ref.column_count(); - let columns: Vec = (0..col_count) - .map(|i| stmt_ref.column_name(i).map(|s| s.to_string()).unwrap_or_else(|_| "?".to_string())) - .collect(); - - let mut result_rows = Vec::new(); - while let Some(row) = rows.next().map_err(|e| e.to_string())? { - let vals: Vec = (0..col_count).map(|i| duckdb_value_to_json(row, i)).collect(); - result_rows.push(vals); - if result_rows.len() > row_limit { - break; - } - } - - let truncated = result_rows.len() > row_limit; - if truncated { - result_rows.truncate(row_limit); - } - Ok(db::QueryResult { - columns, - column_types: Vec::new(), - column_sortables: vec![], - rows: result_rows, - affected_rows: 0, - execution_time_ms: start.elapsed().as_millis(), - truncated, - session_id: None, - has_more: false, - elasticsearch_raw_body: None, - }) - } else { - let affected = con.execute(sql, []).map_err(|e| e.to_string())?; - Ok(db::QueryResult { - columns: vec![], - column_types: Vec::new(), - column_sortables: vec![], - rows: vec![], - affected_rows: affected as u64, - execution_time_ms: start.elapsed().as_millis(), - truncated: false, - session_id: None, - has_more: false, - elasticsearch_raw_body: None, - }) - } -} - -#[cfg(feature = "duckdb-bundled")] -enum DuckDbTaskWait { - Finished(Result), - Draining { error: String, task: JoinHandle> }, -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_join_result( - result: Result, tokio::task::JoinError>, -) -> Result { - result.map_err(|e| e.to_string())? -} - -#[cfg(feature = "duckdb-bundled")] -async fn interrupt_and_drain_duckdb_task( - interrupt_handle: std::sync::Arc, - mut task: JoinHandle>, - error: String, -) -> DuckDbTaskWait { - interrupt_handle.interrupt(); - match timeout(DUCKDB_INTERRUPT_DRAIN_TIMEOUT, &mut task).await { - Ok(result) => { - let _ = result; - DuckDbTaskWait::Finished(Err(error)) - } - Err(_) => DuckDbTaskWait::Draining { error, task }, - } -} - -#[cfg(feature = "duckdb-bundled")] -async fn wait_for_duckdb_task_with_interrupt_outcome( - cancel_token: Option, - timeout_duration: Option, - interrupt_handle: std::sync::Arc, - mut task: JoinHandle>, -) -> DuckDbTaskWait { - match (cancel_token, timeout_duration) { - (Some(token), Some(duration)) => { - tokio::select! { - biased; - _ = token.cancelled() => { - interrupt_and_drain_duckdb_task(interrupt_handle, task, canceled_error()).await - } - result = &mut task => DuckDbTaskWait::Finished(duckdb_join_result(result)), - _ = sleep(duration) => { - interrupt_and_drain_duckdb_task(interrupt_handle, task, timeout_error()).await - } - } - } - (Some(token), None) => { - tokio::select! { - biased; - _ = token.cancelled() => { - interrupt_and_drain_duckdb_task(interrupt_handle, task, canceled_error()).await - } - result = &mut task => DuckDbTaskWait::Finished(duckdb_join_result(result)), - } - } - (None, Some(duration)) => { - tokio::select! { - result = &mut task => DuckDbTaskWait::Finished(duckdb_join_result(result)), - _ = sleep(duration) => { - interrupt_and_drain_duckdb_task(interrupt_handle, task, timeout_error()).await - } - } - } - (None, None) => DuckDbTaskWait::Finished(duckdb_join_result(task.await)), - } -} - -#[cfg(feature = "duckdb-bundled")] -async fn wait_for_duckdb_task_with_interrupt( - cancel_token: Option, - timeout_duration: Option, - interrupt_handle: std::sync::Arc, - task: JoinHandle>, -) -> Result { - match wait_for_duckdb_task_with_interrupt_outcome(cancel_token, timeout_duration, interrupt_handle, task).await { - DuckDbTaskWait::Finished(result) => result, - DuckDbTaskWait::Draining { error, .. } => Err(error), - } -} - -#[cfg(feature = "duckdb-bundled")] -pub(crate) fn duckdb_execute_for_database( - con: &duckdb::Connection, - attached_names: &[String], - database: Option<&str>, - sql: &str, - max_rows: Option, -) -> Result { - if let Some(database) = database.map(str::trim).filter(|database| !database.is_empty()) { - let catalog = if database == "main" { - crate::schema::duckdb_primary_catalog(con, attached_names)? - } else { - database.to_string() - }; - con.execute_batch(&format!("USE {}", duckdb_quote_ident(&catalog))).map_err(|e| e.to_string())?; - } - duckdb_execute_with_max_rows(con, sql, max_rows) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_quote_ident(value: &str) -> String { - format!("\"{}\"", value.replace('"', "\"\"")) -} - pub fn truncate_result(result: db::QueryResult) -> db::QueryResult { truncate_result_with_max_rows(result, None) } @@ -1166,11 +771,6 @@ where } } -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_draining_error() -> String { - DUCKDB_DRAINING_MESSAGE.to_string() -} - pub fn is_canceled(cancel_token: &Option) -> bool { cancel_token.as_ref().map(|token| token.is_cancelled()).unwrap_or(false) } @@ -1366,12 +966,10 @@ pub async fn do_execute( let _activity_touch = state.pool_activity_touch(pool_key); let query_timeout = resolve_query_timeout(options.timeout_secs); - let (_duckdb_attached_names, read_only_connection) = { + let read_only_connection = { let configs = state.configs.read().await; let config = crate::connection::config_for_pool_key(pool_key, &configs); - let attached = config.map(crate::db::duckdb_sql::config_attached_names).unwrap_or_default(); - let connection = config.filter(|config| config.read_only).map(|config| (config.name.clone(), config.db_type)); - (attached, connection) + config.filter(|config| config.read_only).map(|config| (config.name.clone(), config.db_type)) }; let operation_budget = operation_budget_for_pool_key(state, pool_key, query_timeout).await; if let Some((name, database_type)) = read_only_connection { @@ -1383,49 +981,7 @@ pub async fn do_execute( let pool = connections.get(pool_key).ok_or("Connection not found")?; let result = match pool { - #[cfg(feature = "duckdb-bundled")] - PoolKind::DuckDb(con) => { - let con = con.clone(); - if con.is_draining() { - drop(connections); - return Err(duckdb_draining_error()); - } - let interrupt_handle = con.interrupt_handle(); - if let Some(ref execution_id) = options.execution_id { - let cancel_interrupt_handle = interrupt_handle.clone(); - state.running_queries.register_interrupt(execution_id, move || { - cancel_interrupt_handle.interrupt(); - }); - } - let sql = sql.to_string(); - let database = database.map(str::to_string); - let attached_names = _duckdb_attached_names; - let max_rows = options.max_rows; - drop(connections); - let task_con = con.clone(); - let task = tokio::task::spawn_blocking(move || { - let con = task_con.lock().map_err(|e| e.to_string())?; - duckdb_execute_for_database(&con, &attached_names, database.as_deref(), &sql, max_rows) - }); - let result = - wait_for_duckdb_task_with_interrupt_outcome(cancel_token, query_timeout, interrupt_handle, task).await; - match result { - DuckDbTaskWait::Finished(result) => { - if matches!(result.as_ref(), Err(err) if err == QUERY_CANCELED || is_dbx_query_timeout_error(&err.to_lowercase())) - { - con.mark_draining(); - state.spawn_duckdb_pool_cleanup(pool_key.to_string(), con); - } - result - } - DuckDbTaskWait::Draining { error, task } => { - con.mark_draining(); - state.spawn_duckdb_draining_cleanup(pool_key.to_string(), con, task); - Err(error) - } - } - } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] PoolKind::DuckDbWorker(client) => { let client = client.clone(); if let Some(ref execution_id) = options.execution_id { @@ -1445,11 +1001,7 @@ pub async fn do_execute( drop(connections); client.execute(database, sql, max_rows, cancel_token, query_timeout).await } - #[cfg(not(feature = "duckdb-bundled"))] - PoolKind::DuckDb(_) => { - return Err("DuckDB support is not compiled in this build".to_string()); - } - #[cfg(not(feature = "duckdb-bundled"))] + #[cfg(not(feature = "duckdb-sidecar"))] PoolKind::DuckDbWorker(_) => { return Err("DuckDB worker support is not compiled in this build".to_string()); } @@ -1719,32 +1271,6 @@ pub async fn do_execute( } result } - #[cfg(feature = "duckdb-bundled")] - PoolKind::ExternalTabular(ext_pool) => { - if !starts_with_duckdb_result_sql_keyword(sql) { - return Err("External data sources are read-only. Only SELECT queries are supported.".to_string()); - } - let con = ext_pool.cache.clone(); - let interrupt_handle = con.lock().map_err(|e| e.to_string())?.interrupt_handle(); - if let Some(ref execution_id) = options.execution_id { - let cancel_interrupt_handle = interrupt_handle.clone(); - state.running_queries.register_interrupt(execution_id, move || { - cancel_interrupt_handle.interrupt(); - }); - } - let sql = sql.to_string(); - let max_rows = options.max_rows; - drop(connections); - let task = tokio::task::spawn_blocking(move || { - let con = con.lock().map_err(|e| e.to_string())?; - duckdb_execute_with_max_rows(&con, &sql, max_rows) - }); - wait_for_duckdb_task_with_interrupt(cancel_token, query_timeout, interrupt_handle, task).await - } - #[cfg(not(feature = "duckdb-bundled"))] - PoolKind::ExternalTabular(_) => { - Err("External data sources require DuckDB support. Rebuild with default features.".to_string()) - } PoolKind::ExternalDriver { config, session, .. } => { let config = config.clone(); let session = session.clone(); @@ -2769,7 +2295,6 @@ fn schema_diff_failure_outcome( } fn pool_kind_has_transactional_path(pool: &PoolKind) -> bool { - // DuckDb variants always exist (unit stubs when duckdb-bundled is off). match pool { PoolKind::Postgres(_) | PoolKind::Mysql(_, _) @@ -2783,14 +2308,12 @@ fn pool_kind_has_transactional_path(pool: &PoolKind) -> bool { PoolKind::MessageQueue | PoolKind::Nacos | PoolKind::HBase(_) - | PoolKind::DuckDb(_) | PoolKind::DuckDbWorker(_) | PoolKind::Redis(_) | PoolKind::MongoDb(_) | PoolKind::Elasticsearch(_) | PoolKind::VectorDb(_) | PoolKind::InfluxDb(_) - | PoolKind::ExternalTabular(_) | PoolKind::ExternalDriver { .. } => false, } } @@ -2980,25 +2503,12 @@ pub async fn execute_statements_in_transaction_on_pool( | PoolKind::SqlServer(_) | PoolKind::Agent(_) => TxPath::Explicit, PoolKind::MessageQueue | PoolKind::Nacos | PoolKind::HBase(_) => TxPath::None, - #[cfg(feature = "duckdb-bundled")] - PoolKind::DuckDb(_) - | PoolKind::DuckDbWorker(_) + PoolKind::DuckDbWorker(_) | PoolKind::Redis(_) | PoolKind::MongoDb(_) | PoolKind::Elasticsearch(_) | PoolKind::VectorDb(_) | PoolKind::InfluxDb(_) - | PoolKind::ExternalTabular(_) - | PoolKind::ExternalDriver { .. } => TxPath::None, - #[cfg(not(feature = "duckdb-bundled"))] - PoolKind::DuckDb(_) - | PoolKind::DuckDbWorker(_) - | PoolKind::Redis(_) - | PoolKind::MongoDb(_) - | PoolKind::Elasticsearch(_) - | PoolKind::VectorDb(_) - | PoolKind::InfluxDb(_) - | PoolKind::ExternalTabular(_) | PoolKind::ExternalDriver { .. } => TxPath::None, }) }; @@ -4744,192 +4254,6 @@ mod tests { assert_eq!(result.unwrap_err(), QUERY_CANCELED); } - #[cfg(feature = "duckdb-bundled")] - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn duckdb_timeout_interrupts_running_task_without_waiting_for_it_to_finish() { - let con = std::sync::Arc::new(crate::db::duckdb_driver::DuckDbConnection::new( - duckdb::Connection::open_in_memory().unwrap(), - )); - let interrupt_handle = con.interrupt_handle(); - let running_con = con.clone(); - let task = tokio::task::spawn_blocking(move || { - let con = running_con.lock().map_err(|e| e.to_string())?; - duckdb_execute_with_max_rows(&con, "SELECT sum(sin(i::DOUBLE)) FROM range(10000000000) tbl(i)", None) - }); - - let started = std::time::Instant::now(); - let result = - wait_for_duckdb_task_with_interrupt(None, Some(Duration::from_millis(10)), interrupt_handle, task).await; - - assert_eq!(result.unwrap_err(), timeout_error()); - assert!(started.elapsed() < Duration::from_secs(5)); - } - - #[cfg(feature = "duckdb-bundled")] - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn duckdb_cancel_keeps_pool_draining_until_references_drop() { - let dir = std::env::temp_dir().join(format!("dbx-query-duckdb-cancel-{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&dir).unwrap(); - let storage = Storage::open(&dir.join("storage.db")).await.unwrap(); - let state = AppState::new(storage); - let pool_key = "duckdb-1"; - let con = std::sync::Arc::new(crate::db::duckdb_driver::DuckDbConnection::new( - duckdb::Connection::open_in_memory().unwrap(), - )); - let extra_reference = con.clone(); - state.connections.write().await.insert(pool_key.to_string(), PoolKind::DuckDb(con)); - state.configs.write().await.insert(pool_key.to_string(), test_connection_config(DatabaseType::DuckDb)); - - let token = CancellationToken::new(); - let cancel_token = token.clone(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_millis(10)).await; - cancel_token.cancel(); - }); - - let result = do_execute( - &state, - pool_key, - db::mysql::MySqlQueryDialect::default(), - Some("main"), - "SELECT sum(sin(i::DOUBLE)) FROM range(10000000000) tbl(i)", - None, - Some(token), - QueryExecutionOptions::default(), - ) - .await; - - assert_eq!(result.unwrap_err(), QUERY_CANCELED); - let still_present = { - let conns = state.connections.read().await; - matches!(conns.get(pool_key), Some(PoolKind::DuckDb(current)) if current.is_draining()) - }; - assert!(still_present); - - drop(extra_reference); - // The draining-cleanup task (spawn_duckdb_draining_cleanup) cannot drop - // the pool until the cancelled query's blocking DuckDB task has fully - // unwound — the connection must stay alive while the query still holds - // it. That unwind is near-instant on an idle runner but can exceed the - // original 5s window under heavy CI load, flaking this test, so allow - // ample headroom. - timeout(Duration::from_secs(30), async { - loop { - if !state.connections.read().await.contains_key(pool_key) { - break; - } - tokio::time::sleep(Duration::from_millis(10)).await; - } - }) - .await - .expect("draining DuckDB pool should be removed after references drop"); - } - - #[cfg(feature = "duckdb-bundled")] - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn duckdb_draining_pool_rejects_follow_up_query() { - let dir = std::env::temp_dir().join(format!("dbx-query-duckdb-draining-{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&dir).unwrap(); - let storage = Storage::open(&dir.join("storage.db")).await.unwrap(); - let state = AppState::new(storage); - let pool_key = "duckdb-1"; - let con = std::sync::Arc::new(crate::db::duckdb_driver::DuckDbConnection::new( - duckdb::Connection::open_in_memory().unwrap(), - )); - con.mark_draining(); - state.connections.write().await.insert(pool_key.to_string(), PoolKind::DuckDb(con)); - state.configs.write().await.insert(pool_key.to_string(), test_connection_config(DatabaseType::DuckDb)); - - let result = do_execute( - &state, - pool_key, - db::mysql::MySqlQueryDialect::default(), - Some("main"), - "SELECT 1", - None, - None, - QueryExecutionOptions::default(), - ) - .await; - - assert_eq!(result.unwrap_err(), duckdb_draining_error()); - } - - #[cfg(feature = "duckdb-bundled")] - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn duckdb_draining_cleanup_removes_pool_after_task_finishes() { - let dir = std::env::temp_dir().join(format!("dbx-query-duckdb-cleanup-{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&dir).unwrap(); - let storage = Storage::open(&dir.join("storage.db")).await.unwrap(); - let state = AppState::new(storage); - let pool_key = "duckdb-1"; - let con = std::sync::Arc::new(crate::db::duckdb_driver::DuckDbConnection::new( - duckdb::Connection::open_in_memory().unwrap(), - )); - con.mark_draining(); - state.connections.write().await.insert(pool_key.to_string(), PoolKind::DuckDb(con.clone())); - - let task_con = con.clone(); - let task = tokio::task::spawn_blocking(move || { - let _locked = task_con.lock().map_err(|e| e.to_string())?; - std::thread::sleep(Duration::from_millis(100)); - Ok(empty_query_result(0)) - }); - state.spawn_duckdb_draining_cleanup(pool_key.to_string(), con.clone(), task); - - assert!(state.connections.read().await.contains_key(pool_key)); - drop(con); - timeout(Duration::from_secs(5), async { - loop { - if !state.connections.read().await.contains_key(pool_key) { - break; - } - tokio::time::sleep(Duration::from_millis(10)).await; - } - }) - .await - .expect("draining cleanup should remove the DuckDB pool"); - } - - #[cfg(feature = "duckdb-bundled")] - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn duckdb_cleanup_keeps_draining_pool_while_extra_reference_exists() { - let dir = std::env::temp_dir().join(format!("dbx-query-duckdb-cleanup-ref-{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&dir).unwrap(); - let storage = Storage::open(&dir.join("storage.db")).await.unwrap(); - let state = AppState::new(storage); - let pool_key = "duckdb-1"; - let con = std::sync::Arc::new(crate::db::duckdb_driver::DuckDbConnection::new( - duckdb::Connection::open_in_memory().unwrap(), - )); - con.mark_draining(); - state.connections.write().await.insert(pool_key.to_string(), PoolKind::DuckDb(con.clone())); - - let extra_reference = con.clone(); - let task = tokio::task::spawn_blocking(|| Ok(empty_query_result(0))); - state.spawn_duckdb_draining_cleanup(pool_key.to_string(), con.clone(), task); - tokio::time::sleep(Duration::from_millis(100)).await; - - let still_present = { - let conns = state.connections.read().await; - matches!(conns.get(pool_key), Some(PoolKind::DuckDb(current)) if current.is_draining()) - }; - assert!(still_present); - - drop(extra_reference); - drop(con); - timeout(Duration::from_secs(5), async { - loop { - if !state.connections.read().await.contains_key(pool_key) { - break; - } - tokio::time::sleep(Duration::from_millis(10)).await; - } - }) - .await - .expect("draining cleanup should remove the DuckDB pool after extra refs drop"); - } - #[test] fn db_operation_budget_from_config() { let budget = DbOperationBudget::from_config(10, Some(30)); @@ -5162,150 +4486,6 @@ mod tests { assert_eq!(pool_error_action(Some(DatabaseType::Dameng), dameng_err), PoolErrorAction::ReconnectAndRetry); } - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_preserves_double_precision() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - let result = duckdb_execute( - &con, - "SELECT 12.34567::DOUBLE AS sample, 0.5::DOUBLE AS half, 99.99::DOUBLE AS price, 1.0::DOUBLE AS one", - ) - .expect("execute double query"); - - assert_eq!(result.columns, vec!["sample", "half", "price", "one"]); - let row = &result.rows[0]; - assert_eq!(row[0], serde_json::json!(12.34567)); - assert_eq!(row[1], serde_json::json!(0.5)); - assert_eq!(row[2], serde_json::json!(99.99)); - assert_eq!(row[3], serde_json::json!(1.0)); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_create_insert_select_double() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - con.execute_batch("CREATE TABLE tmp1 (tmp_double DOUBLE)").expect("create table"); - con.execute_batch("INSERT INTO tmp1 VALUES (45.678), (12.345), (99.999)").expect("insert"); - - let result = duckdb_execute(&con, "SELECT tmp_double FROM tmp1 ORDER BY tmp_double").expect("select doubles"); - - assert_eq!(result.rows.len(), 3); - assert_eq!(result.rows[0][0], serde_json::json!(12.345)); - assert_eq!(result.rows[1][0], serde_json::json!(45.678)); - assert_eq!(result.rows[2][0], serde_json::json!(99.999)); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_returns_rows_for_from_first_query() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - con.execute_batch("CREATE TABLE users (id INTEGER, name VARCHAR)").expect("create table"); - con.execute_batch("INSERT INTO users VALUES (2, 'Grace'), (1, 'Ada')").expect("insert"); - - let result = duckdb_execute(&con, "FROM users ORDER BY id").expect("execute from-first query"); - - assert_eq!(result.columns, vec!["id", "name"]); - assert_eq!(result.rows.len(), 2); - assert_eq!(result.rows[0], vec![serde_json::json!(1), serde_json::json!("Ada")]); - assert_eq!(result.rows[1], vec![serde_json::json!(2), serde_json::json!("Grace")]); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_returns_rows_for_summarize_query() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - con.execute_batch("CREATE TABLE metrics (value INTEGER)").expect("create table"); - con.execute_batch("INSERT INTO metrics VALUES (1), (2), (NULL)").expect("insert"); - - let result = duckdb_execute(&con, "SUMMARIZE metrics").expect("execute summarize query"); - - assert!(!result.columns.is_empty()); - assert!(!result.rows.is_empty()); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_handles_various_types() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - let result = duckdb_execute( - &con, - "SELECT 42 AS int_val, true AS bool_val, 'hello' AS text_val, 3.14::FLOAT AS float_val, 123456789012345::BIGINT AS big_val", - ) - .expect("execute mixed types query"); - - let row = &result.rows[0]; - assert_eq!(row[0], serde_json::json!(42)); - assert_eq!(row[1], serde_json::json!(true)); - assert_eq!(row[2], serde_json::Value::String("hello".to_string())); - assert!(row[3].is_number()); - assert_eq!(row[4], serde_json::json!(123456789012345_i64)); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_returns_list_values_as_json_arrays() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - let result = duckdb_execute(&con, "SELECT ['a','b','c','d'];").expect("execute list query"); - - assert_eq!(result.rows, vec![vec![serde_json::json!(["a", "b", "c", "d"])]]); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_preserves_nulls_inside_list_values() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - let result = duckdb_execute(&con, "SELECT [1, NULL, 3] AS items;").expect("execute nullable list query"); - - assert_eq!(result.columns, vec!["items"]); - assert_eq!(result.rows, vec![vec![serde_json::json!([1, null, 3])]]); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_returns_nested_complex_values_as_json() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - let result = duckdb_execute( - &con, - "SELECT {'name': 'Ada', 'scores': [10, 20]} AS profile, MAP(['x', 'y'], [1, 2]) AS lookup, [1, 2, 3]::INTEGER[3] AS fixed_items", - ) - .expect("execute complex values query"); - - assert_eq!(result.columns, vec!["profile", "lookup", "fixed_items"]); - assert_eq!( - result.rows, - vec![vec![ - serde_json::json!({ "name": "Ada", "scores": [10, 20] }), - serde_json::json!([ - { "key": "x", "value": 1 }, - { "key": "y", "value": 2 }, - ]), - serde_json::json!([1, 2, 3]), - ]] - ); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_execute_formats_temporal_values_by_column_type() { - let con = duckdb::Connection::open_in_memory().expect("connect in-memory DuckDB"); - let result = duckdb_execute( - &con, - "SELECT DATE '2026-05-14' AS d, TIME '16:58:15' AS t, TIMESTAMP '2026-05-14 16:58:15.0' AS ts, NULL::TIMESTAMP AS nts", - ) - .expect("execute temporal query"); - - assert_eq!(result.columns, vec!["d", "t", "ts", "nts"]); - assert_eq!( - result.rows, - vec![vec![ - serde_json::Value::String("2026-05-14".to_string()), - serde_json::Value::String("16:58:15".to_string()), - serde_json::Value::String("2026-05-14 16:58:15".to_string()), - serde_json::Value::Null, - ]] - ); - } - #[test] fn external_driver_query_params_include_database_and_schema_context() { let config = ConnectionConfig { diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs index 12e2891c56..e7c68bf23c 100644 --- a/crates/dbx-core/src/schema.rs +++ b/crates/dbx-core/src/schema.rs @@ -117,605 +117,6 @@ pub fn table_name_filter_matches(name: &str, filter: Option<&TableNameFilter>) - included && !exclude_patterns.iter().any(|pattern| sql_like_pattern_matches_case_insensitive(pattern, name)) } -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_query_tables(con: &duckdb::Connection) -> Result, String> { - duckdb_query_tables_in_database(con, "main", "main") -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_query_tables_in_database( - con: &duckdb::Connection, - database: &str, - schema: &str, -) -> Result, String> { - duckdb_query_tables_in_database_with_attached(con, database, schema, &[]) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_query_tables_in_database_with_attached( - con: &duckdb::Connection, - database: &str, - schema: &str, - attached_names: &[String], -) -> Result, String> { - let database = duckdb_catalog_name(con, database, attached_names)?; - let mut stmt = con.prepare( - "SELECT table_name, table_type FROM information_schema.tables WHERE table_catalog = ? AND table_schema = ? ORDER BY table_name" - ).map_err(|e| e.to_string())?; - let rows = stmt - .query_map((database.as_str(), schema), |row| { - Ok(db::TableInfo { - name: row.get::<_, String>(0)?, - table_type: row.get::<_, String>(1)?, - comment: None, - parent_schema: None, - parent_name: None, - }) - }) - .map_err(|e| e.to_string())?; - let tables: Vec = rows.filter_map(|r| r.ok()).collect(); - if tables.is_empty() && duckdb_is_quack_catalog(con, &database) { - if let Some(remote) = duckdb_quack_remote_tables(con, &database, schema) { - return Ok(remote); - } - } - Ok(tables) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_attach_database(con: &duckdb::Connection, name: &str, path: &str) -> Result<(), String> { - let name = name.trim(); - let path = path.trim(); - if name.is_empty() || path.is_empty() { - return Err("DuckDB attached database name and path are required".to_string()); - } - let sql = format!("ATTACH {} AS {}", duckdb_quote_string(path), duckdb_quote_ident(name)); - con.execute_batch(&sql).map_err(|e| format!("Failed to attach database \"{name}\": {e}")) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_list_databases(con: &duckdb::Connection) -> Result, String> { - duckdb_list_databases_with_attached(con, &[]) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_list_databases_with_attached( - con: &duckdb::Connection, - attached_names: &[String], -) -> Result, String> { - let primary = duckdb_primary_catalog(con, attached_names)?; - let mut stmt = con.prepare("SHOW DATABASES").map_err(|e| e.to_string())?; - let rows = stmt - .query_map([], |row| { - let name = row.get::<_, String>(0)?; - Ok(db::DatabaseInfo { name: if name == primary { "main".to_string() } else { name } }) - }) - .map_err(|e| e.to_string())?; - Ok(rows.filter_map(|row| row.ok()).collect()) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_list_schemas(con: &duckdb::Connection, database: &str) -> Result, String> { - duckdb_list_schemas_with_attached(con, database, &[]) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_list_schemas_with_attached( - con: &duckdb::Connection, - database: &str, - attached_names: &[String], -) -> Result, String> { - let database = duckdb_catalog_name(con, database, attached_names)?; - let mut stmt = con - .prepare( - "SELECT schema_name FROM information_schema.schemata WHERE catalog_name = ? AND schema_name NOT IN ('information_schema', 'pg_catalog') ORDER BY schema_name", - ) - .map_err(|e| e.to_string())?; - let rows = stmt.query_map([database.as_str()], |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; - let schemas: Vec = rows.filter_map(|r| r.ok()).collect(); - if schemas.is_empty() && duckdb_is_quack_catalog(con, &database) { - if let Some(remote) = duckdb_quack_remote_schemas(con, &database) { - return Ok(remote); - } - } - Ok(schemas) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_object_source_with_attached( - con: &duckdb::Connection, - database: &str, - schema: &str, - name: &str, - object_type: &db::ObjectSourceKind, - attached_names: &[String], -) -> Result { - if !matches!(object_type, db::ObjectSourceKind::View) { - return Err("DuckDB object source only supports views".to_string()); - } - - let database = duckdb_catalog_name(con, database, attached_names)?; - let mut stmt = con - .prepare("SELECT sql FROM duckdb_views() WHERE database_name = ? AND schema_name = ? AND view_name = ?") - .map_err(|e| e.to_string())?; - let mut rows = - stmt.query_map((database.as_str(), schema, name), |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; - - match rows.next() { - Some(row) => row.map_err(|e| e.to_string()), - None => Err(format!("DuckDB view source not found: {database}.{schema}.{name}")), - } -} - -/// Identifies quack catalogs by the storage-extension `type` that -/// `duckdb_databases()` reports, rather than by the attach aliases parsed from -/// the init script: `ATTACH 'quack:host:port'` without an `AS` alias gets a -/// derived catalog name (e.g. `localhost:9494`) that no parser sees. -#[cfg(feature = "duckdb-bundled")] -fn duckdb_is_quack_catalog(con: &duckdb::Connection, database: &str) -> bool { - con.query_row("SELECT type FROM duckdb_databases() WHERE lower(database_name) = lower(?)", [database], |row| { - row.get::<_, String>(0) - }) - .map(|catalog_type| catalog_type.eq_ignore_ascii_case("quack")) - .unwrap_or(false) -} - -/// Quack-attached catalogs expose no metadata on the client side (the beta -/// extension implements name resolution and streaming scans, but not catalog -/// enumeration: `information_schema`, `duckdb_tables()` and `SHOW ALL TABLES` -/// all skip the remote catalog). The extension's `quack_query_by_name` table -/// function runs SQL on the remote server, so when a metadata query for an -/// attached catalog comes back empty we ask the remote for its own -/// information_schema. For non-quack catalogs (or when the extension is not -/// loaded) the function call errors and the fallback quietly yields `None`. -#[cfg(feature = "duckdb-bundled")] -fn duckdb_quack_remote_query( - con: &duckdb::Connection, - alias: &str, - remote_sql: &str, - map_row: impl Fn(&duckdb::Row<'_>) -> Result, -) -> Option> { - let sql = format!( - "SELECT * FROM quack_query_by_name({}, {})", - duckdb_quote_string(alias), - duckdb_quote_string(remote_sql) - ); - let mut stmt = match con.prepare(&sql) { - Ok(stmt) => stmt, - Err(e) => { - log::debug!("quack metadata fallback unavailable for catalog {alias}: {e}"); - return None; - } - }; - let rows = match stmt.query_map([], |row| map_row(row)) { - Ok(rows) => rows, - Err(e) => { - log::debug!("quack metadata fallback query failed for catalog {alias}: {e}"); - return None; - } - }; - Some(rows.filter_map(|r| r.ok()).collect()) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_quack_remote_schemas(con: &duckdb::Connection, alias: &str) -> Option> { - duckdb_quack_remote_query( - con, - alias, - "SELECT DISTINCT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema', 'pg_catalog') ORDER BY schema_name", - |row| row.get::<_, String>(0), - ) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_quack_remote_tables(con: &duckdb::Connection, alias: &str, schema: &str) -> Option> { - let remote_sql = format!( - "SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = {} ORDER BY table_name", - duckdb_quote_string(schema) - ); - duckdb_quack_remote_query(con, alias, &remote_sql, |row| { - Ok(db::TableInfo { - name: row.get::<_, String>(0)?, - table_type: row.get::<_, String>(1)?, - comment: None, - parent_schema: None, - parent_name: None, - }) - }) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_quack_remote_columns( - con: &duckdb::Connection, - alias: &str, - schema: &str, - table: &str, -) -> Option> { - let pk_sql = format!( - "SELECT kcu.column_name - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema - AND tc.table_name = kcu.table_name - WHERE tc.constraint_type = 'PRIMARY KEY' - AND tc.table_schema = {schema} - AND tc.table_name = {table} - ORDER BY kcu.ordinal_position", - schema = duckdb_quote_string(schema), - table = duckdb_quote_string(table), - ); - let primary_keys: std::collections::HashSet = - duckdb_quack_remote_query(con, alias, &pk_sql, |row| row.get::<_, String>(0)) - .map(|names| names.into_iter().collect()) - .unwrap_or_default(); - - let columns_sql = format!( - "SELECT column_name, data_type, is_nullable, column_default - FROM information_schema.columns - WHERE table_schema = {schema} AND table_name = {table} - ORDER BY ordinal_position", - schema = duckdb_quote_string(schema), - table = duckdb_quote_string(table), - ); - duckdb_quack_remote_query(con, alias, &columns_sql, |row| { - let name = row.get::<_, String>(0)?; - Ok(db::ColumnInfo { - is_primary_key: primary_keys.contains(&name), - name, - data_type: row.get::<_, String>(1)?, - is_nullable: row.get::<_, String>(2).unwrap_or_default() == "YES", - column_default: row.get::<_, Option>(3)?, - extra: None, - comment: None, - numeric_precision: None, - numeric_scale: None, - character_maximum_length: None, - enum_values: None, - ..Default::default() - }) - }) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_catalog_name(con: &duckdb::Connection, database: &str, attached_names: &[String]) -> Result { - if database.trim().is_empty() || database == "main" { - return duckdb_primary_catalog(con, attached_names); - } - Ok(database.to_string()) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_primary_catalog(con: &duckdb::Connection, attached_names: &[String]) -> Result { - if attached_names.is_empty() { - return duckdb_current_database(con); - } - let attached: std::collections::HashSet = attached_names.iter().map(|name| name.to_lowercase()).collect(); - let mut stmt = con.prepare("SHOW DATABASES").map_err(|e| e.to_string())?; - let rows = stmt.query_map([], |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; - for row in rows { - let name = row.map_err(|e| e.to_string())?; - if !attached.contains(&name.to_lowercase()) { - return Ok(name); - } - } - duckdb_current_database(con) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_current_database(con: &duckdb::Connection) -> Result { - con.query_row("SELECT current_database()", [], |row| row.get::<_, String>(0)).map_err(|e| e.to_string()) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_quote_ident(value: &str) -> String { - format!("\"{}\"", value.replace('"', "\"\"")) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_quote_string(value: &str) -> String { - format!("'{}'", value.replace('\'', "''")) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_query_columns(con: &duckdb::Connection, table: &str) -> Result, String> { - duckdb_query_columns_in_database(con, "main", "main", table) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_query_columns_in_database( - con: &duckdb::Connection, - database: &str, - schema: &str, - table: &str, -) -> Result, String> { - duckdb_query_columns_in_database_with_attached(con, database, schema, table, &[]) -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_query_columns_in_database_with_attached( - con: &duckdb::Connection, - database: &str, - schema: &str, - table: &str, - attached_names: &[String], -) -> Result, String> { - let database = duckdb_catalog_name(con, database, attached_names)?; - let mut pk_stmt = con - .prepare( - "SELECT kcu.column_name - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema - AND tc.table_name = kcu.table_name - WHERE tc.constraint_type = 'PRIMARY KEY' - AND tc.table_catalog = ? - AND tc.table_schema = ? - AND tc.table_name = ? - ORDER BY kcu.ordinal_position", - ) - .map_err(|e| e.to_string())?; - let pk_rows = pk_stmt - .query_map((database.as_str(), schema, table), |row| row.get::<_, String>(0)) - .map_err(|e| e.to_string())?; - let primary_keys: std::collections::HashSet = pk_rows.filter_map(|r| r.ok()).collect(); - let column_comments = duckdb_column_comments(con, &database, schema, table); - - let mut stmt = con - .prepare( - "SELECT column_name, data_type, is_nullable, column_default - FROM information_schema.columns - WHERE table_catalog = ? AND table_schema = ? AND table_name = ? - ORDER BY ordinal_position", - ) - .map_err(|e| e.to_string())?; - let rows = stmt - .query_map((database.as_str(), schema, table), |row| { - let name = row.get::<_, String>(0)?; - let comment = column_comments.get(&name).cloned().flatten(); - Ok(db::ColumnInfo { - is_primary_key: primary_keys.contains(&name), - name, - data_type: row.get::<_, String>(1)?, - is_nullable: row.get::<_, String>(2).unwrap_or_default() == "YES", - column_default: row.get::<_, Option>(3)?, - extra: None, - comment, - numeric_precision: None, - numeric_scale: None, - character_maximum_length: None, - enum_values: None, - ..Default::default() - }) - }) - .map_err(|e| e.to_string())?; - let columns: Vec = rows.filter_map(|r| r.ok()).collect(); - if columns.is_empty() && duckdb_is_quack_catalog(con, &database) { - if let Some(remote) = duckdb_quack_remote_columns(con, &database, schema, table) { - return Ok(remote); - } - } - Ok(columns) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_column_comments( - con: &duckdb::Connection, - database: &str, - schema: &str, - table: &str, -) -> HashMap> { - let Ok(mut stmt) = con.prepare( - "SELECT column_name, comment FROM duckdb_columns() \ - WHERE database_name = ? AND schema_name = ? AND table_name = ?", - ) else { - // Older DuckDB versions may not expose the comment column; keep metadata browsing functional. - return HashMap::new(); - }; - let Ok(rows) = stmt - .query_map((database, schema, table), |row| Ok((row.get::<_, String>(0)?, row.get::<_, Option>(1)?))) - else { - return HashMap::new(); - }; - rows.filter_map(Result::ok).collect() -} - -#[cfg(feature = "duckdb-bundled")] -pub fn duckdb_completion_assistant_search( - con: &duckdb::Connection, - request: &db::CompletionAssistantRequest, - attached_names: &[String], -) -> Result { - let limit = request.max_results.unwrap_or(100).clamp(1, 1000); - let kinds = if request.object_kinds.is_empty() { - vec![db::CompletionAssistantObjectKind::Table, db::CompletionAssistantObjectKind::View] - } else { - request.object_kinds.clone() - }; - let mut candidates = Vec::new(); - - if kinds.iter().any(|kind| matches!(kind, db::CompletionAssistantObjectKind::Schema)) { - candidates.extend(duckdb_completion_schemas(con, request, attached_names, limit)?); - if candidates.len() >= limit { - return Ok(db::CompletionAssistantResponse { candidates, incomplete: true, fallback_used: false }); - } - } - - if kinds.iter().any(db::CompletionAssistantObjectKind::is_table_like) { - candidates.extend(duckdb_completion_tables(con, request, &kinds, attached_names, limit - candidates.len())?); - if candidates.len() >= limit { - return Ok(db::CompletionAssistantResponse { candidates, incomplete: true, fallback_used: false }); - } - } - - if kinds.iter().any(|kind| matches!(kind, db::CompletionAssistantObjectKind::Column)) { - candidates.extend(duckdb_completion_columns(con, request, attached_names, limit - candidates.len())?); - if candidates.len() >= limit { - return Ok(db::CompletionAssistantResponse { candidates, incomplete: true, fallback_used: false }); - } - } - - Ok(db::CompletionAssistantResponse { candidates, incomplete: false, fallback_used: false }) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_completion_schemas( - con: &duckdb::Connection, - request: &db::CompletionAssistantRequest, - attached_names: &[String], - limit: usize, -) -> Result, String> { - if limit == 0 { - return Ok(Vec::new()); - } - let database = duckdb_catalog_name(con, &request.database, attached_names)?; - let pattern = duckdb_completion_like_pattern(request); - let mut stmt = con - .prepare( - "SELECT schema_name - FROM information_schema.schemata - WHERE catalog_name = ? - AND schema_name NOT IN ('information_schema', 'pg_catalog') - AND lower(schema_name) LIKE lower(?) ESCAPE '\\' - ORDER BY schema_name - LIMIT ?", - ) - .map_err(|e| e.to_string())?; - let rows = stmt - .query_map((database.as_str(), pattern.as_str(), limit as i64), |row| { - let schema = row.get::<_, String>(0)?; - Ok(db::CompletionAssistantCandidate { - name: schema.clone(), - kind: db::CompletionAssistantCandidateKind::Schema, - database: Some(request.database.clone()), - schema: Some(schema), - parent_schema: None, - parent_name: None, - comment: None, - data_type: None, - signature: None, - }) - }) - .map_err(|e| e.to_string())?; - Ok(rows.filter_map(|row| row.ok()).collect()) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_completion_tables( - con: &duckdb::Connection, - request: &db::CompletionAssistantRequest, - kinds: &[db::CompletionAssistantObjectKind], - attached_names: &[String], - limit: usize, -) -> Result, String> { - if limit == 0 { - return Ok(Vec::new()); - } - let database = duckdb_catalog_name(con, &request.database, attached_names)?; - let schema = request.parent_schema.as_deref().or(request.schema.as_deref()).unwrap_or("main"); - let include_tables = kinds.iter().any(|kind| matches!(kind, db::CompletionAssistantObjectKind::Table)); - let include_views = kinds.iter().any(|kind| matches!(kind, db::CompletionAssistantObjectKind::View)); - let pattern = duckdb_completion_like_pattern(request); - let mut stmt = con - .prepare( - "SELECT table_name, table_type - FROM information_schema.tables - WHERE table_catalog = ? - AND table_schema = ? - AND ((? AND table_type = 'BASE TABLE') OR (? AND table_type = 'VIEW')) - AND lower(table_name) LIKE lower(?) ESCAPE '\\' - ORDER BY table_name - LIMIT ?", - ) - .map_err(|e| e.to_string())?; - let rows = stmt - .query_map((database.as_str(), schema, include_tables, include_views, pattern.as_str(), limit as i64), |row| { - let table_type = row.get::<_, String>(1)?; - Ok(db::CompletionAssistantCandidate { - name: row.get(0)?, - kind: if table_type.eq_ignore_ascii_case("VIEW") { - db::CompletionAssistantCandidateKind::View - } else { - db::CompletionAssistantCandidateKind::Table - }, - database: Some(request.database.clone()), - schema: Some(schema.to_string()), - parent_schema: None, - parent_name: None, - comment: None, - data_type: None, - signature: None, - }) - }) - .map_err(|e| e.to_string())?; - Ok(rows.filter_map(|row| row.ok()).collect()) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_completion_columns( - con: &duckdb::Connection, - request: &db::CompletionAssistantRequest, - attached_names: &[String], - limit: usize, -) -> Result, String> { - if limit == 0 { - return Ok(Vec::new()); - } - let Some(table) = request.parent_name.as_deref().filter(|table| !table.trim().is_empty()) else { - return Ok(Vec::new()); - }; - let database = duckdb_catalog_name(con, &request.database, attached_names)?; - let schema = request.parent_schema.as_deref().or(request.schema.as_deref()).unwrap_or("main"); - let pattern = duckdb_completion_like_pattern(request); - let mut stmt = con - .prepare( - "SELECT column_name, data_type - FROM information_schema.columns - WHERE table_catalog = ? - AND table_schema = ? - AND table_name = ? - AND lower(column_name) LIKE lower(?) ESCAPE '\\' - ORDER BY ordinal_position - LIMIT ?", - ) - .map_err(|e| e.to_string())?; - let rows = stmt - .query_map((database.as_str(), schema, table, pattern.as_str(), limit as i64), |row| { - Ok(db::CompletionAssistantCandidate { - name: row.get(0)?, - kind: db::CompletionAssistantCandidateKind::Column, - database: Some(request.database.clone()), - schema: Some(schema.to_string()), - parent_schema: Some(schema.to_string()), - parent_name: Some(table.to_string()), - comment: None, - data_type: Some(row.get(1)?), - signature: None, - }) - }) - .map_err(|e| e.to_string())?; - Ok(rows.filter_map(|row| row.ok()).collect()) -} - -#[cfg(feature = "duckdb-bundled")] -fn duckdb_completion_like_pattern(request: &db::CompletionAssistantRequest) -> String { - let mask = request.mask.trim().trim_matches('%'); - let escaped = mask.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_"); - match request.match_mode.as_ref().unwrap_or(&db::CompletionAssistantMatchMode::Prefix) { - db::CompletionAssistantMatchMode::Prefix => format!("{escaped}%"), - db::CompletionAssistantMatchMode::Contains => format!("%{escaped}%"), - } -} - -#[cfg(feature = "duckdb-bundled")] -async fn duckdb_attached_database_names(state: &AppState, connection_id: &str) -> Vec { - state.configs.read().await.get(connection_id).map(crate::db::duckdb_sql::config_attached_names).unwrap_or_default() -} - -// ClickHouse 无 schema 概念:schema 参数携带 SQL 中 `库.表` 限定的目标库, -// database 参数是 tab/连接的当前库。与 mysql_table_metadata_catalog 一致, -// schema 非空时必须优先,否则跨库查询会在当前库下查不到元数据(字段注释丢失) fn clickhouse_metadata_database<'a>(database: &'a str, schema: &'a str) -> &'a str { if schema.trim().is_empty() { database @@ -1075,10 +476,6 @@ async fn list_databases_once(state: &AppState, connection_id: &str) -> Result Result Result db::sqlite::list_databases(p).await, PoolKind::Rqlite(client) => db::rqlite_driver::list_databases(client).await, PoolKind::HBase(client) => db::hbase_driver::list_namespaces(client).await, - #[cfg(feature = "duckdb-bundled")] - PoolKind::DuckDb(con) => { - let con = con.lock().map_err(|e| e.to_string())?; - duckdb_list_databases_with_attached(&con, &duckdb_attached_names) - } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] PoolKind::DuckDbWorker(client) => { let client = client.clone(); drop(connections); @@ -1340,14 +730,7 @@ async fn list_schemas_once( PoolKind::Postgres(p) => db::postgres::list_schemas_with_system(p, show_system_schemas) .await .map(|schemas| filter_visible_schema_names(schemas, visible_schema_filter.as_deref())), - #[cfg(feature = "duckdb-bundled")] - PoolKind::DuckDb(con) => { - let duckdb_attached_names = duckdb_attached_database_names(state, connection_id).await; - let con = con.lock().map_err(|e| e.to_string())?; - duckdb_list_schemas_with_attached(&con, database, &duckdb_attached_names) - .map(|schemas| filter_visible_schema_names(schemas, visible_schema_filter.as_deref())) - } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] PoolKind::DuckDbWorker(client) => { let client = client.clone(); let database = database.to_string(); @@ -2267,24 +1650,10 @@ async fn list_tables_once( table_name_filter: Option<&TableNameFilter>, ) -> Result, String> { let pool_key = state.get_or_create_pool(connection_id, Some(database)).await?; - #[cfg(feature = "duckdb-bundled")] - let duckdb_attached_names = duckdb_attached_database_names(state, connection_id).await; let db_config = connection_config(state, connection_id).await; { let connections = state.connections.read().await; - #[cfg(feature = "duckdb-bundled")] - if let Some(ext_pool) = extract_pool!(&connections, &pool_key, ExternalTabular) { - drop(connections); - let cache = ext_pool.cache.clone(); - return tokio::task::spawn_blocking(move || { - let con = cache.lock().map_err(|e| e.to_string())?; - duckdb_query_tables(&con) - }) - .await - .map_err(|e| e.to_string())? - .map(|tables| filter_table_infos(tables, filter, limit, offset, object_types, table_name_filter)); - } if let Some(PoolKind::ExternalDriver { config, session, .. }) = connections.get(&pool_key) { let config = config.clone(); let session = session.clone(); @@ -2320,14 +1689,7 @@ async fn list_tables_once( .await .map(|tables| filter_table_infos(tables, filter, limit, offset, object_types, table_name_filter)); } - #[cfg(feature = "duckdb-bundled")] - if let Some(con) = extract_pool!(&connections, &pool_key, DuckDb) { - drop(connections); - let con = con.lock().map_err(|e| e.to_string())?; - return duckdb_query_tables_in_database_with_attached(&con, database, schema, &duckdb_attached_names) - .map(|tables| filter_table_infos(tables, filter, limit, offset, object_types, table_name_filter)); - } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] if let Some(client) = extract_pool!(&connections, &pool_key, DuckDbWorker) { let database = database.to_string(); let schema = schema.to_string(); @@ -2996,11 +2358,6 @@ mod tests { tdengine_table_comment_like_pattern, tdengine_table_comment_sql, tdengine_table_comments_sql, visible_schema_filter, TableNameFilter, TDENGINE_COMMENT_SEARCH_TIMEOUT, TDENGINE_LIKE_PATTERN_MAX_BYTES, }; - #[cfg(feature = "duckdb-bundled")] - use super::{ - duckdb_attach_database, duckdb_completion_assistant_search, duckdb_list_databases, - duckdb_object_source_with_attached, duckdb_query_tables_in_database, - }; use crate::models::connection::{ConnectionConfig, DatabaseType}; use std::collections::HashMap; @@ -3602,187 +2959,6 @@ mod tests { assert_eq!(columns[1].character_maximum_length, Some(64)); } - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_list_databases_includes_attached_database() { - let unique = uuid::Uuid::new_v4(); - let path = std::env::temp_dir().join(format!("dbx-attached-{unique}.duckdb")); - let _ = std::fs::remove_file(&path); - let con = duckdb::Connection::open_in_memory().unwrap(); - - duckdb_attach_database(&con, "analytics", path.to_str().unwrap()).unwrap(); - let databases = duckdb_list_databases(&con).unwrap(); - - assert!(databases.iter().any(|database| database.name == "main")); - assert!(databases.iter().any(|database| database.name == "analytics")); - - let _ = std::fs::remove_file(path); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_query_tables_filters_by_attached_database() { - let unique = uuid::Uuid::new_v4(); - let path = std::env::temp_dir().join(format!("dbx-attached-tables-{unique}.duckdb")); - let _ = std::fs::remove_file(&path); - let con = duckdb::Connection::open_in_memory().unwrap(); - - con.execute_batch("CREATE TABLE main_table(id INTEGER);").unwrap(); - duckdb_attach_database(&con, "analytics", path.to_str().unwrap()).unwrap(); - con.execute_batch("CREATE TABLE analytics.attached_table(id INTEGER);").unwrap(); - - let main_tables = duckdb_query_tables_in_database(&con, "main", "main").unwrap(); - let attached_tables = duckdb_query_tables_in_database(&con, "analytics", "main").unwrap(); - - assert!(main_tables.iter().any(|table| table.name == "main_table")); - assert!(!main_tables.iter().any(|table| table.name == "attached_table")); - assert!(attached_tables.iter().any(|table| table.name == "attached_table")); - assert!(!attached_tables.iter().any(|table| table.name == "main_table")); - - let _ = std::fs::remove_file(path); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_query_columns_includes_column_comments() { - let con = duckdb::Connection::open_in_memory().unwrap(); - con.execute_batch( - "CREATE TABLE users (id INTEGER, name VARCHAR); \ - COMMENT ON COLUMN users.name IS 'Display name';", - ) - .unwrap(); - - let columns = super::duckdb_query_columns(&con, "users").unwrap(); - let name = columns.iter().find(|column| column.name == "name").unwrap(); - - assert_eq!(name.comment.as_deref(), Some("Display name")); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_object_source_filters_by_schema_and_view_name() { - let con = duckdb::Connection::open_in_memory().unwrap(); - con.execute_batch( - "CREATE SCHEMA reporting; \ - CREATE VIEW main.active_orders AS SELECT 'main' AS origin; \ - CREATE VIEW reporting.active_orders AS SELECT 'reporting' AS origin;", - ) - .unwrap(); - - let main_source = - duckdb_object_source_with_attached(&con, "main", "main", "active_orders", &db::ObjectSourceKind::View, &[]) - .unwrap(); - let reporting_source = duckdb_object_source_with_attached( - &con, - "main", - "reporting", - "active_orders", - &db::ObjectSourceKind::View, - &[], - ) - .unwrap(); - - assert!(main_source.contains("'main'")); - assert!(reporting_source.contains("'reporting'")); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_object_source_rejects_unsupported_or_missing_objects() { - let con = duckdb::Connection::open_in_memory().unwrap(); - - let unsupported = duckdb_object_source_with_attached( - &con, - "main", - "main", - "calculate_total", - &db::ObjectSourceKind::Function, - &[], - ) - .unwrap_err(); - let missing = - duckdb_object_source_with_attached(&con, "main", "main", "missing_view", &db::ObjectSourceKind::View, &[]) - .unwrap_err(); - - assert_eq!(unsupported, "DuckDB object source only supports views"); - assert!(missing.contains("DuckDB view source not found")); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_catalog_view_source_can_replace_existing_view() { - let con = duckdb::Connection::open_in_memory().unwrap(); - con.execute_batch("CREATE VIEW active_orders AS SELECT 1 AS value;").unwrap(); - let source = - duckdb_object_source_with_attached(&con, "main", "main", "active_orders", &db::ObjectSourceKind::View, &[]) - .unwrap(); - let edited_source = source.replace("SELECT 1", "SELECT 2"); - assert_ne!(edited_source, source); - - let statements = crate::object_source_sql::build_executable_object_source_statements( - crate::object_source_sql::EditableObjectSourceSqlInput { - database_type: DatabaseType::DuckDb, - object_type: db::ObjectSourceKind::View, - schema: Some("main".to_string()), - name: "active_orders".to_string(), - source: edited_source, - }, - ) - .unwrap(); - con.execute_batch(&statements.join("\n")).unwrap(); - - let value = con.query_row("SELECT value FROM active_orders", [], |row| row.get::<_, i32>(0)).unwrap(); - assert_eq!(value, 2); - } - - #[cfg(feature = "duckdb-bundled")] - #[test] - fn duckdb_completion_assistant_searches_catalog_metadata_with_limit() { - let con = duckdb::Connection::open_in_memory().unwrap(); - con.execute_batch( - "CREATE TABLE account(id INTEGER, display_name VARCHAR); CREATE VIEW account_view AS SELECT id FROM account;", - ) - .unwrap(); - - let request = db::CompletionAssistantRequest { - connection_id: "c1".to_string(), - database: "main".to_string(), - schema: Some("main".to_string()), - object_kinds: vec![db::CompletionAssistantObjectKind::Table, db::CompletionAssistantObjectKind::View], - mask: "account".to_string(), - case_sensitive: false, - global_search: false, - max_results: Some(1), - search_in_comments: false, - search_in_definitions: false, - parent_schema: Some("main".to_string()), - parent_name: None, - match_mode: Some(db::CompletionAssistantMatchMode::Prefix), - }; - - let tables = duckdb_completion_assistant_search(&con, &request, &[]).unwrap(); - assert_eq!(tables.candidates.len(), 1); - assert!(tables.incomplete); - assert!(!tables.fallback_used); - assert_eq!(tables.candidates[0].name, "account"); - - let columns = duckdb_completion_assistant_search( - &con, - &db::CompletionAssistantRequest { - object_kinds: vec![db::CompletionAssistantObjectKind::Column], - mask: "name".to_string(), - max_results: Some(10), - parent_name: Some("account".to_string()), - match_mode: Some(db::CompletionAssistantMatchMode::Contains), - ..request - }, - &[], - ) - .unwrap(); - assert_eq!(columns.candidates.len(), 1); - assert_eq!(columns.candidates[0].name, "display_name"); - } - #[test] fn detects_unsupported_agent_completion_assistant_errors() { assert!(super::is_agent_completion_assistant_unsupported( @@ -4415,14 +3591,12 @@ pub async fn completion_assistant_search_core( } } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] { - let duckdb_attached_names = duckdb_attached_database_names(state, &request.connection_id).await; let connections = state.connections.read().await; - if let Some(con) = extract_pool!(&connections, &pool_key, DuckDb) { + if let Some(client) = extract_pool!(&connections, &pool_key, DuckDbWorker) { drop(connections); - let con = con.lock().map_err(|e| e.to_string())?; - return duckdb_completion_assistant_search(&con, &request, &duckdb_attached_names); + return client.completion_assistant(request.clone()).await; } } @@ -4728,32 +3902,6 @@ async fn list_objects_once( { let connections = state.connections.read().await; - #[cfg(feature = "duckdb-bundled")] - if let Some(ext_pool) = extract_pool!(&connections, &pool_key, ExternalTabular) { - drop(connections); - let cache = ext_pool.cache.clone(); - let objects = tokio::task::spawn_blocking(move || { - let con = cache.lock().map_err(|e| e.to_string())?; - Ok(duckdb_query_tables(&con)? - .into_iter() - .map(|table| db::ObjectInfo { - name: table.name, - object_type: table.table_type, - schema: None, - valid: None, - signature: None, - comment: table.comment, - created_at: None, - updated_at: None, - parent_schema: table.parent_schema, - parent_name: table.parent_name, - }) - .collect()) - }) - .await - .map_err(|e| e.to_string())?; - return objects.map(unpaged_object_list); - } if let Some(PoolKind::ExternalDriver { config, session, .. }) = connections.get(&pool_key) { let config = config.clone(); let session = session.clone(); @@ -5137,24 +4285,10 @@ pub async fn get_columns_core_for_session( let pool_key = state .get_or_create_pool_for_session(connection_id, Some(database), client_session_id) .await?; - #[cfg(feature = "duckdb-bundled")] - let duckdb_attached_names = duckdb_attached_database_names(state, connection_id).await; let db_config = connection_config(state, connection_id).await; { let connections = state.connections.read().await; - #[cfg(feature = "duckdb-bundled")] - if let Some(ext_pool) = extract_pool!(&connections, &pool_key, ExternalTabular) { - drop(connections); - let cache = ext_pool.cache.clone(); - let table = table.to_string(); - return tokio::task::spawn_blocking(move || { - let con = cache.lock().map_err(|e| e.to_string())?; - duckdb_query_columns(&con, &table) - }) - .await - .map_err(|e| e.to_string())?; - } if let Some(PoolKind::ExternalDriver { config, session, .. }) = connections.get(&pool_key) { let config = config.clone(); let session = session.clone(); @@ -5226,19 +4360,7 @@ pub async fn get_columns_core_for_session( } return Ok(deduplicate_column_infos(columns)); } - #[cfg(feature = "duckdb-bundled")] - if let Some(con) = extract_pool!(&connections, &pool_key, DuckDb) { - drop(connections); - let con = con.lock().map_err(|e| e.to_string())?; - return duckdb_query_columns_in_database_with_attached( - &con, - database, - schema, - table, - &duckdb_attached_names, - ); - } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] if let Some(client) = extract_pool!(&connections, &pool_key, DuckDbWorker) { let database = database.to_string(); let schema = schema.to_string(); @@ -5921,19 +5043,11 @@ async fn get_table_ddl_core_with_options( { let connections = state.connections.read().await; - #[cfg(feature = "duckdb-bundled")] - if let Some(con) = extract_pool!(&connections, &pool_key, DuckDb) { + #[cfg(feature = "duckdb-sidecar")] + if let Some(client) = extract_pool!(&connections, &pool_key, DuckDbWorker) { + let client = client.clone(); drop(connections); - let tbl = table.replace('\'', "''"); - let con = con.lock().map_err(|e| e.to_string())?; - let mut stmt = con - .prepare(&format!("SELECT sql FROM duckdb_tables() WHERE table_name = '{tbl}'")) - .map_err(|e| e.to_string())?; - let mut rows = stmt.query([]).map_err(|e| e.to_string())?; - if let Some(row) = rows.next().map_err(|e| e.to_string())? { - return row.get::<_, String>(0).map_err(|e| e.to_string()); - } - return Err("Table not found".to_string()); + return client.get_table_ddl(database.to_string(), schema.to_string(), table.to_string()).await; } if let Some(client) = extract_pool!(&connections, &pool_key, ClickHouse) { drop(connections); @@ -6614,8 +5728,6 @@ async fn get_object_source_once( ) -> Result { let pool_key = state.get_or_create_pool(connection_id, Some(database)).await?; let db_config = connection_config(state, connection_id).await; - #[cfg(feature = "duckdb-bundled")] - let duckdb_attached_names = duckdb_attached_database_names(state, connection_id).await; let source = { let connections = state.connections.read().await; if let Some(PoolKind::ExternalDriver { config, session, .. }) = connections.get(&pool_key) { @@ -6696,19 +5808,7 @@ async fn get_object_source_once( PoolKind::Sqlite(pool) => first_string_cell( db::sqlite::execute_query(pool, &sqlite_object_source_sql(schema, name, &object_type)).await?, )?, - #[cfg(feature = "duckdb-bundled")] - PoolKind::DuckDb(con) => { - let con = con.lock().map_err(|e| e.to_string())?; - duckdb_object_source_with_attached( - &con, - database, - schema, - name, - &object_type, - &duckdb_attached_names, - )? - } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] PoolKind::DuckDbWorker(client) => { let client = client.clone(); let database = database.to_string(); diff --git a/crates/dbx-core/src/schema/duckdb_metadata.rs b/crates/dbx-core/src/schema/duckdb_metadata.rs deleted file mode 100644 index 9ef0a133b5..0000000000 --- a/crates/dbx-core/src/schema/duckdb_metadata.rs +++ /dev/null @@ -1,203 +0,0 @@ -use crate::connection::AppState; -use crate::db; - -pub fn duckdb_query_tables(con: &duckdb::Connection) -> Result, String> { - duckdb_query_tables_in_database(con, "main", "main") -} - -pub fn duckdb_query_tables_in_database( - con: &duckdb::Connection, - database: &str, - schema: &str, -) -> Result, String> { - duckdb_query_tables_in_database_with_attached(con, database, schema, &[]) -} - -pub fn duckdb_query_tables_in_database_with_attached( - con: &duckdb::Connection, - database: &str, - schema: &str, - attached_names: &[String], -) -> Result, String> { - let database = duckdb_catalog_name(con, database, attached_names)?; - let mut stmt = con - .prepare( - "SELECT table_name, table_type FROM information_schema.tables WHERE table_catalog = ? AND table_schema = ? ORDER BY table_name", - ) - .map_err(|e| e.to_string())?; - let rows = stmt - .query_map((database.as_str(), schema), |row| { - Ok(db::TableInfo { - name: row.get::<_, String>(0)?, - table_type: row.get::<_, String>(1)?, - comment: None, - parent_schema: None, - parent_name: None, - }) - }) - .map_err(|e| e.to_string())?; - Ok(rows.filter_map(|r| r.ok()).collect()) -} - -pub fn duckdb_attach_database(con: &duckdb::Connection, name: &str, path: &str) -> Result<(), String> { - let name = name.trim(); - let path = path.trim(); - if name.is_empty() || path.is_empty() { - return Err("DuckDB attached database name and path are required".to_string()); - } - let sql = format!("ATTACH {} AS {}", duckdb_quote_string(path), duckdb_quote_ident(name)); - con.execute_batch(&sql).map_err(|e| e.to_string()) -} - -pub fn duckdb_list_databases(con: &duckdb::Connection) -> Result, String> { - duckdb_list_databases_with_attached(con, &[]) -} - -pub fn duckdb_list_databases_with_attached( - con: &duckdb::Connection, - attached_names: &[String], -) -> Result, String> { - let primary = duckdb_primary_catalog(con, attached_names)?; - let mut stmt = con.prepare("SHOW DATABASES").map_err(|e| e.to_string())?; - let rows = stmt - .query_map([], |row| { - let name = row.get::<_, String>(0)?; - Ok(db::DatabaseInfo { name: if name == primary { "main".to_string() } else { name } }) - }) - .map_err(|e| e.to_string())?; - Ok(rows.filter_map(|row| row.ok()).collect()) -} - -pub fn duckdb_list_schemas(con: &duckdb::Connection, database: &str) -> Result, String> { - duckdb_list_schemas_with_attached(con, database, &[]) -} - -pub fn duckdb_list_schemas_with_attached( - con: &duckdb::Connection, - database: &str, - attached_names: &[String], -) -> Result, String> { - let database = duckdb_catalog_name(con, database, attached_names)?; - let mut stmt = con - .prepare( - "SELECT schema_name FROM information_schema.schemata WHERE catalog_name = ? AND schema_name NOT IN ('information_schema', 'pg_catalog') ORDER BY schema_name", - ) - .map_err(|e| e.to_string())?; - let rows = stmt.query_map([database.as_str()], |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; - Ok(rows.filter_map(|r| r.ok()).collect()) -} - -fn duckdb_catalog_name(con: &duckdb::Connection, database: &str, attached_names: &[String]) -> Result { - if database.trim().is_empty() || database == "main" { - return duckdb_primary_catalog(con, attached_names); - } - Ok(database.to_string()) -} - -pub fn duckdb_primary_catalog(con: &duckdb::Connection, attached_names: &[String]) -> Result { - if attached_names.is_empty() { - return duckdb_current_database(con); - } - let attached: std::collections::HashSet = attached_names.iter().map(|name| name.to_lowercase()).collect(); - let mut stmt = con.prepare("SHOW DATABASES").map_err(|e| e.to_string())?; - let rows = stmt.query_map([], |row| row.get::<_, String>(0)).map_err(|e| e.to_string())?; - for row in rows { - let name = row.map_err(|e| e.to_string())?; - if !attached.contains(&name.to_lowercase()) { - return Ok(name); - } - } - duckdb_current_database(con) -} - -fn duckdb_current_database(con: &duckdb::Connection) -> Result { - con.query_row("SELECT current_database()", [], |row| row.get::<_, String>(0)).map_err(|e| e.to_string()) -} - -fn duckdb_quote_ident(value: &str) -> String { - format!("\"{}\"", value.replace('"', "\"\"")) -} - -fn duckdb_quote_string(value: &str) -> String { - format!("'{}'", value.replace('\'', "''")) -} - -pub fn duckdb_query_columns(con: &duckdb::Connection, table: &str) -> Result, String> { - duckdb_query_columns_in_database(con, "main", "main", table) -} - -pub fn duckdb_query_columns_in_database( - con: &duckdb::Connection, - database: &str, - schema: &str, - table: &str, -) -> Result, String> { - duckdb_query_columns_in_database_with_attached(con, database, schema, table, &[]) -} - -pub fn duckdb_query_columns_in_database_with_attached( - con: &duckdb::Connection, - database: &str, - schema: &str, - table: &str, - attached_names: &[String], -) -> Result, String> { - let database = duckdb_catalog_name(con, database, attached_names)?; - let mut pk_stmt = con - .prepare( - "SELECT kcu.column_name - FROM information_schema.table_constraints tc - JOIN information_schema.key_column_usage kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema - AND tc.table_name = kcu.table_name - WHERE tc.constraint_type = 'PRIMARY KEY' - AND tc.table_catalog = ? - AND tc.table_schema = ? - AND tc.table_name = ? - ORDER BY kcu.ordinal_position", - ) - .map_err(|e| e.to_string())?; - let pk_rows = pk_stmt - .query_map((database.as_str(), schema, table), |row| row.get::<_, String>(0)) - .map_err(|e| e.to_string())?; - let primary_keys: std::collections::HashSet = pk_rows.filter_map(|r| r.ok()).collect(); - - let mut stmt = con - .prepare( - "SELECT column_name, data_type, is_nullable, column_default - FROM information_schema.columns - WHERE table_catalog = ? AND table_schema = ? AND table_name = ? - ORDER BY ordinal_position", - ) - .map_err(|e| e.to_string())?; - let rows = stmt - .query_map((database.as_str(), schema, table), |row| { - let name = row.get::<_, String>(0)?; - Ok(db::ColumnInfo { - is_primary_key: primary_keys.contains(&name), - name, - data_type: row.get::<_, String>(1)?, - is_nullable: row.get::<_, String>(2).unwrap_or_default() == "YES", - column_default: row.get::<_, Option>(3)?, - extra: None, - comment: None, - numeric_precision: None, - numeric_scale: None, - character_maximum_length: None, - ..Default::default() - }) - }) - .map_err(|e| e.to_string())?; - Ok(rows.filter_map(|r| r.ok()).collect()) -} - -pub(super) async fn duckdb_attached_database_names(state: &AppState, connection_id: &str) -> Vec { - state - .configs - .read() - .await - .get(connection_id) - .map(crate::db::duckdb_sql::config_attached_names) - .unwrap_or_default() -} diff --git a/crates/dbx-core/src/table_structure_sql/dialect.rs b/crates/dbx-core/src/table_structure_sql/dialect.rs index 002fad5348..411f4834e2 100644 --- a/crates/dbx-core/src/table_structure_sql/dialect.rs +++ b/crates/dbx-core/src/table_structure_sql/dialect.rs @@ -6,7 +6,7 @@ pub(super) enum StructureDialect { Doris, Postgres, Sqlite, - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] DuckDb, SqlServer, Oracle, @@ -171,7 +171,7 @@ pub(super) fn capabilities_for(database_type: Option) -> TableStru index_filter: true, ..base }, - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] Some(DatabaseType::DuckDb) => TableStructureCapabilities { dialect: StructureDialect::DuckDb, add_column: true, @@ -305,7 +305,7 @@ pub(super) fn dialect_label(dialect: StructureDialect) -> String { StructureDialect::Doris => "doris", StructureDialect::Postgres => "postgres", StructureDialect::Sqlite => "sqlite", - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] StructureDialect::DuckDb => "duckdb", StructureDialect::SqlServer => "sqlserver", StructureDialect::Oracle => "oracle", @@ -326,7 +326,7 @@ pub(super) fn database_type_for_dialect(dialect: StructureDialect) -> Option Some(DatabaseType::Doris), StructureDialect::Postgres => Some(DatabaseType::Postgres), StructureDialect::Sqlite => Some(DatabaseType::Sqlite), - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] StructureDialect::DuckDb => Some(DatabaseType::DuckDb), StructureDialect::SqlServer => Some(DatabaseType::SqlServer), StructureDialect::Oracle => Some(DatabaseType::Oracle), diff --git a/crates/dbx-core/src/table_structure_sql/tests.rs b/crates/dbx-core/src/table_structure_sql/tests.rs index e9cf665004..54c1be3ec9 100644 --- a/crates/dbx-core/src/table_structure_sql/tests.rs +++ b/crates/dbx-core/src/table_structure_sql/tests.rs @@ -2465,7 +2465,7 @@ fn sqlserver_existing_column_identity_change_warns_without_unchanged_foreign_key ); } -#[cfg(feature = "duckdb-bundled")] +#[cfg(feature = "duckdb-sidecar")] #[test] fn builds_duckdb_create_table_statements() { let mut name = column("name"); diff --git a/crates/dbx-core/src/transfer.rs b/crates/dbx-core/src/transfer.rs index a8a9c85e46..aeb0a22eec 100644 --- a/crates/dbx-core/src/transfer.rs +++ b/crates/dbx-core/src/transfer.rs @@ -10,8 +10,6 @@ use crate::models::connection::DatabaseType; use crate::object_source_sql::{build_executable_object_source_statements, EditableObjectSourceSqlInput}; use crate::query::{agent_execute_query_params, pool_error_action, PoolErrorAction, QueryExecutionOptions}; use crate::sql::split_sql_statements; -#[cfg(feature = "duckdb-bundled")] -use crate::sql::starts_with_executable_sql_keyword; use crate::sql_dialect::{qualified_transfer_table, quote_transfer_identifier}; static CANCELLED: std::sync::LazyLock>> = @@ -3048,89 +3046,12 @@ async fn execute_on_pool_once( ); client.execute_query(params).await } - #[cfg(feature = "duckdb-bundled")] - PoolKind::DuckDb(con) => { - let con = con.clone(); + #[cfg(feature = "duckdb-sidecar")] + PoolKind::DuckDbWorker(client) => { + let client = client.clone(); let sql = sql.to_string(); drop(connections); - tokio::task::spawn_blocking(move || { - let con = con.lock().map_err(|e| e.to_string())?; - if max_rows.is_some() - && starts_with_executable_sql_keyword(&sql, &["SELECT", "SHOW", "DESCRIBE", "WITH", "PRAGMA"]) - { - return crate::query::duckdb_execute_with_max_rows(&con, &sql, max_rows); - } - let start = std::time::Instant::now(); - if starts_with_executable_sql_keyword(&sql, &["SELECT", "SHOW", "DESCRIBE", "WITH", "PRAGMA"]) { - let mut stmt = con.prepare(&sql).map_err(|e| e.to_string())?; - let mut rows = stmt.query([]).map_err(|e| e.to_string())?; - let stmt_ref = rows.as_ref().ok_or("DuckDB statement unavailable")?; - let col_count = stmt_ref.column_count(); - let columns: Vec = (0..col_count) - .map(|i| stmt_ref.column_name(i).map(|s| s.to_string()).unwrap_or_else(|_| "?".to_string())) - .collect(); - let mut result_rows = Vec::new(); - while let Some(row) = rows.next().map_err(|e| e.to_string())? { - let vals: Vec = (0..col_count) - .map(|i| { - row.get::<_, String>(i) - .map(serde_json::Value::String) - .or_else(|_| row.get::<_, i64>(i).map(|v| serde_json::Value::Number(v.into()))) - .or_else(|_| { - row.get::<_, f64>(i).map(|v| { - serde_json::Number::from_f64(v) - .map(serde_json::Value::Number) - .unwrap_or(serde_json::Value::Null) - }) - }) - .or_else(|_| row.get::<_, bool>(i).map(serde_json::Value::Bool)) - .unwrap_or(serde_json::Value::Null) - }) - .collect(); - result_rows.push(vals); - } - Ok(db::QueryResult { - columns, - column_types: Vec::new(), - column_sortables: vec![], - rows: result_rows, - affected_rows: 0, - execution_time_ms: start.elapsed().as_millis(), - truncated: false, - session_id: None, - has_more: false, - elasticsearch_raw_body: None, - }) - } else { - let affected = con.execute(&sql, []).map_err(|e| e.to_string())?; - Ok(db::QueryResult { - columns: vec![], - column_types: Vec::new(), - column_sortables: vec![], - rows: vec![], - affected_rows: affected as u64, - execution_time_ms: start.elapsed().as_millis(), - truncated: false, - session_id: None, - has_more: false, - elasticsearch_raw_body: None, - }) - } - }) - .await - .map_err(|e| e.to_string())? - } - #[cfg(feature = "duckdb-bundled")] - PoolKind::ExternalTabular(ext_pool) => { - let con = ext_pool.cache.clone(); - let sql = sql.to_string(); - drop(connections); - tokio::task::spawn_blocking(move || { - let con = con.lock().map_err(|e| e.to_string())?; - crate::query::duckdb_execute_with_max_rows(&con, &sql, max_rows) - }) - .await - .map_err(|e| e.to_string())? + client.execute(None, sql, max_rows, None, None).await } _ => Err("Unsupported database type for transfer".to_string()), } @@ -3161,32 +3082,14 @@ pub async fn get_columns_for_transfer( ) -> Result, String> { let connections = state.connections.read().await; - #[cfg(feature = "duckdb-bundled")] - if let Some(PoolKind::DuckDb(con)) = connections.get(pool_key) { - let con = con.clone(); - drop(connections); - let table = table.to_string(); + #[cfg(feature = "duckdb-sidecar")] + if let Some(PoolKind::DuckDbWorker(client)) = connections.get(pool_key) { + let client = client.clone(); + let database = database.to_string(); let schema = schema.to_string(); - return tokio::task::spawn_blocking(move || { - let con = con.lock().map_err(|e| e.to_string())?; - crate::schema::duckdb_query_columns_in_database(&con, "main", &schema, &table) - }) - .await - .map_err(|e| e.to_string())?; - } - - #[cfg(feature = "duckdb-bundled")] - if let Some(PoolKind::ExternalTabular(ext_pool)) = connections.get(pool_key) { - let con = ext_pool.cache.clone(); - drop(connections); let table = table.to_string(); - let schema = schema.to_string(); - return tokio::task::spawn_blocking(move || { - let con = con.lock().map_err(|e| e.to_string())?; - crate::schema::duckdb_query_columns_in_database(&con, "main", &schema, &table) - }) - .await - .map_err(|e| e.to_string())?; + drop(connections); + return client.list_columns(database, schema, table).await; } if let Some(PoolKind::ClickHouse(client)) = connections.get(pool_key) { @@ -5029,73 +4932,7 @@ where #[cfg(test)] mod tests { use super::*; - #[cfg(feature = "duckdb-bundled")] - use crate::connection::{AppState, PoolKind}; - #[cfg(feature = "duckdb-bundled")] - use crate::models::connection::default_redis_key_separator; - #[cfg(feature = "duckdb-bundled")] - use crate::storage::Storage; use serde_json::json; - #[cfg(feature = "duckdb-bundled")] - use std::sync::Arc; - - #[cfg(feature = "duckdb-bundled")] - fn duckdb_test_config(id: &str) -> crate::models::connection::ConnectionConfig { - crate::models::connection::ConnectionConfig { - id: id.to_string(), - name: id.to_string(), - note: String::new(), - db_type: DatabaseType::DuckDb, - driver_profile: None, - driver_label: None, - url_params: None, - agent_java_options: Vec::new(), - host: ":memory:".to_string(), - port: 0, - username: String::new(), - password: String::new(), - database: None, - visible_databases: None, - visible_schemas: None, - show_system_schemas: false, - attached_databases: Vec::new(), - init_script: None, - color: None, - transport_layers: Vec::new(), - connect_timeout_secs: 5, - query_timeout_secs: 30, - idle_timeout_secs: 60, - keepalive_interval_secs: 0, - ssl: false, - ca_cert_path: String::new(), - client_cert_path: String::new(), - client_key_path: String::new(), - sysdba: false, - oracle_connection_type: None, - connection_string: None, - redis_connection_mode: None, - redis_sentinel_master: String::new(), - redis_sentinel_nodes: String::new(), - redis_sentinel_username: String::new(), - redis_sentinel_password: String::new(), - redis_sentinel_tls: false, - redis_cluster_nodes: String::new(), - redis_key_separator: default_redis_key_separator(), - redis_scan_page_size: None, - redis_database_aliases: Default::default(), - etcd_endpoints: String::new(), - gbase_server: String::new(), - informix_server: String::new(), - external_config: None, - jdbc_driver_class: None, - jdbc_driver_paths: Vec::new(), - one_time: false, - read_only: false, - is_production: false, - production_databases: vec![], - database_info: None, - } - } fn test_column(name: &str, data_type: &str) -> db::ColumnInfo { db::ColumnInfo { @@ -6863,26 +6700,6 @@ SELECT 1 FROM dual"# assert!(statements[0].contains("ON DUPLICATE KEY UPDATE")); } - #[cfg(feature = "duckdb-bundled")] - #[tokio::test] - async fn duckdb_transfer_columns_use_requested_schema() { - let dir = std::env::temp_dir().join(format!("dbx-transfer-test-{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&dir).unwrap(); - let storage = Storage::open(&dir.join("storage.db")).await.unwrap(); - let con = duckdb::Connection::open_in_memory().unwrap(); - con.execute_batch("CREATE SCHEMA analytics; CREATE TABLE analytics.items(id INTEGER);").unwrap(); - - let state = AppState::new(storage); - let con = Arc::new(crate::db::duckdb_driver::DuckDbConnection::new(con)); - state.connections.write().await.insert("duckdb-1".to_string(), PoolKind::DuckDb(con)); - state.configs.write().await.insert("duckdb-1".to_string(), duckdb_test_config("duckdb-1")); - - let columns = - get_columns_for_transfer(&state, "duckdb-1", "duckdb-1", "main", "analytics", "items").await.unwrap(); - - assert_eq!(columns.iter().map(|c| c.name.as_str()).collect::>(), vec!["id"]); - } - #[test] fn database_from_pool_key_handles_session_scoped_keys() { assert_eq!(database_from_pool_key("conn:analytics"), Some("analytics")); diff --git a/crates/dbx-core/tests/agent_service.rs b/crates/dbx-core/tests/agent_service.rs index 0954700684..e65d8ffeab 100644 --- a/crates/dbx-core/tests/agent_service.rs +++ b/crates/dbx-core/tests/agent_service.rs @@ -4,8 +4,9 @@ use dbx_core::agent_manager::{ }; use dbx_core::agent_service::{ build_agent_list, clear_agent_download_cache, github_url_to_r2_path, import_agent_driver, import_agent_jar, - import_agents_from_zip, inspect_offline_zip, is_app_version_compatible, jre_needs_install, - local_agent_jar_candidates, replace_download, uninstall_agent_driver, AgentProgressEvent, + import_agents_from_package, import_agents_from_zip, inspect_offline_package, inspect_offline_zip, + is_app_version_compatible, jre_needs_install, local_agent_jar_candidates, replace_download, uninstall_agent_driver, + AgentProgressEvent, }; fn test_manager(name: &str) -> AgentManager { @@ -22,7 +23,11 @@ fn registry_with_driver(db_type: &str, version: &str, jre: &str) -> AgentRegistr label: db_type.to_string(), min_app_version: "0.1.0".to_string(), jre: jre.to_string(), - jar: Some(ArtifactInfo { url: format!("https://example.com/dbx-agent-{db_type}.jar"), size: 42 }), + jar: Some(ArtifactInfo { + url: format!("https://example.com/dbx-agent-{db_type}.jar"), + size: 42, + format: None, + }), native: std::collections::HashMap::new(), }, ); @@ -50,10 +55,11 @@ fn registry_with_native_driver(db_type: &str, version: &str, jre: &str) -> Agent jar: Some(ArtifactInfo { url: format!("https://example.com/dbx-agent-{db_type}-legacy-placeholder.jar"), size: 0, + format: None, }), native: [( AgentManager::current_platform().to_string(), - ArtifactInfo { url: format!("https://example.com/dbx-agent-{db_type}"), size: 42 }, + ArtifactInfo { url: format!("https://example.com/dbx-agent-{db_type}"), size: 42, format: None }, )] .into_iter() .collect(), @@ -562,6 +568,21 @@ async fn offline_zip_import_installs_release_named_jre() { assert!(events.lock().unwrap().iter().any(|event| event.step == "jre-extract")); } +#[tokio::test] +async fn offline_zip_import_installs_tar_zstd_jre() { + let manager = test_manager("offline-zstd-jre"); + let zip_path = test_path("offline-zstd-jre-zip").join("agents.zip"); + std::fs::create_dir_all(zip_path.parent().unwrap()).unwrap(); + write_offline_driver_zip_with_zstd_jre(&zip_path, "h2", "0.2.0", "21.0.12"); + + let result = import_agents_from_zip(&manager, &zip_path, |_| {}).await.unwrap(); + + assert_eq!(result.jre_installed, vec![DEFAULT_JRE_KEY]); + assert_eq!(result.drivers_installed, vec!["h2"]); + assert!(manager.is_jre_installed(DEFAULT_JRE_KEY)); + assert_eq!(manager.load_state().jre_versions.get(DEFAULT_JRE_KEY).map(String::as_str), Some("21.0.12")); +} + #[tokio::test] async fn offline_zip_import_preserves_existing_jre_when_archive_is_corrupt() { let manager = test_manager("offline-corrupt-jre-preserves-existing"); @@ -688,6 +709,41 @@ async fn offline_zip_import_installs_versioned_native_driver_package() { assert!(!manager.driver_jar_path("kingbase").exists()); } +#[tokio::test] +async fn offline_tar_zstd_import_installs_versioned_native_driver_package() { + let manager = test_manager("offline-tar-zstd-driver-package"); + let package_path = test_path("offline-tar-zstd-driver-package").join("duckdb.tar.zst"); + std::fs::create_dir_all(package_path.parent().unwrap()).unwrap(); + write_offline_tar_zstd_driver_package(&package_path, "duckdb", "0.1.0"); + + let plan = inspect_offline_package(&package_path).unwrap(); + let result = import_agents_from_package(&manager, &package_path, |_| {}).await.unwrap(); + + assert_eq!(plan.driver_keys, vec!["duckdb"]); + assert!(!plan.includes_jre); + assert_eq!(result.drivers_installed, vec!["duckdb"]); + assert_eq!(manager.load_state().installed_drivers["duckdb"].version, "0.1.0"); + assert_eq!(std::fs::read(manager.driver_native_path("duckdb")).unwrap(), current_platform_native_binary()); +} + +#[tokio::test] +async fn offline_tar_zstd_import_installs_versioned_java_driver_package() { + let manager = test_manager("offline-tar-zstd-java-package"); + let package_path = test_path("offline-tar-zstd-java-package").join("dameng.tar.zst"); + std::fs::create_dir_all(package_path.parent().unwrap()).unwrap(); + write_offline_tar_zstd_java_driver_package(&package_path, "dameng", "0.2.0"); + + let plan = inspect_offline_package(&package_path).unwrap(); + let result = import_agents_from_package(&manager, &package_path, |_| {}).await.unwrap(); + + assert_eq!(plan.driver_keys, vec!["dameng"]); + assert!(!plan.includes_jre); + assert_eq!(result.drivers_installed, vec!["dameng"]); + assert_eq!(manager.load_state().installed_drivers["dameng"].version, "0.2.0"); + assert_eq!(std::fs::read(manager.driver_jar_path("dameng")).unwrap(), test_agent_jar_bytes()); + assert!(!manager.driver_native_path("dameng").exists()); +} + #[tokio::test] async fn offline_zip_import_rejects_native_driver_for_another_platform() { let manager = test_manager("offline-wrong-platform-native-driver-zip"); @@ -823,6 +879,86 @@ fn write_offline_native_driver_zip(path: &std::path::Path, db_type: &str, versio write_offline_native_driver_zip_for_platform(path, db_type, version, AgentManager::current_platform()); } +fn write_offline_tar_zstd_driver_package(path: &std::path::Path, db_type: &str, version: &str) { + let native = current_platform_native_binary(); + let platform = AgentManager::current_platform(); + let extension = if platform.starts_with("windows-") { ".exe" } else { "" }; + let filename = format!("dbx-agent-{db_type}-{version}-{platform}{extension}"); + let registry = serde_json::json!({ + "jres": {}, + "drivers": { + db_type: { + "version": version, + "label": db_type, + "min_app_version": "0.6.0", + "jre": DEFAULT_JRE_KEY, + "native": { + platform: { + "url": filename, + "size": native.len() + } + } + } + } + }); + let registry_bytes = registry.to_string().into_bytes(); + let file = std::fs::File::create(path).unwrap(); + let encoder = zstd::stream::write::Encoder::new(file, 3).unwrap(); + let mut archive = tar::Builder::new(encoder); + + let mut registry_header = tar::Header::new_gnu(); + registry_header.set_size(registry_bytes.len() as u64); + registry_header.set_mode(0o644); + registry_header.set_cksum(); + archive.append_data(&mut registry_header, "agent-registry.json", registry_bytes.as_slice()).unwrap(); + + let mut driver_header = tar::Header::new_gnu(); + driver_header.set_size(native.len() as u64); + driver_header.set_mode(0o755); + driver_header.set_cksum(); + archive.append_data(&mut driver_header, format!("drivers/{filename}"), native.as_slice()).unwrap(); + + archive.into_inner().unwrap().finish().unwrap(); +} + +fn write_offline_tar_zstd_java_driver_package(path: &std::path::Path, db_type: &str, version: &str) { + let jar = test_agent_jar_bytes(); + let filename = format!("dbx-agent-{db_type}-{version}.jar"); + let registry = serde_json::json!({ + "jres": {}, + "drivers": { + db_type: { + "version": version, + "label": db_type, + "min_app_version": "0.6.0", + "jre": DEFAULT_JRE_KEY, + "jar": { + "url": filename, + "size": jar.len() + } + } + } + }); + let registry_bytes = registry.to_string().into_bytes(); + let file = std::fs::File::create(path).unwrap(); + let encoder = zstd::stream::write::Encoder::new(file, 3).unwrap(); + let mut archive = tar::Builder::new(encoder); + + let mut registry_header = tar::Header::new_gnu(); + registry_header.set_size(registry_bytes.len() as u64); + registry_header.set_mode(0o644); + registry_header.set_cksum(); + archive.append_data(&mut registry_header, "agent-registry.json", registry_bytes.as_slice()).unwrap(); + + let mut driver_header = tar::Header::new_gnu(); + driver_header.set_size(jar.len() as u64); + driver_header.set_mode(0o644); + driver_header.set_cksum(); + archive.append_data(&mut driver_header, format!("drivers/{filename}"), jar.as_slice()).unwrap(); + + archive.into_inner().unwrap().finish().unwrap(); +} + fn write_offline_native_driver_zip_for_platform(path: &std::path::Path, db_type: &str, version: &str, platform: &str) { write_offline_native_driver_zip_with_bytes(path, db_type, version, platform, current_platform_native_binary()); } @@ -903,6 +1039,46 @@ fn write_offline_driver_zip_with_jre(path: &std::path::Path, db_type: &str, vers write_offline_driver_zip_with_jre_bytes(path, db_type, version, jre_version, test_jre_archive_bytes()); } +fn write_offline_driver_zip_with_zstd_jre(path: &std::path::Path, db_type: &str, version: &str, jre_version: &str) { + let file = std::fs::File::create(path).unwrap(); + let mut zip = zip::ZipWriter::new(file); + let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored); + let jar = test_agent_jar_bytes(); + let jre_archive = test_jre_archive_zstd_bytes(); + let platform = AgentManager::current_platform(); + let registry = serde_json::json!({ + "jres": { + DEFAULT_JRE_KEY: { + "version": jre_version, + "platforms": { + platform: { + "url": format!("https://example.com/dbx-jre-{DEFAULT_JRE_KEY}-{platform}.tar.zst"), + "size": jre_archive.len(), + "format": "tar_zstd" + } + } + } + }, + "drivers": { + db_type: { + "version": version, + "label": db_type, + "min_app_version": "0.1.0", + "jre": DEFAULT_JRE_KEY, + "jar": { "url": format!("https://example.com/dbx-agent-{db_type}-{version}.jar"), "size": jar.len() } + } + } + }); + + zip.start_file("agent-registry.json", options).unwrap(); + std::io::Write::write_all(&mut zip, registry.to_string().as_bytes()).unwrap(); + zip.start_file(format!("jre/dbx-jre-{DEFAULT_JRE_KEY}-{platform}.tar.zst"), options).unwrap(); + std::io::Write::write_all(&mut zip, &jre_archive).unwrap(); + zip.start_file(format!("drivers/dbx-agent-{db_type}-{version}.jar"), options).unwrap(); + std::io::Write::write_all(&mut zip, &jar).unwrap(); + zip.finish().unwrap(); +} + fn write_offline_driver_zip_with_jre_bytes( path: &std::path::Path, db_type: &str, @@ -975,6 +1151,22 @@ fn test_jre_archive_bytes() -> Vec { bytes } +fn test_jre_archive_zstd_bytes() -> Vec { + let root = test_path("jre-zstd-archive"); + let runtime_root = root.join("dbx-jre"); + let bin_dir = runtime_root.join("bin"); + std::fs::create_dir_all(&bin_dir).unwrap(); + std::fs::write(bin_dir.join("java"), b"java").unwrap(); + std::fs::write(bin_dir.join("java.exe"), b"java").unwrap(); + + let encoder = zstd::stream::write::Encoder::new(Vec::new(), 3).unwrap(); + let mut builder = tar::Builder::new(encoder); + builder.append_dir_all("dbx-jre", &runtime_root).unwrap(); + let bytes = builder.into_inner().unwrap().finish().unwrap(); + std::fs::remove_dir_all(root).ok(); + bytes +} + fn write_offline_driver_zip_with_jar(path: &std::path::Path, db_type: &str, version: &str, jar: Vec) { let file = std::fs::File::create(path).unwrap(); let mut zip = zip::ZipWriter::new(file); diff --git a/crates/dbx-core/tests/support/duckdb_worker_test_host.rs b/crates/dbx-core/tests/support/duckdb_worker_test_host.rs deleted file mode 100644 index 6f4e392e87..0000000000 --- a/crates/dbx-core/tests/support/duckdb_worker_test_host.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![cfg(feature = "duckdb-bundled")] - -fn main() { - let runtime = tokio::runtime::Runtime::new().expect("Failed to create DuckDB worker test runtime"); - if let Err(err) = runtime.block_on(dbx_core::db::duckdb_worker_runtime::run_stdio_worker()) { - eprintln!("DuckDB worker test host failed: {err}"); - std::process::exit(1); - } -} diff --git a/crates/dbx-web/Cargo.toml b/crates/dbx-web/Cargo.toml index 86570f5d31..89bfedfcd2 100644 --- a/crates/dbx-web/Cargo.toml +++ b/crates/dbx-web/Cargo.toml @@ -9,8 +9,8 @@ name = "dbx-web" path = "src/main.rs" [features] -default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher", "system-fonts"] -duckdb-bundled = ["dbx-core/duckdb-bundled"] +default = ["duckdb-sidecar", "mq-admin", "sqlite-sqlcipher", "system-fonts"] +duckdb-sidecar = ["dbx-core/duckdb-sidecar"] mq-admin = ["dbx-core/mq-admin"] sqlite-sqlcipher = ["dbx-core/sqlite-sqlcipher"] system-fonts = ["dbx-core/system-fonts"] diff --git a/crates/dbx-web/src/main.rs b/crates/dbx-web/src/main.rs index dc091e8197..2f9d5819ea 100644 --- a/crates/dbx-web/src/main.rs +++ b/crates/dbx-web/src/main.rs @@ -328,6 +328,7 @@ async fn main() { .route("/agents/runtime/restart", post(routes::agents::restart_driver_runtime)) .route("/agents/install", post(routes::agents::install_agent)) .route("/agents/upgrade-all", post(routes::agents::upgrade_all_agents)) + .route("/agents/update-blockers", post(routes::agents::check_agent_update_blockers)) .route("/agents/uninstall", post(routes::agents::uninstall_agent)) .route("/agents/import-offline", post(routes::agents::import_agents_from_zip)) .route("/agents/import-driver", post(routes::agents::import_agent_driver_file)) @@ -747,8 +748,8 @@ async fn main() { .route("/cloud-sync/snippet/upload", post(routes::cloud_sync::snippet_sync_upload)) .route("/cloud-sync/snippet/download", post(routes::cloud_sync::snippet_sync_download)); - // Do not expose DuckDB-only handlers from builds that intentionally omit bundled DuckDB. - #[cfg(feature = "duckdb-bundled")] + // Do not expose DuckDB-only handlers from builds that omit DuckDB sidecar support. + #[cfg(feature = "duckdb-sidecar")] let api = api.route("/query/build-duckdb-attach-database-sql", post(routes::query::build_duckdb_attach_database_sql)); diff --git a/crates/dbx-web/src/routes/agents.rs b/crates/dbx-web/src/routes/agents.rs index 1b30a5c188..e50cda5559 100644 --- a/crates/dbx-web/src/routes/agents.rs +++ b/crates/dbx-web/src/routes/agents.rs @@ -8,13 +8,13 @@ use dbx_core::agent_manager::{ }; use dbx_core::agent_service::{ build_agent_list, clear_agent_download_cache, fetch_registry, import_agent_driver, - import_agents_from_zip as import_agents_from_zip_core, inspect_offline_zip, install_agent_driver, + import_agents_from_package as import_agents_from_package_core, inspect_offline_package, install_agent_driver, invalidate_registry_cache, reinstall_agent_jre, uninstall_agent_driver, uninstall_agent_jre, upgrade_all_agent_drivers, AgentProgressEvent, OfflineImportPlan, }; use dbx_core::driver_runtime::DriverRuntimeSummary; use futures::Stream; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tokio::sync::broadcast; @@ -41,6 +41,18 @@ pub struct AgentOperationRequest { pub operation_id: Option, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AgentUpdateBlockersRequest { + pub db_types: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct AgentUpdateBlocker { + pub db_type: String, + pub label: String, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct JavaRuntimeRequest { @@ -138,10 +150,18 @@ pub async fn upgrade_all_agents( Ok(Json(serde_json::to_value(result).map_err(|err| AppError::from(err.to_string()))?)) } +pub async fn check_agent_update_blockers( + State(state): State>, + Json(req): Json, +) -> Result>, AppError> { + Ok(Json(agent_update_blockers(&state.app, &req.db_types).await)) +} + pub async fn uninstall_agent( State(state): State>, Json(req): Json, ) -> Result, AppError> { + ensure_no_agent_update_blockers(&state.app, std::slice::from_ref(&req.db_type)).await.map_err(AppError::from)?; uninstall_agent_driver(&state.app.agent_manager, &req.db_type).await.map_err(AppError::from)?; Ok(Json(serde_json::json!({ "ok": true }))) } @@ -200,14 +220,13 @@ pub async fn import_agents_from_zip( } let file_name = field.file_name().unwrap_or("offline-drivers.zip").to_string(); - if !file_name.to_ascii_lowercase().ends_with(".zip") { - return Err(AppError::from("Offline driver package must be a .zip file".to_string())); - } - - let zip_path = tmp_dir.join(format!("agent-offline-{}.zip", uuid::Uuid::new_v4())); + let extension = offline_package_extension(&file_name) + .ok_or_else(|| AppError::from("Offline driver package must be a .zip or .tar.zst file".to_string()))?; + let package_path = tmp_dir.join(format!("agent-offline-{}.{}", uuid::Uuid::new_v4(), extension)); let tx = progress_sender(&state, "global").await; let result = async { - let mut upload = tokio::fs::File::create(&zip_path).await.map_err(|err| AppError::from(err.to_string()))?; + let mut upload = + tokio::fs::File::create(&package_path).await.map_err(|err| AppError::from(err.to_string()))?; let mut field = field; while let Some(chunk) = field.chunk().await.map_err(|err| AppError::from(err.to_string()))? { upload.write_all(&chunk).await.map_err(|err| AppError::from(err.to_string()))?; @@ -215,16 +234,16 @@ pub async fn import_agents_from_zip( upload.flush().await.map_err(|err| AppError::from(err.to_string()))?; drop(upload); - let plan = inspect_offline_zip(&zip_path).map_err(AppError::from)?; + let plan = inspect_offline_package(&package_path).map_err(AppError::from)?; ensure_no_offline_import_blockers(&state.app, &plan).await.map_err(AppError::from)?; - import_agents_from_zip_core(&state.app.agent_manager, &zip_path, |event| { + import_agents_from_package_core(&state.app.agent_manager, &package_path, |event| { send_progress_event(&tx, event.with_operation_id(&operation_id)) }) .await .map_err(AppError::from) } .await; - let _ = std::fs::remove_file(&zip_path); + let _ = std::fs::remove_file(&package_path); let result = result?; send_progress_event(&tx, AgentProgressEvent::step("done").with_operation_id(&operation_id)); @@ -327,22 +346,46 @@ async fn ensure_no_agent_update_blockers( state: &dbx_core::connection::AppState, db_types: &[String], ) -> Result<(), String> { - let candidate_keys: std::collections::HashSet<&str> = db_types.iter().map(String::as_str).collect(); - if candidate_keys.is_empty() { + let blockers = update_blockers_from_keys(state.prepare_agent_driver_updates(db_types).await, db_types); + if blockers.is_empty() { return Ok(()); } - let mut blockers = state - .prepare_agent_driver_updates(db_types) - .await + let labels = blockers.into_iter().map(|blocker| blocker.label).collect::>().join(", "); + Err(format!("Close these database connections before updating drivers: {labels}")) +} + +async fn agent_update_blockers(state: &dbx_core::connection::AppState, db_types: &[String]) -> Vec { + update_blockers_from_keys(state.active_agent_connection_driver_keys().await, db_types) +} + +fn update_blockers_from_keys( + active_keys: std::collections::HashSet, + db_types: &[String], +) -> Vec { + let candidate_keys: std::collections::HashSet<&str> = db_types.iter().map(String::as_str).collect(); + if candidate_keys.is_empty() { + return Vec::new(); + } + let mut blockers = active_keys .into_iter() .filter(|key| candidate_keys.contains(key.as_str())) - .map(|key| dbx_core::agent_catalog::label_for_key(&key).unwrap_or(&key).to_string()) + .map(|db_type| AgentUpdateBlocker { + label: dbx_core::agent_catalog::label_for_key(&db_type).unwrap_or(&db_type).to_string(), + db_type, + }) .collect::>(); - blockers.sort(); - if blockers.is_empty() { - Ok(()) + blockers.sort_by(|left, right| left.label.cmp(&right.label)); + blockers +} + +fn offline_package_extension(file_name: &str) -> Option<&'static str> { + let lower_name = file_name.to_ascii_lowercase(); + if lower_name.ends_with(".tar.zst") { + Some("tar.zst") + } else if lower_name.ends_with(".zip") { + Some("zip") } else { - Err(format!("Close these database connections before updating drivers: {}", blockers.join(", "))) + None } } @@ -361,3 +404,25 @@ async fn ensure_no_offline_import_blockers( } ensure_no_agent_update_blockers(state, &driver_keys).await } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn update_blockers_include_active_duckdb_connections() { + let active_keys = ["duckdb".to_string(), "oracle".to_string()].into_iter().collect(); + let blockers = update_blockers_from_keys(active_keys, &["duckdb".to_string()]); + + assert_eq!(blockers.len(), 1); + assert_eq!(blockers[0].db_type, "duckdb"); + assert_eq!(blockers[0].label, "DuckDB"); + } + + #[test] + fn offline_package_extension_accepts_zip_and_tar_zstd() { + assert_eq!(offline_package_extension("dbx-agents.zip"), Some("zip")); + assert_eq!(offline_package_extension("dbx-agent-duckdb.TAR.ZST"), Some("tar.zst")); + assert_eq!(offline_package_extension("dbx-agent-duckdb.zst"), None); + } +} diff --git a/crates/dbx-web/src/routes/query.rs b/crates/dbx-web/src/routes/query.rs index e8f9714f77..00beeb41d1 100644 --- a/crates/dbx-web/src/routes/query.rs +++ b/crates/dbx-web/src/routes/query.rs @@ -142,7 +142,7 @@ pub struct BuildCreateDatabaseSqlRequest { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] -#[cfg(feature = "duckdb-bundled")] +#[cfg(feature = "duckdb-sidecar")] pub struct BuildDuckDbAttachDatabaseSqlRequest { pub options: dbx_core::db_admin_sql::DuckDbAttachDatabaseSqlOptions, } @@ -647,7 +647,7 @@ pub async fn build_create_database_sql( dbx_core::db_admin_sql::build_create_database_sql(req.options).map(Json).map_err(AppError::from) } -#[cfg(feature = "duckdb-bundled")] +#[cfg(feature = "duckdb-sidecar")] pub async fn build_duckdb_attach_database_sql(Json(req): Json) -> Json { Json(dbx_core::db_admin_sql::build_duckdb_attach_database_sql(req.options)) } diff --git a/docs/app/[lang]/drivers/DriversClient.tsx b/docs/app/[lang]/drivers/DriversClient.tsx index e1fe517391..185eeac0a0 100644 --- a/docs/app/[lang]/drivers/DriversClient.tsx +++ b/docs/app/[lang]/drivers/DriversClient.tsx @@ -17,11 +17,11 @@ const i18n = { bundles: "Offline Bundles", bundlesDesc: "Platform-specific ZIP packages that include the agent registry, database drivers, native agents, and the matching JRE.", drivers: "Database Drivers", - driversDesc: "Single-driver offline ZIPs for Java agents. Install the matching JRE separately when it is not already available.", + driversDesc: "Single-driver .tar.zst packages for Java agents. Install the matching JRE separately when it is not already available.", nativeAgents: "Native Agents", - nativeAgentsDesc: "Platform-specific offline ZIPs for Oracle, KingBase, and XuguDB. Import the ZIP directly in Driver Manager.", + nativeAgentsDesc: "Platform-specific .tar.zst packages for DuckDB, Oracle, KingBase, and XuguDB. Import the package directly in Driver Manager.", jre: "Java Runtime (JRE)", - jreDesc: "JRE packages used by agent-based database drivers. Required for Oracle, SQL Server, and other agent-managed connections.", + jreDesc: "JRE packages used by Java agent-based database drivers such as SQL Server and Dameng.", download: "Download", installMethod: "Install", version: "Version", @@ -53,11 +53,11 @@ const i18n = { bundles: "整包下载", bundlesDesc: "按平台提供的 ZIP 离线包,包含 Agent registry、数据库驱动、原生 Agent 和匹配的 JRE。", drivers: "数据库驱动", - driversDesc: "Java Agent 的单驱动离线 ZIP;目标机器尚未安装 JRE 时需要另外安装一次对应 JRE。", + driversDesc: "Java Agent 的单驱动 .tar.zst 包;目标机器尚未安装 JRE 时需要另外安装一次对应 JRE。", nativeAgents: "原生 Agent", - nativeAgentsDesc: "Oracle、人大金仓和虚谷的按平台单驱动离线包,可直接在驱动管理中导入 ZIP。", + nativeAgentsDesc: "DuckDB、Oracle、人大金仓和虚谷的按平台 .tar.zst 单驱动包,可直接在驱动管理中导入。", jre: "Java 运行时 (JRE)", - jreDesc: "Agent 驱动所需的 JRE 环境,Oracle、SQL Server 等数据库通过 Agent 连接时需要。", + jreDesc: "Java Agent 驱动所需的 JRE 环境,例如 SQL Server、达梦等连接会使用。", download: "下载", installMethod: "安装方式", version: "版本", diff --git a/docs/content/docs/contributing.cn.mdx b/docs/content/docs/contributing.cn.mdx index 8a0f24571b..605893a69a 100644 --- a/docs/content/docs/contributing.cn.mdx +++ b/docs/content/docs/contributing.cn.mdx @@ -153,7 +153,7 @@ make `make` 会使用锁文件安装依赖,然后启动 Tauri 桌面开发环境。第一次编译 Rust 依赖需要较长时间,后续启动会明显更快。 -如果你修改的功能与 DuckDB 无关,推荐跳过 DuckDB 源码编译: +如需轻量桌面开发构建,可以保留 DuckDB sidecar 支持,同时跳过其他默认 Rust feature: ```bash make dev-fast @@ -172,10 +172,10 @@ pnpm install --frozen-lockfile pnpm dev:tauri ``` -不需要 DuckDB 时使用: +使用相同的轻量构建: ```powershell -pnpm tauri dev -- --no-default-features +pnpm tauri dev -- --no-default-features --features duckdb-sidecar ``` ### 判断是否启动成功 @@ -451,7 +451,7 @@ git push --force-with-lease ### 第一次 Rust 编译很慢 -这是正常现象。与 DuckDB 无关的开发优先使用 `make dev-fast`、`make cargo-check-fast` 和 `make cargo-test-fast`。 +这是正常现象。轻量桌面开发可以使用仍支持 DuckDB 的 `make dev-fast`。`make cargo-check-fast` 和 `make cargo-test-fast` 会跳过全部可选默认 feature,因此不会验证 DuckDB 专用路径。 ### pnpm 提示版本不正确 diff --git a/docs/content/docs/contributing.mdx b/docs/content/docs/contributing.mdx index d868de13ae..1368032b0e 100644 --- a/docs/content/docs/contributing.mdx +++ b/docs/content/docs/contributing.mdx @@ -153,7 +153,7 @@ make `make` installs dependencies from the lockfile and starts the Tauri desktop development environment. The first Rust build can take a while; later starts are much faster. -If your change does not involve DuckDB, skip the DuckDB source build: +For a lightweight desktop build, keep DuckDB sidecar support while skipping the other default Rust features: ```bash make dev-fast @@ -172,10 +172,10 @@ pnpm install --frozen-lockfile pnpm dev:tauri ``` -To skip DuckDB: +For the same lightweight build: ```powershell -pnpm tauri dev -- --no-default-features +pnpm tauri dev -- --no-default-features --features duckdb-sidecar ``` ### Confirm the Environment Works @@ -451,7 +451,7 @@ git push --force-with-lease ### The First Rust Build Is Slow -This is expected. Use `make dev-fast`, `make cargo-check-fast`, and `make cargo-test-fast` when DuckDB is not part of the change. +This is expected. Use `make dev-fast` for a lightweight desktop build that still supports DuckDB. `make cargo-check-fast` and `make cargo-test-fast` skip all optional default features, so they do not validate DuckDB-specific paths. ### pnpm Reports the Wrong Version diff --git a/docs/lib/agentRegistry.test.ts b/docs/lib/agentRegistry.test.ts index 9d05fd91eb..8aacab4a6f 100644 --- a/docs/lib/agentRegistry.test.ts +++ b/docs/lib/agentRegistry.test.ts @@ -45,8 +45,9 @@ test("catalog falls back from R2 to CNB without using GitHub API", async () => { access: { version: accessVersion, jar: { - url: `https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-agent-access-${accessVersion}.jar`, + url: `https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-agent-access-${accessVersion}.tar.zst`, size: 1, + format: "tar_zstd", }, }, }, @@ -54,8 +55,9 @@ test("catalog falls back from R2 to CNB without using GitHub API", async () => { "21": { platforms: { "macos-aarch64": { - url: "https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-jre-21-macos-aarch64.tar.gz", + url: "https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-jre-21-macos-aarch64.tar.zst", size: 1, + format: "tar_zstd", }, }, }, @@ -77,7 +79,7 @@ test("unknown fallback asset sizes render as unavailable", () => { assert.equal(formatSize(0), "—"); }); -test("Java agent ZIPs are preferred over raw JARs", () => { +test("Java agent tar.zst packages are preferred over raw JARs", () => { const accessVersion = driverVersions.access; const entries = buildDriverEntries([ { @@ -86,17 +88,17 @@ test("Java agent ZIPs are preferred over raw JARs", () => { size: 1024, }, { - name: `dbx-agent-access-${accessVersion}.zip`, - browser_download_url: `https://example.com/dbx-agent-access-${accessVersion}.zip`, + name: `dbx-agent-access-${accessVersion}.tar.zst`, + browser_download_url: `https://example.com/dbx-agent-access-${accessVersion}.tar.zst`, size: 2048, }, ]); assert.equal(entries[0]?.key, "access"); - assert.equal(entries[0]?.jar.url, `https://example.com/dbx-agent-access-${accessVersion}.zip`); + assert.equal(entries[0]?.jar.url, `https://example.com/dbx-agent-access-${accessVersion}.tar.zst`); }); -test("KingBase native ZIPs are preferred over raw release executables", () => { +test("KingBase native tar.zst packages are preferred over raw release executables", () => { const entries = buildNativeAgentEntries([ { name: "dbx-agent-kingbase-windows-x64.exe", @@ -109,13 +111,13 @@ test("KingBase native ZIPs are preferred over raw release executables", () => { size: 2048, }, { - name: "dbx-agent-kingbase-0.1.34-windows-x64.zip", - browser_download_url: "https://example.com/dbx-agent-kingbase-0.1.34-windows-x64.zip", + name: "dbx-agent-kingbase-0.1.34-windows-x64.tar.zst", + browser_download_url: "https://example.com/dbx-agent-kingbase-0.1.34-windows-x64.tar.zst", size: 4096, }, { - name: "dbx-agent-kingbase-0.1.34-linux-x64.zip", - browser_download_url: "https://example.com/dbx-agent-kingbase-0.1.34-linux-x64.zip", + name: "dbx-agent-kingbase-0.1.34-linux-x64.tar.zst", + browser_download_url: "https://example.com/dbx-agent-kingbase-0.1.34-linux-x64.tar.zst", size: 3072, }, ]); @@ -127,14 +129,32 @@ test("KingBase native ZIPs are preferred over raw release executables", () => { key: "kingbase", version: "0.1.34", platformKey: "linux-x64", - filename: "dbx-agent-kingbase-0.1.34-linux-x64.zip", + filename: "dbx-agent-kingbase-0.1.34-linux-x64.tar.zst", }, { key: "kingbase", version: "0.1.34", platformKey: "windows-x64", - filename: "dbx-agent-kingbase-0.1.34-windows-x64.zip", + filename: "dbx-agent-kingbase-0.1.34-windows-x64.tar.zst", }, ], ); }); + +test("DuckDB native tar.zst packages appear in the native catalog", () => { + const entries = buildNativeAgentEntries([ + { + name: "dbx-agent-duckdb-0.1.0-macos-aarch64.tar.zst", + browser_download_url: "https://example.com/dbx-agent-duckdb-0.1.0-macos-aarch64.tar.zst", + size: 4096, + }, + ]); + + assert.deepEqual(entries.map(({ key, platformKey, filename }) => ({ key, platformKey, filename })), [ + { + key: "duckdb", + platformKey: "macos-aarch64", + filename: "dbx-agent-duckdb-0.1.0-macos-aarch64.tar.zst", + }, + ]); +}); diff --git a/docs/lib/agentRegistry.ts b/docs/lib/agentRegistry.ts index 77094886dd..9df7970b55 100644 --- a/docs/lib/agentRegistry.ts +++ b/docs/lib/agentRegistry.ts @@ -21,6 +21,7 @@ export interface GitHubReleaseAsset { interface AgentRegistryArtifact { url: string; size: number; + format?: "tar_zstd"; } interface AgentRegistryDriver { @@ -88,7 +89,7 @@ const GITHUB_RELEASE_DOWNLOAD_PREFIX = "https://github.com/t8y2/dbx/releases/dow const CNB_RELEASE_DOWNLOAD_PREFIX = "https://cnb.cool/dbxio.com/dbx/-/releases/download/"; const MIN_APP_VERSION = "0.6.0"; const driverVersionMap = driverVersions as Record; -const nativeDriverKeys = new Set(["oracle", "kingbase", "xugu"]); +const nativeDriverKeys = new Set(["duckdb", "oracle", "kingbase", "xugu"]); const platformLabels: Record = { "macos-aarch64": "macOS (Apple Silicon)", @@ -107,6 +108,7 @@ const driverLabels: Record = { databend: "Databend", databricks: "Databricks", db2: "DB2", + duckdb: "DuckDB", etcd: "etcd", exasol: "Exasol", firebird: "Firebird", @@ -182,31 +184,14 @@ function registryReleaseAssets(registry: AgentRegistry): GitHubReleaseAsset[] { for (const [jreKey, jre] of Object.entries(registry.jres ?? {})) { for (const [platformKey, artifact] of Object.entries(jre.platforms ?? {})) { - addArtifact(artifact, `dbx-jre-${jreKey}-${platformKey}.tar.gz`); + addArtifact(artifact, `dbx-jre-${jreKey}-${platformKey}.tar.zst`); } } for (const [driverKey, driver] of Object.entries(registry.drivers ?? {})) { - addArtifact(driver.jar, `dbx-agent-${driverKey}.jar`); - if (driver.version && driver.jar?.url && driver.jar.size > 0) { - const packageName = `dbx-agent-${driverKey}-${driver.version}.zip`; - assets.set(packageName, { - name: packageName, - size: 0, - browser_download_url: siblingReleaseAssetUrl(driver.jar.url, packageName), - }); - } + addArtifact(driver.jar, `dbx-agent-${driverKey}-${driver.version ?? "latest"}.tar.zst`); for (const [platformKey, artifact] of Object.entries(driver.native ?? {})) { - addArtifact(artifact, `dbx-agent-${driverKey}-${platformKey}`); - const filename = releaseAssetName(artifact.url, ""); - if (driver.version && filename.includes(`-${driver.version}-${platformKey}`)) { - const packageName = `${filename.replace(/\.exe$/, "")}.zip`; - assets.set(packageName, { - name: packageName, - size: 0, - browser_download_url: siblingReleaseAssetUrl(artifact.url, packageName), - }); - } + addArtifact(artifact, `dbx-agent-${driverKey}-${driver.version ?? "latest"}-${platformKey}.tar.zst`); } } @@ -263,7 +248,7 @@ export function buildJdbcPluginDownloadEntry(): JdbcPluginDownloadEntry { export function buildJreEntries(assets: GitHubReleaseAsset[]): JreDisplayEntry[] { return assets .map((asset) => { - const match = /^dbx-jre-(\d+)-(.+)\.tar\.gz$/.exec(asset.name); + const match = /^dbx-jre-(\d+)-(.+)\.tar\.(?:zst|gz)$/.exec(asset.name); if (!match) return null; const [, jreKey, platformKey] = match; @@ -287,7 +272,7 @@ export function buildDriverEntries(assets: GitHubReleaseAsset[]): DriverDisplayE return currentJavaDriverKeys .map((key) => { const version = driverVersionMap[key] ?? ""; - const asset = byName.get(`dbx-agent-${key}-${version}.zip`) ?? byName.get(`dbx-agent-${key}-${version}.jar`) ?? byName.get(`dbx-agent-${key}.jar`); + const asset = byName.get(`dbx-agent-${key}-${version}.tar.zst`) ?? byName.get(`dbx-agent-${key}-${version}.zip`) ?? byName.get(`dbx-agent-${key}-${version}.jar`) ?? byName.get(`dbx-agent-${key}.jar`); if (!asset) return null; return { @@ -307,9 +292,9 @@ export function buildNativeAgentEntries(assets: GitHubReleaseAsset[]): NativeAge const platforms = "macos-aarch64|macos-x64|linux-aarch64|linux-x64|windows-aarch64|windows-x64"; for (const asset of assets) { - const packageMatch = new RegExp(`^dbx-agent-(oracle|kingbase|xugu)-(.+)-(${platforms})\\.zip$`).exec(asset.name); - const versionedMatch = new RegExp(`^dbx-agent-(oracle|kingbase|xugu)-(.+)-(${platforms})(?:\\.exe)?$`).exec(asset.name); - const legacyMatch = new RegExp(`^dbx-agent-(oracle|kingbase|xugu)-(${platforms})(?:\\.exe)?$`).exec(asset.name); + const packageMatch = new RegExp(`^dbx-agent-(duckdb|oracle|kingbase|xugu)-(.+)-(${platforms})\\.(?:tar\\.zst|zip)$`).exec(asset.name); + const versionedMatch = new RegExp(`^dbx-agent-(duckdb|oracle|kingbase|xugu)-(.+)-(${platforms})(?:\\.exe)?$`).exec(asset.name); + const legacyMatch = new RegExp(`^dbx-agent-(duckdb|oracle|kingbase|xugu)-(${platforms})(?:\\.exe)?$`).exec(asset.name); const match = packageMatch ?? versionedMatch ?? legacyMatch; if (!match) continue; diff --git a/docs/mq-quick-start.md b/docs/mq-quick-start.md index 14b7107773..d6b81e1d3f 100644 --- a/docs/mq-quick-start.md +++ b/docs/mq-quick-start.md @@ -355,7 +355,7 @@ async fn detect_pulsar_version(client: &reqwest::Client, base_url: &str) -> Puls ### 启用 MQ 功能 ```bash -# 默认已启用(default = ["duckdb-bundled", "mq-admin"]) +# 默认已启用(default 包含 "duckdb-sidecar" 和 "mq-admin") cargo build --release # 或显式指定 @@ -364,7 +364,7 @@ cargo build --release --features mq-admin ### 禁用 MQ 功能 ```bash -cargo build --release --no-default-features --features duckdb-bundled +cargo build -p dbx --release --no-default-features --features duckdb-sidecar,sqlite-sqlcipher,system-fonts ``` ### 检查编译 @@ -507,4 +507,3 @@ const response = await mqRawRequest(connectionId, { console.log(response.body) ``` - \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7d8e69635c..9119447cf4 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,8 +13,8 @@ name = "dbx_lib" crate-type = ["staticlib", "cdylib", "rlib"] [features] -default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher", "system-fonts"] -duckdb-bundled = ["duckdb", "duckdb/parquet", "dbx-core/duckdb-bundled"] +default = ["duckdb-sidecar", "mq-admin", "sqlite-sqlcipher", "system-fonts"] +duckdb-sidecar = ["dbx-core/duckdb-sidecar"] mq-admin = ["dbx-core/mq-admin"] sqlite-sqlcipher = ["dbx-core/sqlite-sqlcipher"] system-fonts = ["dbx-core/system-fonts"] @@ -47,7 +47,6 @@ tauri-plugin-window-state = "2" redis = { version = "0.32.2", features = ["tokio-comp", "tls-rustls", "tokio-rustls-comp", "cluster-async"] } rustls = { version = "0.23", features = ["aws-lc-rs"] } portpicker = "0.1.1" -duckdb = { version = "1.3.2", optional = true } tiberius = { version = "0.12.3", default-features = false, features = ["tds73", "chrono", "rust_decimal", "rustls", "sql-browser-tokio"] } tokio-util = { version = "0.7", features = ["compat"] } reqwest = { version = "0.12", features = ["json", "stream"] } diff --git a/src-tauri/src/commands/agents.rs b/src-tauri/src/commands/agents.rs index edb0156ec6..1ec64e9645 100644 --- a/src-tauri/src/commands/agents.rs +++ b/src-tauri/src/commands/agents.rs @@ -5,7 +5,7 @@ use tauri::{Emitter, State}; use dbx_core::agent_manager::{AgentDriverInfo, DriverStoreUsage, JavaRuntimeConfig, JavaRuntimeMode, DEFAULT_JRE_KEY}; use dbx_core::agent_service::{ build_agent_list, clear_agent_download_cache, fetch_registry_from, import_agent_driver, - import_agents_from_zip as import_agents_from_zip_core, inspect_offline_zip, install_agent_driver_from, + import_agents_from_package as import_agents_from_package_core, inspect_offline_package, install_agent_driver_from, invalidate_registry_cache, reinstall_agent_jre_from, uninstall_agent_driver, uninstall_agent_jre, upgrade_all_agent_drivers_from, AgentProgressEvent, OfflineImportPlan, UpgradeAllAgentDriversResult, }; @@ -111,6 +111,7 @@ pub async fn check_agent_update_blockers( #[tauri::command] pub async fn uninstall_agent(state: State<'_, Arc>, db_type: String) -> Result<(), String> { + ensure_no_agent_update_blockers(state.inner().as_ref(), std::slice::from_ref(&db_type)).await?; uninstall_agent_driver(&state.agent_manager, &db_type).await } @@ -166,14 +167,15 @@ pub async fn import_agents_from_zip( operation_id: Option, ) -> Result { let am = &state.agent_manager; - let zip_path = std::path::PathBuf::from(&path); - let plan = inspect_offline_zip(&zip_path)?; + let package_path = std::path::PathBuf::from(&path); + let plan = inspect_offline_package(&package_path)?; ensure_no_offline_import_blockers(state.inner().as_ref(), &plan).await?; let app_handle = app.clone(); let operation_id = operation_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()); - let result = - import_agents_from_zip_core(am, &zip_path, |event| emit_agent_progress(&app_handle, &operation_id, event)) - .await?; + let result = import_agents_from_package_core(am, &package_path, |event| { + emit_agent_progress(&app_handle, &operation_id, event) + }) + .await?; let count = result.drivers_installed.len() as u32; emit_agent_progress(&app, &operation_id, AgentProgressEvent::step("done")); Ok(count) diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index 1979c890e7..a634057618 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -839,13 +839,13 @@ async fn test_connection_with_info_inner( // are reset after both successful and failed Redis tests. test_redis_connection(state, &tunnel_id, &config, &host, port, connect_timeout).await } - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] DatabaseType::DuckDb => { state.test_duckdb_connection_config(&config).await?; Ok("Connection successful".to_string()) } - #[cfg(not(feature = "duckdb-bundled"))] - DatabaseType::DuckDb => Err("DuckDB support not compiled (enable duckdb-bundled feature)".to_string()), + #[cfg(not(feature = "duckdb-sidecar"))] + DatabaseType::DuckDb => Err("DuckDB support is not compiled in this build".to_string()), DatabaseType::MongoDb => { if mongo_uses_legacy_driver(&config) { let am = &state.agent_manager; @@ -1144,22 +1144,10 @@ pub async fn connect_db( }; con } - #[cfg(feature = "duckdb-bundled")] - DatabaseType::DuckDb => { - let con = db::duckdb_driver::connect_path(&expand_tilde(&db_config.host))?; - { - let locked = con.lock().map_err(|e| e.to_string())?; - for attached in &db_config.attached_databases { - dbx_core::schema::duckdb_attach_database(&locked, &attached.name, &expand_tilde(&attached.path))?; - } - if let Some(script) = db_config.init_script.as_deref() { - db::duckdb_driver::run_init_script(&locked, script)?; - } - } - PoolKind::DuckDb(con) - } - #[cfg(not(feature = "duckdb-bundled"))] - DatabaseType::DuckDb => return Err("DuckDB support not compiled (enable duckdb-bundled feature)".to_string()), + #[cfg(feature = "duckdb-sidecar")] + DatabaseType::DuckDb => state.create_duckdb_pool(&db_config).await?, + #[cfg(not(feature = "duckdb-sidecar"))] + DatabaseType::DuckDb => return Err("DuckDB support is not compiled in this build".to_string()), DatabaseType::MongoDb => { if mongo_uses_legacy_driver(&db_config) { let mut client = diff --git a/src-tauri/src/commands/query.rs b/src-tauri/src/commands/query.rs index c135d71b9d..19b50fecb5 100644 --- a/src-tauri/src/commands/query.rs +++ b/src-tauri/src/commands/query.rs @@ -428,7 +428,7 @@ pub fn build_create_database_sql(options: dbx_core::db_admin_sql::CreateDatabase dbx_core::db_admin_sql::build_create_database_sql(options) } -#[cfg(feature = "duckdb-bundled")] +#[cfg(feature = "duckdb-sidecar")] #[tauri::command] pub fn build_duckdb_attach_database_sql( options: dbx_core::db_admin_sql::DuckDbAttachDatabaseSqlOptions, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 9dd9722e75..4a99446169 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1498,7 +1498,7 @@ pub fn run() { commands::query::build_search_result_where, commands::query::build_rename_object_sql, commands::query::build_create_database_sql, - #[cfg(feature = "duckdb-bundled")] + #[cfg(feature = "duckdb-sidecar")] commands::query::build_duckdb_attach_database_sql, commands::query::build_sqlite_attach_database_sql, commands::query::build_drop_object_sql, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index fdfb97e96e..1e0016908e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,15 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - #[cfg(feature = "duckdb-bundled")] - if std::env::args().any(|arg| arg == "--duckdb-worker") { - let runtime = tokio::runtime::Runtime::new().expect("Failed to create DuckDB worker runtime"); - if let Err(err) = runtime.block_on(dbx_core::db::duckdb_worker_runtime::run_stdio_worker()) { - eprintln!("DuckDB worker failed: {err}"); - std::process::exit(1); - } - return; - } - dbx_lib::run(); }