Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions redash/query_runner/duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ def configuration_schema(cls):
"title": "Database Path",
"default": ":memory:",
},
"extensions": {"type": "string", "title": "Extensions (comma separated)"},
"extensions": {
"type": "string",
"title": "Extensions (comma separated)",
},
},
"order": ["dbpath", "extensions"],
"required": ["dbpath"],
Expand Down Expand Up @@ -112,7 +115,7 @@ def run_query(self, query, user) -> tuple:

def get_schema(self, get_stats=False) -> list:
tables_query = """
SELECT table_schema, table_name FROM information_schema.tables
SELECT table_catalog, table_schema, table_name FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'pg_catalog');
"""
tables_results, error = self.run_query(tables_query, None)
Expand All @@ -121,10 +124,21 @@ def get_schema(self, get_stats=False) -> list:

schema = {}
for table_row in tables_results["rows"]:
full_table_name = f"{table_row['table_schema']}.{table_row['table_name']}"
schema[full_table_name] = {"name": full_table_name, "columns": []}
# Include catalog (database) in the full table name for MotherDuck support
catalog = table_row["table_catalog"]
schema_name = table_row["table_schema"]
table_name = table_row["table_name"]

# Skip catalog prefix for default local databases (memory, temp)
# but include it for MotherDuck and attached databases
if catalog.lower() in ("memory", "temp", "system"):
full_table_name = f"{schema_name}.{table_name}"
describe_query = f'DESCRIBE "{schema_name}"."{table_name}";'
else:
full_table_name = f"{catalog}.{schema_name}.{table_name}"
describe_query = f'DESCRIBE "{catalog}"."{schema_name}"."{table_name}";'

describe_query = f'DESCRIBE "{table_row["table_schema"]}"."{table_row["table_name"]}";'
schema[full_table_name] = {"name": full_table_name, "columns": []}
columns_results, error = self.run_query(describe_query, None)
if error:
logger.warning("Failed to describe table %s: %s", full_table_name, error)
Expand Down
51 changes: 49 additions & 2 deletions tests/query_runner/test_duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ def test_simple_schema_build(self, mock_run_query) -> None:
# Simulate queries: first for tables, then for DESCRIBE
mock_run_query.side_effect = [
(
{"rows": [{"table_schema": "main", "table_name": "users"}]},
{
"rows": [
{
"table_catalog": "memory",
"table_schema": "main",
"table_name": "users",
}
]
},
None,
),
(
Expand All @@ -40,7 +48,15 @@ def test_struct_column_expansion(self, mock_run_query) -> None:
# First call to run_query -> tables list
mock_run_query.side_effect = [
(
{"rows": [{"table_schema": "main", "table_name": "events"}]},
{
"rows": [
{
"table_catalog": "memory",
"table_schema": "main",
"table_name": "events",
}
]
},
None,
),
# Second call -> DESCRIBE output
Expand Down Expand Up @@ -99,6 +115,37 @@ def test_nested_struct_expansion(self) -> None:
assert "info.tags.primary_tag" in colnames
assert "info.tags.secondary_tag" in colnames

@patch.object(DuckDB, "run_query")
def test_motherduck_catalog_included(self, mock_run_query) -> None:
# Test that non-default catalogs (like MotherDuck) include catalog in name
mock_run_query.side_effect = [
(
{
"rows": [
{
"table_catalog": "sample_data",
"table_schema": "kaggle",
"table_name": "movies",
}
]
},
None,
),
(
{
"rows": [
{"column_name": "title", "column_type": "VARCHAR"},
]
},
None,
),
]

schema = self.runner.get_schema()
self.assertEqual(len(schema), 1)
# Should include catalog name for non-default catalogs
self.assertEqual(schema[0]["name"], "sample_data.kaggle.movies")

@patch.object(DuckDB, "run_query")
def test_error_propagation(self, mock_run_query) -> None:
mock_run_query.return_value = (None, "boom")
Expand Down
Loading