Skip to content

feat(databases): count databases in the workspace - #254

Merged
shefeek-jinnah merged 1 commit into
mainfrom
feat/databases-count
Aug 12, 2026
Merged

shefeek-jinnah merged 1 commit into
mainfrom
feat/databases-count

Conversation

@shefeek-jinnah

Copy link
Copy Markdown
Contributor

Why

There was no way to ask how many managed databases a workspace holds. databases list shows one page on purpose, and the API's per-page count field is a page size rather than a total — so counting meant paging by hand and adding up rows.

What

$ hotdata databases count
7

$ hotdata databases count -o json
{
  "count": 7
}

$ hotdata databases count -o yaml
count: 7

table prints a bare integer and nothing else, so $(hotdata databases count) drops straight into a script. The spinner writes to stderr and suppresses itself when stderr isn't a TTY, so stdout stays clean when piped. An empty workspace prints 0 in every format — no "No databases found." hint, because zero is the answer, not an empty result needing explanation.

No --limit / --cursor: a count is inherently whole-workspace, and paging flags would let a caller ask for a "count" that silently described one page — the exact confusion this command removes.

How

Backed by GET /v1/databases/count (runtimedb #1053, shipped in SDK 0.11.0), which answers from one indexed aggregate. The first implementation of this command drained the paged listing client-side; that costs a sequential round trip per 100 databases (DATABASE_LIST_MAX_LIMIT) and throws every row away on arrival — roughly 8–25 minutes at a million databases, since cursor paging can't be parallelized. The endpoint exists so the CLI doesn't have to do that.

The output field stays count while the API answers with total. That's deliberate: {"count": N} is what reads naturally from a command called count, whereas the API needed the other name because its list response already spends count on a page size.

Also here — both forced by the 0.11.0 upgrade

  • databases().list gained search and batch, so the three call sites pass None, None. Behaviour is unchanged — the client omits absent query params, and the server applies no join and no predicate without them.
  • TableInfo gained required partition_by / sorted_by, so ten information-schema mocks across the databases and indexes tests now declare them. Worth knowing how this failed: the mocks stopped deserializing, which hit an error path that calls .exit(), so the test process aborted instead of a test failing. The harness reported "test exited abnormally" with no failing test named.

Tests

  • count_databases_reads_the_total_from_the_count_endpoint — mocks the endpoint with expect(1) and registers the listing with expect(0), so a regression back to draining pages fails on the wrong request being made, not just a wrong number.
  • count_databases_empty_workspace_is_zero
  • database_count_serialization_shapes — {"count":7} / count: 7
  • databases --help lists count; databases count --help documents --output

308 unit + 16 integration tests pass. cargo fmt and cargo clippy --all-targets clean.

Docs

skills/hotdata/SKILL.md gains the command and a bullet noting it spans all pages and prints a bare integer for scripting. README.md is a narrative quickstart that doesn't enumerate subcommands — unchanged.

@shefeek-jinnah
shefeek-jinnah requested a review from a team as a code owner August 12, 2026 13:13
@shefeek-jinnah
shefeek-jinnah requested review from rohan-hotdata and removed request for a team August 12, 2026 13:13
`hotdata databases count` reports how many managed databases a workspace
holds. Prints a bare integer by default so it composes in scripts;
--output json/yaml render {"count": N} / count: N.

`databases list` shows one page on purpose, and the API's per-page
`count` field is a page size, not a total, so there was no way to ask
the question without paging by hand and adding up rows.

Backed by GET /v1/databases/count (runtimedb #1053, SDK 0.11.0), which
answers from a single indexed aggregate. The alternative — draining the
paged listing client-side — costs one sequential round trip per 100
databases and discards every row on arrival.

Also in this change, both forced by the 0.11.0 upgrade:

- `databases().list` takes `search` and `batch` filters now, so the
  three call sites pass `None, None`. Behaviour is unchanged: the client
  omits absent query params and the server applies no filter without
  them.
- `TableInfo` gained required `partition_by` / `sorted_by` fields, so
  the information-schema mocks in the databases and indexes tests
  declare them. Without this the mocks fail to deserialize and the test
  process aborts rather than failing an assertion.
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.26087% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/commands/databases.rs 80.59% 13 Missing ⚠️
src/main.rs 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread src/commands/databases.rs
Comment on lines +1033 to +1036
/// One request. The list endpoint's `count` field is a page size, not a
/// workspace total, so this used to drain every page and take the length —
/// which cost a round trip per 100 databases and threw every row away on
/// arrival. `GET /v1/databases/count` answers it in a single indexed query.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: "this used to drain every page" reads as CLI history, but no released version of this command ever drained — the drain only existed in an earlier iteration of this PR (not blocking). Stating it in the present tense keeps the useful part (why the endpoint is used) without implying a past behaviour a reader might go looking for in the changelog, e.g. "the list endpoint's count field is a page size, not a workspace total, so a client-side total would mean a round trip per 100 databases with every row discarded on arrival."

the caller (`src/commands/databases.rs:930`), so counting today means paging by
hand and adding up rows.

The API offers no total-count endpoint. `GET /v1/databases` returns

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this spec is committed alongside an implementation that contradicts it (not blocking). "The API offers no total-count endpoint" is false as of this change — the code calls GET /v1/databases/count (src/commands/databases.rs:1041). The ## Counting section ("reuses the existing cursor-drain helper unchanged", plus the ### Cost paragraph accepting the drain) and testing item 1 ("Drains and sums across pages") all describe the approach that was rejected. This is the only file under docs/superpowers/specs/, so there is no established convention marking these as frozen historical snapshots — a reader will take it as the design of record. Either update the Problem/Counting/Cost/Testing sections to the endpoint-based design, or add a note at the top recording that the drain approach was superseded by the 0.11.0 count endpoint.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cycle 3: the spec doc that contradicted the implementation is no longer part of this PR, so the prior nit is moot. No blocking issues in the count command, the 0.11.0 call-site updates, or the information-schema mock changes. Note that CI / test and Integration Tests / integration were still pending when this review ran — I did not run anything myself, so the SDK signature changes (list(.., None, None), databases().count(None, None), resp.total) are unverified against a real compile.

@shefeek-jinnah
shefeek-jinnah merged commit 6c11117 into main Aug 12, 2026
14 checks passed
@shefeek-jinnah
shefeek-jinnah deleted the feat/databases-count branch August 12, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant