Skip to content

Commit 6eff786

Browse files
authored
fix(endpoints): disable materialization for all versions when endpoint is deactivated (#65787)
1 parent 1114e2c commit 6eff786

2 files changed

Lines changed: 90 additions & 6 deletions

File tree

products/endpoints/backend/services/crud.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,12 @@ def update(
199199
if data.is_active is not None and not version_targeted:
200200
endpoint.is_active = data.is_active
201201
endpoint.save()
202-
if not version_targeted and not endpoint.is_active and was_materialized:
203-
self.materialization.disable_materialization(endpoint, current_version)
202+
if not version_targeted and not endpoint.is_active:
203+
# A deactivated endpoint serves no version, so none should keep a
204+
# materialization schedule running — tear down every materialized
205+
# version, not just the current one.
206+
for materialized_version in endpoint.versions.filter(saved_query__isnull=False):
207+
self.materialization.disable_materialization(endpoint, materialized_version)
204208
if data.is_active is not None and not version_targeted:
205209
# Activation affects throttle classification — force a lazy re-check.
206210
clear_endpoint_materialization_cache(self.team.pk, endpoint.name)
@@ -336,16 +340,17 @@ def _reconcile_materialization(
336340
Returns a materialization error message when enabling failed after a new
337341
version was already committed (the update itself still succeeds).
338342
"""
339-
if not endpoint.is_active:
340-
# Endpoint-level deactivation already tore down the current version's materialization.
341-
return None
342-
343343
if version_targeted and not target_version.is_active:
344344
# Deactivating a version: tear down its own materialization, never enable.
345+
# Checked before the endpoint-active guard so this holds even on an inactive endpoint.
345346
if target_version.saved_query_id is not None:
346347
self.materialization.disable_materialization(endpoint, target_version)
347348
return None
348349

350+
if not endpoint.is_active:
351+
# Endpoint-level deactivation already tore down every version's materialization.
352+
return None
353+
349354
# When targeting a specific version, check that version's materialization state.
350355
# Otherwise use the pre-update state so materialization transfers across a version bump.
351356
check_was_materialized = target_version.saved_query_id is not None if version_targeted else was_materialized

products/endpoints/backend/tests/test_endpoint_materialization.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,6 +1709,85 @@ def test_deactivating_one_version_keeps_current_version_materialization(self):
17091709
assert v2.saved_query is not None
17101710
self.assertFalse(v2.saved_query.deleted)
17111711

1712+
def test_deactivating_materialized_version_disables_its_materialization(self):
1713+
"""PATCH ?version=N {is_active: false} on a materialized version tears down its own schedule."""
1714+
endpoint = create_endpoint_with_version(
1715+
name="version-deactivate-materialized",
1716+
team=self.team,
1717+
query=self.sample_hogql_query,
1718+
created_by=self.user,
1719+
is_active=True,
1720+
)
1721+
1722+
response = self.client.patch(
1723+
f"/api/environments/{self.team.id}/endpoints/{endpoint.name}/",
1724+
{"is_materialized": True},
1725+
format="json",
1726+
)
1727+
self.assertEqual(response.status_code, status.HTTP_200_OK, response.json())
1728+
v1 = endpoint.get_version(1)
1729+
self.assertIsNotNone(v1.saved_query_id)
1730+
saved_query_id = v1.saved_query_id
1731+
1732+
response = self.client.patch(
1733+
f"/api/environments/{self.team.id}/endpoints/{endpoint.name}/",
1734+
{"is_active": False, "version": 1},
1735+
format="json",
1736+
)
1737+
self.assertEqual(response.status_code, status.HTTP_200_OK, response.json())
1738+
1739+
v1.refresh_from_db()
1740+
self.assertFalse(v1.is_active)
1741+
self.assertIsNone(v1.saved_query_id, "deactivated version kept its materialization")
1742+
self.assertTrue(DataWarehouseSavedQuery.objects.get(id=saved_query_id).deleted)
1743+
1744+
def test_deactivating_endpoint_disables_all_versions_materialization(self):
1745+
"""Deactivating the endpoint must tear down every materialized version, not just the current one."""
1746+
endpoint = create_endpoint_with_version(
1747+
name="endpoint-deactivate-all",
1748+
team=self.team,
1749+
query=self.sample_hogql_query,
1750+
created_by=self.user,
1751+
is_active=True,
1752+
)
1753+
1754+
# Materialize v1 (current), then bump to v2 via a query change and materialize it too —
1755+
# leaving both versions materialized at once.
1756+
self.client.patch(
1757+
f"/api/environments/{self.team.id}/endpoints/{endpoint.name}/",
1758+
{"is_materialized": True},
1759+
format="json",
1760+
)
1761+
self.client.patch(
1762+
f"/api/environments/{self.team.id}/endpoints/{endpoint.name}/",
1763+
{"query": {"kind": "HogQLQuery", "query": "SELECT event FROM events LIMIT 5"}},
1764+
format="json",
1765+
)
1766+
v1 = endpoint.get_version(1)
1767+
v2 = endpoint.get_version(2)
1768+
self.assertIsNotNone(v1.saved_query_id)
1769+
self.assertIsNotNone(v2.saved_query_id)
1770+
v1_saved_query_id = v1.saved_query_id
1771+
v2_saved_query_id = v2.saved_query_id
1772+
1773+
response = self.client.patch(
1774+
f"/api/environments/{self.team.id}/endpoints/{endpoint.name}/",
1775+
{"is_active": False},
1776+
format="json",
1777+
)
1778+
self.assertEqual(response.status_code, status.HTTP_200_OK, response.json())
1779+
1780+
v1.refresh_from_db()
1781+
v2.refresh_from_db()
1782+
self.assertIsNone(v1.saved_query_id, "non-current version kept its materialization")
1783+
self.assertIsNone(v2.saved_query_id, "current version kept its materialization")
1784+
self.assertTrue(DataWarehouseSavedQuery.objects.get(id=v1_saved_query_id).deleted)
1785+
self.assertTrue(DataWarehouseSavedQuery.objects.get(id=v2_saved_query_id).deleted)
1786+
self.assertIsNone(v1.saved_query_id, "non-current version kept its materialization")
1787+
self.assertIsNone(v2.saved_query_id, "current version kept its materialization")
1788+
self.assertTrue(DataWarehouseSavedQuery.objects.get(id=v1_saved_query_id).deleted)
1789+
self.assertTrue(DataWarehouseSavedQuery.objects.get(id=v2_saved_query_id).deleted)
1790+
17121791
def test_enable_materialization_does_not_hijack_user_saved_query(self):
17131792
"""A user-created saved query whose name collides with {endpoint}_v{n} must not be taken over."""
17141793
endpoint = create_endpoint_with_version(

0 commit comments

Comments
 (0)