Skip to content

fix(sqlite): resolve TOML database paths instead of rewriting them - #412

Merged
tianzhou merged 1 commit into
bytebase:mainfrom
darylmcd:fix/sqlite-toml-database-path
Sep 1, 2026
Merged

fix(sqlite): resolve TOML database paths instead of rewriting them#412
tianzhou merged 1 commit into
bytebase:mainfrom
darylmcd:fix/sqlite-toml-database-path

Conversation

@darylmcd

Copy link
Copy Markdown
Contributor

Fixes #411.

Problem

buildDSNFromSource() encoded SQLite DSNs by prefixing three slashes unconditionally:

return `sqlite:///${source.database}`;

SQLiteDSNParser reads the path positionally, so this only round-trips for Windows drive letters and :memory:. Verified against 1.2.1 on Node v24.19.0:

TOML database DSN built Path opened
C:/x/y.db sqlite:///C:/x/y.db C:/x/y.db ok
/var/lib/y.db sqlite:////var/lib/y.db var/lib/y.db absolute → relative
data/y.db sqlite:///data/y.db /data/y.db relative → absolute
:memory: sqlite:///:memory: :memory: ok

The relative case fails loudly (ERR_SQLITE_ERROR 14). The absolute case fails silently — with a database at <cwd>/sub/a.db and database = "/sub/a.db", the server starts and answers queries against <cwd>/sub/a.db. A config naming an absolute path can therefore attach to a different file than it names.

Windows ~ expansion was broken the same way: expandHomeDir returns C:\Users\..., which yields sqlite:///C:\Users\...; the drive-letter branch requires C:/, so it fell through and decoded to /C:\Users\....

Change

  1. resolveSqliteDatabasePath() — resolves database at load time. Relative paths resolve against the config file's directory rather than process.cwd(), so a config selects the same database regardless of where the server was launched from. Separators are normalised to forward slashes. :memory: passes through.
  2. buildSqliteDSN() — encodes as sqlite:// + a leading-slash path, which the parser decodes back unchanged for POSIX paths and de-prefixes correctly for C:/.

I picked "resolve against the config file" over "reject relative paths with an error" — happy to switch if you'd rather not have implicit resolution.

Tests

  • Round-trip tests asserting the path SQLiteDSNParser actually returns (POSIX absolute, Windows drive letter, path with spaces, :memory:). This is the coverage that was missing — the previous test asserted the DSN string alone, so an encoding that rewrote absolute paths into relative ones passed review.
  • loadTomlConfig tests: relative resolves against the config directory (config placed in a subdirectory so cwd and config dir differ), absolute is left alone, :memory: untouched.
  • The existing should build SQLite DSN from database path assertion encoded the old four-slash output and is updated.
  • Two ~-expansion assertions now compare against forward slashes; they were platform-dependent and, on Windows, asserted a value that could not be opened.

Worth noting: every SQLite source in src/__fixtures__/toml/*.toml uses database = ":memory:", one of the two forms that round-tripped correctly — which is why CI stayed green.

Verification

  • src/config/__tests__/toml-loader.test.ts: 156/156 pass (154 before, plus new coverage).
  • Full suite: 1317 pass, 2 fail. Both failures reproduce unmodified on main and are environment-dependent — manager.test.ts > should resolve SSH config from ~/.ssh/config and sqlite.integration.test.ts > should open file-based database in readonly mode. Confirmed by stashing the change and re-running.
  • tsc --noEmit reports 19 errors in the two touched files both before and after — delta zero. (tsc --noEmit is not clean on main; the build runs through tsup.)

buildDSNFromSource encoded SQLite DSNs as `sqlite:///${database}`
unconditionally. SQLiteDSNParser reads the path positionally, so that
encoding only round-trips for Windows drive letters and `:memory:`:

  /var/lib/x.db -> sqlite:////var/lib/x.db -> var/lib/x.db  (absolute -> relative)
  data/x.db     -> sqlite:///data/x.db     -> /data/x.db    (relative -> absolute)

The relative case fails loudly with ERR_SQLITE_ERROR 14. The absolute case
fails silently: the server starts and serves queries against a different
file than the config names.

Resolve `database` at load time against the directory holding the config
file rather than process.cwd(), so a config selects the same database
regardless of where the server was launched from. Normalise separators to
forward slashes so the parser's `C:/` drive-letter branch matches, and
encode the DSN as `sqlite://` plus a leading-slash path so the value
survives the round-trip on both platforms.

This also repairs `~` expansion on Windows, which produced
`sqlite:///C:\Users\...` and decoded back to `/C:\Users\...`.

The existing DSN assertion encoded the old behaviour, so it is updated;
the new round-trip tests assert the path the connector actually opens
rather than the intermediate DSN string, which is what let this through.

Fixes bytebase#411

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 29, 2026 16:58
@darylmcd
darylmcd requested a review from tianzhou as a code owner August 29, 2026 16:58

Copilot AI 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.

Pull request overview

This PR fixes SQLite TOML database path handling by resolving paths at config-load time and encoding SQLite DSNs so SQLiteDSNParser round-trips the intended filesystem path (preventing silent absolute→relative rewrites and relative→absolute rewrites).

Changes:

  • Added resolveSqliteDatabasePath() to expand ~, normalize separators, and resolve relative SQLite database paths against the TOML file’s directory.
  • Added buildSqliteDSN() and updated buildDSNFromSource() to emit SQLite DSNs that preserve POSIX absolute paths and Windows drive-letter paths through SQLiteDSNParser.
  • Expanded test coverage to assert actual parser round-trips (and updated existing expectations accordingly).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/config/toml-loader.ts Adds SQLite path resolution + DSN encoding changes and wires them into TOML source processing / DSN building.
src/config/tests/toml-loader.test.ts Adds/updates tests for config-dir-relative resolution, :memory:, ~ normalization expectations, and parser round-trip coverage.
Suppressed comments (1)

src/config/toml-loader.ts:783

  • The SQLite DSN ~ expansion in processSourceConfigs is still incorrect: substring(11) strips the ~ so expandHomeDir() won’t expand, and sqlite:///${...} can also reintroduce the 4-slash form that SQLiteDSNParser decodes as a relative path. Rebuild the DSN using the expanded path and buildSqliteDSN() to preserve POSIX/Windows absolute paths.
      processed.database = resolveSqliteDatabasePath(processed.database, configPath);
    }

    // Expand ~ in DSN for SQLite
    if (processed.dsn && processed.dsn.startsWith("sqlite:///~")) {

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@tianzhou
tianzhou merged commit 900f10c into bytebase:main Sep 1, 2026
2 checks passed
@tianzhou

tianzhou commented Sep 1, 2026

Copy link
Copy Markdown
Member

Thanks for the fix.

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.

SQLite TOML database path is rewritten: POSIX absolute becomes relative, relative becomes absolute

3 participants