1
1
package ai
2
2
3
3
import (
4
+ "sort"
5
+ "strings"
6
+
4
7
rulepkg "github.com/actiontech/sqle/sqle/driver/mysql/rule"
5
8
util "github.com/actiontech/sqle/sqle/driver/mysql/rule/ai/util"
6
9
driverV2 "github.com/actiontech/sqle/sqle/driver/v2"
@@ -54,22 +57,24 @@ func RuleSQLE00055(input *rulepkg.RuleHandlerInput) error {
54
57
switch stmt := input .Node .(type ) {
55
58
case * ast.CreateTableStmt :
56
59
// "create table..."
57
- indexes := [][]string {}
58
-
59
- // get index column in column definition
60
+ colIndexes := map [string ][]string {}
61
+ // 获取列定义中的索引列
60
62
for _ , col := range stmt .Cols {
61
- if util .IsColumnHasOption (col , ast .ColumnOptionUniqKey ) || util .IsColumnPrimaryKey (col ) {
62
- indexes = append (indexes , []string {util .GetColumnName (col )})
63
+ if util .IsColumnHasOption (col , ast .ColumnOptionUniqKey ) {
64
+ colIndexes ["UNIQUE[" + col .Name .Name .O + "]" ] = []string {util .GetColumnName (col )}
65
+ }
66
+ if util .IsColumnPrimaryKey (col ) {
67
+ colIndexes ["Primary Key" ] = []string {util .GetColumnName (col )}
63
68
}
64
69
}
65
70
66
- // get index column in table constraint
67
- indexes = append ( indexes , extractIndexesFromConstraints (util .GetTableConstraints (stmt .Constraints , util .GetIndexConstraintTypes ()... )) ... )
68
-
69
- // check the index is not duplicated
70
- for redundant , source := range calculateIndexRedundant ( indexes ) {
71
- rulepkg . AddResult ( input . Res , input . Rule , SQLE00055 , indexes [ source ], indexes [ redundant ] )
72
- }
71
+ // 获取表约束中的索引列
72
+ constraintsIndexes := extractIndexesFromConstraints (util .GetTableConstraints (stmt .Constraints , util .GetIndexConstraintTypes ()... ))
73
+ // 此处要聚合到一个map中,否则无法获取存在于表约束中的冗余索引
74
+ mergeMap := mergeIndexsMaps ( colIndexes , constraintsIndexes )
75
+ // 获取冗余索引
76
+ existsIndexs , redundantIndexs := GroupDuplicatesByValue ( mergeMap )
77
+ buildAuditResult ( input , existsIndexs , redundantIndexs )
73
78
74
79
case * ast.CreateIndexStmt :
75
80
// "create index..."
@@ -80,12 +85,11 @@ func RuleSQLE00055(input *rulepkg.RuleHandlerInput) error {
80
85
}
81
86
82
87
indexes := extractIndexesFromConstraints (util .GetTableConstraints (createTableStmt .Constraints , util .GetIndexConstraintTypes ()... ))
83
- newIndex := extractIndexesFromIndexStmt (stmt .IndexPartSpecifications )
84
-
85
- // check the index is not duplicated
86
- for redundant , source := range calculateNewIndexRedundant (indexes , newIndex ) {
87
- rulepkg .AddResult (input .Res , input .Rule , SQLE00055 , indexes [source ], newIndex [redundant ])
88
- }
88
+ newIndexMap := make (map [string ][]string )
89
+ newIndexMap [stmt .IndexName ] = extractIndexesFromIndexStmt (stmt .IndexPartSpecifications )
90
+ // 获取冗余索引
91
+ existsIndexs , redundantIndexs := GetIntersectionByValue (indexes , newIndexMap )
92
+ buildAuditResult (input , existsIndexs , redundantIndexs )
89
93
90
94
case * ast.AlterTableStmt :
91
95
// "alter table"
@@ -96,25 +100,39 @@ func RuleSQLE00055(input *rulepkg.RuleHandlerInput) error {
96
100
}
97
101
98
102
indexes := extractIndexesFromConstraints (util .GetTableConstraints (createTableStmt .Constraints , util .GetIndexConstraintTypes ()... ))
99
-
100
- newIndex := [][]string {}
103
+ constraints := make ([]* ast.Constraint , 0 )
101
104
for _ , spec := range util .GetAlterTableCommandsByTypes (stmt , ast .AlterTableAddConstraint ) {
102
105
// "alter table... add index..."
103
- indexCols := extractIndexesFromConstraints (util .GetTableConstraints ([]* ast.Constraint {spec .Constraint }, util .GetIndexConstraintTypes ()... ))
104
- newIndex = append (newIndex , indexCols ... )
105
-
106
- }
107
- // check the index is not duplicated
108
- for redundant , source := range calculateNewIndexRedundant (indexes , newIndex ) {
109
- rulepkg .AddResult (input .Res , input .Rule , SQLE00055 , indexes [source ], newIndex [redundant ])
106
+ constraints = append (constraints , spec .Constraint )
110
107
}
108
+
109
+ // // 获取冗余索引
110
+ newIndexs := extractIndexesFromConstraints (util .GetTableConstraints (constraints , util .GetIndexConstraintTypes ()... ))
111
+ existsIndexs , redundantIndexs := GetIntersectionByValue (indexes , newIndexs )
112
+ buildAuditResult (input , existsIndexs , redundantIndexs )
111
113
}
112
114
113
115
return nil
114
116
}
115
117
116
- func extractIndexesFromConstraints (constraints []* ast.Constraint ) [][]string {
117
- indexes := [][]string {}
118
+ func buildAuditResult (input * rulepkg.RuleHandlerInput , existsIndexs , redundantIndexs map [string ][]string ) {
119
+ idxNames := make ([]string , 0 , len (existsIndexs ))
120
+ idxColNames := make ([][]string , 0 , len (existsIndexs ))
121
+ redundantIdxNames := make ([]string , 0 , len (redundantIndexs ))
122
+ for key , idx := range existsIndexs {
123
+ idxNames = append (idxNames , key )
124
+ idxColNames = append (idxColNames , idx )
125
+ }
126
+ for key := range redundantIndexs {
127
+ redundantIdxNames = append (redundantIdxNames , key )
128
+ }
129
+ if len (idxNames ) > 0 && len (idxColNames ) > 0 && len (redundantIdxNames ) > 0 {
130
+ rulepkg .AddResult (input .Res , input .Rule , SQLE00055 , idxColNames , strings .Join (idxNames , "、" ), redundantIdxNames )
131
+ }
132
+ }
133
+
134
+ func extractIndexesFromConstraints (constraints []* ast.Constraint ) map [string ][]string {
135
+ indexes := make (map [string ][]string )
118
136
119
137
// Iterate over all constraints to extract index columns.
120
138
for _ , constraint := range constraints {
@@ -130,14 +148,30 @@ func extractIndexesFromConstraints(constraints []*ast.Constraint) [][]string {
130
148
131
149
// Only append to indexes if indexCols is not empty, avoiding adding empty index definitions.
132
150
if len (indexCols ) > 0 {
133
- indexes = append (indexes , indexCols )
151
+ key := constraint .Name
152
+ if key == "" && constraint .Tp == ast .ConstraintPrimaryKey {
153
+ key = "Primary Key"
154
+ }
155
+ indexes [key ] = indexCols
134
156
}
135
157
}
136
158
137
159
return indexes
138
160
}
139
161
140
- func extractIndexesFromIndexStmt (index []* ast.IndexPartSpecification ) [][]string {
162
+ func mergeIndexsMaps (m1 , m2 map [string ][]string ,
163
+ ) map [string ][]string {
164
+ result := make (map [string ][]string , len (m1 )+ len (m2 ))
165
+ for k , v := range m1 {
166
+ result [k ] = v
167
+ }
168
+ for k , v := range m2 {
169
+ result [k ] = v
170
+ }
171
+ return result
172
+ }
173
+
174
+ func extractIndexesFromIndexStmt (index []* ast.IndexPartSpecification ) []string {
141
175
// Initialize the slice that will hold the column names for the index.
142
176
var indexCols []string
143
177
@@ -150,73 +184,129 @@ func extractIndexesFromIndexStmt(index []*ast.IndexPartSpecification) [][]string
150
184
151
185
// Return the indexes as a slice of a single index if indexCols is not empty.
152
186
if len (indexCols ) > 0 {
153
- return [][] string { indexCols }
187
+ return indexCols
154
188
}
155
- return [][] string {} // Return an empty slice of indexes if no index columns were found.
189
+ return []string {} // Return an empty slice of indexes if no index columns were found.
156
190
}
157
191
158
- // calculateIndexRedundant takes a slice of string slices representing indexes and their columns
159
- // and returns a map where the key is the redundant index and the value is the corresponding
160
- // original index that makes it redundant.
161
- func calculateIndexRedundant (indexes [][]string ) map [int ]int {
162
- redundantIndexes := make (map [int ]int )
163
-
164
- // Compare each index with every other index to check for redundancy.
165
- for i , indexColumns := range indexes {
166
- for j , otherIndexColumns := range indexes {
167
- // Skip comparing the same index.
168
- if i == j {
169
- continue
170
- }
192
+ // GetIntersectionByValue 比较两个映射的值集合,返回两个结果映射:
193
+ //
194
+ // commonInLeft: 左映射中,其值集合(或其左前缀)出现在右映射中的条目
195
+ // commonInRight: 右映射中,其值集合(或其左前缀)出现在左映射中的条目
196
+ //
197
+ // 参数:
198
+ //
199
+ // leftMap - 键到字符串切片的映射,作为左侧数据源
200
+ // rightMap - 键到字符串切片的映射,作为右侧数据源
201
+ //
202
+ // 返回:
203
+ //
204
+ // commonInLeft - 所有 leftMap 中,其值(或左前缀)能在 rightMap 中找到的项
205
+ // commonInRight - 所有 rightMap 中,其值(或左前缀)能在 leftMap 中找到的项
206
+ func GetIntersectionByValue (leftMap , rightMap map [string ][]string ) (commonInLeft map [string ][]string , commonInRight map [string ][]string ) {
207
+
208
+ // 初始化返回容器
209
+ commonInLeft = make (map [string ][]string )
210
+ commonInRight = make (map [string ][]string )
211
+
212
+ // --- 构建 rightFullIndex:右侧完整切片的快速查找索引 ---
213
+ // 示例:rightMap 中有 entry ["a","b","c"],则索引中添加 "a,b,c"
214
+ rightFullIndex := make (map [string ]bool )
215
+ for _ , values := range rightMap {
216
+ fullKey := strings .Join (values , "," )
217
+ rightFullIndex [fullKey ] = true
218
+ }
171
219
172
- // Check if indexColumns are redundant with respect to otherIndexColumns.
173
- if isRedundant (indexColumns , otherIndexColumns ) {
174
- // If index i is redundant and either it's not in the map yet, or it's in the map
175
- // but the current source index j has fewer columns (and thus is a 'stronger' source of redundancy),
176
- // then add/update the map with the index pair (i, j).
177
- if _ , exists := redundantIndexes [i ]; ! exists || len (indexes [redundantIndexes [i ]]) > len (otherIndexColumns ) {
178
- redundantIndexes [i ] = j
179
- }
220
+ // --- 扫描 leftMap,检测左侧切片或其左前缀是否命中 rightFullIndex ---
221
+ for key , values := range leftMap {
222
+ // 从最长前缀到最短前缀依次尝试
223
+ for length := len (values ); length > 0 ; length -- {
224
+ prefixKey := strings .Join (values [:length ], "," )
225
+ if rightFullIndex [prefixKey ] {
226
+ // 一旦命中,记录该 leftMap 条目为 commonInLeft,跳出前缀循环
227
+ commonInLeft [key ] = copySlice (values )
228
+ break
180
229
}
181
230
}
182
231
}
183
232
184
- return redundantIndexes
233
+ // --- 构造 leftPrefixIndex:左侧所有可能前缀的索引 ---
234
+ // 为了对称地支持右侧完整切片的匹配,需要将左侧每个 values 的所有左前缀都注册
235
+ // 例如 values=["a","b","c"],则注册 "a","a,b","a,b,c"
236
+ leftPrefixIndex := make (map [string ]bool )
237
+ for _ , values := range leftMap {
238
+ for i := 1 ; i <= len (values ); i ++ {
239
+ sig := strings .Join (values [:i ], "," )
240
+ leftPrefixIndex [sig ] = true
241
+ }
242
+ }
243
+
244
+ // --- 扫描 rightMap,检测右侧完整切片是否命中 leftPrefixIndex ---
245
+ for key , values := range rightMap {
246
+ fullKey := strings .Join (values , "," )
247
+ if leftPrefixIndex [fullKey ] {
248
+ // 如果右侧完整切片恰好是左侧某个切片的左前缀,则记录为 commonInRight
249
+ commonInRight [key ] = copySlice (values )
250
+ }
251
+ }
252
+
253
+ return
185
254
}
186
255
187
- // calculateNewIndexRedundant takes a slice of string slices representing existing indexes and their columns
188
- // and a slice of string slices representing new indexes' columns. It returns a map where the key is the index
189
- // of the new index in `newIndexes` and the value is the index of the existing index in `indexes` that makes it redundant.
190
- func calculateNewIndexRedundant (indexes [][]string , newIndexes [][]string ) map [int ]int {
191
- redundantIndexes := make (map [int ]int )
192
-
193
- // Iterate over each new index
194
- for newIndexI , newIndexCols := range newIndexes {
195
- // Compare against each existing index
196
- for existingIndexI , existingIndexCols := range indexes {
197
- if isRedundant (newIndexCols , existingIndexCols ) {
198
- // If the new index is redundant with respect to an existing index, add to the map.
199
- redundantIndexes [newIndexI ] = existingIndexI
200
- // Since we only care about the first occurrence of redundancy, we can break here.
256
+ // GroupDuplicatesByValue 检测值集合重复的键,返回分组结果:
257
+ // - originals: 每个唯一值集合(或其最长左前缀)首次出现的键及其值
258
+ // - duplicates: 后续出现且与某已登记左前缀匹配的键及其值
259
+ func GroupDuplicatesByValue (inputMap map [string ][]string ) (originals map [string ][]string , duplicates map [string ][]string ) {
260
+ originals = make (map [string ][]string )
261
+ duplicates = make (map [string ][]string )
262
+
263
+ // prefixIndex:将“值切片左前缀签名”映射到首次出现该前缀的 key
264
+ prefixIndex := make (map [string ]string )
265
+
266
+ // 为保证稳定性,先对所有 key 排序
267
+ keys := make ([]string , 0 , len (inputMap ))
268
+ for k := range inputMap {
269
+ keys = append (keys , k )
270
+ }
271
+ sort .Strings (keys )
272
+
273
+ for _ , key := range keys {
274
+ values := inputMap [key ]
275
+ matched := false
276
+
277
+ // 从最长前缀到最短前缀尝试匹配
278
+ for length := len (values ); length > 0 ; length -- {
279
+ sig := strings .Join (values [:length ], "," )
280
+ if origKey , exists := prefixIndex [sig ]; exists {
281
+ // 找到已有前缀,归为 duplicates
282
+ if ! matched {
283
+ // 确保 originals 中保留最早出现的完整值
284
+ if _ , ok := originals [origKey ]; ! ok {
285
+ originals [origKey ] = copySlice (inputMap [origKey ])
286
+ }
287
+ matched = true
288
+ }
289
+ duplicates [key ] = copySlice (values )
201
290
break
202
291
}
203
292
}
293
+ if ! matched {
294
+ // 全新起点,将自身所有左前缀注册
295
+ for length := len (values ); length > 0 ; length -- {
296
+ sig := strings .Join (values [:length ], "," )
297
+ prefixIndex [sig ] = key
298
+ }
299
+ }
204
300
}
205
301
206
- return redundantIndexes
302
+ return
207
303
}
208
304
209
- // isRedundant checks if the first index is redundant with respect to the second index,
210
- // meaning all columns in the first index are a leftmost prefix of the second.
211
- func isRedundant (index1 , index2 []string ) bool {
212
- for i , col := range index1 {
213
- // If we reach the end of index2 or the columns differ,
214
- // then index1 cannot be a leftmost prefix of index2.
215
- if i >= len (index2 ) || col != index2 [i ] {
216
- return false
217
- }
218
- }
219
- return true
305
+ // copySlice 深拷贝字符串slice
306
+ func copySlice (src []string ) []string {
307
+ dst := make ([]string , len (src ))
308
+ copy (dst , src )
309
+ return dst
220
310
}
221
311
222
312
// ==== Rule code end ====
0 commit comments