Skip to content

chore(api): v0.3.0 follow-ups (A: typed I/O sources, B: InvalidOperation, C: structured SQLSTATE)#76

Merged
StefanSteiner merged 4 commits into
tableau:mainfrom
StefanSteiner:ssteiner/v0.3.0-followups
May 28, 2026
Merged

chore(api): v0.3.0 follow-ups (A: typed I/O sources, B: InvalidOperation, C: structured SQLSTATE)#76
StefanSteiner merged 4 commits into
tableau:mainfrom
StefanSteiner:ssteiner/v0.3.0-followups

Conversation

@StefanSteiner
Copy link
Copy Markdown
Contributor

Summary

The four #70 follow-ups for the v0.3.0 bundle. 3 of 4 land here; D defers to v0.3.x as #75.

  • A — Typed I/O sources in process.rs. 12 io::Error sites in HyperProcess startup/shutdown move from Error::internal(format!("...: {e}")) to Error::connection_with_io(message, e), preserving the typed std::io::Error so callers can introspect via std::error::Error::source().
  • B — Error::InvalidOperation variant. Separates caller-API misuse from library invariant violations. 5 ArrowInserter state-machine errors (mode mixing, schema-already-sent) move from Error::Internal to Error::InvalidOperation. Error::Internal rustdoc clarified to point at InvalidOperation for caller misuse.
  • C — Structured SQLSTATE on Cancelled / Closed / Connection. Reverses the post-Flatten Error type to canonical M-ERRORS shape (drop Client wrapper, Box<dyn> source, Option<ErrorKind>) #70 caveat. SQLSTATE codes that arrive via cancellation (57014), connection-class (08*), or close-class (57P0*) wire errors are now exposed via the variant's sqlstate field and Error::sqlstate() returns them. Breaking: Error::Cancelled and Error::Closed change from tuple to struct variants. Error::Connection gains a sqlstate field.
  • D (deferred) — Flatten internal client::Error. 51 producers + 15 kinds, larger than originally scoped, internal-only (zero external consumers), no user-facing benefit. Filed as #75 for v0.3.x.

Bundle policy: chore: prefix to defer release-please version bump until the v0.3.0 rollup feat: commit. Each follow-up is breaking on its own; landing under the bundle umbrella.

Commits

  1. 13f6942 — docs typo fix (feat!:feat:) in MIGRATING-0.3.md rollup note.
  2. 9dde324 — Follow-up A. 12 io::Error sites migrated; 6 non-IO sites left as Error::internal (timeout, descriptor parse, state assertions).
  3. 33f73e4 — Follow-up B. New variant + constructor; 5 inserter state-machine sites migrated; mutex-poisoned sites correctly stay Internal (upstream panic, not caller misuse).
  4. 666aa4f — Follow-up C. Variant restructuring + 3 new *_with_sqlstate constructors; broadened Error::sqlstate(); updated From<client::Error> mapping; one MCP match site updated for the tuple→struct transition.

Migration

Highlights from MIGRATING-0.3.md:

// Follow-up B — caller-misuse vs invariant
match err {
    Error::InvalidOperation(_) => /* user-API misuse, caller bug */,
    Error::Internal { .. } => /* library invariant violation */,
    other => return Err(other),
}

// Follow-up C — destructure for SQLSTATE, or use the helper
match err {
    Error::Cancelled { message, sqlstate: Some(code), .. } if code == "57014" => { /* user cancel */ }
    Error::Closed { sqlstate: Some(code), .. } if code == "57P01" => { /* admin shutdown */ }
    other => return Err(other),
}

if err.sqlstate() == Some("08006") { /* connection failure */ }

Verification

Why Follow-up D defers

Scope grew on second look. Original plan estimated "6 producers, mechanical." Actual workspace state: 51 producer call sites in hyperdb-api-core/src/client/{client,async_client,connection,grpc/*,tls}.rs, with 15 ErrorKind variants (3 more than #70's public type — InvalidData, InvalidInput, UnexpectedEof).

No user-facing benefit. client::Error is internal — not re-exported from hyperdb-api, zero external consumers (verified via grep). The benefit of flattening it is purely codebase-internal hygiene.

v0.3.0 has all user-facing improvements without it. Shipping the bundle on time matters more than internal cleanup that downstream callers can't observe.

Filed as #75 for v0.3.x. Approach mirrors #70's pattern.

Test plan

  • All workspace gates green on macOS aarch64
  • Unit tests for Error::InvalidOperation constructor + Error::sqlstate() broadened behavior
  • async_parity_smoke end-to-end against local hyperd
  • Adversarial review (line-level + architectural reviewers in parallel) — both clean
  • Linux CI (will run on push)

This closes the v0.3.0 follow-up work. Next step in the bundle: the rollup feat: commit with a BREAKING CHANGE: footer aggregating #70, #69, #61, #62, and follow-ups A/B/C.

Migrates 10 io::Error sites in HyperProcess startup/shutdown from
Error::internal(format!("...: {e}")) to Error::connection_with_io.
This preserves the typed std::io::Error in Error::Connection { source:
Option<io::Error> } so downstream callers can introspect the cause via
std::error::Error::source() instead of parsing the message string.

Sites migrated:
- TcpListener::bind (callback listener creation)
- local_addr / set_nonblocking on the listener
- create_dir_all (UDS socket directory)
- cmd.spawn (hyperd process start)
- listener.accept (callback wait)
- set_nonblocking on the callback stream
- read_exact (length and descriptor reads)
- child.wait (3 sites in shutdown)

Sites left as Error::internal (non-IO):
- timeout waiting for callback (state assertion)
- empty endpoint descriptor (protocol invariant)
- UTF-8 decode failure on descriptor (FromUtf8Error, not io::Error)
- invalid descriptor format (parse failure)
- require_endpoint / require_grpc_endpoint mode mismatch (state)
Separates true library invariant violations (Error::Internal) from
caller-API misuse (Error::InvalidOperation). The five ArrowInserter
state-machine errors that detect mode mixing or schema-already-sent
conditions are caller bugs, not internal invariant violations — they
move to Error::InvalidOperation so callers can match on them
distinctly.

Variant + constructor follow the established String-tuple pattern
(Error::invalid_operation(impl Into<String>)).

Sites migrated (all in arrow_inserter.rs):
- insert_data() / insert_batch() mode mix
- insert_data() schema-already-sent
- insert_record_batches() / insert_batch() mode mix
- insert_record_batches() before insert_data()
- insert_raw() / insert_batch() mode mix
- insert_batch() / raw-IPC mode mix

Sites left as Error::Internal:
- inserter.rs:709 (Failed to initialize COPY connection — wire-level)
- inserter.rs:1952/2038 (ChunkSender mutex poisoned — actual invariant
  violation indicating an upstream panic)

Also clarifies Error::Internal rustdoc to point at Error::InvalidOperation
for caller misuse, and updates MIGRATING-0.3.md with a new "Follow-up
B" section + adds the variant to the enum diagram + constructor list.
Reverses the post-tableau#70 caveat that non-Server SQLSTATE codes were folded
into the message string. Connection-class (08*), close-class (57P0*),
and cancellation (57014) SQLSTATEs are now exposed structurally via the
variant's sqlstate field, and Error::sqlstate() returns them too.

Variant shape changes (breaking — Cancelled and Closed go from tuple
to struct variants):

  Error::Cancelled(String)
    -> Error::Cancelled { message: String, sqlstate: Option<String> }

  Error::Closed(String)
    -> Error::Closed { message: String, sqlstate: Option<String> }

  Error::Connection { message, source }
    -> Error::Connection { message, source, sqlstate: Option<String> }

New constructors carry SQLSTATE explicitly; existing constructors stay
source-compatible (default sqlstate: None):
  - Error::cancelled_with_sqlstate(msg, code)
  - Error::closed_with_sqlstate(msg, code)
  - Error::connection_with_sqlstate(msg, code)

The From<client::Error> mapping populates sqlstate for these kinds.
Updates Error::sqlstate() rustdoc; updates the existing
sqlstate_returns_some_only_for_server unit test (renamed) to assert the
new structural behavior; updates the one MCP match site that
destructured Closed/Cancelled as tuples.

MIGRATING-0.3.md: adds a Follow-up C section with destructuring +
Error::sqlstate() recipes, updates the enum diagram, and reverses the
prior caveat about non-Server SQLSTATE.
@StefanSteiner StefanSteiner enabled auto-merge (squash) May 28, 2026 21:25
@StefanSteiner StefanSteiner disabled auto-merge May 28, 2026 21:25
@StefanSteiner StefanSteiner merged commit 4008b07 into tableau:main May 28, 2026
11 checks passed
StefanSteiner added a commit that referenced this pull request May 28, 2026
* feat: stabilize v0.3.0 public API bundle

This commit aggregates the breaking and additive API changes that ship
together as v0.3.0. The individual PRs landed under chore: prefixes to
defer release-please from cutting an early version; this single feat:
commit with a BREAKING CHANGE: footer is the trigger for the v0.3.0
release PR.

Bundle contents (all merged to main):
- #70 (PR #71)  — Flat public Error enum + ergonomic constructors
                  workspace-wide
- #69 (PR #73)  — Transaction API consolidation (RAII guard recommended;
                  raw trio deprecated and #[doc(hidden)])
- #61 + #62
  (PR #74)      — FromRow modernization: #[derive(FromRow)] in new
                  hyperdb-api-derive crate, RowAccessor with cached
                  name->index lookup, breaking trait signature change,
                  blanket tuple impls deleted, #[hyperdb(rename)] and
                  #[hyperdb(index)] attributes
- #76           — Follow-ups A/B/C: typed io::Error sources in process.rs,
                  Error::InvalidOperation variant for caller misuse,
                  structured SQLSTATE on Cancelled/Closed/Connection

Follow-up D (flatten internal client::Error) deferred to v0.3.x as
issue #75 — internal-only, zero external consumers, larger than originally
scoped.

The code change in this commit is a small documentation refresh on the
crate-level rustdoc to (a) include hyperdb-api-derive in the companion
crates list and (b) fix a stale crate name (sea-query-hyper ->
sea-query-hyperdb). The body of the commit is the BREAKING CHANGE:
footer below; release-please uses it to generate the v0.3.0 entry in
CHANGELOG.md.

See MIGRATING-0.3.md for full migration recipes covering every breaking
change in the bundle.

BREAKING CHANGE: v0.3.0 reshapes the public hyperdb_api::Error enum
into a flat canonical structure (no Box<dyn StdError> cause channel,
no kind() method, no Other catch-all variant), and its constructor
surface (Error::new and Error::with_cause are deleted in favor of
domain-specific snake_case constructors). It also changes the FromRow
trait signature from fn from_row(row: &Row) to fn from_row(row:
RowAccessor<'_>), deletes the blanket 1/2/3/4-tuple FromRow impls,
deprecates Connection::begin_transaction/commit/rollback (use the RAII
guard at Connection::transaction() instead), introduces a new
Error::InvalidOperation variant, and changes Error::Cancelled and
Error::Closed from tuple to struct variants carrying structured
sqlstate. Every variant has a snake_case constructor; the FromRow
derive lives in a re-exported hyperdb-api-derive crate. See
MIGRATING-0.3.md for migration recipes.

* chore(ci): publish hyperdb-api-derive in release.yml + dry-run in ci.yml

Pre-release adversarial review of the v0.3.0 rollup CI/CD config caught
that hyperdb-api-derive (added in PR #74) was missing from release.yml's
publish-in-dependency-order step. hyperdb-api/Cargo.toml strictly pins
hyperdb-api-derive = "=0.X.Y", so cargo publish -p hyperdb-api would
fail at release time when crates.io can't resolve the strict version
of derive (because release.yml never published it).

Verified topologically:
- hyperdb-api-derive has zero workspace deps (only syn/quote/proc-macro2
  from the registry), so it can publish before any workspace crate.
- It's a runtime dep of hyperdb-api only.
- Inserted right after hyperdb-api-salesforce; existing order otherwise
  unchanged. Added a dependency-order comment to the publish step
  explaining the topology so future contributors don't break it.

Also adds hyperdb-api-derive to ci.yml's publish dry-run job. The
dry-run job exists exactly to catch this class of bug before release
time. Without this addition, the same blocker could re-emerge after a
future major-version refactor of derive.

Updates the stale "7 workspace-member version rows" comment in
release-please.yml to reflect the current 8-member workspace
(hyperdb-api-derive added in #74). The lockfile-sync sentinel
enumerates members at runtime via cargo metadata, so the count is
informational; correctness is unchanged.

Verified locally:
- cargo publish -p hyperdb-api-derive --dry-run: succeeds
- cargo publish -p sea-query-hyperdb --dry-run: succeeds
- cargo publish -p hyperdb-bootstrap --dry-run: succeeds
- cargo metadata workspace topology check: order in release.yml is
  consistent with non-dev deps across all 7 publishable crates.
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