activo 0.4.3 → 0.5.0
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.
- package/README.md +203 -1
- package/data/2026-03-04_20-54.json +181 -0
- package/data/2026-03-04_20-56.json +181 -0
- package/data/apex-rulesets/egov.yaml +469 -0
- package/data/apex-rulesets/modernize.yaml +687 -0
- package/data/apex-rulesets/quality.yaml +1677 -0
- package/data/apex-rulesets/rule-schema.yaml +587 -0
- package/data/apex-rulesets/secure.yaml +1688 -0
- package/data/apex-rulesets/spring.yaml +455 -0
- package/data/apex-rulesets/sql-format.yaml +99 -0
- package/data/apex-rulesets/sql-oracle.yaml +281 -0
- package/data/apex-rulesets/sql.yaml +1660 -0
- package/dist/cli/headless.d.ts.map +1 -1
- package/dist/cli/headless.js +32 -10
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/index.js +31 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/agent.d.ts +3 -3
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +255 -17
- package/dist/core/agent.js.map +1 -1
- package/dist/core/commands.d.ts +2 -1
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +61 -9
- package/dist/core/commands.js.map +1 -1
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +41 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/conversation.d.ts +2 -2
- package/dist/core/conversation.d.ts.map +1 -1
- package/dist/core/conversation.js.map +1 -1
- package/dist/core/intentRouter.d.ts +43 -0
- package/dist/core/intentRouter.d.ts.map +1 -0
- package/dist/core/intentRouter.js +804 -0
- package/dist/core/intentRouter.js.map +1 -0
- package/dist/core/llm/anthropic.d.ts +24 -0
- package/dist/core/llm/anthropic.d.ts.map +1 -0
- package/dist/core/llm/anthropic.js +226 -0
- package/dist/core/llm/anthropic.js.map +1 -0
- package/dist/core/llm/ollama.d.ts +5 -14
- package/dist/core/llm/ollama.d.ts.map +1 -1
- package/dist/core/llm/ollama.js +3 -0
- package/dist/core/llm/ollama.js.map +1 -1
- package/dist/core/llm/types.d.ts +22 -0
- package/dist/core/llm/types.d.ts.map +1 -0
- package/dist/core/llm/types.js +2 -0
- package/dist/core/llm/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +6 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +16 -0
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/init.d.ts +12 -0
- package/dist/core/mcp/init.d.ts.map +1 -0
- package/dist/core/mcp/init.js +55 -0
- package/dist/core/mcp/init.js.map +1 -0
- package/dist/core/mcp/logger.d.ts +14 -0
- package/dist/core/mcp/logger.d.ts.map +1 -0
- package/dist/core/mcp/logger.js +50 -0
- package/dist/core/mcp/logger.js.map +1 -0
- package/dist/core/tools/analyzeAll.d.ts.map +1 -1
- package/dist/core/tools/analyzeAll.js +16 -28
- package/dist/core/tools/analyzeAll.js.map +1 -1
- package/dist/core/tools/analyzePatterns.d.ts +3 -0
- package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
- package/dist/core/tools/analyzePatterns.js +293 -0
- package/dist/core/tools/analyzePatterns.js.map +1 -0
- package/dist/core/tools/apexPaths.d.ts +14 -0
- package/dist/core/tools/apexPaths.d.ts.map +1 -0
- package/dist/core/tools/apexPaths.js +54 -0
- package/dist/core/tools/apexPaths.js.map +1 -0
- package/dist/core/tools/apexUtils.d.ts +36 -0
- package/dist/core/tools/apexUtils.d.ts.map +1 -0
- package/dist/core/tools/apexUtils.js +83 -0
- package/dist/core/tools/apexUtils.js.map +1 -0
- package/dist/core/tools/explainIssue.d.ts +3 -0
- package/dist/core/tools/explainIssue.d.ts.map +1 -0
- package/dist/core/tools/explainIssue.js +181 -0
- package/dist/core/tools/explainIssue.js.map +1 -0
- package/dist/core/tools/fixGen.d.ts +3 -0
- package/dist/core/tools/fixGen.d.ts.map +1 -0
- package/dist/core/tools/fixGen.js +338 -0
- package/dist/core/tools/fixGen.js.map +1 -0
- package/dist/core/tools/generateImprovements.d.ts +21 -0
- package/dist/core/tools/generateImprovements.d.ts.map +1 -0
- package/dist/core/tools/generateImprovements.js +602 -0
- package/dist/core/tools/generateImprovements.js.map +1 -0
- package/dist/core/tools/generateReport.d.ts +3 -0
- package/dist/core/tools/generateReport.d.ts.map +1 -0
- package/dist/core/tools/generateReport.js +315 -0
- package/dist/core/tools/generateReport.js.map +1 -0
- package/dist/core/tools/index.d.ts +7 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +62 -23
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/javaAst.d.ts.map +1 -1
- package/dist/core/tools/javaAst.js +191 -0
- package/dist/core/tools/javaAst.js.map +1 -1
- package/dist/core/tools/recommendProfile.d.ts +3 -0
- package/dist/core/tools/recommendProfile.d.ts.map +1 -0
- package/dist/core/tools/recommendProfile.js +334 -0
- package/dist/core/tools/recommendProfile.js.map +1 -0
- package/dist/core/tools/ruleGen.d.ts +3 -0
- package/dist/core/tools/ruleGen.d.ts.map +1 -0
- package/dist/core/tools/ruleGen.js +1103 -0
- package/dist/core/tools/ruleGen.js.map +1 -0
- package/dist/core/tools/standards.d.ts.map +1 -1
- package/dist/core/tools/standards.js +7 -3
- package/dist/core/tools/standards.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +86 -35
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +1 -3
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +146 -5
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/MessageList.d.ts +3 -1
- package/dist/ui/components/MessageList.d.ts.map +1 -1
- package/dist/ui/components/MessageList.js +13 -7
- package/dist/ui/components/MessageList.js.map +1 -1
- package/dist/ui/components/StatusBar.d.ts +1 -1
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +3 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolStatus.d.ts +3 -1
- package/dist/ui/components/ToolStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolStatus.js +19 -4
- package/dist/ui/components/ToolStatus.js.map +1 -1
- package/package.json +7 -1
- package/demo.gif +0 -0
- package/demo.tape +0 -53
- package/screenshot.png +0 -0
- package/src/cli/banner.ts +0 -38
- package/src/cli/headless.ts +0 -63
- package/src/cli/index.ts +0 -57
- package/src/core/agent.ts +0 -237
- package/src/core/commands.ts +0 -118
- package/src/core/config.ts +0 -98
- package/src/core/conversation.ts +0 -235
- package/src/core/llm/ollama.ts +0 -351
- package/src/core/mcp/client.ts +0 -143
- package/src/core/tools/analyzeAll.ts +0 -494
- package/src/core/tools/ast.ts +0 -826
- package/src/core/tools/builtIn.ts +0 -221
- package/src/core/tools/cache.ts +0 -570
- package/src/core/tools/cssAnalysis.ts +0 -324
- package/src/core/tools/dependencyAnalysis.ts +0 -363
- package/src/core/tools/embeddings.ts +0 -746
- package/src/core/tools/frontendAst.ts +0 -802
- package/src/core/tools/htmlAnalysis.ts +0 -466
- package/src/core/tools/index.ts +0 -160
- package/src/core/tools/javaAst.ts +0 -812
- package/src/core/tools/memory.ts +0 -655
- package/src/core/tools/mybatisAnalysis.ts +0 -322
- package/src/core/tools/openapiAnalysis.ts +0 -431
- package/src/core/tools/pythonAnalysis.ts +0 -477
- package/src/core/tools/sqlAnalysis.ts +0 -298
- package/src/core/tools/standards.test.ts +0 -186
- package/src/core/tools/standards.ts +0 -889
- package/src/core/tools/types.ts +0 -38
- package/src/ui/App.tsx +0 -334
- package/src/ui/components/InputBox.tsx +0 -37
- package/src/ui/components/MessageList.tsx +0 -80
- package/src/ui/components/StatusBar.tsx +0 -36
- package/src/ui/components/ToolStatus.tsx +0 -38
- package/tsconfig.json +0 -21
|
@@ -0,0 +1,1660 @@
|
|
|
1
|
+
# SQL Style Guide Ruleset (ANSI SQL Common)
|
|
2
|
+
# DB 벤더 불문 공통 SQL 규칙
|
|
3
|
+
# Oracle 전용 규칙은 sql-oracle.yaml, 포맷 규칙은 sql-format.yaml 참조
|
|
4
|
+
version: "1.0"
|
|
5
|
+
profile: "sql"
|
|
6
|
+
|
|
7
|
+
languages:
|
|
8
|
+
- language: sql
|
|
9
|
+
rules:
|
|
10
|
+
# ==================== 1. SQL 작성 기본 ====================
|
|
11
|
+
|
|
12
|
+
- id: "sql-fmt-004"
|
|
13
|
+
name: "괄호 내 공백"
|
|
14
|
+
severity: "low"
|
|
15
|
+
category: "format"
|
|
16
|
+
description: "괄호 안에 불필요한 공백이 있습니다."
|
|
17
|
+
enabled: false
|
|
18
|
+
pattern:
|
|
19
|
+
type: "regex"
|
|
20
|
+
regex: "\\(\\s+\\w|\\w\\s+\\)"
|
|
21
|
+
custom:
|
|
22
|
+
rule_id: "SQL-FMT-004"
|
|
23
|
+
|
|
24
|
+
- id: "sql-fmt-005"
|
|
25
|
+
name: "연산자 공백 누락"
|
|
26
|
+
severity: "low"
|
|
27
|
+
category: "format"
|
|
28
|
+
description: "연산자 앞뒤에 공백이 필요합니다."
|
|
29
|
+
enabled: true
|
|
30
|
+
pattern:
|
|
31
|
+
type: "regex"
|
|
32
|
+
regex: "\\w[=!<>]+\\w"
|
|
33
|
+
custom:
|
|
34
|
+
rule_id: "SQL-FMT-005"
|
|
35
|
+
|
|
36
|
+
- id: "sql-cmt-001"
|
|
37
|
+
name: "한줄 주석 사용"
|
|
38
|
+
severity: "high"
|
|
39
|
+
category: "comment"
|
|
40
|
+
description: "-- 주석 대신 /* */ 주석을 사용하세요."
|
|
41
|
+
enabled: false
|
|
42
|
+
pattern:
|
|
43
|
+
type: "regex"
|
|
44
|
+
regex: "--(?!\\+)"
|
|
45
|
+
custom:
|
|
46
|
+
rule_id: "SQL-CMT-001"
|
|
47
|
+
|
|
48
|
+
# ==================== 2. SELECT 문 ====================
|
|
49
|
+
|
|
50
|
+
- id: "sql-sel-001"
|
|
51
|
+
name: "SELECT * 사용"
|
|
52
|
+
severity: "critical"
|
|
53
|
+
category: "select"
|
|
54
|
+
description: "SELECT * 대신 필요한 컬럼만 명시하세요."
|
|
55
|
+
enabled: true
|
|
56
|
+
pattern:
|
|
57
|
+
type: "regex"
|
|
58
|
+
regex: "\\bSELECT\\s+\\*\\s+FROM\\b"
|
|
59
|
+
flags: "i"
|
|
60
|
+
custom:
|
|
61
|
+
rule_id: "SQL-SEL-001"
|
|
62
|
+
|
|
63
|
+
- id: "sql-sel-002"
|
|
64
|
+
name: "컬럼 별칭 누락"
|
|
65
|
+
severity: "low"
|
|
66
|
+
category: "select"
|
|
67
|
+
description: "함수 사용 시 별칭을 지정하세요."
|
|
68
|
+
enabled: true
|
|
69
|
+
pattern:
|
|
70
|
+
type: "method-analysis"
|
|
71
|
+
conditions:
|
|
72
|
+
- "function-column-without-alias"
|
|
73
|
+
custom:
|
|
74
|
+
rule_id: "SQL-SEL-002"
|
|
75
|
+
|
|
76
|
+
- id: "sql-sel-003"
|
|
77
|
+
name: "DISTINCT 남용"
|
|
78
|
+
severity: "high"
|
|
79
|
+
category: "select"
|
|
80
|
+
description: "유일성이 보장되는 경우 불필요한 DISTINCT를 제거하세요."
|
|
81
|
+
enabled: true
|
|
82
|
+
pattern:
|
|
83
|
+
type: "regex"
|
|
84
|
+
regex: "\\bSELECT\\s+DISTINCT\\b"
|
|
85
|
+
flags: "i"
|
|
86
|
+
custom:
|
|
87
|
+
rule_id: "SQL-SEL-003"
|
|
88
|
+
note: "코드 리뷰 필요"
|
|
89
|
+
|
|
90
|
+
- id: "sql-sel-005"
|
|
91
|
+
name: "함수 내 콤마 앞 공백"
|
|
92
|
+
severity: "low"
|
|
93
|
+
category: "select"
|
|
94
|
+
description: "함수 내 콤마는 뒤에만 공백을 사용합니다."
|
|
95
|
+
enabled: true
|
|
96
|
+
pattern:
|
|
97
|
+
type: "regex"
|
|
98
|
+
regex: "\\w+\\s*\\([^)]*\\s+,"
|
|
99
|
+
custom:
|
|
100
|
+
rule_id: "SQL-SEL-005"
|
|
101
|
+
|
|
102
|
+
# ==================== 3. FROM 절 ====================
|
|
103
|
+
|
|
104
|
+
- id: "sql-from-001"
|
|
105
|
+
name: "테이블 오너 누락"
|
|
106
|
+
severity: "high"
|
|
107
|
+
category: "from"
|
|
108
|
+
description: "테이블명은 테이블오너명.테이블명 형식으로 기술하세요."
|
|
109
|
+
enabled: false # 현장에서 DataSource 레벨 스키마 설정이 일반적
|
|
110
|
+
pattern:
|
|
111
|
+
type: "regex"
|
|
112
|
+
regex: "\\bFROM\\s+(?![A-Z_]+\\.)[A-Z_]+(?:\\s+[A-Z])?\\b"
|
|
113
|
+
flags: "i"
|
|
114
|
+
custom:
|
|
115
|
+
rule_id: "SQL-FROM-001"
|
|
116
|
+
|
|
117
|
+
- id: "sql-from-003"
|
|
118
|
+
name: "복수 테이블 별칭 누락"
|
|
119
|
+
severity: "high"
|
|
120
|
+
category: "from"
|
|
121
|
+
description: "테이블이 2개 이상인 경우 별칭이 필수입니다."
|
|
122
|
+
enabled: true
|
|
123
|
+
pattern:
|
|
124
|
+
type: "method-analysis"
|
|
125
|
+
conditions:
|
|
126
|
+
- "multiple-tables-without-alias"
|
|
127
|
+
custom:
|
|
128
|
+
rule_id: "SQL-FROM-003"
|
|
129
|
+
|
|
130
|
+
# ==================== 4. WHERE 절 ====================
|
|
131
|
+
|
|
132
|
+
- id: "sql-where-002"
|
|
133
|
+
name: "묵시적 형변환"
|
|
134
|
+
severity: "critical"
|
|
135
|
+
category: "where"
|
|
136
|
+
description: "NUMBER 컬럼에 문자열 비교는 묵시적 형변환을 유발합니다."
|
|
137
|
+
enabled: true
|
|
138
|
+
pattern:
|
|
139
|
+
type: "method-analysis"
|
|
140
|
+
conditions:
|
|
141
|
+
- "implicit-type-conversion"
|
|
142
|
+
custom:
|
|
143
|
+
rule_id: "SQL-WHERE-002"
|
|
144
|
+
|
|
145
|
+
- id: "sql-where-003"
|
|
146
|
+
name: "조인 조건 누락"
|
|
147
|
+
severity: "critical"
|
|
148
|
+
category: "where"
|
|
149
|
+
description: "조인 조건이 누락되면 Cartesian Product가 발생합니다."
|
|
150
|
+
enabled: true
|
|
151
|
+
pattern:
|
|
152
|
+
type: "method-analysis"
|
|
153
|
+
conditions:
|
|
154
|
+
- "missing-join-condition"
|
|
155
|
+
custom:
|
|
156
|
+
rule_id: "SQL-WHERE-003"
|
|
157
|
+
|
|
158
|
+
- id: "sql-where-004"
|
|
159
|
+
name: "바인드 변수 미사용"
|
|
160
|
+
severity: "critical"
|
|
161
|
+
category: "where"
|
|
162
|
+
description: "리터럴 값 대신 바인드 변수를 사용하세요."
|
|
163
|
+
enabled: true
|
|
164
|
+
pattern:
|
|
165
|
+
type: "regex"
|
|
166
|
+
regex: "\\bWHERE[^;]*=\\s*'[^#]"
|
|
167
|
+
flags: "i"
|
|
168
|
+
custom:
|
|
169
|
+
rule_id: "SQL-WHERE-004"
|
|
170
|
+
|
|
171
|
+
- id: "sql-where-005"
|
|
172
|
+
name: "부정형 조건 남용"
|
|
173
|
+
severity: "high"
|
|
174
|
+
category: "where"
|
|
175
|
+
description: "부정형 조건(<>, !=, NOT IN)은 인덱스를 사용할 수 없습니다."
|
|
176
|
+
enabled: false
|
|
177
|
+
pattern:
|
|
178
|
+
type: "regex"
|
|
179
|
+
regex: "\\bWHERE[^;]*((<>|!=)|NOT\\s+IN)\\b"
|
|
180
|
+
flags: "i"
|
|
181
|
+
custom:
|
|
182
|
+
rule_id: "SQL-WHERE-005"
|
|
183
|
+
|
|
184
|
+
- id: "sql-where-006"
|
|
185
|
+
name: "OR 조건 남용"
|
|
186
|
+
severity: "high"
|
|
187
|
+
category: "where"
|
|
188
|
+
description: "OR 조건은 검색 범위를 확대시킵니다."
|
|
189
|
+
enabled: true
|
|
190
|
+
pattern:
|
|
191
|
+
type: "method-analysis"
|
|
192
|
+
conditions:
|
|
193
|
+
- "excessive-or-conditions"
|
|
194
|
+
custom:
|
|
195
|
+
rule_id: "SQL-WHERE-006"
|
|
196
|
+
|
|
197
|
+
- id: "sql-where-008"
|
|
198
|
+
name: "선행 와일드카드"
|
|
199
|
+
severity: "high"
|
|
200
|
+
category: "where"
|
|
201
|
+
description: "LIKE '%문자열' 패턴은 인덱스를 사용할 수 없습니다."
|
|
202
|
+
enabled: true
|
|
203
|
+
pattern:
|
|
204
|
+
type: "regex"
|
|
205
|
+
regex: "\\bLIKE\\s+['\"]%"
|
|
206
|
+
flags: "i"
|
|
207
|
+
custom:
|
|
208
|
+
rule_id: "SQL-WHERE-008"
|
|
209
|
+
|
|
210
|
+
# ==================== 5. ORDER BY / GROUP BY (4개) ====================
|
|
211
|
+
|
|
212
|
+
- id: "sql-order-001"
|
|
213
|
+
name: "컬럼 순서값 사용"
|
|
214
|
+
severity: "high"
|
|
215
|
+
category: "orderby"
|
|
216
|
+
description: "ORDER BY에서 컬럼 순서값 대신 컬럼명을 사용하세요."
|
|
217
|
+
enabled: true
|
|
218
|
+
pattern:
|
|
219
|
+
type: "regex"
|
|
220
|
+
regex: "\\bORDER\\s+BY\\s+\\d+"
|
|
221
|
+
flags: "i"
|
|
222
|
+
custom:
|
|
223
|
+
rule_id: "SQL-ORDER-001"
|
|
224
|
+
|
|
225
|
+
- id: "sql-order-002"
|
|
226
|
+
name: "정렬 ORDER BY 누락"
|
|
227
|
+
severity: "high"
|
|
228
|
+
category: "orderby"
|
|
229
|
+
description: "정렬이 필요한 경우 ORDER BY를 명시하세요."
|
|
230
|
+
enabled: true
|
|
231
|
+
pattern:
|
|
232
|
+
type: "method-analysis"
|
|
233
|
+
conditions:
|
|
234
|
+
- "sorting-required-without-orderby"
|
|
235
|
+
custom:
|
|
236
|
+
rule_id: "SQL-ORDER-002"
|
|
237
|
+
|
|
238
|
+
- id: "sql-page-002"
|
|
239
|
+
name: "페이징 정렬 누락"
|
|
240
|
+
severity: "critical"
|
|
241
|
+
category: "paging"
|
|
242
|
+
description: "페이징 쿼리에서 ORDER BY가 누락되었습니다."
|
|
243
|
+
enabled: true
|
|
244
|
+
pattern:
|
|
245
|
+
type: "method-analysis"
|
|
246
|
+
conditions:
|
|
247
|
+
- "paging-without-orderby"
|
|
248
|
+
custom:
|
|
249
|
+
rule_id: "SQL-PAGE-002"
|
|
250
|
+
|
|
251
|
+
# ==================== 6. DML 문 (5개) ====================
|
|
252
|
+
|
|
253
|
+
- id: "sql-dml-001"
|
|
254
|
+
name: "DML 테이블 오너 누락"
|
|
255
|
+
severity: "high"
|
|
256
|
+
category: "dml"
|
|
257
|
+
description: "UPDATE/INSERT/DELETE 시 테이블 오너를 기술하세요."
|
|
258
|
+
enabled: false # 현장에서 DataSource 레벨 스키마 설정이 일반적
|
|
259
|
+
pattern:
|
|
260
|
+
type: "regex"
|
|
261
|
+
regex: "\\b(UPDATE|INSERT\\s+INTO|DELETE\\s+FROM)\\s+(?![A-Z_]+\\.)[A-Z_]+\\b"
|
|
262
|
+
flags: "i"
|
|
263
|
+
custom:
|
|
264
|
+
rule_id: "SQL-DML-001"
|
|
265
|
+
|
|
266
|
+
- id: "sql-dml-002"
|
|
267
|
+
name: "MERGE DELETE 사용"
|
|
268
|
+
severity: "high"
|
|
269
|
+
category: "dml"
|
|
270
|
+
description: "MERGE 문에서 DELETE는 사용을 제한합니다."
|
|
271
|
+
enabled: true
|
|
272
|
+
pattern:
|
|
273
|
+
type: "regex"
|
|
274
|
+
regex: "\\bMERGE[^;]*DELETE\\b"
|
|
275
|
+
flags: "i"
|
|
276
|
+
custom:
|
|
277
|
+
rule_id: "SQL-DML-002"
|
|
278
|
+
|
|
279
|
+
- id: "sql-dml-003"
|
|
280
|
+
name: "INSERT 컬럼 누락"
|
|
281
|
+
severity: "high"
|
|
282
|
+
category: "dml"
|
|
283
|
+
description: "INSERT 문에 컬럼 목록을 명시하세요."
|
|
284
|
+
enabled: true
|
|
285
|
+
pattern:
|
|
286
|
+
type: "regex"
|
|
287
|
+
regex: "\\bINSERT\\s+INTO\\s+[A-Z_\\.]+\\s+VALUES\\b"
|
|
288
|
+
flags: "i"
|
|
289
|
+
custom:
|
|
290
|
+
rule_id: "SQL-DML-003"
|
|
291
|
+
|
|
292
|
+
- id: "sql-dml-004"
|
|
293
|
+
name: "UPDATE WHERE 누락"
|
|
294
|
+
severity: "critical"
|
|
295
|
+
category: "dml"
|
|
296
|
+
description: "UPDATE 문에 WHERE 절이 없으면 모든 행이 수정됩니다."
|
|
297
|
+
enabled: true
|
|
298
|
+
pattern:
|
|
299
|
+
type: "regex"
|
|
300
|
+
regex: "\\bUPDATE\\s+[A-Z_\\.]+\\s+SET[^;]+(?!WHERE)"
|
|
301
|
+
flags: "i"
|
|
302
|
+
custom:
|
|
303
|
+
rule_id: "SQL-DML-004"
|
|
304
|
+
|
|
305
|
+
- id: "sql-dml-005"
|
|
306
|
+
name: "DELETE WHERE 누락"
|
|
307
|
+
severity: "critical"
|
|
308
|
+
category: "dml"
|
|
309
|
+
description: "DELETE 문에 WHERE 절이 없으면 모든 행이 삭제됩니다."
|
|
310
|
+
enabled: true
|
|
311
|
+
pattern:
|
|
312
|
+
type: "regex"
|
|
313
|
+
regex: "\\bDELETE\\s+FROM\\s+[A-Z_\\.]+\\s*(?!WHERE)"
|
|
314
|
+
flags: "i"
|
|
315
|
+
custom:
|
|
316
|
+
rule_id: "SQL-DML-005"
|
|
317
|
+
|
|
318
|
+
# ==================== 7. 서브쿼리 (5개) ====================
|
|
319
|
+
|
|
320
|
+
- id: "sql-sub-001"
|
|
321
|
+
name: "스칼라 서브쿼리 남용"
|
|
322
|
+
severity: "high"
|
|
323
|
+
category: "subquery"
|
|
324
|
+
description: "스칼라 서브쿼리는 코드 테이블 조회에만 사용하세요."
|
|
325
|
+
enabled: true
|
|
326
|
+
pattern:
|
|
327
|
+
type: "method-analysis"
|
|
328
|
+
conditions:
|
|
329
|
+
- "scalar-subquery-not-for-code-table"
|
|
330
|
+
custom:
|
|
331
|
+
rule_id: "SQL-SUB-001"
|
|
332
|
+
|
|
333
|
+
- id: "sql-sub-002"
|
|
334
|
+
name: "스칼라 GROUP BY"
|
|
335
|
+
severity: "high"
|
|
336
|
+
category: "subquery"
|
|
337
|
+
description: "스칼라 서브쿼리 내에서 GROUP BY 사용을 금지합니다."
|
|
338
|
+
enabled: true
|
|
339
|
+
pattern:
|
|
340
|
+
type: "regex"
|
|
341
|
+
regex: "\\bSELECT\\s*\\([^)]*SELECT[^)]*GROUP\\s+BY"
|
|
342
|
+
flags: "i"
|
|
343
|
+
custom:
|
|
344
|
+
rule_id: "SQL-SUB-002"
|
|
345
|
+
|
|
346
|
+
- id: "sql-sub-003"
|
|
347
|
+
name: "스칼라 집계함수"
|
|
348
|
+
severity: "high"
|
|
349
|
+
category: "subquery"
|
|
350
|
+
description: "스칼라 서브쿼리 내에서 집계함수 사용을 금지합니다."
|
|
351
|
+
enabled: true
|
|
352
|
+
pattern:
|
|
353
|
+
type: "regex"
|
|
354
|
+
regex: "\\bSELECT\\s*\\([^)]*SELECT[^)]*(SUM|AVG|COUNT|MAX|MIN)\\s*\\("
|
|
355
|
+
flags: "i"
|
|
356
|
+
custom:
|
|
357
|
+
rule_id: "SQL-SUB-003"
|
|
358
|
+
|
|
359
|
+
- id: "sql-sub-004"
|
|
360
|
+
name: "별칭 중복"
|
|
361
|
+
severity: "high"
|
|
362
|
+
category: "subquery"
|
|
363
|
+
description: "메인/서브쿼리 간 별칭이 중복됩니다."
|
|
364
|
+
enabled: true
|
|
365
|
+
pattern:
|
|
366
|
+
type: "method-analysis"
|
|
367
|
+
conditions:
|
|
368
|
+
- "duplicate-alias-main-sub"
|
|
369
|
+
custom:
|
|
370
|
+
rule_id: "SQL-SUB-004"
|
|
371
|
+
|
|
372
|
+
- id: "sql-sub-005"
|
|
373
|
+
name: "괄호 정렬 불량"
|
|
374
|
+
severity: "low"
|
|
375
|
+
category: "subquery"
|
|
376
|
+
description: "서브쿼리 괄호 정렬이 미준수됩니다."
|
|
377
|
+
enabled: true
|
|
378
|
+
pattern:
|
|
379
|
+
type: "method-analysis"
|
|
380
|
+
conditions:
|
|
381
|
+
- "subquery-bracket-misaligned"
|
|
382
|
+
custom:
|
|
383
|
+
rule_id: "SQL-SUB-005"
|
|
384
|
+
|
|
385
|
+
# ==================== 8. SQL 활용 ====================
|
|
386
|
+
|
|
387
|
+
- id: "sql-set-001"
|
|
388
|
+
name: "동일 테이블 UNION ALL"
|
|
389
|
+
severity: "high"
|
|
390
|
+
category: "set"
|
|
391
|
+
description: "동일 테이블에 UNION ALL 사용은 조건절로 해결하세요."
|
|
392
|
+
enabled: true
|
|
393
|
+
pattern:
|
|
394
|
+
type: "method-analysis"
|
|
395
|
+
conditions:
|
|
396
|
+
- "same-table-union-all"
|
|
397
|
+
custom:
|
|
398
|
+
rule_id: "SQL-SET-001"
|
|
399
|
+
|
|
400
|
+
- id: "sql-set-002"
|
|
401
|
+
name: "MINUS 대신 NOT EXISTS"
|
|
402
|
+
severity: "low"
|
|
403
|
+
category: "set"
|
|
404
|
+
description: "MINUS 대신 NOT EXISTS를 사용하세요."
|
|
405
|
+
enabled: false # MINUS는 가독성이 좋고 실무에서 일반적으로 사용됨
|
|
406
|
+
pattern:
|
|
407
|
+
type: "regex"
|
|
408
|
+
regex: "\\bMINUS\\b"
|
|
409
|
+
flags: "i"
|
|
410
|
+
custom:
|
|
411
|
+
rule_id: "SQL-SET-002"
|
|
412
|
+
|
|
413
|
+
- id: "sql-set-003"
|
|
414
|
+
name: "INTERSECT 대신 EXISTS"
|
|
415
|
+
severity: "low"
|
|
416
|
+
category: "set"
|
|
417
|
+
description: "INTERSECT 대신 EXISTS/JOIN을 사용하세요."
|
|
418
|
+
enabled: false # INTERSECT는 가독성이 좋고 실무에서 일반적으로 사용됨
|
|
419
|
+
pattern:
|
|
420
|
+
type: "regex"
|
|
421
|
+
regex: "\\bINTERSECT\\b"
|
|
422
|
+
flags: "i"
|
|
423
|
+
custom:
|
|
424
|
+
rule_id: "SQL-SET-003"
|
|
425
|
+
|
|
426
|
+
# ==================== 9. 제약 사항 (10개) ====================
|
|
427
|
+
|
|
428
|
+
- id: "sql-obj-001"
|
|
429
|
+
name: "UDF 사용"
|
|
430
|
+
severity: "critical"
|
|
431
|
+
category: "restriction"
|
|
432
|
+
description: "사용자 정의 함수(UDF) 사용은 금지됩니다."
|
|
433
|
+
enabled: true
|
|
434
|
+
pattern:
|
|
435
|
+
type: "method-analysis"
|
|
436
|
+
conditions:
|
|
437
|
+
- "using-user-defined-function"
|
|
438
|
+
custom:
|
|
439
|
+
rule_id: "SQL-OBJ-001"
|
|
440
|
+
|
|
441
|
+
- id: "sql-obj-002"
|
|
442
|
+
name: "Trigger 사용"
|
|
443
|
+
severity: "critical"
|
|
444
|
+
category: "restriction"
|
|
445
|
+
description: "트리거 사용은 원칙적으로 금지됩니다."
|
|
446
|
+
enabled: true
|
|
447
|
+
pattern:
|
|
448
|
+
type: "regex"
|
|
449
|
+
regex: "\\bCREATE\\s+(OR\\s+REPLACE\\s+)?TRIGGER\\b"
|
|
450
|
+
flags: "i"
|
|
451
|
+
custom:
|
|
452
|
+
rule_id: "SQL-OBJ-002"
|
|
453
|
+
|
|
454
|
+
- id: "sql-obj-003"
|
|
455
|
+
name: "Procedure 사용"
|
|
456
|
+
severity: "critical"
|
|
457
|
+
category: "restriction"
|
|
458
|
+
description: "프로시저 사용은 원칙적으로 금지됩니다."
|
|
459
|
+
enabled: true
|
|
460
|
+
pattern:
|
|
461
|
+
type: "regex"
|
|
462
|
+
regex: "\\bCREATE\\s+(OR\\s+REPLACE\\s+)?PROCEDURE\\b"
|
|
463
|
+
flags: "i"
|
|
464
|
+
custom:
|
|
465
|
+
rule_id: "SQL-OBJ-003"
|
|
466
|
+
|
|
467
|
+
- id: "sql-obj-004"
|
|
468
|
+
name: "DB LINK 사용"
|
|
469
|
+
severity: "critical"
|
|
470
|
+
category: "restriction"
|
|
471
|
+
description: "DB LINK 사용은 원칙적으로 금지됩니다."
|
|
472
|
+
enabled: false
|
|
473
|
+
pattern:
|
|
474
|
+
type: "regex"
|
|
475
|
+
regex: "@[A-Z_]+"
|
|
476
|
+
flags: "i"
|
|
477
|
+
custom:
|
|
478
|
+
rule_id: "SQL-OBJ-004"
|
|
479
|
+
|
|
480
|
+
- id: "sql-lock-001"
|
|
481
|
+
name: "FOR UPDATE 남용"
|
|
482
|
+
severity: "critical"
|
|
483
|
+
category: "lock"
|
|
484
|
+
description: "일반 조회에서 FOR UPDATE 사용은 금지됩니다."
|
|
485
|
+
enabled: true
|
|
486
|
+
pattern:
|
|
487
|
+
type: "regex"
|
|
488
|
+
regex: "\\bFOR\\s+UPDATE(?!\\s+NOWAIT)\\b"
|
|
489
|
+
flags: "i"
|
|
490
|
+
custom:
|
|
491
|
+
rule_id: "SQL-LOCK-001"
|
|
492
|
+
|
|
493
|
+
- id: "sql-with-001"
|
|
494
|
+
name: "WITH 절 무분별 사용"
|
|
495
|
+
severity: "low"
|
|
496
|
+
category: "restriction"
|
|
497
|
+
description: "WITH 절은 동일 서브쿼리 반복 시에만 사용하세요."
|
|
498
|
+
enabled: false # WITH(CTE)는 가독성 향상에 유용하여 현장에서 권장됨
|
|
499
|
+
pattern:
|
|
500
|
+
type: "regex"
|
|
501
|
+
regex: "^\\s*WITH\\s+\\w+\\s+AS\\b"
|
|
502
|
+
flags: "im"
|
|
503
|
+
custom:
|
|
504
|
+
rule_id: "SQL-WITH-001"
|
|
505
|
+
|
|
506
|
+
- id: "sql-with-002"
|
|
507
|
+
name: "WITH 미래핑"
|
|
508
|
+
severity: "critical"
|
|
509
|
+
category: "restriction"
|
|
510
|
+
description: "MyBatis에서 WITH 절은 인라인뷰로 감싸야 합니다."
|
|
511
|
+
enabled: true
|
|
512
|
+
pattern:
|
|
513
|
+
type: "method-analysis"
|
|
514
|
+
conditions:
|
|
515
|
+
- "with-clause-not-wrapped"
|
|
516
|
+
custom:
|
|
517
|
+
rule_id: "SQL-WITH-002"
|
|
518
|
+
|
|
519
|
+
- id: "sql-null-001"
|
|
520
|
+
name: "IS NULL 조건"
|
|
521
|
+
severity: "high"
|
|
522
|
+
category: "performance"
|
|
523
|
+
description: "인덱스 컬럼에 IS NULL 조건은 인덱스를 사용할 수 없습니다."
|
|
524
|
+
enabled: false
|
|
525
|
+
pattern:
|
|
526
|
+
type: "regex"
|
|
527
|
+
regex: "\\bWHERE[^;]*\\w+\\.\\w+\\s+IS\\s+NULL\\b"
|
|
528
|
+
flags: "i"
|
|
529
|
+
custom:
|
|
530
|
+
rule_id: "SQL-NULL-001"
|
|
531
|
+
|
|
532
|
+
# ==================== 10. SQL 냄새 (Code Smell) 규칙 ====================
|
|
533
|
+
|
|
534
|
+
- id: "sql-smell-001"
|
|
535
|
+
name: "NOT IN (SELECT ...) 사용"
|
|
536
|
+
severity: "high"
|
|
537
|
+
category: "performance"
|
|
538
|
+
description: "NOT IN (SELECT ...)는 NULL 처리 문제와 성능 저하를 유발합니다. NOT EXISTS를 사용하세요."
|
|
539
|
+
enabled: true
|
|
540
|
+
pattern:
|
|
541
|
+
type: "regex"
|
|
542
|
+
regex: "\\bNOT\\s+IN\\s*\\(\\s*SELECT\\b"
|
|
543
|
+
flags: "i"
|
|
544
|
+
custom:
|
|
545
|
+
rule_id: "SQL-SMELL-001"
|
|
546
|
+
fix: "NOT EXISTS (SELECT 1 FROM ... WHERE ...) 패턴으로 변경하세요"
|
|
547
|
+
|
|
548
|
+
- id: "sql-smell-002"
|
|
549
|
+
name: "UNION (중복 제거) 사용"
|
|
550
|
+
severity: "high"
|
|
551
|
+
category: "performance"
|
|
552
|
+
description: "UNION은 내부적으로 SORT UNIQUE를 수행합니다. 중복이 없다면 UNION ALL을 사용하세요."
|
|
553
|
+
enabled: true
|
|
554
|
+
pattern:
|
|
555
|
+
type: "regex"
|
|
556
|
+
regex: "\\bUNION\\b(?!\\s+ALL)"
|
|
557
|
+
flags: "i"
|
|
558
|
+
custom:
|
|
559
|
+
rule_id: "SQL-SMELL-002"
|
|
560
|
+
fix: "중복 가능성이 없다면 UNION ALL로 변경하세요"
|
|
561
|
+
|
|
562
|
+
- id: "sql-smell-004"
|
|
563
|
+
name: "쿼리 라인 수 과다"
|
|
564
|
+
severity: "high"
|
|
565
|
+
category: "maintainability"
|
|
566
|
+
description: "SQL이 100라인을 초과합니다. 쿼리를 분리하거나 WITH절로 가독성을 개선하세요."
|
|
567
|
+
enabled: true
|
|
568
|
+
pattern:
|
|
569
|
+
type: "method-analysis"
|
|
570
|
+
conditions:
|
|
571
|
+
- "query-length-excessive"
|
|
572
|
+
custom:
|
|
573
|
+
rule_id: "SQL-SMELL-004"
|
|
574
|
+
max_lines: 100
|
|
575
|
+
|
|
576
|
+
- id: "sql-smell-005"
|
|
577
|
+
name: "CASE WHEN 분기 과다"
|
|
578
|
+
severity: "high"
|
|
579
|
+
category: "maintainability"
|
|
580
|
+
description: "CASE WHEN 분기가 10개를 초과합니다. 매핑 테이블이나 별도 로직으로 분리하세요."
|
|
581
|
+
enabled: true
|
|
582
|
+
pattern:
|
|
583
|
+
type: "method-analysis"
|
|
584
|
+
conditions:
|
|
585
|
+
- "case-when-excessive-branches"
|
|
586
|
+
custom:
|
|
587
|
+
rule_id: "SQL-SMELL-005"
|
|
588
|
+
max_branches: 10
|
|
589
|
+
|
|
590
|
+
- id: "sql-smell-006"
|
|
591
|
+
name: "AST JOIN 테이블 수 과다"
|
|
592
|
+
severity: "high"
|
|
593
|
+
category: "maintainability"
|
|
594
|
+
description: "JOIN 테이블이 7개를 초과합니다. 쿼리를 분리하거나 뷰를 활용하세요."
|
|
595
|
+
enabled: true
|
|
596
|
+
pattern:
|
|
597
|
+
type: "ast-sql-table"
|
|
598
|
+
conditions:
|
|
599
|
+
- "table-count-excessive"
|
|
600
|
+
custom:
|
|
601
|
+
rule_id: "SQL-SMELL-006"
|
|
602
|
+
max_tables: 7
|
|
603
|
+
|
|
604
|
+
- id: "sql-smell-007"
|
|
605
|
+
name: "AST LEFT JOIN 과다"
|
|
606
|
+
severity: "high"
|
|
607
|
+
category: "maintainability"
|
|
608
|
+
description: "LEFT JOIN이 5개를 초과합니다. 불필요한 LEFT JOIN을 검토하세요."
|
|
609
|
+
enabled: true
|
|
610
|
+
pattern:
|
|
611
|
+
type: "ast-sql-join"
|
|
612
|
+
conditions:
|
|
613
|
+
- "left-join-excessive"
|
|
614
|
+
custom:
|
|
615
|
+
rule_id: "SQL-SMELL-007"
|
|
616
|
+
max_left_joins: 5
|
|
617
|
+
|
|
618
|
+
- id: "sql-smell-008"
|
|
619
|
+
name: "AST SELECT 컬럼 수 과다"
|
|
620
|
+
severity: "high"
|
|
621
|
+
category: "maintainability"
|
|
622
|
+
description: "SELECT 컬럼이 30개를 초과합니다. 필요한 컬럼만 선택하세요."
|
|
623
|
+
enabled: true
|
|
624
|
+
pattern:
|
|
625
|
+
type: "ast-sql-select"
|
|
626
|
+
conditions:
|
|
627
|
+
- "column-count-excessive"
|
|
628
|
+
custom:
|
|
629
|
+
rule_id: "SQL-SMELL-008"
|
|
630
|
+
max_columns: 30
|
|
631
|
+
|
|
632
|
+
- id: "sql-smell-009"
|
|
633
|
+
name: "AST 스칼라 서브쿼리 과다"
|
|
634
|
+
severity: "high"
|
|
635
|
+
category: "performance"
|
|
636
|
+
description: "SELECT 절에 스칼라 서브쿼리가 2개를 초과합니다. JOIN으로 변환하세요."
|
|
637
|
+
enabled: true
|
|
638
|
+
pattern:
|
|
639
|
+
type: "ast-sql-subquery"
|
|
640
|
+
conditions:
|
|
641
|
+
- "scalar-subquery-excessive"
|
|
642
|
+
custom:
|
|
643
|
+
rule_id: "SQL-SMELL-009"
|
|
644
|
+
max_scalar_subqueries: 2
|
|
645
|
+
|
|
646
|
+
- id: "sql-smell-010"
|
|
647
|
+
name: "NULL = 비교"
|
|
648
|
+
severity: "high"
|
|
649
|
+
category: "correctness"
|
|
650
|
+
description: "col = NULL은 항상 UNKNOWN을 반환합니다. IS NULL / IS NOT NULL을 사용하세요."
|
|
651
|
+
enabled: true
|
|
652
|
+
pattern:
|
|
653
|
+
type: "regex"
|
|
654
|
+
regex: "\\b\\w+\\s*(?:=|!=|<>)\\s*NULL\\b"
|
|
655
|
+
flags: "i"
|
|
656
|
+
custom:
|
|
657
|
+
rule_id: "SQL-SMELL-010"
|
|
658
|
+
fix: "= NULL 대신 IS NULL, != NULL 대신 IS NOT NULL을 사용하세요"
|
|
659
|
+
|
|
660
|
+
- id: "sql-smell-011"
|
|
661
|
+
name: "NATURAL JOIN 사용"
|
|
662
|
+
severity: "high"
|
|
663
|
+
category: "maintainability"
|
|
664
|
+
description: "NATURAL JOIN은 컬럼 추가 시 조인 조건이 암묵적으로 변경되어 위험합니다."
|
|
665
|
+
enabled: true
|
|
666
|
+
pattern:
|
|
667
|
+
type: "regex"
|
|
668
|
+
regex: "\\bNATURAL\\s+JOIN\\b"
|
|
669
|
+
flags: "i"
|
|
670
|
+
custom:
|
|
671
|
+
rule_id: "SQL-SMELL-011"
|
|
672
|
+
fix: "명시적 JOIN ... ON 조건을 사용하세요"
|
|
673
|
+
|
|
674
|
+
- id: "sql-smell-012"
|
|
675
|
+
name: "ON 1=1 가짜 조인"
|
|
676
|
+
severity: "high"
|
|
677
|
+
category: "correctness"
|
|
678
|
+
description: "ON 1=1은 Cartesian Product를 조인처럼 위장한 코드입니다."
|
|
679
|
+
enabled: true
|
|
680
|
+
pattern:
|
|
681
|
+
type: "regex"
|
|
682
|
+
regex: "\\bON\\s+1\\s*=\\s*1\\b"
|
|
683
|
+
flags: "i"
|
|
684
|
+
custom:
|
|
685
|
+
rule_id: "SQL-SMELL-012"
|
|
686
|
+
fix: "실제 조인 조건을 명시하거나 CROSS JOIN을 사용하세요"
|
|
687
|
+
|
|
688
|
+
- id: "sql-smell-013"
|
|
689
|
+
name: "자기 자신 별칭"
|
|
690
|
+
severity: "low"
|
|
691
|
+
category: "maintainability"
|
|
692
|
+
description: "COL AS COL은 무의미한 별칭입니다. 코드 정리가 필요합니다."
|
|
693
|
+
enabled: true
|
|
694
|
+
pattern:
|
|
695
|
+
type: "regex"
|
|
696
|
+
regex: "\\b(\\w+)\\s+AS\\s+\\1\\b"
|
|
697
|
+
flags: "i"
|
|
698
|
+
custom:
|
|
699
|
+
rule_id: "SQL-SMELL-013"
|
|
700
|
+
fix: "불필요한 AS 별칭을 제거하세요"
|
|
701
|
+
|
|
702
|
+
- id: "sql-smell-017"
|
|
703
|
+
name: "COUNT(DISTINCT) 주의"
|
|
704
|
+
severity: "low"
|
|
705
|
+
category: "performance"
|
|
706
|
+
description: "COUNT(DISTINCT)는 대량 데이터에서 정렬/해시 비용이 큽니다. JOIN 문제를 의심하세요."
|
|
707
|
+
enabled: true
|
|
708
|
+
pattern:
|
|
709
|
+
type: "regex"
|
|
710
|
+
regex: "\\bCOUNT\\s*\\(\\s*DISTINCT\\b"
|
|
711
|
+
flags: "i"
|
|
712
|
+
custom:
|
|
713
|
+
rule_id: "SQL-SMELL-017"
|
|
714
|
+
fix: "JOIN 조건 또는 데이터 구조를 재검토하세요"
|
|
715
|
+
|
|
716
|
+
- id: "sql-smell-018"
|
|
717
|
+
name: "WHERE 1=1 항상 참"
|
|
718
|
+
severity: "low"
|
|
719
|
+
category: "maintainability"
|
|
720
|
+
description: "WHERE 1=1은 동적 SQL의 흔적입니다. 코드 정리가 필요합니다."
|
|
721
|
+
enabled: true
|
|
722
|
+
pattern:
|
|
723
|
+
type: "regex"
|
|
724
|
+
regex: "\\bWHERE\\s+1\\s*=\\s*1\\b"
|
|
725
|
+
flags: "i"
|
|
726
|
+
custom:
|
|
727
|
+
rule_id: "SQL-SMELL-018"
|
|
728
|
+
fix: "동적 SQL 생성 로직을 개선하여 WHERE 1=1을 제거하세요"
|
|
729
|
+
|
|
730
|
+
# ==================== 10-2. SQL 냄새 멀티라인 규칙 ====================
|
|
731
|
+
|
|
732
|
+
- id: "sql-smell-020"
|
|
733
|
+
name: "DISTINCT + GROUP BY 중복"
|
|
734
|
+
severity: "high"
|
|
735
|
+
category: "performance"
|
|
736
|
+
description: "SELECT DISTINCT와 GROUP BY를 동시에 사용하면 불필요한 중복 정렬이 발생합니다."
|
|
737
|
+
enabled: true
|
|
738
|
+
pattern:
|
|
739
|
+
type: "regex-multiline"
|
|
740
|
+
regex: "\\bSELECT\\s+DISTINCT\\b[\\s\\S]*?\\bGROUP\\s+BY\\b"
|
|
741
|
+
flags: "i"
|
|
742
|
+
custom:
|
|
743
|
+
rule_id: "SQL-SMELL-020"
|
|
744
|
+
fix: "GROUP BY만으로 유일성이 보장됩니다. DISTINCT를 제거하세요"
|
|
745
|
+
|
|
746
|
+
- id: "sql-smell-021"
|
|
747
|
+
name: "INSERT INTO ... ORDER BY"
|
|
748
|
+
severity: "high"
|
|
749
|
+
category: "performance"
|
|
750
|
+
description: "INSERT INTO 문에 ORDER BY는 무의미한 정렬 비용을 유발합니다."
|
|
751
|
+
enabled: true
|
|
752
|
+
pattern:
|
|
753
|
+
type: "regex-multiline"
|
|
754
|
+
regex: "\\bINSERT\\s+INTO\\b[\\s\\S]*?\\bORDER\\s+BY\\b"
|
|
755
|
+
flags: "i"
|
|
756
|
+
custom:
|
|
757
|
+
rule_id: "SQL-SMELL-021"
|
|
758
|
+
fix: "INSERT 문에서 ORDER BY를 제거하세요"
|
|
759
|
+
|
|
760
|
+
- id: "sql-smell-023"
|
|
761
|
+
name: "DISTINCT + JOIN 조합"
|
|
762
|
+
severity: "high"
|
|
763
|
+
category: "maintainability"
|
|
764
|
+
description: "SELECT DISTINCT와 JOIN을 함께 사용하면 잘못된 JOIN 중복을 숨기는 패턴일 수 있습니다."
|
|
765
|
+
enabled: true
|
|
766
|
+
pattern:
|
|
767
|
+
type: "regex-multiline"
|
|
768
|
+
regex: "\\bSELECT\\s+DISTINCT\\b[\\s\\S]*?\\bJOIN\\b"
|
|
769
|
+
flags: "i"
|
|
770
|
+
custom:
|
|
771
|
+
rule_id: "SQL-SMELL-023"
|
|
772
|
+
fix: "JOIN 조건을 재검토하여 DISTINCT 없이 올바른 결과가 나오는지 확인하세요"
|
|
773
|
+
|
|
774
|
+
- id: "sql-smell-024"
|
|
775
|
+
name: "LEFT JOIN + COUNT(*)"
|
|
776
|
+
severity: "high"
|
|
777
|
+
category: "correctness"
|
|
778
|
+
description: "LEFT JOIN과 COUNT(*)를 함께 사용하면 NULL 행까지 카운트됩니다."
|
|
779
|
+
enabled: true
|
|
780
|
+
pattern:
|
|
781
|
+
type: "regex-multiline"
|
|
782
|
+
regex: "\\bCOUNT\\s*\\(\\s*\\*\\s*\\)[\\s\\S]*?\\bLEFT\\s+(?:OUTER\\s+)?JOIN\\b"
|
|
783
|
+
flags: "i"
|
|
784
|
+
custom:
|
|
785
|
+
rule_id: "SQL-SMELL-024"
|
|
786
|
+
fix: "COUNT(컬럼명)을 사용하여 NULL이 아닌 행만 카운트하세요"
|
|
787
|
+
|
|
788
|
+
- id: "sql-smell-025"
|
|
789
|
+
name: "동일 컬럼 다중 OR"
|
|
790
|
+
severity: "low"
|
|
791
|
+
category: "maintainability"
|
|
792
|
+
description: "같은 컬럼에 대한 OR 비교는 IN 절로 변환하면 가독성이 향상됩니다."
|
|
793
|
+
enabled: true
|
|
794
|
+
pattern:
|
|
795
|
+
type: "regex-multiline"
|
|
796
|
+
regex: "(\\w+\\.\\w+)\\s*=\\s*'[^']*'\\s+OR\\s+\\1\\s*=\\s*'[^']*'"
|
|
797
|
+
flags: "i"
|
|
798
|
+
custom:
|
|
799
|
+
rule_id: "SQL-SMELL-025"
|
|
800
|
+
fix: "COL='A' OR COL='B' 대신 COL IN ('A','B')로 변경하세요"
|
|
801
|
+
|
|
802
|
+
- id: "sql-smell-026"
|
|
803
|
+
name: "중첩 CTE"
|
|
804
|
+
severity: "high"
|
|
805
|
+
category: "maintainability"
|
|
806
|
+
description: "WITH 절 안에 WITH가 중첩되어 있습니다. 순차 CTE로 풀어야 합니다."
|
|
807
|
+
enabled: true
|
|
808
|
+
pattern:
|
|
809
|
+
type: "regex-multiline"
|
|
810
|
+
regex: "\\bWITH\\b[\\s\\S]*?\\bAS\\s*\\([\\s\\S]*?\\bWITH\\b[\\s\\S]*?\\bAS\\s*\\("
|
|
811
|
+
flags: "i"
|
|
812
|
+
custom:
|
|
813
|
+
rule_id: "SQL-SMELL-026"
|
|
814
|
+
fix: "중첩된 WITH를 순차 CTE (WITH a AS (...), b AS (...))로 변환하세요"
|
|
815
|
+
|
|
816
|
+
- id: "sql-smell-027"
|
|
817
|
+
name: "AND/OR 괄호 누락"
|
|
818
|
+
severity: "high"
|
|
819
|
+
category: "correctness"
|
|
820
|
+
description: "WHERE절에서 AND와 OR를 혼합 사용할 때 괄호가 없으면 우선순위 실수가 발생합니다."
|
|
821
|
+
enabled: true
|
|
822
|
+
pattern:
|
|
823
|
+
type: "regex-multiline"
|
|
824
|
+
regex: "\\bWHERE\\b[\\s\\S]*?\\bAND\\b[\\s\\S]*?\\bOR\\b(?![\\s\\S]*?\\))"
|
|
825
|
+
flags: "i"
|
|
826
|
+
custom:
|
|
827
|
+
rule_id: "SQL-SMELL-027"
|
|
828
|
+
fix: "AND와 OR를 혼합할 때는 반드시 괄호로 우선순위를 명시하세요"
|
|
829
|
+
|
|
830
|
+
# ==================== 10-3. SQL 냄새 AST 규칙 ====================
|
|
831
|
+
|
|
832
|
+
- id: "sql-smell-030"
|
|
833
|
+
name: "LEFT JOIN 후 WHERE 우측 필터"
|
|
834
|
+
severity: "high"
|
|
835
|
+
category: "correctness"
|
|
836
|
+
description: "LEFT JOIN한 테이블을 WHERE에서 필터링하면 INNER JOIN과 동일합니다."
|
|
837
|
+
enabled: true
|
|
838
|
+
pattern:
|
|
839
|
+
type: "ast-sql-join"
|
|
840
|
+
conditions:
|
|
841
|
+
- "left-join-where-filter"
|
|
842
|
+
custom:
|
|
843
|
+
rule_id: "SQL-SMELL-030"
|
|
844
|
+
fix: "필터 조건을 ON절로 이동하거나 INNER JOIN으로 변경하세요"
|
|
845
|
+
|
|
846
|
+
- id: "sql-smell-031"
|
|
847
|
+
name: "LEFT JOIN 뒤 INNER JOIN"
|
|
848
|
+
severity: "high"
|
|
849
|
+
category: "correctness"
|
|
850
|
+
description: "LEFT JOIN 체인에서 INNER JOIN이 이전 LEFT JOIN을 무효화합니다."
|
|
851
|
+
enabled: true
|
|
852
|
+
pattern:
|
|
853
|
+
type: "ast-sql-join"
|
|
854
|
+
conditions:
|
|
855
|
+
- "inner-after-left"
|
|
856
|
+
custom:
|
|
857
|
+
rule_id: "SQL-SMELL-031"
|
|
858
|
+
fix: "LEFT JOIN 체인의 일관성을 검토하세요"
|
|
859
|
+
|
|
860
|
+
- id: "sql-smell-032"
|
|
861
|
+
name: "HAVING 비집계 조건"
|
|
862
|
+
severity: "high"
|
|
863
|
+
category: "performance"
|
|
864
|
+
description: "HAVING절에 비집계 조건은 WHERE로 이동해야 성능이 향상됩니다."
|
|
865
|
+
enabled: true
|
|
866
|
+
pattern:
|
|
867
|
+
type: "ast-sql-where"
|
|
868
|
+
conditions:
|
|
869
|
+
- "having-non-aggregate"
|
|
870
|
+
custom:
|
|
871
|
+
rule_id: "SQL-SMELL-032"
|
|
872
|
+
fix: "비집계 조건을 WHERE절로 이동하세요"
|
|
873
|
+
|
|
874
|
+
- id: "sql-smell-033"
|
|
875
|
+
name: "미사용 CTE"
|
|
876
|
+
severity: "high"
|
|
877
|
+
category: "maintainability"
|
|
878
|
+
description: "WITH절에 정의된 CTE가 본문에서 참조되지 않습니다."
|
|
879
|
+
enabled: true
|
|
880
|
+
pattern:
|
|
881
|
+
type: "ast-sql-table"
|
|
882
|
+
conditions:
|
|
883
|
+
- "unused-cte"
|
|
884
|
+
custom:
|
|
885
|
+
rule_id: "SQL-SMELL-033"
|
|
886
|
+
fix: "사용하지 않는 CTE를 제거하세요"
|
|
887
|
+
|
|
888
|
+
- id: "sql-smell-034"
|
|
889
|
+
name: "서브쿼리 내 ORDER BY"
|
|
890
|
+
severity: "high"
|
|
891
|
+
category: "performance"
|
|
892
|
+
description: "페이징 없는 서브쿼리의 ORDER BY는 무의미한 정렬 비용을 유발합니다."
|
|
893
|
+
enabled: true
|
|
894
|
+
pattern:
|
|
895
|
+
type: "ast-sql-orderby"
|
|
896
|
+
conditions:
|
|
897
|
+
- "orderby-in-subquery"
|
|
898
|
+
custom:
|
|
899
|
+
rule_id: "SQL-SMELL-034"
|
|
900
|
+
fix: "서브쿼리에서 불필요한 ORDER BY를 제거하세요"
|
|
901
|
+
|
|
902
|
+
- id: "sql-smell-035"
|
|
903
|
+
name: "인라인뷰 별칭 누락"
|
|
904
|
+
severity: "high"
|
|
905
|
+
category: "correctness"
|
|
906
|
+
description: "인라인뷰(서브쿼리 FROM절)에 별칭이 없으면 참조할 수 없습니다."
|
|
907
|
+
enabled: true
|
|
908
|
+
pattern:
|
|
909
|
+
type: "ast-sql-table"
|
|
910
|
+
conditions:
|
|
911
|
+
- "inline-view-alias-missing"
|
|
912
|
+
custom:
|
|
913
|
+
rule_id: "SQL-SMELL-035"
|
|
914
|
+
fix: "인라인뷰에 별칭을 추가하세요"
|
|
915
|
+
|
|
916
|
+
# ==================== 11. AST 기반 규칙 (sql-ast-*) ====================
|
|
917
|
+
|
|
918
|
+
- id: "sql-ast-from-001"
|
|
919
|
+
name: "AST 테이블 오너 누락"
|
|
920
|
+
severity: "high"
|
|
921
|
+
category: "from"
|
|
922
|
+
description: "AST 기반: 테이블명에 오너(스키마)가 누락되었습니다."
|
|
923
|
+
enabled: false # 현장에서 DataSource 레벨 스키마 설정이 일반적
|
|
924
|
+
pattern:
|
|
925
|
+
type: "ast-sql-table"
|
|
926
|
+
conditions:
|
|
927
|
+
- "owner-missing"
|
|
928
|
+
custom:
|
|
929
|
+
rule_id: "SQL-AST-FROM-001"
|
|
930
|
+
|
|
931
|
+
- id: "sql-ast-from-003"
|
|
932
|
+
name: "AST 복수 테이블 별칭 누락"
|
|
933
|
+
severity: "high"
|
|
934
|
+
category: "from"
|
|
935
|
+
description: "AST 기반: 복수 테이블 사용 시 별칭이 누락되었습니다."
|
|
936
|
+
enabled: true
|
|
937
|
+
pattern:
|
|
938
|
+
type: "ast-sql-table"
|
|
939
|
+
conditions:
|
|
940
|
+
- "alias-missing"
|
|
941
|
+
custom:
|
|
942
|
+
rule_id: "SQL-AST-FROM-003"
|
|
943
|
+
|
|
944
|
+
- id: "sql-ast-sel-001"
|
|
945
|
+
name: "AST SELECT * 사용"
|
|
946
|
+
severity: "critical"
|
|
947
|
+
category: "select"
|
|
948
|
+
description: "AST 기반: SELECT * 대신 필요한 컬럼만 명시하세요."
|
|
949
|
+
enabled: true
|
|
950
|
+
pattern:
|
|
951
|
+
type: "ast-sql-select"
|
|
952
|
+
conditions:
|
|
953
|
+
- "select-star"
|
|
954
|
+
custom:
|
|
955
|
+
rule_id: "SQL-AST-SEL-001"
|
|
956
|
+
|
|
957
|
+
- id: "sql-ast-sel-002"
|
|
958
|
+
name: "AST 함수 별칭 누락"
|
|
959
|
+
severity: "low"
|
|
960
|
+
category: "select"
|
|
961
|
+
description: "AST 기반: 함수 사용 시 별칭을 지정하세요."
|
|
962
|
+
enabled: true
|
|
963
|
+
pattern:
|
|
964
|
+
type: "ast-sql-select"
|
|
965
|
+
conditions:
|
|
966
|
+
- "function-alias-missing"
|
|
967
|
+
custom:
|
|
968
|
+
rule_id: "SQL-AST-SEL-002"
|
|
969
|
+
|
|
970
|
+
- id: "sql-ast-where-003"
|
|
971
|
+
name: "AST 조인 조건 누락"
|
|
972
|
+
severity: "critical"
|
|
973
|
+
category: "where"
|
|
974
|
+
description: "AST 기반: 복수 테이블에 조인 조건이 누락되었습니다."
|
|
975
|
+
enabled: true
|
|
976
|
+
pattern:
|
|
977
|
+
type: "ast-sql-join"
|
|
978
|
+
conditions:
|
|
979
|
+
- "join-condition-missing"
|
|
980
|
+
custom:
|
|
981
|
+
rule_id: "SQL-AST-WHERE-003"
|
|
982
|
+
|
|
983
|
+
- id: "sql-ast-dml-004"
|
|
984
|
+
name: "AST UPDATE/DELETE WHERE 누락"
|
|
985
|
+
severity: "critical"
|
|
986
|
+
category: "dml"
|
|
987
|
+
description: "AST 기반: UPDATE/DELETE 문에 WHERE 절이 누락되었습니다."
|
|
988
|
+
enabled: true
|
|
989
|
+
pattern:
|
|
990
|
+
type: "ast-sql-where"
|
|
991
|
+
conditions:
|
|
992
|
+
- "dml-where-missing"
|
|
993
|
+
custom:
|
|
994
|
+
rule_id: "SQL-AST-DML-004"
|
|
995
|
+
|
|
996
|
+
- id: "sql-ast-order-001"
|
|
997
|
+
name: "AST ORDER BY 숫자"
|
|
998
|
+
severity: "high"
|
|
999
|
+
category: "orderby"
|
|
1000
|
+
description: "AST 기반: ORDER BY에서 숫자 순서값 대신 컬럼명을 사용하세요."
|
|
1001
|
+
enabled: true
|
|
1002
|
+
pattern:
|
|
1003
|
+
type: "ast-sql-orderby"
|
|
1004
|
+
conditions:
|
|
1005
|
+
- "orderby-numeric"
|
|
1006
|
+
custom:
|
|
1007
|
+
rule_id: "SQL-AST-ORDER-001"
|
|
1008
|
+
|
|
1009
|
+
- id: "sql-ast-order-002"
|
|
1010
|
+
name: "AST GROUP BY시 ORDER BY 누락"
|
|
1011
|
+
severity: "high"
|
|
1012
|
+
category: "orderby"
|
|
1013
|
+
description: "AST 기반: GROUP BY가 있지만 ORDER BY가 누락되었습니다."
|
|
1014
|
+
enabled: true
|
|
1015
|
+
pattern:
|
|
1016
|
+
type: "ast-sql-orderby"
|
|
1017
|
+
conditions:
|
|
1018
|
+
- "groupby-without-orderby"
|
|
1019
|
+
custom:
|
|
1020
|
+
rule_id: "SQL-AST-ORDER-002"
|
|
1021
|
+
|
|
1022
|
+
- id: "sql-ast-sub-001"
|
|
1023
|
+
name: "AST 서브쿼리 중첩 깊이 과다"
|
|
1024
|
+
severity: "high"
|
|
1025
|
+
category: "subquery"
|
|
1026
|
+
description: "AST 기반: 서브쿼리 중첩 깊이가 과도합니다."
|
|
1027
|
+
enabled: true
|
|
1028
|
+
pattern:
|
|
1029
|
+
type: "ast-sql-subquery"
|
|
1030
|
+
conditions:
|
|
1031
|
+
- "excessive-depth"
|
|
1032
|
+
custom:
|
|
1033
|
+
rule_id: "SQL-AST-SUB-001"
|
|
1034
|
+
max_depth: 3
|
|
1035
|
+
|
|
1036
|
+
- id: "sql-ast-sub-004"
|
|
1037
|
+
name: "AST 별칭 충돌"
|
|
1038
|
+
severity: "high"
|
|
1039
|
+
category: "subquery"
|
|
1040
|
+
description: "AST 기반: 메인/서브쿼리 간 별칭이 중복됩니다."
|
|
1041
|
+
enabled: true
|
|
1042
|
+
pattern:
|
|
1043
|
+
type: "ast-sql-subquery"
|
|
1044
|
+
conditions:
|
|
1045
|
+
- "alias-conflict"
|
|
1046
|
+
custom:
|
|
1047
|
+
rule_id: "SQL-AST-SUB-004"
|
|
1048
|
+
|
|
1049
|
+
- id: "sql-ast-set-001"
|
|
1050
|
+
name: "AST 동일 테이블 UNION ALL"
|
|
1051
|
+
severity: "high"
|
|
1052
|
+
category: "set"
|
|
1053
|
+
description: "AST 기반: 동일 테이블에 UNION ALL 사용은 조건절로 해결하세요."
|
|
1054
|
+
enabled: true
|
|
1055
|
+
pattern:
|
|
1056
|
+
type: "ast-sql-setop"
|
|
1057
|
+
conditions:
|
|
1058
|
+
- "same-table-union-all"
|
|
1059
|
+
custom:
|
|
1060
|
+
rule_id: "SQL-AST-SET-001"
|
|
1061
|
+
|
|
1062
|
+
- id: "sql-ast-set-002"
|
|
1063
|
+
name: "AST MINUS 대신 NOT EXISTS"
|
|
1064
|
+
severity: "low"
|
|
1065
|
+
category: "set"
|
|
1066
|
+
description: "AST 기반: MINUS 대신 NOT EXISTS를 사용하세요."
|
|
1067
|
+
enabled: false # MINUS는 가독성이 좋고 실무에서 일반적으로 사용됨
|
|
1068
|
+
pattern:
|
|
1069
|
+
type: "ast-sql-setop"
|
|
1070
|
+
conditions:
|
|
1071
|
+
- "minus-usage"
|
|
1072
|
+
custom:
|
|
1073
|
+
rule_id: "SQL-AST-SET-002"
|
|
1074
|
+
|
|
1075
|
+
- id: "sql-ast-set-003"
|
|
1076
|
+
name: "AST INTERSECT 대신 EXISTS"
|
|
1077
|
+
severity: "low"
|
|
1078
|
+
category: "set"
|
|
1079
|
+
description: "AST 기반: INTERSECT 대신 EXISTS/JOIN을 사용하세요."
|
|
1080
|
+
enabled: false # INTERSECT는 가독성이 좋고 실무에서 일반적으로 사용됨
|
|
1081
|
+
pattern:
|
|
1082
|
+
type: "ast-sql-setop"
|
|
1083
|
+
conditions:
|
|
1084
|
+
- "intersect-usage"
|
|
1085
|
+
custom:
|
|
1086
|
+
rule_id: "SQL-AST-SET-003"
|
|
1087
|
+
|
|
1088
|
+
# ==================== SQL 성능 규칙 (Performance) ====================
|
|
1089
|
+
|
|
1090
|
+
- id: "sql-perf-001"
|
|
1091
|
+
name: "WHERE 절 인덱스 컬럼에 TO_CHAR 함수 사용"
|
|
1092
|
+
severity: "high"
|
|
1093
|
+
category: "performance"
|
|
1094
|
+
description: "WHERE/JOIN 조건에서 인덱스 컬럼을 TO_CHAR()로 감싸면 인덱스를 사용할 수 없어 풀스캔이 발생합니다."
|
|
1095
|
+
enabled: true
|
|
1096
|
+
pattern:
|
|
1097
|
+
type: "regex"
|
|
1098
|
+
regex: "\\b(WHERE|AND|OR|ON)\\s+[^<]*TO_CHAR\\s*\\(\\s*[A-Za-z_][A-Za-z0-9_.]*"
|
|
1099
|
+
flags: "i"
|
|
1100
|
+
custom:
|
|
1101
|
+
rule_id: "SQL-PERF-001"
|
|
1102
|
+
fix: "비교 대상을 변환하거나 함수 기반 인덱스를 생성하세요"
|
|
1103
|
+
|
|
1104
|
+
- id: "sql-perf-002"
|
|
1105
|
+
name: "WHERE 절 인덱스 컬럼에 SUBSTR 함수 사용"
|
|
1106
|
+
severity: "high"
|
|
1107
|
+
category: "performance"
|
|
1108
|
+
description: "WHERE 조건에서 인덱스 컬럼을 SUBSTR()로 감싸면 인덱스를 사용할 수 없습니다."
|
|
1109
|
+
enabled: true
|
|
1110
|
+
pattern:
|
|
1111
|
+
type: "regex"
|
|
1112
|
+
regex: "\\b(WHERE|AND|OR)\\s+[^<]*SUBSTR\\s*\\(\\s*[A-Za-z_][A-Za-z0-9_.]*"
|
|
1113
|
+
flags: "i"
|
|
1114
|
+
custom:
|
|
1115
|
+
rule_id: "SQL-PERF-002"
|
|
1116
|
+
fix: "LIKE 'prefix%' 또는 별도 컬럼으로 대체하세요"
|
|
1117
|
+
|
|
1118
|
+
- id: "sql-perf-003"
|
|
1119
|
+
name: "NOT IN (서브쿼리) 사용"
|
|
1120
|
+
severity: "medium"
|
|
1121
|
+
category: "performance"
|
|
1122
|
+
description: "NOT IN (서브쿼리)는 NULL 반환 시 전체 결과가 빈 집합이 되며 성능도 떨어집니다. NOT EXISTS를 사용하세요."
|
|
1123
|
+
enabled: true
|
|
1124
|
+
pattern:
|
|
1125
|
+
type: "regex"
|
|
1126
|
+
regex: "\\bNOT\\s+IN\\s*\\(\\s*SELECT\\b"
|
|
1127
|
+
flags: "i"
|
|
1128
|
+
custom:
|
|
1129
|
+
rule_id: "SQL-PERF-003"
|
|
1130
|
+
fix: "NOT EXISTS (SELECT 1 FROM ... WHERE ...) 로 변경하세요"
|
|
1131
|
+
|
|
1132
|
+
- id: "sql-perf-004"
|
|
1133
|
+
name: "UNION 대신 UNION ALL 고려"
|
|
1134
|
+
severity: "low"
|
|
1135
|
+
category: "performance"
|
|
1136
|
+
description: "UNION은 중복 제거를 위해 정렬합니다. 중복이 없거나 허용 가능하면 UNION ALL이 빠릅니다."
|
|
1137
|
+
enabled: true
|
|
1138
|
+
pattern:
|
|
1139
|
+
type: "regex"
|
|
1140
|
+
regex: "\\bUNION\\s+SELECT\\b"
|
|
1141
|
+
flags: "i"
|
|
1142
|
+
custom:
|
|
1143
|
+
rule_id: "SQL-PERF-004"
|
|
1144
|
+
fix: "중복이 불가능하거나 허용 가능하면 UNION ALL로 변경하세요"
|
|
1145
|
+
|
|
1146
|
+
- id: "sql-perf-005"
|
|
1147
|
+
name: "SELECT * FROM (서브쿼리) 래핑"
|
|
1148
|
+
severity: "low"
|
|
1149
|
+
category: "performance"
|
|
1150
|
+
description: "SELECT * FROM (서브쿼리)는 불필요한 데이터 전달 계층을 추가합니다. 필요한 컬럼만 명시하세요."
|
|
1151
|
+
enabled: true
|
|
1152
|
+
pattern:
|
|
1153
|
+
type: "regex"
|
|
1154
|
+
regex: "\\bSELECT\\s+\\*\\s+FROM\\s*\\("
|
|
1155
|
+
flags: "i"
|
|
1156
|
+
custom:
|
|
1157
|
+
rule_id: "SQL-PERF-005"
|
|
1158
|
+
fix: "필요한 컬럼만 명시적으로 선택하세요"
|
|
1159
|
+
|
|
1160
|
+
- id: "sql-perf-006"
|
|
1161
|
+
name: "DB Link (@dblink) 분산 쿼리 사용"
|
|
1162
|
+
severity: "medium"
|
|
1163
|
+
category: "performance"
|
|
1164
|
+
description: "DB Link를 통한 분산 쿼리는 네트워크 지연과 장애에 취약합니다."
|
|
1165
|
+
enabled: true
|
|
1166
|
+
pattern:
|
|
1167
|
+
type: "regex"
|
|
1168
|
+
regex: "\\b[A-Za-z_][A-Za-z0-9_]*@[A-Za-z_][A-Za-z0-9_]*\\b"
|
|
1169
|
+
flags: "i"
|
|
1170
|
+
custom:
|
|
1171
|
+
rule_id: "SQL-PERF-006"
|
|
1172
|
+
fix: "데이터 복제, API, 또는 ETL로 대체를 검토하세요"
|
|
1173
|
+
|
|
1174
|
+
- id: "sql-perf-007"
|
|
1175
|
+
name: "WHERE 절 인덱스 컬럼에 NVL 함수 사용"
|
|
1176
|
+
severity: "medium"
|
|
1177
|
+
category: "performance"
|
|
1178
|
+
description: "WHERE 조건에서 인덱스 컬럼을 NVL()로 감싸면 인덱스를 사용할 수 없습니다."
|
|
1179
|
+
enabled: true
|
|
1180
|
+
pattern:
|
|
1181
|
+
type: "regex"
|
|
1182
|
+
regex: "\\b(WHERE|AND|OR)\\s+[^<]*NVL\\s*\\(\\s*[A-Za-z_][A-Za-z0-9_.]*"
|
|
1183
|
+
flags: "i"
|
|
1184
|
+
custom:
|
|
1185
|
+
rule_id: "SQL-PERF-007"
|
|
1186
|
+
fix: "(col = value OR col IS NULL) 또는 COALESCE 인덱스를 사용하세요"
|
|
1187
|
+
|
|
1188
|
+
- id: "sql-perf-008"
|
|
1189
|
+
name: "SELECT 절 다중 스칼라 서브쿼리 (동일 테이블)"
|
|
1190
|
+
severity: "high"
|
|
1191
|
+
category: "performance"
|
|
1192
|
+
description: "SELECT 절에 동일 테이블을 참조하는 스칼라 서브쿼리가 여러 개 있으면 행마다 N회 쿼리가 실행됩니다. JOIN으로 변경하세요."
|
|
1193
|
+
enabled: true
|
|
1194
|
+
pattern:
|
|
1195
|
+
type: "regex"
|
|
1196
|
+
regex: "\\bSELECT\\b[^;]*(\\(\\s*SELECT\\b[^)]*\\))[^;]*(\\(\\s*SELECT\\b[^)]*\\))"
|
|
1197
|
+
flags: "is"
|
|
1198
|
+
custom:
|
|
1199
|
+
rule_id: "SQL-PERF-008"
|
|
1200
|
+
fix: "스칼라 서브쿼리를 LEFT JOIN으로 통합하세요"
|
|
1201
|
+
|
|
1202
|
+
- id: "sql-perf-009"
|
|
1203
|
+
name: "WHERE 절 상관 서브쿼리에 MAX/MIN 사용"
|
|
1204
|
+
severity: "medium"
|
|
1205
|
+
category: "performance"
|
|
1206
|
+
description: "WHERE 절에서 상관 서브쿼리로 MAX/MIN을 구하면 외부 행마다 반복 실행됩니다."
|
|
1207
|
+
enabled: true
|
|
1208
|
+
pattern:
|
|
1209
|
+
type: "regex"
|
|
1210
|
+
regex: "\\b(WHERE|AND)\\s+[^<]*=\\s*\\(\\s*SELECT\\s+MAX\\b"
|
|
1211
|
+
flags: "i"
|
|
1212
|
+
custom:
|
|
1213
|
+
rule_id: "SQL-PERF-009"
|
|
1214
|
+
fix: "인라인 뷰 또는 윈도우 함수(ROW_NUMBER, RANK)로 대체하세요"
|
|
1215
|
+
|
|
1216
|
+
- id: "sql-perf-010"
|
|
1217
|
+
name: "ROWNUM 페이징에서 ORDER BY 위치 오류"
|
|
1218
|
+
severity: "medium"
|
|
1219
|
+
category: "performance"
|
|
1220
|
+
description: "ROWNUM은 ORDER BY보다 먼저 할당되므로, ORDER BY가 포함된 서브쿼리를 감싸서 ROWNUM을 적용해야 합니다."
|
|
1221
|
+
enabled: true
|
|
1222
|
+
pattern:
|
|
1223
|
+
type: "regex"
|
|
1224
|
+
regex: "\\bROWNUM\\b[^)]*\\bORDER\\s+BY\\b"
|
|
1225
|
+
flags: "i"
|
|
1226
|
+
custom:
|
|
1227
|
+
rule_id: "SQL-PERF-010"
|
|
1228
|
+
fix: "ORDER BY를 안쪽 서브쿼리에 넣고, ROWNUM은 바깥에서 적용하세요"
|
|
1229
|
+
|
|
1230
|
+
# ==================== SQL 스멜 추가 규칙 ====================
|
|
1231
|
+
|
|
1232
|
+
- id: "sql-smell-040"
|
|
1233
|
+
name: "SELECT 절 스칼라 서브쿼리"
|
|
1234
|
+
severity: "medium"
|
|
1235
|
+
category: "smell"
|
|
1236
|
+
description: "SELECT 절의 스칼라 서브쿼리는 행마다 실행되어 N+1 쿼리 문제를 유발합니다."
|
|
1237
|
+
enabled: true
|
|
1238
|
+
pattern:
|
|
1239
|
+
type: "regex"
|
|
1240
|
+
regex: "\\bSELECT\\b[^(]*\\(\\s*SELECT\\b"
|
|
1241
|
+
flags: "i"
|
|
1242
|
+
custom:
|
|
1243
|
+
rule_id: "SQL-SMELL-040"
|
|
1244
|
+
fix: "LEFT JOIN으로 변경하세요"
|
|
1245
|
+
|
|
1246
|
+
- id: "sql-smell-041"
|
|
1247
|
+
name: "3단계 이상 중첩 서브쿼리"
|
|
1248
|
+
severity: "medium"
|
|
1249
|
+
category: "smell"
|
|
1250
|
+
description: "서브쿼리가 3단계 이상 중첩되면 가독성과 옵티마이저 성능이 저하됩니다."
|
|
1251
|
+
enabled: true
|
|
1252
|
+
pattern:
|
|
1253
|
+
type: "regex"
|
|
1254
|
+
regex: "\\(\\s*SELECT\\b[^)]*\\(\\s*SELECT\\b[^)]*\\(\\s*SELECT\\b"
|
|
1255
|
+
flags: "is"
|
|
1256
|
+
custom:
|
|
1257
|
+
rule_id: "SQL-SMELL-041"
|
|
1258
|
+
fix: "CTE(WITH 절) 또는 인라인 뷰로 분리하세요"
|
|
1259
|
+
|
|
1260
|
+
- id: "sql-smell-042"
|
|
1261
|
+
name: "과다 DECODE 분기 (5개 이상)"
|
|
1262
|
+
severity: "low"
|
|
1263
|
+
category: "smell"
|
|
1264
|
+
description: "DECODE 함수의 분기가 5개 이상이면 CASE WHEN으로 변경하는 것이 가독성이 좋습니다."
|
|
1265
|
+
enabled: true
|
|
1266
|
+
pattern:
|
|
1267
|
+
type: "regex"
|
|
1268
|
+
regex: "\\bDECODE\\s*\\([^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,[^,]+"
|
|
1269
|
+
flags: "i"
|
|
1270
|
+
custom:
|
|
1271
|
+
rule_id: "SQL-SMELL-042"
|
|
1272
|
+
fix: "CASE WHEN으로 변경하세요"
|
|
1273
|
+
|
|
1274
|
+
- language: xml
|
|
1275
|
+
rules:
|
|
1276
|
+
# ==================== MyBatis XML 관련 규칙 ====================
|
|
1277
|
+
|
|
1278
|
+
- id: "sql-mybatis-001"
|
|
1279
|
+
name: "MyBatis ${} SQL Injection 취약"
|
|
1280
|
+
severity: "critical"
|
|
1281
|
+
category: "security"
|
|
1282
|
+
description: "조건절에서 ${}는 SQL Injection에 취약합니다. #{}를 사용하세요."
|
|
1283
|
+
enabled: true
|
|
1284
|
+
pattern:
|
|
1285
|
+
type: "regex"
|
|
1286
|
+
regex: "=\\s*['\"]?\\$\\{[^}]+\\}['\"]?"
|
|
1287
|
+
custom:
|
|
1288
|
+
rule_id: "SQL-MYBATIS-001"
|
|
1289
|
+
fix: "${} 대신 #{}를 사용하세요"
|
|
1290
|
+
|
|
1291
|
+
- id: "sql-mybatis-002"
|
|
1292
|
+
name: "MyBatis SELECT *"
|
|
1293
|
+
severity: "critical"
|
|
1294
|
+
category: "select"
|
|
1295
|
+
description: "MyBatis에서 SELECT *는 사용 금지입니다."
|
|
1296
|
+
enabled: true
|
|
1297
|
+
pattern:
|
|
1298
|
+
type: "regex"
|
|
1299
|
+
regex: "<select[^>]*>\\s*SELECT\\s+\\*"
|
|
1300
|
+
flags: "i"
|
|
1301
|
+
custom:
|
|
1302
|
+
rule_id: "SQL-MYBATIS-002"
|
|
1303
|
+
|
|
1304
|
+
- id: "sql-mybatis-003"
|
|
1305
|
+
name: "MyBatis 한줄 주석"
|
|
1306
|
+
severity: "high"
|
|
1307
|
+
category: "comment"
|
|
1308
|
+
description: "MyBatis SQL에서 -- 주석은 사용 금지입니다. XML 주석(<!-- -->)은 제외합니다."
|
|
1309
|
+
enabled: true
|
|
1310
|
+
pattern:
|
|
1311
|
+
type: "regex"
|
|
1312
|
+
regex: "(?:^|[^!])--(?![>+])"
|
|
1313
|
+
custom:
|
|
1314
|
+
rule_id: "SQL-MYBATIS-003"
|
|
1315
|
+
|
|
1316
|
+
- id: "sql-mybatis-005"
|
|
1317
|
+
name: "MyBatis ORDER BY 숫자"
|
|
1318
|
+
severity: "high"
|
|
1319
|
+
category: "orderby"
|
|
1320
|
+
description: "MyBatis SQL에서 ORDER BY 숫자는 사용 금지입니다."
|
|
1321
|
+
enabled: false # sql-order-001과 동일 regex 중복
|
|
1322
|
+
pattern:
|
|
1323
|
+
type: "regex"
|
|
1324
|
+
regex: "\\bORDER\\s+BY\\s+\\d+"
|
|
1325
|
+
flags: "i"
|
|
1326
|
+
custom:
|
|
1327
|
+
rule_id: "SQL-MYBATIS-005"
|
|
1328
|
+
|
|
1329
|
+
- id: "sql-mybatis-006"
|
|
1330
|
+
name: "MyBatis 테이블 오너 누락"
|
|
1331
|
+
severity: "high"
|
|
1332
|
+
category: "from"
|
|
1333
|
+
description: "MyBatis SQL에서 테이블 오너가 누락되었습니다. (일반적으로 DataSource 레벨에서 스키마를 설정하므로 대부분 불필요)"
|
|
1334
|
+
enabled: false
|
|
1335
|
+
pattern:
|
|
1336
|
+
type: "regex"
|
|
1337
|
+
regex: "\\bFROM\\s+(?![A-Z_]+\\.)[A-Z_]+\\s"
|
|
1338
|
+
flags: "i"
|
|
1339
|
+
custom:
|
|
1340
|
+
rule_id: "SQL-MYBATIS-006"
|
|
1341
|
+
|
|
1342
|
+
- id: "sql-mybatis-008"
|
|
1343
|
+
name: "MyBatis 선행 와일드카드"
|
|
1344
|
+
severity: "high"
|
|
1345
|
+
category: "where"
|
|
1346
|
+
description: "MyBatis SQL에서 LIKE '%...' 패턴은 성능 저하를 유발합니다."
|
|
1347
|
+
enabled: true
|
|
1348
|
+
pattern:
|
|
1349
|
+
type: "regex"
|
|
1350
|
+
regex: "\\bLIKE\\s+['\"]%|\\bLIKE\\s+CONCAT\\s*\\(\\s*['\"]%"
|
|
1351
|
+
flags: "i"
|
|
1352
|
+
custom:
|
|
1353
|
+
rule_id: "SQL-MYBATIS-008"
|
|
1354
|
+
|
|
1355
|
+
- id: "sql-mybatis-009"
|
|
1356
|
+
name: "MyBatis UPDATE WHERE 누락"
|
|
1357
|
+
severity: "critical"
|
|
1358
|
+
category: "dml"
|
|
1359
|
+
description: "MyBatis UPDATE 문에 WHERE 절이 누락되었습니다."
|
|
1360
|
+
enabled: false
|
|
1361
|
+
pattern:
|
|
1362
|
+
type: "method-analysis"
|
|
1363
|
+
conditions:
|
|
1364
|
+
- "mybatis-update-without-where"
|
|
1365
|
+
custom:
|
|
1366
|
+
rule_id: "SQL-MYBATIS-009"
|
|
1367
|
+
|
|
1368
|
+
- id: "sql-mybatis-010"
|
|
1369
|
+
name: "MyBatis DELETE WHERE 누락"
|
|
1370
|
+
severity: "critical"
|
|
1371
|
+
category: "dml"
|
|
1372
|
+
description: "MyBatis DELETE 문에 WHERE 절이 누락되었습니다."
|
|
1373
|
+
enabled: false
|
|
1374
|
+
pattern:
|
|
1375
|
+
type: "method-analysis"
|
|
1376
|
+
conditions:
|
|
1377
|
+
- "mybatis-delete-without-where"
|
|
1378
|
+
custom:
|
|
1379
|
+
rule_id: "SQL-MYBATIS-010"
|
|
1380
|
+
|
|
1381
|
+
- id: "quality-mybatis-if-001"
|
|
1382
|
+
name: "MyBatis 과도한 동적 SQL 조건"
|
|
1383
|
+
severity: "high"
|
|
1384
|
+
category: "complexity"
|
|
1385
|
+
description: "SQL 문에 <if> 조건이 과도하게 많습니다. SQL을 분리하거나 단순화하세요."
|
|
1386
|
+
enabled: true
|
|
1387
|
+
pattern:
|
|
1388
|
+
type: "method-analysis"
|
|
1389
|
+
conditions:
|
|
1390
|
+
- "mybatis-excessive-if"
|
|
1391
|
+
custom:
|
|
1392
|
+
max_if_conditions: 15
|
|
1393
|
+
rule_id: "MYBATIS-IF-001"
|
|
1394
|
+
fix: "SQL을 여러 개로 분리하거나, 공통 조건을 묶어 단순화하세요"
|
|
1395
|
+
|
|
1396
|
+
# ==================== MyBatis 추가 규칙 ====================
|
|
1397
|
+
|
|
1398
|
+
- id: "sql-mybatis-011"
|
|
1399
|
+
name: "MyBatis resultType에 HashMap/Map 사용"
|
|
1400
|
+
severity: "medium"
|
|
1401
|
+
category: "mybatis"
|
|
1402
|
+
description: "resultType에 hashMap/map을 사용하면 타입 안전성이 사라지고, 컬럼명 변경 시 컴파일 오류를 감지할 수 없습니다."
|
|
1403
|
+
enabled: true
|
|
1404
|
+
pattern:
|
|
1405
|
+
type: "regex"
|
|
1406
|
+
regex: "resultType\\s*=\\s*\"(hashMap|hashmap|HashMap|map|java\\.util\\.(Hash)?Map)\""
|
|
1407
|
+
custom:
|
|
1408
|
+
rule_id: "SQL-MYBATIS-011"
|
|
1409
|
+
fix: "전용 VO/DTO 클래스를 만들어 resultType으로 사용하세요"
|
|
1410
|
+
|
|
1411
|
+
- id: "sql-mybatis-012"
|
|
1412
|
+
name: "MyBatis parameterType에 HashMap/Map 사용"
|
|
1413
|
+
severity: "medium"
|
|
1414
|
+
category: "mybatis"
|
|
1415
|
+
description: "parameterType에 hashMap/map을 사용하면 파라미터 키 오타를 컴파일 시 감지할 수 없습니다."
|
|
1416
|
+
enabled: true
|
|
1417
|
+
pattern:
|
|
1418
|
+
type: "regex"
|
|
1419
|
+
regex: "parameterType\\s*=\\s*\"(hashMap|hashmap|HashMap|map|java\\.util\\.(Hash)?Map)\""
|
|
1420
|
+
custom:
|
|
1421
|
+
rule_id: "SQL-MYBATIS-012"
|
|
1422
|
+
fix: "전용 VO/DTO 클래스를 parameterType으로 사용하세요"
|
|
1423
|
+
|
|
1424
|
+
- id: "sql-mybatis-013"
|
|
1425
|
+
name: "MyBatis SQL 블록 재사용 누락"
|
|
1426
|
+
severity: "medium"
|
|
1427
|
+
category: "mybatis"
|
|
1428
|
+
description: "동일 mapper 내 SQL 문이 매우 유사합니다. <sql>/<include>를 사용하여 공통 SQL을 재사용하세요."
|
|
1429
|
+
enabled: false # method-analysis 핸들러 미구현, 향후 구현 예정
|
|
1430
|
+
pattern:
|
|
1431
|
+
type: "method-analysis"
|
|
1432
|
+
conditions:
|
|
1433
|
+
- "mybatis-duplicate-sql-body"
|
|
1434
|
+
custom:
|
|
1435
|
+
rule_id: "SQL-MYBATIS-013"
|
|
1436
|
+
min_duplicate_count: 3
|
|
1437
|
+
fix: "<sql id='...'>로 공통 SQL을 추출하고 <include refid='...'>로 재사용하세요"
|
|
1438
|
+
|
|
1439
|
+
- id: "sql-mybatis-014"
|
|
1440
|
+
name: "MyBatis WHERE 1=1 사용 (<where> 태그 권장)"
|
|
1441
|
+
severity: "low"
|
|
1442
|
+
category: "mybatis"
|
|
1443
|
+
description: "WHERE 1=1과 <if>를 사용하는 대신 MyBatis <where> 태그를 사용하면 AND/OR이 자동 조정됩니다."
|
|
1444
|
+
enabled: true
|
|
1445
|
+
pattern:
|
|
1446
|
+
type: "regex"
|
|
1447
|
+
regex: "WHERE\\s+1\\s*=\\s*1"
|
|
1448
|
+
flags: "i"
|
|
1449
|
+
custom:
|
|
1450
|
+
rule_id: "SQL-MYBATIS-014"
|
|
1451
|
+
fix: "WHERE 1=1을 제거하고 MyBatis <where> 태그로 감싸세요"
|
|
1452
|
+
|
|
1453
|
+
- id: "sql-mybatis-015"
|
|
1454
|
+
name: "MyBatis <insert> 태그에 UPDATE 문 사용"
|
|
1455
|
+
severity: "medium"
|
|
1456
|
+
category: "mybatis"
|
|
1457
|
+
description: "<insert> 태그 안에 UPDATE 문이 있습니다. DML 유형에 맞는 태그(<update>)를 사용하세요."
|
|
1458
|
+
enabled: true
|
|
1459
|
+
pattern:
|
|
1460
|
+
type: "regex"
|
|
1461
|
+
regex: "<insert\\s[^>]*>[\\s\\S]*?\\bUPDATE\\s+\\w"
|
|
1462
|
+
flags: "i"
|
|
1463
|
+
custom:
|
|
1464
|
+
rule_id: "SQL-MYBATIS-015"
|
|
1465
|
+
fix: "<update> 태그를 사용하세요"
|
|
1466
|
+
|
|
1467
|
+
- id: "sql-mybatis-016"
|
|
1468
|
+
name: "MyBatis <update> 태그에 DELETE 문 사용"
|
|
1469
|
+
severity: "medium"
|
|
1470
|
+
category: "mybatis"
|
|
1471
|
+
description: "<update> 태그 안에 DELETE 문이 있습니다. DML 유형에 맞는 태그(<delete>)를 사용하세요."
|
|
1472
|
+
enabled: true
|
|
1473
|
+
pattern:
|
|
1474
|
+
type: "regex"
|
|
1475
|
+
regex: "<update\\s[^>]*>[\\s\\S]*?\\bDELETE\\s+FROM\\b"
|
|
1476
|
+
flags: "i"
|
|
1477
|
+
custom:
|
|
1478
|
+
rule_id: "SQL-MYBATIS-016"
|
|
1479
|
+
fix: "<delete> 태그를 사용하세요"
|
|
1480
|
+
|
|
1481
|
+
- id: "sql-mybatis-017"
|
|
1482
|
+
name: "MyBatis <selectKey> order 속성 누락"
|
|
1483
|
+
severity: "low"
|
|
1484
|
+
category: "mybatis"
|
|
1485
|
+
description: "<selectKey>에 order 속성이 없으면 실행 순서가 불명확합니다."
|
|
1486
|
+
enabled: true
|
|
1487
|
+
pattern:
|
|
1488
|
+
type: "regex"
|
|
1489
|
+
regex: "<selectKey\\s+(?!.*order\\s*=)[^>]*>"
|
|
1490
|
+
custom:
|
|
1491
|
+
rule_id: "SQL-MYBATIS-017"
|
|
1492
|
+
fix: "order=\"BEFORE\" 또는 order=\"AFTER\"를 명시하세요"
|
|
1493
|
+
|
|
1494
|
+
- id: "sql-mybatis-018"
|
|
1495
|
+
name: "MyBatis 페이지네이션에서 별칭.* 사용"
|
|
1496
|
+
severity: "low"
|
|
1497
|
+
category: "mybatis"
|
|
1498
|
+
description: "페이지네이션 래퍼에서 별칭.*를 사용하면 불필요한 컬럼이 포함됩니다. 필요한 컬럼만 명시하세요."
|
|
1499
|
+
enabled: true
|
|
1500
|
+
pattern:
|
|
1501
|
+
type: "regex"
|
|
1502
|
+
regex: "SELECT\\s+[A-Za-z]\\w*\\.\\*\\s+FROM\\s*\\("
|
|
1503
|
+
flags: "i"
|
|
1504
|
+
custom:
|
|
1505
|
+
rule_id: "SQL-MYBATIS-018"
|
|
1506
|
+
fix: "SELECT A.col1, A.col2, ... 로 필요한 컬럼만 명시하세요"
|
|
1507
|
+
|
|
1508
|
+
# ==================== SQL 성능 규칙 (XML 내 MyBatis SQL용) ====================
|
|
1509
|
+
|
|
1510
|
+
- id: "sql-xml-perf-001"
|
|
1511
|
+
name: "WHERE 절 인덱스 컬럼에 TO_CHAR 사용 (MyBatis)"
|
|
1512
|
+
severity: "high"
|
|
1513
|
+
category: "performance"
|
|
1514
|
+
description: "WHERE/JOIN 조건에서 인덱스 컬럼을 TO_CHAR()로 감싸면 인덱스를 사용할 수 없어 풀스캔이 발생합니다."
|
|
1515
|
+
enabled: true
|
|
1516
|
+
pattern:
|
|
1517
|
+
type: "regex"
|
|
1518
|
+
regex: "\\b(WHERE|AND|OR|ON)\\s+[^<]*TO_CHAR\\s*\\(\\s*[A-Za-z_][A-Za-z0-9_.]*"
|
|
1519
|
+
flags: "i"
|
|
1520
|
+
custom:
|
|
1521
|
+
rule_id: "SQL-XML-PERF-001"
|
|
1522
|
+
fix: "비교 대상을 변환하거나 함수 기반 인덱스를 생성하세요"
|
|
1523
|
+
|
|
1524
|
+
- id: "sql-xml-perf-002"
|
|
1525
|
+
name: "WHERE 절 인덱스 컬럼에 SUBSTR 사용 (MyBatis)"
|
|
1526
|
+
severity: "high"
|
|
1527
|
+
category: "performance"
|
|
1528
|
+
description: "WHERE 조건에서 인덱스 컬럼을 SUBSTR()로 감싸면 인덱스를 사용할 수 없습니다."
|
|
1529
|
+
enabled: true
|
|
1530
|
+
pattern:
|
|
1531
|
+
type: "regex"
|
|
1532
|
+
regex: "\\b(WHERE|AND|OR)\\s+[^<]*SUBSTR\\s*\\(\\s*[A-Za-z_][A-Za-z0-9_.]*"
|
|
1533
|
+
flags: "i"
|
|
1534
|
+
custom:
|
|
1535
|
+
rule_id: "SQL-XML-PERF-002"
|
|
1536
|
+
fix: "LIKE 'prefix%' 또는 별도 컬럼으로 대체하세요"
|
|
1537
|
+
|
|
1538
|
+
- id: "sql-xml-perf-003"
|
|
1539
|
+
name: "NOT IN (서브쿼리) 사용 (MyBatis)"
|
|
1540
|
+
severity: "medium"
|
|
1541
|
+
category: "performance"
|
|
1542
|
+
description: "NOT IN (서브쿼리)는 NULL 반환 시 전체 결과가 빈 집합이 되며 성능도 떨어집니다."
|
|
1543
|
+
enabled: true
|
|
1544
|
+
pattern:
|
|
1545
|
+
type: "regex-multiline"
|
|
1546
|
+
regex: "\\bNOT\\s+IN\\s*\\(\\s*SELECT\\b"
|
|
1547
|
+
custom:
|
|
1548
|
+
rule_id: "SQL-XML-PERF-003"
|
|
1549
|
+
flags: "i"
|
|
1550
|
+
fix: "NOT EXISTS (SELECT 1 FROM ... WHERE ...) 로 변경하세요"
|
|
1551
|
+
|
|
1552
|
+
- id: "sql-xml-perf-004"
|
|
1553
|
+
name: "UNION 대신 UNION ALL 고려 (MyBatis)"
|
|
1554
|
+
severity: "low"
|
|
1555
|
+
category: "performance"
|
|
1556
|
+
description: "UNION은 중복 제거를 위해 정렬합니다. 중복이 없으면 UNION ALL이 빠릅니다."
|
|
1557
|
+
enabled: true
|
|
1558
|
+
pattern:
|
|
1559
|
+
type: "regex"
|
|
1560
|
+
regex: "\\bUNION\\s+SELECT\\b"
|
|
1561
|
+
flags: "i"
|
|
1562
|
+
custom:
|
|
1563
|
+
rule_id: "SQL-XML-PERF-004"
|
|
1564
|
+
fix: "중복이 불가능하면 UNION ALL로 변경하세요"
|
|
1565
|
+
|
|
1566
|
+
- id: "sql-xml-perf-005"
|
|
1567
|
+
name: "SELECT * FROM (서브쿼리) 래핑 (MyBatis)"
|
|
1568
|
+
severity: "low"
|
|
1569
|
+
category: "performance"
|
|
1570
|
+
description: "SELECT * FROM (서브쿼리)는 불필요한 데이터 전달 계층을 추가합니다."
|
|
1571
|
+
enabled: true
|
|
1572
|
+
pattern:
|
|
1573
|
+
type: "regex"
|
|
1574
|
+
regex: "\\bSELECT\\s+\\*\\s+FROM\\s*\\("
|
|
1575
|
+
flags: "i"
|
|
1576
|
+
custom:
|
|
1577
|
+
rule_id: "SQL-XML-PERF-005"
|
|
1578
|
+
fix: "필요한 컬럼만 명시적으로 선택하세요"
|
|
1579
|
+
|
|
1580
|
+
- id: "sql-xml-perf-006"
|
|
1581
|
+
name: "DB Link 분산 쿼리 사용 (MyBatis)"
|
|
1582
|
+
severity: "medium"
|
|
1583
|
+
category: "performance"
|
|
1584
|
+
description: "DB Link를 통한 분산 쿼리는 네트워크 지연과 장애에 취약합니다."
|
|
1585
|
+
enabled: true
|
|
1586
|
+
pattern:
|
|
1587
|
+
type: "regex"
|
|
1588
|
+
regex: "\\b[A-Za-z_][A-Za-z0-9_]*@[A-Za-z_][A-Za-z0-9_]*\\b"
|
|
1589
|
+
custom:
|
|
1590
|
+
rule_id: "SQL-XML-PERF-006"
|
|
1591
|
+
fix: "데이터 복제, API, 또는 ETL로 대체를 검토하세요"
|
|
1592
|
+
|
|
1593
|
+
- id: "sql-xml-perf-007"
|
|
1594
|
+
name: "WHERE 절 인덱스 컬럼에 NVL 사용 (MyBatis)"
|
|
1595
|
+
severity: "medium"
|
|
1596
|
+
category: "performance"
|
|
1597
|
+
description: "WHERE 조건에서 인덱스 컬럼을 NVL()로 감싸면 인덱스를 사용할 수 없습니다."
|
|
1598
|
+
enabled: true
|
|
1599
|
+
pattern:
|
|
1600
|
+
type: "regex"
|
|
1601
|
+
regex: "\\b(WHERE|AND|OR)\\s+[^<]*NVL\\s*\\(\\s*[A-Za-z_][A-Za-z0-9_.]*"
|
|
1602
|
+
flags: "i"
|
|
1603
|
+
custom:
|
|
1604
|
+
rule_id: "SQL-XML-PERF-007"
|
|
1605
|
+
fix: "(col = value OR col IS NULL) 또는 COALESCE 인덱스를 사용하세요"
|
|
1606
|
+
|
|
1607
|
+
- id: "sql-xml-perf-008"
|
|
1608
|
+
name: "SELECT 절 다중 스칼라 서브쿼리 (MyBatis)"
|
|
1609
|
+
severity: "high"
|
|
1610
|
+
category: "performance"
|
|
1611
|
+
description: "SELECT 절에 스칼라 서브쿼리가 여러 개 있으면 행마다 N회 쿼리가 실행됩니다."
|
|
1612
|
+
enabled: true
|
|
1613
|
+
pattern:
|
|
1614
|
+
type: "regex-multiline"
|
|
1615
|
+
regex: "\\bSELECT\\b[^;]*(\\(\\s*SELECT\\b[^)]*\\))[^;]*(\\(\\s*SELECT\\b[^)]*\\))"
|
|
1616
|
+
custom:
|
|
1617
|
+
rule_id: "SQL-XML-PERF-008"
|
|
1618
|
+
flags: "i"
|
|
1619
|
+
fix: "스칼라 서브쿼리를 LEFT JOIN으로 통합하세요"
|
|
1620
|
+
|
|
1621
|
+
- id: "sql-xml-perf-009"
|
|
1622
|
+
name: "WHERE 절 상관 서브쿼리에 MAX/MIN 사용 (MyBatis)"
|
|
1623
|
+
severity: "medium"
|
|
1624
|
+
category: "performance"
|
|
1625
|
+
description: "WHERE 절에서 상관 서브쿼리로 MAX/MIN을 구하면 외부 행마다 반복 실행됩니다."
|
|
1626
|
+
enabled: true
|
|
1627
|
+
pattern:
|
|
1628
|
+
type: "regex"
|
|
1629
|
+
regex: "=\\s*\\(\\s*SELECT\\s+(MAX|MIN)\\s*\\("
|
|
1630
|
+
flags: "i"
|
|
1631
|
+
custom:
|
|
1632
|
+
rule_id: "SQL-XML-PERF-009"
|
|
1633
|
+
fix: "인라인 뷰 또는 윈도우 함수(ROW_NUMBER, RANK)로 대체하세요"
|
|
1634
|
+
|
|
1635
|
+
- id: "sql-xml-smell-040"
|
|
1636
|
+
name: "SELECT 절 스칼라 서브쿼리 (MyBatis)"
|
|
1637
|
+
severity: "medium"
|
|
1638
|
+
category: "smell"
|
|
1639
|
+
description: "SELECT 절의 스칼라 서브쿼리는 행마다 실행되어 N+1 쿼리 문제를 유발합니다."
|
|
1640
|
+
enabled: true
|
|
1641
|
+
pattern:
|
|
1642
|
+
type: "regex"
|
|
1643
|
+
regex: "\\bSELECT\\b[^(]*\\(\\s*SELECT\\b"
|
|
1644
|
+
flags: "i"
|
|
1645
|
+
custom:
|
|
1646
|
+
rule_id: "SQL-XML-SMELL-040"
|
|
1647
|
+
fix: "LEFT JOIN으로 변경하세요"
|
|
1648
|
+
|
|
1649
|
+
- id: "sql-xml-oracle-020"
|
|
1650
|
+
name: "Oracle (+) 외부조인 구문 사용 (MyBatis)"
|
|
1651
|
+
severity: "low"
|
|
1652
|
+
category: "portability"
|
|
1653
|
+
description: "Oracle 전용 (+) 외부조인 구문은 ANSI LEFT/RIGHT JOIN으로 대체하세요."
|
|
1654
|
+
enabled: true
|
|
1655
|
+
pattern:
|
|
1656
|
+
type: "regex"
|
|
1657
|
+
regex: "\\w+\\s*\\(\\+\\)"
|
|
1658
|
+
custom:
|
|
1659
|
+
rule_id: "SQL-XML-ORACLE-020"
|
|
1660
|
+
fix: "LEFT JOIN 또는 RIGHT JOIN ANSI 구문으로 변경하세요"
|