@netoalmanca/advpl-sensei 1.1.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/agents/changelog-generator.md +63 -0
- package/agents/code-generator.md +215 -0
- package/agents/code-reviewer.md +145 -0
- package/agents/debugger.md +83 -0
- package/agents/doc-generator.md +67 -0
- package/agents/docs-reference.md +86 -0
- package/agents/migrator.md +84 -0
- package/agents/process-consultant.md +97 -0
- package/agents/refactorer.md +75 -0
- package/agents/sx-configurator.md +67 -0
- package/commands/changelog.md +66 -0
- package/commands/diagnose.md +67 -0
- package/commands/docs.md +81 -0
- package/commands/document.md +67 -0
- package/commands/explain.md +60 -0
- package/commands/generate.md +111 -0
- package/commands/migrate.md +81 -0
- package/commands/process.md +111 -0
- package/commands/refactor.md +65 -0
- package/commands/review.md +60 -0
- package/commands/sxgen.md +98 -0
- package/commands/test.md +103 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/package.json +30 -0
- package/skills/advpl-code-generation/SKILL.md +163 -0
- package/skills/advpl-code-generation/patterns-fwformbrowse.md +485 -0
- package/skills/advpl-code-generation/patterns-jobs.md +519 -0
- package/skills/advpl-code-generation/patterns-mvc.md +765 -0
- package/skills/advpl-code-generation/patterns-pontos-entrada.md +708 -0
- package/skills/advpl-code-generation/patterns-rest.md +974 -0
- package/skills/advpl-code-generation/patterns-soap.md +639 -0
- package/skills/advpl-code-generation/patterns-treport.md +481 -0
- package/skills/advpl-code-generation/patterns-workflow.md +779 -0
- package/skills/advpl-code-generation/templates-classes.md +1096 -0
- package/skills/advpl-code-review/SKILL.md +72 -0
- package/skills/advpl-code-review/rules-best-practices.md +444 -0
- package/skills/advpl-code-review/rules-modernization.md +290 -0
- package/skills/advpl-code-review/rules-performance.md +333 -0
- package/skills/advpl-code-review/rules-security.md +302 -0
- package/skills/advpl-debugging/SKILL.md +265 -0
- package/skills/advpl-debugging/common-errors.md +1124 -0
- package/skills/advpl-debugging/performance-tips.md +768 -0
- package/skills/advpl-refactoring/SKILL.md +139 -0
- package/skills/advpl-to-tlpp-migration/SKILL.md +293 -0
- package/skills/advpl-to-tlpp-migration/migration-checklist.md +122 -0
- package/skills/advpl-to-tlpp-migration/migration-rules.md +265 -0
- package/skills/changelog-patterns/SKILL.md +99 -0
- package/skills/code-explanation/SKILL.md +66 -0
- package/skills/documentation-patterns/SKILL.md +172 -0
- package/skills/embedded-sql/SKILL.md +379 -0
- package/skills/probat-testing/SKILL.md +226 -0
- package/skills/probat-testing/patterns-unit-tests.md +614 -0
- package/skills/protheus-business/SKILL.md +92 -0
- package/skills/protheus-business/modulo-compras.md +780 -0
- package/skills/protheus-business/modulo-contabilidade.md +874 -0
- package/skills/protheus-business/modulo-estoque.md +876 -0
- package/skills/protheus-business/modulo-faturamento.md +800 -0
- package/skills/protheus-business/modulo-financeiro.md +1015 -0
- package/skills/protheus-business/modulo-fiscal.md +749 -0
- package/skills/protheus-business/modulo-manutencao.md +848 -0
- package/skills/protheus-business/modulo-pcp.md +743 -0
- package/skills/protheus-reference/SKILL.md +119 -0
- package/skills/protheus-reference/native-functions.md +7029 -0
- package/skills/protheus-reference/rest-api-reference.md +1758 -0
- package/skills/protheus-reference/restricted-functions.md +265 -0
- package/skills/protheus-reference/sx-dictionary.md +854 -0
- package/skills/sx-configuration/SKILL.md +184 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Modernization Rules
|
|
2
|
+
|
|
3
|
+
Rules for identifying modernization opportunities in ADVPL/TLPP code. Each rule includes a detection pattern, violation example, correct example, and explanation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## [MOD-001] .prw file using class structures that could be migrated to .tlpp
|
|
8
|
+
|
|
9
|
+
**Severity:** INFO
|
|
10
|
+
|
|
11
|
+
**Description:** Classes defined in `.prw` files using `CLASS ... ENDCLASS` or `CLASS ... FROM` syntax are candidates for migration to `.tlpp`, which offers native OOP support with proper namespaces, annotations, and stronger encapsulation.
|
|
12
|
+
|
|
13
|
+
**What to look for:** `.prw` files containing `CLASS`, `DATA`, `METHOD`, `ENDCLASS` keywords defining object-oriented structures.
|
|
14
|
+
|
|
15
|
+
**Violation:**
|
|
16
|
+
|
|
17
|
+
```advpl
|
|
18
|
+
// File: OrderService.prw
|
|
19
|
+
#Include "TOTVS.CH"
|
|
20
|
+
|
|
21
|
+
Class OrderService
|
|
22
|
+
|
|
23
|
+
Data cOrderId
|
|
24
|
+
Data nTotal
|
|
25
|
+
Data lProcessed
|
|
26
|
+
|
|
27
|
+
Method New() Constructor
|
|
28
|
+
Method Process()
|
|
29
|
+
Method Validate()
|
|
30
|
+
|
|
31
|
+
EndClass
|
|
32
|
+
|
|
33
|
+
Method New(cId) Class OrderService
|
|
34
|
+
::cOrderId := cId
|
|
35
|
+
::nTotal := 0
|
|
36
|
+
::lProcessed := .F.
|
|
37
|
+
Return Self
|
|
38
|
+
|
|
39
|
+
Method Process() Class OrderService
|
|
40
|
+
// Processing logic
|
|
41
|
+
Return
|
|
42
|
+
|
|
43
|
+
Method Validate() Class OrderService
|
|
44
|
+
// Validation logic
|
|
45
|
+
Return .T.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Correct:**
|
|
49
|
+
|
|
50
|
+
```tlpp
|
|
51
|
+
// File: OrderService.tlpp
|
|
52
|
+
#Include "tlpp-core.th"
|
|
53
|
+
|
|
54
|
+
Namespace custom.vendas
|
|
55
|
+
|
|
56
|
+
Class OrderService
|
|
57
|
+
|
|
58
|
+
Private Data cOrderId As Character
|
|
59
|
+
Private Data nTotal As Numeric
|
|
60
|
+
Private Data lProcessed As Logical
|
|
61
|
+
|
|
62
|
+
Public Method New(cId As Character) As Object
|
|
63
|
+
Public Method Process() As Logical
|
|
64
|
+
Public Method Validate() As Logical
|
|
65
|
+
|
|
66
|
+
EndClass
|
|
67
|
+
|
|
68
|
+
Method New(cId) Class OrderService
|
|
69
|
+
::cOrderId := cId
|
|
70
|
+
::nTotal := 0
|
|
71
|
+
::lProcessed := .F.
|
|
72
|
+
Return Self
|
|
73
|
+
|
|
74
|
+
Method Process() Class OrderService
|
|
75
|
+
// Processing logic
|
|
76
|
+
Return .T.
|
|
77
|
+
|
|
78
|
+
Method Validate() Class OrderService
|
|
79
|
+
// Validation logic
|
|
80
|
+
Return .T.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Why it matters:** TLPP provides typed parameters, typed return values, access modifiers (`Public`, `Private`, `Protected`), proper namespaces, and annotations. These features improve code safety, IDE tooling, documentation, and alignment with modern development practices. Migrating classes to TLPP is the first step toward a more maintainable codebase.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## [MOD-002] using namespace directive instead of .th includes in TLPP files
|
|
88
|
+
|
|
89
|
+
**Severity:** INFO
|
|
90
|
+
|
|
91
|
+
**Description:** TLPP files should use `.th` include files (`tlpp-core.th`, `tlpp-rest.th`, `tlpp-object.th`, `tlpp-probat.th`) instead of `using namespace` directives. The `.th` includes are the officially supported mechanism and provide all necessary definitions.
|
|
92
|
+
|
|
93
|
+
**What to look for:** `using namespace tlpp.core`, `using namespace tlpp.rest`, `using namespace tlpp.log`, `using namespace tlpp.data`, or any `using namespace tlpp.*` directive in `.tlpp` files.
|
|
94
|
+
|
|
95
|
+
**Violation:**
|
|
96
|
+
|
|
97
|
+
```tlpp
|
|
98
|
+
// File: ClientAPI.tlpp
|
|
99
|
+
using namespace tlpp.core
|
|
100
|
+
using namespace tlpp.rest
|
|
101
|
+
using namespace tlpp.log
|
|
102
|
+
|
|
103
|
+
Namespace custom.cadastro
|
|
104
|
+
|
|
105
|
+
Class ClientAPI
|
|
106
|
+
|
|
107
|
+
@Get("/api/v1/clients")
|
|
108
|
+
Public Method GetClients() As Logical
|
|
109
|
+
|
|
110
|
+
EndClass
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Correct:**
|
|
114
|
+
|
|
115
|
+
```tlpp
|
|
116
|
+
// File: ClientAPI.tlpp
|
|
117
|
+
#Include "tlpp-core.th"
|
|
118
|
+
#Include "tlpp-rest.th"
|
|
119
|
+
|
|
120
|
+
Namespace custom.cadastro
|
|
121
|
+
|
|
122
|
+
Class ClientAPI
|
|
123
|
+
|
|
124
|
+
@Get("/api/v1/clients")
|
|
125
|
+
Public Method GetClients() As Logical
|
|
126
|
+
|
|
127
|
+
EndClass
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Why it matters:** The `using namespace` directive is not the recommended pattern for TLPP. The `.th` include files (`tlpp-core.th`, `tlpp-rest.th`, `tlpp-object.th`, `tlpp-probat.th`) provide all necessary type definitions, constants, and macros required by the TLPP runtime. Using `.th` includes ensures compatibility with current and future TOTVS releases.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## [MOD-003] Procedural functions with object-like patterns that should be class methods
|
|
135
|
+
|
|
136
|
+
**Severity:** INFO
|
|
137
|
+
|
|
138
|
+
**Description:** Groups of related functions that share a common prefix and pass context via parameters or private variables are candidates for refactoring into a class. This improves encapsulation, reusability, and testability.
|
|
139
|
+
|
|
140
|
+
**What to look for:** Multiple `User Function` or `Static Function` definitions with a common naming prefix (e.g., `Ped_Create`, `Ped_Validate`, `Ped_Save`, `Ped_Delete`) that operate on the same data set, often passing the same variables between them.
|
|
141
|
+
|
|
142
|
+
**Violation:**
|
|
143
|
+
|
|
144
|
+
```advpl
|
|
145
|
+
// Procedural approach with shared prefix and repeated context passing
|
|
146
|
+
User Function Ped_Create(cCodCli, cCondPag)
|
|
147
|
+
Private aPedido := {}
|
|
148
|
+
Private cPedNum := GetSXENum("SC5", "C5_NUM")
|
|
149
|
+
|
|
150
|
+
aAdd(aPedido, {"C5_CLIENTE", cCodCli})
|
|
151
|
+
aAdd(aPedido, {"C5_CONDPAG", cCondPag})
|
|
152
|
+
|
|
153
|
+
If Ped_Validate(aPedido)
|
|
154
|
+
Ped_Save(aPedido)
|
|
155
|
+
EndIf
|
|
156
|
+
|
|
157
|
+
Return cPedNum
|
|
158
|
+
|
|
159
|
+
Static Function Ped_Validate(aPedido)
|
|
160
|
+
// Validation using the shared array
|
|
161
|
+
Return .T.
|
|
162
|
+
|
|
163
|
+
Static Function Ped_Save(aPedido)
|
|
164
|
+
// Save using the shared array
|
|
165
|
+
Return
|
|
166
|
+
|
|
167
|
+
Static Function Ped_Delete(cPedNum)
|
|
168
|
+
// Delete logic
|
|
169
|
+
Return
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Correct:**
|
|
173
|
+
|
|
174
|
+
```tlpp
|
|
175
|
+
// File: PedidoService.tlpp
|
|
176
|
+
#Include "tlpp-core.th"
|
|
177
|
+
|
|
178
|
+
Namespace custom.vendas
|
|
179
|
+
|
|
180
|
+
Class PedidoService
|
|
181
|
+
|
|
182
|
+
Private Data cCodCli As Character
|
|
183
|
+
Private Data cCondPag As Character
|
|
184
|
+
Private Data cPedNum As Character
|
|
185
|
+
Private Data aItems As Array
|
|
186
|
+
|
|
187
|
+
Public Method New(cCodCli As Character, cCondPag As Character) As Object
|
|
188
|
+
Public Method Validate() As Logical
|
|
189
|
+
Public Method Save() As Logical
|
|
190
|
+
Public Method Delete() As Logical
|
|
191
|
+
|
|
192
|
+
EndClass
|
|
193
|
+
|
|
194
|
+
Method New(cCodCli, cCondPag) Class PedidoService
|
|
195
|
+
::cCodCli := cCodCli
|
|
196
|
+
::cCondPag := cCondPag
|
|
197
|
+
::cPedNum := GetSXENum("SC5", "C5_NUM")
|
|
198
|
+
::aItems := {}
|
|
199
|
+
Return Self
|
|
200
|
+
|
|
201
|
+
Method Validate() Class PedidoService
|
|
202
|
+
// Validation using instance data - no parameter passing needed
|
|
203
|
+
Return .T.
|
|
204
|
+
|
|
205
|
+
Method Save() Class PedidoService
|
|
206
|
+
If ::Validate()
|
|
207
|
+
// Save using instance data
|
|
208
|
+
EndIf
|
|
209
|
+
Return .T.
|
|
210
|
+
|
|
211
|
+
Method Delete() Class PedidoService
|
|
212
|
+
// Delete using ::cPedNum
|
|
213
|
+
Return .T.
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Why it matters:** Procedural code with shared prefixes is essentially an ad-hoc class without encapsulation. Converting to a proper class eliminates `Private` variable pollution, makes dependencies explicit, enables unit testing via mock objects, and improves code navigation in IDEs.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## [MOD-004] Legacy UI patterns that could use FWFormBrowse or MVC
|
|
221
|
+
|
|
222
|
+
**Severity:** INFO
|
|
223
|
+
|
|
224
|
+
**Description:** Legacy UI functions like `AxCadastro`, `Modelo 2` (`Ax2Model`), and `Modelo 3` (`Ax3Model`) are outdated patterns for building CRUD screens. Modern Protheus development uses `FWFormBrowse` or the MVC framework (`FWMVCModel`, `FWFormView`) for maintainable and standardized interfaces.
|
|
225
|
+
|
|
226
|
+
**What to look for:** Calls to `AxCadastro()`, `AxPesqui()`, `MBrowse()`, `Modelo2()`, `Modelo3()`, or manual `DEFINE MSDIALOG` / `ACTIVATE MSDIALOG` patterns for CRUD operations.
|
|
227
|
+
|
|
228
|
+
**Violation:**
|
|
229
|
+
|
|
230
|
+
```advpl
|
|
231
|
+
// Legacy AxCadastro approach
|
|
232
|
+
User Function XPED001()
|
|
233
|
+
Local cAlias := "SC5"
|
|
234
|
+
|
|
235
|
+
Private cCadastro := "Pedidos de Venda"
|
|
236
|
+
Private aRotina := {}
|
|
237
|
+
|
|
238
|
+
aAdd(aRotina, {"Pesquisar", "AxPesqui()", 0, 1})
|
|
239
|
+
aAdd(aRotina, {"Incluir", "XPED001I()", 0, 3})
|
|
240
|
+
aAdd(aRotina, {"Alterar", "XPED001A()", 0, 4})
|
|
241
|
+
aAdd(aRotina, {"Excluir", "XPED001E()", 0, 5})
|
|
242
|
+
aAdd(aRotina, {"Visualizar","XPED001V()", 0, 2})
|
|
243
|
+
|
|
244
|
+
DbSelectArea(cAlias)
|
|
245
|
+
DbSetOrder(1)
|
|
246
|
+
|
|
247
|
+
AxCadastro(cAlias, cCadastro, aRotina)
|
|
248
|
+
|
|
249
|
+
Return
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Correct:**
|
|
253
|
+
|
|
254
|
+
```advpl
|
|
255
|
+
// MVC approach
|
|
256
|
+
#Include "TOTVS.CH"
|
|
257
|
+
|
|
258
|
+
User Function XPED001()
|
|
259
|
+
|
|
260
|
+
Local oBrowse := FWMBrowse():New()
|
|
261
|
+
|
|
262
|
+
oBrowse:SetAlias("SC5")
|
|
263
|
+
oBrowse:SetDescription("Pedidos de Venda")
|
|
264
|
+
oBrowse:Activate()
|
|
265
|
+
|
|
266
|
+
Return
|
|
267
|
+
|
|
268
|
+
Static Function ModelDef()
|
|
269
|
+
Local oModel := MPFormModel():New("XPED001M")
|
|
270
|
+
Local oStMaster := FWFormStruct(1, "SC5")
|
|
271
|
+
|
|
272
|
+
oModel:AddFields("SC5MASTER", /*cOwner*/, oStMaster)
|
|
273
|
+
oModel:SetPrimaryKey({"C5_FILIAL", "C5_NUM"})
|
|
274
|
+
|
|
275
|
+
Return oModel
|
|
276
|
+
|
|
277
|
+
Static Function ViewDef()
|
|
278
|
+
Local oView := FWFormView():New()
|
|
279
|
+
Local oModel := FWLoadModel("XPED001")
|
|
280
|
+
Local oStView := FWFormStruct(2, "SC5")
|
|
281
|
+
|
|
282
|
+
oView:SetModel(oModel)
|
|
283
|
+
oView:AddField("VIEW_SC5", oStView, "SC5MASTER")
|
|
284
|
+
oView:CreateHorizontalBox("SUPERIOR", 100)
|
|
285
|
+
oView:SetOwnerView("VIEW_SC5", "SUPERIOR")
|
|
286
|
+
|
|
287
|
+
Return oView
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Why it matters:** Legacy UI patterns like `AxCadastro` do not support modern features such as field validation callbacks, automatic audit logging, REST API exposure, and standardized CRUD operations. The MVC framework provides a consistent architecture, reduces boilerplate code, and integrates with Protheus features like workflow approval and data auditing out of the box.
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# Performance Rules
|
|
2
|
+
|
|
3
|
+
Rules for detecting performance issues in ADVPL/TLPP code. Each rule includes a detection pattern, violation example, correct example, and explanation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## [PERF-001] SELECT * in Embedded SQL
|
|
8
|
+
|
|
9
|
+
**Severity:** CRITICAL
|
|
10
|
+
|
|
11
|
+
**Description:** Using `SELECT *` in Embedded SQL or TCQuery fetches all columns from the table, including large memo fields and unused columns. This wastes memory, increases network traffic, and slows down query execution.
|
|
12
|
+
|
|
13
|
+
**What to look for:** `SELECT *` or `SELECT ALIAS.*` inside `BeginSQL`/`EndSQL` blocks or in `TCQuery` strings.
|
|
14
|
+
|
|
15
|
+
**Violation:**
|
|
16
|
+
|
|
17
|
+
```advpl
|
|
18
|
+
User Function GetOrders()
|
|
19
|
+
Local cAlias := GetNextAlias()
|
|
20
|
+
|
|
21
|
+
BeginSQL Alias cAlias
|
|
22
|
+
SELECT *
|
|
23
|
+
FROM %table:SC5% SC5
|
|
24
|
+
WHERE SC5.%notDel%
|
|
25
|
+
AND SC5.C5_FILIAL = %xfilial:SC5%
|
|
26
|
+
EndSQL
|
|
27
|
+
|
|
28
|
+
Return
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Correct:**
|
|
32
|
+
|
|
33
|
+
```advpl
|
|
34
|
+
User Function GetOrders()
|
|
35
|
+
Local cAlias := GetNextAlias()
|
|
36
|
+
|
|
37
|
+
BeginSQL Alias cAlias
|
|
38
|
+
SELECT SC5.C5_NUM, SC5.C5_CLIENTE, SC5.C5_LOJACLI,
|
|
39
|
+
SC5.C5_EMISSAO, SC5.C5_CONDPAG, SC5.C5_TOTPED
|
|
40
|
+
FROM %table:SC5% SC5
|
|
41
|
+
WHERE SC5.%notDel%
|
|
42
|
+
AND SC5.C5_FILIAL = %xfilial:SC5%
|
|
43
|
+
EndSQL
|
|
44
|
+
|
|
45
|
+
Return
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Why it matters:** Protheus tables often have 100+ columns. Fetching all of them when only 5-6 are needed can multiply query time and memory usage by 10x or more. Specifying columns also makes the code self-documenting about which fields are actually used.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## [PERF-002] DbSeek inside loop when a single query could replace it
|
|
53
|
+
|
|
54
|
+
**Severity:** WARNING
|
|
55
|
+
|
|
56
|
+
**Description:** Performing `DbSeek` inside a loop (While, For) to look up data for each iteration causes N individual index lookups. A single SQL query with a JOIN or IN clause is significantly faster.
|
|
57
|
+
|
|
58
|
+
**What to look for:** `DbSeek` calls inside `While`/`EndDo`, `For`/`Next`, or similar loop constructs, especially when looking up supplementary data for records being processed.
|
|
59
|
+
|
|
60
|
+
**Violation:**
|
|
61
|
+
|
|
62
|
+
```advpl
|
|
63
|
+
User Function ListOrderItems(cOrder)
|
|
64
|
+
Local aItems := {}
|
|
65
|
+
|
|
66
|
+
DbSelectArea("SC6")
|
|
67
|
+
DbSetOrder(1)
|
|
68
|
+
DbSeek(xFilial("SC6") + cOrder)
|
|
69
|
+
|
|
70
|
+
While !Eof() .And. SC6->C6_NUM == cOrder
|
|
71
|
+
// DbSeek inside loop for each item - N lookups!
|
|
72
|
+
DbSelectArea("SB1")
|
|
73
|
+
DbSetOrder(1)
|
|
74
|
+
If DbSeek(xFilial("SB1") + SC6->C6_PRODUTO)
|
|
75
|
+
aAdd(aItems, {SC6->C6_PRODUTO, SB1->B1_DESC, SC6->C6_QTDVEN})
|
|
76
|
+
EndIf
|
|
77
|
+
|
|
78
|
+
DbSelectArea("SC6")
|
|
79
|
+
DbSkip()
|
|
80
|
+
EndDo
|
|
81
|
+
|
|
82
|
+
Return aItems
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Correct:**
|
|
86
|
+
|
|
87
|
+
```advpl
|
|
88
|
+
User Function ListOrderItems(cOrder)
|
|
89
|
+
Local aItems := {}
|
|
90
|
+
Local cAlias := GetNextAlias()
|
|
91
|
+
|
|
92
|
+
BeginSQL Alias cAlias
|
|
93
|
+
SELECT SC6.C6_PRODUTO, SB1.B1_DESC, SC6.C6_QTDVEN
|
|
94
|
+
FROM %table:SC6% SC6
|
|
95
|
+
INNER JOIN %table:SB1% SB1
|
|
96
|
+
ON SB1.B1_FILIAL = %xfilial:SB1%
|
|
97
|
+
AND SB1.B1_COD = SC6.C6_PRODUTO
|
|
98
|
+
AND SB1.%notDel%
|
|
99
|
+
WHERE SC6.%notDel%
|
|
100
|
+
AND SC6.C6_FILIAL = %xfilial:SC6%
|
|
101
|
+
AND SC6.C6_NUM = %exp:cOrder%
|
|
102
|
+
EndSQL
|
|
103
|
+
|
|
104
|
+
DbSelectArea(cAlias)
|
|
105
|
+
While !(cAlias)->(Eof())
|
|
106
|
+
aAdd(aItems, {(cAlias)->C6_PRODUTO, (cAlias)->B1_DESC, (cAlias)->C6_QTDVEN})
|
|
107
|
+
(cAlias)->(DbSkip())
|
|
108
|
+
EndDo
|
|
109
|
+
(cAlias)->(DbCloseArea())
|
|
110
|
+
|
|
111
|
+
Return aItems
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Why it matters:** Each `DbSeek` involves an index lookup and potential disk I/O. With 1,000 order items, that means 1,000 individual seeks to SB1. A single JOIN query lets the database engine optimize the access path and return all results in one round trip.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## [PERF-003] Missing TCSetField for column typing after query execution
|
|
119
|
+
|
|
120
|
+
**Severity:** WARNING
|
|
121
|
+
|
|
122
|
+
**Description:** After executing a query via `TCQuery` or `BeginSQL`, date and numeric columns are returned as character strings by default. Without `TCSetField` to define column types, every comparison or arithmetic operation requires manual type conversion, which is slower and error-prone.
|
|
123
|
+
|
|
124
|
+
**What to look for:** `TCQuery` or `BeginSQL` usage without subsequent `TCSetField` calls for date or numeric columns that are used in comparisons or calculations.
|
|
125
|
+
|
|
126
|
+
**Violation:**
|
|
127
|
+
|
|
128
|
+
```advpl
|
|
129
|
+
User Function GetDueDate()
|
|
130
|
+
Local cAlias := GetNextAlias()
|
|
131
|
+
Local dDueDate
|
|
132
|
+
|
|
133
|
+
BeginSQL Alias cAlias
|
|
134
|
+
SELECT E2_VENCTO, E2_VALOR
|
|
135
|
+
FROM %table:SE2% SE2
|
|
136
|
+
WHERE SE2.%notDel%
|
|
137
|
+
AND SE2.E2_FILIAL = %xfilial:SE2%
|
|
138
|
+
EndSQL
|
|
139
|
+
|
|
140
|
+
// E2_VENCTO comes back as character "20250115"
|
|
141
|
+
// E2_VALOR comes back as character - arithmetic fails or gives wrong results
|
|
142
|
+
dDueDate := StoD((cAlias)->E2_VENCTO) // manual conversion needed everywhere
|
|
143
|
+
|
|
144
|
+
Return dDueDate
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Correct:**
|
|
148
|
+
|
|
149
|
+
```advpl
|
|
150
|
+
User Function GetDueDate()
|
|
151
|
+
Local cAlias := GetNextAlias()
|
|
152
|
+
Local dDueDate
|
|
153
|
+
|
|
154
|
+
BeginSQL Alias cAlias
|
|
155
|
+
SELECT E2_VENCTO, E2_VALOR
|
|
156
|
+
FROM %table:SE2% SE2
|
|
157
|
+
WHERE SE2.%notDel%
|
|
158
|
+
AND SE2.E2_FILIAL = %xfilial:SE2%
|
|
159
|
+
EndSQL
|
|
160
|
+
|
|
161
|
+
TCSetField(cAlias, "E2_VENCTO", "D")
|
|
162
|
+
TCSetField(cAlias, "E2_VALOR", "N", 14, 2)
|
|
163
|
+
|
|
164
|
+
// Now fields are properly typed
|
|
165
|
+
dDueDate := (cAlias)->E2_VENCTO // already a Date type
|
|
166
|
+
|
|
167
|
+
Return dDueDate
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Why it matters:** Without `TCSetField`, developers must manually convert every field access with `StoD()`, `Val()`, etc. This adds overhead, produces verbose code, and introduces bugs when a conversion is forgotten. Setting field types once after the query is cleaner and faster.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## [PERF-004] String concatenation inside loop
|
|
175
|
+
|
|
176
|
+
**Severity:** WARNING
|
|
177
|
+
|
|
178
|
+
**Description:** Concatenating strings with `+` or `+=` inside a loop creates a new string object on every iteration. For large loops, this causes excessive memory allocation and garbage collection, severely degrading performance.
|
|
179
|
+
|
|
180
|
+
**What to look for:** String concatenation using `+` or `+=` inside `While`/`EndDo`, `For`/`Next` loops, especially when building large texts, CSV content, or log output.
|
|
181
|
+
|
|
182
|
+
**Violation:**
|
|
183
|
+
|
|
184
|
+
```advpl
|
|
185
|
+
User Function BuildCSV()
|
|
186
|
+
Local cCSV := ""
|
|
187
|
+
|
|
188
|
+
DbSelectArea("SA1")
|
|
189
|
+
DbGoTop()
|
|
190
|
+
|
|
191
|
+
While !Eof()
|
|
192
|
+
// Creates a new string on every iteration
|
|
193
|
+
cCSV += SA1->A1_COD + ";" + SA1->A1_NOME + CRLF
|
|
194
|
+
DbSkip()
|
|
195
|
+
EndDo
|
|
196
|
+
|
|
197
|
+
Return cCSV
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Correct:**
|
|
201
|
+
|
|
202
|
+
```advpl
|
|
203
|
+
User Function BuildCSV()
|
|
204
|
+
Local aLines := {}
|
|
205
|
+
Local cCSV := ""
|
|
206
|
+
|
|
207
|
+
DbSelectArea("SA1")
|
|
208
|
+
DbGoTop()
|
|
209
|
+
|
|
210
|
+
While !Eof()
|
|
211
|
+
aAdd(aLines, SA1->A1_COD + ";" + SA1->A1_NOME)
|
|
212
|
+
DbSkip()
|
|
213
|
+
EndDo
|
|
214
|
+
|
|
215
|
+
cCSV := ArrTokStr(aLines, CRLF)
|
|
216
|
+
|
|
217
|
+
Return cCSV
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Why it matters:** String concatenation in a loop has O(n^2) time complexity because each concatenation copies the entire accumulated string. With 10,000 records, this can take minutes instead of seconds. Collecting parts in an array and joining once at the end is O(n).
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## [PERF-005] RecCount() to check if records exist
|
|
225
|
+
|
|
226
|
+
**Severity:** WARNING
|
|
227
|
+
|
|
228
|
+
**Description:** Using `RecCount()` to check whether a query returned results forces a full table/result scan to count all records. To check for existence, use `!Eof()` after a seek or query, which returns immediately.
|
|
229
|
+
|
|
230
|
+
**What to look for:** `RecCount()` used in conditions like `If RecCount() > 0`, `If RecCount() == 0`, or similar existence checks.
|
|
231
|
+
|
|
232
|
+
**Violation:**
|
|
233
|
+
|
|
234
|
+
```advpl
|
|
235
|
+
User Function HasOrders(cCodCli)
|
|
236
|
+
Local lHas := .F.
|
|
237
|
+
Local cAlias := GetNextAlias()
|
|
238
|
+
|
|
239
|
+
BeginSQL Alias cAlias
|
|
240
|
+
SELECT C5_NUM
|
|
241
|
+
FROM %table:SC5% SC5
|
|
242
|
+
WHERE SC5.%notDel%
|
|
243
|
+
AND SC5.C5_FILIAL = %xfilial:SC5%
|
|
244
|
+
AND SC5.C5_CLIENTE = %exp:cCodCli%
|
|
245
|
+
EndSQL
|
|
246
|
+
|
|
247
|
+
// RecCount scans all matching records just to check existence
|
|
248
|
+
If (cAlias)->(RecCount()) > 0
|
|
249
|
+
lHas := .T.
|
|
250
|
+
EndIf
|
|
251
|
+
|
|
252
|
+
(cAlias)->(DbCloseArea())
|
|
253
|
+
|
|
254
|
+
Return lHas
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Correct:**
|
|
258
|
+
|
|
259
|
+
```advpl
|
|
260
|
+
User Function HasOrders(cCodCli)
|
|
261
|
+
Local lHas := .F.
|
|
262
|
+
Local cAlias := GetNextAlias()
|
|
263
|
+
|
|
264
|
+
BeginSQL Alias cAlias
|
|
265
|
+
SELECT TOP 1 C5_NUM
|
|
266
|
+
FROM %table:SC5% SC5
|
|
267
|
+
WHERE SC5.%notDel%
|
|
268
|
+
AND SC5.C5_FILIAL = %xfilial:SC5%
|
|
269
|
+
AND SC5.C5_CLIENTE = %exp:cCodCli%
|
|
270
|
+
EndSQL
|
|
271
|
+
|
|
272
|
+
// Eof() returns immediately - no need to count all records
|
|
273
|
+
lHas := !(cAlias)->(Eof())
|
|
274
|
+
|
|
275
|
+
(cAlias)->(DbCloseArea())
|
|
276
|
+
|
|
277
|
+
Return lHas
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Why it matters:** `RecCount()` may trigger a full scan of the result set to count rows. If the query matches 50,000 records, all of them are counted just to check if any exist. `!Eof()` after a `SELECT TOP 1` stops at the first match, making existence checks virtually instant.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## [PERF-006] Query without proper index hint or ORDER BY matching existing index
|
|
285
|
+
|
|
286
|
+
**Severity:** INFO
|
|
287
|
+
|
|
288
|
+
**Description:** SQL queries that filter or sort by columns not covered by an existing index force full table scans. ORDER BY clauses should match the column order of an existing index for optimal performance.
|
|
289
|
+
|
|
290
|
+
**What to look for:** `WHERE` clauses filtering on columns that are not part of any index, or `ORDER BY` with column order that does not match any available index. Cross-reference with the SIX (index) table definition.
|
|
291
|
+
|
|
292
|
+
**Violation:**
|
|
293
|
+
|
|
294
|
+
```advpl
|
|
295
|
+
User Function GetRecentOrders()
|
|
296
|
+
Local cAlias := GetNextAlias()
|
|
297
|
+
|
|
298
|
+
BeginSQL Alias cAlias
|
|
299
|
+
SELECT C5_NUM, C5_EMISSAO, C5_CLIENTE
|
|
300
|
+
FROM %table:SC5% SC5
|
|
301
|
+
WHERE SC5.%notDel%
|
|
302
|
+
AND SC5.C5_FILIAL = %xfilial:SC5%
|
|
303
|
+
ORDER BY C5_EMISSAO DESC
|
|
304
|
+
// C5_EMISSAO alone may not be an indexed column
|
|
305
|
+
// This could cause a full table sort
|
|
306
|
+
EndSQL
|
|
307
|
+
|
|
308
|
+
Return
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Correct:**
|
|
312
|
+
|
|
313
|
+
```advpl
|
|
314
|
+
User Function GetRecentOrders()
|
|
315
|
+
Local cAlias := GetNextAlias()
|
|
316
|
+
|
|
317
|
+
// Check SIX for SC5 indexes to find one that starts with C5_FILIAL + C5_EMISSAO
|
|
318
|
+
// If no suitable index exists, consider creating one or using a different approach
|
|
319
|
+
|
|
320
|
+
BeginSQL Alias cAlias
|
|
321
|
+
SELECT C5_NUM, C5_EMISSAO, C5_CLIENTE
|
|
322
|
+
FROM %table:SC5% SC5
|
|
323
|
+
WHERE SC5.%notDel%
|
|
324
|
+
AND SC5.C5_FILIAL = %xfilial:SC5%
|
|
325
|
+
AND SC5.C5_EMISSAO >= %exp:DtoS(dDataIni)%
|
|
326
|
+
ORDER BY C5_FILIAL, C5_EMISSAO DESC
|
|
327
|
+
// ORDER BY matches index column order for better performance
|
|
328
|
+
EndSQL
|
|
329
|
+
|
|
330
|
+
Return
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Why it matters:** Queries without proper index support perform full table scans, which grow linearly with table size. A table with 1 million records can take minutes to scan versus milliseconds with a proper index seek. Always verify that your WHERE and ORDER BY clauses align with the available indexes in the SIX table.
|