recce-nightly 1.2.0.20250506__py3-none-any.whl → 1.26.0.20251124__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 +810 -480
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +39 -28
- recce/apis/check_func.py +33 -27
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +29 -23
- recce/artifact.py +119 -51
- recce/cli.py +1299 -323
- recce/config.py +42 -33
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404.html +1 -1
- recce/data/__next.__PAGE__.txt +10 -0
- recce/data/__next._full.txt +23 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +8 -0
- recce/data/__next._tree.txt +5 -0
- recce/data/_next/static/52aV_JrNUZU6dMFgvTQEO/_buildManifest.js +11 -0
- recce/data/_next/static/52aV_JrNUZU6dMFgvTQEO/_clientMiddlewareManifest.json +1 -0
- recce/data/_next/static/chunks/02b996c7f6a29a06.js +4 -0
- recce/data/_next/static/chunks/19c10d219a6a21ff.js +1 -0
- recce/data/_next/static/chunks/2df9ec28a061971d.js +11 -0
- recce/data/_next/static/chunks/3098c987393bda15.js +1 -0
- recce/data/_next/static/chunks/393dc43e483f717a.css +2 -0
- recce/data/_next/static/chunks/399e8d91a7e45073.js +2 -0
- recce/data/_next/static/chunks/4d0186f631230245.js +1 -0
- recce/data/_next/static/chunks/5794ba9e10a9c060.js +11 -0
- recce/data/_next/static/chunks/715761c929a3f28b.js +110 -0
- recce/data/_next/static/chunks/71f88fcc615bf282.js +1 -0
- recce/data/_next/static/chunks/80d2a95eaf1201ea.js +1 -0
- recce/data/_next/static/chunks/9979c6109bbbee35.js +1 -0
- recce/data/_next/static/chunks/99d638224186c118.js +1 -0
- recce/data/_next/static/chunks/d003eb36240e92f3.js +1 -0
- recce/data/_next/static/chunks/d3167cdfec4fc351.js +1 -0
- recce/data/_next/static/chunks/e124bccf574a3361.css +1 -0
- recce/data/_next/static/chunks/f40141db1bdb46f0.css +6 -0
- recce/data/_next/static/chunks/fcc53a88741a52f9.js +1 -0
- recce/data/_next/static/chunks/turbopack-b1920d28cfb1f28d.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/media/reload-image.7aa931c7.svg +4 -0
- recce/data/_not-found/__next._full.txt +17 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +8 -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 +3 -0
- recce/data/_not-found.html +1 -0
- recce/data/_not-found.txt +17 -0
- recce/data/auth_callback.html +68 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +1 -27
- recce/data/index.txt +23 -7
- recce/diff.py +6 -12
- 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 +716 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +6 -7
- recce/models/run.py +1 -0
- recce/models/types.py +131 -28
- recce/pull_request.py +27 -25
- recce/run.py +165 -121
- recce/server.py +303 -111
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +632 -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 +188 -143
- 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 +139 -87
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/valuediff.py +216 -152
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +87 -85
- recce/util/cll.py +274 -219
- 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 +322 -72
- recce/util/singleton.py +4 -4
- recce/yaml/__init__.py +7 -10
- recce_cloud/__init__.py +24 -0
- recce_cloud/api/__init__.py +17 -0
- recce_cloud/api/base.py +111 -0
- recce_cloud/api/client.py +150 -0
- recce_cloud/api/exceptions.py +26 -0
- recce_cloud/api/factory.py +63 -0
- recce_cloud/api/github.py +76 -0
- recce_cloud/api/gitlab.py +82 -0
- recce_cloud/artifact.py +57 -0
- recce_cloud/ci_providers/__init__.py +9 -0
- recce_cloud/ci_providers/base.py +82 -0
- recce_cloud/ci_providers/detector.py +147 -0
- recce_cloud/ci_providers/github_actions.py +136 -0
- recce_cloud/ci_providers/gitlab_ci.py +130 -0
- recce_cloud/cli.py +245 -0
- recce_cloud/upload.py +214 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/METADATA +68 -37
- recce_nightly-1.26.0.20251124.dist-info/RECORD +180 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/WHEEL +1 -1
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/top_level.txt +1 -0
- tests/adapter/dbt_adapter/conftest.py +9 -5
- tests/adapter/dbt_adapter/dbt_test_helper.py +37 -22
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -15
- tests/adapter/dbt_adapter/test_dbt_cll.py +656 -41
- tests/adapter/dbt_adapter/test_selector.py +22 -21
- tests/recce_cloud/__init__.py +0 -0
- tests/recce_cloud/test_ci_providers.py +351 -0
- tests/recce_cloud/test_cli.py +372 -0
- tests/recce_cloud/test_client.py +273 -0
- tests/recce_cloud/test_platform_clients.py +333 -0
- tests/tasks/conftest.py +1 -1
- tests/tasks/test_histogram.py +58 -66
- tests/tasks/test_lineage.py +36 -23
- tests/tasks/test_preset_checks.py +45 -31
- tests/tasks/test_profile.py +339 -15
- tests/tasks/test_query.py +46 -46
- tests/tasks/test_row_count.py +65 -46
- tests/tasks/test_schema.py +65 -42
- tests/tasks/test_top_k.py +22 -18
- tests/tasks/test_valuediff.py +43 -32
- tests/test_cli.py +174 -60
- tests/test_cli_mcp_optional.py +45 -0
- tests/test_cloud_listing_cli.py +324 -0
- tests/test_config.py +7 -9
- tests/test_connect_to_cloud.py +82 -0
- tests/test_core.py +151 -4
- tests/test_dbt.py +7 -7
- tests/test_mcp_server.py +332 -0
- tests/test_pull_request.py +1 -1
- tests/test_server.py +25 -19
- tests/test_summary.py +29 -17
- recce/data/_next/static/Kcbs3GEIyH2LxgLYat0es/_buildManifest.js +0 -1
- 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/368-7587b306577df275.js +0 -65
- 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/3a92ee20-3b5d922d4157af5e.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/6dc81886-c94b9b91bc2c3caf.js +0 -1
- recce/data/_next/static/chunks/6ef81909-694dc38134099299.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/8d700b6a-f0b1f6b9e0d97ce2.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-cee661090afbd6aa.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/state.py +0 -753
- recce_nightly-1.2.0.20250506.dist-info/RECORD +0 -142
- tests/test_state.py +0 -123
- /recce/data/_next/static/{Kcbs3GEIyH2LxgLYat0es → 52aV_JrNUZU6dMFgvTQEO}/_ssgManifest.js +0 -0
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.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,33 +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
|
-
change_category =
|
|
71
|
+
change_category = "non_breaking"
|
|
76
72
|
changed_columns = {}
|
|
77
73
|
|
|
78
74
|
# check if the upstream scopes is not breaking
|
|
79
75
|
for source_name, source in new_scope.sources.items():
|
|
80
76
|
if scope_changes_map.get(source) is not None:
|
|
81
|
-
|
|
82
|
-
if
|
|
83
|
-
change_category =
|
|
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"
|
|
84
89
|
|
|
85
90
|
# check if non-select expressions are the same
|
|
86
91
|
old_select = old_scope.expression # type: exp.Select
|
|
87
92
|
new_select = new_scope.expression # type: exp.Select
|
|
88
93
|
for arg_key in old_select.args.keys() | new_select.args.keys():
|
|
89
|
-
if arg_key in [
|
|
94
|
+
if arg_key in ["expressions", "with", "from", "with_", "from_"]:
|
|
90
95
|
continue
|
|
91
96
|
|
|
92
97
|
if old_select.args.get(arg_key) != new_select.args.get(arg_key):
|
|
93
|
-
change_category =
|
|
98
|
+
change_category = "breaking"
|
|
94
99
|
|
|
95
100
|
def source_column_change_status(ref_column: exp.Column) -> Optional[ChangeStatus]:
|
|
96
101
|
table_name = ref_column.table
|
|
@@ -108,9 +113,10 @@ def _diff_select_scope(
|
|
|
108
113
|
# selects
|
|
109
114
|
old_column_map = {projection.alias_or_name: projection for projection in old_select.selects}
|
|
110
115
|
new_column_map = {projection.alias_or_name: projection for projection in new_select.selects}
|
|
111
|
-
is_distinct = new_select.args.get(
|
|
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,116 +130,112 @@ 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
|
-
change_category =
|
|
133
|
+
change_category = "breaking"
|
|
128
134
|
elif _has_udtf(new_column):
|
|
129
|
-
change_category =
|
|
135
|
+
change_category = "breaking"
|
|
130
136
|
|
|
131
|
-
changed_columns[column_name] =
|
|
137
|
+
changed_columns[column_name] = "added"
|
|
132
138
|
elif new_column is None:
|
|
133
139
|
if is_distinct:
|
|
134
|
-
change_category =
|
|
140
|
+
change_category = "breaking"
|
|
135
141
|
elif _has_udtf(old_column):
|
|
136
|
-
change_category =
|
|
142
|
+
change_category = "breaking"
|
|
137
143
|
|
|
138
|
-
changed_columns[column_name] =
|
|
139
|
-
if change_category !=
|
|
140
|
-
change_category =
|
|
144
|
+
changed_columns[column_name] = "removed"
|
|
145
|
+
if change_category != "breaking":
|
|
146
|
+
change_category = "partial_breaking"
|
|
141
147
|
elif old_column != new_column:
|
|
142
148
|
if is_distinct:
|
|
143
|
-
change_category =
|
|
149
|
+
change_category = "breaking"
|
|
144
150
|
elif _has_udtf(old_column) and _has_udtf(new_column):
|
|
145
|
-
change_category =
|
|
151
|
+
change_category = "breaking"
|
|
146
152
|
elif _has_aggregate(old_column) != _has_aggregate(new_column):
|
|
147
|
-
change_category =
|
|
153
|
+
change_category = "breaking"
|
|
148
154
|
|
|
149
|
-
changed_columns[column_name] =
|
|
150
|
-
if change_category !=
|
|
151
|
-
change_category =
|
|
155
|
+
changed_columns[column_name] = "modified"
|
|
156
|
+
if change_category != "breaking":
|
|
157
|
+
change_category = "partial_breaking"
|
|
152
158
|
else:
|
|
153
159
|
if _has_star(new_column):
|
|
154
160
|
for source_name, (_, source) in new_scope.selected_sources.items():
|
|
155
161
|
change = scope_changes_map.get(source)
|
|
156
162
|
if change is not None:
|
|
157
|
-
if change.category ==
|
|
158
|
-
change_category =
|
|
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 change_category !=
|
|
163
|
-
change_category =
|
|
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
|
-
change_category =
|
|
176
|
+
change_category = "breaking"
|
|
171
177
|
elif _has_udtf(new_column):
|
|
172
|
-
change_category =
|
|
178
|
+
change_category = "breaking"
|
|
173
179
|
|
|
174
|
-
if change_category !=
|
|
175
|
-
change_category =
|
|
176
|
-
changed_columns[column_name] =
|
|
180
|
+
if change_category != "breaking":
|
|
181
|
+
change_category = "partial_breaking"
|
|
182
|
+
changed_columns[column_name] = "modified"
|
|
177
183
|
|
|
178
184
|
def selected_column_change_status(ref_column: exp.Column) -> Optional[ChangeStatus]:
|
|
179
185
|
column_name = ref_column.name
|
|
180
186
|
return changed_columns.get(column_name)
|
|
181
187
|
|
|
182
188
|
# joins clause: Reference the source columns
|
|
183
|
-
if new_select.args.get(
|
|
184
|
-
joins = new_select.args.get(
|
|
189
|
+
if new_select.args.get("joins"):
|
|
190
|
+
joins = new_select.args.get("joins")
|
|
185
191
|
for join in joins:
|
|
186
192
|
if isinstance(join, exp.Join):
|
|
187
193
|
for ref_column in join.find_all(exp.Column):
|
|
188
194
|
if source_column_change_status(ref_column) is not None:
|
|
189
|
-
change_category =
|
|
195
|
+
change_category = "breaking"
|
|
190
196
|
|
|
191
197
|
# where clauses: Reference the source columns
|
|
192
|
-
if new_select.args.get(
|
|
193
|
-
where = new_select.args.get(
|
|
198
|
+
if new_select.args.get("where"):
|
|
199
|
+
where = new_select.args.get("where")
|
|
194
200
|
if isinstance(where, exp.Where):
|
|
195
201
|
for ref_column in where.find_all(exp.Column):
|
|
196
202
|
if source_column_change_status(ref_column) is not None:
|
|
197
|
-
change_category =
|
|
203
|
+
change_category = "breaking"
|
|
198
204
|
|
|
199
205
|
# group by clause: Reference the source columns, column index
|
|
200
|
-
if new_select.args.get(
|
|
201
|
-
group = new_select.args.get(
|
|
206
|
+
if new_select.args.get("group"):
|
|
207
|
+
group = new_select.args.get("group")
|
|
202
208
|
if isinstance(group, exp.Group):
|
|
203
209
|
for ref_column in group.find_all(exp.Column):
|
|
204
210
|
if source_column_change_status(ref_column) is not None:
|
|
205
|
-
change_category =
|
|
211
|
+
change_category = "breaking"
|
|
206
212
|
|
|
207
213
|
# having clause: Reference the source columns, selected columns
|
|
208
|
-
if new_select.args.get(
|
|
209
|
-
having = new_select.args.get(
|
|
214
|
+
if new_select.args.get("having"):
|
|
215
|
+
having = new_select.args.get("having")
|
|
210
216
|
if isinstance(having, exp.Having):
|
|
211
217
|
for ref_column in having.find_all(exp.Column):
|
|
212
218
|
if source_column_change_status(ref_column) is not None:
|
|
213
|
-
change_category =
|
|
219
|
+
change_category = "breaking"
|
|
214
220
|
elif selected_column_change_status(ref_column) is not None:
|
|
215
|
-
change_category =
|
|
221
|
+
change_category = "breaking"
|
|
216
222
|
|
|
217
223
|
# order by clause: Reference the source columns, selected columns, column index
|
|
218
|
-
if new_select.args.get(
|
|
219
|
-
order = new_select.args.get(
|
|
224
|
+
if new_select.args.get("order"):
|
|
225
|
+
order = new_select.args.get("order")
|
|
220
226
|
if isinstance(order, exp.Order):
|
|
221
227
|
for ref_column in order.find_all(exp.Column):
|
|
222
228
|
if source_column_change_status(ref_column) is not None:
|
|
223
|
-
change_category =
|
|
229
|
+
change_category = "breaking"
|
|
224
230
|
elif selected_column_change_status(ref_column) is not None:
|
|
225
|
-
change_category =
|
|
231
|
+
change_category = "breaking"
|
|
226
232
|
|
|
227
233
|
return NodeChange(category=change_category, columns=changed_columns)
|
|
228
234
|
|
|
229
235
|
|
|
230
|
-
def _diff_union_scope(
|
|
231
|
-
old_scope
|
|
232
|
-
new_scope
|
|
233
|
-
scope_changes_map: dict[Scope, NodeChange]
|
|
234
|
-
) -> NodeChange:
|
|
235
|
-
assert old_scope.expression.key == 'union'
|
|
236
|
-
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"
|
|
237
239
|
assert len(old_scope.union_scopes) == len(new_scope.union_scopes)
|
|
238
240
|
assert new_scope.union_scopes is not None
|
|
239
241
|
assert len(new_scope.union_scopes) > 0
|
|
@@ -244,11 +246,11 @@ def _diff_union_scope(
|
|
|
244
246
|
|
|
245
247
|
for sub_scope in new_scope.union_scopes[1:]:
|
|
246
248
|
result_right = scope_changes_map.get(sub_scope)
|
|
247
|
-
if change_category ==
|
|
248
|
-
if result_right.category in [
|
|
249
|
+
if change_category == "partial_breaking":
|
|
250
|
+
if result_right.category in ["breaking"]:
|
|
249
251
|
change_category = result_right.category
|
|
250
|
-
elif change_category ==
|
|
251
|
-
if result_right.category in [
|
|
252
|
+
elif change_category == "non_breaking":
|
|
253
|
+
if result_right.category in ["breaking", "partial_breaking"]:
|
|
252
254
|
change_category = result_right.category
|
|
253
255
|
for column_name, column_change_status in result_right.columns.items():
|
|
254
256
|
changed_columns[column_name] = column_change_status
|
|
@@ -265,7 +267,7 @@ def parse_change_category(
|
|
|
265
267
|
perf_tracking: BreakingPerformanceTracking = None,
|
|
266
268
|
) -> NodeChange:
|
|
267
269
|
if old_sql == new_sql:
|
|
268
|
-
return NodeChange(category=
|
|
270
|
+
return NodeChange(category="non_breaking")
|
|
269
271
|
|
|
270
272
|
try:
|
|
271
273
|
dialect = Dialect.get(dialect)
|
|
@@ -294,31 +296,31 @@ def parse_change_category(
|
|
|
294
296
|
old_scopes = traverse_scope(old_exp)
|
|
295
297
|
new_scopes = traverse_scope(new_exp)
|
|
296
298
|
if len(old_scopes) != len(new_scopes):
|
|
297
|
-
return NodeChange(category=
|
|
299
|
+
return NodeChange(category="breaking", columns={})
|
|
298
300
|
|
|
299
301
|
scope_changes_map = {}
|
|
300
302
|
for old_scope, new_scope in zip(old_scopes, new_scopes):
|
|
301
303
|
if old_scope.expression.key != new_scope.expression.key:
|
|
302
|
-
scope_changes_map[new_scope] = NodeChange(category=
|
|
304
|
+
scope_changes_map[new_scope] = NodeChange(category="breaking")
|
|
303
305
|
continue
|
|
304
306
|
if old_scope == new_scope:
|
|
305
|
-
scope_changes_map[new_scope] = NodeChange(category=
|
|
307
|
+
scope_changes_map[new_scope] = NodeChange(category="non_breaking")
|
|
306
308
|
continue
|
|
307
309
|
|
|
308
310
|
scope_type = old_scope.expression.key
|
|
309
|
-
if scope_type ==
|
|
311
|
+
if scope_type == "select":
|
|
310
312
|
# CTE, Subquery, Root
|
|
311
313
|
result = _diff_select_scope(old_scope, new_scope, scope_changes_map)
|
|
312
|
-
elif scope_type ==
|
|
314
|
+
elif scope_type == "union":
|
|
313
315
|
# Union
|
|
314
316
|
result = _diff_union_scope(old_scope, new_scope, scope_changes_map)
|
|
315
317
|
else:
|
|
316
318
|
if old_scope.expression != new_scope.expression:
|
|
317
|
-
result = NodeChange(category=
|
|
319
|
+
result = NodeChange(category="breaking", columns={})
|
|
318
320
|
else:
|
|
319
|
-
result = NodeChange(category=
|
|
321
|
+
result = NodeChange(category="non_breaking", columns={})
|
|
320
322
|
|
|
321
|
-
if result.category ==
|
|
323
|
+
if result.category == "unknown":
|
|
322
324
|
return result
|
|
323
325
|
|
|
324
326
|
scope_changes_map[new_scope] = result
|