@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,379 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: embedded-sql
|
|
3
|
+
description: Use when writing SQL queries in ADVPL/TLPP using BeginSQL/EndSQL blocks, %table%, %notDel%, %xfilial%, %exp% macros, or when choosing between Embedded SQL and TCQuery string concatenation
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Embedded SQL in ADVPL/TLPP
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Embedded SQL allows writing SQL queries directly in ADVPL/TLPP code using `BeginSQL ... EndSQL` blocks with special macro expressions. It replaces error-prone string concatenation (`cQuery += "SELECT..."`) with readable, type-safe, and maintainable SQL blocks.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Writing any SQL query in ADVPL/TLPP (prefer over string concatenation)
|
|
15
|
+
- Need to query with proper filial filtering, deletion handling, and table naming
|
|
16
|
+
- Building reports with complex SELECT, JOIN, GROUP BY
|
|
17
|
+
- Any situation where `TCQuery` with concatenated strings is currently used
|
|
18
|
+
- When readability and maintainability of SQL code matters
|
|
19
|
+
|
|
20
|
+
## BeginSQL vs TCQuery (String Concatenation)
|
|
21
|
+
|
|
22
|
+
| Aspect | BeginSQL (Modern) | TCQuery + Strings (Legacy) |
|
|
23
|
+
|--------|-------------------|---------------------------|
|
|
24
|
+
| Readability | SQL written naturally | SQL buried in string concat |
|
|
25
|
+
| Table names | `%table:SE2%` automatic | `RetSqlName("SE2")` manual |
|
|
26
|
+
| Filial filter | `%xfilial:SE2%` automatic | `xFilial("SE2")` manual |
|
|
27
|
+
| Deletion filter | `%notDel%` automatic | `D_E_L_E_T_ = ' '` manual |
|
|
28
|
+
| Variable binding | `%exp:cVar%` | `"'" + cVar + "'"` (SQL injection risk) |
|
|
29
|
+
| Column types | `column X as Date` | `TCSetField` manual |
|
|
30
|
+
| Maintenance | Easy to read and modify | Hard to find errors in strings |
|
|
31
|
+
| SQL Injection | Protected via macros | Vulnerable if not careful |
|
|
32
|
+
|
|
33
|
+
**Always prefer BeginSQL for new code.**
|
|
34
|
+
|
|
35
|
+
## Core Syntax
|
|
36
|
+
|
|
37
|
+
```advpl
|
|
38
|
+
BeginSQL Alias cAlias
|
|
39
|
+
SELECT columns
|
|
40
|
+
FROM %table:ALIAS% ALIAS
|
|
41
|
+
WHERE ALIAS.%notDel%
|
|
42
|
+
AND ALIAS.FIELD_FILIAL = %xfilial:ALIAS%
|
|
43
|
+
AND ALIAS.FIELD = %exp:cVariable%
|
|
44
|
+
ORDER BY %Order:ALIAS%
|
|
45
|
+
EndSQL
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Important:** The alias parameter can be a string literal or `GetNextAlias()`:
|
|
49
|
+
|
|
50
|
+
```advpl
|
|
51
|
+
Local cAlias := GetNextAlias()
|
|
52
|
+
|
|
53
|
+
BeginSQL Alias cAlias
|
|
54
|
+
SELECT A1_COD, A1_LOJA, A1_NOME
|
|
55
|
+
FROM %table:SA1% SA1
|
|
56
|
+
WHERE SA1.%notDel%
|
|
57
|
+
AND SA1.A1_FILIAL = %xfilial:SA1%
|
|
58
|
+
EndSQL
|
|
59
|
+
|
|
60
|
+
DbSelectArea(cAlias)
|
|
61
|
+
While !Eof()
|
|
62
|
+
Conout(cAlias->A1_NOME)
|
|
63
|
+
DbSkip()
|
|
64
|
+
EndDo
|
|
65
|
+
DbCloseArea()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Special Macro Expressions
|
|
69
|
+
|
|
70
|
+
### %table:TABLE%
|
|
71
|
+
|
|
72
|
+
Resolves the physical table name with proper database schema/owner.
|
|
73
|
+
|
|
74
|
+
```advpl
|
|
75
|
+
-- Input:
|
|
76
|
+
FROM %table:SA1% SA1
|
|
77
|
+
|
|
78
|
+
-- Expands to (example):
|
|
79
|
+
FROM SA1010 SA1
|
|
80
|
+
-- (where 010 = company code, depends on environment)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### %xfilial:TABLE%
|
|
84
|
+
|
|
85
|
+
Returns the current branch (filial) value for the table.
|
|
86
|
+
|
|
87
|
+
```advpl
|
|
88
|
+
-- Input:
|
|
89
|
+
AND SA1.A1_FILIAL = %xfilial:SA1%
|
|
90
|
+
|
|
91
|
+
-- Expands to:
|
|
92
|
+
AND SA1.A1_FILIAL = '01'
|
|
93
|
+
-- (or '' if table is not branch-filtered)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### %notDel%
|
|
97
|
+
|
|
98
|
+
Filters out logically deleted records.
|
|
99
|
+
|
|
100
|
+
```advpl
|
|
101
|
+
-- Input:
|
|
102
|
+
WHERE SA1.%notDel%
|
|
103
|
+
|
|
104
|
+
-- Expands to:
|
|
105
|
+
WHERE SA1.D_E_L_E_T_ <> '*'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Always include this.** Protheus uses logical deletion, not physical.
|
|
109
|
+
|
|
110
|
+
### %exp:EXPRESSION%
|
|
111
|
+
|
|
112
|
+
Binds ADVPL variables, expressions, or function results into the SQL.
|
|
113
|
+
|
|
114
|
+
```advpl
|
|
115
|
+
Local cCodCli := "000001"
|
|
116
|
+
Local cLoja := "01"
|
|
117
|
+
Local dDataIni := CtoD("01/01/2026")
|
|
118
|
+
|
|
119
|
+
BeginSQL Alias cAlias
|
|
120
|
+
SELECT E2_PREFIXO, E2_NUM, E2_VALOR, E2_EMISSAO
|
|
121
|
+
FROM %table:SE2% SE2
|
|
122
|
+
WHERE SE2.%notDel%
|
|
123
|
+
AND SE2.E2_FILIAL = %xfilial:SE2%
|
|
124
|
+
AND SE2.E2_FORNECE = %exp:cCodCli%
|
|
125
|
+
AND SE2.E2_LOJA = %exp:cLoja%
|
|
126
|
+
AND SE2.E2_EMISSAO >= %exp:DtoS(dDataIni)%
|
|
127
|
+
EndSQL
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Note:** `%exp:%` handles quoting automatically for character fields. For dates, use `DtoS()` to convert.
|
|
131
|
+
|
|
132
|
+
### %Order:TABLE%
|
|
133
|
+
|
|
134
|
+
Returns the primary key ordering for the table.
|
|
135
|
+
|
|
136
|
+
```advpl
|
|
137
|
+
-- Input:
|
|
138
|
+
ORDER BY %Order:SE2%
|
|
139
|
+
|
|
140
|
+
-- Expands to the primary key columns of SE2
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Column Type Declaration
|
|
144
|
+
|
|
145
|
+
Declare result column types to avoid manual `TCSetField` calls:
|
|
146
|
+
|
|
147
|
+
```advpl
|
|
148
|
+
BeginSQL Alias cAlias
|
|
149
|
+
column E2_EMISSAO as Date
|
|
150
|
+
column E2_VENCTO as Date
|
|
151
|
+
column E2_VALOR as Numeric(16,2)
|
|
152
|
+
|
|
153
|
+
SELECT E2_PREFIXO, E2_NUM, E2_EMISSAO, E2_VENCTO, E2_VALOR
|
|
154
|
+
FROM %table:SE2% SE2
|
|
155
|
+
WHERE SE2.%notDel%
|
|
156
|
+
AND SE2.E2_FILIAL = %xfilial:SE2%
|
|
157
|
+
EndSQL
|
|
158
|
+
|
|
159
|
+
// Columns are automatically typed - no TCSetField needed
|
|
160
|
+
DbSelectArea(cAlias)
|
|
161
|
+
While !Eof()
|
|
162
|
+
Local dEmissao := (cAlias)->E2_EMISSAO // Already Date type
|
|
163
|
+
Local nValor := (cAlias)->E2_VALOR // Already Numeric
|
|
164
|
+
DbSkip()
|
|
165
|
+
EndDo
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Without `column` declaration**, all fields return as Character and need manual conversion.
|
|
169
|
+
|
|
170
|
+
## JOIN Patterns
|
|
171
|
+
|
|
172
|
+
### INNER JOIN
|
|
173
|
+
|
|
174
|
+
```advpl
|
|
175
|
+
BeginSQL Alias cAlias
|
|
176
|
+
SELECT SE2.E2_PREFIXO, SE2.E2_NUM, SE2.E2_VALOR,
|
|
177
|
+
SA2.A2_NOME, SA2.A2_CGC
|
|
178
|
+
FROM %table:SE2% SE2
|
|
179
|
+
INNER JOIN %table:SA2% SA2
|
|
180
|
+
ON SE2.E2_FORNECE = SA2.A2_COD
|
|
181
|
+
AND SE2.E2_LOJA = SA2.A2_LOJA
|
|
182
|
+
AND SA2.A2_FILIAL = %xfilial:SA2%
|
|
183
|
+
AND SA2.%notDel%
|
|
184
|
+
WHERE SE2.%notDel%
|
|
185
|
+
AND SE2.E2_FILIAL = %xfilial:SE2%
|
|
186
|
+
AND SE2.E2_EMISSAO >= %exp:DtoS(dDataIni)%
|
|
187
|
+
ORDER BY SE2.E2_EMISSAO
|
|
188
|
+
EndSQL
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### LEFT JOIN
|
|
192
|
+
|
|
193
|
+
```advpl
|
|
194
|
+
BeginSQL Alias cAlias
|
|
195
|
+
SELECT SC5.C5_NUM, SC5.C5_CLIENTE, SC5.C5_LOJACLI,
|
|
196
|
+
SA1.A1_NOME
|
|
197
|
+
FROM %table:SC5% SC5
|
|
198
|
+
LEFT JOIN %table:SA1% SA1
|
|
199
|
+
ON SC5.C5_CLIENTE = SA1.A1_COD
|
|
200
|
+
AND SC5.C5_LOJACLI = SA1.A1_LOJA
|
|
201
|
+
AND SA1.A1_FILIAL = %xfilial:SA1%
|
|
202
|
+
AND SA1.%notDel%
|
|
203
|
+
WHERE SC5.%notDel%
|
|
204
|
+
AND SC5.C5_FILIAL = %xfilial:SC5%
|
|
205
|
+
EndSQL
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Best practice:** Always use explicit JOIN syntax, not comma-separated FROM.
|
|
209
|
+
|
|
210
|
+
## Aggregation Patterns
|
|
211
|
+
|
|
212
|
+
### SUM / COUNT / AVG
|
|
213
|
+
|
|
214
|
+
```advpl
|
|
215
|
+
BeginSQL Alias cAlias
|
|
216
|
+
column TOTAL as Numeric(16,2)
|
|
217
|
+
column QTD as Numeric(10,0)
|
|
218
|
+
|
|
219
|
+
SELECT SE2.E2_FORNECE, SE2.E2_LOJA,
|
|
220
|
+
SUM(SE2.E2_VALOR) AS TOTAL,
|
|
221
|
+
COUNT(*) AS QTD
|
|
222
|
+
FROM %table:SE2% SE2
|
|
223
|
+
WHERE SE2.%notDel%
|
|
224
|
+
AND SE2.E2_FILIAL = %xfilial:SE2%
|
|
225
|
+
AND SE2.E2_EMISSAO BETWEEN %exp:DtoS(dDataIni)% AND %exp:DtoS(dDataFim)%
|
|
226
|
+
GROUP BY SE2.E2_FORNECE, SE2.E2_LOJA
|
|
227
|
+
HAVING SUM(SE2.E2_VALOR) > 0
|
|
228
|
+
ORDER BY TOTAL DESC
|
|
229
|
+
EndSQL
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Subquery
|
|
233
|
+
|
|
234
|
+
```advpl
|
|
235
|
+
BeginSQL Alias cAlias
|
|
236
|
+
SELECT SA1.A1_COD, SA1.A1_NOME
|
|
237
|
+
FROM %table:SA1% SA1
|
|
238
|
+
WHERE SA1.%notDel%
|
|
239
|
+
AND SA1.A1_FILIAL = %xfilial:SA1%
|
|
240
|
+
AND SA1.A1_COD IN (
|
|
241
|
+
SELECT SC5.C5_CLIENTE
|
|
242
|
+
FROM %table:SC5% SC5
|
|
243
|
+
WHERE SC5.%notDel%
|
|
244
|
+
AND SC5.C5_FILIAL = %xfilial:SC5%
|
|
245
|
+
AND SC5.C5_EMISSAO >= %exp:DtoS(dDataIni)%
|
|
246
|
+
)
|
|
247
|
+
EndSQL
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Complete Pattern: Query with Cursor Iteration
|
|
251
|
+
|
|
252
|
+
```advpl
|
|
253
|
+
#Include "TOTVS.CH"
|
|
254
|
+
#Include "TopConn.ch"
|
|
255
|
+
|
|
256
|
+
User Function QueryEx()
|
|
257
|
+
Local cAlias := GetNextAlias()
|
|
258
|
+
Local aArea := GetArea()
|
|
259
|
+
Local aResult := {}
|
|
260
|
+
Local dDataIni := Date() - 30
|
|
261
|
+
|
|
262
|
+
BeginSQL Alias cAlias
|
|
263
|
+
column E2_EMISSAO as Date
|
|
264
|
+
column E2_VENCTO as Date
|
|
265
|
+
column E2_VALOR as Numeric(16,2)
|
|
266
|
+
column SALDO as Numeric(16,2)
|
|
267
|
+
|
|
268
|
+
SELECT SE2.E2_PREFIXO, SE2.E2_NUM, SE2.E2_PARCELA,
|
|
269
|
+
SE2.E2_FORNECE, SE2.E2_LOJA,
|
|
270
|
+
SE2.E2_EMISSAO, SE2.E2_VENCTO,
|
|
271
|
+
SE2.E2_VALOR,
|
|
272
|
+
(SE2.E2_VALOR - SE2.E2_PAGO) AS SALDO,
|
|
273
|
+
SA2.A2_NOME
|
|
274
|
+
FROM %table:SE2% SE2
|
|
275
|
+
INNER JOIN %table:SA2% SA2
|
|
276
|
+
ON SE2.E2_FORNECE = SA2.A2_COD
|
|
277
|
+
AND SE2.E2_LOJA = SA2.A2_LOJA
|
|
278
|
+
AND SA2.A2_FILIAL = %xfilial:SA2%
|
|
279
|
+
AND SA2.%notDel%
|
|
280
|
+
WHERE SE2.%notDel%
|
|
281
|
+
AND SE2.E2_FILIAL = %xfilial:SE2%
|
|
282
|
+
AND SE2.E2_EMISSAO >= %exp:DtoS(dDataIni)%
|
|
283
|
+
AND (SE2.E2_VALOR - SE2.E2_PAGO) > 0
|
|
284
|
+
ORDER BY SE2.E2_VENCTO
|
|
285
|
+
EndSQL
|
|
286
|
+
|
|
287
|
+
DbSelectArea(cAlias)
|
|
288
|
+
While !(cAlias)->(Eof())
|
|
289
|
+
aAdd(aResult, {;
|
|
290
|
+
(cAlias)->E2_PREFIXO,;
|
|
291
|
+
(cAlias)->E2_NUM,;
|
|
292
|
+
(cAlias)->E2_VALOR,;
|
|
293
|
+
(cAlias)->SALDO,;
|
|
294
|
+
(cAlias)->A2_NOME;
|
|
295
|
+
})
|
|
296
|
+
(cAlias)->(DbSkip())
|
|
297
|
+
EndDo
|
|
298
|
+
(cAlias)->(DbCloseArea())
|
|
299
|
+
|
|
300
|
+
RestArea(aArea)
|
|
301
|
+
Return aResult
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Restrictions and Gotchas
|
|
305
|
+
|
|
306
|
+
| Rule | Why |
|
|
307
|
+
|------|-----|
|
|
308
|
+
| Do NOT start a line with `*` inside BeginSQL | ADVPL pre-compiler treats `*` at line start as comment |
|
|
309
|
+
| Always include `%notDel%` | Protheus uses logical deletion, never rely on physical |
|
|
310
|
+
| Always include `%xfilial%` or filial filter | Multi-branch environments will return wrong data |
|
|
311
|
+
| Use `GetNextAlias()` for the alias | Avoids alias name conflicts with open work areas |
|
|
312
|
+
| Always `DbCloseArea()` after done | Prevents work area leaks (limited number of aliases) |
|
|
313
|
+
| Do NOT use `SELECT *` | Specify columns explicitly for performance and clarity |
|
|
314
|
+
| Date values must use `DtoS()` | Database stores dates as YYYYMMDD strings |
|
|
315
|
+
| `column` declarations go BEFORE the SELECT | They define result types, not query columns |
|
|
316
|
+
|
|
317
|
+
## DML Operations
|
|
318
|
+
|
|
319
|
+
For INSERT, UPDATE, DELETE use `TCSqlExec` instead of BeginSQL:
|
|
320
|
+
|
|
321
|
+
```advpl
|
|
322
|
+
// INSERT
|
|
323
|
+
Local cSql := "INSERT INTO " + RetSqlName("ZZ1") + " "
|
|
324
|
+
cSql += "(ZZ1_FILIAL, ZZ1_CODIGO, ZZ1_DESCRI, D_E_L_E_T_, R_E_C_N_O_) "
|
|
325
|
+
cSql += "VALUES ('" + xFilial("ZZ1") + "', '001', 'Teste', ' ', " + cValToChar(GetSxeNum("ZZ1","ZZ1_CODIGO")) + ")"
|
|
326
|
+
nRet := TCSqlExec(cSql)
|
|
327
|
+
|
|
328
|
+
// UPDATE
|
|
329
|
+
cSql := "UPDATE " + RetSqlName("ZZ1") + " SET "
|
|
330
|
+
cSql += "ZZ1_DESCRI = 'Novo Valor' "
|
|
331
|
+
cSql += "WHERE ZZ1_FILIAL = '" + xFilial("ZZ1") + "' "
|
|
332
|
+
cSql += "AND ZZ1_CODIGO = '001' "
|
|
333
|
+
cSql += "AND D_E_L_E_T_ = ' '"
|
|
334
|
+
nRet := TCSqlExec(cSql)
|
|
335
|
+
|
|
336
|
+
If nRet < 0
|
|
337
|
+
Conout("SQL Error: " + TCSqlError())
|
|
338
|
+
EndIf
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**BeginSQL is for SELECT only.** Use `TCSqlExec` for DML (INSERT/UPDATE/DELETE).
|
|
342
|
+
|
|
343
|
+
## Performance Tips
|
|
344
|
+
|
|
345
|
+
1. **Use `column` declarations** to avoid post-query type conversions
|
|
346
|
+
2. **Use JOINs** instead of subqueries when possible
|
|
347
|
+
3. **Filter early** with WHERE, not after reading all rows in ADVPL
|
|
348
|
+
4. **Use NOLOCK** hint for read-only queries (SQL Server): `FROM %table:SA1% SA1 WITH(NOLOCK)`
|
|
349
|
+
5. **Use TOP N** when you only need limited results
|
|
350
|
+
6. **Close aliases** immediately after use to free work areas
|
|
351
|
+
7. **Avoid loops with BeginSQL inside** - build one query that returns all needed data
|
|
352
|
+
|
|
353
|
+
## Migration from TCQuery to BeginSQL
|
|
354
|
+
|
|
355
|
+
**Before (string concatenation):**
|
|
356
|
+
```advpl
|
|
357
|
+
Local cQuery := ""
|
|
358
|
+
cQuery += "SELECT A1_COD, A1_NOME "
|
|
359
|
+
cQuery += "FROM " + RetSqlName("SA1") + " SA1 "
|
|
360
|
+
cQuery += "WHERE SA1.D_E_L_E_T_ = ' ' "
|
|
361
|
+
cQuery += "AND A1_FILIAL = '" + xFilial("SA1") + "' "
|
|
362
|
+
cQuery += "AND A1_TIPO = '" + cTipo + "'"
|
|
363
|
+
TCQuery cQuery New Alias "QRY_CLI"
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**After (Embedded SQL):**
|
|
367
|
+
```advpl
|
|
368
|
+
Local cAlias := GetNextAlias()
|
|
369
|
+
|
|
370
|
+
BeginSQL Alias cAlias
|
|
371
|
+
SELECT A1_COD, A1_NOME
|
|
372
|
+
FROM %table:SA1% SA1
|
|
373
|
+
WHERE SA1.%notDel%
|
|
374
|
+
AND SA1.A1_FILIAL = %xfilial:SA1%
|
|
375
|
+
AND SA1.A1_TIPO = %exp:cTipo%
|
|
376
|
+
EndSQL
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Benefits: No manual `RetSqlName`, no `D_E_L_E_T_`, no `xFilial()`, no string quoting, no SQL injection risk.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: probat-testing
|
|
3
|
+
description: Use when generating ProBat unit tests for TLPP classes and functions on TOTVS Protheus
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ProBat Testing
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
ProBat is the native unit testing engine of tlppCore for creating and executing tests in TLPP programs on TOTVS Protheus. The name comes from Latin and means "proof/tests". It supports unit, functional, and integration tests, code coverage analysis, TDD workflows, and CI/CD integration.
|
|
11
|
+
|
|
12
|
+
> **ProBat only works with TLPP.** Tests for ADVPL sources must also be written in TLPP. If the target code is `.prw`, the test file is still `.tlpp`.
|
|
13
|
+
|
|
14
|
+
## Required Include
|
|
15
|
+
|
|
16
|
+
Every test file must include the ProBat header:
|
|
17
|
+
|
|
18
|
+
```tlpp
|
|
19
|
+
#include "tlpp-probat.th"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
If using tlppCore features (JsonObject, REST, etc.), also include:
|
|
23
|
+
|
|
24
|
+
```tlpp
|
|
25
|
+
#include "tlpp-core.th"
|
|
26
|
+
#include "tlpp-probat.th"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Namespace Convention
|
|
30
|
+
|
|
31
|
+
Test namespaces follow the pattern `test.<module>` or `custom.tests.<module>`:
|
|
32
|
+
|
|
33
|
+
```tlpp
|
|
34
|
+
namespace test.financeiro
|
|
35
|
+
namespace custom.tests.faturamento
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Annotations
|
|
39
|
+
|
|
40
|
+
All annotations below are confirmed in the official TOTVS `tlpp-probat-samples` repository.
|
|
41
|
+
|
|
42
|
+
### @TestFixture
|
|
43
|
+
|
|
44
|
+
Marks a function or class as a test fixture. This is the **only** way ProBat discovers test code.
|
|
45
|
+
|
|
46
|
+
```tlpp
|
|
47
|
+
// Minimal
|
|
48
|
+
@TestFixture()
|
|
49
|
+
|
|
50
|
+
// With properties
|
|
51
|
+
@TestFixture(owner='team_name')
|
|
52
|
+
@TestFixture(owner='team_name', target='source_file.tlpp')
|
|
53
|
+
@TestFixture(owner='team_name', suite='suite_name')
|
|
54
|
+
@TestFixture(priority=1)
|
|
55
|
+
@TestFixture(runWithAll=.F.) // Exclude from "run all" mode
|
|
56
|
+
@TestFixture(rwa=.F.) // Short alias for runWithAll
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Property | Type | Description |
|
|
60
|
+
|----------|------|-------------|
|
|
61
|
+
| `owner` | Character | Team or author identifier |
|
|
62
|
+
| `target` | Character | Source file being tested (for traceability) |
|
|
63
|
+
| `suite` | Character | Suite name (for grouped execution) |
|
|
64
|
+
| `priority` | Numeric | Execution order (lower = first) |
|
|
65
|
+
| `runWithAll` / `rwa` | Logical | If `.F.`, test is excluded from "run all" mode |
|
|
66
|
+
|
|
67
|
+
### @Test
|
|
68
|
+
|
|
69
|
+
Marks a method in a `@TestFixture` class as a test to execute. The description parameter is **mandatory**.
|
|
70
|
+
|
|
71
|
+
```tlpp
|
|
72
|
+
@Test('description of what is being tested')
|
|
73
|
+
public method myTestMethod()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> If a class has `@TestFixture` but no methods with `@Test`, the result will be SKIPPED.
|
|
77
|
+
|
|
78
|
+
### @OneTimeSetUp (runs once before all tests)
|
|
79
|
+
|
|
80
|
+
Marks a method to run **once** before all `@Test` methods in the class. Multiple `@OneTimeSetUp` methods are allowed.
|
|
81
|
+
|
|
82
|
+
```tlpp
|
|
83
|
+
@OneTimeSetUp()
|
|
84
|
+
public method initDatabase()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### @Setup (runs before each test)
|
|
88
|
+
|
|
89
|
+
Marks a method to run **before each** `@Test` method.
|
|
90
|
+
|
|
91
|
+
```tlpp
|
|
92
|
+
@Setup()
|
|
93
|
+
public method resetState()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### @TearDown (runs after each test)
|
|
97
|
+
|
|
98
|
+
Marks a method to run **after each** `@Test` method.
|
|
99
|
+
|
|
100
|
+
```tlpp
|
|
101
|
+
@TearDown()
|
|
102
|
+
public method cleanupState()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### @OneTimeTearDown (runs once after all tests)
|
|
106
|
+
|
|
107
|
+
Marks a method to run **once** after all `@Test` methods in the class.
|
|
108
|
+
|
|
109
|
+
```tlpp
|
|
110
|
+
@OneTimeTearDown()
|
|
111
|
+
public method closeConnections()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### @Skip
|
|
115
|
+
|
|
116
|
+
Skips an entire test fixture (function or class) or a specific method. Supports conditional filters.
|
|
117
|
+
|
|
118
|
+
```tlpp
|
|
119
|
+
// Skip unconditionally
|
|
120
|
+
@Skip()
|
|
121
|
+
|
|
122
|
+
// Skip with filters
|
|
123
|
+
@Skip(system="windows")
|
|
124
|
+
@Skip(appServerName="HARPIA")
|
|
125
|
+
@Skip(tlppVersion=">= 01.02.10")
|
|
126
|
+
@Skip(system="windows", appServerName="HARPIA", tlppVersion=">= 01.02.10")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### @ErrorLog
|
|
130
|
+
|
|
131
|
+
Marks a test that is expected to produce an error. The test passes if the specified error occurs.
|
|
132
|
+
|
|
133
|
+
```tlpp
|
|
134
|
+
@Test('test that expects an error')
|
|
135
|
+
@ErrorLog('type mismatch')
|
|
136
|
+
public method testExpectedError()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### SKIPASSERT (inline command)
|
|
140
|
+
|
|
141
|
+
Skips the next assertion only. Useful for conditional skipping inside a test.
|
|
142
|
+
|
|
143
|
+
```tlpp
|
|
144
|
+
SKIPASSERT
|
|
145
|
+
assertError('this assert will be skipped')
|
|
146
|
+
|
|
147
|
+
// With filters
|
|
148
|
+
SKIPASSERT TLPPVERSION ">= 01.02.10"
|
|
149
|
+
SKIPASSERT SYSTEM "windows"
|
|
150
|
+
SKIPASSERT APPSERVERNAME "HARPIA"
|
|
151
|
+
SKIPASSERT SYSTEM "windows" APPSERVERNAME "HARPIA" TLPPVERSION ">= 01.02.10"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Assertion Functions
|
|
155
|
+
|
|
156
|
+
All assertions belong to the `tlpp.probat` namespace. They can be called either by using the namespace directive or with the full qualified name.
|
|
157
|
+
|
|
158
|
+
```tlpp
|
|
159
|
+
// Option 1: using namespace
|
|
160
|
+
using namespace tlpp.probat
|
|
161
|
+
assertEquals(xValue, xExpected, 'description')
|
|
162
|
+
|
|
163
|
+
// Option 2: full qualified name
|
|
164
|
+
tlpp.probat.assertEquals(xValue, xExpected, 'description')
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Complete Assertion Reference
|
|
168
|
+
|
|
169
|
+
| Function | Signature | Description |
|
|
170
|
+
|----------|-----------|-------------|
|
|
171
|
+
| `assertEquals` | `(xValue, xExpected [, cDesc])` | Values must be equal |
|
|
172
|
+
| `assertNotEquals` | `(xValue, xExpected [, cDesc])` | Values must be different |
|
|
173
|
+
| `assertTrue` | `(lValue [, cDesc])` | Value must be `.T.` |
|
|
174
|
+
| `assertFalse` | `(lValue [, cDesc])` | Value must be `.F.` |
|
|
175
|
+
| `assertGreater` | `(xValue, xCompare [, cDesc])` | Value must be greater than expected |
|
|
176
|
+
| `assertGreaterOrEqual` | `(xValue, xCompare [, cDesc])` | Value must be greater than or equal to expected |
|
|
177
|
+
| `assertLess` | `(xValue, xCompare [, cDesc])` | Value must be less than expected |
|
|
178
|
+
| `assertLessOrEqual` | `(xValue, xCompare [, cDesc])` | Value must be less than or equal to expected |
|
|
179
|
+
| `assertNil` | `(xValue [, cDesc])` | Value must be NIL |
|
|
180
|
+
| `assertVector` | `(aValue, aExpected [, cDesc])` | Arrays must be equal |
|
|
181
|
+
| `assertJson` | `(xValue, xExpected [, cDesc])` | JSON objects/strings must be equal |
|
|
182
|
+
| `assertIsRegExFull` | `(cValue, cPattern [, cDesc])` | String must fully match regex |
|
|
183
|
+
| `assertIsRegExPartial` | `(cValue, cPattern [, cDesc])` | String must partially match regex |
|
|
184
|
+
| `assertIsContained` | `(cValue, cSearch [, cDesc])` | String must contain the search value |
|
|
185
|
+
| `assertOK` | `(cDesc)` | Registers a positive result (always passes) |
|
|
186
|
+
| `assertError` | `(cDesc)` | Registers a negative result (always fails) |
|
|
187
|
+
| `assertWarning` | `(cDesc)` | Registers a warning (no pass/fail impact) |
|
|
188
|
+
|
|
189
|
+
## Execution Lifecycle (Class-based)
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
Constructor (new)
|
|
193
|
+
|
|
|
194
|
+
+-- @OneTimeSetUp methods (once)
|
|
195
|
+
|
|
|
196
|
+
+-- @Setup methods (before each test)
|
|
197
|
+
| |
|
|
198
|
+
| +-- @Test method
|
|
199
|
+
| |
|
|
200
|
+
| +-- @TearDown methods (after each test)
|
|
201
|
+
|
|
|
202
|
+
+-- @Setup methods (before each test)
|
|
203
|
+
| |
|
|
204
|
+
| +-- @Test method
|
|
205
|
+
| |
|
|
206
|
+
| +-- @TearDown methods (after each test)
|
|
207
|
+
|
|
|
208
|
+
+-- @OneTimeTearDown methods (once)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Test Execution
|
|
212
|
+
|
|
213
|
+
### Via REST API (programmatic)
|
|
214
|
+
|
|
215
|
+
Tests can be discovered and executed using ProBat's REST API or through the main runner. See the official `tlpp-probat-samples` repository for script-based execution examples.
|
|
216
|
+
|
|
217
|
+
### Via VSCode Extension
|
|
218
|
+
|
|
219
|
+
TOTVS provides a VSCode extension for ProBat that integrates test discovery and execution directly into the editor.
|
|
220
|
+
|
|
221
|
+
## References
|
|
222
|
+
|
|
223
|
+
- `patterns-unit-tests.md` - Templates and patterns for writing ProBat tests
|
|
224
|
+
- [Official samples](https://github.com/totvs/tlpp-probat-samples) - TOTVS official ProBat examples repository
|
|
225
|
+
- [TDN ProBat](https://tdn.totvs.com/display/tec/PROBAT) - Official documentation on TOTVS Developer Network
|
|
226
|
+
- [TDN Asserts](https://tdn.totvs.com/display/tec/d+-+Asserts) - Assertion functions reference
|