Compare commits

...

1 Commits

Author SHA1 Message Date
Beto Dealmeida
d3c695a876 fix(semantic layers): guards for zero rows 2026-06-30 20:01:16 -04:00
2 changed files with 59 additions and 0 deletions

View File

@@ -121,6 +121,7 @@ def get_results(query_object: QueryObject) -> QueryResult:
# Step 2: Execute the main query (first in the list)
main_query = queries[0]
main_result = dispatcher(main_query)
main_result = _coerce_empty_result(main_result, main_query)
main_df = stringify_extension_columns(main_result.results).to_pandas()
@@ -152,6 +153,7 @@ def get_results(query_object: QueryObject) -> QueryResult:
):
# Execute the offset query
result = dispatcher(offset_query)
result = _coerce_empty_result(result, offset_query)
# Add this query's requests to the collection
all_requests.extend(result.requests)
@@ -207,6 +209,32 @@ def get_results(query_object: QueryObject) -> QueryResult:
)
def _coerce_empty_result(
semantic_result: SemanticResult,
query: SemanticQuery,
) -> SemanticResult:
"""
Guard against ``SemanticResult.results is None``.
Some semantic-layer driver implementations return ``None`` when a query
produces zero rows (for example, ``snowflake.connector``'s
``fetch_arrow_all`` does this). Downstream consumers expect an Arrow table,
so build an empty one with the columns implied by the query's dimensions
and metrics.
"""
if semantic_result.results is not None:
return semantic_result
columns = {
**{dim.name: pa.array([], type=dim.type) for dim in query.dimensions},
**{metric.name: pa.array([], type=metric.type) for metric in query.metrics},
}
return SemanticResult(
requests=semantic_result.requests,
results=pa.table(columns),
)
def map_semantic_result_to_query_result(
semantic_result: SemanticResult,
query_object: ValidatedQueryObject,

View File

@@ -1694,6 +1694,37 @@ def test_get_results_with_multiple_dimensions(
pd.testing.assert_frame_equal(result.df, expected_df)
def test_get_results_handles_none_results(
mock_datasource: MagicMock,
mocker: MockerFixture,
) -> None:
"""
Some semantic-layer driver implementations return ``SemanticResult(results=None)``
when a query produces zero rows (for example, ``snowflake.connector``'s
``fetch_arrow_all``). ``get_results`` must coerce that to an empty Arrow table
rather than crashing on ``None.to_pandas()``.
"""
mock_result = SemanticResult(
requests=[SemanticRequest(type="SQL", definition="SELECT 1")],
results=None,
)
mock_datasource.implementation.get_table = mocker.Mock(return_value=mock_result)
query_object = ValidatedQueryObject(
datasource=mock_datasource,
from_dttm=datetime(2025, 10, 15),
to_dttm=datetime(2025, 10, 22),
metrics=["total_sales"],
columns=["category"],
)
result = get_results(query_object)
assert result.df is not None
assert result.df.empty
assert set(result.df.columns) == {"category", "total_sales"}
def test_get_results_no_datasource() -> None:
"""
Test that get_results raises error when datasource is missing.