From 60a395b7b693bb81dde11260592b7829088eab9e Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Fri, 23 Oct 2026 16:14:13 +1000 Subject: [PATCH 1/7] Statically link DuckDB ICU and JSON extensions --- .github/workflows/build-check.yaml | 64 ++++++++++++++++++++++++++++++ Cargo.lock | 26 ++++++++++-- Cargo.toml | 7 +++- src/utils.rs | 7 +++- 4 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/build-check.yaml diff --git a/.github/workflows/build-check.yaml b/.github/workflows/build-check.yaml new file mode 100644 index 0000000..b564b43 --- /dev/null +++ b/.github/workflows/build-check.yaml @@ -0,0 +1,64 @@ +name: Build check (temporary) +on: + push: + branches: + - static-duckdb-extensions + +permissions: + contents: read + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + run: true + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + run: false + - target: x86_64-apple-darwin + os: macos-latest + run: true + - target: aarch64-apple-darwin + os: macos-latest + run: true + - target: x86_64-pc-windows-msvc + os: windows-latest + run: true + - target: aarch64-pc-windows-msvc + os: windows-latest + run: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + with: + targets: ${{ matrix.target }} + - name: Install aarch64 cross-compilation tools + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + - name: Build + shell: bash + run: time cargo build --release --target ${{ matrix.target }} + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc + CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ + - name: Report size + shell: bash + run: ls -l target/${{ matrix.target }}/release/apitally* + - name: Smoke test + if: matrix.run + shell: bash + run: | + BIN=target/${{ matrix.target }}/release/apitally + $BIN reset-db --db test.duckdb + for i in $(seq 20); do + echo "SELECT date_trunc('day', now() AT TIME ZONE 'UTC') AS day, now()::DATE AS d, now() - INTERVAL 1 DAY AS y, '{\"a\":1}'::JSON->>'\$.a' AS j" | $BIN sql --db test.duckdb + done + echo "SELECT extension_name, install_mode FROM duckdb_extensions() WHERE loaded" | $BIN sql --db test.duckdb diff --git a/Cargo.lock b/Cargo.lock index 9acd277..7f9da06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,6 +478,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" @@ -657,8 +666,7 @@ dependencies = [ [[package]] name = "duckdb" version = "1.10505.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970e05eedd3f55c435194d9104f90a9b4a79a80d6e73251bc9ff43e178130c4e" +source = "git+https://github.com/duckdb/duckdb-rs?tag=v1.10505.0#88f673c5cdf09e287c010a910db53b97e523a9fa" dependencies = [ "arrow", "cast", @@ -1234,10 +1242,10 @@ checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" [[package]] name = "libduckdb-sys" version = "1.10505.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb514dab5e271e849235c1cb98bd65a2ae107fbd619a6740219319c54a71d95" +source = "git+https://github.com/duckdb/duckdb-rs?tag=v1.10505.0#88f673c5cdf09e287c010a910db53b97e523a9fa" dependencies = [ "cc", + "cmake", "flate2", "pkg-config", "serde", @@ -1245,6 +1253,7 @@ dependencies = [ "tar", "ureq", "vcpkg", + "which", "zip", ] @@ -2261,6 +2270,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 5ca67dc..6dd0b15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,12 @@ arrow-ipc = { version = "=58.4.0", features = ["lz4"] } chrono = { version = "0.4", default-features = false, features = ["clock"] } clap = { version = "4", features = ["derive", "env"] } dirs = "6" -duckdb = { version = "=1.10505.0", features = ["bundled", "appender-arrow"] } +duckdb = { git = "https://github.com/duckdb/duckdb-rs", tag = "v1.10505.0", features = [ + "bundled-cmake", + "icu", + "json", + "appender-arrow", +] } iana-time-zone = "0.1" open = "5" regex = "1" diff --git a/src/utils.rs b/src/utils.rs index 8e29c92..f8ba278 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -57,8 +57,11 @@ pub fn resolve_db(db: Option>) -> Result> { } pub fn open_db(path: &Path) -> Result { - duckdb::Connection::open(path) - .with_context(|| format!("failed to open database {}", path.display())) + let conn = duckdb::Connection::open(path) + .with_context(|| format!("failed to open database {}", path.display()))?; + // Makes date/time functions independent of the machine's time zone + conn.execute_batch("SET TimeZone = 'UTC'")?; + Ok(conn) } /// If `s` is a compact duration string (``, e.g. `24h` or `7d`) or a simple From 8ef902eb93928c25a666f7ebc2da8d26d42d5d37 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Fri, 23 Oct 2026 16:37:50 +1000 Subject: [PATCH 2/7] Use native ARM runners and short Windows target dir in build check --- .github/workflows/build-check.yaml | 36 +++++++++++++----------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-check.yaml b/.github/workflows/build-check.yaml index b564b43..bbdc8ab 100644 --- a/.github/workflows/build-check.yaml +++ b/.github/workflows/build-check.yaml @@ -15,48 +15,44 @@ jobs: include: - target: x86_64-unknown-linux-gnu os: ubuntu-latest - run: true - target: aarch64-unknown-linux-gnu - os: ubuntu-latest - run: false + os: ubuntu-24.04-arm - target: x86_64-apple-darwin os: macos-latest - run: true - target: aarch64-apple-darwin os: macos-latest - run: true - target: x86_64-pc-windows-msvc os: windows-latest - run: true - target: aarch64-pc-windows-msvc - os: windows-latest - run: false + os: windows-11-arm runs-on: ${{ matrix.os }} + env: + # Keeps DuckDB's CMake object paths below the MSVC 260 character limit + CARGO_TARGET_DIR: ${{ runner.os == 'Windows' && 'C:/t' || 'target' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: targets: ${{ matrix.target }} - - name: Install aarch64 cross-compilation tools - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - sudo apt-get update - sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - name: Build shell: bash run: time cargo build --release --target ${{ matrix.target }} - env: - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc - CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ - name: Report size shell: bash - run: ls -l target/${{ matrix.target }}/release/apitally* + run: ls -l $CARGO_TARGET_DIR/${{ matrix.target }}/release/apitally* + - name: Package (Unix) + if: runner.os != 'Windows' + run: tar czf apitally-${{ matrix.target }}.tar.gz -C $CARGO_TARGET_DIR/${{ matrix.target }}/release apitally && ls -l apitally-*.tar.gz + - name: Package (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + Compress-Archive -Path $env:CARGO_TARGET_DIR/${{ matrix.target }}/release/apitally.exe -DestinationPath apitally-${{ matrix.target }}.zip + Get-Item apitally-*.zip | Format-List Name, Length - name: Smoke test - if: matrix.run shell: bash run: | - BIN=target/${{ matrix.target }}/release/apitally + BIN=$CARGO_TARGET_DIR/${{ matrix.target }}/release/apitally $BIN reset-db --db test.duckdb for i in $(seq 20); do echo "SELECT date_trunc('day', now() AT TIME ZONE 'UTC') AS day, now()::DATE AS d, now() - INTERVAL 1 DAY AS y, '{\"a\":1}'::JSON->>'\$.a' AS j" | $BIN sql --db test.duckdb From 31d9af29ec13d186c5936f607ede2d346be4b053 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Fri, 23 Oct 2026 16:38:21 +1000 Subject: [PATCH 3/7] Fix build check workflow env expression --- .github/workflows/build-check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-check.yaml b/.github/workflows/build-check.yaml index bbdc8ab..4f62bab 100644 --- a/.github/workflows/build-check.yaml +++ b/.github/workflows/build-check.yaml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} env: # Keeps DuckDB's CMake object paths below the MSVC 260 character limit - CARGO_TARGET_DIR: ${{ runner.os == 'Windows' && 'C:/t' || 'target' }} + CARGO_TARGET_DIR: ${{ startsWith(matrix.os, 'windows') && 'C:/t' || 'target' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable From 4f82d3fd275d530439bc9161a83c1da83665a404 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Fri, 23 Oct 2026 17:32:23 +1000 Subject: [PATCH 4/7] Build release targets natively and remove ICU workarounds from skill --- .github/workflows/build-check.yaml | 60 ------------------------------ .github/workflows/release.yaml | 20 ++++------ AGENTS.md | 8 +++- renovate.json | 2 +- skills/apitally-cli/SKILL.md | 10 ++--- 5 files changed, 19 insertions(+), 81 deletions(-) delete mode 100644 .github/workflows/build-check.yaml diff --git a/.github/workflows/build-check.yaml b/.github/workflows/build-check.yaml deleted file mode 100644 index 4f62bab..0000000 --- a/.github/workflows/build-check.yaml +++ /dev/null @@ -1,60 +0,0 @@ -name: Build check (temporary) -on: - push: - branches: - - static-duckdb-extensions - -permissions: - contents: read - -jobs: - build: - strategy: - fail-fast: false - matrix: - include: - - target: x86_64-unknown-linux-gnu - os: ubuntu-latest - - target: aarch64-unknown-linux-gnu - os: ubuntu-24.04-arm - - target: x86_64-apple-darwin - os: macos-latest - - target: aarch64-apple-darwin - os: macos-latest - - target: x86_64-pc-windows-msvc - os: windows-latest - - target: aarch64-pc-windows-msvc - os: windows-11-arm - runs-on: ${{ matrix.os }} - env: - # Keeps DuckDB's CMake object paths below the MSVC 260 character limit - CARGO_TARGET_DIR: ${{ startsWith(matrix.os, 'windows') && 'C:/t' || 'target' }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - with: - targets: ${{ matrix.target }} - - name: Build - shell: bash - run: time cargo build --release --target ${{ matrix.target }} - - name: Report size - shell: bash - run: ls -l $CARGO_TARGET_DIR/${{ matrix.target }}/release/apitally* - - name: Package (Unix) - if: runner.os != 'Windows' - run: tar czf apitally-${{ matrix.target }}.tar.gz -C $CARGO_TARGET_DIR/${{ matrix.target }}/release apitally && ls -l apitally-*.tar.gz - - name: Package (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - Compress-Archive -Path $env:CARGO_TARGET_DIR/${{ matrix.target }}/release/apitally.exe -DestinationPath apitally-${{ matrix.target }}.zip - Get-Item apitally-*.zip | Format-List Name, Length - - name: Smoke test - shell: bash - run: | - BIN=$CARGO_TARGET_DIR/${{ matrix.target }}/release/apitally - $BIN reset-db --db test.duckdb - for i in $(seq 20); do - echo "SELECT date_trunc('day', now() AT TIME ZONE 'UTC') AS day, now()::DATE AS d, now() - INTERVAL 1 DAY AS y, '{\"a\":1}'::JSON->>'\$.a' AS j" | $BIN sql --db test.duckdb - done - echo "SELECT extension_name, install_mode FROM duckdb_extensions() WHERE loaded" | $BIN sql --db test.duckdb diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f4db0e0..fea0af4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,7 +16,7 @@ jobs: - target: x86_64-unknown-linux-gnu os: ubuntu-latest - target: aarch64-unknown-linux-gnu - os: ubuntu-latest + os: ubuntu-24.04-arm - target: x86_64-apple-darwin os: macos-latest - target: aarch64-apple-darwin @@ -24,8 +24,11 @@ jobs: - target: x86_64-pc-windows-msvc os: windows-latest - target: aarch64-pc-windows-msvc - os: windows-latest + os: windows-11-arm runs-on: ${{ matrix.os }} + env: + # Keeps DuckDB's CMake object paths below the MSVC 260 character limit + CARGO_TARGET_DIR: ${{ startsWith(matrix.os, 'windows') && 'C:/build' || 'target' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable @@ -37,24 +40,15 @@ jobs: run: | VERSION="${GITHUB_REF_NAME#v}" sed -i.bak 's/^version = ".*"/version = "'"$VERSION"'"/' Cargo.toml - - name: Install aarch64 cross-compilation tools - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - sudo apt-get update - sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - name: Build run: cargo build --release --target ${{ matrix.target }} - env: - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc - CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ - name: Package (Unix) if: runner.os != 'Windows' - run: tar czf apitally-${{ matrix.target }}.tar.gz -C target/${{ matrix.target }}/release apitally + run: tar czf apitally-${{ matrix.target }}.tar.gz -C $CARGO_TARGET_DIR/${{ matrix.target }}/release apitally - name: Package (Windows) if: runner.os == 'Windows' shell: pwsh - run: Compress-Archive -Path target/${{ matrix.target }}/release/apitally.exe -DestinationPath apitally-${{ matrix.target }}.zip + run: Compress-Archive -Path $env:CARGO_TARGET_DIR/${{ matrix.target }}/release/apitally.exe -DestinationPath apitally-${{ matrix.target }}.zip - name: Upload artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: diff --git a/AGENTS.md b/AGENTS.md index 419a95b..2a04b98 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ skills/ - **HTTP client**: ureq (blocking; response body implements `Read` for streaming) - **Serialization**: serde + serde_json - **Error handling**: anyhow -- **Embedded database**: duckdb (statically linked, bundled Arrow via `duckdb::arrow::*`) +- **Embedded database**: duckdb (statically linked with the ICU and JSON extensions, bundled Arrow via `duckdb::arrow::*`) - **Distribution**: npm package (`@apitally/cli`); postinstall downloads the native binary ## CLI Subcommands @@ -68,6 +68,8 @@ The API base URL follows the same pattern: `--api-base-url` flag > `APITALLY_API ## Common Commands +Building requires CMake, because DuckDB is compiled from source with the ICU extension. + ```bash cargo build # Build (debug) cargo build --release # Build (release, optimized) @@ -85,7 +87,7 @@ The CLI is published to npm as `@apitally/cli`. The npm package contains no Rust Triggered by publishing a GitHub release. Two jobs: -1. **build** — cross-compiles for 6 targets (linux x64/arm64, macOS x64/arm64, Windows x64/arm64), packages as `.tar.gz`/`.zip`, uploads as artifacts. +1. **build** — builds for 6 targets (linux x64/arm64, macOS x64/arm64, Windows x64/arm64) on native runners, except macOS x64, which is cross-compiled on macOS arm64. Packages as `.tar.gz`/`.zip`, uploads as artifacts. 2. **publish** — attaches artifacts to the GitHub release, then publishes to npm (version derived from the git tag; pre-releases use the `next` dist-tag). ## Architecture Patterns @@ -125,6 +127,8 @@ Inline `#[cfg(test)] mod tests` in each source file. Run with `cargo test`. - Prefer simple, concise code; avoid unnecessary abstractions - Use `anyhow::Result` for error handling in application code - Use `duckdb::arrow::*` re-exports for all Arrow type imports (ensures type compatibility with duckdb). The standalone `arrow` and `arrow-ipc` crates in `Cargo.toml` exist solely to enable features (`ipc`, `json`, `chrono-tz` on `arrow`; `lz4` on `arrow-ipc`) that duckdb doesn't activate on its bundled Arrow. Cargo unifies both into a single Arrow instance. When updating `duckdb`, check that its Arrow version still matches and bump the `arrow`/`arrow-ipc` versions in `Cargo.toml` if needed (`cargo tree -p duckdb --depth 1`). +- `duckdb` is a git dependency on a duckdb-rs release tag, because the ICU extension requires the `bundled-cmake` feature, which needs the full DuckDB sources that the crates.io package omits. To update, change the `tag` in `Cargo.toml`. +- The DuckDB session time zone is set to UTC in `open_db`, so date/time functions don't depend on the machine's time zone. - Follow existing code patterns and conventions - Verify work compiles and passes `cargo clippy` before considering a task complete - Keep user-facing output concise diff --git a/renovate.json b/renovate.json index 5d55ead..9482f78 100644 --- a/renovate.json +++ b/renovate.json @@ -4,7 +4,7 @@ "packageRules": [ { "matchManagers": ["cargo"], - "matchPackageNames": ["duckdb", "arrow", "arrow-ipc"], + "matchDepNames": ["duckdb", "arrow", "arrow-ipc"], "groupName": "duckdb and arrow" }, { diff --git a/skills/apitally-cli/SKILL.md b/skills/apitally-cli/SKILL.md index f9340c5..ef98d23 100644 --- a/skills/apitally-cli/SKILL.md +++ b/skills/apitally-cli/SKILL.md @@ -151,12 +151,12 @@ npx @apitally/cli request-details ### Discover slow spans and expand a trace -The examples below use app `1` and a sample trace ID; substitute IDs from your results. Discover spans taking at least 100 ms. SQL time bounds use epoch nanoseconds because the bundled DuckDB does not support `TIMESTAMPTZ - INTERVAL` (24 hours = 86400000000000 ns): +The examples below use app `1` and a sample trace ID; substitute IDs from your results. Discover spans taking at least 100 ms: ```bash npx @apitally/cli traces 1 --since 24h \ --filters '[{"field":"duration_ns","op":"gte","value":100000000}]' --db -npx @apitally/cli sql "SELECT trace_id, span_id, name, duration_ns / 1000000.0 AS duration_ms FROM spans WHERE app_id = 1 AND start_time_ns >= epoch_ns(current_timestamp) - 86400000000000 AND duration_ns >= 100000000 ORDER BY duration_ns DESC LIMIT 20" +npx @apitally/cli sql "SELECT trace_id, span_id, name, duration_ns / 1000000.0 AS duration_ms FROM spans WHERE app_id = 1 AND start_time_ns >= epoch_ns(now() - INTERVAL 1 DAY) AND duration_ns >= 100000000 ORDER BY duration_ns DESC LIMIT 20" ``` Fetch all available spans for a discovered trace ID. Keep only the trace-ID filter, remove sampling and time bounds, and select the fields needed for the investigation. `--fields` replaces defaults, so this example lists all fields: @@ -177,7 +177,7 @@ Find request trace IDs, then expand them using the trace-ID fetch above: ```bash npx @apitally/cli request-logs 1 --since 24h \ --fields 'status_code,response_time_ms' --db -npx @apitally/cli sql "SELECT request_uuid, trace_id, status_code, response_time_ms FROM request_logs WHERE app_id = 1 AND epoch_ns(timestamp) >= epoch_ns(current_timestamp) - 86400000000000 AND trace_id IS NOT NULL ORDER BY response_time_ms DESC LIMIT 20" +npx @apitally/cli sql "SELECT request_uuid, trace_id, status_code, response_time_ms FROM request_logs WHERE app_id = 1 AND timestamp >= now() - INTERVAL 1 DAY AND trace_id IS NOT NULL ORDER BY response_time_ms DESC LIMIT 20" ``` Join on **both app and trace IDs**, not request UUID: @@ -259,10 +259,10 @@ See [references/duckdb_json_functions.md](references/duckdb_json_functions.md) f ### Date truncation and grouping -Datetime columns are `TIMESTAMPTZ`. Direct casts like `timestamp::DATE` error out (no ICU extension). Use `AT TIME ZONE 'UTC'` first: +Datetime columns are `TIMESTAMPTZ`. The session time zone is UTC, so date functions and casts like `timestamp::DATE` use UTC boundaries: ```sql -SELECT date_trunc('day', timestamp AT TIME ZONE 'UTC') AS day, count(*) AS n +SELECT date_trunc('day', timestamp) AS day, count(*) AS n FROM request_logs WHERE app_id = AND timestamp >= '' GROUP BY day ORDER BY day ``` From c0bba2deff0411a678d929e0bd6151c97315312d Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Fri, 23 Oct 2026 17:36:27 +1000 Subject: [PATCH 5/7] Use C:/target as Windows target dir --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fea0af4..9a2cb02 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} env: # Keeps DuckDB's CMake object paths below the MSVC 260 character limit - CARGO_TARGET_DIR: ${{ startsWith(matrix.os, 'windows') && 'C:/build' || 'target' }} + CARGO_TARGET_DIR: ${{ startsWith(matrix.os, 'windows') && 'C:/target' || 'target' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable From ff6543d987e992ce52df365d8c1585b37d912735 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Fri, 23 Oct 2026 17:40:26 +1000 Subject: [PATCH 6/7] Use system time zone for DuckDB session --- AGENTS.md | 1 - skills/apitally-cli/SKILL.md | 2 +- skills/apitally-cli/references/commands.md | 2 +- src/utils.rs | 7 ++----- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2a04b98..36b693c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -128,7 +128,6 @@ Inline `#[cfg(test)] mod tests` in each source file. Run with `cargo test`. - Use `anyhow::Result` for error handling in application code - Use `duckdb::arrow::*` re-exports for all Arrow type imports (ensures type compatibility with duckdb). The standalone `arrow` and `arrow-ipc` crates in `Cargo.toml` exist solely to enable features (`ipc`, `json`, `chrono-tz` on `arrow`; `lz4` on `arrow-ipc`) that duckdb doesn't activate on its bundled Arrow. Cargo unifies both into a single Arrow instance. When updating `duckdb`, check that its Arrow version still matches and bump the `arrow`/`arrow-ipc` versions in `Cargo.toml` if needed (`cargo tree -p duckdb --depth 1`). - `duckdb` is a git dependency on a duckdb-rs release tag, because the ICU extension requires the `bundled-cmake` feature, which needs the full DuckDB sources that the crates.io package omits. To update, change the `tag` in `Cargo.toml`. -- The DuckDB session time zone is set to UTC in `open_db`, so date/time functions don't depend on the machine's time zone. - Follow existing code patterns and conventions - Verify work compiles and passes `cargo clippy` before considering a task complete - Keep user-facing output concise diff --git a/skills/apitally-cli/SKILL.md b/skills/apitally-cli/SKILL.md index ef98d23..56fceab 100644 --- a/skills/apitally-cli/SKILL.md +++ b/skills/apitally-cli/SKILL.md @@ -259,7 +259,7 @@ See [references/duckdb_json_functions.md](references/duckdb_json_functions.md) f ### Date truncation and grouping -Datetime columns are `TIMESTAMPTZ`. The session time zone is UTC, so date functions and casts like `timestamp::DATE` use UTC boundaries: +Datetime columns are `TIMESTAMPTZ`. SQL output, date functions, and casts like `timestamp::DATE` use the system time zone, the same default as `metrics --timezone`. For UTC boundaries, use `timestamp AT TIME ZONE 'UTC'`: ```sql SELECT date_trunc('day', timestamp) AS day, count(*) AS n diff --git a/skills/apitally-cli/references/commands.md b/skills/apitally-cli/references/commands.md index 597c5c1..c5716ba 100644 --- a/skills/apitally-cli/references/commands.md +++ b/skills/apitally-cli/references/commands.md @@ -396,7 +396,7 @@ Available tables: `apps`, `app_envs`, `consumers`, `endpoints`, `metrics`, `requ **Important:** The database may contain data from previous sessions. Always filter queries by `app_id` and the current investigation scope: `timestamp` for request logs, `period_start`/`period_end` for metrics, and exact `trace_id` values or integer `start_time_ns` bounds for spans. -DuckDB uses a [PostgreSQL-compatible SQL dialect](https://duckdb.org/docs/stable/sql/dialect/overview). The bundled DuckDB has no ICU extension, so `TIMESTAMPTZ` columns cannot be cast directly to `DATE`. Use `(timestamp AT TIME ZONE 'UTC')::DATE` or `date_trunc('day', timestamp AT TIME ZONE 'UTC')` for date conversion and grouping. +DuckDB uses a [PostgreSQL-compatible SQL dialect](https://duckdb.org/docs/stable/sql/dialect/overview). `TIMESTAMPTZ` values are output, truncated, and cast to `DATE` in the system time zone. Use `timestamp AT TIME ZONE 'UTC'` for UTC. Example output: diff --git a/src/utils.rs b/src/utils.rs index f8ba278..8e29c92 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -57,11 +57,8 @@ pub fn resolve_db(db: Option>) -> Result> { } pub fn open_db(path: &Path) -> Result { - let conn = duckdb::Connection::open(path) - .with_context(|| format!("failed to open database {}", path.display()))?; - // Makes date/time functions independent of the machine's time zone - conn.execute_batch("SET TimeZone = 'UTC'")?; - Ok(conn) + duckdb::Connection::open(path) + .with_context(|| format!("failed to open database {}", path.display())) } /// If `s` is a compact duration string (``, e.g. `24h` or `7d`) or a simple From 3de7068b3236cb73e3b433dbe328f0ce26e806d7 Mon Sep 17 00:00:00 2001 From: Simon Gurcke Date: Fri, 23 Oct 2026 17:41:34 +1000 Subject: [PATCH 7/7] Simplify time zone note in skill --- skills/apitally-cli/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/apitally-cli/SKILL.md b/skills/apitally-cli/SKILL.md index 56fceab..fad7fe8 100644 --- a/skills/apitally-cli/SKILL.md +++ b/skills/apitally-cli/SKILL.md @@ -259,7 +259,7 @@ See [references/duckdb_json_functions.md](references/duckdb_json_functions.md) f ### Date truncation and grouping -Datetime columns are `TIMESTAMPTZ`. SQL output, date functions, and casts like `timestamp::DATE` use the system time zone, the same default as `metrics --timezone`. For UTC boundaries, use `timestamp AT TIME ZONE 'UTC'`: +Datetime columns are `TIMESTAMPTZ`. SQL output, date functions, and casts like `timestamp::DATE` use the system time zone. For UTC boundaries, use `timestamp AT TIME ZONE 'UTC'`: ```sql SELECT date_trunc('day', timestamp) AS day, count(*) AS n