Skip to content

Commit 7ba40e4

Browse files
committed
Address copilot review
1 parent ed6d9c3 commit 7ba40e4

3 files changed

Lines changed: 31 additions & 9 deletions

File tree

src/dbt_mcp/dbt_admin/run_artifacts/extractors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ def extract_from_catalog(data: dict[str, Any]) -> dict[str, list[tuple]]:
409409
col.get("name") or col_name,
410410
col.get("index"),
411411
col.get("type"),
412-
col.get("comment") or "",
412+
col.get("comment"),
413413
)
414414
)
415415

src/dbt_mcp/dbt_admin/run_artifacts/store.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import logging
8+
import re
89
import time
910
from typing import Any
1011

@@ -19,6 +20,7 @@
1920
from dbt_mcp.errors.artifact_search import (
2021
ArtifactNotLoadedError,
2122
ArtifactQueryError,
23+
ArtifactSearchError,
2224
ArtifactValidationError,
2325
)
2426

@@ -56,8 +58,16 @@ class ArtifactStore:
5658

5759
def __init__(self) -> None:
5860
self.conn = duckdb.connect()
59-
self.conn.execute("INSTALL fts;")
60-
self.conn.execute("LOAD fts;")
61+
try:
62+
self.conn.execute("LOAD fts;")
63+
except duckdb.Error:
64+
try:
65+
self.conn.execute("INSTALL fts;")
66+
self.conn.execute("LOAD fts;")
67+
except duckdb.Error as e:
68+
raise ArtifactSearchError(
69+
f"Failed to load the DuckDB FTS extension: {e}"
70+
) from e
6171
self._loaded_tables: set[str] = set()
6272
self._tables_created: bool = False
6373
self._pending_index_tables: set[str] = set()
@@ -277,7 +287,7 @@ def _build_indexes(self, config: TableConfig) -> None:
277287
)
278288

279289
for col in config.index_columns:
280-
idx_name = f"idx_{config.table_name[:4]}_{col}"
290+
idx_name = f"idx_{config.table_name}_{col}"
281291
self.conn.execute(
282292
f"CREATE INDEX IF NOT EXISTS {idx_name} "
283293
f'ON {config.table_name}("{col}");'
@@ -327,7 +337,10 @@ def describe_table(self, table_name: str) -> list[dict[str, str]]:
327337

328338
def query(self, sql: str) -> list[dict[str, Any]]:
329339
"""Execute a read-only SQL query. Results capped at 500 rows."""
330-
tokens = sql.strip().upper().split()
340+
sanitized = _strip_sql_comments(sql)
341+
if ";" in sanitized:
342+
raise ArtifactQueryError("Multi-statement queries are not allowed.")
343+
tokens = sanitized.strip().upper().split()
331344
for token in tokens:
332345
if token in READONLY_BLOCKED:
333346
raise ArtifactQueryError(
@@ -391,6 +404,13 @@ def _validate_table_name(self, table_name: str) -> None:
391404
)
392405

393406

407+
def _strip_sql_comments(sql: str) -> str:
408+
"""Remove SQL block (/* */) and line (--) comments."""
409+
sql = re.sub(r"/\*.*?\*/", " ", sql, flags=re.DOTALL)
410+
sql = re.sub(r"--[^\n]*", " ", sql)
411+
return sql
412+
413+
394414
def _serialize(val: Any) -> Any:
395415
"""Ensure values are JSON-serializable."""
396416
if val is None or isinstance(val, (str, int, float, bool)):

tests/unit/dbt_admin/run_artifacts/test_store.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,14 @@ def test_mutating_keywords_are_blocked(self, store: ArtifactStore) -> None:
133133
with pytest.raises(ArtifactQueryError, match="Blocked keyword"):
134134
store.query(f"{keyword} something")
135135

136-
def test_mutating_keywords_blocked_anywhere_in_query(
137-
self, store: ArtifactStore
138-
) -> None:
139-
with pytest.raises(ArtifactQueryError, match="Blocked keyword"):
136+
def test_multi_statement_query_blocked(self, store: ArtifactStore) -> None:
137+
with pytest.raises(ArtifactQueryError, match="Multi-statement"):
140138
store.query("SELECT 1; DROP TABLE nodes")
141139

140+
def test_comment_bypass_blocked(self, store: ArtifactStore) -> None:
141+
with pytest.raises(ArtifactQueryError, match="Multi-statement"):
142+
store.query("SELECT 1;/**/DROP TABLE nodes")
143+
142144
def test_invalid_sql_raises(self, store: ArtifactStore) -> None:
143145
with pytest.raises(ArtifactQueryError, match="Query failed"):
144146
store.query("SELECT * FROM table_that_does_not_exist_xyz")

0 commit comments

Comments
 (0)