Skip to content

feat: Snowflake connector integration - #401

Merged
ilyabo merged 18 commits into
mainfrom
cursor/snowflake-connector-integration-f6d5
Feb 28, 2026
Merged

ilyabo merged 18 commits into
mainfrom
cursor/snowflake-connector-integration-f6d5

Conversation

@ilyabo

@ilyabo ilyabo commented Feb 27, 2026 •

Copy link
Copy Markdown
Collaborator

Add a scalable Python connector framework with Snowflake support to DbSlice for use in notebook cells.

The existing DbSlice only supported DuckDB and the Python server had hardcoded Postgres logic. This PR introduces a generic connector registry on the Python backend, allowing new database connections like Snowflake to be dynamically registered and exposed to the frontend. This enables notebook SQL cells to select and query against these custom connections.

image

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Pluggable DB bridge with multiple connector support (Postgres, Snowflake) and runtime-exposed connections
    • AI providers/runtime model configuration exposed to UI and CLI; unified Chat-based assistant UI
    • Streaming query results and server-side query cancellation; streaming-based materialization
  • Bug Fixes

    • Improved error messages and robustness for bridge communication and streaming
  • Documentation

    • CLI docs updated with config file examples, new flags, and connector usage
  • Tests

    • Extensive new tests for bridge, streaming, launcher, and CLI config handling
  • Chores

    • Optional connector dependencies moved to extras; dev script simplified

@cursor

cursor Bot commented Feb 27, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai

coderabbitai Bot commented Feb 27, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a pluggable DB bridge system (registry + connectors) with Postgres and Snowflake connectors, streaming Arrow support and framing, runtime exposure of dbBridge and aiProviders to the frontend, and registry-driven HTTP endpoints replacing hard-coded DSNs and bridge flags.

Changes

Cohort / File(s) Summary
Frontend runtime & store
apps/sqlrooms-cli-ui/src/runtimeConfig.ts, apps/sqlrooms-cli-ui/src/store.ts
Add aiProviders and dbBridge to runtime config; derive runtime bridge config and initialize HTTP DB bridge + connections; wire provider-aware AI defaults and per-provider apiKey lookup.
HTTP launcher & server CLI
python/sqlrooms-cli/sqlrooms/web/launcher.py, python/sqlrooms-cli/sqlrooms/cli.py
Introduce DB_BRIDGE_ID, accept connector settings, build DbBridgeRegistry, expose dbBridge and aiProviders in runtime config, and route API handlers (test/list/execute/fetch-stream/cancel) to registry methods with framing.
Backend DB bridge framework (Python)
python/sqlrooms-cli/sqlrooms/web/db_bridge.py
New pluggable connector protocol, Postgres and Snowflake connector implementations, DbBridgeRegistry, registry factory, utilities for Arrow/json payloads, and UnknownBridgeConnectionError.
Bridge transport & error handling (TS)
packages/db/src/bridge.ts, packages/db/src/types.ts
Normalize bridge baseUrl trimming, improve error extraction/details, add cancelQuery API, and add optional fetchArrowStream signature to DbBridge types.
Arrow streaming implementation (TS)
packages/db/src/arrow-streaming.ts, packages/db/src/DbSlice.ts
New framed-binary parser and Arrow IPC decoder; add streaming materialization path in DbSlice that decodes framed Arrow batches and incrementally materializes into tables.
Server streaming endpoints & framing
python/sqlrooms-cli/sqlrooms/web/launcher.py
Add framed stream encoder, /api/db/fetch-arrow-stream endpoint, cancel endpoint, and registry-backed handlers with disconnect/error handling.
Tests (JS/Py)
python/sqlrooms-cli/tests/test_db_bridge.py, python/sqlrooms-cli/tests/test_launcher.py, packages/db/__tests__/*
Add tests for registry/connectors, launcher runtime metadata, framed-stream parsing, Arrow IPC decoding, DbSlice streaming/materialization, and CLI config loaders.
CLI config & docs (Python)
python/sqlrooms-cli/sqlrooms/cli.py, python/sqlrooms-cli/README.md, python/sqlrooms-cli/pyproject.toml
Shift CLI to TOML config-driven connectors and ai_providers, add config flags, optional dependencies for connectors, and document configuration examples and precedence.
UI Assistant & behavior tweaks
apps/sqlrooms-cli-ui/src/components/AssistantDrawer.tsx, packages/cells/src/defaultCellRegistry.tsx
Replace multi-component assistant with Chat-based UI and inline provider apiKey updates; trigger dependency-graph update on explicit SQL cell run.
Misc & deps
packages/db/package.json, python/sqlrooms-cli/package.json, packages/db/src/helpers.ts
Add dev deps (jest globals, zustand), remove psycopg from main deps (optional extras), remove default postgres DSN from dev script, and rename default ephemeral DB identifier.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server as SqlroomsHttpServer
    participant Registry as DbBridgeRegistry
    participant Connector as DB Connector
    participant DB as Database

    Client->>Server: POST /api/db/execute (connectionId, sql, queryType)
    Server->>Registry: execute_query(connectionId, sql, queryType)
    Registry->>Registry: _get_connector(connectionId)
    Registry->>Connector: execute_query(sql, queryType)
    Connector->>DB: run SQL
    DB-->>Connector: rows/result or Arrow bytes
    Connector-->>Registry: result
    Registry-->>Server: result
    Server-->>Client: JSON or binary response

    Client->>Server: POST /api/db/fetch-arrow-stream (connectionId, sql, chunkRows)
    Server->>Registry: stream_arrow_batches(connectionId, sql, chunkRows, queryId)
    Registry->>Connector: stream_arrow_batches(sql, chunkRows, queryId)
    Connector->>DB: stream query
    DB-->>Connector: framed Arrow chunks
    Connector-->>Registry: framed chunks (async iterable)
    Registry-->>Server: stream frames
    Server-->>Client: chunked framed stream
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I tunneled through stacks and built a bright bridge,
Postgres and Snowflake now leap the ridge,
Arrow streams sparkle like carrots in flight,
Registry hums softly, queries take bite. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.60% 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
Title check ✅ Passed The title 'feat: Snowflake connector integration' accurately summarizes the main objective of the PR, which adds Snowflake connector support to the database bridge framework.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/snowflake-connector-integration-f6d5

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.

@ilyabo ilyabo changed the title Snowflake connector integration feat: Snowflake connector integration Feb 27, 2026
@ilyabo
ilyabo marked this pull request as ready for review February 27, 2026 18:41

@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

🧹 Nitpick comments (3)
apps/sqlrooms-cli-ui/src/runtimeConfig.ts (1)

9-20: Extract a shared bridge config type to avoid drift.

The connection shape here is duplicated in apps/sqlrooms-cli-ui/src/store.ts. Exporting a shared RuntimeDbBridgeConfig/RuntimeDbBridgeConnection type from this module would keep both sides in sync.

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

In `@apps/sqlrooms-cli-ui/src/runtimeConfig.ts` around lines 9 - 20, Extract and
export shared types for the bridge config from this module: define and export
RuntimeDbBridgeConnection (with fields id, engineId, title, runtimeSupport?:
'browser'|'server'|'both', requiresBridge?: boolean, bridgeId?: string, isCore?:
boolean) and RuntimeDbBridgeConfig (with id and connections:
RuntimeDbBridgeConnection[]), then replace the inline connection/type definition
in store.ts to import and use these exported types
(RuntimeDbBridgeConfig/RuntimeDbBridgeConnection) so both modules share the
exact same type definitions.
python/sqlrooms-cli/tests/test_db_bridge.py (1)

31-70: Add a regression test for duplicate connector IDs.

Given registry registration is ID-keyed, add a test that registering the same connection_id twice raises (after the register guard fix). This prevents future silent overrides.

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

In `@python/sqlrooms-cli/tests/test_db_bridge.py` around lines 31 - 70, Add a
regression test that ensures DbBridgeRegistry.register rejects duplicate
connection IDs: create a DbBridgeRegistry(bridge_id="bridge-id"), register a
connector with a specific connection id (e.g., using the existing
_FakeConnector()), then attempt to register another connector with the same
connection id and assert that registering the second one raises an exception
(use pytest.raises and refer to DbBridgeRegistry.register). Ensure the test name
reflects duplicate registration (e.g.,
test_registry_raises_for_duplicate_connection_id) and uses the same connection
id as in test_registry_routes_to_registered_connector so future silent overrides
are prevented.
python/sqlrooms-cli/README.md (1)

83-86: Prefer env vars over CLI args for Snowflake passwords in the primary example.

Line 85 documents --snowflake-password, which is easy to leak via shell history/process inspection. Recommend using env vars in the main snippet and keeping CLI password flag as secondary/advanced usage.

📝 Safer docs tweak
 uvx sqlrooms \
   ./sqlrooms.db \
   --ws-port 4000 \
   --port 4173 \
   --snowflake-account "<account>" \
   --snowflake-user "<user>" \
-  --snowflake-password "<password>" \
   --snowflake-warehouse "<warehouse>" \
   --snowflake-database "<database>" \
   --snowflake-schema "<schema>"
+
+# Prefer env var for secrets:
+# SNOWFLAKE_PASSWORD="<password>" uvx sqlrooms ...

Also applies to: 101-102

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

In `@python/sqlrooms-cli/README.md` around lines 83 - 86, Update the primary
README example to avoid passing secrets on the CLI by replacing the use of the
--snowflake-password flag with an environment variable (e.g.,
SNOWFLAKE_PASSWORD) and show how to export it before running the command; keep
the existing --snowflake-password option only as a secondary/advanced usage
note. Reference the example lines that currently show --snowflake-password and
the related Snowflake flags so reviewers can update the snippet and add a short
note about why env var usage is preferred.
🤖 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/web/db_bridge.py`:
- Around line 78-80: The register method currently overwrites existing entries
in the _connectors dict; update register(self, connector: DbBridgeConnector) to
check connector.connection_id against self._connectors and raise a clear
exception (e.g., ValueError or a custom DuplicateConnectionError) if the id
already exists, so duplicate connection_id registrations fail fast instead of
silently replacing the existing connector.

In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 228-231: Replace all occurrences in
python/sqlrooms-cli/sqlrooms/web/launcher.py where exceptions are returned to
the client using str(exc) (notably the except UnknownBridgeConnectionError and
generic except Exception handlers) so the API sends a generic, client-safe error
message (e.g., {"ok": False, "error": "internal server error"} or a harmless
domain-specific message), and instead log the full exception details server-side
using the module logger (logger.exception or logger.error(..., exc_info=True))
to capture stack and sensitive context; update handlers around the
UnknownBridgeConnectionError and other exception blocks referenced (the except
blocks that currently return str(exc) at the mentioned lines) to follow this
pattern.
- Around line 226-227: The synchronous db_bridge_registry calls (e.g.,
self.db_bridge_registry.test_connection and the other registry methods called at
the same spots) are being invoked directly inside async request handlers and
must be offloaded to a thread pool; replace each direct call with an awaited
run_in_threadpool invocation (for example, await
run_in_threadpool(self.db_bridge_registry.test_connection, connection_id)) using
the run_in_threadpool helper from starlette.concurrency (or fastapi.concurrency)
so the registry I/O does not block the event loop, and apply this pattern to the
calls at the locations that previously invoked
db_bridge_registry.test_connection, and the similar calls at the other four
lines.

---

Nitpick comments:
In `@apps/sqlrooms-cli-ui/src/runtimeConfig.ts`:
- Around line 9-20: Extract and export shared types for the bridge config from
this module: define and export RuntimeDbBridgeConnection (with fields id,
engineId, title, runtimeSupport?: 'browser'|'server'|'both', requiresBridge?:
boolean, bridgeId?: string, isCore?: boolean) and RuntimeDbBridgeConfig (with id
and connections: RuntimeDbBridgeConnection[]), then replace the inline
connection/type definition in store.ts to import and use these exported types
(RuntimeDbBridgeConfig/RuntimeDbBridgeConnection) so both modules share the
exact same type definitions.

In `@python/sqlrooms-cli/README.md`:
- Around line 83-86: Update the primary README example to avoid passing secrets
on the CLI by replacing the use of the --snowflake-password flag with an
environment variable (e.g., SNOWFLAKE_PASSWORD) and show how to export it before
running the command; keep the existing --snowflake-password option only as a
secondary/advanced usage note. Reference the example lines that currently show
--snowflake-password and the related Snowflake flags so reviewers can update the
snippet and add a short note about why env var usage is preferred.

In `@python/sqlrooms-cli/tests/test_db_bridge.py`:
- Around line 31-70: Add a regression test that ensures
DbBridgeRegistry.register rejects duplicate connection IDs: create a
DbBridgeRegistry(bridge_id="bridge-id"), register a connector with a specific
connection id (e.g., using the existing _FakeConnector()), then attempt to
register another connector with the same connection id and assert that
registering the second one raises an exception (use pytest.raises and refer to
DbBridgeRegistry.register). Ensure the test name reflects duplicate registration
(e.g., test_registry_raises_for_duplicate_connection_id) and uses the same
connection id as in test_registry_routes_to_registered_connector so future
silent overrides are prevented.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0692aac and 665429a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • apps/sqlrooms-cli-ui/package.json
  • apps/sqlrooms-cli-ui/src/runtimeConfig.ts
  • apps/sqlrooms-cli-ui/src/store.ts
  • packages/db/src/bridge.ts
  • python/sqlrooms-cli/README.md
  • python/sqlrooms-cli/sqlrooms/cli.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge.py
  • python/sqlrooms-cli/sqlrooms/web/launcher.py
  • python/sqlrooms-cli/tests/test_db_bridge.py
  • python/sqlrooms-cli/tests/test_launcher.py

Comment on lines +78 to +80
def register(self, connector: DbBridgeConnector) -> None:
self._connectors[connector.connection_id] = connector

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

Fail fast on duplicate connection_id registration.

register() silently replaces an existing connector with the same ID. That can hide configuration mistakes and unexpectedly reroute queries to another engine.

🔧 Proposed fix
 def register(self, connector: DbBridgeConnector) -> None:
-    self._connectors[connector.connection_id] = connector
+    existing = self._connectors.get(connector.connection_id)
+    if existing is not None and existing is not connector:
+        raise ValueError(
+            f"Duplicate DB bridge connection_id: {connector.connection_id}"
+        )
+    self._connectors[connector.connection_id] = connector
📝 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
def register(self, connector: DbBridgeConnector) -> None:
self._connectors[connector.connection_id] = connector
def register(self, connector: DbBridgeConnector) -> None:
existing = self._connectors.get(connector.connection_id)
if existing is not None and existing is not connector:
raise ValueError(
f"Duplicate DB bridge connection_id: {connector.connection_id}"
)
self._connectors[connector.connection_id] = connector
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py` around lines 78 - 80, The
register method currently overwrites existing entries in the _connectors dict;
update register(self, connector: DbBridgeConnector) to check
connector.connection_id against self._connectors and raise a clear exception
(e.g., ValueError or a custom DuplicateConnectionError) if the id already
exists, so duplicate connection_id registrations fail fast instead of silently
replacing the existing connector.

Comment on lines +226 to +227
ok = self.db_bridge_registry.test_connection(connection_id)
return {"ok": bool(ok)}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if registry methods are synchronous
echo "=== Registry methods in db_bridge.py ==="
rg -nP --type=py 'def (test_connection|list_catalog|execute_query|fetch_arrow_base64|cancel_query)\(' python/sqlrooms-cli/sqlrooms/web/db_bridge.py | head -20

echo ""
echo "=== Launcher route handlers (checking async def) ==="
rg -nP --type=py 'async def' python/sqlrooms-cli/sqlrooms/web/launcher.py | head -20

echo ""
echo "=== Direct registry calls in launcher.py ==="
rg -nP --type=py 'self\.db_bridge_registry\.(test_connection|list_catalog|execute_query|fetch_arrow_base64|cancel_query)\(' python/sqlrooms-cli/sqlrooms/web/launcher.py | head -30

Repository: sqlrooms/sqlrooms

Length of output: 2356


🏁 Script executed:

#!/bin/bash
# Check the actual code at the specified lines
echo "=== Lines 220-230 (test_connection at 226) ==="
sed -n '220,230p' python/sqlrooms-cli/sqlrooms/web/launcher.py

echo ""
echo "=== Lines 240-250 (list_catalog at 244) ==="
sed -n '240,250p' python/sqlrooms-cli/sqlrooms/web/launcher.py

echo ""
echo "=== Lines 260-270 (execute_query at 265) ==="
sed -n '260,270p' python/sqlrooms-cli/sqlrooms/web/launcher.py

echo ""
echo "=== Lines 280-290 (fetch_arrow_base64 at 284) ==="
sed -n '280,290p' python/sqlrooms-cli/sqlrooms/web/launcher.py

echo ""
echo "=== Lines 300-310 (cancel_query at 303) ==="
sed -n '300,310p' python/sqlrooms-cli/sqlrooms/web/launcher.py

echo ""
echo "=== Check for run_in_threadpool usage in launcher.py ==="
rg -n 'run_in_threadpool' python/sqlrooms-cli/sqlrooms/web/launcher.py

echo ""
echo "=== Check imports in launcher.py ==="
head -50 python/sqlrooms-cli/sqlrooms/web/launcher.py | grep -E 'import|from'

Repository: sqlrooms/sqlrooms

Length of output: 3631


Wrap synchronous registry calls in run_in_threadpool within async handlers.

Direct synchronous I/O calls to db_bridge_registry methods at lines 226, 244, 265, 284, and 303 will block the event loop and stall unrelated requests. These must be offloaded to a thread pool.

Suggested pattern (apply to all five locations)
+from fastapi.concurrency import run_in_threadpool
@@
-                ok = self.db_bridge_registry.test_connection(connection_id)
+                ok = await run_in_threadpool(
+                    self.db_bridge_registry.test_connection,
+                    connection_id,
+                )
@@
-                return self.db_bridge_registry.list_catalog(connection_id)
+                return await run_in_threadpool(
+                    self.db_bridge_registry.list_catalog,
+                    connection_id,
+                )
@@
-                return self.db_bridge_registry.execute_query(
-                    connection_id=connection_id,
-                    sql=sql,
-                    query_type=query_type,
-                )
+                return await run_in_threadpool(
+                    self.db_bridge_registry.execute_query,
+                    connection_id=connection_id,
+                    sql=sql,
+                    query_type=query_type,
+                )
📝 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
ok = self.db_bridge_registry.test_connection(connection_id)
return {"ok": bool(ok)}
ok = await run_in_threadpool(
self.db_bridge_registry.test_connection,
connection_id,
)
return {"ok": bool(ok)}
🤖 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 226 - 227, The
synchronous db_bridge_registry calls (e.g.,
self.db_bridge_registry.test_connection and the other registry methods called at
the same spots) are being invoked directly inside async request handlers and
must be offloaded to a thread pool; replace each direct call with an awaited
run_in_threadpool invocation (for example, await
run_in_threadpool(self.db_bridge_registry.test_connection, connection_id)) using
the run_in_threadpool helper from starlette.concurrency (or fastapi.concurrency)
so the registry I/O does not block the event loop, and apply this pattern to the
calls at the locations that previously invoked
db_bridge_registry.test_connection, and the similar calls at the other four
lines.

Comment on lines +228 to 231
except UnknownBridgeConnectionError as exc:
return {"ok": False, "error": str(exc)}
except Exception as exc:
return {"ok": False, "error": str(exc)}

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

Avoid returning raw exception text in API responses.

At Line 229, Line 246, Line 271/273, and Line 290/292, str(exc) is sent to clients. Connector exceptions can expose sensitive internals (DSN/account details, SQL text, stack-level messages). Return a generic client-safe message and log full details server-side.

🔧 Suggested hardening pattern
+        def _safe_error(message: str, *, status_code: int = 500, exc: Exception | None = None):
+            if exc is not None:
+                logger.exception("DB bridge request failed: %s", message, exc_info=exc)
+            return JSONResponse({"error": message}, status_code=status_code)
+
         `@app.post`("/api/db/execute-query")
         async def execute_query(payload: Dict[str, Any]):
@@
-            except UnknownBridgeConnectionError as exc:
-                return JSONResponse({"error": str(exc)}, status_code=404)
-            except Exception as exc:
-                return JSONResponse({"error": str(exc)}, status_code=500)
+            except UnknownBridgeConnectionError as exc:
+                return _safe_error("Unknown connectionId", status_code=404, exc=exc)
+            except Exception as exc:
+                return _safe_error("Query execution failed", status_code=500, exc=exc)

Also applies to: 245-248, 270-273, 289-292

🤖 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 228 - 231, Replace
all occurrences in python/sqlrooms-cli/sqlrooms/web/launcher.py where exceptions
are returned to the client using str(exc) (notably the except
UnknownBridgeConnectionError and generic except Exception handlers) so the API
sends a generic, client-safe error message (e.g., {"ok": False, "error":
"internal server error"} or a harmless domain-specific message), and instead log
the full exception details server-side using the module logger (logger.exception
or logger.error(..., exc_info=True)) to capture stack and sensitive context;
update handlers around the UnknownBridgeConnectionError and other exception
blocks referenced (the except blocks that currently return str(exc) at the
mentioned lines) to follow this pattern.

…lake-connector-integration-f6d5

Signed-off-by: Ilya Boyandin <[email protected]>
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.

🧹 Nitpick comments (3)
apps/sqlrooms-cli-ui/src/store.ts (1)

262-281: Consider wrapping bridge registration in a try-catch.

The bridge registration runs at module load time. If createHttpDbBridge or the registration methods throw, it could prevent the entire application from loading.

🛡️ Suggested defensive wrapper
 const bridgeConfig = getRuntimeBridgeConfig();
 if (bridgeConfig?.connections.length) {
+  try {
     const bridge = createHttpDbBridge({
       id: bridgeConfig.id,
       baseUrl: runtimeConfig.apiBaseUrl || '',
     });
     const state = roomStore.getState();
     state.db.connectors.registerBridge(bridge);
     for (const connection of bridgeConfig.connections) {
       const normalizedConnection: DbConnection = {
         id: connection.id,
         engineId: connection.engineId,
         title: connection.title || connection.id,
         runtimeSupport: connection.runtimeSupport || 'server',
         requiresBridge: connection.requiresBridge ?? true,
         bridgeId: connection.bridgeId || bridgeConfig.id,
         isCore: connection.isCore ?? false,
       };
       state.db.connectors.registerConnection(normalizedConnection);
     }
+  } catch (err) {
+    console.error('Failed to register DB bridge:', err);
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/sqlrooms-cli-ui/src/store.ts` around lines 262 - 281, The bridge
registration at module load uses getRuntimeBridgeConfig(), createHttpDbBridge(),
roomStore.getState(), state.db.connectors.registerBridge(), and
state.db.connectors.registerConnection() and can throw during app
initialization; wrap the entire block that creates the bridge and registers
connections in a try-catch so any exception from createHttpDbBridge or the
register* calls is caught, log or report the error (including the error object)
and skip registering the bridge/connections as a safe fallback to avoid breaking
app load.
python/sqlrooms-cli/sqlrooms/web/db_bridge.py (2)

145-150: Wrap connection test in exception handling to return False on failure.

If _connect() or the SELECT 1 query fails (e.g., bad credentials, network issues), the exception propagates to the caller. The test_connection semantics suggest returning False on failure rather than raising.

🛡️ Suggested pattern
 def test_connection(self) -> bool:
+    try:
         with self._connect() as conn:
             with conn.cursor() as cur:
                 cur.execute("SELECT 1")
                 cur.fetchone()
         return True
+    except Exception:
+        return False

Also applies to: 275-280

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

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py` around lines 145 - 150, The
test_connection function currently lets exceptions from _connect() or the SELECT
1 query bubble up; wrap the body of test_connection in a try/except that catches
broad connection/query exceptions (Exception) and returns False on any failure
while returning True on success; keep the existing context managers (with
self._connect() as conn / with conn.cursor() as cur) so resources close
normally, and apply the same try/except pattern to the similar connection-test
block around lines 275–280 (the other connection-testing function/section) to
ensure failures return False instead of raising.

208-210: Query cancellation is a no-op for both connectors.

Both PostgresBridgeConnector.cancel_query and SnowflakeBridgeConnector.cancel_query return False without attempting cancellation. Consider adding a TODO comment or raising NotImplementedError to make this explicit, or implementing actual cancellation using pg_cancel_backend() for Postgres and Snowflake's query cancellation API.

Also applies to: 355-357

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

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py` around lines 208 - 210, The
cancel_query implementations are no-ops (cancel_query in the base or connectors,
and PostgresBridgeConnector.cancel_query /
SnowflakeBridgeConnector.cancel_query) which silently return False; either make
the lack of support explicit by raising NotImplementedError or add clear TODO
comments, or implement real cancellation: for Postgres call pg_cancel_backend()
using the backend PID tracked by the query/session, and for Snowflake call its
query cancellation API; update cancel_query (and the duplicate at the other
occurrence) to either raise NotImplementedError or perform the appropriate
cancellation and return True/False based on success.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/sqlrooms-cli-ui/src/store.ts`:
- Around line 262-281: The bridge registration at module load uses
getRuntimeBridgeConfig(), createHttpDbBridge(), roomStore.getState(),
state.db.connectors.registerBridge(), and
state.db.connectors.registerConnection() and can throw during app
initialization; wrap the entire block that creates the bridge and registers
connections in a try-catch so any exception from createHttpDbBridge or the
register* calls is caught, log or report the error (including the error object)
and skip registering the bridge/connections as a safe fallback to avoid breaking
app load.

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py`:
- Around line 145-150: The test_connection function currently lets exceptions
from _connect() or the SELECT 1 query bubble up; wrap the body of
test_connection in a try/except that catches broad connection/query exceptions
(Exception) and returns False on any failure while returning True on success;
keep the existing context managers (with self._connect() as conn / with
conn.cursor() as cur) so resources close normally, and apply the same try/except
pattern to the similar connection-test block around lines 275–280 (the other
connection-testing function/section) to ensure failures return False instead of
raising.
- Around line 208-210: The cancel_query implementations are no-ops (cancel_query
in the base or connectors, and PostgresBridgeConnector.cancel_query /
SnowflakeBridgeConnector.cancel_query) which silently return False; either make
the lack of support explicit by raising NotImplementedError or add clear TODO
comments, or implement real cancellation: for Postgres call pg_cancel_backend()
using the backend PID tracked by the query/session, and for Snowflake call its
query cancellation API; update cancel_query (and the duplicate at the other
occurrence) to either raise NotImplementedError or perform the appropriate
cancellation and return True/False based on success.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 665429a and 690d069.

📒 Files selected for processing (7)
  • apps/sqlrooms-cli-ui/src/runtimeConfig.ts
  • apps/sqlrooms-cli-ui/src/store.ts
  • packages/db/src/bridge.ts
  • python/sqlrooms-cli/sqlrooms/cli.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge.py
  • python/sqlrooms-cli/sqlrooms/web/launcher.py
  • python/sqlrooms-cli/tests/test_db_bridge.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/sqlrooms-cli-ui/src/runtimeConfig.ts

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 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: 2

🧹 Nitpick comments (3)
python/sqlrooms-cli/sqlrooms/web/db_bridge.py (1)

238-240: Query cancellation not implemented for Postgres or Snowflake.

Both connectors' cancel_query methods return False without attempting cancellation. Consider adding a TODO comment or docstring noting this limitation, especially since the streaming endpoints support abort signals that trigger cancellation requests.

📝 Suggested documentation
     def cancel_query(self, query_id: str) -> bool:
+        # TODO: Implement query cancellation using pg_cancel_backend() or connection.cancel()
         _ = query_id
         return False
     def cancel_query(self, query_id: str) -> bool:
+        # TODO: Implement query cancellation using session-level ABORT or connection.cursor.abort_query()
         _ = query_id
         return False

Also applies to: 399-401

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

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py` around lines 238 - 240, The
cancel_query methods (cancel_query) for the Postgres and Snowflake connectors
currently return False without attempting cancellation; update those methods to
either implement cancellation using the connector's cancel API or at minimum add
a clear TODO/docstring stating that cancellation is not supported and why, and
document that streaming endpoints send abort signals that will trigger
cancellation requests; reference the cancel_query method in db_bridge.py (and
the duplicate occurrences around the other cancel_query implementation) and
ensure the docstring/TODO mentions Postgres/Snowflake specifically and links to
where an abort/cancel request should be handled.
packages/db/src/DbSlice.ts (1)

292-314: Consider making chunkRows configurable.

The chunkRows is hardcoded to 5000 at Line 300. Consider exposing this as an optional parameter on QueryExecutionRequest to allow tuning for different query sizes and memory constraints.

♻️ Proposed extension

In types.ts QueryExecutionRequest:

+  /**
+   * Optional row batch size for streaming materialization.
+   */
+  chunkRows?: number;

Then in DbSlice.ts:

-            chunkRows: 5000,
+            chunkRows: request.chunkRows ?? 5000,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/src/DbSlice.ts` around lines 292 - 314, The code hardcodes
chunkRows to 5000 in DbSlice.ts when calling bridge.fetchArrowStream inside the
materialization branch; make chunk size configurable by adding an optional
chunkRows?: number field to the QueryExecutionRequest type and use
request.chunkRows (falling back to 5000) when calling bridge.fetchArrowStream in
the materializeArrowStreamResult flow (symbols to change: QueryExecutionRequest,
DbSlice.ts call site where bridge.fetchArrowStream is invoked, and any caller
that constructs QueryExecutionRequest to optionally pass chunkRows); update
callers/tests to pass a tuned value where needed.
packages/db/__tests__/arrow-streaming.test.ts (1)

49-71: Unused variable payload2 in test.

payload2 is defined at Line 49 but never used in the test logic - it's only checked in an assertion that it still equals its original value, which is always true. This appears to be leftover code.

🧹 Proposed cleanup
   it('parses framed binary messages across chunk boundaries', async () => {
     const payload1 = new Uint8Array([1, 2, 3]);
-    const payload2 = new Uint8Array([4, 5]);
     const frame1 = encodeFrame({type: 'batch', payload: payload1});
     const frame2 = encodeFrame({type: 'end'});
...
     expect(parsed[1]?.type).toBe('end');
     expect(Array.from(parsed[1]?.payload ?? [])).toEqual([]);
-    expect(Array.from(payload2)).toEqual([4, 5]);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/__tests__/arrow-streaming.test.ts` around lines 49 - 71, The test
defines an unused variable payload2 and only asserts its literal value at the
end; remove the dead code by deleting the payload2 declaration and the final
expect(Array.from(payload2)).toEqual([4, 5]) assertion, or instead use payload2
in the test frames if the intent was to validate a second payload. Locate
payload2 and the trailing expect in the test (related symbols: payload2,
encodeFrame, readerFromChunks, parseFramedBinaryStream) and either remove both
lines or modify the frames/expectations so payload2 is actually sent and
asserted.
🤖 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/src/arrow-streaming.ts`:
- Around line 49-54: Wrap the JSON.parse call that creates header from
headerBytes in a try/catch to defensively handle malformed frames: decode
headerBytes with textDecoder, attempt JSON.parse inside the try, and on failure
construct a clear Error describing "malformed header JSON" that includes the
decoded header string (or a truncated safe representation) and either throw or
propagate it to the stream error/close path so the stream doesn't crash
silently; also validate the resulting header object (check header.payloadLength
is a finite number and header.type is a string) before proceeding. Use the
existing identifiers headerBytes, textDecoder, header, and JSON.parse to locate
where to add the try/catch and validation and ensure downstream code reacts to
the thrown/propagated error instead of continuing with an invalid header.

In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 377-398: The async generator _stream currently iterates the
synchronous iterator returned by db_bridge_registry.stream_arrow_batches, which
performs blocking DB I/O and will block the event loop; change the
implementation to avoid direct iteration in the event loop by either making
stream_arrow_batches async (preferred) or moving the synchronous iteration into
a thread executor: use asyncio.get_event_loop().run_in_executor to pull batches
from stream_arrow_batches (or push them into an asyncio.Queue from the thread)
and then await request.is_disconnected between yields, emitting frames with
_encode_stream_frame as before; ensure you reference and update the _stream
coroutine, db_bridge_registry.stream_arrow_batches call site,
request.is_disconnected checks, and _encode_stream_frame usage.

---

Nitpick comments:
In `@packages/db/__tests__/arrow-streaming.test.ts`:
- Around line 49-71: The test defines an unused variable payload2 and only
asserts its literal value at the end; remove the dead code by deleting the
payload2 declaration and the final expect(Array.from(payload2)).toEqual([4, 5])
assertion, or instead use payload2 in the test frames if the intent was to
validate a second payload. Locate payload2 and the trailing expect in the test
(related symbols: payload2, encodeFrame, readerFromChunks,
parseFramedBinaryStream) and either remove both lines or modify the
frames/expectations so payload2 is actually sent and asserted.

In `@packages/db/src/DbSlice.ts`:
- Around line 292-314: The code hardcodes chunkRows to 5000 in DbSlice.ts when
calling bridge.fetchArrowStream inside the materialization branch; make chunk
size configurable by adding an optional chunkRows?: number field to the
QueryExecutionRequest type and use request.chunkRows (falling back to 5000) when
calling bridge.fetchArrowStream in the materializeArrowStreamResult flow
(symbols to change: QueryExecutionRequest, DbSlice.ts call site where
bridge.fetchArrowStream is invoked, and any caller that constructs
QueryExecutionRequest to optionally pass chunkRows); update callers/tests to
pass a tuned value where needed.

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py`:
- Around line 238-240: The cancel_query methods (cancel_query) for the Postgres
and Snowflake connectors currently return False without attempting cancellation;
update those methods to either implement cancellation using the connector's
cancel API or at minimum add a clear TODO/docstring stating that cancellation is
not supported and why, and document that streaming endpoints send abort signals
that will trigger cancellation requests; reference the cancel_query method in
db_bridge.py (and the duplicate occurrences around the other cancel_query
implementation) and ensure the docstring/TODO mentions Postgres/Snowflake
specifically and links to where an abort/cancel request should be handled.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 690d069 and cfbcab7.

📒 Files selected for processing (10)
  • packages/cells/src/defaultCellRegistry.tsx
  • packages/db/__tests__/DbSlice.test.ts
  • packages/db/__tests__/arrow-streaming.test.ts
  • packages/db/package.json
  • packages/db/src/DbSlice.ts
  • packages/db/src/arrow-streaming.ts
  • packages/db/src/bridge.ts
  • packages/db/src/types.ts
  • python/sqlrooms-cli/sqlrooms/web/db_bridge.py
  • python/sqlrooms-cli/sqlrooms/web/launcher.py

Comment on lines +49 to +54
const headerBytes = buffer.slice(4, 4 + headerLen);
const header = JSON.parse(textDecoder.decode(headerBytes)) as {
type?: string;
payloadLength?: number;
error?: string;
};

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

Wrap JSON.parse in error handling for malformed frames.

If the header bytes contain invalid JSON (due to corruption or a malicious payload), JSON.parse will throw an unhandled exception that propagates up and terminates the stream without a clear error message.

🛡️ Proposed defensive handling
       const headerBytes = buffer.slice(4, 4 + headerLen);
-      const header = JSON.parse(textDecoder.decode(headerBytes)) as {
-        type?: string;
-        payloadLength?: number;
-        error?: string;
-      };
+      let header: {type?: string; payloadLength?: number; error?: string};
+      try {
+        header = JSON.parse(textDecoder.decode(headerBytes));
+      } catch {
+        yield {type: 'error', payload: new Uint8Array(0), error: 'Malformed frame header'};
+        return;
+      }
📝 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
const headerBytes = buffer.slice(4, 4 + headerLen);
const header = JSON.parse(textDecoder.decode(headerBytes)) as {
type?: string;
payloadLength?: number;
error?: string;
};
const headerBytes = buffer.slice(4, 4 + headerLen);
let header: {type?: string; payloadLength?: number; error?: string};
try {
header = JSON.parse(textDecoder.decode(headerBytes));
} catch {
yield {type: 'error', payload: new Uint8Array(0), error: 'Malformed frame header'};
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/src/arrow-streaming.ts` around lines 49 - 54, Wrap the JSON.parse
call that creates header from headerBytes in a try/catch to defensively handle
malformed frames: decode headerBytes with textDecoder, attempt JSON.parse inside
the try, and on failure construct a clear Error describing "malformed header
JSON" that includes the decoded header string (or a truncated safe
representation) and either throw or propagate it to the stream error/close path
so the stream doesn't crash silently; also validate the resulting header object
(check header.payloadLength is a finite number and header.type is a string)
before proceeding. Use the existing identifiers headerBytes, textDecoder,
header, and JSON.parse to locate where to add the try/catch and validation and
ensure downstream code reacts to the thrown/propagated error instead of
continuing with an invalid header.

Comment on lines +377 to +398
async def _stream():
try:
for batch in self.db_bridge_registry.stream_arrow_batches(
connection_id=connection_id,
sql=sql,
chunk_rows=chunk_rows,
query_id=query_id,
):
if await request.is_disconnected():
break
yield _encode_stream_frame(
"batch", query_id=query_id, payload=batch
)
yield _encode_stream_frame("end", query_id=query_id)
except UnknownBridgeConnectionError as exc:
yield _encode_stream_frame(
"error", query_id=query_id, error=str(exc)
)
except Exception as exc:
yield _encode_stream_frame(
"error", query_id=query_id, error=str(exc)
)

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

Streaming loop iterates synchronous generator in async handler.

The stream_arrow_batches returns a synchronous Iterable[bytes] that performs database I/O. Iterating it directly in the async _stream() generator blocks the event loop during each batch fetch. Consider wrapping in run_in_executor or making the connector methods async.

🔧 Potential mitigation pattern
import asyncio
from concurrent.futures import ThreadPoolExecutor

_executor = ThreadPoolExecutor(max_workers=4)

async def _stream():
    loop = asyncio.get_event_loop()
    try:
        # Run synchronous iterator in thread pool
        batches = await loop.run_in_executor(
            _executor,
            lambda: list(self.db_bridge_registry.stream_arrow_batches(
                connection_id=connection_id,
                sql=sql,
                chunk_rows=chunk_rows,
                query_id=query_id,
            ))
        )
        for batch in batches:
            if await request.is_disconnected():
                break
            yield _encode_stream_frame("batch", query_id=query_id, payload=batch)
        yield _encode_stream_frame("end", query_id=query_id)
    except Exception as exc:
        yield _encode_stream_frame("error", query_id=query_id, error=str(exc))

Note: This buffers all batches, losing streaming benefit. A proper fix requires making the connector async or using an async queue pattern.

🤖 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 377 - 398, The
async generator _stream currently iterates the synchronous iterator returned by
db_bridge_registry.stream_arrow_batches, which performs blocking DB I/O and will
block the event loop; change the implementation to avoid direct iteration in the
event loop by either making stream_arrow_batches async (preferred) or moving the
synchronous iteration into a thread executor: use
asyncio.get_event_loop().run_in_executor to pull batches from
stream_arrow_batches (or push them into an asyncio.Queue from the thread) and
then await request.is_disconnected between yields, emitting frames with
_encode_stream_frame as before; ensure you reference and update the _stream
coroutine, db_bridge_registry.stream_arrow_batches call site,
request.is_disconnected checks, and _encode_stream_frame usage.

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 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/canvas/src/CanvasSlice.ts (1)

157-164: ⚠️ Potential issue | 🟡 Minor

Add error handling for the fire-and-forget promise.

Calling runAllCellsCascade without await and without a .catch() means any rejection becomes an unhandled promise rejection. Consider adding error handling to log or gracefully handle failures.

Additionally, since nothing is awaited anymore, the async keyword and Promise<void> return type are misleading—callers might expect initialization to be complete when the returned promise resolves.

🛡️ Proposed fix to add error handling
         async initialize() {
           const sheetId =
             get().cells.config.currentSheetId ||
             get().cells.config.sheetOrder[0];
           if (!sheetId) return;
           // don't await this - it will block the UI
-          get().cells.runAllCellsCascade(sheetId);
+          get().cells.runAllCellsCascade(sheetId).catch((err) => {
+            console.error('Failed to run cells cascade during initialization:', err);
+          });
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/canvas/src/CanvasSlice.ts` around lines 157 - 164, The initialize
method currently fires get().cells.runAllCellsCascade(sheetId) without awaiting
or handling errors and remains declared async; change it to a non-async method
(remove the async keyword and any Promise<void> return) and call
runAllCellsCascade as a fire-and-forget with a .catch handler that logs the
error (e.g., get().cells.runAllCellsCascade(sheetId).catch(err => /* log using
your logger or console.error */)), so failures are not lost as unhandled promise
rejections and the method signature correctly reflects its synchronous return.
♻️ Duplicate comments (1)
python/sqlrooms-cli/sqlrooms/web/db_bridge.py (1)

81-82: ⚠️ Potential issue | 🟠 Major

Duplicate connection_id registration still silently overwrites.

This issue was flagged in a previous review. register() silently replaces an existing connector with the same ID, which can hide configuration mistakes and unexpectedly reroute queries.

🔧 Proposed fix
 def register(self, connector: DbBridgeConnector) -> None:
+    existing = self._connectors.get(connector.connection_id)
+    if existing is not None and existing is not connector:
+        raise ValueError(
+            f"Duplicate DB bridge connection_id: {connector.connection_id}"
+        )
     self._connectors[connector.connection_id] = connector
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py` around lines 81 - 82, The
register method currently overwrites an existing connector silently; change
register(self, connector: DbBridgeConnector) to check self._connectors for
connector.connection_id and refuse/raise an explicit error (e.g., ValueError or
KeyError) or return a clear failure if the ID already exists, so duplicate
connection_id registrations are detected instead of silently replacing the
existing entry; ensure the check references self._connectors and
connector.connection_id and update any callers/tests to expect the new behavior.
🧹 Nitpick comments (6)
apps/sqlrooms-cli-ui/src/components/AssistantDrawer.tsx (1)

143-143: Minor: Trailing whitespace after closing div.

Line 143 has a trailing {' '} JSX expression that appears unnecessary.

🧹 Remove trailing whitespace
-              </div>{' '}
+              </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/sqlrooms-cli-ui/src/components/AssistantDrawer.tsx` at line 143, In
AssistantDrawer.tsx, remove the unnecessary trailing JSX whitespace expression
{' '} after the closing div on line with the JSX fragment (inside the
AssistantDrawer component render/return) — locate the closing </div> near the
referenced snippet and delete the {' '} token so the markup has no trailing
whitespace expression.
python/sqlrooms-cli/tests/test_cli.py (1)

111-116: Consider using pytest.raises for cleaner exception assertions.

The try/except/else pattern works but pytest.raises is more idiomatic in pytest.

♻️ Suggested refactor
-    try:
-        _load_connector_config([config_path])
-    except RuntimeError as exc:
-        assert "Duplicate connector id" in str(exc)
-    else:
-        raise AssertionError("Expected duplicate connector id failure")
+    with pytest.raises(RuntimeError, match="Duplicate connector id"):
+        _load_connector_config([config_path])
🤖 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 111 - 116, Replace the
manual try/except/else assertion with pytest's context manager: in the test
calling _load_connector_config([config_path]) use pytest.raises(RuntimeError) as
excinfo to assert the exception is raised, then assert "Duplicate connector id"
in str(excinfo.value); update the test import to include pytest if not present
and remove the try/except/else block around the _load_connector_config call.
python/sqlrooms-cli/README.md (1)

133-149: Postgres and Snowflake sections show identical commands.

Both connector sections show the exact same CLI command with no differentiation. Consider adding comments or notes explaining that connectors are configured via sqlrooms.toml, or show example config snippets inline.

📝 Suggested improvement
 ### Postgres

+Ensure your `sqlrooms.toml` includes a Postgres connector:
+
+```toml
+[[connectors]]
+id = "postgres-local"
+engine = "postgres"
+dsn = "postgresql://user:pass@localhost:5432/db"
+```
+
 ```bash
 uvx sqlrooms \
   ./sqlrooms.db \
   --ws-port 4000 \
   --port 4173
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @python/sqlrooms-cli/README.md around lines 133 - 149, The Postgres and
Snowflake sections currently show identical CLI usage; update the README to
clarify that connector selection is done via the sqlrooms.toml config rather
than differing CLI flags: in the Postgres and Snowflake subsections add a short
note stating "connectors are configured in sqlrooms.toml" and include an example
[[connectors]] TOML snippet showing engine = "postgres" with a DSN and engine =
"snowflake" with appropriate fields, and either keep the shared uvx sqlrooms
command with that explanatory note or show one identical command plus the
distinct example config per connector to make the difference explicit.


</details>

</blockquote></details>
<details>
<summary>python/sqlrooms-cli/sqlrooms/web/db_bridge.py (2)</summary><blockquote>

`237-253`: **`cancel_query` is a no-op for PostgresBridgeConnector.**

The method ignores `query_id` and always returns `False`. While this may be acceptable for an initial implementation, consider documenting this limitation or implementing actual cancellation using `pg_cancel_backend` if query IDs are tracked.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @python/sqlrooms-cli/sqlrooms/web/db_bridge.py around lines 237 - 253, The
cancel_query method is a no-op; either document this limitation or implement
real cancellation: when running stream_arrow_batches (or other query-starting
methods) capture the Postgres backend PID (pg_backend_pid()) and associate it
with the query_id (e.g., in an in-memory dict keyed by query_id), then implement
cancel_query(query_id) to look up the PID and call pg_cancel_backend(pid) (using
a new connection via _connect()) and return True/False based on the result;
ensure cleanup of the mapping when the query completes or is canceled.


</details>

---

`54-69`: **Memory concern: Full result set materialized before Arrow serialization.**

`_rows_to_arrow_bytes` loads all rows into memory via `_rows_to_json_rows` before creating the Arrow table. For large result sets, this could cause significant memory pressure. Consider streaming Arrow batches directly from cursor chunks in `stream_arrow_batches`.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @python/sqlrooms-cli/sqlrooms/web/db_bridge.py around lines 54 - 69,
_rows_to_arrow_bytes currently materializes the entire result set by calling
_rows_to_json_rows and pa.Table.from_pylist, which risks OOM for large queries;
change the implementation so it does not build the full JSON list but instead
accepts/iterates cursor chunks (or expose a helper used by stream_arrow_batches)
and writes Arrow output incrementally using RecordBatch(s) (e.g., build
pa.RecordBatch from each chunk and use
pa.ipc.new_stream/pa.RecordBatchStreamWriter to write batches to a
BufferOutputStream), ensuring the schema is derived up front (or from the first
chunk) and handling the empty-columns case similarly to the current columns ->
pa.null() behavior; update or create an overload used by stream_arrow_batches so
rows can be streamed rather than fully materialized by _rows_to_json_rows and
pa.Table.from_pylist.


</details>

</blockquote></details>
<details>
<summary>apps/sqlrooms-cli-ui/src/store.ts (1)</summary><blockquote>

`239-240`: **Avoid `as any` type assertion for `defaultProvider`.**

The `as any` cast bypasses type checking. Consider using proper typing or a more specific assertion if the provider string needs to conform to a union type.


<details>
<summary>♻️ Suggested improvement</summary>

If `defaultProvider` must be a specific union type, validate it at runtime:

```diff
-        defaultProvider: defaultProviderFromConfig as any,
+        defaultProvider: defaultProviderFromConfig,

If the type is genuinely incompatible, consider updating AiSliceConfig to accept string or adding a type guard.

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

In `@apps/sqlrooms-cli-ui/src/store.ts` around lines 239 - 240, The assignment
uses a blanket `as any` cast for defaultProvider, which bypasses type safety;
remove the `as any` and either (a) narrow the value by casting to the specific
union (e.g., validate with an isValidProvider(value) type guard and assign only
if it passes, falling back to a safe default), or (b) update the
AiSliceConfig/defaultProvider type to accept string if appropriate; locate the
code where defaultProvider: defaultProviderFromConfig is set and implement a
runtime check (isValidProvider or a small switch/lookup) or adjust the type
definitions so the assignment is type-correct without `as any`.
🤖 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/README.md`:
- Around line 92-93: Replace the plaintext password example by instructing users
to load credentials from environment variables or a secrets manager instead of
assigning password = "your-password"; reference the existing api_key_env pattern
and show that password should be read from an env var (e.g., PASSWORD or
SNOWFLAKE_PASSWORD) or a secret retrieval call, and add a note suggesting
passwordless options like authenticator = "externalbrowser" as an alternative
for the Snowflake connection example (also mention warehouse remains unchanged).

---

Outside diff comments:
In `@packages/canvas/src/CanvasSlice.ts`:
- Around line 157-164: The initialize method currently fires
get().cells.runAllCellsCascade(sheetId) without awaiting or handling errors and
remains declared async; change it to a non-async method (remove the async
keyword and any Promise<void> return) and call runAllCellsCascade as a
fire-and-forget with a .catch handler that logs the error (e.g.,
get().cells.runAllCellsCascade(sheetId).catch(err => /* log using your logger or
console.error */)), so failures are not lost as unhandled promise rejections and
the method signature correctly reflects its synchronous return.

---

Duplicate comments:
In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py`:
- Around line 81-82: The register method currently overwrites an existing
connector silently; change register(self, connector: DbBridgeConnector) to check
self._connectors for connector.connection_id and refuse/raise an explicit error
(e.g., ValueError or KeyError) or return a clear failure if the ID already
exists, so duplicate connection_id registrations are detected instead of
silently replacing the existing entry; ensure the check references
self._connectors and connector.connection_id and update any callers/tests to
expect the new behavior.

---

Nitpick comments:
In `@apps/sqlrooms-cli-ui/src/components/AssistantDrawer.tsx`:
- Line 143: In AssistantDrawer.tsx, remove the unnecessary trailing JSX
whitespace expression {' '} after the closing div on line with the JSX fragment
(inside the AssistantDrawer component render/return) — locate the closing </div>
near the referenced snippet and delete the {' '} token so the markup has no
trailing whitespace expression.

In `@apps/sqlrooms-cli-ui/src/store.ts`:
- Around line 239-240: The assignment uses a blanket `as any` cast for
defaultProvider, which bypasses type safety; remove the `as any` and either (a)
narrow the value by casting to the specific union (e.g., validate with an
isValidProvider(value) type guard and assign only if it passes, falling back to
a safe default), or (b) update the AiSliceConfig/defaultProvider type to accept
string if appropriate; locate the code where defaultProvider:
defaultProviderFromConfig is set and implement a runtime check (isValidProvider
or a small switch/lookup) or adjust the type definitions so the assignment is
type-correct without `as any`.

In `@python/sqlrooms-cli/README.md`:
- Around line 133-149: The Postgres and Snowflake sections currently show
identical CLI usage; update the README to clarify that connector selection is
done via the sqlrooms.toml config rather than differing CLI flags: in the
Postgres and Snowflake subsections add a short note stating "connectors are
configured in sqlrooms.toml" and include an example [[connectors]] TOML snippet
showing engine = "postgres" with a DSN and engine = "snowflake" with appropriate
fields, and either keep the shared uvx sqlrooms command with that explanatory
note or show one identical command plus the distinct example config per
connector to make the difference explicit.

In `@python/sqlrooms-cli/sqlrooms/web/db_bridge.py`:
- Around line 237-253: The cancel_query method is a no-op; either document this
limitation or implement real cancellation: when running stream_arrow_batches (or
other query-starting methods) capture the Postgres backend PID
(pg_backend_pid()) and associate it with the query_id (e.g., in an in-memory
dict keyed by query_id), then implement cancel_query(query_id) to look up the
PID and call pg_cancel_backend(pid) (using a new connection via _connect()) and
return True/False based on the result; ensure cleanup of the mapping when the
query completes or is canceled.
- Around line 54-69: _rows_to_arrow_bytes currently materializes the entire
result set by calling _rows_to_json_rows and pa.Table.from_pylist, which risks
OOM for large queries; change the implementation so it does not build the full
JSON list but instead accepts/iterates cursor chunks (or expose a helper used by
stream_arrow_batches) and writes Arrow output incrementally using RecordBatch(s)
(e.g., build pa.RecordBatch from each chunk and use
pa.ipc.new_stream/pa.RecordBatchStreamWriter to write batches to a
BufferOutputStream), ensuring the schema is derived up front (or from the first
chunk) and handling the empty-columns case similarly to the current columns ->
pa.null() behavior; update or create an overload used by stream_arrow_batches so
rows can be streamed rather than fully materialized by _rows_to_json_rows and
pa.Table.from_pylist.

In `@python/sqlrooms-cli/tests/test_cli.py`:
- Around line 111-116: Replace the manual try/except/else assertion with
pytest's context manager: in the test calling
_load_connector_config([config_path]) use pytest.raises(RuntimeError) as excinfo
to assert the exception is raised, then assert "Duplicate connector id" in
str(excinfo.value); update the test import to include pytest if not present and
remove the try/except/else block around the _load_connector_config call.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfbcab7 and b97f91b.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • apps/sqlrooms-cli-ui/src/components/AssistantDrawer.tsx
  • apps/sqlrooms-cli-ui/src/runtimeConfig.ts
  • apps/sqlrooms-cli-ui/src/store.ts
  • packages/canvas/src/CanvasSlice.ts
  • packages/db/__tests__/DbSlice.test.ts
  • packages/db/src/DbSlice.ts
  • packages/db/src/helpers.ts
  • packages/db/src/types.ts
  • python/sqlrooms-cli/README.md
  • python/sqlrooms-cli/cli
  • python/sqlrooms-cli/package.json
  • python/sqlrooms-cli/pyproject.toml
  • python/sqlrooms-cli/sqlrooms/cli.py
  • python/sqlrooms-cli/sqlrooms/web/db_bridge.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 skipped from review as they are similar to previous changes (2)
  • python/sqlrooms-cli/tests/test_db_bridge.py
  • apps/sqlrooms-cli-ui/src/runtimeConfig.ts

Comment on lines +92 to +93
password = "your-password"
warehouse = "your-warehouse"

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

Security: Avoid showing plaintext password in documentation example.

The example shows password = "your-password" in plaintext. Consider recommending environment variable usage or referencing a secrets manager, similar to how api_key_env is used for AI providers.

📝 Suggested documentation improvement
-password = "your-password"
+# Use environment variable for password:
+# password_env = "SNOWFLAKE_PASSWORD"
+# Or set directly (not recommended for shared configs):
+# password = "your-password"

Alternatively, add a note about using authenticator = "externalbrowser" or other passwordless auth methods for Snowflake.

📝 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
password = "your-password"
warehouse = "your-warehouse"
# Use environment variable for password:
# password_env = "SNOWFLAKE_PASSWORD"
# Or set directly (not recommended for shared configs):
# password = "your-password"
warehouse = "your-warehouse"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/sqlrooms-cli/README.md` around lines 92 - 93, Replace the plaintext
password example by instructing users to load credentials from environment
variables or a secrets manager instead of assigning password = "your-password";
reference the existing api_key_env pattern and show that password should be read
from an env var (e.g., PASSWORD or SNOWFLAKE_PASSWORD) or a secret retrieval
call, and add a note suggesting passwordless options like authenticator =
"externalbrowser" as an alternative for the Snowflake connection example (also
mention warehouse remains unchanged).

Signed-off-by: Ilya Boyandin <[email protected]>
@ilyabo
ilyabo merged commit 1ccbe61 into main Feb 28, 2026
6 checks passed
@ilyabo
ilyabo deleted the cursor/snowflake-connector-integration-f6d5 branch February 28, 2026 22:30
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.

2 participants