Skip to content

Commit 6486ea0

Browse files
committed
feat(gooddata-sdk): add HLL_TYPE org setting helpers
Add typed `set_hll_type` / `get_hll_type` methods on `CatalogOrganizationService` for the new `hyperLogLogType` org setting introduced by gdc-nas (`HLL_TYPE("hyperLogLogType", SettingConfiguration.HyperLogLogType)`). The setting controls which HLL function family calcique uses when generating SQL over HLL synopses: - `"Native"` (default) emits StarRocks-native `HLL_*` functions — works when the platform (or PIPE pipeline) builds synopses StarRocks-side. - `"Presto"` emits Presto-compatible HLL functions, required when synopses arrive from an upstream Presto pipeline (the binary layout and hash family differ between the two). Requires the StarRocks deployment to carry the Presto HLL UDFs. Surface: - `HLLType` Literal alias (`"Native" | "Presto"`) re-exported from `gooddata_sdk` for consumer annotations. - `HLL_TYPE_SETTING_ID` (`"hyperLogLogType"`) and `HLL_TYPE_SETTING_TYPE` (`"HLL_TYPE"`) constants for callers that prefer to drive the generic `CatalogOrganizationSetting.init(...)` path directly. - `service.set_hll_type(value)` is idempotent — tries update first, falls back to create when the setting doesn't exist yet. - `service.get_hll_type()` returns `HLLType | None`; defensively returns `None` for unrecognized stored values. Tests: 7 unit tests with mocked `entities_api` — no live stack needed. Cover create-on-missing, update-on-existing, both `"Native"` and `"Presto"` reads, absent-setting case, and the public `HLLType` alias. Verified: full SDK unit suite — 438 passed, 2 skipped, 1 xfailed. JIRA: CQ-2320 risk: low
1 parent d960007 commit 6486ea0

3 files changed

Lines changed: 166 additions & 2 deletions

File tree

packages/gooddata-sdk/src/gooddata_sdk/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,12 @@
137137
CatalogDeclarativeNotificationChannel,
138138
CatalogWebhook,
139139
)
140-
from gooddata_sdk.catalog.organization.service import CatalogOrganizationService
140+
from gooddata_sdk.catalog.organization.service import (
141+
HLL_TYPE_SETTING_ID,
142+
HLL_TYPE_SETTING_TYPE,
143+
CatalogOrganizationService,
144+
HLLType,
145+
)
141146
from gooddata_sdk.catalog.permission.declarative_model.dashboard_assignees import (
142147
CatalogAvailableAssignees,
143148
CatalogUserAssignee,

packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/service.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from __future__ import annotations
33

44
import functools
5-
from typing import Any
5+
from typing import Any, Literal
66

77
from gooddata_api_client.exceptions import NotFoundException
88
from gooddata_api_client.model.declarative_export_templates import DeclarativeExportTemplates
@@ -35,6 +35,16 @@
3535
from gooddata_sdk.client import GoodDataApiClient
3636
from gooddata_sdk.utils import load_all_entities, load_all_entities_dict
3737

38+
# Org-level setting controlling which HLL function family calcique uses when
39+
# generating SQL over HLL synopses. `Native` (default) emits StarRocks-native
40+
# `HLL_*` functions; `Presto` emits the Presto-compatible HLL function family
41+
# and assumes the StarRocks deployment carries the Presto HLL UDFs. Pick
42+
# `Presto` when synopses are produced by an upstream Presto pipeline (the
43+
# binary layout / hash family differ between the two).
44+
HLLType = Literal["Native", "Presto"]
45+
HLL_TYPE_SETTING_ID = "hyperLogLogType"
46+
HLL_TYPE_SETTING_TYPE = "HLL_TYPE"
47+
3848

3949
class CatalogOrganizationService(CatalogServiceBase):
4050
def __init__(self, api_client: GoodDataApiClient) -> None:
@@ -217,6 +227,40 @@ def delete_organization_setting(self, organization_setting_id: str) -> None:
217227
f"This organization setting does not exist."
218228
)
219229

230+
def set_hll_type(self, value: HLLType) -> None:
231+
"""Set the organization-level HyperLogLog function family.
232+
233+
Idempotent: creates the `hyperLogLogType` setting if missing,
234+
otherwise updates the existing one. The platform exposes this as
235+
the `HLL_TYPE` org setting (gdc-nas `HLL_TYPE("hyperLogLogType",
236+
SettingConfiguration.HyperLogLogType)`).
237+
238+
Args:
239+
value: `"Native"` for StarRocks-native HLL functions (default
240+
on the platform side), or `"Presto"` for Presto-compatible
241+
HLL functions (use when synopses come from a Presto
242+
pipeline; requires the Presto HLL UDFs registered in
243+
StarRocks).
244+
"""
245+
setting = CatalogOrganizationSetting.init(
246+
setting_id=HLL_TYPE_SETTING_ID,
247+
setting_type=HLL_TYPE_SETTING_TYPE,
248+
content={"value": value},
249+
)
250+
try:
251+
self.update_organization_setting(setting)
252+
except ValueError:
253+
self.create_organization_setting(setting)
254+
255+
def get_hll_type(self) -> HLLType | None:
256+
"""Return the current `hyperLogLogType` value, or `None` if unset."""
257+
try:
258+
setting = self.get_organization_setting(HLL_TYPE_SETTING_ID)
259+
except NotFoundException:
260+
return None
261+
value = setting.attributes.content.get("value")
262+
return value if value in ("Native", "Presto") else None
263+
220264
def update_organization_setting(self, organization_setting: CatalogOrganizationSetting) -> None:
221265
"""Update an organization setting.
222266
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# (C) 2026 GoodData Corporation
2+
"""Unit tests for the `set_hll_type` / `get_hll_type` org-setting helpers.
3+
4+
These exercise the typed wrappers around the generic
5+
`CatalogOrganizationService.{create,update,get}_organization_setting`
6+
machinery; they don't need a live stack because the helpers' interesting
7+
logic is the create-or-update fallback and the JSON shape sent to the
8+
api-client.
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import typing
14+
from types import SimpleNamespace
15+
from unittest.mock import MagicMock
16+
17+
from gooddata_api_client.exceptions import NotFoundException
18+
from gooddata_sdk import HLLType
19+
from gooddata_sdk.catalog.organization.service import (
20+
HLL_TYPE_SETTING_ID,
21+
HLL_TYPE_SETTING_TYPE,
22+
CatalogOrganizationService,
23+
)
24+
25+
26+
def _make_service() -> tuple[CatalogOrganizationService, MagicMock]:
27+
"""Build a service whose entities-api side is fully mocked."""
28+
fake_entities_api = MagicMock(name="EntitiesApi")
29+
fake_client = SimpleNamespace(
30+
entities_api=fake_entities_api,
31+
layout_api=MagicMock(name="LayoutApi"),
32+
actions_api=MagicMock(name="ActionsApi"),
33+
user_management_api=MagicMock(name="UserManagementApi"),
34+
)
35+
service = CatalogOrganizationService(fake_client) # type: ignore[arg-type]
36+
return service, fake_entities_api
37+
38+
39+
def _captured_setting_payload(api: MagicMock, mock_call: MagicMock) -> dict:
40+
"""Pull out the `attributes` dict the SDK posted to the api-client."""
41+
document = (
42+
mock_call.call_args.kwargs.get("json_api_organization_setting_in_document") or mock_call.call_args.args[-1]
43+
)
44+
data = document.data
45+
return {
46+
"id": data.id,
47+
"type": data.attributes.type,
48+
"content": dict(data.attributes.content),
49+
}
50+
51+
52+
class TestSetHllType:
53+
def test_updates_existing_setting_when_present(self) -> None:
54+
service, api = _make_service()
55+
# update succeeds → no fallback to create
56+
service.set_hll_type("Presto")
57+
assert api.update_entity_organization_settings.call_count == 1
58+
assert api.create_entity_organization_settings.call_count == 0
59+
sent_id = api.update_entity_organization_settings.call_args.args[0]
60+
assert sent_id == HLL_TYPE_SETTING_ID
61+
sent = _captured_setting_payload(api, api.update_entity_organization_settings)
62+
assert sent["id"] == HLL_TYPE_SETTING_ID
63+
assert sent["type"] == HLL_TYPE_SETTING_TYPE
64+
assert sent["content"] == {"value": "Presto"}
65+
66+
def test_creates_setting_when_missing(self) -> None:
67+
service, api = _make_service()
68+
api.update_entity_organization_settings.side_effect = NotFoundException(status=404, reason="not found")
69+
service.set_hll_type("Native")
70+
assert api.update_entity_organization_settings.call_count == 1
71+
assert api.create_entity_organization_settings.call_count == 1
72+
sent = _captured_setting_payload(api, api.create_entity_organization_settings)
73+
assert sent["content"] == {"value": "Native"}
74+
75+
76+
class TestGetHllType:
77+
def test_returns_native_when_set(self) -> None:
78+
service, api = _make_service()
79+
api.get_entity_organization_settings.return_value = SimpleNamespace(
80+
data={
81+
"id": HLL_TYPE_SETTING_ID,
82+
"attributes": {"type": HLL_TYPE_SETTING_TYPE, "content": {"value": "Native"}},
83+
}
84+
)
85+
assert service.get_hll_type() == "Native"
86+
87+
def test_returns_presto_when_set(self) -> None:
88+
service, api = _make_service()
89+
api.get_entity_organization_settings.return_value = SimpleNamespace(
90+
data={
91+
"id": HLL_TYPE_SETTING_ID,
92+
"attributes": {"type": HLL_TYPE_SETTING_TYPE, "content": {"value": "Presto"}},
93+
}
94+
)
95+
assert service.get_hll_type() == "Presto"
96+
97+
def test_returns_none_when_absent(self) -> None:
98+
service, api = _make_service()
99+
api.get_entity_organization_settings.side_effect = NotFoundException(status=404, reason="not found")
100+
assert service.get_hll_type() is None
101+
102+
def test_returns_none_on_unrecognized_value(self) -> None:
103+
service, api = _make_service()
104+
api.get_entity_organization_settings.return_value = SimpleNamespace(
105+
data={
106+
"id": HLL_TYPE_SETTING_ID,
107+
"attributes": {"type": HLL_TYPE_SETTING_TYPE, "content": {"value": "garbage"}},
108+
}
109+
)
110+
assert service.get_hll_type() is None
111+
112+
113+
def test_hll_type_literal_is_exposed_on_public_api() -> None:
114+
"""The `HLLType` Literal is the canonical surface consumers should annotate against."""
115+
assert typing.get_args(HLLType) == ("Native", "Presto")

0 commit comments

Comments
 (0)