Skip to content

feat: allow passing a startup script per connection#352

Open
boredland wants to merge 4 commits into
TabularisDB:mainfrom
boredland:feat/connection-startup-script
Open

feat: allow passing a startup script per connection#352
boredland wants to merge 4 commits into
TabularisDB:mainfrom
boredland:feat/connection-startup-script

Conversation

@boredland

Copy link
Copy Markdown

Closes #350.

Adds an optional startup script to a connection — SQL run on every new pooled connection, so session settings like SELECT set_config('app.bypass_rls', 'on', false); (or any SET) apply to every query regardless of which pooled connection serves it. This is the DataGrip-style "startup script" requested in the issue, and makes RLS-bypass-in-dev practical.

Changes

  • ConnectionParams.startup_script, persisted with the connection (non-secret).
  • Executed per physical connection so the setting sticks across the whole pool: MySQL/SQLite via sqlx after_connect, Postgres via deadpool post_create. Blank/whitespace scripts are skipped; multiple statements are separated by ;.
  • New Advanced tab in the connection editor with a startup-script field; i18n added for all 8 locales.

Tests

SQLite pool tests (no DB server needed) cover: script runs on connect, blank script is skipped, invalid script surfaces an error.

Run optional SQL on every new pooled connection (MySQL/SQLite
after_connect, Postgres deadpool post_create) so session settings like
set_config apply to every query. Editable via a new "Advanced" tab in
the connection editor.

Closes TabularisDB#350
@kilo-code-bot

kilo-code-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Files Reviewed (3 files)
  • src-tauri/src/pool_manager.rs — previous SQLite timeout issue resolved; no new issues
  • src-tauri/src/pool_manager_tests.rs — unchanged since previous review
  • src/components/modals/NewConnectionModal.tsx — unchanged since previous review

Previous Issue Resolution

File Previous Issue Status
src-tauri/src/pool_manager.rs SQLite startup script preflight lacked timeout Fixed — now wrapped in tokio::time::timeout(30s)
Previous Review Summaries (3 snapshots, latest commit 2186106)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 2186106)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
src-tauri/src/pool_manager.rs 742 SQLite startup script preflight lacks timeout
Files Reviewed (3 files)
  • src-tauri/src/pool_manager.rs — 1 issue (SQLite preflight lacks timeout guard)
  • src-tauri/src/pool_manager_tests.rs — updated test for attributed errors
  • src/components/modals/NewConnectionModal.tsx — replaced textarea with SqlEditorWrapper

Fix these issues in Kilo Cloud

Previous review (commit 30b5548)

Status: No Issues Found | Recommendation: Merge

The previous CRITICAL issue has been resolved. build_connection_key now includes a SHA256 hash of the startup_script in the pool cache key, so editing a connection's startup script forces creation of a fresh pool whose new connections run the updated script.

Changes verified

  • build_connection_key folds startup_script into the cache key ({key}:startup:{digest:x})
  • startup_script() helper trims whitespace and treats blank scripts as absent
  • Pool after_connect/post_create hooks added for MySQL, PostgreSQL, and SQLite
  • Unit tests verify key changes on script change and blank-script equivalence
  • Integration tests verify script execution, blank-script skipping, and error surfacing
Files Reviewed (2 incremental files)
  • src-tauri/src/pool_manager.rs — startup script hashing in pool key, per-driver connection hooks
  • src-tauri/src/pool_manager_tests.rs — key change tests, integration tests for hook behavior

Previous review (commit 14eeac9)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
src-tauri/src/pool_manager.rs N/A (outside diff) startup_script not included in build_connection_key pool cache key

startup_script must be part of the pool cache keybuild_connection_key does not include startup_script, so editing a connection's startup script reuses the existing cached pool and the new script never runs. For saved connections (keyed by connection_id), the pool persists indefinitely until app restart. The fix is to include a hash (or trimmed string) of startup_script in build_connection_key so that script changes create a new pool.

Files Reviewed (15 files)
  • src-tauri/src/models.rs
  • src-tauri/src/plugins/driver.rs
  • src-tauri/src/pool_manager.rs
  • src-tauri/src/pool_manager_tests.rs
  • src/components/modals/NewConnectionModal.tsx
  • src/contexts/DatabaseContext.ts
  • src/utils/connections.ts
  • src/i18n/locales/de.json
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/it.json
  • src/i18n/locales/ja.json
  • src/i18n/locales/ru.json
  • src/i18n/locales/zh.json

Fix these issues in Kilo Cloud


Reviewed by kimi-k2.6-20260420 · Input: 50.3K · Output: 5.5K · Cached: 157.6K

build_connection_key hashed only connection_id/host/tls, so editing a
saved connection's startup script reused the cached pool and the new
script never ran until app restart. Fold a SHA-256 of the trimmed
script into the key (only when set, so script-free pools keep their
keys). Addresses the critical review finding on TabularisDB#352.
@boredland

Copy link
Copy Markdown
Author

Good catch — fixed in 30b5548.

build_connection_key now folds a SHA-256 of the trimmed startup_script into the pool cache key (only when a script is set, so script-free connections keep their existing keys). Editing the script therefore yields a new key → a fresh pool whose connections run the updated script, instead of reusing the stale cached pool.

Added two unit tests: the key changes when the script changes, and a blank/whitespace script is treated as absent (no key fragmentation).

@debba

debba commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this PR.

Couple of things before this goes in:

  1. The field takes arbitrary SQL, so what's the story for scripts that error out? From the hooks it looks like a failed statement bubbles straight out of after_connect/post_create and the connection never opens, so a single typo in the script locks you out of the data source entirely. Is blocking the behaviour we actually want here? I'd lean towards letting the connection through and surfacing the script failure as a warning instead. If we do want to keep it hard-blocking (which is defensible for stuff like RLS bypass, where you don't want a half-configured session), then the error needs to clearly say it came from the startup script, otherwise it just reads like a bad host or wrong credentials.

  2. Can we swap the plain textarea for SqlEditorWrapper? That's what the editor and the other SQL fields already use, so we'd get syntax highlighting, theming and the font settings for free and it stays consistent with the rest of the app.

A broken startup script now fails fast with a clearly attributed error
instead of a misleading pool timeout or generic connection error:

- MySQL/SQLite preflight the script on a throwaway connection. sqlx
  swallows after_connect errors and retries until the acquire timeout,
  reporting "pool timed out"; the preflight surfaces "Startup script
  failed: ..." up front. Connection failures are returned verbatim so a
  bad host/credentials is not mislabelled as a script error.
- Postgres post_create reports the startup script as the cause.

Also swap the Advanced-tab startup-script textarea for the shared
SqlEditorWrapper (Monaco) for syntax highlighting, theming and font
settings, matching the editor and other SQL fields.
@boredland

Copy link
Copy Markdown
Author

Both addressed in 2186106.

1. Error story. Kept it hard-blocking — for the RLS-bypass case a half-configured session silently serving queries is worse than a clear failure — but made the failure fast and clearly attributed, and distinct from a connectivity error.

Worth flagging what I found while doing this: for MySQL/SQLite the old behaviour wasn't actually "bubbles straight out". sqlx swallows after_connect errors (logs, hard-closes the connection, retries until acquire_timeout) and then returns PoolTimedOut — so a script typo read as "pool timed out", exactly the misleading case you described. Fixes:

  • MySQL/SQLite now preflight the script on a throwaway connection at pool-creation time. A failed executeStartup script failed: …; a failed connect is returned verbatim, so a bad host/credentials still reads as such rather than as a script error. The per-connection after_connect hook is unchanged — the script still runs on every pooled connection.
  • Postgres (deadpool doesn't swallow/retry) just gets the attribution: post_create now reports `post_create` hook failed: Startup script failed: <cause chain>, surfaced on ping/first query.

One residual edge: a script that passes preflight but fails non-deterministically on a later pooled connection still hits sqlx's retry → PoolTimedOut. The realistic deterministic failures (typos, bad permissions, unknown settings) are caught and attributed.

2. Editor. Swapped the textarea for SqlEditorWrapper, same as the editor and the other SQL fields — syntax highlighting, theming and font settings for free. Kept the RLS example as the Monaco placeholder, so no i18n changes.

The SQLite integration test now asserts the error is attributed (Startup script failed) and returns immediately via the preflight instead of timing out.

Comment thread src-tauri/src/pool_manager.rs Outdated
})?;
let mut pool_options = sqlx::sqlite::SqlitePoolOptions::new().max_connections(5); // SQLite has lower concurrency needs
if let Some(script) = startup_script(params) {
run_sqlite_startup_script(&options, &script).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: SQLite startup script preflight lacks timeout

MySQL preflight is wrapped in tokio::time::timeout(connect_timeout, ...) so a hanging script surfaces as a clear timeout error rather than blocking indefinitely. The SQLite preflight has no such guard — a startup script that encounters a database lock or runs an unbounded query would block pool creation forever.

Consider adding a timeout around run_sqlite_startup_script so SQLite behaves consistently with MySQL and users aren't left waiting indefinitely.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 947325b. Wrapped the SQLite preflight in a 30s timeout to match MySQL, so a broken (or stalled-mount/custom-VFS) script can never wedge pool creation. cargo test --lib startup_script still passes and returns immediately.

…meout

The MySQL preflight was wrapped in the connect timeout but SQLite's was
not. SQLite is file-based so a hang is unlikely, but a custom VFS or a
path on a stalled network mount could wedge pool creation indefinitely.
Wrap it in a 30s timeout to match MySQL.
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.

[Feat]: Allow passing a startup script

2 participants