contractforge-databricks 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.
- contractforge_databricks/__init__.py +172 -0
- contractforge_databricks/adapter.py +69 -0
- contractforge_databricks/annotations/__init__.py +10 -0
- contractforge_databricks/annotations/application.py +52 -0
- contractforge_databricks/annotations/audit.py +49 -0
- contractforge_databricks/annotations/sql.py +142 -0
- contractforge_databricks/api.py +65 -0
- contractforge_databricks/bundles/__init__.py +9 -0
- contractforge_databricks/bundles/assets.py +47 -0
- contractforge_databricks/bundles/project.py +213 -0
- contractforge_databricks/bundles/project_config.py +133 -0
- contractforge_databricks/capabilities/__init__.py +17 -0
- contractforge_databricks/capabilities/builders.py +43 -0
- contractforge_databricks/capabilities/evaluate.py +162 -0
- contractforge_databricks/capabilities/mapping.py +36 -0
- contractforge_databricks/capabilities/models.py +44 -0
- contractforge_databricks/capabilities/runtime.py +111 -0
- contractforge_databricks/capabilities/uc.py +47 -0
- contractforge_databricks/cli.py +196 -0
- contractforge_databricks/cli_deploy.py +98 -0
- contractforge_databricks/cli_governance.py +142 -0
- contractforge_databricks/cli_io.py +91 -0
- contractforge_databricks/cli_maintenance.py +69 -0
- contractforge_databricks/coercion.py +31 -0
- contractforge_databricks/contract_extensions.py +70 -0
- contractforge_databricks/cost/__init__.py +11 -0
- contractforge_databricks/cost/model.py +22 -0
- contractforge_databricks/cost/report.py +65 -0
- contractforge_databricks/cost/sql.py +136 -0
- contractforge_databricks/dashboards/__init__.py +15 -0
- contractforge_databricks/dashboards/control_tables.py +150 -0
- contractforge_databricks/diagnostics/__init__.py +7 -0
- contractforge_databricks/diagnostics/explain.py +40 -0
- contractforge_databricks/environment.py +53 -0
- contractforge_databricks/evidence/__init__.py +98 -0
- contractforge_databricks/evidence/ddl.py +35 -0
- contractforge_databricks/evidence/governance_log.py +175 -0
- contractforge_databricks/evidence/helpers.py +29 -0
- contractforge_databricks/evidence/ops_log.py +210 -0
- contractforge_databricks/evidence/records.py +27 -0
- contractforge_databricks/evidence/run_log.py +74 -0
- contractforge_databricks/evidence/schemas.py +7 -0
- contractforge_databricks/evidence/sql.py +144 -0
- contractforge_databricks/evidence/tables.py +20 -0
- contractforge_databricks/evidence/writer.py +118 -0
- contractforge_databricks/execution/__init__.py +70 -0
- contractforge_databricks/execution/delta_basic.py +57 -0
- contractforge_databricks/execution/hash_diff.py +126 -0
- contractforge_databricks/execution/hash_diff_latest.py +142 -0
- contractforge_databricks/execution/replace_partitions.py +40 -0
- contractforge_databricks/execution/results.py +5 -0
- contractforge_databricks/execution/retry.py +36 -0
- contractforge_databricks/execution/scd2.py +213 -0
- contractforge_databricks/execution/scd2_deletes.py +65 -0
- contractforge_databricks/execution/scd2_late.py +30 -0
- contractforge_databricks/execution/snapshot.py +77 -0
- contractforge_databricks/execution/sql_merge.py +85 -0
- contractforge_databricks/execution/tables.py +98 -0
- contractforge_databricks/execution/windows.py +58 -0
- contractforge_databricks/governance/__init__.py +30 -0
- contractforge_databricks/governance/access.py +185 -0
- contractforge_databricks/governance/application.py +93 -0
- contractforge_databricks/governance/drift.py +49 -0
- contractforge_databricks/governance/runtime.py +60 -0
- contractforge_databricks/governance/sql.py +31 -0
- contractforge_databricks/governance/validation.py +135 -0
- contractforge_databricks/lakeflow/__init__.py +21 -0
- contractforge_databricks/lakeflow/compatibility.py +194 -0
- contractforge_databricks/lakeflow/rendering.py +175 -0
- contractforge_databricks/lineage/__init__.py +7 -0
- contractforge_databricks/lineage/openlineage.py +182 -0
- contractforge_databricks/maintenance/__init__.py +27 -0
- contractforge_databricks/maintenance/retention.py +90 -0
- contractforge_databricks/maintenance/sql.py +68 -0
- contractforge_databricks/metrics/__init__.py +19 -0
- contractforge_databricks/metrics/history.py +21 -0
- contractforge_databricks/metrics/write.py +63 -0
- contractforge_databricks/operations/__init__.py +4 -0
- contractforge_databricks/operations/application.py +38 -0
- contractforge_databricks/operations/sql.py +95 -0
- contractforge_databricks/parity/__init__.py +18 -0
- contractforge_databricks/parity/catalog.py +59 -0
- contractforge_databricks/parity/models.py +7 -0
- contractforge_databricks/parity/scenarios.py +111 -0
- contractforge_databricks/partitioning/__init__.py +3 -0
- contractforge_databricks/partitioning/predicates.py +28 -0
- contractforge_databricks/preparation/__init__.py +47 -0
- contractforge_databricks/preparation/deduplicate.py +87 -0
- contractforge_databricks/preparation/encoding.py +37 -0
- contractforge_databricks/preparation/hashing.py +18 -0
- contractforge_databricks/preparation/pyspark.py +178 -0
- contractforge_databricks/preparation/pyspark_staging.py +70 -0
- contractforge_databricks/preparation/shape.py +209 -0
- contractforge_databricks/preparation/shape_validation.py +94 -0
- contractforge_databricks/preparation/staging.py +17 -0
- contractforge_databricks/preparation/zip_arrays.py +51 -0
- contractforge_databricks/presets/__init__.py +3 -0
- contractforge_databricks/presets/base.py +24 -0
- contractforge_databricks/presets/bronze.py +57 -0
- contractforge_databricks/presets/catalog.py +22 -0
- contractforge_databricks/presets/core.py +134 -0
- contractforge_databricks/presets/gold.py +62 -0
- contractforge_databricks/presets/modifiers.py +51 -0
- contractforge_databricks/presets/runtime.py +22 -0
- contractforge_databricks/presets/silver.py +101 -0
- contractforge_databricks/presets/write_engine.py +57 -0
- contractforge_databricks/quality/__init__.py +41 -0
- contractforge_databricks/quality/evaluation.py +178 -0
- contractforge_databricks/quality/persistence.py +81 -0
- contractforge_databricks/quality/registry.py +134 -0
- contractforge_databricks/quality/results.py +17 -0
- contractforge_databricks/quality/sql.py +113 -0
- contractforge_databricks/rendering/__init__.py +11 -0
- contractforge_databricks/rendering/bundle.py +93 -0
- contractforge_databricks/rendering/markdown.py +50 -0
- contractforge_databricks/rendering/names.py +56 -0
- contractforge_databricks/results.py +15 -0
- contractforge_databricks/runtime/__init__.py +101 -0
- contractforge_databricks/runtime/available_now.py +147 -0
- contractforge_databricks/runtime/bundles.py +211 -0
- contractforge_databricks/runtime/cache.py +20 -0
- contractforge_databricks/runtime/control_tables.py +19 -0
- contractforge_databricks/runtime/deploy.py +197 -0
- contractforge_databricks/runtime/detection.py +114 -0
- contractforge_databricks/runtime/dry_run.py +46 -0
- contractforge_databricks/runtime/errors.py +54 -0
- contractforge_databricks/runtime/file_selection.py +109 -0
- contractforge_databricks/runtime/finalization.py +168 -0
- contractforge_databricks/runtime/governance.py +37 -0
- contractforge_databricks/runtime/hooks.py +45 -0
- contractforge_databricks/runtime/http_file.py +37 -0
- contractforge_databricks/runtime/http_retry.py +15 -0
- contractforge_databricks/runtime/http_safety.py +9 -0
- contractforge_databricks/runtime/json_materialization.py +97 -0
- contractforge_databricks/runtime/lineage.py +164 -0
- contractforge_databricks/runtime/maintenance.py +43 -0
- contractforge_databricks/runtime/merge_validation.py +98 -0
- contractforge_databricks/runtime/metadata.py +21 -0
- contractforge_databricks/runtime/metrics.py +34 -0
- contractforge_databricks/runtime/models.py +32 -0
- contractforge_databricks/runtime/options.py +33 -0
- contractforge_databricks/runtime/orchestration_context.py +185 -0
- contractforge_databricks/runtime/orchestrator.py +147 -0
- contractforge_databricks/runtime/partitioning.py +93 -0
- contractforge_databricks/runtime/quality_quarantine.py +92 -0
- contractforge_databricks/runtime/rest_api.py +46 -0
- contractforge_databricks/runtime/rest_auth.py +21 -0
- contractforge_databricks/runtime/rest_pagination.py +21 -0
- contractforge_databricks/runtime/run_payload.py +177 -0
- contractforge_databricks/runtime/schema.py +106 -0
- contractforge_databricks/runtime/source_metadata.py +30 -0
- contractforge_databricks/runtime/source_registry.py +43 -0
- contractforge_databricks/runtime/source_schema.py +24 -0
- contractforge_databricks/runtime/sources.py +208 -0
- contractforge_databricks/runtime/spark.py +183 -0
- contractforge_databricks/runtime/spark_defaults.py +35 -0
- contractforge_databricks/runtime/storage_auth.py +132 -0
- contractforge_databricks/runtime/streaming.py +131 -0
- contractforge_databricks/runtime/success.py +104 -0
- contractforge_databricks/runtime/utils.py +52 -0
- contractforge_databricks/runtime/watermark.py +71 -0
- contractforge_databricks/runtime/windows.py +184 -0
- contractforge_databricks/runtime/write.py +66 -0
- contractforge_databricks/runtime/write_flow.py +146 -0
- contractforge_databricks/runtime/write_strategy.py +40 -0
- contractforge_databricks/schema/__init__.py +21 -0
- contractforge_databricks/schema/diff.py +11 -0
- contractforge_databricks/schema/policy.py +33 -0
- contractforge_databricks/schema/sync.py +23 -0
- contractforge_databricks/security/__init__.py +21 -0
- contractforge_databricks/security/errors.py +5 -0
- contractforge_databricks/security/redaction.py +5 -0
- contractforge_databricks/security/secrets.py +114 -0
- contractforge_databricks/security/source_policy.py +17 -0
- contractforge_databricks/shapes/__init__.py +3 -0
- contractforge_databricks/shapes/sql.py +123 -0
- contractforge_databricks/sources/__init__.py +67 -0
- contractforge_databricks/sources/artifacts.py +100 -0
- contractforge_databricks/sources/autoloader.py +48 -0
- contractforge_databricks/sources/bounded_streams.py +44 -0
- contractforge_databricks/sources/classification.py +115 -0
- contractforge_databricks/sources/delta_share.py +21 -0
- contractforge_databricks/sources/files.py +48 -0
- contractforge_databricks/sources/http_file.py +46 -0
- contractforge_databricks/sources/interpret.py +76 -0
- contractforge_databricks/sources/jdbc.py +32 -0
- contractforge_databricks/sources/metadata.py +18 -0
- contractforge_databricks/sources/native_passthrough.py +33 -0
- contractforge_databricks/sources/rds_iam.py +15 -0
- contractforge_databricks/sources/rds_iam_runtime.py +191 -0
- contractforge_databricks/sources/rest_api.py +33 -0
- contractforge_databricks/sources/support.py +50 -0
- contractforge_databricks/sources/table_refs.py +65 -0
- contractforge_databricks/sql/__init__.py +4 -0
- contractforge_databricks/sql/identifiers.py +17 -0
- contractforge_databricks/sql/literals.py +36 -0
- contractforge_databricks/state/__init__.py +39 -0
- contractforge_databricks/state/ddl.py +24 -0
- contractforge_databricks/state/migrations.py +146 -0
- contractforge_databricks/state/queries.py +149 -0
- contractforge_databricks/state/sql.py +116 -0
- contractforge_databricks/state/tables.py +9 -0
- contractforge_databricks/state/writer.py +83 -0
- contractforge_databricks/templates/__init__.py +15 -0
- contractforge_databricks/templates/catalog.py +205 -0
- contractforge_databricks/templates/catalog_parity.py +85 -0
- contractforge_databricks/templates/core.py +83 -0
- contractforge_databricks/templates/enrichment.py +175 -0
- contractforge_databricks/transforms/__init__.py +3 -0
- contractforge_databricks/transforms/sql.py +118 -0
- contractforge_databricks/watermark/__init__.py +6 -0
- contractforge_databricks/watermark/sql.py +91 -0
- contractforge_databricks/write_modes/__init__.py +20 -0
- contractforge_databricks/write_modes/registry.py +44 -0
- contractforge_databricks/write_modes/sql.py +33 -0
- contractforge_databricks/write_modes/strategy.py +192 -0
- contractforge_databricks-0.1.0.dist-info/METADATA +34 -0
- contractforge_databricks-0.1.0.dist-info/RECORD +220 -0
- contractforge_databricks-0.1.0.dist-info/WHEEL +4 -0
- contractforge_databricks-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Full ContractForge run ledger SQL rendering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from contractforge_databricks.evidence.helpers import cast_sql
|
|
9
|
+
from contractforge_databricks.evidence.schemas import EVIDENCE_TABLE_COLUMNS
|
|
10
|
+
from contractforge_databricks.evidence.tables import evidence_table_names
|
|
11
|
+
from contractforge_databricks.security import redact_text, redact_value
|
|
12
|
+
from contractforge_databricks.sql import quote_table_name, sql_int, sql_string
|
|
13
|
+
|
|
14
|
+
RUN_COLUMNS = tuple(column.split(" ", 1)[0] for column in EVIDENCE_TABLE_COLUMNS["runs"])
|
|
15
|
+
RUN_INT_COLUMNS = {
|
|
16
|
+
"rows_read",
|
|
17
|
+
"rows_written",
|
|
18
|
+
"rows_inserted",
|
|
19
|
+
"rows_updated",
|
|
20
|
+
"rows_deleted",
|
|
21
|
+
"rows_expired",
|
|
22
|
+
"rows_quarantined",
|
|
23
|
+
"ctrl_schema_version",
|
|
24
|
+
}
|
|
25
|
+
RUN_FLOAT_COLUMNS = {"duration_seconds"}
|
|
26
|
+
RUN_BOOL_COLUMNS = {"write_committed"}
|
|
27
|
+
RUN_DATE_COLUMNS = {"run_date"}
|
|
28
|
+
RUN_TIMESTAMP_COLUMNS = {
|
|
29
|
+
"run_ts_utc",
|
|
30
|
+
"started_at_utc",
|
|
31
|
+
"finished_at_utc",
|
|
32
|
+
"write_started_at_utc",
|
|
33
|
+
"write_finished_at_utc",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def render_run_log_insert_sql(
|
|
38
|
+
payload: dict[str, Any],
|
|
39
|
+
*,
|
|
40
|
+
catalog: str = "main",
|
|
41
|
+
schema: str = "ops",
|
|
42
|
+
) -> str:
|
|
43
|
+
table = evidence_table_names(catalog, schema)["runs"]
|
|
44
|
+
values = [_render_run_value(column, payload.get(column)) for column in RUN_COLUMNS]
|
|
45
|
+
return (
|
|
46
|
+
f"INSERT INTO {quote_table_name(table)} ({', '.join(RUN_COLUMNS)}) VALUES "
|
|
47
|
+
f"({', '.join(values)})"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _render_run_value(column: str, value: Any) -> str:
|
|
52
|
+
if column in RUN_INT_COLUMNS:
|
|
53
|
+
return sql_int(value)
|
|
54
|
+
if column in RUN_FLOAT_COLUMNS:
|
|
55
|
+
return "NULL" if value is None else str(float(value))
|
|
56
|
+
if column in RUN_BOOL_COLUMNS:
|
|
57
|
+
return "NULL" if value is None else str(bool(value)).lower()
|
|
58
|
+
if column in RUN_DATE_COLUMNS:
|
|
59
|
+
return cast_sql(value, "DATE")
|
|
60
|
+
if column in RUN_TIMESTAMP_COLUMNS or column.endswith("_utc"):
|
|
61
|
+
return cast_sql(value, "TIMESTAMP")
|
|
62
|
+
if column.endswith("_json"):
|
|
63
|
+
return sql_string(_json_text(value))
|
|
64
|
+
if column == "error_message":
|
|
65
|
+
return sql_string(redact_text(str(value))[:4000] if value is not None else None)
|
|
66
|
+
return sql_string(redact_value(value))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _json_text(value: Any) -> str | None:
|
|
70
|
+
if value is None:
|
|
71
|
+
return None
|
|
72
|
+
if isinstance(value, str):
|
|
73
|
+
return redact_text(value)
|
|
74
|
+
return json.dumps(redact_value(value), sort_keys=True, separators=(",", ":"))
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Render INSERT statements for Databricks evidence tables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from contractforge_core.evidence import (
|
|
10
|
+
AccessEvidenceRecord,
|
|
11
|
+
CostEvidenceRecord,
|
|
12
|
+
ErrorEvidenceRecord,
|
|
13
|
+
LineageEvidenceRecord,
|
|
14
|
+
QualityEvidenceRecord,
|
|
15
|
+
QuarantineEvidenceRecord,
|
|
16
|
+
RunEvidenceRecord,
|
|
17
|
+
SchemaChangeEvidenceRecord,
|
|
18
|
+
SourceMetadataEvidenceRecord,
|
|
19
|
+
StreamBatchEvidenceRecord,
|
|
20
|
+
)
|
|
21
|
+
from contractforge_databricks.evidence.tables import evidence_table_names
|
|
22
|
+
from contractforge_databricks.sql import quote_table_name
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def render_run_insert_sql(record: RunEvidenceRecord, *, catalog: str = "main", schema: str = "ops") -> str:
|
|
26
|
+
table = evidence_table_names(catalog, schema)["runs"]
|
|
27
|
+
return (
|
|
28
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
29
|
+
"(run_id, target_table, mode, status, started_at_utc, finished_at_utc, metrics_json) VALUES "
|
|
30
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.mode)}, {_s(record.status)}, "
|
|
31
|
+
f"{_ts(record.started_at_utc)}, {_ts(record.finished_at_utc)}, {_json(record.metrics or {})})"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def render_error_insert_sql(record: ErrorEvidenceRecord, *, catalog: str = "main", schema: str = "ops") -> str:
|
|
36
|
+
table = evidence_table_names(catalog, schema)["errors"]
|
|
37
|
+
return (
|
|
38
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
39
|
+
"(run_id, target_table, error_class, error_message, occurred_at_utc) VALUES "
|
|
40
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.error_class)}, {_s(record.error_message)}, "
|
|
41
|
+
f"{_ts(record.occurred_at_utc)})"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def render_lineage_insert_sql(record: LineageEvidenceRecord, *, catalog: str = "main", schema: str = "ops") -> str:
|
|
46
|
+
table = evidence_table_names(catalog, schema)["lineage"]
|
|
47
|
+
return (
|
|
48
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
49
|
+
"(run_id, target_table, source_name, event_json, event_time_utc) VALUES "
|
|
50
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.source_name)}, {_json(record.event)}, "
|
|
51
|
+
f"{_ts(record.event_time_utc)})"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def render_quality_insert_sql(record: QualityEvidenceRecord, *, catalog: str = "main", schema: str = "ops") -> str:
|
|
56
|
+
table = evidence_table_names(catalog, schema)["quality"]
|
|
57
|
+
return (
|
|
58
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
59
|
+
"(run_id, target_table, rule_name, status, observed_value, checked_at_utc) VALUES "
|
|
60
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.rule_name)}, {_s(record.status)}, "
|
|
61
|
+
f"{_s(record.observed_value)}, {_ts(record.checked_at_utc)})"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def render_schema_change_insert_sql(
|
|
66
|
+
record: SchemaChangeEvidenceRecord, *, catalog: str = "main", schema: str = "ops"
|
|
67
|
+
) -> str:
|
|
68
|
+
table = evidence_table_names(catalog, schema)["schema_changes"]
|
|
69
|
+
return (
|
|
70
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
71
|
+
"(run_id, target_table, change_type, payload_json, changed_at_utc) VALUES "
|
|
72
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.change_type)}, {_json(record.payload)}, "
|
|
73
|
+
f"{_ts(record.changed_at_utc)})"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def render_cost_insert_sql(record: CostEvidenceRecord, *, catalog: str = "main", schema: str = "ops") -> str:
|
|
78
|
+
table = evidence_table_names(catalog, schema)["cost"]
|
|
79
|
+
return (
|
|
80
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
81
|
+
"(run_id, target_table, signal_name, signal_value, payload_json, captured_at_utc) VALUES "
|
|
82
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.signal_name)}, {record.signal_value}, "
|
|
83
|
+
f"{_json(record.payload)}, {_ts(record.captured_at_utc)})"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def render_quarantine_insert_sql(
|
|
88
|
+
record: QuarantineEvidenceRecord, *, catalog: str = "main", schema: str = "ops"
|
|
89
|
+
) -> str:
|
|
90
|
+
table = evidence_table_names(catalog, schema)["quarantine"]
|
|
91
|
+
return (
|
|
92
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
93
|
+
"(run_id, target_table, record_ref, reason, quarantined_at_utc) VALUES "
|
|
94
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.record_ref)}, {_s(record.reason)}, "
|
|
95
|
+
f"{_ts(record.quarantined_at_utc)})"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def render_source_metadata_insert_sql(
|
|
100
|
+
record: SourceMetadataEvidenceRecord, *, catalog: str = "main", schema: str = "ops"
|
|
101
|
+
) -> str:
|
|
102
|
+
table = evidence_table_names(catalog, schema)["metadata"]
|
|
103
|
+
return (
|
|
104
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
105
|
+
"(run_id, target_table, source_metadata_json, captured_at_utc) VALUES "
|
|
106
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_json(record.source_metadata)}, "
|
|
107
|
+
f"{_ts(record.captured_at_utc)})"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def render_stream_batch_insert_sql(
|
|
112
|
+
record: StreamBatchEvidenceRecord, *, catalog: str = "main", schema: str = "ops"
|
|
113
|
+
) -> str:
|
|
114
|
+
table = evidence_table_names(catalog, schema)["streams"]
|
|
115
|
+
return (
|
|
116
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
117
|
+
"(run_id, target_table, batch_id, batch_metrics_json, captured_at_utc) VALUES "
|
|
118
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.batch_id)}, {_json(record.batch_metrics)}, "
|
|
119
|
+
f"{_ts(record.captured_at_utc)})"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def render_access_insert_sql(record: AccessEvidenceRecord, *, catalog: str = "main", schema: str = "ops") -> str:
|
|
124
|
+
table = evidence_table_names(catalog, schema)["access"]
|
|
125
|
+
return (
|
|
126
|
+
f"INSERT INTO {quote_table_name(table)} "
|
|
127
|
+
"(run_id, target_table, action, status, payload_json, applied_at_utc) VALUES "
|
|
128
|
+
f"({_s(record.run_id)}, {_s(record.target_table)}, {_s(record.action)}, {_s(record.status)}, "
|
|
129
|
+
f"{_json(record.payload)}, {_ts(record.applied_at_utc)})"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _json(value: dict[str, Any]) -> str:
|
|
134
|
+
return _s(json.dumps(value, sort_keys=True, separators=(",", ":")))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _s(value: object) -> str:
|
|
138
|
+
return "'" + str(value).replace("'", "''") + "'"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _ts(value: datetime | None) -> str:
|
|
142
|
+
if value is None:
|
|
143
|
+
return "NULL"
|
|
144
|
+
return f"TIMESTAMP {_s(value.strftime('%Y-%m-%d %H:%M:%S'))}"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Databricks implementation notes for the core evidence model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contractforge_core.evidence import EVIDENCE_TABLES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def evidence_table_names(catalog: str, schema: str) -> dict[str, str]:
|
|
9
|
+
return {name: f"{catalog}.{schema}.{table}" for name, table in EVIDENCE_TABLES.items()}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def render_evidence_table_notes(*, catalog: str = "main", schema: str = "ops") -> str:
|
|
13
|
+
lines = [
|
|
14
|
+
"-- Databricks evidence table mapping.",
|
|
15
|
+
"-- These notes are intentionally non-executing review artifacts.",
|
|
16
|
+
"",
|
|
17
|
+
]
|
|
18
|
+
for name, table in evidence_table_names(catalog, schema).items():
|
|
19
|
+
lines.append(f"-- {name}: {table}")
|
|
20
|
+
return "\n".join(lines) + "\n"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Evidence writer using an injected SQL runner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contractforge_core.evidence import (
|
|
6
|
+
AccessEvidenceRecord,
|
|
7
|
+
CostEvidenceRecord,
|
|
8
|
+
ErrorEvidenceRecord,
|
|
9
|
+
LineageEvidenceRecord,
|
|
10
|
+
QualityEvidenceRecord,
|
|
11
|
+
QuarantineEvidenceRecord,
|
|
12
|
+
RunEvidenceRecord,
|
|
13
|
+
SchemaChangeEvidenceRecord,
|
|
14
|
+
SourceMetadataEvidenceRecord,
|
|
15
|
+
StreamBatchEvidenceRecord,
|
|
16
|
+
)
|
|
17
|
+
from contractforge_databricks.evidence.helpers import TimestampClock
|
|
18
|
+
from contractforge_databricks.evidence.ops_log import (
|
|
19
|
+
render_error_log_insert_sql,
|
|
20
|
+
render_schema_change_log_insert_sql,
|
|
21
|
+
render_stream_finish_update_sql,
|
|
22
|
+
render_stream_log_insert_sql,
|
|
23
|
+
)
|
|
24
|
+
from contractforge_databricks.evidence.governance_log import (
|
|
25
|
+
render_access_log_insert_sql,
|
|
26
|
+
render_annotation_log_insert_sql,
|
|
27
|
+
render_operations_log_insert_sql,
|
|
28
|
+
)
|
|
29
|
+
from contractforge_databricks.evidence.run_log import render_run_log_insert_sql
|
|
30
|
+
from contractforge_databricks.evidence.sql import (
|
|
31
|
+
render_access_insert_sql,
|
|
32
|
+
render_cost_insert_sql,
|
|
33
|
+
render_error_insert_sql,
|
|
34
|
+
render_lineage_insert_sql,
|
|
35
|
+
render_quality_insert_sql,
|
|
36
|
+
render_quarantine_insert_sql,
|
|
37
|
+
render_run_insert_sql,
|
|
38
|
+
render_schema_change_insert_sql,
|
|
39
|
+
render_source_metadata_insert_sql,
|
|
40
|
+
render_stream_batch_insert_sql,
|
|
41
|
+
)
|
|
42
|
+
from contractforge_databricks.execution.sql_merge import SqlRunner
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EvidenceWriter:
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
runner: SqlRunner,
|
|
49
|
+
*,
|
|
50
|
+
catalog: str = "main",
|
|
51
|
+
schema: str = "ops",
|
|
52
|
+
clock: TimestampClock | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
self.runner = runner
|
|
55
|
+
self.catalog = catalog
|
|
56
|
+
self.schema = schema
|
|
57
|
+
self.clock = clock
|
|
58
|
+
|
|
59
|
+
def write_run(self, record: RunEvidenceRecord) -> None:
|
|
60
|
+
self.runner.sql(render_run_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
61
|
+
|
|
62
|
+
def write_run_log(self, payload: dict[str, object]) -> None:
|
|
63
|
+
self.runner.sql(render_run_log_insert_sql(payload, catalog=self.catalog, schema=self.schema))
|
|
64
|
+
|
|
65
|
+
def write_error(self, record: ErrorEvidenceRecord) -> None:
|
|
66
|
+
self.runner.sql(render_error_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
67
|
+
|
|
68
|
+
def write_error_log(self, payload: dict[str, object]) -> None:
|
|
69
|
+
self.runner.sql(render_error_log_insert_sql(payload, catalog=self.catalog, schema=self.schema))
|
|
70
|
+
|
|
71
|
+
def write_lineage(self, record: LineageEvidenceRecord) -> None:
|
|
72
|
+
self.runner.sql(render_lineage_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
73
|
+
|
|
74
|
+
def write_quality(self, record: QualityEvidenceRecord) -> None:
|
|
75
|
+
self.runner.sql(render_quality_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
76
|
+
|
|
77
|
+
def write_schema_change(self, record: SchemaChangeEvidenceRecord) -> None:
|
|
78
|
+
self.runner.sql(render_schema_change_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
79
|
+
|
|
80
|
+
def write_schema_change_log(self, payload: dict[str, object]) -> None:
|
|
81
|
+
self.runner.sql(render_schema_change_log_insert_sql(payload, catalog=self.catalog, schema=self.schema))
|
|
82
|
+
|
|
83
|
+
def write_cost(self, record: CostEvidenceRecord) -> None:
|
|
84
|
+
self.runner.sql(render_cost_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
85
|
+
|
|
86
|
+
def write_quarantine(self, record: QuarantineEvidenceRecord) -> None:
|
|
87
|
+
self.runner.sql(render_quarantine_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
88
|
+
|
|
89
|
+
def write_source_metadata(self, record: SourceMetadataEvidenceRecord) -> None:
|
|
90
|
+
self.runner.sql(render_source_metadata_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
91
|
+
|
|
92
|
+
def write_stream_batch(self, record: StreamBatchEvidenceRecord) -> None:
|
|
93
|
+
self.runner.sql(render_stream_batch_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
94
|
+
|
|
95
|
+
def write_stream_log(self, payload: dict[str, object]) -> None:
|
|
96
|
+
self.runner.sql(render_stream_log_insert_sql(payload, catalog=self.catalog, schema=self.schema, clock=self.clock))
|
|
97
|
+
|
|
98
|
+
def finish_stream_log(self, *, stream_run_id: str, payload: dict[str, object]) -> None:
|
|
99
|
+
statement = render_stream_finish_update_sql(
|
|
100
|
+
stream_run_id=stream_run_id,
|
|
101
|
+
payload=payload,
|
|
102
|
+
catalog=self.catalog,
|
|
103
|
+
schema=self.schema,
|
|
104
|
+
)
|
|
105
|
+
if statement is not None:
|
|
106
|
+
self.runner.sql(statement)
|
|
107
|
+
|
|
108
|
+
def write_access(self, record: AccessEvidenceRecord) -> None:
|
|
109
|
+
self.runner.sql(render_access_insert_sql(record, catalog=self.catalog, schema=self.schema))
|
|
110
|
+
|
|
111
|
+
def write_annotation_log(self, payload: dict[str, object]) -> None:
|
|
112
|
+
self.runner.sql(render_annotation_log_insert_sql(payload, catalog=self.catalog, schema=self.schema, clock=self.clock))
|
|
113
|
+
|
|
114
|
+
def write_access_log(self, payload: dict[str, object]) -> None:
|
|
115
|
+
self.runner.sql(render_access_log_insert_sql(payload, catalog=self.catalog, schema=self.schema, clock=self.clock))
|
|
116
|
+
|
|
117
|
+
def write_operations_log(self, payload: dict[str, object]) -> None:
|
|
118
|
+
self.runner.sql(render_operations_log_insert_sql(payload, catalog=self.catalog, schema=self.schema, clock=self.clock))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from contractforge_core.execution import ExecutionOutcome
|
|
2
|
+
from contractforge_databricks.execution.delta_basic import (
|
|
3
|
+
execute_append,
|
|
4
|
+
execute_overwrite,
|
|
5
|
+
render_append_sql,
|
|
6
|
+
render_overwrite_sql,
|
|
7
|
+
)
|
|
8
|
+
from contractforge_databricks.execution.hash_diff import execute_hash_diff_insert, render_hash_diff_insert_sql
|
|
9
|
+
from contractforge_databricks.execution.replace_partitions import (
|
|
10
|
+
execute_replace_partitions,
|
|
11
|
+
render_replace_partitions_sql,
|
|
12
|
+
)
|
|
13
|
+
from contractforge_databricks.execution.retry import is_retryable_delta_concurrency_error, with_delta_retry
|
|
14
|
+
from contractforge_databricks.execution.scd2 import execute_scd2_merge, render_scd2_merge_sql, render_scd2_stage_sql
|
|
15
|
+
from contractforge_databricks.execution.scd2_deletes import render_scd2_delete_merge_sql
|
|
16
|
+
from contractforge_databricks.execution.sql_merge import SqlRunner, execute_scd1_merge, render_scd1_merge_sql
|
|
17
|
+
from contractforge_databricks.execution.snapshot import execute_snapshot_soft_delete, render_snapshot_soft_delete_sql
|
|
18
|
+
from contractforge_databricks.execution.tables import (
|
|
19
|
+
execute_table_setup,
|
|
20
|
+
render_cluster_by_sql,
|
|
21
|
+
render_create_delta_table_sql,
|
|
22
|
+
render_create_schema_sql,
|
|
23
|
+
render_delta_properties_sql,
|
|
24
|
+
render_table_setup_sql,
|
|
25
|
+
)
|
|
26
|
+
from contractforge_databricks.execution.windows import (
|
|
27
|
+
ChildWindowPlan,
|
|
28
|
+
ExecutionWindow,
|
|
29
|
+
build_child_window_plan,
|
|
30
|
+
build_time_windows,
|
|
31
|
+
combine_filter,
|
|
32
|
+
render_window_filter_sql,
|
|
33
|
+
summarize_window_results,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"ChildWindowPlan",
|
|
38
|
+
"ExecutionOutcome",
|
|
39
|
+
"ExecutionWindow",
|
|
40
|
+
"is_retryable_delta_concurrency_error",
|
|
41
|
+
"with_delta_retry",
|
|
42
|
+
"SqlRunner",
|
|
43
|
+
"build_child_window_plan",
|
|
44
|
+
"build_time_windows",
|
|
45
|
+
"combine_filter",
|
|
46
|
+
"execute_append",
|
|
47
|
+
"execute_hash_diff_insert",
|
|
48
|
+
"execute_overwrite",
|
|
49
|
+
"execute_replace_partitions",
|
|
50
|
+
"execute_scd1_merge",
|
|
51
|
+
"execute_scd2_merge",
|
|
52
|
+
"execute_snapshot_soft_delete",
|
|
53
|
+
"execute_table_setup",
|
|
54
|
+
"render_append_sql",
|
|
55
|
+
"render_cluster_by_sql",
|
|
56
|
+
"render_create_delta_table_sql",
|
|
57
|
+
"render_create_schema_sql",
|
|
58
|
+
"render_delta_properties_sql",
|
|
59
|
+
"render_hash_diff_insert_sql",
|
|
60
|
+
"render_overwrite_sql",
|
|
61
|
+
"render_replace_partitions_sql",
|
|
62
|
+
"render_scd2_merge_sql",
|
|
63
|
+
"render_scd2_stage_sql",
|
|
64
|
+
"render_scd2_delete_merge_sql",
|
|
65
|
+
"render_scd1_merge_sql",
|
|
66
|
+
"render_snapshot_soft_delete_sql",
|
|
67
|
+
"render_table_setup_sql",
|
|
68
|
+
"render_window_filter_sql",
|
|
69
|
+
"summarize_window_results",
|
|
70
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Databricks Delta append and overwrite helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contractforge_core.semantic import SemanticContract
|
|
6
|
+
from contractforge_core.execution import ExecutionOutcome
|
|
7
|
+
from contractforge_databricks.execution.sql_merge import SqlRunner
|
|
8
|
+
from contractforge_databricks.rendering.names import target_full_name
|
|
9
|
+
from contractforge_databricks.sql import quote_table_name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def render_append_sql(*, target_table: str, source_view: str) -> str:
|
|
13
|
+
return f"INSERT INTO {quote_table_name(target_table)}\nSELECT * FROM {quote_table_name(source_view)}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def render_overwrite_sql(*, target_table: str, source_view: str) -> str:
|
|
17
|
+
return f"INSERT OVERWRITE TABLE {quote_table_name(target_table)}\nSELECT * FROM {quote_table_name(source_view)}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def execute_append(
|
|
21
|
+
*,
|
|
22
|
+
runner: SqlRunner,
|
|
23
|
+
contract: SemanticContract,
|
|
24
|
+
source_view: str,
|
|
25
|
+
) -> ExecutionOutcome:
|
|
26
|
+
if contract.write.mode != "scd0_append":
|
|
27
|
+
raise ValueError(f"execute_append only supports scd0_append, got {contract.write.mode}")
|
|
28
|
+
target = target_full_name(contract)
|
|
29
|
+
statement = render_append_sql(target_table=target, source_view=source_view)
|
|
30
|
+
runner.sql(statement)
|
|
31
|
+
return ExecutionOutcome(
|
|
32
|
+
status="SUCCESS",
|
|
33
|
+
operation="delta_append",
|
|
34
|
+
target=target,
|
|
35
|
+
metrics={},
|
|
36
|
+
sql=statement,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def execute_overwrite(
|
|
41
|
+
*,
|
|
42
|
+
runner: SqlRunner,
|
|
43
|
+
contract: SemanticContract,
|
|
44
|
+
source_view: str,
|
|
45
|
+
) -> ExecutionOutcome:
|
|
46
|
+
if contract.write.mode != "scd0_overwrite":
|
|
47
|
+
raise ValueError(f"execute_overwrite only supports scd0_overwrite, got {contract.write.mode}")
|
|
48
|
+
target = target_full_name(contract)
|
|
49
|
+
statement = render_overwrite_sql(target_table=target, source_view=source_view)
|
|
50
|
+
runner.sql(statement)
|
|
51
|
+
return ExecutionOutcome(
|
|
52
|
+
status="SUCCESS",
|
|
53
|
+
operation="delta_overwrite",
|
|
54
|
+
target=target,
|
|
55
|
+
metrics={},
|
|
56
|
+
sql=statement,
|
|
57
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""ContractForge-compatible SCD1 hash-diff append SQL."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contractforge_core.runtime import QueryOne
|
|
6
|
+
from contractforge_core.semantic import SemanticContract
|
|
7
|
+
from contractforge_core.execution import ExecutionOutcome
|
|
8
|
+
from contractforge_databricks.execution.hash_diff_latest import (
|
|
9
|
+
resolve_hash_diff_latest_selection,
|
|
10
|
+
validate_hash_diff_target_latest,
|
|
11
|
+
)
|
|
12
|
+
from contractforge_databricks.execution.sql_merge import SqlRunner
|
|
13
|
+
from contractforge_databricks.rendering.names import target_full_name
|
|
14
|
+
from contractforge_databricks.sql import quote_identifier, quote_table_name
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def render_hash_diff_insert_sql(
|
|
18
|
+
*,
|
|
19
|
+
target_table: str,
|
|
20
|
+
source_view: str,
|
|
21
|
+
merge_keys: tuple[str, ...],
|
|
22
|
+
source_columns: tuple[str, ...],
|
|
23
|
+
row_hash_column: str = "row_hash",
|
|
24
|
+
latest_order_by: str | None = None,
|
|
25
|
+
) -> str:
|
|
26
|
+
if not merge_keys:
|
|
27
|
+
raise ValueError("scd1_hash_diff requires merge_keys")
|
|
28
|
+
_require_columns(source_columns, merge_keys, "merge_keys")
|
|
29
|
+
if row_hash_column not in source_columns:
|
|
30
|
+
raise ValueError(f"prepared hash-diff source is missing {row_hash_column}")
|
|
31
|
+
|
|
32
|
+
join_condition = " AND ".join(
|
|
33
|
+
f"t.{quote_identifier(key)} <=> s.{quote_identifier(key)}" for key in merge_keys
|
|
34
|
+
)
|
|
35
|
+
null_condition = " AND ".join(f"t.{quote_identifier(key)} IS NULL" for key in merge_keys)
|
|
36
|
+
columns = ", ".join(quote_identifier(column) for column in source_columns)
|
|
37
|
+
values = ", ".join(f"s.{quote_identifier(column)}" for column in source_columns)
|
|
38
|
+
|
|
39
|
+
target_relation = _target_latest_relation(
|
|
40
|
+
target_table=target_table,
|
|
41
|
+
merge_keys=merge_keys,
|
|
42
|
+
row_hash_column=row_hash_column,
|
|
43
|
+
latest_order_by=latest_order_by,
|
|
44
|
+
)
|
|
45
|
+
lines = [
|
|
46
|
+
f"INSERT INTO {quote_table_name(target_table)} ({columns})",
|
|
47
|
+
f"SELECT {values}",
|
|
48
|
+
f"FROM {quote_table_name(source_view)} s",
|
|
49
|
+
"LEFT JOIN (",
|
|
50
|
+
*target_relation,
|
|
51
|
+
") t",
|
|
52
|
+
f"ON {join_condition}",
|
|
53
|
+
f"WHERE ({null_condition}) OR NOT (t.{quote_identifier(row_hash_column)} <=> s.{quote_identifier(row_hash_column)})",
|
|
54
|
+
]
|
|
55
|
+
return "\n".join(lines)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _target_latest_relation(
|
|
59
|
+
*,
|
|
60
|
+
target_table: str,
|
|
61
|
+
merge_keys: tuple[str, ...],
|
|
62
|
+
row_hash_column: str,
|
|
63
|
+
latest_order_by: str | None,
|
|
64
|
+
) -> list[str]:
|
|
65
|
+
selected = f"{', '.join(quote_identifier(key) for key in merge_keys)}, {quote_identifier(row_hash_column)}"
|
|
66
|
+
if not latest_order_by:
|
|
67
|
+
return [f" SELECT {selected}", f" FROM {quote_table_name(target_table)}"]
|
|
68
|
+
partition = ", ".join(quote_identifier(key) for key in merge_keys)
|
|
69
|
+
return [
|
|
70
|
+
f" SELECT {selected}",
|
|
71
|
+
" FROM (",
|
|
72
|
+
f" SELECT {selected},",
|
|
73
|
+
f" row_number() OVER (PARTITION BY {partition} ORDER BY {latest_order_by}) AS __cf_latest_rank",
|
|
74
|
+
f" FROM {quote_table_name(target_table)}",
|
|
75
|
+
" ) latest",
|
|
76
|
+
" WHERE __cf_latest_rank = 1",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _require_columns(columns: tuple[str, ...], required: tuple[str, ...], context: str) -> None:
|
|
81
|
+
missing = [column for column in required if column not in columns]
|
|
82
|
+
if missing:
|
|
83
|
+
raise ValueError(f"prepared hash-diff source is missing {context}: {missing}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def execute_hash_diff_insert(
|
|
87
|
+
*,
|
|
88
|
+
runner: SqlRunner,
|
|
89
|
+
contract: SemanticContract,
|
|
90
|
+
source_view: str,
|
|
91
|
+
source_columns: tuple[str, ...],
|
|
92
|
+
target_schema: dict[str, str] | None = None,
|
|
93
|
+
query_one: QueryOne | None = None,
|
|
94
|
+
) -> ExecutionOutcome:
|
|
95
|
+
if contract.write.mode != "scd1_hash_diff":
|
|
96
|
+
raise ValueError(f"execute_hash_diff_insert only supports scd1_hash_diff, got {contract.write.mode}")
|
|
97
|
+
target = target_full_name(contract)
|
|
98
|
+
merge_keys = contract.write.merge_keys or contract.write.hash_keys
|
|
99
|
+
selection = resolve_hash_diff_latest_selection(contract, target_schema)
|
|
100
|
+
validate_hash_diff_target_latest(
|
|
101
|
+
query_one=query_one,
|
|
102
|
+
target_table=target,
|
|
103
|
+
merge_keys=merge_keys,
|
|
104
|
+
selection=selection,
|
|
105
|
+
)
|
|
106
|
+
statement = render_hash_diff_insert_sql(
|
|
107
|
+
target_table=target,
|
|
108
|
+
source_view=source_view,
|
|
109
|
+
merge_keys=merge_keys,
|
|
110
|
+
source_columns=source_columns,
|
|
111
|
+
latest_order_by=selection.order_by,
|
|
112
|
+
)
|
|
113
|
+
runner.sql(statement)
|
|
114
|
+
return ExecutionOutcome(
|
|
115
|
+
status="SUCCESS",
|
|
116
|
+
operation="core_managed_hash_diff_delta",
|
|
117
|
+
target=target,
|
|
118
|
+
metrics={
|
|
119
|
+
"source_columns": len(source_columns),
|
|
120
|
+
"merge_keys": len(merge_keys),
|
|
121
|
+
"hash_keys": len(contract.write.hash_keys),
|
|
122
|
+
"target_latest_ordered": bool(selection.order_by),
|
|
123
|
+
"target_latest_reason": selection.reason,
|
|
124
|
+
},
|
|
125
|
+
sql=statement,
|
|
126
|
+
)
|