Skip to content
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
4 changes: 2 additions & 2 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -1401,7 +1401,7 @@
"filename": "sdk/python/tests/unit/infra/offline_stores/test_clickhouse.py",
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false,
"line_number": 21
"line_number": 83
}
],
"sdk/python/tests/unit/infra/offline_stores/test_offline_store.py": [
Expand Down Expand Up @@ -1564,5 +1564,5 @@
}
]
},
"generated_at": "2026-08-20T15:20:58Z"
"generated_at": "2026-08-24T05:29:25Z"
}
Original file line number Diff line number Diff line change
Expand Up @@ -533,10 +533,13 @@ def _append_alias(field_names: List[str], alias: str) -> List[str]:
entity_dataframe."{{featureview.name}}__entity_row_unique_id"
FROM "{{ featureview.name }}__subquery" AS subquery
INNER JOIN "{{ featureview.name }}__entity_dataframe" AS entity_dataframe
ON TRUE
{% if featureview.entities %}
{% for entity in featureview.entities %}
AND subquery."{{ entity }}" = entity_dataframe."{{ entity }}"
{% if loop.first %}ON{% else %}AND{% endif %} subquery."{{ entity }}" = entity_dataframe."{{ entity }}"
{% endfor %}
{% else %}
ON 1 = 1
{% endif %}
WHERE TRUE
AND subquery.event_timestamp <= entity_dataframe.entity_timestamp

Expand Down Expand Up @@ -625,6 +628,6 @@ def _append_alias(field_names: List[str], alias: str) -> List[str]:
,"{% if full_feature_names %}{{ featureview.name }}__{{featureview.field_mapping.get(feature, feature)}}{% else %}{{ featureview.field_mapping.get(feature, feature) }}{% endif %}"
{% endfor %}
FROM "{{ featureview.name }}__cleaned"
) AS "{{featureview.name}}" USING ("{{featureview.name}}__entity_row_unique_id")
) AS "{{featureview.name}}" ON "{{featureview.name}}"."{{featureview.name}}__entity_row_unique_id" = entity_dataframe."{{featureview.name}}__entity_row_unique_id"
{% endfor %}
"""
62 changes: 62 additions & 0 deletions sdk/python/tests/unit/infra/offline_stores/test_clickhouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,74 @@

import pytest

from feast.infra.offline_stores.contrib.clickhouse_offline_store.clickhouse import (
MULTIPLE_FEATURE_VIEW_POINT_IN_TIME_JOIN,
build_point_in_time_query,
)
from feast.infra.utils.clickhouse.clickhouse_config import ClickhouseConfig
from feast.infra.utils.clickhouse.connection_utils import get_client, thread_local

logger = logging.getLogger(__name__)


def _feature_view_query_context(name, entities):
return {
"name": name,
"ttl": 3600,
"entities": entities,
"features": ["feature1"],
"field_mapping": {},
"timestamp_field": "event_timestamp",
"created_timestamp_column": None,
"table_subquery": f"{name}_table",
"entity_selections": [f'"{entity}" as "{entity}"' for entity in entities],
"min_event_timestamp": None,
"max_event_timestamp": "2023-01-01",
"date_partition_column": None,
"timestamp_field_type": None,
}


class TestMultipleFeatureViewPointInTimeJoinQuery:
"""
ClickHouse rejects `ON TRUE` (INVALID_JOIN_ON_EXPRESSION) and rejects more than
one `USING` clause per query (Code: 48). Both appeared in the multi-feature-view
point-in-time join template, breaking any query that joined 2+ FeatureViews, as
well as every single-FeatureView query via the `ON TRUE` clause.
"""

def test_no_on_true_or_multiple_using_with_entities(self):
query = build_point_in_time_query(
[
_feature_view_query_context("fv1", ["driver_id"]),
_feature_view_query_context("fv2", ["driver_id"]),
],
left_table_query_string="entity_table",
entity_df_event_timestamp_col="event_timestamp",
entity_df_columns=["driver_id", "event_timestamp"],
query_template=MULTIPLE_FEATURE_VIEW_POINT_IN_TIME_JOIN,
)

assert "ON TRUE" not in query
assert 'ON "fv1"."fv1__entity_row_unique_id"' in query
assert 'ON "fv2"."fv2__entity_row_unique_id"' in query
assert 'USING ("fv1__entity_row_unique_id")' not in query
assert 'USING ("fv2__entity_row_unique_id")' not in query

def test_no_on_true_with_no_entities(self):
"""Non-entity FeatureViews (entities=[]) must still produce a valid ON clause."""
query = build_point_in_time_query(
[_feature_view_query_context("fv1", [])],
left_table_query_string="entity_table",
entity_df_event_timestamp_col="event_timestamp",
entity_df_columns=["event_timestamp"],
query_template=MULTIPLE_FEATURE_VIEW_POINT_IN_TIME_JOIN,
)

assert "ON TRUE" not in query
assert "ON 1 = 1" in query


@pytest.fixture
def clickhouse_config():
"""Create a test ClickHouse configuration."""
Expand Down