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
@@ -0,0 +1,393 @@
1
+ """Durable preparation of a fresh physical copy using only customer-local connections."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from time import monotonic_ns
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from .errors import MigrationRefused
10
+ from .groups import colocation_groups
11
+ from .local_cutover import CutoverRecoveryRequired
12
+ from .physical import refuse_findings
13
+ from .placement import WATERMARK_TABLE
14
+ from .staging import StagingPlan, StagingReceipt, load_staging_plan
15
+
16
+ if TYPE_CHECKING:
17
+ from .local_cutover import LocalCutover
18
+
19
+
20
+ def _snapshot(operator: LocalCutover, plan: StagingPlan, state: dict[str, Any]) -> dict[str, Any]:
21
+ from .engines._staging import NativeStaging, creation_marker
22
+
23
+ plan.check_current(operator.active_map())
24
+ needed = {
25
+ material.engine for placed in plan.prepared.groups.values() for material in placed.all()
26
+ }
27
+ if not needed <= set(operator.engines):
28
+ raise MigrationRefused("staging is missing a configured local engine binding")
29
+ group = next(value for value in colocation_groups(operator.model) if value.name == plan.group)
30
+ target = plan.prepared.groups[plan.group].derived[0]
31
+ keys = {entity: operator.model.entity(entity).key for entity in group.members}
32
+ NativeStaging(operator.native[target.engine]).preflight(target.layout, keys)
33
+ sources = []
34
+ allowed: dict[str, set[str]] = {name: {WATERMARK_TABLE} for name in operator.engines}
35
+ for placed in plan.current.groups.values():
36
+ for material in placed.all():
37
+ engine = operator.engines[material.engine]
38
+ engine.validate_schema(material.layout)
39
+ for table in material.layout.tables.values():
40
+ allowed[material.engine].add(table)
41
+ identity = operator.native[material.engine].identity(table)
42
+ state_ = engine.write_fence(table, project_id=operator.project_id).state()
43
+ if not state_.complete or state_.epoch != placed.write_epoch or state_.holds:
44
+ raise MigrationRefused(
45
+ "staging needs the current generation without another barrier"
46
+ )
47
+ sources.append(
48
+ {
49
+ "engine": material.engine,
50
+ "identity": identity.as_record(),
51
+ "epoch": placed.write_epoch,
52
+ }
53
+ )
54
+ allowed[target.engine].update(target.layout.tables.values())
55
+ bindings = {}
56
+ for name, native in operator.native.items():
57
+ value = operator.engines[name].map_watermark()
58
+ if value is not None and value > plan.current.map_version:
59
+ raise MigrationRefused("a newer map was adopted before local staging")
60
+ native.qualify([WATERMARK_TABLE], sorted(allowed[name]))
61
+ bindings[name] = {
62
+ "endpoint": list(native.endpoint()),
63
+ "watermark_identity": native.identity(WATERMARK_TABLE).as_record(),
64
+ "principals": dict(native.principal_ids),
65
+ "allowed_tables": sorted(allowed[name]),
66
+ }
67
+ table_names = set(target.layout.tables.values())
68
+ if any(row[-1] in table_names for row in state["retired_names"]):
69
+ raise MigrationRefused("staging cannot reuse a retired physical name")
70
+ for previous in state.get("stages", {}).values():
71
+ # From the stored authorization, not the receipt: an abandoned staging's receipt names no
72
+ # identity for a table it never created, and its names are spent all the same.
73
+ record = previous["plan"]
74
+ used = record["prepared"]["groups"][record["group"]]["derived"][0]["layout"]["tables"]
75
+ if table_names & set(used.values()):
76
+ raise MigrationRefused("staging cannot reuse a previously prepared physical name")
77
+ return {
78
+ "kind": "staging",
79
+ "plan_id": plan.stage_id,
80
+ "plan_fingerprint": plan.fingerprint,
81
+ "plan": plan.as_record(),
82
+ "phase": "stage_prepared",
83
+ "pending_hold": None,
84
+ "decision": None,
85
+ "sources": sources,
86
+ "bindings": bindings,
87
+ "tables": [
88
+ {
89
+ "entity": entity,
90
+ "engine": target.engine,
91
+ "table": table,
92
+ "marker": creation_marker(operator.project_id, plan.stage_id, table),
93
+ "identity": None,
94
+ }
95
+ for entity, table in sorted(target.layout.tables.items())
96
+ ],
97
+ }
98
+
99
+
100
+ def _recheck_sources(operator: LocalCutover, execution: dict[str, Any]) -> None:
101
+ from .engines._operator import TableIdentity
102
+
103
+ for row in execution["sources"]:
104
+ expected = TableIdentity(**row["identity"])
105
+ native = operator.native[row["engine"]]
106
+ if native.identity(expected.name).physical_key != expected.physical_key:
107
+ raise MigrationRefused("a current table was replaced during staging")
108
+ state = (
109
+ operator.engines[row["engine"]]
110
+ .write_fence(expected.name, project_id=operator.project_id)
111
+ .state()
112
+ )
113
+ if not state.complete or state.epoch != row["epoch"] or state.holds:
114
+ raise MigrationRefused("the current write generation changed during staging")
115
+
116
+
117
+ def _finish(
118
+ operator: LocalCutover, state: dict[str, Any], plan: StagingPlan, *, recovered: bool
119
+ ) -> StagingReceipt:
120
+ from .engines._operator import TableIdentity
121
+ from .engines._staging import NativeStaging
122
+
123
+ execution = state["execution"]
124
+ if execution["decision"] == "abandoned":
125
+ return _abandon(operator, state, plan, recovered=recovered)
126
+ target = plan.prepared.groups[plan.group].derived[0]
127
+ epoch = plan.prepared.groups[plan.group].write_epoch
128
+ assert epoch is not None
129
+ keys = {entity: operator.model.entity(entity).key for entity in target.layout.tables}
130
+ for name, binding in execution["bindings"].items():
131
+ native = operator.native[name]
132
+ if list(native.endpoint()) != binding["endpoint"]:
133
+ raise MigrationRefused("a staging binding names another native database")
134
+ expected_watermark = TableIdentity(**binding["watermark_identity"])
135
+ if native.identity(WATERMARK_TABLE).physical_key != expected_watermark.physical_key:
136
+ raise MigrationRefused("a staging namespace or watermark identity changed")
137
+ native.qualify([WATERMARK_TABLE], binding["allowed_tables"])
138
+ if native.principal_ids != binding["principals"]:
139
+ raise MigrationRefused("a runtime login identity changed during staging")
140
+ _recheck_sources(operator, execution)
141
+ creator = NativeStaging(operator.native[target.engine])
142
+ for row in execution["tables"]:
143
+
144
+ def create(row: dict[str, Any] = row) -> None:
145
+ identity = creator.create_table(
146
+ entity=row["entity"], layout=target.layout, keys=keys, marker=row["marker"]
147
+ )
148
+ if row["identity"] is not None and identity.as_record() != row["identity"]:
149
+ raise MigrationRefused("a stage-owned table was replaced")
150
+ row["identity"] = identity.as_record()
151
+
152
+ operator._step(state, "stage_create_" + row["entity"], create)
153
+
154
+ def prepare_generation(row: dict[str, Any] = row) -> None:
155
+ operator.engines[target.engine].write_fence(
156
+ row["table"], project_id=operator.project_id
157
+ ).prepare(epoch)
158
+
159
+ operator._step(state, "stage_generation_" + row["entity"], prepare_generation)
160
+ operator._step(state, "stage_indexes", lambda: creator.create_indexes(target.layout))
161
+
162
+ def qualify() -> None:
163
+ # The fresh copy must be exactly the physical design its signed map authorized.
164
+ refuse_findings(
165
+ operator.engines[target.engine].validate_schema(target.layout, keys=keys),
166
+ MigrationRefused,
167
+ )
168
+ native = operator.native[target.engine]
169
+ native.qualify(
170
+ sorted(target.layout.tables.values()),
171
+ execution["bindings"][target.engine]["allowed_tables"],
172
+ required_access=False,
173
+ )
174
+ if native.principal_ids != execution["bindings"][target.engine]["principals"]:
175
+ raise MigrationRefused("a runtime login changed before staging grants")
176
+ for row in execution["tables"]:
177
+ expected = TableIdentity(**row["identity"])
178
+ if native.identity(expected.name).physical_key != expected.physical_key:
179
+ raise MigrationRefused("a stage-owned table was replaced before activation")
180
+ observed = (
181
+ operator.engines[target.engine]
182
+ .write_fence(expected.name, project_id=operator.project_id)
183
+ .state()
184
+ )
185
+ if not observed.complete or observed.epoch != epoch or observed.closed:
186
+ raise MigrationRefused("the staged write generation is not ready for activation")
187
+
188
+ operator._step(state, "stage_qualify", qualify)
189
+ operator._step(
190
+ state,
191
+ "stage_grants",
192
+ lambda: operator.native[target.engine].access(
193
+ [TableIdentity(**row["identity"]) for row in execution["tables"]], enabled=True
194
+ ),
195
+ )
196
+ _recheck_sources(operator, execution)
197
+ qualify()
198
+ execution["decision"] = "prepared"
199
+ operator.store.write(state)
200
+ operator._after_step("stage_decision")
201
+
202
+ def watermarks() -> None:
203
+ for engine in operator.engines.values():
204
+ observed = engine.map_watermark()
205
+ if observed is not None and observed > plan.prepared.map_version:
206
+ raise MigrationRefused("a newer map appeared while finishing staging")
207
+ engine.record_map_version(
208
+ plan.prepared.map_version, model_version=operator.model.version
209
+ )
210
+
211
+ operator._step(state, "stage_watermarks", watermarks)
212
+
213
+ def publish() -> None:
214
+ current = operator.active_map()
215
+ if current.fingerprint not in (plan.current.fingerprint, plan.prepared.fingerprint):
216
+ raise MigrationRefused("another active map replaced the staging instruction")
217
+ operator.store.publish(plan.prepared_payload())
218
+
219
+ operator._step(state, "stage_publish", publish)
220
+ receipt = {
221
+ "protocol": 1,
222
+ "stage_id": plan.stage_id,
223
+ "stage_fingerprint": plan.fingerprint,
224
+ "project_id": operator.project_id,
225
+ "group": plan.group,
226
+ "outcome": "prepared",
227
+ "map_version": plan.prepared.map_version,
228
+ "map_fingerprint": plan.prepared.fingerprint,
229
+ "tables": [
230
+ {"engine": row["engine"], "entity": row["entity"], "identity": row["identity"]}
231
+ for row in execution["tables"]
232
+ ],
233
+ "elapsed_ms": operator._elapsed(),
234
+ "recovered": recovered,
235
+ }
236
+ state["stages"][plan.stage_id] = {
237
+ "plan_fingerprint": plan.fingerprint,
238
+ "plan": plan.as_record(),
239
+ "receipt": receipt,
240
+ }
241
+ state["active_map"], state["execution"] = json.loads(plan.prepared_payload()), None
242
+ operator.store.write(state)
243
+ operator._after_step("stage_complete")
244
+ return StagingReceipt(receipt)
245
+
246
+
247
+ def _abandon(
248
+ operator: LocalCutover, state: dict[str, Any], plan: StagingPlan, *, recovered: bool
249
+ ) -> StagingReceipt:
250
+ """Remove this staging's own tables and keep the map in force; the authorization is spent.
251
+
252
+ It needs only the same native database: not the runtime logins, not a source free of another
253
+ barrier - the things whose absence is why a staging cannot finish - because it publishes
254
+ nothing. Anything under a staging name that is not this staging's is left where it is.
255
+ """
256
+ from .engines._operator import TableIdentity
257
+ from .engines._staging import NativeStaging
258
+
259
+ execution = state["execution"]
260
+ target = plan.prepared.groups[plan.group].derived[0]
261
+ binding = execution["bindings"][target.engine]
262
+ native = operator.native[target.engine]
263
+ if list(native.endpoint()) != binding["endpoint"]:
264
+ raise MigrationRefused("a staging binding names another native database")
265
+ creator = NativeStaging(native)
266
+ for row in execution["tables"]:
267
+
268
+ def drop(row: dict[str, Any] = row) -> None:
269
+ if "removed" not in row:
270
+ recorded = None if row["identity"] is None else TableIdentity(**row["identity"])
271
+ found = creator.owned(row["table"], row["marker"], recorded)
272
+ row["removed"] = None if found is None else found.as_record()
273
+ # Durable before the destructive statement: a crash after DROP must still know
274
+ # which object the receipt removed.
275
+ operator.store.write(state)
276
+ if row["removed"] is not None:
277
+ creator.drop_owned(TableIdentity(**row["removed"]), row["marker"])
278
+
279
+ operator._step(state, "stage_drop_" + row["entity"], drop)
280
+ removed = [
281
+ TableIdentity(**row["removed"]) for row in execution["tables"] if row["removed"] is not None
282
+ ]
283
+ if native.dialect == "clickhouse" and removed:
284
+ operator._step(
285
+ state,
286
+ "stage_revoke",
287
+ lambda: creator.revoke_runtime(removed, sorted(binding["principals"])),
288
+ )
289
+ if operator.active_map().fingerprint != plan.current.fingerprint:
290
+ raise MigrationRefused("an abandoned staging found another active map")
291
+ receipt = {
292
+ "protocol": 1,
293
+ "stage_id": plan.stage_id,
294
+ "stage_fingerprint": plan.fingerprint,
295
+ "project_id": operator.project_id,
296
+ "group": plan.group,
297
+ "outcome": "abandoned",
298
+ "map_version": plan.current.map_version,
299
+ "map_fingerprint": plan.current.fingerprint,
300
+ "tables": [
301
+ {"engine": row["engine"], "entity": row["entity"], "identity": row["removed"]}
302
+ for row in execution["tables"]
303
+ ],
304
+ "elapsed_ms": operator._elapsed(),
305
+ "recovered": recovered,
306
+ }
307
+ state["stages"][plan.stage_id] = {
308
+ "plan_fingerprint": plan.fingerprint,
309
+ "plan": plan.as_record(),
310
+ "receipt": receipt,
311
+ }
312
+ state.setdefault("indexes", {}) # an abandoned staging record is storage contract 4
313
+ state["execution"] = None
314
+ operator.store.write(state)
315
+ operator._after_step("stage_complete")
316
+ return StagingReceipt(receipt)
317
+
318
+
319
+ def abandon_stage(operator: LocalCutover) -> StagingReceipt:
320
+ """Abandon the unfinished staging before its decision: its own tables go, the map stays."""
321
+ with operator.store.lock():
322
+ state = operator.store.read()
323
+ execution = state["execution"]
324
+ if execution is None or execution.get("kind") != "staging":
325
+ raise MigrationRefused("there is no unfinished staging to abandon")
326
+ if execution["decision"] == "prepared":
327
+ raise MigrationRefused(
328
+ "a prepared staging cannot be abandoned: its next map is decided; resume to "
329
+ "publish it, or abort its cutover"
330
+ )
331
+ plan = load_staging_plan(
332
+ execution["plan"],
333
+ model=operator.model,
334
+ project_id=operator.project_id,
335
+ public_key=operator.keys,
336
+ )
337
+ if plan.fingerprint != execution["plan_fingerprint"]:
338
+ raise MigrationRefused("staging recovery names another signed authorization")
339
+ if execution["decision"] is None:
340
+ execution["decision"] = "abandoned"
341
+ operator.store.write(state)
342
+ operator._after_step("stage_abandoned")
343
+ operator._started, operator._enforce_budget = monotonic_ns(), False
344
+ try:
345
+ return _finish(operator, state, plan, recovered=True)
346
+ except Exception as exc:
347
+ raise CutoverRecoveryRequired(
348
+ "abandoning the staging remains incomplete; preserve its state"
349
+ ) from exc
350
+
351
+
352
+ def execute_stage(operator: LocalCutover, plan: StagingPlan) -> StagingReceipt:
353
+ with operator.store.lock():
354
+ state = operator.store.read()
355
+ complete = state.get("stages", {}).get(plan.stage_id)
356
+ if complete is not None:
357
+ if complete["plan_fingerprint"] != plan.fingerprint:
358
+ raise MigrationRefused("the completed staging id names another authorization")
359
+ operator.store.confirm()
360
+ return StagingReceipt(complete["receipt"])
361
+ if state["execution"] is not None:
362
+ raise CutoverRecoveryRequired("an unfinished local operation requires resume")
363
+ execution = _snapshot(operator, plan, state)
364
+ state.setdefault("stages", {})
365
+ state["execution"] = execution
366
+ operator.store.write(state)
367
+ operator._after_step("stage_prepared")
368
+ operator._started, operator._enforce_budget = monotonic_ns(), False
369
+ try:
370
+ return _finish(operator, state, plan, recovered=False)
371
+ except Exception as exc:
372
+ raise CutoverRecoveryRequired(
373
+ "local staging needs recovery; preserve its project state"
374
+ ) from exc
375
+
376
+
377
+ def resume_stage(operator: LocalCutover, state: dict[str, Any]) -> StagingReceipt:
378
+ execution = state["execution"]
379
+ plan = load_staging_plan(
380
+ execution["plan"],
381
+ model=operator.model,
382
+ project_id=operator.project_id,
383
+ public_key=operator.keys,
384
+ )
385
+ if plan.fingerprint != execution["plan_fingerprint"]:
386
+ raise MigrationRefused("staging recovery names another signed authorization")
387
+ operator._started, operator._enforce_budget = monotonic_ns(), False
388
+ try:
389
+ return _finish(operator, state, plan, recovered=True)
390
+ except Exception as exc:
391
+ raise CutoverRecoveryRequired(
392
+ "staging recovery remains incomplete; preserve its state"
393
+ ) from exc