pretensor 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.
- pretensor/__init__.py +50 -0
- pretensor/benchmark/__init__.py +54 -0
- pretensor/benchmark/cli.py +294 -0
- pretensor/benchmark/fixtures.py +84 -0
- pretensor/benchmark/l1/__init__.py +23 -0
- pretensor/benchmark/l1/metrics.py +141 -0
- pretensor/benchmark/l1/pipeline.py +188 -0
- pretensor/benchmark/l1/runner.py +245 -0
- pretensor/benchmark/l2/__init__.py +27 -0
- pretensor/benchmark/l2/gold.py +236 -0
- pretensor/benchmark/l2/metrics.py +146 -0
- pretensor/benchmark/l2/pipeline.py +124 -0
- pretensor/benchmark/l2/runner.py +530 -0
- pretensor/benchmark/l3/__init__.py +73 -0
- pretensor/benchmark/l3/agent.py +316 -0
- pretensor/benchmark/l3/db.py +188 -0
- pretensor/benchmark/l3/gold.py +85 -0
- pretensor/benchmark/l3/llm_client.py +395 -0
- pretensor/benchmark/l3/mcp_client.py +357 -0
- pretensor/benchmark/l3/pretensor_runner.py +456 -0
- pretensor/benchmark/l3/prompt.py +132 -0
- pretensor/benchmark/l3/runner.py +358 -0
- pretensor/benchmark/l3/sql_equivalence.py +176 -0
- pretensor/benchmark/release_gate.py +448 -0
- pretensor/benchmark/results.py +298 -0
- pretensor/benchmark/runner.py +109 -0
- pretensor/cli/__init__.py +1 -0
- pretensor/cli/commands/_source_runner.py +147 -0
- pretensor/cli/commands/analyze.py +201 -0
- pretensor/cli/commands/connections/__init__.py +7 -0
- pretensor/cli/commands/connections/add_remove.py +126 -0
- pretensor/cli/commands/connections/register.py +12 -0
- pretensor/cli/commands/export.py +131 -0
- pretensor/cli/commands/index.py +559 -0
- pretensor/cli/commands/list.py +76 -0
- pretensor/cli/commands/quickstart.py +207 -0
- pretensor/cli/commands/reindex.py +646 -0
- pretensor/cli/commands/semantic.py +190 -0
- pretensor/cli/commands/serve.py +144 -0
- pretensor/cli/commands/sync_grants.py +149 -0
- pretensor/cli/commands/validate.py +176 -0
- pretensor/cli/config_file.py +442 -0
- pretensor/cli/constants.py +10 -0
- pretensor/cli/dbt_enrichment.py +96 -0
- pretensor/cli/main.py +109 -0
- pretensor/cli/paths.py +43 -0
- pretensor/cli/plugin.py +52 -0
- pretensor/config.py +226 -0
- pretensor/connectors/__init__.py +29 -0
- pretensor/connectors/base.py +165 -0
- pretensor/connectors/bigquery.py +468 -0
- pretensor/connectors/inspect.py +321 -0
- pretensor/connectors/lineage_sqlglot.py +97 -0
- pretensor/connectors/models.py +130 -0
- pretensor/connectors/mysql.py +402 -0
- pretensor/connectors/pg_array_parse.py +53 -0
- pretensor/connectors/postgres.py +938 -0
- pretensor/connectors/registry.py +93 -0
- pretensor/connectors/snapshot.py +244 -0
- pretensor/connectors/snowflake.py +908 -0
- pretensor/core/__init__.py +1 -0
- pretensor/core/builder.py +307 -0
- pretensor/core/dsn_crypto.py +51 -0
- pretensor/core/graph_schema_manager.py +246 -0
- pretensor/core/graph_store.py +1226 -0
- pretensor/core/ids.py +101 -0
- pretensor/core/portable_export.py +276 -0
- pretensor/core/query_runner.py +67 -0
- pretensor/core/registry.py +209 -0
- pretensor/core/schema.py +473 -0
- pretensor/core/secure_io.py +93 -0
- pretensor/core/store.py +469 -0
- pretensor/enrichment/__init__.py +1 -0
- pretensor/enrichment/analyze/__init__.py +0 -0
- pretensor/enrichment/analyze/classify.py +49 -0
- pretensor/enrichment/analyze/extract_python.py +196 -0
- pretensor/enrichment/analyze/parse.py +141 -0
- pretensor/enrichment/analyze/pipeline.py +195 -0
- pretensor/enrichment/analyze/summary.py +38 -0
- pretensor/enrichment/analyze/walker.py +98 -0
- pretensor/enrichment/analyze/writers.py +214 -0
- pretensor/enrichment/dbt/__init__.py +30 -0
- pretensor/enrichment/dbt/lineage.py +100 -0
- pretensor/enrichment/dbt/manifest.py +300 -0
- pretensor/enrichment/dbt/metadata.py +263 -0
- pretensor/enrichment/dbt/pipeline.py +77 -0
- pretensor/enrichment/dbt/resolution.py +101 -0
- pretensor/enrichment/dbt/signals.py +305 -0
- pretensor/entities/__init__.py +27 -0
- pretensor/entities/builder.py +63 -0
- pretensor/entities/classifier.py +383 -0
- pretensor/entities/llm_extract.py +66 -0
- pretensor/errors.py +35 -0
- pretensor/graph_models/__init__.py +17 -0
- pretensor/graph_models/base.py +11 -0
- pretensor/graph_models/consumer.py +71 -0
- pretensor/graph_models/edge.py +35 -0
- pretensor/graph_models/entity.py +21 -0
- pretensor/graph_models/node.py +79 -0
- pretensor/graph_models/relationship.py +33 -0
- pretensor/integrations/__init__.py +42 -0
- pretensor/integrations/_base.py +138 -0
- pretensor/integrations/google_adk.py +49 -0
- pretensor/integrations/langchain.py +55 -0
- pretensor/integrations/llamaindex.py +53 -0
- pretensor/intelligence/__init__.py +33 -0
- pretensor/intelligence/cluster_labeler.py +425 -0
- pretensor/intelligence/clustering.py +168 -0
- pretensor/intelligence/combining.py +32 -0
- pretensor/intelligence/discovery.py +114 -0
- pretensor/intelligence/embeddings.py +317 -0
- pretensor/intelligence/graph_export.py +200 -0
- pretensor/intelligence/heuristic.py +544 -0
- pretensor/intelligence/join_paths/__init__.py +130 -0
- pretensor/intelligence/join_paths/on_demand.py +516 -0
- pretensor/intelligence/join_paths/storage.py +70 -0
- pretensor/intelligence/llm_infer.py +78 -0
- pretensor/intelligence/llm_runtime.py +62 -0
- pretensor/intelligence/metric_templates.py +193 -0
- pretensor/intelligence/pipeline.py +364 -0
- pretensor/intelligence/role_exemplars.py +263 -0
- pretensor/intelligence/schema_classification.py +360 -0
- pretensor/intelligence/scoring.py +76 -0
- pretensor/intelligence/semantic.py +240 -0
- pretensor/intelligence/shadow_alias.py +101 -0
- pretensor/intelligence/statistical.py +50 -0
- pretensor/intelligence/steps.py +191 -0
- pretensor/intelligence/steps_embedding.py +168 -0
- pretensor/introspection/__init__.py +6 -0
- pretensor/introspection/inspector.py +5 -0
- pretensor/introspection/models/__init__.py +0 -0
- pretensor/introspection/models/base.py +5 -0
- pretensor/introspection/models/config.py +237 -0
- pretensor/introspection/models/dsn.py +550 -0
- pretensor/introspection/models/plan.py +116 -0
- pretensor/introspection/models/schema.py +10 -0
- pretensor/introspection/models/semantic.py +121 -0
- pretensor/introspection/models/validation.py +116 -0
- pretensor/introspection/snapshot.py +46 -0
- pretensor/mcp/__init__.py +16 -0
- pretensor/mcp/config_json.py +24 -0
- pretensor/mcp/payload_types.py +274 -0
- pretensor/mcp/resources/__init__.py +17 -0
- pretensor/mcp/resources/markdown.py +314 -0
- pretensor/mcp/server.py +285 -0
- pretensor/mcp/service.py +49 -0
- pretensor/mcp/service_context.py +142 -0
- pretensor/mcp/service_registry.py +294 -0
- pretensor/mcp/store_cache.py +43 -0
- pretensor/mcp/tool_registry.py +136 -0
- pretensor/mcp/tools/__init__.py +1 -0
- pretensor/mcp/tools/_rank.py +244 -0
- pretensor/mcp/tools/_timed.py +26 -0
- pretensor/mcp/tools/compile_metric.py +144 -0
- pretensor/mcp/tools/consumers.py +161 -0
- pretensor/mcp/tools/context.py +1121 -0
- pretensor/mcp/tools/cypher.py +509 -0
- pretensor/mcp/tools/detect_changes.py +254 -0
- pretensor/mcp/tools/impact.py +271 -0
- pretensor/mcp/tools/list.py +131 -0
- pretensor/mcp/tools/schema.py +170 -0
- pretensor/mcp/tools/search.py +316 -0
- pretensor/mcp/tools/semantic_search.py +282 -0
- pretensor/mcp/tools/traverse.py +1027 -0
- pretensor/mcp/tools/validate_sql.py +150 -0
- pretensor/observability.py +203 -0
- pretensor/py.typed +0 -0
- pretensor/quickstart/README.md +29 -0
- pretensor/quickstart/__init__.py +6 -0
- pretensor/quickstart/docker-compose.yml +18 -0
- pretensor/quickstart/pagila_data.sql +63 -0
- pretensor/quickstart/pagila_ddl.sql +92 -0
- pretensor/search/__init__.py +6 -0
- pretensor/search/base.py +80 -0
- pretensor/search/index.py +435 -0
- pretensor/semantic/__init__.py +24 -0
- pretensor/semantic/base.py +123 -0
- pretensor/semantic/compiler.py +487 -0
- pretensor/semantic/yaml_layer.py +180 -0
- pretensor/skills/__init__.py +5 -0
- pretensor/skills/generator.py +235 -0
- pretensor/staleness/__init__.py +15 -0
- pretensor/staleness/graph_patcher.py +355 -0
- pretensor/staleness/impact_analyzer.py +162 -0
- pretensor/staleness/snapshot_store.py +38 -0
- pretensor/validation/__init__.py +9 -0
- pretensor/validation/query_validator.py +436 -0
- pretensor/visibility/__init__.py +23 -0
- pretensor/visibility/config.py +126 -0
- pretensor/visibility/filter.py +143 -0
- pretensor/visibility/kuzu_helpers.py +32 -0
- pretensor/visibility/runtime.py +36 -0
- pretensor/visibility/sync_grants.py +188 -0
- pretensor-0.1.0.dist-info/METADATA +251 -0
- pretensor-0.1.0.dist-info/RECORD +198 -0
- pretensor-0.1.0.dist-info/WHEEL +4 -0
- pretensor-0.1.0.dist-info/entry_points.txt +2 -0
- pretensor-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Registry mapping connection types to connector implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
|
|
7
|
+
from pretensor.connectors.base import BaseConnector
|
|
8
|
+
from pretensor.introspection.models.config import ConnectionConfig, DatabaseType
|
|
9
|
+
|
|
10
|
+
_POSTGRES_EXTRA_HINT = (
|
|
11
|
+
"Install the Postgres extra: pip install 'pretensor[postgres]' "
|
|
12
|
+
"(or pip install psycopg2-binary)."
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_SNOWFLAKE_EXTRA_HINT = (
|
|
16
|
+
"Install the Snowflake extra: pip install 'pretensor[snowflake]' "
|
|
17
|
+
"(or pip install snowflake-sqlalchemy)."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_MYSQL_EXTRA_HINT = (
|
|
21
|
+
"Install the MySQL extra: pip install 'pretensor[mysql]' (or pip install PyMySQL)."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _postgres_connector_class() -> type[BaseConnector]:
|
|
26
|
+
try:
|
|
27
|
+
# psycopg2 is an optional dependency under the ``postgres`` extra. Import
|
|
28
|
+
# it lazily here (mirroring the other connectors) so a bare install can
|
|
29
|
+
# import pretensor and only fails — with a clear hint — when a Postgres
|
|
30
|
+
# connection is actually attempted.
|
|
31
|
+
import_module("psycopg2")
|
|
32
|
+
except ImportError as exc:
|
|
33
|
+
msg = f"Postgres connector requires psycopg2. {_POSTGRES_EXTRA_HINT}"
|
|
34
|
+
raise ImportError(msg) from exc
|
|
35
|
+
from pretensor.connectors.postgres import PostgresConnector
|
|
36
|
+
|
|
37
|
+
return PostgresConnector
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _snowflake_connector_class() -> type[BaseConnector]:
|
|
41
|
+
try:
|
|
42
|
+
# Optional dep: string path keeps pyright happy when the extra is not installed.
|
|
43
|
+
import_module("snowflake.sqlalchemy")
|
|
44
|
+
except ImportError as exc:
|
|
45
|
+
msg = f"Snowflake connector requires snowflake-sqlalchemy. {_SNOWFLAKE_EXTRA_HINT}"
|
|
46
|
+
raise ImportError(msg) from exc
|
|
47
|
+
from pretensor.connectors.snowflake import SnowflakeConnector
|
|
48
|
+
|
|
49
|
+
return SnowflakeConnector
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _bigquery_connector_class() -> type[BaseConnector]:
|
|
53
|
+
try:
|
|
54
|
+
import_module("google.cloud.bigquery")
|
|
55
|
+
except ImportError as exc:
|
|
56
|
+
from pretensor.connectors.bigquery import _BIGQUERY_EXTRA_HINT
|
|
57
|
+
|
|
58
|
+
msg = (
|
|
59
|
+
f"BigQuery connector requires google-cloud-bigquery. {_BIGQUERY_EXTRA_HINT}"
|
|
60
|
+
)
|
|
61
|
+
raise ImportError(msg) from exc
|
|
62
|
+
from pretensor.connectors.bigquery import BigQueryConnector
|
|
63
|
+
|
|
64
|
+
return BigQueryConnector
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _mysql_connector_class() -> type[BaseConnector]:
|
|
68
|
+
try:
|
|
69
|
+
import_module("pymysql")
|
|
70
|
+
except ImportError as exc:
|
|
71
|
+
msg = f"MySQL connector requires PyMySQL. {_MYSQL_EXTRA_HINT}"
|
|
72
|
+
raise ImportError(msg) from exc
|
|
73
|
+
from pretensor.connectors.mysql import MySQLConnector
|
|
74
|
+
|
|
75
|
+
return MySQLConnector
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_connector(config: ConnectionConfig) -> BaseConnector:
|
|
79
|
+
"""Return a connector instance for the given configuration."""
|
|
80
|
+
if config.type == DatabaseType.POSTGRES:
|
|
81
|
+
cls = _postgres_connector_class()
|
|
82
|
+
return cls(config)
|
|
83
|
+
if config.type == DatabaseType.SNOWFLAKE:
|
|
84
|
+
cls = _snowflake_connector_class()
|
|
85
|
+
return cls(config)
|
|
86
|
+
if config.type == DatabaseType.BIGQUERY:
|
|
87
|
+
cls = _bigquery_connector_class()
|
|
88
|
+
return cls(config)
|
|
89
|
+
if config.type == DatabaseType.MYSQL:
|
|
90
|
+
cls = _mysql_connector_class()
|
|
91
|
+
return cls(config)
|
|
92
|
+
msg = f"No connector registered for database type: {config.type}"
|
|
93
|
+
raise ValueError(msg)
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Snapshot diff — compare two schema snapshots for drift detection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
|
|
7
|
+
from pretensor.connectors.models import Column, SchemaSnapshot, Table, ViewDependency
|
|
8
|
+
from pretensor.introspection.models.base import PretensorModel
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ChangeTarget",
|
|
12
|
+
"ChangeType",
|
|
13
|
+
"SchemaChange",
|
|
14
|
+
"diff_snapshots",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChangeType(StrEnum):
|
|
19
|
+
ADDED = "added"
|
|
20
|
+
REMOVED = "removed"
|
|
21
|
+
MODIFIED = "modified"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ChangeTarget(StrEnum):
|
|
25
|
+
TABLE = "table"
|
|
26
|
+
COLUMN = "column"
|
|
27
|
+
LINEAGE = "lineage"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SchemaChange(PretensorModel):
|
|
31
|
+
"""A single difference between two schema snapshots."""
|
|
32
|
+
|
|
33
|
+
change_type: ChangeType
|
|
34
|
+
target: ChangeTarget
|
|
35
|
+
table_name: str
|
|
36
|
+
schema_name: str
|
|
37
|
+
column_name: str | None = None
|
|
38
|
+
details: str = ""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def diff_snapshots(old: SchemaSnapshot, new: SchemaSnapshot) -> list[SchemaChange]:
|
|
42
|
+
"""Compare two snapshots and return a list of changes.
|
|
43
|
+
|
|
44
|
+
Detects added/removed tables and added/removed/modified columns.
|
|
45
|
+
"""
|
|
46
|
+
changes: list[SchemaChange] = []
|
|
47
|
+
|
|
48
|
+
old_tables = {(t.schema_name, t.name): t for t in old.tables}
|
|
49
|
+
new_tables = {(t.schema_name, t.name): t for t in new.tables}
|
|
50
|
+
|
|
51
|
+
old_keys = set(old_tables.keys())
|
|
52
|
+
new_keys = set(new_tables.keys())
|
|
53
|
+
|
|
54
|
+
for schema_name, table_name in sorted(new_keys - old_keys):
|
|
55
|
+
changes.append(
|
|
56
|
+
SchemaChange(
|
|
57
|
+
change_type=ChangeType.ADDED,
|
|
58
|
+
target=ChangeTarget.TABLE,
|
|
59
|
+
table_name=table_name,
|
|
60
|
+
schema_name=schema_name,
|
|
61
|
+
details=f"New table with {len(new_tables[(schema_name, table_name)].columns)} columns",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
for schema_name, table_name in sorted(old_keys - new_keys):
|
|
66
|
+
changes.append(
|
|
67
|
+
SchemaChange(
|
|
68
|
+
change_type=ChangeType.REMOVED,
|
|
69
|
+
target=ChangeTarget.TABLE,
|
|
70
|
+
table_name=table_name,
|
|
71
|
+
schema_name=schema_name,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
for key in sorted(old_keys & new_keys):
|
|
76
|
+
schema_name, table_name = key
|
|
77
|
+
old_table = old_tables[key]
|
|
78
|
+
new_table = new_tables[key]
|
|
79
|
+
|
|
80
|
+
old_cols = {c.name: c for c in old_table.columns}
|
|
81
|
+
new_cols = {c.name: c for c in new_table.columns}
|
|
82
|
+
|
|
83
|
+
for col_name in sorted(set(new_cols) - set(old_cols)):
|
|
84
|
+
col = new_cols[col_name]
|
|
85
|
+
changes.append(
|
|
86
|
+
SchemaChange(
|
|
87
|
+
change_type=ChangeType.ADDED,
|
|
88
|
+
target=ChangeTarget.COLUMN,
|
|
89
|
+
table_name=table_name,
|
|
90
|
+
schema_name=schema_name,
|
|
91
|
+
column_name=col_name,
|
|
92
|
+
details=f"type={col.data_type}, nullable={col.nullable}",
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
for col_name in sorted(set(old_cols) - set(new_cols)):
|
|
97
|
+
changes.append(
|
|
98
|
+
SchemaChange(
|
|
99
|
+
change_type=ChangeType.REMOVED,
|
|
100
|
+
target=ChangeTarget.COLUMN,
|
|
101
|
+
table_name=table_name,
|
|
102
|
+
schema_name=schema_name,
|
|
103
|
+
column_name=col_name,
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
for col_name in sorted(set(old_cols) & set(new_cols)):
|
|
108
|
+
old_col = old_cols[col_name]
|
|
109
|
+
new_col = new_cols[col_name]
|
|
110
|
+
diffs = _column_diffs(old_col, new_col)
|
|
111
|
+
if diffs:
|
|
112
|
+
changes.append(
|
|
113
|
+
SchemaChange(
|
|
114
|
+
change_type=ChangeType.MODIFIED,
|
|
115
|
+
target=ChangeTarget.COLUMN,
|
|
116
|
+
table_name=table_name,
|
|
117
|
+
schema_name=schema_name,
|
|
118
|
+
column_name=col_name,
|
|
119
|
+
details=", ".join(diffs),
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
table_diffs = _table_diffs(old_table, new_table)
|
|
124
|
+
if table_diffs:
|
|
125
|
+
changes.append(
|
|
126
|
+
SchemaChange(
|
|
127
|
+
change_type=ChangeType.MODIFIED,
|
|
128
|
+
target=ChangeTarget.TABLE,
|
|
129
|
+
table_name=table_name,
|
|
130
|
+
schema_name=schema_name,
|
|
131
|
+
details=", ".join(table_diffs),
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
changes.extend(_lineage_diffs(old.view_dependencies, new.view_dependencies))
|
|
136
|
+
|
|
137
|
+
return changes
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _lineage_key(dep: ViewDependency) -> tuple[str, str, str, str, str]:
|
|
141
|
+
return (
|
|
142
|
+
dep.source_schema,
|
|
143
|
+
dep.source_table,
|
|
144
|
+
dep.target_schema,
|
|
145
|
+
dep.target_table,
|
|
146
|
+
dep.lineage_type,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _lineage_diffs(
|
|
151
|
+
old_deps: list[ViewDependency], new_deps: list[ViewDependency]
|
|
152
|
+
) -> list[SchemaChange]:
|
|
153
|
+
"""Detect added and removed lineage edges between two snapshots."""
|
|
154
|
+
old_map = {_lineage_key(d): d for d in old_deps}
|
|
155
|
+
new_map = {_lineage_key(d): d for d in new_deps}
|
|
156
|
+
changes: list[SchemaChange] = []
|
|
157
|
+
for key in sorted(set(new_map) - set(old_map)):
|
|
158
|
+
d = new_map[key]
|
|
159
|
+
changes.append(
|
|
160
|
+
SchemaChange(
|
|
161
|
+
change_type=ChangeType.ADDED,
|
|
162
|
+
target=ChangeTarget.LINEAGE,
|
|
163
|
+
schema_name=d.source_schema,
|
|
164
|
+
table_name=d.source_table,
|
|
165
|
+
details=(
|
|
166
|
+
f"{d.lineage_type}: {d.source_schema}.{d.source_table}"
|
|
167
|
+
f" → {d.target_schema}.{d.target_table}"
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
for key in sorted(set(old_map) - set(new_map)):
|
|
172
|
+
d = old_map[key]
|
|
173
|
+
changes.append(
|
|
174
|
+
SchemaChange(
|
|
175
|
+
change_type=ChangeType.REMOVED,
|
|
176
|
+
target=ChangeTarget.LINEAGE,
|
|
177
|
+
schema_name=d.source_schema,
|
|
178
|
+
table_name=d.source_table,
|
|
179
|
+
details=(
|
|
180
|
+
f"{d.lineage_type}: {d.source_schema}.{d.source_table}"
|
|
181
|
+
f" → {d.target_schema}.{d.target_table}"
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
return changes
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _column_diffs(old: Column, new: Column) -> list[str]:
|
|
189
|
+
"""Compare two Column objects on structural and catalog-stat fields.
|
|
190
|
+
|
|
191
|
+
``parent_column`` and ``is_array`` are intentionally excluded: they are
|
|
192
|
+
set once during initial introspection (e.g. BigQuery nested fields) and
|
|
193
|
+
are not expected to change across re-indexes of the same table.
|
|
194
|
+
"""
|
|
195
|
+
structural_fields = (
|
|
196
|
+
"data_type",
|
|
197
|
+
"nullable",
|
|
198
|
+
"is_primary_key",
|
|
199
|
+
"is_foreign_key",
|
|
200
|
+
"default_value",
|
|
201
|
+
"comment",
|
|
202
|
+
"is_indexed",
|
|
203
|
+
"check_constraints",
|
|
204
|
+
"ordinal_position",
|
|
205
|
+
"most_common_values",
|
|
206
|
+
"histogram_bounds",
|
|
207
|
+
"stats_correlation",
|
|
208
|
+
)
|
|
209
|
+
diffs: list[str] = []
|
|
210
|
+
for field in structural_fields:
|
|
211
|
+
old_val = getattr(old, field)
|
|
212
|
+
new_val = getattr(new, field)
|
|
213
|
+
if old_val != new_val:
|
|
214
|
+
diffs.append(f"{field}: {old_val!r} → {new_val!r}")
|
|
215
|
+
return diffs
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _table_diffs(old: Table, new: Table) -> list[str]:
|
|
219
|
+
"""Compare table-level metadata (not column sets)."""
|
|
220
|
+
diffs: list[str] = []
|
|
221
|
+
for field in (
|
|
222
|
+
"comment",
|
|
223
|
+
"table_type",
|
|
224
|
+
"row_count",
|
|
225
|
+
"seq_scan_count",
|
|
226
|
+
"idx_scan_count",
|
|
227
|
+
"insert_count",
|
|
228
|
+
"update_count",
|
|
229
|
+
"delete_count",
|
|
230
|
+
"is_partitioned",
|
|
231
|
+
"partition_key",
|
|
232
|
+
"grants",
|
|
233
|
+
"access_read_count",
|
|
234
|
+
"access_write_count",
|
|
235
|
+
"days_since_last_access",
|
|
236
|
+
"potentially_unused",
|
|
237
|
+
"table_bytes",
|
|
238
|
+
"clustering_key",
|
|
239
|
+
):
|
|
240
|
+
old_val = getattr(old, field)
|
|
241
|
+
new_val = getattr(new, field)
|
|
242
|
+
if old_val != new_val:
|
|
243
|
+
diffs.append(f"{field}: {old_val!r} → {new_val!r}")
|
|
244
|
+
return diffs
|