Skip to content

ladder: rung 1 -- pastebin - #89

Merged
Yaraslaut merged 2 commits into
masterfrom
ladder-pastebin
Aug 15, 2026
Merged

ladder: rung 1 -- pastebin#89
Yaraslaut merged 2 commits into
masterfrom
ladder-pastebin

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

What

Rung 1 of the application ladder: pastebin, anchored on MicroBin. Split out of #41 into its own PR, targeting #88's ladder-foundation branch since it depends on the shared testkit/CMake infra there.

Draft until #88 merges — retarget to master and un-draft once it does.

Contents

examples/pastebin/ in full: model, DTOs, entity, schema migration, server, Qt/QML GUI (forms controller, presenter, bridges), and tests — 29 files.

Includes two fixes folded in during review of the combined branch:

  • PasteModel acquires connections from Lightweight::GlobalDataMapperPool() per execute() call rather than holding one for its own lifetime (WithMapper removed) — see the framework-wide rationale in Application ladder: rungs 0-3 (pastebin, bookmarks, polls) + shared infrastructure #41's discussion.
  • content is Light::SqlMaxDynamicWideString, not std::string/SqlText — both of those are char-based and render as VARCHAR(MAX) (a single-byte-collation column) on the SQL Server backend this same test suite can target via ODBC_CONNECTION_STRING. SqlMaxDynamicWideString's wchar_t-based storage self-declares NVarchar, giving real Unicode columns on every backend.

Verification

Configured and built standalone against the ladder-foundation base with -DMORPH_LADDER_RUNGS=pastebin (no other rung present). Full suite passes: 834 assertions in 51 test cases (SQLite default). Spec-citation and test-type-name lints clean.

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Split out of application-ladder (originally bundled with bookmarks/polls
and the shared foundation in #41) into its own PR against the rung-0
foundation (#88). Includes the pool-migration and Unicode-content fixes
folded in during review of the combined branch:

- PasteModel acquires connections from Lightweight::GlobalDataMapperPool()
  per execute() call rather than holding one for its own lifetime
  (WithMapper removed).
- content is Light::SqlMaxDynamicWideString, not std::string/SqlText --
  both of those are char-based and would render as VARCHAR(MAX) (a
  single-byte-collation column) on the SQL Server backend this same test
  suite can target via ODBC_CONNECTION_STRING; SqlMaxDynamicWideString's
  wchar_t-based storage self-declares NVarchar, giving real Unicode
  columns on every backend.

Verified standalone against the ladder-foundation base: configures and
builds with -DMORPH_LADDER_RUNGS=pastebin and no other rung present.
Full suite passes: 834 assertions in 51 test cases (SQLite default).
Spec-citation and test-type-name lints clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Yaraslaut
Yaraslaut changed the base branch from ladder-foundation to master August 15, 2026 11:25
@Yaraslaut
Yaraslaut marked this pull request as ready for review August 15, 2026 11:25
Investigating PR #89's codecov/patch gap (85.04% vs 97.24% target)
found two genuinely reachable, previously-uncovered branches in
EditPaste's post-CAS-miss classification (paste_model.cpp's "Zero rows
matched: classify why" block) -- distinct from the pre-CAS checks the
existing "refuses an immutable paste, an unknown id" test already
covers, since those never reach the guarded UPDATE at all:

- A concurrent delete between EditPaste's first read and its guarded
  write throws NotFound, not Conflict -- the row genuinely vanished
  underneath the pending edit.
- A concurrent is_editable flip (simulated directly through the locking
  connection, since no ordinary action un-edits a paste) throws
  ValidationError instead.

Both forced deterministically via the same WaitForGuardedUpdate
SqlLogger hook idiom the existing Conflict test already established --
no sleep_for, no guessing at scheduling.

Also investigated (but left undone, with a comment explaining why) a
third gap: app.cpp's sweep .onError() path, which decrements
sweepInFlight when a swept ExpirePaste fails. Forcing a real SQLITE_BUSY
inside sweepExpiredOnce()'s worker-thread-dispatched execute() needs
drainPoolIdleMappers()'s "next Acquire() is fresh" guarantee to hold
across that async dispatch; confirmed by direct instrumentation that it
currently does not (some other Acquire()/Return() pair repopulates the
idle pool first), and there is no way to observe which path a given
Acquire() took to root-cause that further. Filed
LASTRADA-Software/Lightweight#548 requesting SqlLogger::OnConnectionIdle/
OnConnectionReuse (already declared, never called anywhere in the
library) actually get wired up, which would answer this directly.

Two other apparent gaps (paste_qml_bridges.hpp, app.hpp -- Q_OBJECT/
constructor/destructor declaration lines) are the same llvm-cov
per-declaration-line reporting artifact confirmed twice already this
session: both classes are thoroughly exercised via their .cpp
definitions (0 uncovered lines each), and their constructors are
directly instantiated in this file's own App-level tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Yaraslaut
Yaraslaut merged commit ebaa9d6 into master Aug 15, 2026
23 of 24 checks passed
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