Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests for custom offset window #1585

Open
wants to merge 6 commits into
base: court/custom-offset6.5
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -860,3 +860,24 @@ metric:
- name: instant_bookings
alias: shared_alias
---
metric:
name: bookings_offset_one_martian_day
description: bookings offset by one martian_day
type: derived
type_params:
expr: bookings
metrics:
- name: bookings
offset_window: 1 martian_day
---
metric:
name: bookings_martian_day_over_martian_day
description: bookings growth martian day over martian day
type: derived
type_params:
expr: bookings - bookings_offset / NULLIF(bookings_offset, 0)
metrics:
- name: bookings
offset_window: 1 martian_day
alias: bookings_offset
- name: bookings
62 changes: 62 additions & 0 deletions tests_metricflow/integration/query_output/test_offset_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import pytest
from _pytest.fixtures import FixtureRequest
from dbt_semantic_interfaces.type_enums.date_part import DatePart
from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity
from metricflow_semantics.specs.query_param_implementations import OrderByParameter, TimeDimensionParameter
from metricflow_semantics.test_helpers.config_helpers import MetricFlowTestConfiguration

from metricflow.engine.metricflow_engine import MetricFlowQueryRequest
Expand Down Expand Up @@ -58,3 +61,62 @@ def test_offset_to_grain_with_multiple_granularities( # noqa: D103
snapshot_str=query_result.result_df.text_format(),
sql_engine=sql_client.sql_engine_type,
)


@pytest.mark.sql_engine_snapshot
def test_custom_offset_window_with_base_grain(
request: FixtureRequest,
mf_test_configuration: MetricFlowTestConfiguration,
sql_client: SqlClient,
it_helpers: IntegrationTestHelpers,
) -> None:
"""Gives a side by side comparison of bookings and bookings_offset_one_martian_day."""
query_result = it_helpers.mf_engine.query(
MetricFlowQueryRequest.create_with_random_request_id(
metric_names=["bookings", "bookings_offset_one_martian_day"],
group_by_names=["metric_time__day", "metric_time__martian_day"],
order_by_names=["metric_time__day", "metric_time__martian_day"],
)
)
assert query_result.result_df is not None, "Unexpected empty result."

assert_str_snapshot_equal(
request=request,
mf_test_configuration=mf_test_configuration,
snapshot_id="query_output",
snapshot_str=query_result.result_df.text_format(),
sql_engine=sql_client.sql_engine_type,
)


@pytest.mark.sql_engine_snapshot
def test_custom_offset_window_with_grains_and_date_part( # noqa: D103
request: FixtureRequest,
mf_test_configuration: MetricFlowTestConfiguration,
sql_client: SqlClient,
it_helpers: IntegrationTestHelpers,
) -> None:
query_result = it_helpers.mf_engine.query(
MetricFlowQueryRequest.create_with_random_request_id(
metric_names=["bookings_offset_one_martian_day"],
group_by=(
TimeDimensionParameter(name="booking__ds", grain=TimeGranularity.MONTH.name),
TimeDimensionParameter(name="metric_time", date_part=DatePart.YEAR),
TimeDimensionParameter(name="metric_time", grain="martian_day"),
),
order_by=(
OrderByParameter(TimeDimensionParameter(name="booking__ds", grain=TimeGranularity.MONTH.name)),
OrderByParameter(TimeDimensionParameter(name="metric_time", date_part=DatePart.YEAR)),
OrderByParameter(TimeDimensionParameter(name="metric_time", grain="martian_day")),
),
)
)
assert query_result.result_df is not None, "Unexpected empty result."

assert_str_snapshot_equal(
request=request,
mf_test_configuration=mf_test_configuration,
snapshot_id="query_output",
snapshot_str=query_result.result_df.text_format(),
sql_engine=sql_client.sql_engine_type,
)
130 changes: 130 additions & 0 deletions tests_metricflow/integration/test_cases/itest_granularity.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -961,3 +961,133 @@ integration_test:
GROUP BY subq_2.martian_day
) subq_5
ON subq_6.metric_time__martian_day = subq_5.metric_time__martian_day
---
integration_test:
name: custom_offset_window
description: Test querying a metric with a custom offset window
model: SIMPLE_MODEL
metrics: ["bookings_offset_one_martian_day"]
group_bys: ["metric_time__day"]
check_query: |
WITH cte AS (
SELECT
ds AS ds__day
, martian_day AS ds__martian_day
, FIRST_VALUE(ds) OVER (
PARTITION BY martian_day
ORDER BY ds
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS ds__martian_day__first_value
, LAST_VALUE(ds) OVER (
PARTITION BY martian_day
ORDER BY ds
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS ds__martian_day__last_value
, ROW_NUMBER() OVER (
PARTITION BY martian_day
ORDER BY ds
) AS ds__day__row_number
FROM {{ source_schema }}.mf_time_spine ts
)

SELECT
subq_8.ds__day__lead AS metric_time__day
, SUM(1) AS bookings_offset_one_martian_day
FROM (
SELECT
cte.ds__day AS ds__day
, CASE
WHEN {{ render_date_add("subq_7.ds__martian_day__first_value__offset", "(cte.ds__day__row_number - 1)", TimeGranularity.DAY) }} <= subq_7.ds__martian_day__last_value__offset
THEN {{ render_date_add("subq_7.ds__martian_day__first_value__offset", "(cte.ds__day__row_number - 1)", TimeGranularity.DAY) }}
ELSE subq_7.ds__martian_day__last_value__offset
END AS ds__day__lead
FROM cte
INNER JOIN (
SELECT
ds__martian_day
, LEAD(ds__martian_day__first_value, 1) OVER (ORDER BY ds__martian_day) AS ds__martian_day__first_value__offset
, LEAD(ds__martian_day__last_value, 1) OVER (ORDER BY ds__martian_day) AS ds__martian_day__last_value__offset
FROM (
SELECT
ds__martian_day__first_value
, ds__martian_day__last_value
, ds__martian_day
FROM cte
GROUP BY
ds__martian_day__first_value
, ds__martian_day__last_value
, ds__martian_day
) subq_5
) subq_7
ON cte.ds__martian_day = subq_7.ds__martian_day
) subq_8
INNER JOIN {{ source_schema }}.fct_bookings b ON subq_8.ds__day = {{ render_date_trunc("b.ds", TimeGranularity.DAY) }}
GROUP BY subq_8.ds__day__lead
---
integration_test:
name: custom_offset_window_with_grain_and_date_part
description: Test querying a metric with a custom offset window
model: SIMPLE_MODEL
metrics: ["bookings_offset_one_martian_day"]
group_by_objs: [{"name": "booking__ds", "grain": "week"}, {"name": "metric_time", "date_part": "month"}, {"name": "booking__ds", "grain": "martian_day"}]
check_query: |
WITH cte AS (
SELECT
ds AS ds__day
, martian_day AS ds__martian_day
, FIRST_VALUE(ds) OVER (
PARTITION BY martian_day
ORDER BY ds
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS ds__martian_day__first_value
, LAST_VALUE(ds) OVER (
PARTITION BY martian_day
ORDER BY ds
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS ds__martian_day__last_value
, ROW_NUMBER() OVER (
PARTITION BY martian_day
ORDER BY ds
) AS ds__day__row_number
FROM {{ source_schema }}.mf_time_spine ts
)

SELECT
subq_11.martian_day AS booking__ds__martian_day
, {{ render_date_trunc("subq_8.ds__day__lead", TimeGranularity.WEEK) }} AS booking__ds__week
, {{ render_extract("subq_8.ds__day__lead", DatePart.MONTH) }} AS metric_time__extract_month
, SUM(1) AS bookings_offset_one_martian_day
FROM (
SELECT
cte.ds__day
, CASE
WHEN {{ render_date_add("subq_7.ds__martian_day__first_value__offset", "(cte.ds__day__row_number - 1)", TimeGranularity.DAY) }} <= subq_7.ds__martian_day__last_value__offset
THEN {{ render_date_add("subq_7.ds__martian_day__first_value__offset", "(cte.ds__day__row_number - 1)", TimeGranularity.DAY) }}
ELSE subq_7.ds__martian_day__last_value__offset
END AS ds__day__lead
FROM cte
INNER JOIN (
SELECT
ds__martian_day
, LEAD(ds__martian_day__first_value, 1) OVER (ORDER BY ds__martian_day) AS ds__martian_day__first_value__offset
, LEAD(ds__martian_day__last_value, 1) OVER (ORDER BY ds__martian_day) AS ds__martian_day__last_value__offset
FROM (
SELECT
ds__martian_day__first_value
, ds__martian_day__last_value
, ds__martian_day
FROM cte
GROUP BY
ds__martian_day__first_value
, ds__martian_day__last_value
, ds__martian_day
) subq_5
) subq_7
ON cte.ds__martian_day = subq_7.ds__martian_day
) subq_8
INNER JOIN {{ source_schema }}.fct_bookings b ON subq_8.ds__day = {{ render_date_trunc("b.ds", TimeGranularity.DAY) }}
LEFT OUTER JOIN {{ source_schema }}.mf_time_spine subq_11 ON subq_8.ds__day__lead = subq_11.ds
GROUP BY
subq_11.martian_day
, {{ render_date_trunc("subq_8.ds__day__lead", TimeGranularity.WEEK) }}
, {{ render_extract("subq_8.ds__day__lead", DatePart.MONTH) }}
17 changes: 17 additions & 0 deletions tests_metricflow/integration/test_configured_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from metricflow_semantics.protocols.query_parameter import DimensionOrEntityQueryParameter
from metricflow_semantics.specs.query_param_implementations import DimensionOrEntityParameter, TimeDimensionParameter
from metricflow_semantics.sql.sql_exprs import (
SqlAddTimeExpression,
SqlCastToTimestampExpression,
SqlColumnReference,
SqlColumnReferenceExpression,
Expand Down Expand Up @@ -98,6 +99,20 @@ def render_date_sub(
)
return self._sql_client.sql_query_plan_renderer.expr_renderer.render_sql_expr(expr).sql

def render_date_add(
self,
date_column: str,
count_column: str,
granularity: TimeGranularity,
) -> str:
"""Renders a date add expression."""
expr = SqlAddTimeExpression.create(
arg=SqlStringExpression.create(sql_expr=date_column, requires_parenthesis=False),
count_expr=SqlStringExpression.create(sql_expr=count_column, requires_parenthesis=False),
granularity=granularity,
)
return self._sql_client.sql_query_plan_renderer.expr_renderer.render_sql_expr(expr).sql

def render_date_trunc(self, expr: str, granularity: TimeGranularity) -> str:
"""Return the DATE_TRUNC() call that can be used for converting the given expr to the granularity."""
renderable_expr = SqlDateTruncExpression.create(
Expand Down Expand Up @@ -291,6 +306,7 @@ def test_case(
TimeGranularity=TimeGranularity,
DatePart=DatePart,
render_date_sub=check_query_helpers.render_date_sub,
render_date_add=check_query_helpers.render_date_add,
render_date_trunc=check_query_helpers.render_date_trunc,
render_extract=check_query_helpers.render_extract,
render_percentile_expr=check_query_helpers.render_percentile_expr,
Expand Down Expand Up @@ -324,6 +340,7 @@ def test_case(
TimeGranularity=TimeGranularity,
DatePart=DatePart,
render_date_sub=check_query_helpers.render_date_sub,
render_date_add=check_query_helpers.render_date_add,
render_date_trunc=check_query_helpers.render_date_trunc,
render_extract=check_query_helpers.render_extract,
render_percentile_expr=check_query_helpers.render_percentile_expr,
Expand Down
57 changes: 57 additions & 0 deletions tests_metricflow/query_rendering/test_custom_granularity.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
from _pytest.fixtures import FixtureRequest
from dbt_semantic_interfaces.implementations.filters.where_filter import PydanticWhereFilter
from dbt_semantic_interfaces.references import EntityReference
from dbt_semantic_interfaces.type_enums.date_part import DatePart
from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity
from metricflow_semantics.query.query_parser import MetricFlowQueryParser
from metricflow_semantics.specs.metric_spec import MetricSpec
from metricflow_semantics.specs.query_param_implementations import TimeDimensionParameter
from metricflow_semantics.specs.query_spec import MetricFlowQuerySpec
from metricflow_semantics.specs.time_dimension_spec import TimeDimensionSpec
from metricflow_semantics.test_helpers.config_helpers import MetricFlowTestConfiguration
Expand Down Expand Up @@ -610,3 +612,58 @@ def test_join_to_timespine_metric_with_custom_granularity_filter_not_in_group_by
dataflow_plan_builder=dataflow_plan_builder,
query_spec=query_spec,
)


@pytest.mark.sql_engine_snapshot
def test_custom_offset_window( # noqa: D103
request: FixtureRequest,
mf_test_configuration: MetricFlowTestConfiguration,
dataflow_plan_builder: DataflowPlanBuilder,
dataflow_to_sql_converter: DataflowToSqlQueryPlanConverter,
sql_client: SqlClient,
query_parser: MetricFlowQueryParser,
) -> None:
query_spec = query_parser.parse_and_validate_query(
metric_names=("bookings_offset_one_martian_day",),
group_by_names=("metric_time__day",),
).query_spec

render_and_check(
request=request,
mf_test_configuration=mf_test_configuration,
dataflow_to_sql_converter=dataflow_to_sql_converter,
sql_client=sql_client,
dataflow_plan_builder=dataflow_plan_builder,
query_spec=query_spec,
)


@pytest.mark.sql_engine_snapshot
def test_custom_offset_window_with_granularity_and_date_part( # noqa: D103
request: FixtureRequest,
mf_test_configuration: MetricFlowTestConfiguration,
dataflow_plan_builder: DataflowPlanBuilder,
dataflow_to_sql_converter: DataflowToSqlQueryPlanConverter,
sql_client: SqlClient,
query_parser: MetricFlowQueryParser,
) -> None:
query_spec = query_parser.parse_and_validate_query(
metric_names=("bookings_offset_one_martian_day",),
group_by=(
TimeDimensionParameter(name="booking__ds", grain=TimeGranularity.MONTH.name),
TimeDimensionParameter(name="metric_time", date_part=DatePart.YEAR),
TimeDimensionParameter(name="metric_time", grain="martian_day"),
),
).query_spec

render_and_check(
request=request,
mf_test_configuration=mf_test_configuration,
dataflow_to_sql_converter=dataflow_to_sql_converter,
sql_client=sql_client,
dataflow_plan_builder=dataflow_plan_builder,
query_spec=query_spec,
)


# TODO: add test with where filter not included in group by
Loading
Loading