Repository navigation
feat: Snowflake connector integration - #401
Conversation
Co-authored-by: Ilya Boyandin <[email protected]>
|
Cursor Agent can help with this pull request. Just |
📝 WalkthroughWalkthroughAdds 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
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
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 sharedRuntimeDbBridgeConfig/RuntimeDbBridgeConnectiontype 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_idtwice 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (10)
apps/sqlrooms-cli-ui/package.jsonapps/sqlrooms-cli-ui/src/runtimeConfig.tsapps/sqlrooms-cli-ui/src/store.tspackages/db/src/bridge.tspython/sqlrooms-cli/README.mdpython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/db_bridge.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/sqlrooms-cli/tests/test_db_bridge.pypython/sqlrooms-cli/tests/test_launcher.py
| def register(self, connector: DbBridgeConnector) -> None: | ||
| self._connectors[connector.connection_id] = connector | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| ok = self.db_bridge_registry.test_connection(connection_id) | ||
| return {"ok": bool(ok)} |
There was a problem hiding this comment.
🧩 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 -30Repository: 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.
| 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.
| except UnknownBridgeConnectionError as exc: | ||
| return {"ok": False, "error": str(exc)} | ||
| except Exception as exc: | ||
| return {"ok": False, "error": str(exc)} |
There was a problem hiding this comment.
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]>
There was a problem hiding this comment.
🧹 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
createHttpDbBridgeor 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 theSELECT 1query fails (e.g., bad credentials, network issues), the exception propagates to the caller. Thetest_connectionsemantics suggest returningFalseon 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 FalseAlso 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_queryandSnowflakeBridgeConnector.cancel_queryreturnFalsewithout attempting cancellation. Consider adding a TODO comment or raisingNotImplementedErrorto make this explicit, or implementing actual cancellation usingpg_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
📒 Files selected for processing (7)
apps/sqlrooms-cli-ui/src/runtimeConfig.tsapps/sqlrooms-cli-ui/src/store.tspackages/db/src/bridge.tspython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/db_bridge.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/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]>
There was a problem hiding this comment.
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_querymethods returnFalsewithout 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 Falsedef cancel_query(self, query_id: str) -> bool: + # TODO: Implement query cancellation using session-level ABORT or connection.cursor.abort_query() _ = query_id return FalseAlso 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 makingchunkRowsconfigurable.The
chunkRowsis hardcoded to 5000 at Line 300. Consider exposing this as an optional parameter onQueryExecutionRequestto allow tuning for different query sizes and memory constraints.♻️ Proposed extension
In
types.tsQueryExecutionRequest:+ /** + * 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 variablepayload2in test.
payload2is 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
📒 Files selected for processing (10)
packages/cells/src/defaultCellRegistry.tsxpackages/db/__tests__/DbSlice.test.tspackages/db/__tests__/arrow-streaming.test.tspackages/db/package.jsonpackages/db/src/DbSlice.tspackages/db/src/arrow-streaming.tspackages/db/src/bridge.tspackages/db/src/types.tspython/sqlrooms-cli/sqlrooms/web/db_bridge.pypython/sqlrooms-cli/sqlrooms/web/launcher.py
| const headerBytes = buffer.slice(4, 4 + headerLen); | ||
| const header = JSON.parse(textDecoder.decode(headerBytes)) as { | ||
| type?: string; | ||
| payloadLength?: number; | ||
| error?: string; | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
| 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) | ||
| ) |
There was a problem hiding this comment.
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]>
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
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 | 🟡 MinorAdd error handling for the fire-and-forget promise.
Calling
runAllCellsCascadewithoutawaitand 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
asynckeyword andPromise<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 | 🟠 MajorDuplicate
connection_idregistration 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 usingpytest.raisesfor cleaner exception assertions.The try/except/else pattern works but
pytest.raisesis 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.mdaround 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.pyaround 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.pyaround 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
AiSliceConfigto acceptstringor 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
⛔ Files ignored due to path filters (1)
python/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (18)
apps/sqlrooms-cli-ui/src/components/AssistantDrawer.tsxapps/sqlrooms-cli-ui/src/runtimeConfig.tsapps/sqlrooms-cli-ui/src/store.tspackages/canvas/src/CanvasSlice.tspackages/db/__tests__/DbSlice.test.tspackages/db/src/DbSlice.tspackages/db/src/helpers.tspackages/db/src/types.tspython/sqlrooms-cli/README.mdpython/sqlrooms-cli/clipython/sqlrooms-cli/package.jsonpython/sqlrooms-cli/pyproject.tomlpython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/db_bridge.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/sqlrooms-cli/tests/test_cli.pypython/sqlrooms-cli/tests/test_db_bridge.pypython/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
| password = "your-password" | ||
| warehouse = "your-warehouse" |
There was a problem hiding this comment.
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.
| 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]>
Add a scalable Python connector framework with Snowflake support to
DbSlicefor use in notebook cells.The existing
DbSliceonly 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.Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores