Skip to content

Commit a366f23

Browse files
Arthurvdvclaude
andauthored
docs(AC0031, AC0032): document DataTransfer CopyFields/CopyRows as table operations (#157)
CopyFields counts as r on the source and m on the destination, CopyRows as r and i, with the tables taken from every SetTables(Database::X, Database::Y) call on the same variable (bare or this.-qualified) in the same procedure — provided every one of them resolves. Unresolvable SetTables: AC0031 reports nothing; AC0032 is disabled for the object, like the RecordRef case. Companion to ALCops/Analyzers#491. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2621536 commit a366f23

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

content/docs/analyzers/ApplicationCop/AC0031.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,40 @@ XmlPort `tableelement` nodes require permissions based on the `Direction` proper
8787

8888
Setting `AutoSave`, `AutoReplace`, or `AutoUpdate` to `false` suppresses the corresponding requirement.
8989

90+
### DataTransfer operations
91+
92+
A `DataTransfer` variable performs its database work in a single bulk statement when `CopyFields` or `CopyRows` is called. The tables involved are the ones named in `SetTables`:
93+
94+
| Method | Source table | Destination table |
95+
|---|---|---|
96+
| `CopyFields` | Read (`r`) | Modify (`m`) |
97+
| `CopyRows` | Read (`r`) | Insert (`i`) |
98+
99+
The other `DataTransfer` methods (`SetTables`, `AddFieldValue`, `AddConstantValue`, `AddJoin`, `AddSourceFilter`, `AddDestinationFilter`, `UpdateAuditFields`) only configure the transfer in memory and do not require permissions. The diagnostic is reported on the `CopyFields` or `CopyRows` call.
100+
101+
{{< highlight al "hl_lines=14" >}}
102+
codeunit 50100 "Upgrade Purch. Cr. Memo Line"
103+
{
104+
Subtype = Upgrade;
105+
Permissions = tabledata "Purch. Cr. Memo Line" = r; // missing m: CopyFields modifies the destination
106+
107+
procedure UpgradeCustomCode(OldValue: Code[20]; NewValue: Code[20])
108+
var
109+
PurchCrMemoLine: Record "Purch. Cr. Memo Line";
110+
PurchCrMemoLineDataTransfer: DataTransfer;
111+
begin
112+
PurchCrMemoLineDataTransfer.SetTables(Database::"Purch. Cr. Memo Line", Database::"Purch. Cr. Memo Line");
113+
PurchCrMemoLineDataTransfer.AddSourceFilter(PurchCrMemoLine.FieldNo("Custom Code"), '=%1', OldValue);
114+
PurchCrMemoLineDataTransfer.AddConstantValue(NewValue, PurchCrMemoLine.FieldNo("Custom Code"));
115+
PurchCrMemoLineDataTransfer.CopyFields(); // Table data access requires explicit object permissions [AC0031]
116+
end;
117+
}
118+
{{< /highlight >}}
119+
120+
The tables are resolved from every `SetTables` call on the same `DataTransfer` variable **in the same procedure or trigger** as the `CopyFields`/`CopyRows` call, and only when both arguments are `Database::"Table Name"` literals. When several `SetTables` calls exist in that body, all of their tables are considered — provided every one of them resolves; a single `SetTables` with a non-literal argument makes the whole call unresolvable. The variable itself may be a local, a global (also when addressed as `this.MyDataTransfer`), or a parameter.
121+
122+
When the tables cannot be determined — no `SetTables` in the same body (for example, configured in another procedure or by the caller), or an argument that is a variable, parameter, or expression instead of a `Database::` literal — no diagnostic is reported for that call.
123+
90124
### Exceptions
91125

92126
The diagnostic is suppressed when any of the following conditions apply:

content/docs/analyzers/ApplicationCop/AC0032.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ The diagnostic is suppressed when any of the following conditions apply:
8989
- The codeunit is a test codeunit with `TestPermissions = Disabled`
9090
- The containing object is a `permissionset` or `permissionsetextension` (these declare permissions structurally, not for access control)
9191
- The object contains a database operation on a `RecordRef` (see below)
92+
- The object contains a `DataTransfer` `CopyFields` or `CopyRows` call whose tables cannot be resolved (see below)
9293

9394
### Record sets positioned by another object
9495

@@ -134,6 +135,41 @@ codeunit 50100 "Ledger Entry Management"
134135

135136
Note that this also suppresses genuinely unused entries in the same object. This trade-off is deliberate: a false "unused" report combined with the code fix would remove permissions that are required at runtime.
136137

138+
### DataTransfer operations
139+
140+
A `DataTransfer` variable touches the database only when `CopyFields` or `CopyRows` is called, and the tables involved are the ones named in `SetTables`. These calls count as using the following permissions:
141+
142+
| Method | Source table | Destination table |
143+
|---|---|---|
144+
| `CopyFields` | Read (`r`) | Modify (`m`) |
145+
| `CopyRows` | Read (`r`) | Insert (`i`) |
146+
147+
The other `DataTransfer` methods (`SetTables`, `AddFieldValue`, `AddConstantValue`, `AddJoin`, `AddSourceFilter`, `AddDestinationFilter`, `UpdateAuditFields`) configure the transfer in memory and do not count as usage. Chars beyond what the executor needs are still reported: `tabledata X = rimd` with only a same-table `CopyFields` reports `id` as unused.
148+
149+
{{< highlight al >}}
150+
codeunit 50100 "Upgrade Purch. Cr. Memo Line"
151+
{
152+
Subtype = Upgrade;
153+
// Not reported: CopyFields reads and modifies "Purch. Cr. Memo Line"
154+
Permissions = tabledata "Purch. Cr. Memo Line" = rm;
155+
156+
procedure UpgradeCustomCode(OldValue: Code[20]; NewValue: Code[20])
157+
var
158+
PurchCrMemoLine: Record "Purch. Cr. Memo Line";
159+
PurchCrMemoLineDataTransfer: DataTransfer;
160+
begin
161+
PurchCrMemoLineDataTransfer.SetTables(Database::"Purch. Cr. Memo Line", Database::"Purch. Cr. Memo Line");
162+
PurchCrMemoLineDataTransfer.AddSourceFilter(PurchCrMemoLine.FieldNo("Custom Code"), '=%1', OldValue);
163+
PurchCrMemoLineDataTransfer.AddConstantValue(NewValue, PurchCrMemoLine.FieldNo("Custom Code"));
164+
PurchCrMemoLineDataTransfer.CopyFields();
165+
end;
166+
}
167+
{{< /highlight >}}
168+
169+
The tables are resolved from every `SetTables` call on the same `DataTransfer` variable **in the same procedure or trigger** as the `CopyFields`/`CopyRows` call, and only when both arguments are `Database::"Table Name"` literals. When several `SetTables` calls exist in that body, all of their tables count as used — provided every one of them resolves; a single `SetTables` with a non-literal argument makes the whole call unresolvable. The variable may be a local, a global (also when addressed as `this.MyDataTransfer`), or a parameter.
170+
171+
When the tables cannot be determined — no `SetTables` in the same body (for example, configured in another procedure or by the caller), or an argument that is a variable, parameter, or expression instead of a `Database::` literal — the operation may target any table, and AC0032 is disabled for the entire object, exactly as for `RecordRef` operations above.
172+
137173
### Temporary tables
138174

139175
Access through a temporary table does **not** count as using a permission. Temporary tables never touch the database, so a `Permissions` entry for a table that is accessed **only** through temporary records is dead code and **is reported** as unused. This applies to every way a temporary table can be implemented: the `temporary` keyword on a record variable, a table object with `TableType = Temporary`, a page with `SourceTableTemporary = true`, and report data items or XMLPort table elements with `UseTemporary = true`.

0 commit comments

Comments
 (0)