bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
373 lines (308 loc) • 11.3 kB
Markdown
# Temporary Table Safety - AL Code Examples
## Good Examples
### Secure Temporary Table with Cleanup
```al
procedure ProcessSensitiveCustomerData(CustomerFilter: Text): Boolean
var
Customer: Record Customer;
TempCustomerBuffer: Record Customer temporary;
ProcessingResult: Boolean;
begin
// Validate user permissions before populating temporary table
if not UserPermissions.CanViewCustomerData() then
exit(false);
Customer.SetFilter("No.", CustomerFilter);
if not Customer.FindSet() then
exit(false);
try
// Populate temporary table with filtered data
repeat
// Only include data user has permission to see
if UserPermissions.CanViewCustomer(Customer."No.") then begin
TempCustomerBuffer := Customer;
TempCustomerBuffer.Insert();
end;
until Customer.Next() = 0;
// Process temporary data
ProcessingResult := ProcessCustomerBuffer(TempCustomerBuffer);
finally
// Always clear sensitive temporary data
TempCustomerBuffer.DeleteAll();
end;
exit(ProcessingResult);
end;
```
### Permission-Aware Temporary Processing
```al
procedure GenerateCustomerReport(CustomerNo: Code[20]): Text
var
Customer: Record Customer;
CustLedgerEntry: Record "Cust. Ledger Entry";
TempReportBuffer: Record "Name/Value Buffer" temporary;
ReportText: Text;
begin
// Early permission validation
if not Customer.ReadPermission then
exit('Access Denied');
if not Customer.Get(CustomerNo) then
exit('Customer Not Found');
// Verify specific customer access
if not UserPermissions.CanViewCustomer(CustomerNo) then
exit('Access Denied');
try
// Build temporary report data with permission checks
TempReportBuffer.Init();
TempReportBuffer.Name := 'Customer Name';
TempReportBuffer.Value := Customer.Name;
TempReportBuffer.Insert();
// Only include financial data if user has permission
if CustLedgerEntry.ReadPermission and UserPermissions.CanViewFinancialData() then begin
CustLedgerEntry.SetRange("Customer No.", CustomerNo);
CustLedgerEntry.CalcSums("Remaining Amt. (LCY)");
TempReportBuffer.Init();
TempReportBuffer.Name := 'Outstanding Amount';
TempReportBuffer.Value := Format(CustLedgerEntry."Remaining Amt. (LCY)");
TempReportBuffer.Insert();
end;
// Generate report from temporary data
ReportText := BuildReportFromBuffer(TempReportBuffer);
finally
// Always clear temporary data containing sensitive information
TempReportBuffer.DeleteAll();
end;
exit(ReportText);
end;
```
### Scoped Temporary Table Processing
```al
procedure CalculateItemProfitability(ItemFilter: Text): Decimal
var
Item: Record Item;
ItemLedgerEntry: Record "Item Ledger Entry";
TempProfitBuffer: Record "Integer" temporary;
TotalProfit: Decimal;
LineNo: Integer;
begin
// Validate permissions for cost data access
if not UserPermissions.CanViewCostInformation() then
exit(0);
Item.SetFilter("No.", ItemFilter);
if not Item.FindSet() then
exit(0);
try
LineNo := 0;
repeat
// Only process items user can view
if UserPermissions.CanViewItem(Item."No.") then begin
LineNo += 10000;
// Calculate profit for this item
ItemLedgerEntry.SetRange("Item No.", Item."No.");
ItemLedgerEntry.CalcSums("Sales Amount (Actual)", "Cost Amount (Actual)");
// Store in temporary buffer with limited scope
TempProfitBuffer.Init();
TempProfitBuffer.Number := LineNo;
TempProfitBuffer.Insert();
TotalProfit += (ItemLedgerEntry."Sales Amount (Actual)" - ItemLedgerEntry."Cost Amount (Actual)");
end;
until Item.Next() = 0;
finally
// Cleanup temporary data
TempProfitBuffer.DeleteAll();
end;
exit(TotalProfit);
end;
```
## Bad Examples
### Temporary Table Without Permission Validation
```al
procedure ProcessAllCustomerData(): Boolean
var
Customer: Record Customer;
TempCustomerBuffer: Record Customer temporary;
begin
// BAD: No permission validation before accessing sensitive data
Customer.FindSet();
repeat
TempCustomerBuffer := Customer;
TempCustomerBuffer.Insert();
until Customer.Next() = 0;
// BAD: Processing all customer data regardless of user permissions
ProcessCustomerBuffer(TempCustomerBuffer);
// BAD: No cleanup of sensitive temporary data
exit(true);
end;
```
### No Cleanup in Error Scenarios
```al
procedure RiskyTemporaryProcessing(CustomerNo: Code[20]): Boolean
var
Customer: Record Customer;
TempSensitiveData: Record Customer temporary;
begin
if Customer.Get(CustomerNo) then begin
TempSensitiveData := Customer;
TempSensitiveData.Insert();
// BAD: If this processing fails, temporary data is not cleaned up
if ProcessSensitiveData(TempSensitiveData) then begin
TempSensitiveData.DeleteAll();
exit(true);
end;
// BAD: Error path doesn't clean up temporary data
exit(false);
end;
end;
```
### Cross-Session Data Leakage Risk
```al
codeunit 50100 "Unsafe Temporary Processing"
{
// BAD: Global temporary table could leak data between sessions
var
GlobalTempBuffer: Record Customer temporary;
procedure ProcessCustomer(CustomerNo: Code[20]): Boolean
begin
// BAD: Global temporary data persists across procedure calls
GlobalTempBuffer."No." := CustomerNo;
GlobalTempBuffer.Insert();
// BAD: No session isolation or cleanup
exit(ProcessGlobalBuffer());
end;
}
```
## Best Practices
### Comprehensive Error Handling
```al
procedure SecureTemporaryProcessing(RecordID: RecordID): Boolean
var
RecordRef: RecordRef;
TempProcessingBuffer: Record "Integer" temporary;
ProcessingSuccess: Boolean;
ErrorOccurred: Boolean;
begin
// Early permission validation
if not UserPermissions.CanAccessRecord(RecordID) then
exit(false);
if not RecordRef.Get(RecordID) then
exit(false);
try
// Initialize temporary processing with minimal data
TempProcessingBuffer.Init();
TempProcessingBuffer.Number := RecordID.RecordId;
TempProcessingBuffer.Insert();
// Secure processing with error handling
ProcessingSuccess := ProcessRecordSecurely(RecordRef, TempProcessingBuffer);
except
ErrorOccurred := true;
end;
// Always cleanup, even in error scenarios
TempProcessingBuffer.DeleteAll();
if ErrorOccurred then
exit(false);
exit(ProcessingSuccess);
end;
```
### Session-Isolated Temporary Processing
```al
procedure SessionSafeProcessing(UserID: Code[50]): Boolean
var
TempUserBuffer: Record User temporary;
SessionGuid: Guid;
begin
// Create session-specific identifier
SessionGuid := CreateGuid();
// Validate current user permissions
if (UserID <> UserId) and (not UserPermissions.CanViewOtherUsers()) then
exit(false);
try
// Session-isolated temporary data
TempUserBuffer.Init();
TempUserBuffer."User Security ID" := SessionGuid;
TempUserBuffer."User Name" := UserID;
TempUserBuffer.Insert();
// Process with session context
exit(ProcessUserInSession(TempUserBuffer, SessionGuid));
finally
// Session cleanup
TempUserBuffer.DeleteAll();
end;
end;
```
### Audit-Compliant Temporary Processing
```al
procedure AuditSafeProcessing(DocumentNo: Code[20]): Boolean
var
SalesHeader: Record "Sales Header";
TempAuditBuffer: Record "Change Log Entry" temporary;
AuditEntryNo: Integer;
begin
// Permission and audit validation
if not UserPermissions.CanViewDocument(DocumentNo) then begin
LogSecurityEvent('Unauthorized access attempt', DocumentNo);
exit(false);
end;
if not SalesHeader.Get(SalesHeader."Document Type"::Order, DocumentNo) then
exit(false);
try
// Create audit trail for temporary data access
AuditEntryNo := CreateAuditEntry('Temporary Processing', DocumentNo);
// Limited temporary data with audit context
TempAuditBuffer.Init();
TempAuditBuffer."Entry No." := AuditEntryNo;
TempAuditBuffer."Table No." := Database::"Sales Header";
TempAuditBuffer."Primary Key Field 1 Value" := DocumentNo;
TempAuditBuffer.Insert();
// Audited processing
exit(ProcessWithAuditTrail(SalesHeader, TempAuditBuffer));
finally
// Cleanup with audit completion
TempAuditBuffer.DeleteAll();
CompleteAuditEntry(AuditEntryNo);
end;
end;
```
### Memory-Efficient Cleanup
```al
procedure LargeDatasetProcessing(FilterText: Text): Boolean
var
SourceRecord: Record "Item Ledger Entry";
TempProcessingBuffer: Record "Item Ledger Entry" temporary;
BatchSize: Integer;
ProcessedCount: Integer;
begin
BatchSize := 1000; // Process in batches to manage memory
SourceRecord.SetFilter("Item No.", FilterText);
if not SourceRecord.FindSet() then
exit(false);
repeat
try
// Process in batches with cleanup
ProcessedCount := 0;
repeat
TempProcessingBuffer := SourceRecord;
TempProcessingBuffer.Insert();
ProcessedCount += 1;
// Batch boundary cleanup
if ProcessedCount >= BatchSize then begin
ProcessBatch(TempProcessingBuffer);
TempProcessingBuffer.DeleteAll();
ProcessedCount := 0;
end;
until (SourceRecord.Next() = 0) or (ProcessedCount >= BatchSize);
// Process remaining records
if ProcessedCount > 0 then
ProcessBatch(TempProcessingBuffer);
finally
// Always cleanup batch data
TempProcessingBuffer.DeleteAll();
end;
until SourceRecord.Next() = 0;
exit(true);
end;
```
## Security Guidelines
1. **Validate permissions before populating temporary tables with sensitive data**
2. **Use try-finally blocks to ensure cleanup even in error scenarios**
3. **Apply session isolation to prevent cross-user data leakage**
4. **Implement audit trails for temporary access to sensitive information**
5. **Process large datasets in batches with regular cleanup**
6. **Document temporary table security requirements and cleanup procedures**