Note that it handles and reports on both packed and zoned numerics. There are a lot of comments so hopefully it will be understandable even without the accompanying article.
Code:
**Free // This code is provided only as a teaching example of a concept // It is not complete and should not be used in production code ctl-opt Option(*NoDebugIO : *SrcStmt ) DftActgrp(*No); ctl-opt CCSID(*GRAPH:*JOBRUN); // FieldData is the oufile from: // DSPFFD FILE(PRODUCT) OUTPUT(*OUTFILE) OUTFILE(FIELDDATA) // Normally that command would be built in to this program via QCMDEXC // or system() but we're "cheating" here to keep the example short. dcl-f FieldData; // These are the DS images of the before and after state of the records // They could be pointer mapped to trigger buffer images, or mapped from // journal records, or simply be before and after images from an // operator's screen. Basically anything where you need to know what changed // For this proof of concept example we will simply hard-code some values // in the "after" DS to ensure a difference between "before" and "after" dcl-ds before ExtName('PRODUCTEXT') Qualified Inz end-ds; dcl-ds after ExtName('PRODUCTEXT') Qualified Inz end-ds; // The following DS array contains the details of the fields in // the DS. Their name, data type, position, etc. // It provides the data needed for comparing before and after images // In this case it was built from DSPFFD data but it could have // been from (say) SYSCOLUMNS2 or the QUSLFLD API. dcl-ds fieldDetail Dim(99) Qualified; startPosn Like(WHIBO); // Offset within the record length like(WHFLDB); // Length in bytes name like(WHFLDI); dataType like(WHFLDT); decimals like(WHFLDP); // Number of decimal places End-Ds; dcl-s fieldBefore varchar(256); // This length assumes that no fields dcl-s fieldAfter varchar(256); // will exceed 256 bytes in length // Adjust size as needed // This structure is used to enable the bytes that represent a // numeric field's value to be processed as numeric in // this program. It basically re-maps bytes as packed or zoned numeric dcl-ds mapNumeric; zonedValue zoned(15); packedValue packed(29) Overlay(zonedValue); end-Ds; dcl-s decimalValue packed(29:5); dcl-s i int(5); dcl-s count int(5); dcl-s start int(5); // Data types for numeric fields - this code assumes that only // character/packed/zoned fields are present in the DS dcl-c PACKED 'P'; dcl-c ZONED 'S'; // This loop simply reads the file produced by DSPFFD and stores // the required data in the fieldData array for later reference // Normally this code would be in a Service Program and might use // SQL or API calls as noted earlier. read FieldData; DoU %Eof(FieldData); count += 1; fieldDetail(count).name = WHFLDI; fieldDetail(count).dataType = WHFLDT; fieldDetail(count).startPosn = WHIBO; fieldDetail(count).length = WHFLDB; fieldDetail(count).dataType = WHFLDT; fieldDetail(count).decimals = WHFLDP; read FieldData; EndDo; // To show the logic works we'll change some values in DS after. // The rest of the after DS & all of before DS has blanks/zeros after.STOH = 99; after.SELLPR = 123.45; after.MAXDISC = 9.999; // This loop will process each field in turn reporting on // any that have changed. For i = 1 to count; // Copy relevant portions of before/after images to a // temporary field to simplify subsequent logic fieldAfter = %subst(after:fieldDetail(i).startPosn:fieldDetail(i).length); fieldBefore = %subst(before:fieldDetail(i).startPosn:fieldDetail(i).length); if fieldAfter <> fieldBefore; // Field has changed so report it // Replace with any logic needed to report before & after values Dsply ('Field ' + fieldDetail(i).name + ' has changed'); // Determine where numeric field data starts in the // mapNumeric DS. start = %Len(mapNumeric) - fieldDetail(i).length + 1; // To handle packed and numeric values we convert them // and scale them so that we can display them. The logic // here is intended to give you ideas - it is not a definitive // solution for all data types and sizes. If fieldDetail(i).dataType = PACKED; packedValue = 0; // Initialize packed work field %subst(mapNumeric: start ) = fieldAfter; // Now scale the numeric value to account for decimals If fieldDetail(i).decimals > 0; decimalValue = packedValue / (10 ** fieldDetail(i).decimals); Else; decimalValue = packedValue; EndIf; Dsply ('New value ' + %Char(decimalValue) ) ; ElseIf fieldDetail(i).dataType = ZONED; zonedValue = 0; // Initialize all positions in zoned work field %subst(mapNumeric: start ) = fieldAfter; If fieldDetail(i).decimals > 0; decimalValue = zonedValue / (10 ** fieldDetail(i).decimals); Else; decimalValue =zonedValue; EndIf; Dsply ('New value ' + %Char(decimalValue) ) ; EndIf; EndIf; EndFor; *InLr = *On;
Leave a comment: