0.9.65
Explain no longer runs a statement it cannot explain
The Explain button deliberately skips the dangerous-query confirmation dialog - prompting "are you sure?" before a read-only EXPLAIN would be pure noise. That shortcut was only sound while an EXPLAIN was actually built, and the direct-explain path fell back to the original statement whenever it was not:
let queryToRun = queryToExecute;
if (isExplain && explainStrategy) {
const explainSql = explainStrategy.buildSql(queryToExecute, "analyze");
if (explainSql) {
queryToRun = explainSql; // else: keeps the original statement
}
}Every dialect strategy returns nothing for a non-SELECT, and the Explain affordance is offered for whatever sits in the editor. So clicking Explain with an UPDATE employee SET ... executed that UPDATE for real - no confirmation dialog, no undo - and then rendered an empty plan.
An explain run now only ever sends SQL a dialect strategy built for it. When there is none, nothing is executed and the reason is reported:
- The statement is not a SELECT: "Only SELECT statements can be explained."
- The provider declares no EXPLAIN support, or ships no plan format: "EXPLAIN is not available for this database type."
- Connection metadata has not resolved yet: "Connection metadata is still loading. Try again in a moment."
The pattern predates the plan-rendering work - PostgreSQL and MySQL carried it for as long as the Explain button has existed. SQLite EXPLAIN QUERY PLAN, added in 0.9.56, is what widened the exposure, because the zero-config sample database puts the Explain button in front of every fresh install.
Fixing it also surfaced a gap in our own tests: the supportsExplain: false denial had a test that could not fail. It mocked no fetch route, so the request failed and raised an error toast either way, and expect(mockToastError).toHaveBeenCalled() passed whether the capability check ran or not - at 100% line coverage. Coverage proves a line ran, never that it mattered. All three refusal paths now assert both that no request is sent and which reason the user is given.
Distribution: three channels stop reporting SKIP
The drift table could measure any channel whose served version is one document a regex can read. Three tier-0 channels did not fit that shape and printed a permanent SKIP, which meant their publish-time green checkmark was the only evidence that publication had worked - what a user actually pulls today was never re-verified.
bun run distribution:check gains a probe pin strategy for exactly those channels:
| Probe | Channel | Measures |
|---|---|---|
ghcr-tag-digest |
docker-ghcr |
digest(:latest) == digest(:<version>) |
dockerhub-tag-digest |
docker-hub-mirror |
the same equality on the mirror |
snap-store-channel |
snap |
the stable version of each architecture |
winget-max-version |
winget |
the highest version the catalog enumerates |
Probe URLs live in distribution/channels.yaml rather than in the script, so the unit tests point them at a local Bun.serve and the suite never touches the real network. A probe reports its sources through the same agree/compare path as a multi-file pin, so a lagging Snap architecture reads as drift with both architectures visible.
Two blockers the issue had assumed turned out not to exist: GHCR needs no read:packages secret (its anonymous pull token is enough), and no manifest-list unwrapping is required to compare tag digests.
winget is live
The first LibreDB.Studio manifests merged into microsoft/winget-pkgs, so the channel moved from pending to live in distribution/channels.yaml and in the docs. winget install LibreDB.Studio now resolves against the community catalog, and the new winget-max-version probe above is what keeps that claim honest release to release.
Helm chart: 0.1.27
Chart 0.1.27 tracks this app release. There are no chart template changes - the rendered manifests are identical to 0.1.26.
Full changelog: 0.9.64...0.9.65