smart-data-engine-sdk 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. sde/__init__.py +318 -0
  2. sde/_cutover_project.py +179 -0
  3. sde/_local_state.py +188 -0
  4. sde/_operator_deadline.py +50 -0
  5. sde/_usage.py +314 -0
  6. sde/bulk.py +79 -0
  7. sde/canonical.py +141 -0
  8. sde/capabilities.py +62 -0
  9. sde/cutover.py +286 -0
  10. sde/engines/__init__.py +0 -0
  11. sde/engines/_clickhouse_connection.py +224 -0
  12. sde/engines/_index_build.py +294 -0
  13. sde/engines/_operator.py +394 -0
  14. sde/engines/_staging.py +222 -0
  15. sde/engines/_storage.py +22 -0
  16. sde/engines/_write_fences.py +271 -0
  17. sde/engines/clickhouse.py +1115 -0
  18. sde/engines/orderbook.py +457 -0
  19. sde/engines/postgres.py +967 -0
  20. sde/entity.py +170 -0
  21. sde/errors.py +103 -0
  22. sde/explain.py +300 -0
  23. sde/frozen_verification.py +152 -0
  24. sde/generation.py +131 -0
  25. sde/groups.py +97 -0
  26. sde/hashing.py +242 -0
  27. sde/index_build.py +313 -0
  28. sde/index_operator.py +347 -0
  29. sde/infer.py +461 -0
  30. sde/inspection.py +62 -0
  31. sde/internal.py +90 -0
  32. sde/layout.py +669 -0
  33. sde/local_cutover.py +801 -0
  34. sde/logging.py +143 -0
  35. sde/migration.py +856 -0
  36. sde/model.py +482 -0
  37. sde/physical.py +531 -0
  38. sde/placement.py +1010 -0
  39. sde/provisioning.py +63 -0
  40. sde/py.typed +0 -0
  41. sde/query.py +521 -0
  42. sde/routing.py +85 -0
  43. sde/schema.py +466 -0
  44. sde/session.py +993 -0
  45. sde/shapes.py +153 -0
  46. sde/staging.py +264 -0
  47. sde/staging_operator.py +393 -0
  48. sde/telemetry.py +1087 -0
  49. sde/testing/__init__.py +14 -0
  50. sde/testing/loader.py +175 -0
  51. sde/testing/memory.py +331 -0
  52. sde/types.py +228 -0
  53. sde/verification.py +220 -0
  54. sde/watermark.py +222 -0
  55. sde/write_fence.py +283 -0
  56. sde_demo/__init__.py +1 -0
  57. sde_demo/__main__.py +183 -0
  58. sde_demo/diagnostics.py +92 -0
  59. sde_demo/model.py +75 -0
  60. sde_demo/project.py +312 -0
  61. sde_demo/py.typed +0 -0
  62. sde_demo/query_count.py +301 -0
  63. sde_demo/resources.py +969 -0
  64. sde_demo/runtime.py +419 -0
  65. sde_demo/verification.py +242 -0
  66. sde_operator/__init__.py +1 -0
  67. sde_operator/__main__.py +210 -0
  68. smart_data_engine_sdk-0.1.0.dist-info/METADATA +174 -0
  69. smart_data_engine_sdk-0.1.0.dist-info/RECORD +73 -0
  70. smart_data_engine_sdk-0.1.0.dist-info/WHEEL +4 -0
  71. smart_data_engine_sdk-0.1.0.dist-info/entry_points.txt +3 -0
  72. smart_data_engine_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
  73. smart_data_engine_sdk-0.1.0.dist-info/licenses/NOTICE +13 -0
sde_demo/project.py ADDED
@@ -0,0 +1,312 @@
1
+ """Validated customer configuration and local Weather bootstrap orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import binascii
7
+ import importlib
8
+ import json
9
+ import re
10
+ import stat
11
+ from collections.abc import Iterable, Iterator, Mapping
12
+ from contextlib import contextmanager
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ import sde
17
+ from sde._local_state import confirm_file, transaction, write_bytes
18
+ from sde.generation import GENERATIONS_SINCE, check_map_project
19
+
20
+ from .model import model
21
+
22
+
23
+ class DemoRefused(ValueError):
24
+ """An actionable starter error whose message contains no driver or input values."""
25
+
26
+
27
+ def payload(path: Path, *, private: bool = False) -> bytes:
28
+ if path.is_symlink() or not path.is_file():
29
+ raise DemoRefused("A required local file is missing or is a symlink.")
30
+ if private and stat.S_IMODE(path.stat().st_mode) != 0o600:
31
+ raise DemoRefused("Credential files must have mode 0600; fix their permissions.")
32
+ with path.open("rb") as stream:
33
+ data = stream.read(2 * 1024 * 1024 + 1)
34
+ if len(data) > 2 * 1024 * 1024:
35
+ raise DemoRefused("The local JSON input exceeds 2 MiB.")
36
+ return data
37
+
38
+
39
+ def decode(data: bytes) -> dict[str, Any]:
40
+ def unique(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
41
+ result: dict[str, Any] = {}
42
+ for name, value in pairs:
43
+ if name in result:
44
+ raise DemoRefused("JSON contains duplicate keys; restore the original file.")
45
+ result[name] = value
46
+ return result
47
+
48
+ value = json.loads(data, object_pairs_hook=unique)
49
+ if not isinstance(value, dict):
50
+ raise DemoRefused("The local JSON input must be an object.")
51
+ return value
52
+
53
+
54
+ def read(path: Path, *, private: bool = False) -> dict[str, Any]:
55
+ return decode(payload(path, private=private))
56
+
57
+
58
+ def write(path: Path, value: Mapping[str, Any], *, once: bool = False) -> None:
59
+ payload = (
60
+ json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False) + "\n"
61
+ ).encode()
62
+ if once and path.exists():
63
+ if path.is_symlink() or path.read_bytes() != payload:
64
+ raise DemoRefused("This directory belongs to another setup; use a fresh directory.")
65
+ confirm_file(path)
66
+ return
67
+ write_bytes(path, payload, replace=not once)
68
+
69
+
70
+ def public_keys(value: Any) -> dict[str, bytes]:
71
+ if not isinstance(value, dict) or not value:
72
+ raise DemoRefused("Bootstrap needs trusted public keys supplied by your operator.")
73
+ result = {}
74
+ for name, encoded in value.items():
75
+ if not isinstance(name, str) or not name or not isinstance(encoded, str):
76
+ raise DemoRefused("Invalid public key configuration.")
77
+ try:
78
+ decoded = base64.b64decode(encoded, validate=True)
79
+ except binascii.Error:
80
+ raise DemoRefused("Invalid public key configuration.") from None
81
+ if len(decoded) != 32:
82
+ raise DemoRefused("Ed25519 public keys must contain 32 bytes.")
83
+ result[name] = decoded
84
+ return result
85
+
86
+
87
+ def bootstrap(value: dict[str, Any]) -> tuple[dict[str, Any], sde.PlacementMap]:
88
+ if (
89
+ set(value)
90
+ != {"kind", "protocol", "project_id", "model", "public_keys", "engines", "current_map"}
91
+ or value["kind"] != "sde-weather-bootstrap"
92
+ or type(value["protocol"]) is not int
93
+ or value["protocol"] != 1
94
+ ):
95
+ raise DemoRefused("Use the complete protocol-1 Weather bootstrap from your operator.")
96
+ from sde.testing.loader import model_from_neutral
97
+
98
+ logical = model()
99
+ if model_from_neutral(value["model"]).version != logical.version:
100
+ raise DemoRefused("This starter requires the Weather model; request a matching bootstrap.")
101
+ engines = value["engines"]
102
+ if (
103
+ not isinstance(engines, dict)
104
+ or not 1 <= len(engines) <= 2
105
+ or any(not re.fullmatch(r"[a-z][a-z0-9_-]{0,47}", name) for name in engines)
106
+ or any(dialect not in ("postgres", "clickhouse") for dialect in engines.values())
107
+ or len(set(engines.values())) != len(engines)
108
+ ):
109
+ raise DemoRefused(
110
+ "Weather demo needs one or two uniquely named PostgreSQL/ClickHouse bindings."
111
+ )
112
+ keys = public_keys(value["public_keys"])
113
+ try:
114
+ placement = sde.load_map(
115
+ value["current_map"], model=logical, public_key=keys, require_signature=True
116
+ )
117
+ except sde.MapError as exc:
118
+ # Refused before setup writes anything, and the reason is about the map - a missing
119
+ # 'signed' extra, a bad signature - never about a credential, so it can be said in full.
120
+ raise DemoRefused(f"The bootstrap's map does not load: {exc}") from None
121
+ check_map_project(placement, value["project_id"])
122
+ if placement.contract < GENERATIONS_SINCE or placement.map_version != 1:
123
+ raise DemoRefused(
124
+ "A new demo must start from a signed generation-bearing initial map (version 1)."
125
+ )
126
+ for group in placement.groups.values():
127
+ if len(group.all()) != 1 or group.source.engine not in engines:
128
+ raise DemoRefused("Bootstrap must contain only bound initial source materializations.")
129
+ config = {
130
+ "protocol": 1,
131
+ "project_id": value["project_id"],
132
+ "model": sde.neutral_declaration(logical),
133
+ "public_keys": value["public_keys"],
134
+ "engines": {
135
+ name: {
136
+ "dialect": dialect,
137
+ "operator_dsn_env": f"SDE_WEATHER_OPERATOR_{index}",
138
+ "runtime_dsn_envs": [f"SDE_WEATHER_RUNTIME_{index}"],
139
+ }
140
+ for index, (name, dialect) in enumerate(sorted(engines.items()))
141
+ },
142
+ }
143
+ return config, placement
144
+
145
+
146
+ def config(root: Path, *, ready: bool = True) -> dict[str, Any]:
147
+ expected, _ = bootstrap(read(root / "bootstrap.json"))
148
+ if read(root / "config.json") != expected:
149
+ raise DemoRefused("Local config differs from the enrolled bootstrap; restore it.")
150
+ if ready:
151
+ if (root / "reset-request.json").exists():
152
+ raise DemoRefused("Reset was requested; finish reset and set up a fresh directory.")
153
+ marker = read(root / "setup-complete.json")
154
+ if marker != {"protocol": 1, "config_digest": sde.digest16(expected)}:
155
+ raise DemoRefused(
156
+ "Setup is incomplete or changed; rerun setup with its original input."
157
+ )
158
+ return expected
159
+
160
+
161
+ def credentials(root: Path, purpose: str, bindings: Mapping[str, Any]) -> dict[str, str]:
162
+ import hashlib
163
+
164
+ filename = purpose + "-credentials.json"
165
+ data = payload(root / filename, private=True)
166
+ value = decode(data)
167
+ if set(value) != set(bindings) or any(
168
+ not isinstance(dsn, str) or not dsn for dsn in value.values()
169
+ ):
170
+ raise DemoRefused("The local credential bindings are incomplete.")
171
+ # Verify the file this caller needs, without opening the other privilege level's secrets.
172
+ metadata = read(root / "resources.json")
173
+ hashes = metadata.get("credential_hashes")
174
+ if (
175
+ not isinstance(hashes, dict)
176
+ or metadata.get("status") != "ready"
177
+ or hashes.get(purpose) != hashlib.sha256(data).hexdigest()
178
+ ):
179
+ raise DemoRefused("Local credentials differ from the resource allocation; restore them.")
180
+ return value
181
+
182
+
183
+ DRIVERS = {"postgres": ("psycopg", "postgres"), "clickhouse": ("clickhouse_connect", "clickhouse")}
184
+ """The driver module each binding's adapter imports, and the SDK extra that installs it."""
185
+
186
+
187
+ def require_drivers(dialects: Iterable[str]) -> None:
188
+ """Refuse before anything is written when a binding's driver cannot be imported.
189
+
190
+ Without this the first session open failed inside a run's retry loop - ten seconds of retries,
191
+ a message that could only say "incomplete", and a report left `incomplete` for later fleet runs
192
+ to stop at. The import is the adapter's own, so a module that is present but cannot load counts
193
+ as missing too.
194
+ """
195
+ missing = []
196
+ for dialect in sorted(set(dialects)):
197
+ module, extra = DRIVERS[dialect]
198
+ try:
199
+ importlib.import_module(module)
200
+ except ImportError:
201
+ missing.append(extra)
202
+ if missing:
203
+ raise DemoRefused(
204
+ "Install the drivers for this demo's engines: "
205
+ f"pip install 'smart-data-engine-sdk[{','.join(missing)}]'"
206
+ )
207
+
208
+
209
+ def engine(dialect: str, dsn: str) -> Any:
210
+ if dialect == "postgres":
211
+ from sde.engines.postgres import PostgresEngine
212
+
213
+ return PostgresEngine(dsn)
214
+ if dialect == "clickhouse":
215
+ from sde.engines.clickhouse import ClickHouseEngine
216
+
217
+ return ClickHouseEngine(dsn)
218
+ raise DemoRefused("Unsupported engine dialect.")
219
+
220
+
221
+ @contextmanager
222
+ def connections(
223
+ root: Path, settings: dict[str, Any]
224
+ ) -> Iterator[tuple[dict[str, Any], dict[str, Any]]]:
225
+ opened: list[Any] = []
226
+ try:
227
+ operators: dict[str, Any] = {}
228
+ runtime: dict[str, Any] = {}
229
+ for purpose, target in (("operator", operators), ("runtime", runtime)):
230
+ dsns = credentials(root, purpose, settings["engines"])
231
+ for name, binding in sorted(settings["engines"].items()):
232
+ adapter = engine(binding["dialect"], dsns[name])
233
+ opened.append(adapter)
234
+ adapter.connect()
235
+ target[name] = adapter
236
+ yield operators, runtime
237
+ finally:
238
+ # Attempt every cleanup, even if an earlier adapter cannot close.
239
+ failures: list[Exception] = []
240
+ for adapter in reversed(opened):
241
+ try:
242
+ adapter.close()
243
+ except Exception as exc:
244
+ failures.append(exc)
245
+ if failures:
246
+ raise DemoRefused("An engine connection could not close; stop this process.")
247
+
248
+
249
+ def setup(root: Path, supplied: dict[str, Any], admin_dsns: Mapping[str, str]) -> dict[str, Any]:
250
+ from .resources import allocate, grant_tables
251
+
252
+ settings, placement = bootstrap(supplied)
253
+ require_drivers(supplied["engines"].values())
254
+ logical = model()
255
+ with transaction(root):
256
+ if (root / "reset-request.json").exists():
257
+ raise DemoRefused(
258
+ "This allocation is being reset; use a new directory for the next demo."
259
+ )
260
+ write(root / "bootstrap.json", supplied, once=True)
261
+ write(root / "config.json", settings, once=True)
262
+ allocate(root, supplied["engines"], admin_dsns)
263
+ if (root / "setup-complete.json").exists():
264
+ config(root)
265
+ from sde._cutover_project import ProjectState
266
+
267
+ store = ProjectState(root / "state", settings["project_id"], logical.version)
268
+ with store.lock():
269
+ store.read()
270
+ current = sde.load_local_map(
271
+ root / "state",
272
+ model=logical,
273
+ project_id=settings["project_id"],
274
+ public_key=public_keys(settings["public_keys"]),
275
+ )
276
+ store.confirm()
277
+ confirm_file(root / "setup-complete.json")
278
+ return {"status": "ready", "map_version": current.map_version}
279
+ if (root / "state" / "active-map.json").exists():
280
+ current = sde.load_local_map(
281
+ root / "state",
282
+ model=logical,
283
+ project_id=settings["project_id"],
284
+ public_key=public_keys(settings["public_keys"]),
285
+ )
286
+ if current.fingerprint != placement.fingerprint:
287
+ raise DemoRefused(
288
+ "An unfinished setup already advanced its map; inspect local state."
289
+ )
290
+ with connections(root, settings) as (operators, runtime):
291
+ sde.prepare_schema(logical, placement, operators, project_id=settings["project_id"])
292
+ tables: dict[str, list[str]] = {name: ["sde_map_state"] for name in operators}
293
+ for group in placement.groups.values():
294
+ tables[group.source.engine].extend(group.source.layout.tables.values())
295
+ grant_tables(root, tables, admin_dsns)
296
+ local = sde.LocalCutover(
297
+ root / "state",
298
+ model=logical,
299
+ project_id=settings["project_id"],
300
+ public_key=public_keys(settings["public_keys"]),
301
+ operators=operators,
302
+ runtime={name: [adapter] for name, adapter in runtime.items()},
303
+ )
304
+ local.enroll(supplied["current_map"])
305
+ with sde.Session(logical, placement, runtime, project_id=settings["project_id"]):
306
+ pass
307
+ write(
308
+ root / "setup-complete.json",
309
+ {"protocol": 1, "config_digest": sde.digest16(settings)},
310
+ once=True,
311
+ )
312
+ return {"status": "ready", "map_version": placement.map_version}
sde_demo/py.typed ADDED
File without changes
@@ -0,0 +1,301 @@
1
+ """Execute the Weather demo's approved COUNT locally; business values never enter receipts.
2
+
3
+ This is deliberately a closed COUNT(*) grammar over the current source, not a general SQL runner.
4
+ The metadata constructor is pure and can be used by the controller without importing drivers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import json
11
+ import re
12
+ from collections.abc import Mapping
13
+ from contextlib import ExitStack
14
+ from dataclasses import dataclass
15
+ from datetime import UTC, datetime
16
+ from pathlib import Path
17
+ from typing import Any
18
+ from uuid import uuid4
19
+
20
+ import sde
21
+ from sde.generation import GENERATIONS_SINCE, check_map_project
22
+ from sde.schema import QUOTE
23
+
24
+ from .model import model
25
+ from .project import DemoRefused, config, credentials, engine, public_keys, write
26
+
27
+ KIND = "sde-weather-count-query"
28
+ FIELDS = {
29
+ "kind",
30
+ "protocol",
31
+ "project_id",
32
+ "name",
33
+ "revision",
34
+ "map_fingerprint",
35
+ "query",
36
+ "digest",
37
+ }
38
+
39
+
40
+ def _bytes(value: Any) -> bytes:
41
+ return (
42
+ json.dumps(
43
+ value, sort_keys=True, ensure_ascii=False, separators=(",", ":"), allow_nan=False
44
+ )
45
+ + "\n"
46
+ ).encode()
47
+
48
+
49
+ def _digest(value: Mapping[str, Any]) -> str:
50
+ return hashlib.sha256(
51
+ _bytes({key: item for key, item in value.items() if key != "digest"})
52
+ ).hexdigest()
53
+
54
+
55
+ def _source(placement: sde.PlacementMap, project_id: str) -> sde.Materialization:
56
+ check_map_project(placement, project_id)
57
+ if (
58
+ placement.contract < GENERATIONS_SINCE
59
+ or not placement.signed
60
+ or placement.fingerprint is None
61
+ or placement.model_version != model().version
62
+ or set(placement.groups) != {"WeatherReading"}
63
+ ):
64
+ raise DemoRefused("The count demo needs the loaded, signed Weather project map.")
65
+ # The source is the one authoritative copy, also while a staging maintains another: the count
66
+ # reads it and a query must name it. A query written for the copy stays refused until the
67
+ # cutover makes the copy the source.
68
+ return placement.groups["WeatherReading"].source
69
+
70
+
71
+ def count_sql(sql: Any, *, table: str, dialect: str) -> str:
72
+ """Permit exactly one count over the named source; no expressions, settings or comments."""
73
+ if (
74
+ not isinstance(sql, str)
75
+ or not 1 <= len(sql) <= 8192
76
+ or dialect not in ("postgres", "clickhouse")
77
+ ):
78
+ raise DemoRefused("The demo accepts one bounded COUNT(*) statement on its current source.")
79
+ identifier = re.escape(QUOTE[dialect](table))
80
+ if re.fullmatch(r"[a-z_][a-z0-9_]*", table):
81
+ # PostgreSQL folds unquoted names; quoted names and ClickHouse names remain exact.
82
+ bare = "(?i:" + re.escape(table) + ")" if dialect == "postgres" else re.escape(table)
83
+ quoted_alternative = "|" + re.escape('"' + table + '"') if dialect == "clickhouse" else ""
84
+ identifier = "(?:" + identifier + "|" + bare + quoted_alternative + ")"
85
+ alias_name = r'[A-Za-z_][A-Za-z0-9_]*|"[A-Za-z_][A-Za-z0-9_]*"'
86
+ if dialect == "clickhouse":
87
+ alias_name += r"|`[A-Za-z_][A-Za-z0-9_]*`"
88
+ alias = r"(?:\s+(?i:AS)\s+(?:" + alias_name + r"))?"
89
+ final = r"\s+(?i:FINAL)" if dialect == "clickhouse" else ""
90
+ pattern = (
91
+ r"\s*(?i:SELECT)\s+(?i:COUNT)\s*\(\s*\*\s*\)"
92
+ + alias
93
+ + r"\s+(?i:FROM)\s+"
94
+ + identifier
95
+ + final
96
+ + r"\s*;?\s*"
97
+ )
98
+ # SQL token folding is ASCII. Unicode IGNORECASE would also accept dotless i/long s/Kelvin
99
+ # as another identifier and could execute a count on a different accessible native table.
100
+ if re.fullmatch(pattern, sql, flags=re.ASCII) is None:
101
+ raise DemoRefused(
102
+ "Use SELECT COUNT(*) [AS alias] FROM the exact Weather source "
103
+ "(with FINAL on ClickHouse); other SQL is outside this demo."
104
+ )
105
+ return sql
106
+
107
+
108
+ def _query(value: Any, placement: sde.PlacementMap, project_id: str, dialect: str) -> str:
109
+ source = _source(placement, project_id)
110
+ if not isinstance(value, dict) or not isinstance(value.get("stamp"), dict):
111
+ raise DemoRefused("Supply the approved query record, including its stamp.")
112
+ stamp = value["stamp"]
113
+ if (
114
+ value.get("group") != "WeatherReading"
115
+ or value.get("engine") != source.engine
116
+ or value.get("materialization") != source.id
117
+ or value.get("dialect") != dialect
118
+ or stamp.get("model_version") != placement.model_version
119
+ or type(stamp.get("map_version")) is not int
120
+ or stamp["map_version"] != placement.map_version
121
+ or stamp.get("materialization") != source.id
122
+ ):
123
+ raise DemoRefused("The query does not describe this project's current source and map.")
124
+ return count_sql(
125
+ value.get("sql"), table=source.layout.table_for("WeatherReading"), dialect=dialect
126
+ )
127
+
128
+
129
+ def make_count_request(
130
+ *,
131
+ project_id: str,
132
+ name: str,
133
+ revision: int,
134
+ query: Mapping[str, Any],
135
+ placement: sde.PlacementMap,
136
+ dialect: str,
137
+ ) -> dict[str, Any]:
138
+ """Prepare metadata for an authenticated handoff; no connection or customer file is used."""
139
+ if (
140
+ not isinstance(name, str)
141
+ or not name
142
+ or len(name) > 128
143
+ or type(revision) is not int
144
+ or revision < 1
145
+ ):
146
+ raise DemoRefused("The count handoff needs a query name and positive integer revision.")
147
+ copied = json.loads(_bytes(dict(query)))
148
+ _query(copied, placement, project_id, dialect)
149
+ document = {
150
+ "kind": KIND,
151
+ "protocol": 1,
152
+ "project_id": project_id,
153
+ "name": name,
154
+ "revision": revision,
155
+ "map_fingerprint": placement.fingerprint,
156
+ "query": copied,
157
+ }
158
+ document["digest"] = _digest(document)
159
+ return document
160
+
161
+
162
+ @dataclass(frozen=True)
163
+ class CountRequest:
164
+ name: str
165
+ revision: int
166
+ sql: str
167
+ engine: str
168
+ dialect: str
169
+ digest: str
170
+ map_fingerprint: str
171
+
172
+
173
+ def load_count_request(
174
+ value: Mapping[str, Any],
175
+ *,
176
+ project_id: str,
177
+ placement: sde.PlacementMap,
178
+ dialects: Mapping[str, str],
179
+ ) -> CountRequest:
180
+ copied = json.loads(_bytes(dict(value)))
181
+ if (
182
+ set(copied) != FIELDS
183
+ or copied["kind"] != KIND
184
+ or type(copied["protocol"]) is not int
185
+ or copied["protocol"] != 1
186
+ or copied["project_id"] != project_id
187
+ or copied["map_fingerprint"] != placement.fingerprint
188
+ or copied["digest"] != _digest(copied)
189
+ ):
190
+ raise DemoRefused(
191
+ "The count handoff is incomplete, changed or belongs to another map/project."
192
+ )
193
+ source = _source(placement, project_id)
194
+ if source.engine not in dialects or dialects[source.engine] not in ("postgres", "clickhouse"):
195
+ raise DemoRefused("The count source has no supported local runtime binding.")
196
+ rebuilt = make_count_request(
197
+ project_id=project_id,
198
+ name=copied["name"],
199
+ revision=copied["revision"],
200
+ query=copied["query"],
201
+ placement=placement,
202
+ dialect=dialects[source.engine],
203
+ )
204
+ if copied != rebuilt:
205
+ raise DemoRefused("The count handoff has inconsistent metadata.")
206
+ return CountRequest(
207
+ copied["name"],
208
+ copied["revision"],
209
+ copied["query"]["sql"],
210
+ source.engine,
211
+ dialects[source.engine],
212
+ copied["digest"],
213
+ str(placement.fingerprint),
214
+ )
215
+
216
+
217
+ def _count(adapter: Any, query: str) -> int:
218
+ if adapter.dialect == "postgres":
219
+ with adapter._cx.transaction(), adapter._cx.cursor() as cursor:
220
+ cursor.execute("SET TRANSACTION READ ONLY")
221
+ cursor.execute("SET LOCAL statement_timeout = '10000ms'")
222
+ cursor.execute("SET LOCAL lock_timeout = '2000ms'")
223
+ sde.explain(adapter, query)
224
+ cursor.execute(query)
225
+ rows = cursor.fetchall()
226
+ else:
227
+ settings = {"readonly": 1, "max_execution_time": 10, "timeout_overflow_mode": "throw"}
228
+ # The SQL grammar cannot override these settings or call external table functions.
229
+ adapter._cx.query("EXPLAIN PLAN " + query, settings=settings)
230
+ rows = adapter._cx.query(query, settings=settings).result_rows
231
+ if len(rows) != 1 or len(rows[0]) != 1 or type(rows[0][0]) is not int or rows[0][0] < 0:
232
+ raise DemoRefused("The count result is not one exact nonnegative integer.")
233
+ return rows[0][0]
234
+
235
+
236
+ def run_count_query(root: Path, document: Mapping[str, Any]) -> dict[str, Any]:
237
+ from .verification import load_completed_runs, verify_runs
238
+
239
+ settings, logical = config(root), model()
240
+ keys = public_keys(settings["public_keys"])
241
+
242
+ def current() -> sde.PlacementMap:
243
+ return sde.load_local_map(
244
+ root / "state", model=logical, project_id=settings["project_id"], public_key=keys
245
+ )
246
+
247
+ placement = current()
248
+ request = load_count_request(
249
+ document,
250
+ project_id=settings["project_id"],
251
+ placement=placement,
252
+ dialects={name: item["dialect"] for name, item in settings["engines"].items()},
253
+ )
254
+ ids = sorted(path.name for path in (root / "runs").iterdir())
255
+ runs = load_completed_runs(root, ids)
256
+ if not runs:
257
+ raise DemoRefused("Finish a demo workload before running its count oracle.")
258
+ verify_runs(root, ids)
259
+ if current().fingerprint != placement.fingerprint:
260
+ raise DemoRefused("The local map changed during data verification; retry after cutover.")
261
+ # A staging's map also names the engine of the copy it maintains, and the session checks the
262
+ # map against every engine it names; the count itself runs on the source's adapter. Each has a
263
+ # local binding: the run verification above refused this map otherwise.
264
+ named = sorted({item.engine for group in placement.groups.values() for item in group.all()})
265
+ dsns = credentials(root, "runtime", settings["engines"])
266
+ with ExitStack() as cleanup: # every adapter is closed, even if an earlier one cannot close
267
+ adapters: dict[str, Any] = {}
268
+ for name in named:
269
+ adapters[name] = engine(settings["engines"][name]["dialect"], dsns[name])
270
+ cleanup.callback(adapters[name].close)
271
+ adapters[name].connect()
272
+ with sde.Session(logical, placement, adapters, project_id=settings["project_id"]):
273
+ total = _count(adapters[request.engine], request.sql)
274
+ if total != sum(run.expected_rows for run in runs):
275
+ raise DemoRefused("The local SQL result differs from the completed synthetic runs.")
276
+ if (
277
+ current().fingerprint != placement.fingerprint
278
+ or ids != sorted(path.name for path in (root / "runs").iterdir())
279
+ or load_completed_runs(root, ids) != runs
280
+ ):
281
+ raise DemoRefused(
282
+ "The map or run catalog changed during the query; retry after it settles."
283
+ )
284
+ identity = uuid4().hex
285
+ receipt = {
286
+ "protocol": 1,
287
+ "kind": "sde-weather-count-observation",
288
+ "execution_id": identity,
289
+ "project_id": settings["project_id"],
290
+ "map_fingerprint": request.map_fingerprint,
291
+ "query_name": request.name,
292
+ "query_revision": request.revision,
293
+ "query_digest": request.digest,
294
+ "verified": True,
295
+ "run_ids": ids,
296
+ "observed_at": datetime.now(UTC).isoformat(),
297
+ }
298
+ # Business values exist only in this private local result file, never in the exported receipt.
299
+ write(root / "query-results" / identity / "result.json", {"count": total, "receipt": receipt})
300
+ write(root / "query-results" / identity / "receipt.json", receipt)
301
+ return receipt