sql-diff-explainer 0.1.1__py3-none-any.whl → 0.1.2__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.
@@ -23,4 +23,4 @@ __all__ = [
23
23
  "explain_diff",
24
24
  "explain_sql_change",
25
25
  ]
26
- __version__ = "0.1.1"
26
+ __version__ = "0.1.2"
sql_diff_explainer/cli.py CHANGED
@@ -7,6 +7,8 @@ from pathlib import Path
7
7
 
8
8
  from .core import SQLDiffError, explain_sql_change
9
9
 
10
+ _RISK_SEVERITY = {"low": 0, "medium": 1, "high": 2}
11
+
10
12
 
11
13
  def main() -> int:
12
14
  """Run the command-line interface."""
@@ -17,6 +19,11 @@ def main() -> int:
17
19
  parser.add_argument("after", type=Path, help="Updated SQL file")
18
20
  parser.add_argument("--dialect", help="sqlglot dialect used to parse both files")
19
21
  parser.add_argument("--format", choices=("text", "json"), default="text", dest="output_format")
22
+ parser.add_argument(
23
+ "--fail-on-risk",
24
+ choices=tuple(_RISK_SEVERITY),
25
+ help="Exit with status 1 when detected risk meets or exceeds this level",
26
+ )
20
27
  args = parser.parse_args()
21
28
 
22
29
  try:
@@ -40,6 +47,12 @@ def main() -> int:
40
47
  print(f" before: {change.before}")
41
48
  if change.after is not None:
42
49
  print(f" after: {change.after}")
50
+ if (
51
+ args.fail_on_risk is not None
52
+ and result.changes
53
+ and _RISK_SEVERITY[result.risk] >= _RISK_SEVERITY[args.fail_on_risk]
54
+ ):
55
+ return 1
43
56
  return 0
44
57
 
45
58
 
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.5
2
+ Name: sql-diff-explainer
3
+ Version: 0.1.2
4
+ Summary: Explain SQL schema and migration differences
5
+ Author: zmodz1010
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: sqlglot>=25.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.0; extra == 'dev'
12
+ Requires-Dist: ruff>=0.6; extra == 'dev'
13
+ Description-Content-Type: text/markdown
14
+
15
+ # SQL Diff Explainer
16
+
17
+ Explain the meaningful structural differences between two SQL `SELECT` queries.
18
+
19
+ The package uses [sqlglot](https://github.com/tobymao/sqlglot) behind an abstract
20
+ parser interface, so callers receive a stable result even if the parser
21
+ implementation changes. It reports changes to projections, joins, filters,
22
+ grouping, aggregation, ordering, pagination, and `DISTINCT`, with a static
23
+ low/medium/high risk classification.
24
+
25
+ The next patch release is **0.1.2**. It adds the CI-friendly CLI
26
+ `--fail-on-risk` threshold.
27
+
28
+ ## Why use SQL Diff Explainer?
29
+
30
+ Use it in a pull request, migration review, or CI check when a textual SQL diff
31
+ does not explain the behavioral impact of a query change. It is especially
32
+ useful for data-engineering teams reviewing:
33
+
34
+ - dbt model changes where a join or filter can change row counts
35
+ - warehouse reports where grouping or aggregation changes the grain
36
+ - incremental pipeline queries where `DISTINCT`, `ORDER BY`, `LIMIT`, or
37
+ `OFFSET` changes the records consumed downstream
38
+ - generated SQL changes where normalized syntax is easier to compare than
39
+ formatting or alias changes
40
+
41
+ The result is a structured object, so a CI integration can fail or annotate a
42
+ review based on `result.risk_level`, inspect `result.changes`, or serialize
43
+ `result.to_dict()` as JSON.
44
+
45
+ ## Real-life data-engineering examples
46
+
47
+ The `examples/` directory contains runnable before/after pairs. For example:
48
+
49
+ ```powershell
50
+ sql-diff-explainer examples\join_before.sql examples\join_after.sql
51
+ sql-diff-explainer examples\filter_before.sql examples\filter_after.sql --format json
52
+ sql-diff-explainer examples\aggregation_before.sql examples\aggregation_after.sql
53
+ sql-diff-explainer examples\distinct_before.sql examples\distinct_after.sql
54
+ ```
55
+
56
+ These scenarios demonstrate the changes reviewers most often need to discuss:
57
+
58
+ 1. **Outer join to inner join:** changing `LEFT JOIN` to `INNER JOIN` can
59
+ exclude dimension or fact rows without a match, so the tool reports one
60
+ semantic join change at high risk.
61
+ 2. **Added `WHERE` filter:** adding `WHERE order_status = 'completed'` narrows
62
+ the population and is reported as a filter change.
63
+ 3. **Aggregation or grouping change:** changing `COUNT(*)` to `SUM(revenue)`,
64
+ or changing the `GROUP BY` grain from customer to customer and day, can
65
+ change metric meaning and row cardinality.
66
+ 4. **`DISTINCT`, ordering, and pagination:** adding `DISTINCT` changes duplicate
67
+ handling; changing `ORDER BY` changes presentation or deterministic paging;
68
+ changing `LIMIT`/`OFFSET` changes which rows a downstream consumer receives.
69
+
70
+ Try the same comparisons from Python:
71
+
72
+ ```python
73
+ from sql_diff_explainer import explain_sql_change
74
+
75
+ result = explain_sql_change(
76
+ "SELECT c.id FROM customers c LEFT JOIN orders o ON c.id = o.customer_id",
77
+ "SELECT c.id FROM customers c INNER JOIN orders o ON c.id = o.customer_id",
78
+ )
79
+ assert result.risk_level == "high"
80
+ for change in result.changes:
81
+ print(change.category, change.risk, change.description)
82
+ ```
83
+
84
+ ## What it catches—and what it cannot know
85
+
86
+ The analyzer parses both inputs with sqlglot and compares the supported
87
+ `SELECT` structure: projections, joins and join types, `WHERE`, `GROUP BY`,
88
+ aggregate functions, `ORDER BY`, `LIMIT`, `OFFSET`, and `DISTINCT`. It provides
89
+ static risk guidance; it does not execute SQL or connect to a warehouse.
90
+
91
+ Risk is therefore a useful review signal, not a correctness proof. The tool
92
+ cannot infer table statistics, actual null rates, constraints, query plans,
93
+ cost, freshness, permissions, dialect-specific UDF behavior, or whether a
94
+ business metric is correct. It also does not validate schema availability or
95
+ compare multiple statements in a migration file. Pair its output with
96
+ warehouse tests, representative row-count checks, query-plan review, and
97
+ domain-owner approval.
98
+
99
+ ## Development
100
+
101
+ This project uses a `src/` layout and requires Python 3.10 or newer.
102
+
103
+ ```powershell
104
+ python -m venv .venv
105
+ .venv\Scripts\Activate.ps1
106
+ python -m pip install -e ".[dev]"
107
+ pytest
108
+ ```
109
+
110
+ ## Library usage
111
+
112
+ ```python
113
+ from sql_diff_explainer import explain_sql_change
114
+
115
+ result = explain_sql_change(
116
+ "SELECT department, COUNT(*) FROM employees GROUP BY department",
117
+ "SELECT department, COUNT(*) FROM employees WHERE active = TRUE GROUP BY department",
118
+ )
119
+ print(result.to_dict())
120
+ ```
121
+
122
+ `explain_sql_change` raises `SQLParseError` for invalid SQL and `UnsupportedSQL`
123
+ for non-`SELECT` statements. Pass `dialect="postgres"` (or another sqlglot
124
+ dialect) when parsing dialect-specific syntax.
125
+
126
+ ## CLI
127
+
128
+ ```powershell
129
+ sql-diff-explainer before.sql after.sql
130
+ sql-diff-explainer before.sql after.sql --dialect postgres --format json
131
+ sql-diff-explainer before.sql after.sql --fail-on-risk medium
132
+ sql-diff-explainer before.sql after.sql --format json --fail-on-risk high
133
+ ```
134
+
135
+ `--fail-on-risk` accepts `low`, `medium`, or `high` and exits with status 1 when
136
+ detected overall risk meets or exceeds the chosen threshold. It does not suppress
137
+ the regular text or JSON report. Comparisons with no detected changes exit
138
+ successfully even though their baseline report risk is `low`. JSON output is
139
+ intended for CI and integrations; text output is optimized for human review.
140
+
141
+ Exit codes:
142
+
143
+ - `0`: the comparison succeeded and no detected change met the configured
144
+ threshold (or `--fail-on-risk` was not set).
145
+ - `1`: detected overall risk met or exceeded `--fail-on-risk`.
146
+ - `2`: an input file could not be read or an expected SQL parse/support error
147
+ occurred.
@@ -0,0 +1,8 @@
1
+ sql_diff_explainer/__init__.py,sha256=P-jgWkLNkeuM1kcLChW7rSYWn6EpqOY8K0qMvALxqyA,488
2
+ sql_diff_explainer/cli.py,sha256=XNYIVozEXbjJa_1PXX6ZYmgqxmqHkLHAYNuKaovMCJI,2091
3
+ sql_diff_explainer/core.py,sha256=P9NrPjHGivx-sGlyIyVi7V41x4R2lH3ETC4y8opz_yQ,13265
4
+ sql_diff_explainer-0.1.2.dist-info/METADATA,sha256=0UOgvJOzI3j8gbI64wGVOq1YkGKkNNajipAEGyYJjL4,5863
5
+ sql_diff_explainer-0.1.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
6
+ sql_diff_explainer-0.1.2.dist-info/entry_points.txt,sha256=Pu2gG46es25JExzGO58plyLEsFedeCUM-TQnUeLpOlI,67
7
+ sql_diff_explainer-0.1.2.dist-info/licenses/LICENSE,sha256=OJ2eEuGmpMxVb7jSmuVgeDcVHCEOMBkZgLgTlnK5mQQ,1087
8
+ sql_diff_explainer-0.1.2.dist-info/RECORD,,
@@ -1,65 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: sql-diff-explainer
3
- Version: 0.1.1
4
- Summary: Explain SQL schema and migration differences
5
- Author: zmodz1010
6
- License: MIT
7
- License-File: LICENSE
8
- Requires-Python: >=3.10
9
- Requires-Dist: sqlglot>=25.0
10
- Provides-Extra: dev
11
- Requires-Dist: pytest>=8.0; extra == 'dev'
12
- Requires-Dist: ruff>=0.6; extra == 'dev'
13
- Description-Content-Type: text/markdown
14
-
15
- # SQL Diff Explainer
16
-
17
- Explain the meaningful structural differences between two SQL `SELECT` queries.
18
-
19
- The package uses [sqlglot](https://github.com/tobymao/sqlglot) behind an abstract
20
- parser interface, so callers receive a stable result even if the parser
21
- implementation changes. It reports changes to projections, joins, filters,
22
- grouping, aggregation, ordering, pagination, and `DISTINCT`, with a static
23
- low/medium/high risk classification.
24
-
25
- The current patch release is **0.1.1**. It includes the stable `risk_level`
26
- compatibility property, semantic join-type pairing, and high-risk classification
27
- for outer-to-inner join changes.
28
-
29
- ## Development
30
-
31
- This project uses a `src/` layout and requires Python 3.10 or newer.
32
-
33
- ```powershell
34
- python -m venv .venv
35
- .venv\Scripts\Activate.ps1
36
- python -m pip install -e ".[dev]"
37
- pytest
38
- ```
39
-
40
- ## Library usage
41
-
42
- ```python
43
- from sql_diff_explainer import explain_sql_change
44
-
45
- result = explain_sql_change(
46
- "SELECT department, COUNT(*) FROM employees GROUP BY department",
47
- "SELECT department, COUNT(*) FROM employees WHERE active = TRUE GROUP BY department",
48
- )
49
- print(result.to_dict())
50
- ```
51
-
52
- `explain_sql_change` raises `SQLParseError` for invalid SQL and `UnsupportedSQL`
53
- for non-`SELECT` statements. Pass `dialect="postgres"` (or another sqlglot
54
- dialect) when parsing dialect-specific syntax.
55
-
56
- ## CLI
57
-
58
- ```powershell
59
- sql-diff-explainer before.sql after.sql
60
- sql-diff-explainer before.sql after.sql --dialect postgres --format json
61
- ```
62
-
63
- JSON output is intended for CI and integrations; text output is optimized for
64
- human review. The process exits with status 2 for unreadable files or expected
65
- SQL errors.
@@ -1,8 +0,0 @@
1
- sql_diff_explainer/__init__.py,sha256=n-nvklsRpRpqafOg6dZtPE5yJ3uh9r54BLzaa9xx0_Y,488
2
- sql_diff_explainer/cli.py,sha256=pRgyrCD6bzPpMjOSSJSTZVF_D3Nj4w71cnvPvP6T1DM,1672
3
- sql_diff_explainer/core.py,sha256=P9NrPjHGivx-sGlyIyVi7V41x4R2lH3ETC4y8opz_yQ,13265
4
- sql_diff_explainer-0.1.1.dist-info/METADATA,sha256=LG0recDF171dnNC1qplkMuYxBvGwdm3ng0yHC8KQlBI,2009
5
- sql_diff_explainer-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
6
- sql_diff_explainer-0.1.1.dist-info/entry_points.txt,sha256=Pu2gG46es25JExzGO58plyLEsFedeCUM-TQnUeLpOlI,67
7
- sql_diff_explainer-0.1.1.dist-info/licenses/LICENSE,sha256=OJ2eEuGmpMxVb7jSmuVgeDcVHCEOMBkZgLgTlnK5mQQ,1087
8
- sql_diff_explainer-0.1.1.dist-info/RECORD,,