feat(kv): add native key-value store to hyperdb-api#3
Closed
StefanSteiner wants to merge 19 commits into
Closed
Conversation
Single-table KvStore abstraction over _hyperdb_kv_store, sync + async twins, serde get_as/set_as, upsert emulation (Hyper has no ON CONFLICT). Covers both milestones; M1 (feat) planned next, M2 (fix) later.
Reconcile findings from both plan reviewers (Phase 2/3): - Drop PRIMARY KEY from all backing-table DDL (empirically rejected by Hyper: 0A000 Index support is disabled). Single kv_create_table_sql helper is the sole DDL source for both twins + both kv_list_stores guards. - Replace invented Connection::query_count with execute_query + scalar. - Uniform .as_str() on all bound params across sync + async twins. - Benchmark: #[expect]->#[allow(cast_precision_loss)] to avoid unfulfilled_lint_expectations; drop unused mod common.
Scaffolds the KV module: KV_TABLE, KV_MAX_NAME_BYTES, KV_CHARSET consts, validate_kv_name(), and the kv_create_table_sql() DDL helper (no PRIMARY KEY; Hyper rejects one). These are consumed by KvStore in the next commit; the transient dead_code warnings resolve there.
KvStore<'conn> borrows &Connection (mirrors Catalog/Inserter). open() creates the PK-less backing table via kv_create_table_sql; new() uses the default location, with_target() is the M2 seam. Connection gains kv_store() and kv_list_stores(). PK-probe test empirically confirms (pinned hyperd): a raw duplicate INSERT is ACCEPTED (1 row) because the table has no PRIMARY KEY -- which is exactly why set() must use the app-side conditional-INSERT upsert.
set() is an UPDATE-then-conditional-INSERT upsert (distinct $4/$5 placeholders, extended-query-safe); get_as/set_as JSON-(de)serialize via serde_json, mapping failures to Error::Serialization. Empirically verified against real hyperd (7 integration tests pass; upsert overwrite confirmed). Closes the per-iteration reviewer's transient doctest finding: the KvStore struct example calling set/get now compiles (cargo test --doc green).
delete returns whether a row was removed; size uses COUNT(*) scalar (always one row); keys streams ORDER BY key ASC; clear removes only this store's rows from the shared table. Verified against real hyperd, incl. same-key/different-store isolation and empty kv_list_stores on fresh db.
pop peeks the lowest-ordered key and deletes it in one transaction (first_row consumes the Rowset, releasing its statement guard before the DELETE). set_batch validates every key up front, then upserts all pairs in one transaction; both roll back on error preserving the original error.
Add a Key-Value Store README section, CHANGELOG Added entry, and a DEVELOPMENT design note. Convert public-doc intra-doc links to the crate-private KV_TABLE / kv_create_table_sql into inline code (they tripped rustdoc's private_intra_doc_links lint). Also correct the stale Parameterized Queries note to reflect the real extended-query protocol (prepare_typed + binary params), not escaped literals.
The `pool` module's `//!` header links (`[`Pool`]`, `[`create_pool`], `[`PoolConfig`]`, `[`ConnectionPool`]`, `[`SyncPoolConfig`]`, `[`RecycleStrategy`]`, `[`SyncRecycleStrategy`]`, and their methods) all failed to resolve under `RUSTDOCFLAGS="-D warnings"` with "no item named X in scope" — 9 errors that broke `cargo doc` on the crate. Root cause: `pub mod pool;` (and `pub mod copy;`) each carried a redundant *outer* `///` doc line in lib.rs *in addition to* the module file's own *inner* `//!` header. When a module declaration has both, rustdoc merges the two doc strings and resolves the whole block in the parent (crate-root) scope. In crate-root scope, extern crates (`deadpool`) and root re-exports resolve, but items reachable only as `crate::pool::*` do not — hence the failures. `pool` was the only module whose inner `//!` links pointed at module-only items, so it alone surfaced the break; `copy` had the same latent misconfiguration without triggering it. Fix: drop both redundant outer `///` lines. Each module's `//!` header already carries the same one-line summary (it renders identically on the crate index), and every other module in lib.rs already uses inner-only docs. Restores pure module-scope link resolution. `cargo doc -p hyperdb-api --no-deps` now builds clean with `RUSTDOCFLAGS="-D warnings"` (0 warnings); fmt + clippy unaffected.
Adversarial Phase-5 sweep found the key-validation tests only exercised set/get, and the async suite lacked isolation + empty-pop coverage. - sync: validates_key_on_every_entry_point now asserts InvalidName from set/get/delete/exists/get_as/set_as (was set+get only) - async: add async_validates_key_on_every_entry_point mirroring sync - async: add async_store_isolation (same key, two stores, distinct values) - async: tighten clear() assertion to == 2 and assert empty-store pop == None
- lib.rs: add KvStore/AsyncKvStore to the crate-level "Key Types" list (they were already in the lifetime-safety hierarchy but missing here) - async_kv_store: add the `&str`-coercion bind comment present in the sync KvStore::get twin, so the async path documents the same rationale
Owner
Author
|
Superseded by the upstream PR against tableau/hyper-api-rust (which has the CI runners): tableau#182. Closing this fork-targeted duplicate. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a string-native key-value store to
hyperdb-api(Milestone 1 of the KVfeature; the MCP surfacing is a separate follow-up).
Connection::kv_store(name)and
AsyncConnection::kv_store(name)return aKvStore/AsyncKvStorehandlescoped to a store name, all namespaced within a single fixed backing table
_hyperdb_kv_store(store_name, key, value).API surface
Per handle (sync + async twins):
get/set— plain-string values (upsert)get_as::<T>/set_as::<T>— anyserde-(de)serializable type, stored as JSONdelete,exists,size,keys(ordered),clearpop— destructive get-of-lowest-key, in one transactionset_batch— write many entries atomically in one transactionPlus
Connection::kv_list_stores()/AsyncConnection::kv_list_stores()— across-store discovery op returning the names of all non-empty stores.
New error variant:
Error::Serialization(JSON round-trip failures inget_as/set_as).Design notes
PRIMARY KEYon the backing table. Hyper rejectsPRIMARY KEYatCREATE TABLE(0A000: Index support is disabled, verified live). Uniquenessis enforced application-side by an UPDATE-then-conditional-
INSERT … SELECT … WHERE NOT EXISTSupsert — the same idiom this repo already uses for_table_catalog. hyperd serializes statements per connection, so no duplicaterows result.
store_nameandkey/valueare always boundas
$Nparams via the real extended-query protocol (Parse/Bind/Execute); onlythe bare table name is interpolated (trusted, construction-time).
kv_create_table_sql()helper so the sync/asynctwins can't diverge.
Docs
hyperdb-api/README.md— overview bullet + a Key-Value Store sectioncompile_failborrow test) and the "Key Types" listhyperdb-api/CHANGELOG.md—### Addedentryhyperdb-api/DEVELOPMENT.md— internalsdocs/superpowers/Testing
cargo fmt --check— cleancargo clippy -p hyperdb-api --all-targets -- -D warnings— cleanRUSTDOCFLAGS="-D warnings" cargo doc -p hyperdb-api --no-deps— 0 warnings(also fixes pre-existing broken intra-doc links in the
poolmodule docs)make test-api(core + api) — 918 passed / 0 failed against a realhyperd(14 dedicated KV tests: 12 sync + 4 async, covering upsert, serde round-trip,
ordering, destructive pop, empty-store pop, batch atomicity, per-method
invalid-name rejection, and store isolation)
Notes for reviewers
This branch went through the full plan → adversarial-plan-review → execute →
final-adversarial-sweep loop. The final sweep's 6 confirmed findings (all
test-coverage / doc polish; no correctness or security defects) are folded into
the last two commits.
hyperdb-mcpis intentionally untouched — surfacingthe store through MCP tools is Milestone 2, a separate PR.