Skip to content

feat: Db settings and connections editing - #492

Merged
ilyabo merged 13 commits into
mainfrom
db-settings
Mar 23, 2026
Merged

ilyabo merged 13 commits into
mainfrom
db-settings

Conversation

@ilyabo

@ilyabo ilyabo commented Mar 23, 2026 •

Copy link
Copy Markdown
Collaborator
image

Summary by CodeRabbit

  • New Features

    • New DB settings package and UI: manage connections, per-engine config fields, save/test actions, and driver diagnostics.
    • Schema Explorer component: improved schema navigation with refresh and tree view.
    • Server API: endpoints to get/update DB settings and test connections; CLI accepts discrete DB fields.
  • Deprecated

    • TableStructurePanel replaced by Schema Explorer; old connector dialog replaced by new diagnostics UI.
  • Bug Fixes

    • Schema explorer handles missing schema data gracefully.

ilyabo added 10 commits March 22, 2026 13:15
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
@coderabbitai

coderabbitai Bot commented Mar 23, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

A new @sqlrooms/db-settings package implements DB connection state, UI, and persistence. Frontend replaces legacy driver dialog and TableStructurePanel with DbSettings + SchemaExplorer. Runtime config and Python DB bridge gain per-engine fields, ephemeral connector creation, and server endpoints for managing/testing connections.

Changes

Cohort / File(s) Summary
DB Settings package & infra
packages/db-settings/package.json, packages/db-settings/tsconfig.json, packages/db-settings/eslint.config.js, packages/db-settings/typedoc.js, packages/db-settings/README.md
New package scaffold, build/type tooling, README and publish metadata for @sqlrooms/db-settings.
DB Settings state & config schemas
packages/db-settings/src/DbSettingsSliceConfig.ts, packages/db-settings/src/DbSettingsSlice.ts, packages/db-settings/src/index.ts
Adds Zod schemas, Zustand slice, sync helper, save/test APIs, and public exports for DB settings state and types.
DB Settings UI components
packages/db-settings/src/components/DbSettings.tsx, .../DbConnectionForm.tsx, .../DbConnectionsList.tsx, .../ConnectorDriversDiagnostics.tsx
New React UI: trigger, connections list/form, diagnostics dialog, drivers tab label, and save/test/copy flows with per‑engine config fields.
Frontend integration & removal
apps/sqlrooms-cli-ui/src/store.ts, apps/sqlrooms-cli-ui/src/runtimeConfig.ts, apps/sqlrooms-cli-ui/package.json, apps/sqlrooms-cli-ui/src/components/DataSourcesPanel.tsx, apps/sqlrooms-cli-ui/src/components/ConnectorDriversDialog.tsx
Replaced legacy connector diagnostics slice/component with DbSettings slice and UI; runtimeConfig extended with supportedEngines, engineConfigFields, and per-connection config; removed ConnectorDriversDialog; added workspace dep @sqlrooms/db-settings.
Schema explorer & examples
packages/sql-editor/src/components/SchemaExplorer.tsx, packages/sql-editor/src/components/TableStructurePanel.tsx, packages/sql-editor/src/index.ts, examples/*/DataSourcesPanel.tsx, examples/*/DataPanel.tsx
Introduces SchemaExplorer (Header/Tree/RefreshButton), deprecates TableStructurePanel, and replaces TableStructurePanel with SchemaExplorer across examples.
Schema tree & UI tweaks
packages/schema-tree/src/TableSchemaTree.tsx, packages/schema-tree/src/nodes/RefreshButton.tsx
Make schemaTrees optional with memoized fallback; adjust refresh button sizing.
Core DB types
packages/db/src/types.ts, packages/db/src/index.ts
Adds optional config?: Record<string,string> to DbConnection and exports some runtime symbols as values.
Python DB bridge & factory
python/sqlrooms-cli/sqlrooms/web/db_bridge/factory.py, .../types.py, .../connectors/postgres.py, .../connectors/snowflake.py, .../registry.py, .../__init__.py
Add SUPPORTED_ENGINES/ENGINE_CONFIG_FIELDS, build_ephemeral_connector(engine,config), config_dict() on connectors, protocol method, and registry support to include filtered config in runtime output.
Server, CLI, docs & persistence
python/sqlrooms-cli/sqlrooms/web/launcher.py, python/sqlrooms-cli/sqlrooms/cli.py, python/sqlrooms-cli/README.md, python/sqlrooms-cli/pyproject.toml
Server exposes GET/PUT /api/db/settings, extends /api/config with engine metadata, writes TOML connectors, supports ephemeral test by engine+config; CLI/docs split Postgres dsn into discrete fields and add tomlkit.
Tests
python/sqlrooms-cli/tests/test_cli.py, .../test_db_bridge.py, .../test_launcher.py
Updated tests to use discrete Postgres fields, add tests for build_ephemeral_connector, config serialization, secret redaction, and new launcher endpoints.

Sequence Diagram(s)

sequenceDiagram
    participant User as Frontend User
    participant UI as DbSettings UI
    participant Store as Zustand Store
    participant Server as Sqlrooms HTTP Server
    participant Bridge as Python DB Bridge

    User->>UI: Open Settings Modal
    UI->>Store: read connections, diagnostics, engineConfigFields
    Store-->>UI: state snapshot

    User->>UI: Add/Edit connection
    UI->>Store: upsertConnection(...)
    Store->>Store: mark unsaved

    User->>UI: Test connection
    UI->>Store: testConnection(engine, config)
    Store->>Server: POST /api/db/test-connection {engine, config}
    Server->>Bridge: build_ephemeral_connector(engine, config)
    Bridge->>Bridge: connector.test_connection()
    Bridge-->>Server: result
    Server-->>Store: {ok, error}
    Store-->>UI: display result

    User->>UI: Save
    UI->>Store: saveToServer()
    Store->>Server: PUT /api/db/settings {connections}
    Server->>Server: _write_db_connectors_to_toml(config_path,...)
    Server-->>Store: success/failure
    Store-->>UI: toast
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • bdjulbic

Poem

🐇 In burrows of code I nudge and tweak,
Connections lined up, secrets we keep,
Old dialogs hop away, new trees unfurl,
Drivers copied, tests give a twirl,
Saved settings snug — a rabbit's happy leap!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Db settings and connections editing' clearly and directly summarizes the main changes, which focus on adding database settings management and connection configuration UI components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch db-settings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 9

🧹 Nitpick comments (2)
python/sqlrooms-cli/tests/test_cli.py (1)

54-57: Add an assertion for port in the parsed Postgres config.

The fixture now provides port, but the test does not validate it. This leaves a gap in the new field-based parsing contract.

✅ Suggested test assertion
     assert data[0].host == "localhost"
+    assert data[0].port == "5432"
     assert data[0].database == "db"
     assert data[0].user == "u"
     assert data[0].password == "p"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/tests/test_cli.py` around lines 54 - 57, Add an assertion
that the parsed Postgres config includes the port value by checking data[0].port
equals the expected port from the test fixture (e.g., assert data[0].port ==
<expected_port>); update the assertion alongside the existing checks for
data[0].host, data[0].database, data[0].user, and data[0].password so the test
validates the new field-based parsing contract for the Postgres config.
packages/sql-editor/src/components/SchemaExplorer.tsx (1)

49-54: Potential re-render issue with inline fallback array.

The ?? [] fallback on Line 52 creates a new array reference each render when schemaTrees is undefined, which may cause unnecessary re-renders of TableSchemaTree.

♻️ Option: Use a stable empty array constant
+const EMPTY_SCHEMA_TREES: never[] = [];
+
 function SchemaExplorerTree({className}: SchemaExplorerTreeProps) {
   const schemaTrees = useStoreWithSqlEditor((s) => s.db.schemaTrees);
   return (
-    <TableSchemaTree schemaTrees={schemaTrees ?? []} className={className} />
+    <TableSchemaTree schemaTrees={schemaTrees ?? EMPTY_SCHEMA_TREES} className={className} />
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sql-editor/src/components/SchemaExplorer.tsx` around lines 49 - 54,
The fallback "?? []" in SchemaExplorerTree creates a new array each render
causing TableSchemaTree to rerender; fix by returning a stable empty array
instead of a new literal when schemaTrees is undefined—e.g., define a shared
constant (EMPTY_SCHEMA_TREES) or memoize the fallback and pass schemaTrees ??
EMPTY_SCHEMA_TREES to TableSchemaTree; adjust SchemaExplorerTree (and any
related props) so TableSchemaTree receives a stable reference to avoid
unnecessary re-renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/db-settings/src/components/DbConnectionForm.tsx`:
- Around line 70-85: The effect that captures originalRef only on the first
truthy editing causes stale originals when the parent reuses the form for a
different connection; update the useEffect watching editing so that when editing
becomes truthy you set originalRef.current = editing if originalRef.current is
unset OR originalRef.current.id !== editing.id (i.e., capture the new record),
and when editing is falsy clear originalRef.current and reset
hasInitializedRef.current to false; apply the same guard/clear logic to the
other initialization block referenced (the code around lines 128-133) so
handleCancel() will restore the correct connection.

In `@packages/db-settings/src/components/DbSettings.tsx`:
- Around line 30-38: The settings trigger Button in DbSettings (the Button
element that forwards ref and renders <SettingsIcon />) is missing an accessible
name; add an aria-label (or aria-labelledby referencing a nearby visually-hidden
label) to the Button props so screen readers announce its purpose (e.g.,
aria-label="Database settings" or similar), and ensure the label text is
localized/unique if needed; keep the visual icon-only appearance unchanged and
apply the attribute on the Button element that renders <SettingsIcon />.

In `@packages/db-settings/src/DbSettingsSlice.ts`:
- Around line 212-230: The loop that evicts stale connections only checks
whether an existing connection is in desiredIds, but does not remove entries
whose diagnostics flipped to unavailable; update the first loop over
existingConnections (where existing.isCore is checked) to also look up
diagnosticsByKey using diagnosticsKey(existing.id, existing.engineId) and call
state.db.connectors.removeConnection(existing.id) when the diagnostic exists and
diag.available === false, so unavailable connections are removed even if their
id is still in desiredIds.
- Around line 117-148: The code captures the pre-request connections in
saveToServer but later rebuilds savedSnapshot from the live store, which can
fold in in-flight edits; change the success branch to compute savedSnapshot from
the captured connections variable (use savedSnapshot =
connectionsSnapshot(connections)) and then set
draft.dbSettings.hasUnsavedChanges based on comparing the current
draft.dbSettings.config.connections to that captured snapshot (or set to false
only when they match) so in-flight edits are not incorrectly marked saved;
update references in the success block where savedSnapshot and
draft.dbSettings.hasUnsavedChanges are set.

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/postgres.py`:
- Around line 20-22: The resolve_dsn method currently interpolates self.password
directly into the DSN which breaks when the password contains characters like @,
:, /, or %; update resolve_dsn to URL-encode the password (use
urllib.parse.quote) before inserting it into the DSN string, preserving the
existing conditional (pw = f":{encoded_pw}" if self.password else "") and
keeping the rest of the returned
f"postgresql://{self.user}{pw}@{self.host}:{self.port}/{self.database}" intact.

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge/registry.py`:
- Around line 42-44: The code copies connector runtime configs directly via
conn.config_dict() into entry["config"], exposing secrets like password; update
the assignment to sanitize or filter out sensitive keys (e.g., "password",
"secret", "api_key", "token") before storing in entry["config"]—either call a
redact helper (e.g., redact_config(config)) or create a sanitized dict by
removing or masking those keys from the dict returned by conn.config_dict() so
no credentials are sent to the frontend.

In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 359-388: After successfully writing TOML in put_db_settings (after
_write_db_connectors_to_toml(self.config_path, connections)), reload the runtime
registry so get_db_settings and the connectionId endpoints see the new settings:
either call a reload method on self.db_bridge_registry (e.g.
self.db_bridge_registry.reload_from_config(self.config_path)) or reinitialize it
from the config file (e.g. self.db_bridge_registry =
DbBridgeRegistry(self.config_path) or equivalent), ensuring the refreshed
registry replaces the old one before returning the response; alternatively,
modify get_db_settings to read directly from self.config_path when present
instead of relying on the one-time-built self.db_bridge_registry.
- Around line 77-99: The code currently overlays only non-empty engine_config
fields onto base, so blanking a field in the form leaves the old value in base;
update the loop that processes engine_config (the variable engine_config from
conn.get("config")) to treat explicit empty/blank values as removals: for each
ck, cv in engine_config.items(), if cv is None or cv == "" then remove ck from
base if present (instead of skipping), otherwise set base[ck] = cv; this ensures
connectors_aot built from base no longer retains stale values after a user
clears a field.

In `@python/sqlrooms-cli/tests/test_launcher.py`:
- Around line 105-110: The runtime_connections() response currently includes raw
connector config_dict() values (from PostgresConnector.config_dict and
SnowflakeConnector.to_connect_kwargs), which can leak secrets like "password";
modify registry.py::runtime_connections to apply an explicit allowlist of
non-sensitive keys (e.g., host, port, database, user, maybe account) and filter
each connector.config_dict() through that allowlist before adding it to the API
payload, ensuring any "password" or other secret keys are omitted; then update
the tests in tests/test_launcher.py to assert that sensitive fields (e.g.,
"password") are not present in the returned config objects for connectors.

---

Nitpick comments:
In `@packages/sql-editor/src/components/SchemaExplorer.tsx`:
- Around line 49-54: The fallback "?? []" in SchemaExplorerTree creates a new
array each render causing TableSchemaTree to rerender; fix by returning a stable
empty array instead of a new literal when schemaTrees is undefined—e.g., define
a shared constant (EMPTY_SCHEMA_TREES) or memoize the fallback and pass
schemaTrees ?? EMPTY_SCHEMA_TREES to TableSchemaTree; adjust SchemaExplorerTree
(and any related props) so TableSchemaTree receives a stable reference to avoid
unnecessary re-renders.

In `@python/sqlrooms-cli/tests/test_cli.py`:
- Around line 54-57: Add an assertion that the parsed Postgres config includes
the port value by checking data[0].port equals the expected port from the test
fixture (e.g., assert data[0].port == <expected_port>); update the assertion
alongside the existing checks for data[0].host, data[0].database, data[0].user,
and data[0].password so the test validates the new field-based parsing contract
for the Postgres config.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bbc216bd-e2ad-44ad-bc19-5100c3592e9d

📥 Commits

Reviewing files that changed from the base of the PR and between 751ec8e and 37e7552.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (48)
  • apps/sqlrooms-cli-ui/package.json
  • apps/sqlrooms-cli-ui/src/components/ConnectorDriversDialog.tsx
  • apps/sqlrooms-cli-ui/src/components/DataSourcesPanel.tsx
  • apps/sqlrooms-cli-ui/src/runtimeConfig.ts
  • apps/sqlrooms-cli-ui/src/store.ts
  • examples/ai-rag/src/components/DataSourcesPanel.tsx
  • examples/ai/src/components/DataSourcesPanel.tsx
  • examples/canvas-sync/src/DataSourcesPanel.tsx
  • examples/canvas/src/DataSourcesPanel.tsx
  • examples/deckgl/src/components/DataPanel.tsx
  • examples/kepler/src/components/DataPanel.tsx
  • examples/notebook/src/DataSourcesPanel.tsx
  • examples/query-motherduck/src/DataPanel.tsx
  • examples/query-pwa/src/DataPanel.tsx
  • examples/query-websocket/src/DataPanel.tsx
  • examples/query/src/DataPanel.tsx
  • packages/db-settings/README.md
  • packages/db-settings/eslint.config.js
  • packages/db-settings/package.json
  • packages/db-settings/src/DbSettingsSlice.ts
  • packages/db-settings/src/DbSettingsSliceConfig.ts
  • packages/db-settings/src/components/ConnectorDriversDiagnostics.tsx
  • packages/db-settings/src/components/DbConnectionForm.tsx
  • packages/db-settings/src/components/DbConnectionsList.tsx
  • packages/db-settings/src/components/DbSettings.tsx
  • packages/db-settings/src/index.ts
  • packages/db-settings/tsconfig.json
  • packages/db-settings/typedoc.js
  • packages/db/src/index.ts
  • packages/db/src/types.ts
  • packages/schema-tree/src/TableSchemaTree.tsx
  • packages/schema-tree/src/nodes/RefreshButton.tsx
  • packages/sql-editor/src/components/SchemaExplorer.tsx
  • packages/sql-editor/src/components/TableStructurePanel.tsx
  • packages/sql-editor/src/index.ts
  • python/sqlrooms-cli/README.md
  • python/sqlrooms-cli/pyproject.toml
  • python/sqlrooms-cli/sqlrooms/cli.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/__init__.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/postgres.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/snowflake.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/factory.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/registry.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/types.py
  • python/sqlrooms-cli/sqlrooms/web/launcher.py
  • python/sqlrooms-cli/tests/test_cli.py
  • python/sqlrooms-cli/tests/test_db_bridge.py
  • python/sqlrooms-cli/tests/test_launcher.py
💤 Files with no reviewable changes (1)
  • apps/sqlrooms-cli-ui/src/components/ConnectorDriversDialog.tsx

Comment on lines +70 to +85
React.useEffect(() => {
if (editing) {
suppressSyncRef.current = true;
setEngineId(editing.engineId);
setConnectionId(editing.id);
setTitle(editing.title);
setBridgeId(editing.bridgeId ?? '');
setConfig(editing.config ?? {});
if (!hasInitializedRef.current) {
originalRef.current = editing;
hasInitializedRef.current = true;
}
} else {
hasInitializedRef.current = false;
}
}, [editing]);

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.

⚠️ Potential issue | 🟠 Major

Reset originalRef when editing switches to a different connection.

originalRef is only captured for the first truthy editing value. If the parent reuses this form for connection B after connection A, handleCancel() restores A and overwrites the wrong record.

Suggested fix
-  const originalRef = React.useRef(editing);
-  const suppressSyncRef = React.useRef(false);
-  const hasInitializedRef = React.useRef(false);
+  const originalRef = React.useRef(editing);
+  const suppressSyncRef = React.useRef(false);
+  const lastEditingIdRef = React.useRef(editing?.id);

   React.useEffect(() => {
     if (editing) {
       suppressSyncRef.current = true;
+      if (lastEditingIdRef.current !== editing.id) {
+        originalRef.current = editing;
+        lastEditingIdRef.current = editing.id;
+      }
       setEngineId(editing.engineId);
       setConnectionId(editing.id);
       setTitle(editing.title);
       setBridgeId(editing.bridgeId ?? '');
       setConfig(editing.config ?? {});
-      if (!hasInitializedRef.current) {
-        originalRef.current = editing;
-        hasInitializedRef.current = true;
-      }
     } else {
-      hasInitializedRef.current = false;
+      originalRef.current = undefined;
+      lastEditingIdRef.current = undefined;
     }
   }, [editing]);

Also applies to: 128-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db-settings/src/components/DbConnectionForm.tsx` around lines 70 -
85, The effect that captures originalRef only on the first truthy editing causes
stale originals when the parent reuses the form for a different connection;
update the useEffect watching editing so that when editing becomes truthy you
set originalRef.current = editing if originalRef.current is unset OR
originalRef.current.id !== editing.id (i.e., capture the new record), and when
editing is falsy clear originalRef.current and reset hasInitializedRef.current
to false; apply the same guard/clear logic to the other initialization block
referenced (the code around lines 128-133) so handleCancel() will restore the
correct connection.

Comment thread packages/db-settings/src/components/DbSettings.tsx
Comment thread packages/db-settings/src/DbSettingsSlice.ts
Comment thread packages/db-settings/src/DbSettingsSlice.ts
Comment thread python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/postgres.py Outdated
Comment thread python/sqlrooms-cli/sqlrooms/web/db_bridge/registry.py Outdated
Comment thread python/sqlrooms-cli/sqlrooms/web/launcher.py
Comment on lines +359 to +388
@app.get("/api/db/settings")
async def get_db_settings():
connections = self.db_bridge_registry.runtime_connections()
diagnostics = self.db_bridge_registry.runtime_diagnostics()
return {
"connections": connections,
"diagnostics": diagnostics,
"supportedEngines": SUPPORTED_ENGINES,
"engineConfigFields": ENGINE_CONFIG_FIELDS,
}

@app.put("/api/db/settings")
async def put_db_settings(payload: Dict[str, Any]):
if self.config_path is None:
return JSONResponse(
{"error": "No config file available (started with --no-config or no config found)."},
status_code=400,
)
connections = payload.get("connections")
if not isinstance(connections, list):
return JSONResponse(
{"error": "'connections' must be an array."},
status_code=400,
)
try:
_write_db_connectors_to_toml(self.config_path, connections)
except Exception as exc:
logger.error("Failed to write db settings to %s: %s", self.config_path, exc)
return JSONResponse({"error": str(exc)}, status_code=500)
return {"ok": True, "configPath": str(self.config_path)}

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.

⚠️ Potential issue | 🟠 Major

/api/db/settings stays stale after a successful save.

put_db_settings() only writes the TOML file. get_db_settings() still reads self.db_bridge_registry, and that registry is only built once in __init__. A refresh right after saving will reload the old connection list, and the connectionId-based runtime endpoints keep using stale settings until the process restarts.

Either rebuild self.db_bridge_registry after _write_db_connectors_to_toml(...), or have the settings read path load from self.config_path when it is present.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 359 - 388, After
successfully writing TOML in put_db_settings (after
_write_db_connectors_to_toml(self.config_path, connections)), reload the runtime
registry so get_db_settings and the connectionId endpoints see the new settings:
either call a reload method on self.db_bridge_registry (e.g.
self.db_bridge_registry.reload_from_config(self.config_path)) or reinitialize it
from the config file (e.g. self.db_bridge_registry =
DbBridgeRegistry(self.config_path) or equivalent), ensuring the refreshed
registry replaces the old one before returning the response; alternatively,
modify get_db_settings to read directly from self.config_path when present
instead of relying on the one-time-built self.db_bridge_registry.

Comment on lines +105 to +110
"config": {
"host": "localhost",
"port": "5432",
"database": "example",
"user": "u",
},

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Locate serializer and API response assembly paths.
fd -t f 'launcher.py|registry.py|postgres.py|snowflake.py|factory.py|types.py' python/sqlrooms-cli/sqlrooms/web

# 2) Inspect where config payload is built and whether secrets are filtered.
rg -n -C3 'config_dict|dbBridge|connections|/api/config|password|private_key|token|secret' python/sqlrooms-cli/sqlrooms/web

# 3) Inspect connector settings models for sensitive fields.
rg -n -C3 'class .*ConnectorSettings|password|private_key|token|secret' python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors

Repository: sqlrooms/sqlrooms

Length of output: 19693


🏁 Script executed:

cat -n python/sqlrooms-cli/tests/test_launcher.py | sed -n '100,115p'

Repository: sqlrooms/sqlrooms

Length of output: 638


🏁 Script executed:

cat -n python/sqlrooms-cli/tests/test_launcher.py | sed -n '145,160p'

Repository: sqlrooms/sqlrooms

Length of output: 775


🏁 Script executed:

cat -n python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/snowflake.py | head -80

Repository: sqlrooms/sqlrooms

Length of output: 2874


🏁 Script executed:

cat -n python/sqlrooms-cli/tests/test_launcher.py | head -80

Repository: sqlrooms/sqlrooms

Length of output: 2866


🏁 Script executed:

rg -n 'password|test_with_postgres|test_with_snowflake' python/sqlrooms-cli/tests/test_launcher.py | head -20

Repository: sqlrooms/sqlrooms

Length of output: 43


🏁 Script executed:

cat -n python/sqlrooms-cli/tests/test_launcher.py | sed -n '80,160p'

Repository: sqlrooms/sqlrooms

Length of output: 3258


Credentials leak through /api/config in raw config objects.

Both PostgresConnector.config_dict() and SnowflakeConnector.config_dict() include the password field if set in connector settings. The /api/config endpoint returns these values without filtering:

  • postgres.py (line 38–44): iterates ("host", "port", "database", "user", "password") and includes any non-empty value
  • snowflake.py (line 36–51): to_connect_kwargs() iterates similar fields including password

The test assertions at lines 105–110 and 149–152 pass only because the test fixtures don't set passwords. If a connector is configured with a password, it will be exposed in the API response and logs.

Add an explicit allowlist in registry.py runtime_connections() to exclude sensitive fields before including config_dict() in the response, and update assertions to verify secret fields are not present.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/tests/test_launcher.py` around lines 105 - 110, The
runtime_connections() response currently includes raw connector config_dict()
values (from PostgresConnector.config_dict and
SnowflakeConnector.to_connect_kwargs), which can leak secrets like "password";
modify registry.py::runtime_connections to apply an explicit allowlist of
non-sensitive keys (e.g., host, port, database, user, maybe account) and filter
each connector.config_dict() through that allowlist before adding it to the API
payload, ensuring any "password" or other secret keys are omitted; then update
the tests in tests/test_launcher.py to assert that sensitive fields (e.g.,
"password") are not present in the returned config objects for connectors.

Signed-off-by: Ilya Boyandin <[email protected]>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

♻️ Duplicate comments (1)
python/sqlrooms-cli/sqlrooms/web/launcher.py (1)

385-392: ⚠️ Potential issue | 🟠 Major

Reload the bridge registry after a successful save.

This persists the TOML file, but get_db_settings() and the connectionId-based endpoints still read self.db_bridge_registry, which was built once in __init__. After saving, a refresh keeps showing the old connections until restart.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 385 - 392, The
handler currently writes the TOML via _write_db_connectors_to_toml but does not
update the in-memory registry built in __init__, so get_db_settings() and
connectionId-based endpoints keep using the stale self.db_bridge_registry; after
a successful write (the block that returns {"ok": True, "configPath": ...}),
reload or rebuild self.db_bridge_registry (e.g., call the registry-loading
method used in __init__ or a dedicated refresh method) so the in-memory registry
reflects the newly saved connections and subsequent
get_db_settings()/connectionId endpoints return the updated data.
🧹 Nitpick comments (2)
packages/db-settings/src/components/DbConnectionForm.tsx (2)

147-162: Consider guarding against state updates on unmounted component.

If the user closes the form while a connection test is in-flight, setTestState and setTestError will be called on an unmounted component. While React 18+ no longer warns about this, the state update is still wasted work.

Optional: Add cleanup with AbortController or mounted flag
+  const mountedRef = React.useRef(true);
+  React.useEffect(() => {
+    return () => { mountedRef.current = false; };
+  }, []);
+
   const handleTestConnection = async () => {
     if (!engineId) return;
     setTestState('testing');
     setTestError('');
     const cleanConfig: Record<string, string> = {};
     for (const [k, v] of Object.entries(config)) {
       if (v) cleanConfig[k] = v;
     }
     const result = await testConnection(engineId, cleanConfig);
+    if (!mountedRef.current) return;
     if (result.ok) {
       setTestState('success');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db-settings/src/components/DbConnectionForm.tsx` around lines 147 -
162, The handleTestConnection function can call setTestState/setTestError after
the component unmounts; add a mounted guard or AbortController to prevent state
updates when unmounted: create a ref/flag (e.g., isMounted) or an
AbortController in the component, set it true on mount and false/abort on
cleanup, then check the flag or signal before calling setTestState/setTestError
(and pass the signal to testConnection if using AbortController) so any
in-flight testConnection results are ignored when the form unmounts.

91-111: The dependency suppression is acceptable but warrants a note.

The eslint-disable for react-hooks/exhaustive-deps is intentional here—editing triggers the sync effect above (lines 70-89) which sets suppressSyncRef, and including it would create a loop. The inline comment explains the pattern well.

Consider adding upsertConnection to the dependency array since store actions are typically stable references and it makes the intent clearer, though this is a minor nitpick.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db-settings/src/components/DbConnectionForm.tsx` around lines 91 -
111, The effect that auto-syncs form edits into the store (uses isEditing,
suppressSyncRef, buildConnection) currently disables exhaustive-deps; add
upsertConnection to the dependency array so the effect explicitly depends on the
store action (upsertConnection) while retaining the existing suppression
behavior for editing via suppressSyncRef to avoid the prop/state loop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/sqlrooms-cli/sqlrooms/cli.py`:
- Line 414: The current call passes the loaded config_path through
(config_path=config_path) which lets fresh installs treat absence of a file the
same as an explicit opt-out; change the logic so you preserve two distinct
values: one "loaded_config_path" (the path found or created) used for reading
and a separate "save_config_path" that is None only when the user passed the
explicit --no-config flag; update the call site that currently uses
config_path=config_path to use save_config_path for writable targets and ensure
functions/classes that accept config_path (e.g., the initializer or function
named with parameter config_path) continue to receive None only when user opted
out and otherwise receive a concrete filesystem path for persistence.
- Around line 116-123: The port value can be an int from tomllib, so calling
_normalize_config_string(item.get("port")) returns None and falls back to
"5432"; update the code that constructs the PostgresConnectorSettings (or
_normalize_config_string) to accept integer ports by converting item.get("port")
to a string before normalization (e.g., coerce via str() when item.get("port")
is not None and not already a str) so the actual configured port (e.g., 6543) is
preserved instead of silently defaulting; reference PostgresConnectorSettings
and _normalize_config_string to locate the change.

In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 78-97: When building each connection entry in the loop over
connections, detect if the incoming conn's engine identifier (e.g.
conn.get("engineId") or conn.get("engine_id")) differs from the stored entry in
existing_by_id for that conn_id; if it does, don't reuse the old entry
fields—reset base to only include non-engine/shared fields (or start with an
empty dict) before merging frontend_to_toml keys and engine_config. Update the
logic around existing_by_id, base and engine_config so that when engineId
changes you clear engine-specific keys (e.g. Snowflake-only keys like account,
warehouse, schema) instead of carrying them forward into the new TOML entry.

---

Duplicate comments:
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 385-392: The handler currently writes the TOML via
_write_db_connectors_to_toml but does not update the in-memory registry built in
__init__, so get_db_settings() and connectionId-based endpoints keep using the
stale self.db_bridge_registry; after a successful write (the block that returns
{"ok": True, "configPath": ...}), reload or rebuild self.db_bridge_registry
(e.g., call the registry-loading method used in __init__ or a dedicated refresh
method) so the in-memory registry reflects the newly saved connections and
subsequent get_db_settings()/connectionId endpoints return the updated data.

---

Nitpick comments:
In `@packages/db-settings/src/components/DbConnectionForm.tsx`:
- Around line 147-162: The handleTestConnection function can call
setTestState/setTestError after the component unmounts; add a mounted guard or
AbortController to prevent state updates when unmounted: create a ref/flag
(e.g., isMounted) or an AbortController in the component, set it true on mount
and false/abort on cleanup, then check the flag or signal before calling
setTestState/setTestError (and pass the signal to testConnection if using
AbortController) so any in-flight testConnection results are ignored when the
form unmounts.
- Around line 91-111: The effect that auto-syncs form edits into the store (uses
isEditing, suppressSyncRef, buildConnection) currently disables exhaustive-deps;
add upsertConnection to the dependency array so the effect explicitly depends on
the store action (upsertConnection) while retaining the existing suppression
behavior for editing via suppressSyncRef to avoid the prop/state loop.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37ebdebb-2928-4498-9a8c-ffc06a74bf99

📥 Commits

Reviewing files that changed from the base of the PR and between 37e7552 and 5f79982.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • packages/db-settings/src/DbSettingsSlice.ts
  • packages/db-settings/src/components/DbConnectionForm.tsx
  • packages/db-settings/src/components/DbSettings.tsx
  • packages/schema-tree/src/nodes/RefreshButton.tsx
  • packages/sql-editor/src/components/SchemaExplorer.tsx
  • python/sqlrooms-cli/sqlrooms/cli.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/postgres.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/snowflake.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/registry.py
  • python/sqlrooms-cli/sqlrooms/web/launcher.py
  • python/sqlrooms-cli/tests/test_cli.py
  • python/sqlrooms-cli/tests/test_launcher.py
✅ Files skipped from review due to trivial changes (2)
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/registry.py
  • packages/db-settings/src/DbSettingsSlice.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • python/sqlrooms-cli/tests/test_cli.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/snowflake.py
  • packages/schema-tree/src/nodes/RefreshButton.tsx
  • packages/sql-editor/src/components/SchemaExplorer.tsx
  • python/sqlrooms-cli/tests/test_launcher.py
  • packages/db-settings/src/components/DbSettings.tsx

Comment thread python/sqlrooms-cli/sqlrooms/cli.py
Comment thread python/sqlrooms-cli/sqlrooms/cli.py Outdated
Comment thread python/sqlrooms-cli/sqlrooms/web/launcher.py
Signed-off-by: Ilya Boyandin <[email protected]>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
python/sqlrooms-cli/sqlrooms/web/launcher.py (1)

389-411: ⚠️ Potential issue | 🟠 Major

/api/db/settings returns stale data after a successful save.

After _write_db_connectors_to_toml writes to disk, self.db_bridge_registry is not refreshed. Subsequent GET /api/db/settings calls and runtime endpoints (e.g., /api/db/execute-query) continue using the old connector list until the server restarts.

Rebuild the registry after a successful write or have the GET path read directly from the config file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 389 - 411, After
successfully writing connectors with _write_db_connectors_to_toml inside
put_db_settings, refresh the in-memory registry so runtime reads the new
connectors: call the same logic used at startup to rebuild
self.db_bridge_registry (e.g., invoke an existing initializer like
self._init_db_bridge_registry() or add a small helper
refresh_db_bridge_registry(config_path) that re-parses the toml and repopulates
self.db_bridge_registry) before returning {"ok": True, ...}; alternatively
change the GET /api/db/settings handler to read directly from self.config_path
on each request so it always returns the on-disk state.
🧹 Nitpick comments (1)
python/sqlrooms-cli/sqlrooms/web/launcher.py (1)

129-134: Redundant import of re.

re is already imported at line 5. This local import on line 131 is unnecessary.

♻️ Suggested fix
     raw = tomlkit.dumps(doc)
     # Collapse runs of blank lines that tomlkit may accumulate on repeated writes
-    import re
-
     raw = re.sub(r"\n{3,}", "\n\n", raw)
     config_path.write_text(raw, encoding="utf-8")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 129 - 134, Remove
the redundant local import of the regex module inside the block that serializes
the toml document: delete the inline "import re" and rely on the module-level
"re" import already present; ensure the code that uses re.sub on the serialized
"raw" string (around tomlkit.dumps and config_path.write_text) still references
the top-level re import so the collapse-of-blank-lines logic continues to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/sqlrooms-cli/tests/test_launcher.py`:
- Around line 299-302: Replace the direct import of tomllib in test_launcher.py
with the same compatibility pattern used in cli.py: try to import tomllib, and
on ImportError import tomli as tomllib so the subsequent call doc =
tomllib.load(fh) works on Python 3.10; update the top of the test (where tomllib
is imported and doc = tomllib.load(fh) is used) to use that try/except import
fallback so tomli is used when tomllib is unavailable.

---

Duplicate comments:
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 389-411: After successfully writing connectors with
_write_db_connectors_to_toml inside put_db_settings, refresh the in-memory
registry so runtime reads the new connectors: call the same logic used at
startup to rebuild self.db_bridge_registry (e.g., invoke an existing initializer
like self._init_db_bridge_registry() or add a small helper
refresh_db_bridge_registry(config_path) that re-parses the toml and repopulates
self.db_bridge_registry) before returning {"ok": True, ...}; alternatively
change the GET /api/db/settings handler to read directly from self.config_path
on each request so it always returns the on-disk state.

---

Nitpick comments:
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 129-134: Remove the redundant local import of the regex module
inside the block that serializes the toml document: delete the inline "import
re" and rely on the module-level "re" import already present; ensure the code
that uses re.sub on the serialized "raw" string (around tomlkit.dumps and
config_path.write_text) still references the top-level re import so the
collapse-of-blank-lines logic continues to work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68b05fca-485b-499a-8d85-56c0b038106b

📥 Commits

Reviewing files that changed from the base of the PR and between 5f79982 and 4d92026.

📒 Files selected for processing (4)
  • python/sqlrooms-cli/sqlrooms/cli.py
  • python/sqlrooms-cli/sqlrooms/web/launcher.py
  • python/sqlrooms-cli/tests/test_cli.py
  • python/sqlrooms-cli/tests/test_launcher.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/sqlrooms-cli/tests/test_cli.py

Comment on lines +299 to +302
import tomllib

with config_path.open("rb") as fh:
doc = tomllib.load(fh)

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check minimum Python version requirements
rg -n "python_requires|python-requires" python/sqlrooms-cli/pyproject.toml python/sqlrooms-cli/setup.py 2>/dev/null || echo "No explicit Python version requirement found"

# Check if tomli is listed as a dependency
rg -n "tomli" python/sqlrooms-cli/pyproject.toml python/sqlrooms-cli/setup.py 2>/dev/null || echo "tomli not found in dependencies"

Repository: sqlrooms/sqlrooms

Length of output: 218


🏁 Script executed:

# Find cli.py and check for tomllib/tomli handling
find . -name "cli.py" -path "*/sqlrooms-cli/*" -type f | head -5

Repository: sqlrooms/sqlrooms

Length of output: 98


🏁 Script executed:

# Check the full context around lines 299-302 in test_launcher.py
cat -n python/sqlrooms-cli/tests/test_launcher.py | sed -n '295,310p'

Repository: sqlrooms/sqlrooms

Length of output: 548


🏁 Script executed:

# Search for tomllib/tomli imports in cli.py to verify the try/except pattern
rg -n -A 5 "tomllib|tomli" python/sqlrooms-cli/src/sqlrooms_cli/cli.py

Repository: sqlrooms/sqlrooms

Length of output: 144


🏁 Script executed:

# Search for tomllib/tomli imports in the correct cli.py path
rg -n -A 5 "tomllib|tomli" python/sqlrooms-cli/sqlrooms/cli.py

Repository: sqlrooms/sqlrooms

Length of output: 427


Update test_launcher.py to handle Python 3.10 compatibility for tomllib.

The code at lines 299-302 directly imports tomllib, which is only available in Python 3.11+. Since the project declares tomli>=2.0.0; python_version < '3.11' as a dependency, Python 3.10 support is intended. Use the same try/except pattern as in cli.py:

Suggested fix
-    import tomllib
+    try:
+        import tomllib  # type: ignore[attr-defined]
+    except ModuleNotFoundError:
+        import tomli as tomllib  # type: ignore[no-redef]

     with config_path.open("rb") as fh:
         doc = tomllib.load(fh)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import tomllib
with config_path.open("rb") as fh:
doc = tomllib.load(fh)
try:
import tomllib # type: ignore[attr-defined]
except ModuleNotFoundError:
import tomli as tomllib # type: ignore[no-redef]
with config_path.open("rb") as fh:
doc = tomllib.load(fh)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/tests/test_launcher.py` around lines 299 - 302, Replace
the direct import of tomllib in test_launcher.py with the same compatibility
pattern used in cli.py: try to import tomllib, and on ImportError import tomli
as tomllib so the subsequent call doc = tomllib.load(fh) works on Python 3.10;
update the top of the test (where tomllib is imported and doc = tomllib.load(fh)
is used) to use that try/except import fallback so tomli is used when tomllib is
unavailable.

@ilyabo
ilyabo merged commit aebcd22 into main Mar 23, 2026
7 checks passed
@ilyabo
ilyabo deleted the db-settings branch March 23, 2026 15:44
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