sql-error-categorizer 0.2.4__tar.gz → 0.3.0__tar.gz
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.
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/.gitignore +1 -1
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/PKG-INFO +2 -2
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/pyproject.toml +2 -2
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/requirements.txt +1 -1
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/src/sql_error_categorizer/detectors/complications.py +68 -63
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/src/sql_error_categorizer/detectors/logical.py +291 -199
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/src/sql_error_categorizer/detectors/semantic.py +25 -197
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/src/sql_error_categorizer/detectors/syntax.py +108 -251
- sql_error_categorizer-0.2.4/tests/1_syn/test_002_ambiguous_column.py → sql_error_categorizer-0.3.0/tests/1_syn/test_001_ambiguous_column.py +1 -1
- sql_error_categorizer-0.2.4/tests/1_syn/test_004_undefined_column.py → sql_error_categorizer-0.3.0/tests/1_syn/test_003_undefined_column.py +1 -1
- sql_error_categorizer-0.2.4/tests/1_syn/test_005_undefined_function.py → sql_error_categorizer-0.3.0/tests/1_syn/test_004_undefined_function.py +1 -1
- sql_error_categorizer-0.2.4/tests/1_syn/test_006_undefined_parameter.py → sql_error_categorizer-0.3.0/tests/1_syn/test_005_undefined_parameter.py +1 -1
- sql_error_categorizer-0.2.4/tests/1_syn/test_007_undefined_tables.py → sql_error_categorizer-0.3.0/tests/1_syn/test_006_undefined_object.py +1 -1
- sql_error_categorizer-0.2.4/tests/1_syn/test_008_invalid_schema_names.py → sql_error_categorizer-0.3.0/tests/1_syn/test_007_invalid_schema_names.py +1 -1
- sql_error_categorizer-0.2.4/tests/1_syn/test_009_misspellings.py → sql_error_categorizer-0.3.0/tests/1_syn/test_008_misspellings.py +1 -1
- sql_error_categorizer-0.2.4/tests/1_syn/test_035_is_where_not_applicable.py → sql_error_categorizer-0.3.0/tests/1_syn/test_012_is_where_not_applicable.py +1 -1
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/1_syn/test_013_data_type_mismatch.py +1 -1
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/1_syn/test_014_aggregate_function_outside_select_or_having.py +1 -1
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/1_syn/test_015_nested_aggregate_functions.py +1 -1
- sql_error_categorizer-0.3.0/tests/1_syn/test_016_extraneous_omitted_grouping_column.py +47 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_017_having_without_group_by.py +59 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_018_too_many_columns_in_subquery.py +42 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_021_using_where_twice.py +53 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_022_omitted_from.py +51 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_024_036_additional_omitted_semicolons.py +60 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_024_comparison_with_null.py +40 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_026_duplicate_clause.py +43 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_029_keywords_order.py +36 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_032_033_curly_square_or_unmatched_brackets.py +68 -0
- sql_error_categorizer-0.3.0/tests/1_syn/test_035_nonstandard_operators.py +31 -0
- sql_error_categorizer-0.2.4/tests/2_sem/test_040_tautological_inconsistent_expressions.py → sql_error_categorizer-0.3.0/tests/2_sem/test_038_tautological_inconsistent_expressions.py +1 -1
- sql_error_categorizer-0.3.0/tests/2_sem/test_039_distinct_sum_avg.py +51 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/3_log/test_058_join_on_incorrect_table.py +3 -3
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/3_log/test_059_join_when_join_needs_to_be_omitted.py +3 -3
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/3_log/test_062_missing_join.py +3 -3
- sql_error_categorizer-0.3.0/tests/3_log/test_067_wildcards_without_like.py +60 -0
- sql_error_categorizer-0.3.0/tests/3_log/test_068_069_wrong_invalid_wildcard.py +85 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/3_log/test_070_extraneous_column_in_select.py +2 -2
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/3_log/test_071_missing_column_from_select.py +2 -2
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/3_log/test_072_missing_distinct_from_select.py +2 -2
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/3_log/test_073_missing_as_from_select.py +2 -2
- sql_error_categorizer-0.2.4/tests/4_com/test_083_unnecessary_distinct_in_select.py → sql_error_categorizer-0.3.0/tests/4_com/test_096_unnecessary_distinct_in_select.py +15 -6
- sql_error_categorizer-0.3.0/tests/4_com/test_102_like_without_wildcards.py +34 -0
- sql_error_categorizer-0.2.4/tests/4_com/test_092_unnecessary_distinct_in_aggregate_function.py → sql_error_categorizer-0.3.0/tests/4_com/test_106_unnecessary_distinct_in_aggregate_function.py +4 -4
- sql_error_categorizer-0.2.4/tests/4_com/test_095_group_by_with_singleton_groups.py → sql_error_categorizer-0.3.0/tests/4_com/test_109_group_by_with_singleton_groups.py +3 -3
- sql_error_categorizer-0.2.4/tests/4_com/test_097_group_by_can_be_replaced_by_distinct.py → sql_error_categorizer-0.3.0/tests/4_com/test_111_group_by_can_be_replaced_by_distinct.py +3 -3
- sql_error_categorizer-0.3.0/tests/4_com/test_114_order_by_in_subquery.py +42 -0
- sql_error_categorizer-0.2.4/tests/1_syn/test_016_extraneous_omitted_grouping_column.py +0 -81
- sql_error_categorizer-0.2.4/tests/1_syn/test_017_having_without_group_by.py +0 -80
- sql_error_categorizer-0.2.4/tests/1_syn/test_019_using_where_twice.py +0 -60
- sql_error_categorizer-0.2.4/tests/1_syn/test_020_missing_from.py +0 -76
- sql_error_categorizer-0.2.4/tests/1_syn/test_021_comparison_with_null.py +0 -43
- sql_error_categorizer-0.2.4/tests/1_syn/test_022_038_additional_omitted_semicolons.py +0 -56
- sql_error_categorizer-0.2.4/tests/1_syn/test_024_duplicate_clause.py +0 -71
- sql_error_categorizer-0.2.4/tests/1_syn/test_026_too_many_columns_in_subquery.py +0 -70
- sql_error_categorizer-0.2.4/tests/1_syn/test_030_keywords_order.py +0 -37
- sql_error_categorizer-0.2.4/tests/1_syn/test_034_curly_square_or_unmatched_brackets.py +0 -59
- sql_error_categorizer-0.2.4/tests/1_syn/test_037_nonstandard_operators.py +0 -29
- sql_error_categorizer-0.2.4/tests/2_sem/test_041_distinct_sum_avg.py +0 -77
- sql_error_categorizer-0.2.4/tests/2_sem/test_043_wildcards_without_like.py +0 -104
- sql_error_categorizer-0.2.4/tests/2_sem/test_044_incorrect_wildcards.py +0 -143
- sql_error_categorizer-0.2.4/tests/4_com/test_088_like_no_wildcards.py +0 -32
- sql_error_categorizer-0.2.4/tests/4_com/test_100_order_by_in_subquery.py +0 -55
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/.readthedocs.yaml +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/LICENSE +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/Makefile +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/README.md +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/datasets/catalogs/constraints.json +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/datasets/catalogs/miedema.json +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/datasets/sql/constraints.sql +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/datasets/sql/miedema.sql +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/docs/Makefile +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/docs/conf.py +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/docs/index.rst +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/docs/make.bat +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/docs/requirements.txt +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/src/sql_error_categorizer/__init__.py +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/src/sql_error_categorizer/detectors/__init__.py +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/src/sql_error_categorizer/detectors/base.py +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/test_detector.py +0 -0
- {sql_error_categorizer-0.2.4 → sql_error_categorizer-0.3.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql_error_categorizer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: This project analyses SQL statements and labels possible errors or complications.
|
|
5
5
|
Project-URL: Repository, https://github.com/DavidePonzini/sql_error_categorizer
|
|
6
6
|
Project-URL: Documentation, https://sql-error-categorizer.readthedocs.io/en/latest/index.html
|
|
@@ -14,7 +14,7 @@ Requires-Python: >=3.11
|
|
|
14
14
|
Requires-Dist: psycopg2
|
|
15
15
|
Requires-Dist: python-dateutil
|
|
16
16
|
Requires-Dist: pyyaml
|
|
17
|
-
Requires-Dist: sql-error-taxonomy
|
|
17
|
+
Requires-Dist: sql-error-taxonomy>=1.1.2
|
|
18
18
|
Requires-Dist: sqlglot
|
|
19
19
|
Requires-Dist: sqlparse
|
|
20
20
|
Requires-Dist: sqlscope>=1.0.15
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sql_error_categorizer"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Davide Ponzini", email="davide.ponzini95@gmail.com" },
|
|
10
10
|
]
|
|
@@ -22,7 +22,7 @@ dependencies = [
|
|
|
22
22
|
"sqlparse",
|
|
23
23
|
"sqlglot",
|
|
24
24
|
"sqlscope>=1.0.15",
|
|
25
|
-
"sql_error_taxonomy",
|
|
25
|
+
"sql_error_taxonomy>=1.1.2",
|
|
26
26
|
"z3-solver",
|
|
27
27
|
"python-dateutil",
|
|
28
28
|
]
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
'''Detector for complications in SQL queries.'''
|
|
2
2
|
|
|
3
|
-
import difflib
|
|
4
|
-
import re
|
|
5
|
-
import sqlparse
|
|
6
|
-
import sqlparse.keywords
|
|
7
3
|
from typing import Callable
|
|
8
4
|
from sqlglot import exp
|
|
9
5
|
from sql_error_taxonomy import SqlErrors
|
|
10
|
-
from sqlscope.catalog import ConstraintType, ConstraintColumn
|
|
11
|
-
from sqlscope import Query
|
|
6
|
+
from sqlscope.catalog import ConstraintType, ConstraintColumn
|
|
7
|
+
from sqlscope import Query
|
|
12
8
|
from sqlscope import util
|
|
13
9
|
|
|
14
10
|
from .base import BaseDetector, DetectedError
|
|
@@ -36,29 +32,31 @@ class ComplicationDetector(BaseDetector):
|
|
|
36
32
|
results: list[DetectedError] = super().run()
|
|
37
33
|
|
|
38
34
|
checks = [
|
|
39
|
-
self.
|
|
40
|
-
self.
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
47
|
-
self.
|
|
48
|
-
self.
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
51
|
-
self.
|
|
52
|
-
self.
|
|
53
|
-
self.
|
|
54
|
-
self.
|
|
55
|
-
self.
|
|
56
|
-
self.
|
|
57
|
-
self.
|
|
58
|
-
self.
|
|
59
|
-
self.
|
|
60
|
-
self.
|
|
61
|
-
self.
|
|
35
|
+
self.detect_95_unnecessary_complication,
|
|
36
|
+
self.detect_96_unnecessary_distinct_in_select_clause,
|
|
37
|
+
self.detect_97_unnecessary_table_reference,
|
|
38
|
+
self.detect_98_unused_correlation_name,
|
|
39
|
+
self.detect_99_tables_have_same_data,
|
|
40
|
+
self.detect_100_correlation_name_identical_to_table_name,
|
|
41
|
+
self.detect_101_unnecessary_general_comparison_operator,
|
|
42
|
+
self.detect_102_like_without_wildcards,
|
|
43
|
+
self.detect_103_unnecessarily_complicated_select_in_exists_subquery,
|
|
44
|
+
self.detect_104_in_exists_can_be_replaced_by_comparison,
|
|
45
|
+
self.detect_105_unnecessary_aggregate_function,
|
|
46
|
+
self.detect_106_unnecessary_distinct_in_aggregate_function,
|
|
47
|
+
self.detect_107_unnecessary_argument_of_count,
|
|
48
|
+
self.detect_108_unnecessary_group_by_in_exists_subquery,
|
|
49
|
+
self.detect_109_group_by_with_singleton_groups,
|
|
50
|
+
self.detect_110_group_by_with_only_a_single_group,
|
|
51
|
+
self.detect_111_group_by_can_be_replaced_by_distinct,
|
|
52
|
+
self.detect_112_union_can_be_replaced_by_or,
|
|
53
|
+
self.detect_113_unnecessary_column_in_order_by_clause,
|
|
54
|
+
self.detect_114_order_by_in_subquery,
|
|
55
|
+
self.detect_115_inefficient_having,
|
|
56
|
+
self.detect_116_inefficient_union,
|
|
57
|
+
self.detect_117_condition_in_the_subquery_can_be_moved_up,
|
|
58
|
+
self.detect_118_outer_join_can_be_replaced_by_inner_join,
|
|
59
|
+
self.detect_119_unused_cte,
|
|
62
60
|
]
|
|
63
61
|
|
|
64
62
|
for chk in checks:
|
|
@@ -66,7 +64,11 @@ class ComplicationDetector(BaseDetector):
|
|
|
66
64
|
|
|
67
65
|
return results
|
|
68
66
|
|
|
69
|
-
def
|
|
67
|
+
def detect_95_unnecessary_complication(self) -> list[DetectedError]:
|
|
68
|
+
'''NOTE: this is an umbrella term, so it can't be directly detected.'''
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
def detect_96_unnecessary_distinct_in_select_clause(self) -> list[DetectedError]:
|
|
70
72
|
'''
|
|
71
73
|
Flags a SELECT DISTINCT clause that is unnecessary because the selected
|
|
72
74
|
columns are already unique due to existing constraints.
|
|
@@ -81,12 +83,12 @@ class ComplicationDetector(BaseDetector):
|
|
|
81
83
|
constraints = [c for c in select.output.unique_constraints if c.constraint_type != ConstraintType.DISTINCT]
|
|
82
84
|
|
|
83
85
|
if len(constraints) > 0:
|
|
84
|
-
result.append(DetectedError(SqlErrors.
|
|
86
|
+
result.append(DetectedError(SqlErrors.UNNECESSARY_DISTINCT_IN_SELECT_CLAUSE, (select.sql,)))
|
|
85
87
|
|
|
86
88
|
return result
|
|
87
89
|
|
|
88
90
|
# TODO: refactor
|
|
89
|
-
def
|
|
91
|
+
def detect_97_unnecessary_table_reference(self) -> list[DetectedError]:
|
|
90
92
|
'''
|
|
91
93
|
Flags a query that joins to a table not present in the correct solution.
|
|
92
94
|
'''
|
|
@@ -110,25 +112,29 @@ class ComplicationDetector(BaseDetector):
|
|
|
110
112
|
# Find the original table name (with alias if it was used) to report back
|
|
111
113
|
original_table_name = next((t for t in original_q_tables if t.lower().startswith(table_name_lower)), table_name_lower)
|
|
112
114
|
results.append((
|
|
113
|
-
SqlErrors.
|
|
115
|
+
SqlErrors.UNNECESSARY_TABLE_REFERENCE,
|
|
114
116
|
f"Unnecessary JOIN: The table '{original_table_name}' is not needed to answer the query."
|
|
115
117
|
))
|
|
116
118
|
|
|
117
119
|
return results
|
|
118
120
|
|
|
119
121
|
# TODO: implement
|
|
120
|
-
def
|
|
122
|
+
def detect_98_unused_correlation_name(self) -> list[DetectedError]:
|
|
121
123
|
return []
|
|
122
124
|
|
|
123
125
|
# TODO: implement
|
|
124
|
-
def
|
|
126
|
+
def detect_99_tables_have_same_data(self) -> list[DetectedError]:
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
# TODO: implement
|
|
130
|
+
def detect_100_correlation_name_identical_to_table_name(self) -> list[DetectedError]:
|
|
125
131
|
return []
|
|
126
132
|
|
|
127
133
|
# TODO: implement
|
|
128
|
-
def
|
|
134
|
+
def detect_101_unnecessary_general_comparison_operator(self) -> list[DetectedError]:
|
|
129
135
|
return []
|
|
130
136
|
|
|
131
|
-
def
|
|
137
|
+
def detect_102_like_without_wildcards(self) -> list[DetectedError]:
|
|
132
138
|
'''
|
|
133
139
|
Flags queries where the LIKE operator is used without wildcards ('%' or '_').
|
|
134
140
|
This indicates a potential misunderstanding, where the '=' operator should
|
|
@@ -157,23 +163,23 @@ class ComplicationDetector(BaseDetector):
|
|
|
157
163
|
if '%' not in pattern_value and '_' not in pattern_value:
|
|
158
164
|
full_expression = str(like)
|
|
159
165
|
|
|
160
|
-
results.append(DetectedError(SqlErrors.
|
|
166
|
+
results.append(DetectedError(SqlErrors.LIKE_WITHOUT_WILDCARDS, (full_expression,)))
|
|
161
167
|
|
|
162
168
|
return results
|
|
163
169
|
|
|
164
170
|
# TODO: implement
|
|
165
|
-
def
|
|
171
|
+
def detect_103_unnecessarily_complicated_select_in_exists_subquery(self) -> list[DetectedError]:
|
|
166
172
|
return []
|
|
167
173
|
|
|
168
174
|
# TODO: implement
|
|
169
|
-
def
|
|
175
|
+
def detect_104_in_exists_can_be_replaced_by_comparison(self) -> list[DetectedError]:
|
|
170
176
|
return []
|
|
171
177
|
|
|
172
178
|
# TODO: implement
|
|
173
|
-
def
|
|
179
|
+
def detect_105_unnecessary_aggregate_function(self) -> list[DetectedError]:
|
|
174
180
|
return []
|
|
175
181
|
|
|
176
|
-
def
|
|
182
|
+
def detect_106_unnecessary_distinct_in_aggregate_function(self) -> list[DetectedError]:
|
|
177
183
|
'''MIN and MAX never require DISTINCT. For other aggregate functions, DISTINCT is unnecessary if the argument is unique.'''
|
|
178
184
|
|
|
179
185
|
results: list[DetectedError] = []
|
|
@@ -189,7 +195,7 @@ class ComplicationDetector(BaseDetector):
|
|
|
189
195
|
continue
|
|
190
196
|
|
|
191
197
|
if isinstance(agg_func, (exp.Min, exp.Max)):
|
|
192
|
-
results.append(DetectedError(SqlErrors.
|
|
198
|
+
results.append(DetectedError(SqlErrors.UNNECESSARY_DISTINCT_IN_AGGREGATE_FUNCTION, (str(agg_func),)))
|
|
193
199
|
continue
|
|
194
200
|
|
|
195
201
|
arg_expr = agg_func.this.expressions # `.this` is the DISTINCT, `.expressions` are the arguments
|
|
@@ -199,7 +205,7 @@ class ComplicationDetector(BaseDetector):
|
|
|
199
205
|
for expr in arg_expr:
|
|
200
206
|
# Check if the argument is a constant literal
|
|
201
207
|
if isinstance(expr, exp.Literal):
|
|
202
|
-
results.append(DetectedError(SqlErrors.
|
|
208
|
+
results.append(DetectedError(SqlErrors.UNNECESSARY_DISTINCT_IN_AGGREGATE_FUNCTION, (str(agg_func),)))
|
|
203
209
|
continue
|
|
204
210
|
|
|
205
211
|
# Check if the argument is a column
|
|
@@ -210,18 +216,18 @@ class ComplicationDetector(BaseDetector):
|
|
|
210
216
|
unique_constraints = [c for c in select.all_constraints if c.constraint_type == ConstraintType.UNIQUE]
|
|
211
217
|
for constraint in unique_constraints:
|
|
212
218
|
if { ConstraintColumn(column_name, table_idx=select._get_table_idx_for_column(expr)) } == constraint.columns:
|
|
213
|
-
results.append(DetectedError(SqlErrors.
|
|
219
|
+
results.append(DetectedError(SqlErrors.UNNECESSARY_DISTINCT_IN_AGGREGATE_FUNCTION, (str(agg_func),)))
|
|
214
220
|
break
|
|
215
221
|
return results
|
|
216
222
|
|
|
217
|
-
def
|
|
223
|
+
def detect_107_unnecessary_argument_of_count(self) -> list[DetectedError]:
|
|
218
224
|
return []
|
|
219
225
|
|
|
220
226
|
# TODO: implement
|
|
221
|
-
def
|
|
227
|
+
def detect_108_unnecessary_group_by_in_exists_subquery(self) -> list[DetectedError]:
|
|
222
228
|
return []
|
|
223
229
|
|
|
224
|
-
def
|
|
230
|
+
def detect_109_group_by_with_singleton_groups(self) -> list[DetectedError]:
|
|
225
231
|
'''
|
|
226
232
|
Flags GROUP BY clauses on singleton groups due to the presence
|
|
227
233
|
of UNIQUE constraints on the grouped columns.
|
|
@@ -241,16 +247,16 @@ class ComplicationDetector(BaseDetector):
|
|
|
241
247
|
|
|
242
248
|
for constraint in constraints:
|
|
243
249
|
if constraint.columns.issubset(group_by_constraint.columns):
|
|
244
|
-
results.append(DetectedError(SqlErrors.
|
|
250
|
+
results.append(DetectedError(SqlErrors.GROUP_BY_WITH_SINGLETON_GROUPS, (group_by_constraint, constraint)))
|
|
245
251
|
break
|
|
246
252
|
|
|
247
253
|
return results
|
|
248
254
|
|
|
249
255
|
# TODO: implement
|
|
250
|
-
def
|
|
256
|
+
def detect_110_group_by_with_only_a_single_group(self) -> list[DetectedError]:
|
|
251
257
|
return []
|
|
252
258
|
|
|
253
|
-
def
|
|
259
|
+
def detect_111_group_by_can_be_replaced_by_distinct(self) -> list[DetectedError]:
|
|
254
260
|
'''
|
|
255
261
|
Flags GROUP BY clauses that can be replaced by SELECT DISTINCT.
|
|
256
262
|
This occurs when all selected columns are included in the GROUP BY clause
|
|
@@ -290,18 +296,18 @@ class ComplicationDetector(BaseDetector):
|
|
|
290
296
|
group_by_col_names = {(util.ast.column.get_real_name(col), select._get_table_idx_for_column(col)) for col in group_by_columns}
|
|
291
297
|
|
|
292
298
|
if select_col_names == group_by_col_names:
|
|
293
|
-
results.append(DetectedError(SqlErrors.
|
|
299
|
+
results.append(DetectedError(SqlErrors.GROUP_BY_CAN_BE_REPLACED_WITH_DISTINCT, (select_col_names,)))
|
|
294
300
|
|
|
295
301
|
return results
|
|
296
302
|
|
|
297
303
|
|
|
298
304
|
|
|
299
305
|
# TODO: implement
|
|
300
|
-
def
|
|
306
|
+
def detect_112_union_can_be_replaced_by_or(self) -> list[DetectedError]:
|
|
301
307
|
return []
|
|
302
308
|
|
|
303
309
|
# TODO: refactor
|
|
304
|
-
def
|
|
310
|
+
def detect_113_unnecessary_column_in_order_by_clause(self) -> list[DetectedError]:
|
|
305
311
|
'''
|
|
306
312
|
Flags when the ORDER BY clause contains unnecessary columns in addition
|
|
307
313
|
to the required ones.
|
|
@@ -330,7 +336,7 @@ class ComplicationDetector(BaseDetector):
|
|
|
330
336
|
return results
|
|
331
337
|
|
|
332
338
|
# TODO: implement
|
|
333
|
-
def
|
|
339
|
+
def detect_114_order_by_in_subquery(self) -> list[DetectedError]:
|
|
334
340
|
'''
|
|
335
341
|
Flags when a subquery contains an ORDER BY clause.
|
|
336
342
|
Subqueries both ORDER BY and LIMIT are considered valid.
|
|
@@ -348,31 +354,30 @@ class ComplicationDetector(BaseDetector):
|
|
|
348
354
|
|
|
349
355
|
checked_subqueries.add(subquery.sql)
|
|
350
356
|
if subquery.order_by and not subquery.limit:
|
|
351
|
-
results.append(DetectedError(SqlErrors.
|
|
357
|
+
results.append(DetectedError(SqlErrors.ORDER_BY_IN_SUBQUERY, (subquery.sql,)))
|
|
352
358
|
|
|
353
359
|
return results
|
|
354
360
|
|
|
355
361
|
# TODO: implement
|
|
356
|
-
def
|
|
362
|
+
def detect_115_inefficient_having(self) -> list[DetectedError]:
|
|
357
363
|
return []
|
|
358
364
|
|
|
359
365
|
# TODO: implement
|
|
360
|
-
def
|
|
366
|
+
def detect_116_inefficient_union(self) -> list[DetectedError]:
|
|
361
367
|
return []
|
|
362
368
|
|
|
363
369
|
# TODO: implement
|
|
364
|
-
def
|
|
370
|
+
def detect_117_condition_in_the_subquery_can_be_moved_up(self) -> list[DetectedError]:
|
|
365
371
|
return []
|
|
366
372
|
|
|
367
373
|
# TODO: implement
|
|
368
|
-
def
|
|
374
|
+
def detect_118_outer_join_can_be_replaced_by_inner_join(self) -> list[DetectedError]:
|
|
369
375
|
return []
|
|
370
|
-
|
|
376
|
+
|
|
371
377
|
# TODO: implement
|
|
372
|
-
def
|
|
378
|
+
def detect_119_unused_cte(self) -> list[DetectedError]:
|
|
373
379
|
return []
|
|
374
380
|
|
|
375
|
-
|
|
376
381
|
#region Utility methods
|
|
377
382
|
def _get_select_columns(self, ast: dict) -> list:
|
|
378
383
|
'''
|