recce-nightly 0.62.0.20250417__py3-none-any.whl → 1.30.0.20251221__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.
Potentially problematic release.
This version of recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +27 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +845 -461
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +59 -42
- recce/apis/check_events_api.py +353 -0
- recce/apis/check_func.py +41 -35
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +64 -25
- recce/artifact.py +119 -51
- recce/cli.py +1301 -324
- recce/config.py +43 -34
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404/index.html +2 -0
- recce/data/404.html +2 -1
- recce/data/__next.@lineage.!KHNsb3Qp.__PAGE__.txt +7 -0
- recce/data/__next.@lineage.!KHNsb3Qp.txt +4 -0
- recce/data/__next.__PAGE__.txt +6 -0
- recce/data/__next._full.txt +32 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +14 -0
- recce/data/__next._tree.txt +8 -0
- recce/data/_next/static/chunks/025a7e3e3f9f40ae.js +1 -0
- recce/data/_next/static/chunks/0ce56d67ef5779ca.js +4 -0
- recce/data/_next/static/chunks/1a6a78780155dac7.js +48 -0
- recce/data/_next/static/chunks/1de8485918b9182a.css +2 -0
- recce/data/_next/static/chunks/1e4b1b50d1e34993.js +1 -0
- recce/data/_next/static/chunks/206d5d181e4c738e.js +1 -0
- recce/data/_next/static/chunks/2c357efc34c5b859.js +25 -0
- recce/data/_next/static/chunks/2e9d95d2d48c479c.js +1 -0
- recce/data/_next/static/chunks/2f016dc4a3edad2e.js +2 -0
- recce/data/_next/static/chunks/313251962d698f7c.js +1 -0
- recce/data/_next/static/chunks/3a9f021f38eb5574.css +1 -0
- recce/data/_next/static/chunks/40079da8d2b8f651.js +1 -0
- recce/data/_next/static/chunks/4599182bffb64661.js +38 -0
- recce/data/_next/static/chunks/4e62f6e184173580.js +1 -0
- recce/data/_next/static/chunks/5c4dfb0d09eaa401.js +1 -0
- recce/data/_next/static/chunks/69e4f06ccfdfc3ac.js +1 -0
- recce/data/_next/static/chunks/6b206cb4707d6bee.js +1 -0
- recce/data/_next/static/chunks/6d8557f062aa4386.css +1 -0
- recce/data/_next/static/chunks/7fbe3650bd83b6b5.js +1 -0
- recce/data/_next/static/chunks/83fa823a825674f6.js +1 -0
- recce/data/_next/static/chunks/848a6c9b5f55f7ed.js +1 -0
- recce/data/_next/static/chunks/859462b0858aef88.css +2 -0
- recce/data/_next/static/chunks/923964f18c87d0f1.css +1 -0
- recce/data/_next/static/chunks/939390f911895d7c.js +48 -0
- recce/data/_next/static/chunks/99a9817237a07f43.js +1 -0
- recce/data/_next/static/chunks/9fed8b4b2b924054.js +5 -0
- recce/data/_next/static/chunks/b6949f6c5892110c.js +1 -0
- recce/data/_next/static/chunks/b851a1d3f8149828.js +1 -0
- recce/data/_next/static/chunks/c734f9ad957de0b4.js +1 -0
- recce/data/_next/static/chunks/cdde321b0ec75717.js +2 -0
- recce/data/_next/static/chunks/d0f91117d77ff844.css +1 -0
- recce/data/_next/static/chunks/d6c8667911c2500f.js +1 -0
- recce/data/_next/static/chunks/da8dab68c02752cf.js +74 -0
- recce/data/_next/static/chunks/dc074049c9d12d97.js +109 -0
- recce/data/_next/static/chunks/ee7f1a8227342421.js +1 -0
- recce/data/_next/static/chunks/fa2f4e56c2fccc73.js +1 -0
- recce/data/_next/static/chunks/turbopack-1fad664f62979b93.js +3 -0
- recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_buildManifest.js +11 -0
- recce/data/_next/static/nX-Uz0AH6Tc6hIQUFGqaB/_clientMiddlewareManifest.json +1 -0
- recce/data/_not-found/__next._full.txt +24 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +13 -0
- recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
- recce/data/_not-found/__next._not-found.txt +4 -0
- recce/data/_not-found/__next._tree.txt +6 -0
- recce/data/_not-found/index.html +2 -0
- recce/data/_not-found/index.txt +24 -0
- recce/data/auth_callback.html +68 -0
- recce/data/checks/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/checks/__next._full.txt +39 -0
- recce/data/checks/__next._head.txt +8 -0
- recce/data/checks/__next._index.txt +14 -0
- recce/data/checks/__next._tree.txt +8 -0
- recce/data/checks/__next.checks.__PAGE__.txt +10 -0
- recce/data/checks/__next.checks.txt +4 -0
- recce/data/checks/index.html +2 -0
- recce/data/checks/index.txt +39 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +2 -27
- recce/data/index.txt +32 -7
- recce/data/lineage/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/lineage/__next._full.txt +39 -0
- recce/data/lineage/__next._head.txt +8 -0
- recce/data/lineage/__next._index.txt +14 -0
- recce/data/lineage/__next._tree.txt +8 -0
- recce/data/lineage/__next.lineage.__PAGE__.txt +10 -0
- recce/data/lineage/__next.lineage.txt +4 -0
- recce/data/lineage/index.html +2 -0
- recce/data/lineage/index.txt +39 -0
- recce/data/query/__next.@lineage.__DEFAULT__.txt +7 -0
- recce/data/query/__next._full.txt +37 -0
- recce/data/query/__next._head.txt +8 -0
- recce/data/query/__next._index.txt +14 -0
- recce/data/query/__next._tree.txt +8 -0
- recce/data/query/__next.query.__PAGE__.txt +9 -0
- recce/data/query/__next.query.txt +4 -0
- recce/data/query/index.html +2 -0
- recce/data/query/index.txt +37 -0
- recce/diff.py +6 -12
- recce/event/CONFIG.bak +1 -0
- recce/event/__init__.py +86 -74
- recce/event/collector.py +33 -22
- recce/event/track.py +49 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/mcp_server.py +725 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +438 -21
- recce/models/run.py +1 -0
- recce/models/types.py +134 -28
- recce/pull_request.py +27 -25
- recce/run.py +179 -122
- recce/server.py +394 -104
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +644 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +119 -0
- recce/state/state_loader.py +174 -0
- recce/summary.py +196 -149
- recce/tasks/__init__.py +19 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +82 -18
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +152 -86
- recce/tasks/query.py +180 -89
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/utils.py +147 -0
- recce/tasks/valuediff.py +247 -155
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +105 -100
- recce/util/cll.py +274 -219
- recce/util/cloud/__init__.py +15 -0
- recce/util/cloud/base.py +115 -0
- recce/util/cloud/check_events.py +190 -0
- recce/util/cloud/checks.py +242 -0
- recce/util/io.py +22 -17
- recce/util/lineage.py +65 -16
- recce/util/logger.py +1 -1
- recce/util/onboarding_state.py +45 -0
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +347 -72
- recce/util/singleton.py +4 -4
- recce/util/startup_perf.py +121 -0
- recce/yaml/__init__.py +7 -10
- recce_nightly-1.30.0.20251221.dist-info/METADATA +195 -0
- recce_nightly-1.30.0.20251221.dist-info/RECORD +183 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/WHEEL +1 -2
- recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
- recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
- recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
- recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
- recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
- recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
- recce/data/_next/static/chunks/500-e51c92a025a51234.js +0 -65
- recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
- recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
- recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
- recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
- recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-9adc25782272ed2e.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
- recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
- recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
- recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
- recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
- recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
- recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
- recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
- recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/data/_next/static/qiyFlux77VkhxiceAJe_F/_buildManifest.js +0 -1
- recce/state.py +0 -753
- recce_nightly-0.62.0.20250417.dist-info/METADATA +0 -311
- recce_nightly-0.62.0.20250417.dist-info/RECORD +0 -139
- recce_nightly-0.62.0.20250417.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/__init__.py +0 -0
- tests/adapter/dbt_adapter/conftest.py +0 -13
- tests/adapter/dbt_adapter/dbt_test_helper.py +0 -283
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -40
- tests/adapter/dbt_adapter/test_dbt_cll.py +0 -102
- tests/adapter/dbt_adapter/test_selector.py +0 -177
- tests/tasks/__init__.py +0 -0
- tests/tasks/conftest.py +0 -4
- tests/tasks/test_histogram.py +0 -137
- tests/tasks/test_lineage.py +0 -42
- tests/tasks/test_preset_checks.py +0 -50
- tests/tasks/test_profile.py +0 -73
- tests/tasks/test_query.py +0 -151
- tests/tasks/test_row_count.py +0 -116
- tests/tasks/test_schema.py +0 -99
- tests/tasks/test_top_k.py +0 -73
- tests/tasks/test_valuediff.py +0 -74
- tests/test_cli.py +0 -122
- tests/test_config.py +0 -45
- tests/test_core.py +0 -27
- tests/test_dbt.py +0 -36
- tests/test_pull_request.py +0 -130
- tests/test_server.py +0 -98
- tests/test_state.py +0 -123
- tests/test_summary.py +0 -57
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- /recce/data/_next/static/{qiyFlux77VkhxiceAJe_F → nX-Uz0AH6Tc6hIQUFGqaB}/_ssgManifest.js +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/entry_points.txt +0 -0
- {recce_nightly-0.62.0.20250417.dist-info → recce_nightly-1.30.0.20251221.dist-info}/licenses/LICENSE +0 -0
recce/util/breaking.py
CHANGED
|
@@ -3,15 +3,15 @@ from dataclasses import dataclass
|
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
5
|
import sqlglot.expressions as exp
|
|
6
|
-
from sqlglot import
|
|
6
|
+
from sqlglot import Dialect, parse_one
|
|
7
7
|
from sqlglot.errors import SqlglotError
|
|
8
|
-
from sqlglot.optimizer import
|
|
8
|
+
from sqlglot.optimizer import Scope, traverse_scope
|
|
9
9
|
from sqlglot.optimizer.qualify import qualify
|
|
10
10
|
|
|
11
|
-
from recce.models.types import
|
|
11
|
+
from recce.models.types import ChangeStatus, NodeChange
|
|
12
12
|
|
|
13
|
-
CHANGE_CATEGORY_UNKNOWN = NodeChange(category=
|
|
14
|
-
CHANGE_CATEGORY_BREAKING = NodeChange(category=
|
|
13
|
+
CHANGE_CATEGORY_UNKNOWN = NodeChange(category="unknown")
|
|
14
|
+
CHANGE_CATEGORY_BREAKING = NodeChange(category="breaking")
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@dataclass
|
|
@@ -48,11 +48,11 @@ class BreakingPerformanceTracking:
|
|
|
48
48
|
|
|
49
49
|
def to_dict(self):
|
|
50
50
|
return {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
"lineage_diff_elapsed_ms": self.lineage_diff_elapsed,
|
|
52
|
+
"modified_nodes": self.modified_nodes,
|
|
53
|
+
"sqlglot_error_nodes": self.sqlglot_error_nodes,
|
|
54
|
+
"other_error_nodes": self.other_error_nodes,
|
|
55
|
+
"checkpoints": self.checkpoints,
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
def reset(self):
|
|
@@ -64,32 +64,38 @@ class BreakingPerformanceTracking:
|
|
|
64
64
|
self.checkpoints = {}
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def _diff_select_scope(
|
|
68
|
-
old_scope
|
|
69
|
-
new_scope
|
|
70
|
-
scope_changes_map: dict[Scope, NodeChange]
|
|
71
|
-
) -> NodeChange:
|
|
72
|
-
assert old_scope.expression.key == 'select'
|
|
73
|
-
assert new_scope.expression.key == 'select'
|
|
67
|
+
def _diff_select_scope(old_scope: Scope, new_scope: Scope, scope_changes_map: dict[Scope, NodeChange]) -> NodeChange:
|
|
68
|
+
assert old_scope.expression.key == "select"
|
|
69
|
+
assert new_scope.expression.key == "select"
|
|
74
70
|
|
|
75
|
-
|
|
71
|
+
change_category = "non_breaking"
|
|
72
|
+
changed_columns = {}
|
|
76
73
|
|
|
77
74
|
# check if the upstream scopes is not breaking
|
|
78
75
|
for source_name, source in new_scope.sources.items():
|
|
79
76
|
if scope_changes_map.get(source) is not None:
|
|
80
|
-
|
|
81
|
-
if
|
|
82
|
-
|
|
77
|
+
change = scope_changes_map[source]
|
|
78
|
+
if change.category == "breaking":
|
|
79
|
+
change_category = "breaking"
|
|
80
|
+
|
|
81
|
+
# check if the upstream scopes sources table are the same
|
|
82
|
+
if len(old_scope.sources) != len(new_scope.sources):
|
|
83
|
+
change_category = "breaking"
|
|
84
|
+
else:
|
|
85
|
+
old_source_tables = [s.name for s in old_scope.sources.values() if isinstance(s, exp.Table)]
|
|
86
|
+
new_source_tables = [s.name for s in new_scope.sources.values() if isinstance(s, exp.Table)]
|
|
87
|
+
if sorted(old_source_tables) != sorted(new_source_tables):
|
|
88
|
+
change_category = "breaking"
|
|
83
89
|
|
|
84
90
|
# check if non-select expressions are the same
|
|
85
91
|
old_select = old_scope.expression # type: exp.Select
|
|
86
92
|
new_select = new_scope.expression # type: exp.Select
|
|
87
93
|
for arg_key in old_select.args.keys() | new_select.args.keys():
|
|
88
|
-
if arg_key in [
|
|
94
|
+
if arg_key in ["expressions", "with", "from", "with_", "from_"]:
|
|
89
95
|
continue
|
|
90
96
|
|
|
91
97
|
if old_select.args.get(arg_key) != new_select.args.get(arg_key):
|
|
92
|
-
|
|
98
|
+
change_category = "breaking"
|
|
93
99
|
|
|
94
100
|
def source_column_change_status(ref_column: exp.Column) -> Optional[ChangeStatus]:
|
|
95
101
|
table_name = ref_column.table
|
|
@@ -107,10 +113,10 @@ def _diff_select_scope(
|
|
|
107
113
|
# selects
|
|
108
114
|
old_column_map = {projection.alias_or_name: projection for projection in old_select.selects}
|
|
109
115
|
new_column_map = {projection.alias_or_name: projection for projection in new_select.selects}
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
is_distinct = new_select.args.get("distinct") is not None
|
|
117
|
+
|
|
118
|
+
for column_name in old_column_map.keys() | new_column_map.keys():
|
|
112
119
|
|
|
113
|
-
for column_name in (old_column_map.keys() | new_column_map.keys()):
|
|
114
120
|
def _has_udtf(expr: exp.Expression) -> bool:
|
|
115
121
|
return expr.find(exp.UDTF) is not None
|
|
116
122
|
|
|
@@ -124,133 +130,132 @@ def _diff_select_scope(
|
|
|
124
130
|
new_column = new_column_map.get(column_name)
|
|
125
131
|
if old_column is None:
|
|
126
132
|
if is_distinct:
|
|
127
|
-
|
|
133
|
+
change_category = "breaking"
|
|
134
|
+
elif _has_udtf(new_column):
|
|
135
|
+
change_category = "breaking"
|
|
128
136
|
|
|
129
|
-
|
|
130
|
-
return CHANGE_CATEGORY_BREAKING
|
|
131
|
-
|
|
132
|
-
changed_columns[column_name] = 'added'
|
|
137
|
+
changed_columns[column_name] = "added"
|
|
133
138
|
elif new_column is None:
|
|
134
139
|
if is_distinct:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return CHANGE_CATEGORY_BREAKING
|
|
140
|
+
change_category = "breaking"
|
|
141
|
+
elif _has_udtf(old_column):
|
|
142
|
+
change_category = "breaking"
|
|
139
143
|
|
|
140
|
-
changed_columns[column_name] =
|
|
141
|
-
|
|
144
|
+
changed_columns[column_name] = "removed"
|
|
145
|
+
if change_category != "breaking":
|
|
146
|
+
change_category = "partial_breaking"
|
|
142
147
|
elif old_column != new_column:
|
|
143
148
|
if is_distinct:
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
result.category = 'partial_breaking'
|
|
149
|
+
change_category = "breaking"
|
|
150
|
+
elif _has_udtf(old_column) and _has_udtf(new_column):
|
|
151
|
+
change_category = "breaking"
|
|
152
|
+
elif _has_aggregate(old_column) != _has_aggregate(new_column):
|
|
153
|
+
change_category = "breaking"
|
|
154
|
+
|
|
155
|
+
changed_columns[column_name] = "modified"
|
|
156
|
+
if change_category != "breaking":
|
|
157
|
+
change_category = "partial_breaking"
|
|
154
158
|
else:
|
|
155
159
|
if _has_star(new_column):
|
|
156
160
|
for source_name, (_, source) in new_scope.selected_sources.items():
|
|
157
161
|
change = scope_changes_map.get(source)
|
|
158
162
|
if change is not None:
|
|
163
|
+
if change.category == "breaking":
|
|
164
|
+
change_category = "breaking"
|
|
159
165
|
for sub_column_name in change.columns.keys():
|
|
160
166
|
column_change_status = change.columns[sub_column_name]
|
|
161
167
|
changed_columns[sub_column_name] = column_change_status
|
|
162
|
-
if column_change_status in [
|
|
163
|
-
|
|
168
|
+
if change_category != "breaking" and column_change_status in ["removed", "modified"]:
|
|
169
|
+
change_category = "partial_breaking"
|
|
164
170
|
continue
|
|
165
171
|
|
|
166
172
|
ref_columns = new_column.find_all(exp.Column)
|
|
167
173
|
for ref_column in ref_columns:
|
|
168
174
|
if source_column_change_status(ref_column) is not None:
|
|
169
175
|
if is_distinct:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
change_category = "breaking"
|
|
177
|
+
elif _has_udtf(new_column):
|
|
178
|
+
change_category = "breaking"
|
|
179
|
+
|
|
180
|
+
if change_category != "breaking":
|
|
181
|
+
change_category = "partial_breaking"
|
|
182
|
+
changed_columns[column_name] = "modified"
|
|
175
183
|
|
|
176
184
|
def selected_column_change_status(ref_column: exp.Column) -> Optional[ChangeStatus]:
|
|
177
185
|
column_name = ref_column.name
|
|
178
186
|
return changed_columns.get(column_name)
|
|
179
187
|
|
|
180
188
|
# joins clause: Reference the source columns
|
|
181
|
-
if new_select.args.get(
|
|
182
|
-
joins = new_select.args.get(
|
|
189
|
+
if new_select.args.get("joins"):
|
|
190
|
+
joins = new_select.args.get("joins")
|
|
183
191
|
for join in joins:
|
|
184
192
|
if isinstance(join, exp.Join):
|
|
185
193
|
for ref_column in join.find_all(exp.Column):
|
|
186
194
|
if source_column_change_status(ref_column) is not None:
|
|
187
|
-
|
|
195
|
+
change_category = "breaking"
|
|
188
196
|
|
|
189
197
|
# where clauses: Reference the source columns
|
|
190
|
-
if new_select.args.get(
|
|
191
|
-
where = new_select.args.get(
|
|
198
|
+
if new_select.args.get("where"):
|
|
199
|
+
where = new_select.args.get("where")
|
|
192
200
|
if isinstance(where, exp.Where):
|
|
193
201
|
for ref_column in where.find_all(exp.Column):
|
|
194
202
|
if source_column_change_status(ref_column) is not None:
|
|
195
|
-
|
|
203
|
+
change_category = "breaking"
|
|
196
204
|
|
|
197
205
|
# group by clause: Reference the source columns, column index
|
|
198
|
-
if new_select.args.get(
|
|
199
|
-
group = new_select.args.get(
|
|
206
|
+
if new_select.args.get("group"):
|
|
207
|
+
group = new_select.args.get("group")
|
|
200
208
|
if isinstance(group, exp.Group):
|
|
201
209
|
for ref_column in group.find_all(exp.Column):
|
|
202
210
|
if source_column_change_status(ref_column) is not None:
|
|
203
|
-
|
|
211
|
+
change_category = "breaking"
|
|
204
212
|
|
|
205
213
|
# having clause: Reference the source columns, selected columns
|
|
206
|
-
if new_select.args.get(
|
|
207
|
-
having = new_select.args.get(
|
|
214
|
+
if new_select.args.get("having"):
|
|
215
|
+
having = new_select.args.get("having")
|
|
208
216
|
if isinstance(having, exp.Having):
|
|
209
217
|
for ref_column in having.find_all(exp.Column):
|
|
210
218
|
if source_column_change_status(ref_column) is not None:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
219
|
+
change_category = "breaking"
|
|
220
|
+
elif selected_column_change_status(ref_column) is not None:
|
|
221
|
+
change_category = "breaking"
|
|
214
222
|
|
|
215
223
|
# order by clause: Reference the source columns, selected columns, column index
|
|
216
|
-
if new_select.args.get(
|
|
217
|
-
order = new_select.args.get(
|
|
224
|
+
if new_select.args.get("order"):
|
|
225
|
+
order = new_select.args.get("order")
|
|
218
226
|
if isinstance(order, exp.Order):
|
|
219
227
|
for ref_column in order.find_all(exp.Column):
|
|
220
228
|
if source_column_change_status(ref_column) is not None:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
change_category = "breaking"
|
|
230
|
+
elif selected_column_change_status(ref_column) is not None:
|
|
231
|
+
change_category = "breaking"
|
|
224
232
|
|
|
225
|
-
|
|
226
|
-
return result
|
|
233
|
+
return NodeChange(category=change_category, columns=changed_columns)
|
|
227
234
|
|
|
228
235
|
|
|
229
|
-
def _diff_union_scope(
|
|
230
|
-
old_scope
|
|
231
|
-
new_scope
|
|
232
|
-
scope_changes_map: dict[Scope, NodeChange]
|
|
233
|
-
) -> NodeChange:
|
|
234
|
-
assert old_scope.expression.key == 'union'
|
|
235
|
-
assert new_scope.expression.key == 'union'
|
|
236
|
+
def _diff_union_scope(old_scope: Scope, new_scope: Scope, scope_changes_map: dict[Scope, NodeChange]) -> NodeChange:
|
|
237
|
+
assert old_scope.expression.key == "union"
|
|
238
|
+
assert new_scope.expression.key == "union"
|
|
236
239
|
assert len(old_scope.union_scopes) == len(new_scope.union_scopes)
|
|
237
240
|
assert new_scope.union_scopes is not None
|
|
238
241
|
assert len(new_scope.union_scopes) > 0
|
|
239
242
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
result_left = scope_changes_map.get(new_scope.union_scopes[0])
|
|
244
|
+
change_category = result_left.category
|
|
245
|
+
changed_columns = result_left.columns.copy()
|
|
243
246
|
|
|
244
247
|
for sub_scope in new_scope.union_scopes[1:]:
|
|
245
248
|
result_right = scope_changes_map.get(sub_scope)
|
|
246
|
-
if
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
if change_category == "partial_breaking":
|
|
250
|
+
if result_right.category in ["breaking"]:
|
|
251
|
+
change_category = result_right.category
|
|
252
|
+
elif change_category == "non_breaking":
|
|
253
|
+
if result_right.category in ["breaking", "partial_breaking"]:
|
|
254
|
+
change_category = result_right.category
|
|
250
255
|
for column_name, column_change_status in result_right.columns.items():
|
|
251
|
-
|
|
256
|
+
changed_columns[column_name] = column_change_status
|
|
252
257
|
|
|
253
|
-
return
|
|
258
|
+
return NodeChange(category=change_category, columns=changed_columns)
|
|
254
259
|
|
|
255
260
|
|
|
256
261
|
def parse_change_category(
|
|
@@ -262,7 +267,7 @@ def parse_change_category(
|
|
|
262
267
|
perf_tracking: BreakingPerformanceTracking = None,
|
|
263
268
|
) -> NodeChange:
|
|
264
269
|
if old_sql == new_sql:
|
|
265
|
-
return NodeChange(category=
|
|
270
|
+
return NodeChange(category="non_breaking")
|
|
266
271
|
|
|
267
272
|
try:
|
|
268
273
|
dialect = Dialect.get(dialect)
|
|
@@ -291,31 +296,31 @@ def parse_change_category(
|
|
|
291
296
|
old_scopes = traverse_scope(old_exp)
|
|
292
297
|
new_scopes = traverse_scope(new_exp)
|
|
293
298
|
if len(old_scopes) != len(new_scopes):
|
|
294
|
-
return
|
|
299
|
+
return NodeChange(category="breaking", columns={})
|
|
295
300
|
|
|
296
301
|
scope_changes_map = {}
|
|
297
302
|
for old_scope, new_scope in zip(old_scopes, new_scopes):
|
|
298
303
|
if old_scope.expression.key != new_scope.expression.key:
|
|
299
|
-
scope_changes_map[new_scope] =
|
|
304
|
+
scope_changes_map[new_scope] = NodeChange(category="breaking")
|
|
300
305
|
continue
|
|
301
306
|
if old_scope == new_scope:
|
|
302
|
-
scope_changes_map[new_scope] = NodeChange(category=
|
|
307
|
+
scope_changes_map[new_scope] = NodeChange(category="non_breaking")
|
|
303
308
|
continue
|
|
304
309
|
|
|
305
310
|
scope_type = old_scope.expression.key
|
|
306
|
-
if scope_type ==
|
|
311
|
+
if scope_type == "select":
|
|
307
312
|
# CTE, Subquery, Root
|
|
308
313
|
result = _diff_select_scope(old_scope, new_scope, scope_changes_map)
|
|
309
|
-
elif scope_type ==
|
|
314
|
+
elif scope_type == "union":
|
|
310
315
|
# Union
|
|
311
316
|
result = _diff_union_scope(old_scope, new_scope, scope_changes_map)
|
|
312
317
|
else:
|
|
313
318
|
if old_scope.expression != new_scope.expression:
|
|
314
|
-
result =
|
|
319
|
+
result = NodeChange(category="breaking", columns={})
|
|
315
320
|
else:
|
|
316
|
-
result = NodeChange(category=
|
|
321
|
+
result = NodeChange(category="non_breaking", columns={})
|
|
317
322
|
|
|
318
|
-
if result.category ==
|
|
323
|
+
if result.category == "unknown":
|
|
319
324
|
return result
|
|
320
325
|
|
|
321
326
|
scope_changes_map[new_scope] = result
|