Fast 4gl systems, inc.
Corporate Technical Documentation
Progress Coding Standards Manual
Corporate technical documentation
Progress Coding Standards Manual
ã Fast 4GL Systems, Inc.
http://www.fast4gl.com/
220 White Horse Pike • Audubon, NJ 08106
Phone +1 877 327-8445 • Fax +1 609 547-7876
By: Christopher Schreiber
Table of Contents
Document Overview *
This Manual is intended to
This Manual is not intended to
*Database Design Considerations *
Definition
*Tables (or Files)
*Fields (or Columns)
*Indexes
*Referential Integrity
*Naming Conventions *
General Naming Conventions
*Procedures
*Variables
*Widgets
*Frames
*Buffers
*Streams
*Blocks
*Workfiles and Temp Tables
*External References
*Procedure Structure *
General Outline
*Program Header
*Variables
*Other Defines
*Forms
*Startup Routines
*Procedure Style *
Indentation
*Statements Per Line
*Capitalization
*Block Labels
*Punctuation
*Comments
*Dictionary Formats and Labels
*Include Files
*If / Then / Else
*Case Statement
*Program Exits
*Listing Field Names
*Listing Format Phrase Options
*Listing Frame Phrase Options
*Keywords to Not Use
*Keywords to Avoid
*Abbreviations
*Miscellaneous
*Frames
*Transactions
*Error Handling
*Record Reading
*Record Locking
*Portability *
Terminals
*Names
*Workfiles
*Operating Systems
*Color
*Operating System Specific
*Performance *
Coding Techniques
*Multi-Database *
Using Multiple Databases
*Understanding Transactions *
Transaction Scope Overview
*Default Block Scope
*Controlling Transactions
*Sub-transactions
*Record Locks and Scope *
Record Locks
*Record Scope
*Using XREF and Listings *
Listings
*XREF
*These are the reasons why this Progress Coding Standards Manual was created.
In order to ease Progress program readability and future maintenance, it is important to identify and encourage a consistent coding style. This document does not attempt to address every possible issue because our experience has shown us that the longer the standard is the less compliance there is. We don't expect everyone to agree with the standards outlined here. However, these are the standards and guidelines that come from years of experience and should not be deviated from, except under special situations.
No document of this nature should ever be written in a vacuum. We would encourage you let us know of any omissions and weak spots that we can make the necessary changes and help others get off to a good start in their Progress applications.
It should be a straightforward matter to apply the standards set forth here to all new programs. The aim is to introduce an efficient style of coding that will improve readability, maintenance and performance.
This Manual is not intended to
This document does not suggest any standard or style for Smartobjects and GUI programming in general. It covers the development of 4GL code and data dictionary only.
Database Design Considerations
Proper Database Design and Naming conventions are probably the most important part of the application development process.
In order for Progress code to be easily readable and maintainable, extensive forethought must go into the design and naming conventions for the database itself.
Example
UPDATE qty price. /* user enters 5 and ? */
ASSIGN total = qty * price. /* total now equals ? */
Example
Format: N/G
Label: Net or Gross
Initial: N
The "N" in Initial might lead the uninitiated into believing that the default is going the be "Net" when actually it will default to a logical No or "G" for "Gross".
Example
INSERT customer.
This will check the syntax of all validation expressions in the file. Also remember that the dictionary validation only applies to data entry statements (UPDATE, SET and PROMPT-FOR).
Example
Index-A
Index-BIndex-A would be considered redundant in this example, unless if Index-A was unique and Index-B was not.
Referential integrity means ensuring that all foreign keys fields can be resolved to a valid entry in an associated table.
This implies that when these foreign key fields are populated a check must be made to ensure the valid entry exists. If an entry is deleted then a check should be made for entries in any table that may use its fields as foreign keys. If such relations exist then the deletion should be disallowed.
Typically referential integrity checks must be put in place when:
Referential integrity can carried out in three places, the data dictionary, database triggers (
CREATE, FIND, ASSIGN, WRITE and DELETE) and the application code itself.Use the
DELETE trigger to enforce deletion integrity and audit and the WRITE trigger for audit.Following basic Naming Conventions will greatly enhance code readability.
In order to ease Progress program readability and future maintenance, it is important to identify and encourage a consistent coding style. This document does not attempt to address every possible issue because our experience has shown us that the longer the standard is the less compliance there is.
We don't expect everyone to agree with the standards outlined here. However, these are the standards and guidelines that come from years of experience and should not be deviated from, except under special situations.
File Name Extensions
.p Procedure
.i Include File
.w Window
Variable Naming
DEFINE VARIABLE vDate AS DATE NO-UNDO.
DEFINE VARIABLE vItem_no LIKE Item.Item_no NO-UNDO.
DEFINE INPUT PARAMETER ipUpdate AS LOGICAL NO-UNDO.
DEFINE SHARED VARIABLE sGLCode AS CHARACTER NO-UNDO.
DEFINE GLOBAL SHARED VARIABLE gsCompany AS INTEGER NO-UNDO.
Widget Naming
br Browse
qu Query
cb Combo Box
ed Editor Box
tg Toggle Box
rs Radio Set
sl Selection List
Widget Examples
DEFINE QUERY quOffice_Header FOR Office_Header.
DEFINE BROWSE brOffice_Header QUERY quOffice_Header
DISPLAY Office_Header.Office_Number
Office_Header.Office_Type
WITH TITLE "Select a Office" 8 DOWN.
DEFINE VARIABLE cbHist_Date AS DATE FORMAT "99/99/9999"
VIEW-AS COMBO-BOX
LIST-ITEMS 04/07/1066, 20/01/1966/27/10/1991.
Frame Naming
DEFINE FRAME fMain.
Buffer Naming
DEFINE BUFFER bCustomer FOR Customer.
Stream Naming
DEFINE STREAM sReport.
Block Labels
MAIN-LOOP:
REPEAT:
DISPLAY Customer.Name.
END. /* MAIN-LOOP: */
Block Label Names
MAIN-LOOP:
REPEAT:
/* user interaction */
TRANS-LOOP:
/* update transaction */
END. /* TRANS-LOOP: */
END. /* MAIN-LOOP: */
Block Label Comments
MAIN-LOOP:
DO:
/* block code here */
END. /* MAIN-LOOP: */
Workfiles and Temp Tables
Workfile and Temp Table Naming
DEFINE TEMP-TABLE ttCustomer LIKE Customer.
DEFINE WORKFILE wfCustomer LIKE Customer.
Index Naming
Index Name: cust-ord
Index containing cust-num and ord-no fields.
Following these Procedure Structure guidelines will allow for being able to track the program’s flow more easily.
The general outline or flow of a program should be as follows:
General Outline
The header of each program shall be organized as follows:
Program Header
Variable should be arranged in the following order:
Variables
System wide include file variables.
Application specific include file variables.
new global shared
new shared
shared
local
Variables should be listed alphabetically to make locating them easier.
Variable Alignment
DEFINE VARIABLE vName AS CHARACTER FORMAT "x(10)"
LABEL "Name" NO-UNDO.
DEFINE VARIABLE vKey AS INTEGER FORMAT "zz9" NO-UNDO.
Other definitions will be described in the following order:
Other Defines
Forms definitions will be listed in the following order:
Forms
Procedure Style is unique to each programmer. However, following these suggestions will allow the programs that you write to be more easily maintained.
Indentation Example
MAIN-BLOCK:
FOR EACH Customer:
DISPLAY Customer.
ORDER-BLOCK:
FOR EACH Order WHERE
Order.Cust_Num = Customer.Cust_Num
NO-LOCK:
DISPLAY Order.
END. /* ORDER-BLOCK: */
END. /* MAIN-BLOCK: */
Comments Example
/* Update Customer Records */
CUST-UPDATE-BLOCK:
REPEAT:
PROMPT-FOR Customer.Cust_Num.
FIND Customer USING Customer.Cust_Num.
UPDATE Customer WITH 1 COLUMN.
END. /* CUST-UPDATE-BLOCK: */
Multi-Line Comments Example
/* This line is the first comments line
** and this is the second line */
Single-Line Comments Example
/* This is a one line comment */
No need to Comment the END
IF true-condition THEN
DO:
MESSAGE " True ".
END.
Column-Label Example
Last
Date Last Date
Invoiced Last Date Invoiced Invoiced
-------- ------------------ ---------
12/31/99 12/31/99 12/31/99
In this example, Last!Date!Invoiced may be the best for the convservation of horizontial space at the cost of one line of vertical space.
Simple Include Files
{std-var.i}
Simple Parameter Include Files
{std-var.i &var-type = "NEW"}
Multiple Parameter Include Files
{ lookup.i
&file-name = Customer
&frame-attr = "NO-BOX 2 COLUMNS"
&key = Customer.Cust_Num
}
Do NOT Use Comments Within an Include File Reference
{ lookup.i /* This may blow up! */
¶m = "value"
}
Mixed AND / OR Conditions Example
IF true-cond1 AND
true-cond2 AND
(true-cond3 OR
true-cond4) THEN
Example of when to use DO: END. with an IF Statement
IF true-condition THEN {include.i}.
If this include file originally had only one statement and more were added later, the new statement would not be conditionally executed. Instead you should use:
IF true-condition THEN
DO:
{include.i}
END.
Example of when not to use DO: END. with an IF Statement
IF selection = "A" THEN RUN add.p.
ELSE
IF selection = "C" THEN RUN change.p.
ELSE
IF selection = "D" THEN RUN delete.p.
Compound AND / OR Example
IF condition AND
vBegDate > Order.Order_Date AND
vEndDate < Order.Order_Date THEN
DO:
/* Statements */
END.
Multiple IF / THEN / ELSE Example
IF condition AND
vBegDate > Order.Order_Date AND
vEndDate < Order.Order_Date THEN
DO:
/* Statements */
END.
ELSE
IF NOT (condition) THEN
DO:
/* Statements */
END.
Null THEN / ELSE Example
IF condition THEN . ELSE .
This code does compile and illustrates what a "null" THEN or ELSE is.
Example of the CASE statement
CASE selection
WHEN "A" THEN RUN add.p.
WHEN "C" THEN RUN change.p.
WHEN "D" THEN RUN delete.p.
OTHERWISE MESSAGE "Incorrect Selection.".
END CASE.
Example of Program Exits
MAIN:
REPEAT:
PROMPT-FOR Customer.Cust_Num.
FIND Customer USING Customer.Cust_Num NO-ERROR.
IF NOT(AVAILABLE(Customer)) THEN LEAVE MAIN.
UPDATE Customer WITH 2 COLUMNS.
END. /* MAIN */
RETURN.
Example of Fields on one line
DISPLAY Customer.Cust_Num Customer.Name.
Example of Fields on multiple lines
UPDATE
Customer.Cust_Num
Customer.Name
Customer.Address
Customer.Phone
WITH FRAME fCustFrame.
Example of Using an Array
DISPLAY array[1 FOR 5].
This method is easier to read and maintain than listing out each individual array element.
Example of Format Phrase Options
UPDATE
Customer.Name
HELP "Enter Customer Name"
VALIDATE (Customer.Name NE "","Customer Name Must Be Entered")
Customer.Contact
AT 20
HELP "Enter Customer Contact".
UPDATE
vStartDate AT 10 HELP "Enter Start Date"
vEndDate AT 50 HELP "Enter Ending Date"
UPDATE
Customer.Name HELP "Enter Customer Name"
VALIDATE (Customer.Name NE "","Error")
Customer.Contact AT 20
HELP "Enter Customer Contact".
Example of Frame Phrase Options to Avoid
UPDATE
Customer.Name
Customer.Phone
WITH RETAIN 1 8 DOWN ROW 2 2 COLUMNS.
Example of Frame Phrase Options Preferred
UPDATE
Customer.Name
Customer.Phone
WITH 2 COLUMNS 8 DOWN ROW 2 RETAIN 1.
Keywords to NOT Abbreviate
ACCUMULATE
AVAILABLE
AVERAGE
ALL COLORS (BLACK, BLUE, CYAN, ETC.)
BLINK
DATE
LIGHT
NO-VALIDATE
PROMPT-FOR
RECID
SUB-AVERAGE
TRANSACTION
UNDERLINE
Keywords that can be Abbreviated
KEYWORD ALLOWED
Example of Coding Keylabels
MESSAGE "Enter Data and Press " + KBLABEL("GO").
Example of Long Messages
MESSAGE " This is a very long message that will not fit on one"
"line in the code. ".
Example of Titles
FORM
Customer.Cust_Num
Customer.Name
WITH TITLE " Customer Information " CENTERED.
Example of Operators
Preferred Instead Of
Example of an Acceptable Check
TEST-BLOCK:
DO WHILE TRUE:
/* statements */
IF vCounter <= 0 THEN
LEAVE TEST-BLOCK.
END. /* TEST-BLOCK */
Example of an Unacceptable Check
TEST-BLOCK:
DO WHILE TRUE:
/* statements */
IF vCounter = 0 THEN
LEAVE TEST-BLOCK.
END. /* TEST-BLOCK */
Example of a Form Statement
FORM
Customer.Cust_Num
Customer.Name
Customer.Phone
WITH CENTERED ROW 2.
Example of an Unacceptable Form Statement
FORM Customer.Cust_Num Customer.Name Customer.Phone WITH CENTERED.
Frame Widths
10 CPI: 75/125
12 CPI: 90/140
etc.
Example of Acceptable Record Reading
ORDER-BLOCK:
FOR EACH Order WHERE
Order.Cust_Num = "1000"
NO-LOCK:
DISPLAY
Order.Cust_Num
Order.Ord_Num.
ITEM-BLOCK:
FOR EACH Order_Line
OF Order
NO-LOCK:
DISPLAY
Order_Line.Order_Line_Num
Order_Line.Item_Num.
END. /* ITEM-BLOCK: */
END. /* ORDER-BLOCK: */
Example of Unacceptable Record Reading
ORDER-BLOCK:
FOR EACH Order WHERE
Order.Cust_Num = "1000"
NO-LOCK:
DISPLAY
Order.Cust_Num
Order.Ord_Num.
ITEM-BLOCK:
FOR EACH Order_Line
OF Order WHERE
Order_Line.Order_Qty > 0
NO-LOCK:
DISPLAY
Order_Line.Order_Line_Num
Order_Line.Item_Num.
END. /* ITEM-BLOCK: */
END. /* ORDER-BLOCK: */
Example of a Where Clause
FIND Customer WHERE Customer.Cust_Num = 10.
Instead of:
FIND Customer WHERE 10 = Customer.Cust_Num.
Example of a Compound FOR EACH Statement
FOR EACH Customer NO-LOCK,
EACH Order NO-LOCK WHERE
Order.Cust_Num = Customer.Cust_Num,
EACH Order_Line NO-LOCK WHERE
Order_Line.Order_Num = Order.Ord_Num:
END.
Example of Multiple Condition WHERE Clause
Example 1:
FOR EACH Customer WHERE
Customer.Cust_Num > 10 AND
Customer.Cust_Num < 100 AND
Customer.State = "MA"
NO-LOCK:
END.
Example 2:
FOR EACH Customer NO-LOCK WHERE
Customer.Cust_Num > 10 AND
Customer.Cust_Num < 100 AND
Customer.State = "MA",
EACH Order NO-LOCK WHERE
Order.Cust_Num = Customer.Cust_Num AND
Order.Order_Num > 100
BREAK
BY Order.Order_Date
BY Customer.Name:
END.
Order of Comparisons in a WHERE Clause
Do Not Use a Join for a one-to-one Relationship
FOR EACH Order_Line:
DISPLAY Order_Line.
FOR EACH Item OF Order_Line:
DISPLAY Item.
END.
END.
The use of FOR EACH when retrieving the item misleads one into thinking that the order-line: item relationship is one-to-many rather than one-to-one.
Since Progress programs can be deployed on many different platforms, it is important to keep some of these Portability issues in mind when programming.
Screen Checking Functions
SCREEN-LINES
FRAME-DOWN
FRAME-ROW
FRAME-LINE
In the Frame Phrase:
expression DOWN
ROW expression
DOS Limitations
File Names
Procedure Names
Include File Names
Checking for the Operating System Example
CASE OPSYS
WHEN "unix" THEN UNIX.
WHEN "msdos" THEN DOS.
WHEN "vms" THEN VMS.
WHEN "btos" THEN BTOS.
WHEN "os2" THEN OS2.
WHEN "os400" THEN OS400.
WHEN "nt" THEN NT.
OTHERWISE
MESSAGE "Unknown Operating System:" OPSYS.
END CASE.
Although Performance is often not the first priority when programming, it is important to keep some of these basic concepts in mind.
Example of Displaying Constant Data
FORM
"This is a constant string"
WITH FRAME fTest.
VIEW FRAME fTest.
/* This is more efficient then: */
DISPLAY "This is a constant string".
Example of the Assign Statement
ASSIGN
vVar1 = vVar2
vVar3 = vVar4.
/* This is more efficient, and produces smaller R-code then
** the following: */
vVar1 = vVar2.
vVar3 = vVar4.
When is use NO-UNDO Variables
Examples of CAN-DO and LOOKUP
CAN-DO("PA,NJ,DE",vState) /* This is the most effiecent */
LOOKUP(vState,"PA,NJ,DE") <> 0
vState = "PA" OR vState = "NJ" OR vState = "DE" /* Do Not Use */
Example of Reducing Transactions
INPUT FROM datafile.
REPEAT TRANSACTION:
REPEAT vCounter = 1 TO 100:
CREATE Customer.
IMPORT Customer.
END.
END.
/* Is more efficient then: */
INPUT FROM datafile.
REPEAT:
CREATE Customer.
IMPORT Customer.
END.
Working in a Multiple Database environment can bring up some more issues to be concerned about.
Transactions, and how Progress handles them, are often difficult but yet an important concept for programmers to grasp.
The manner in which Progress handles transactions is one of the unique and powerful features of the 4GL. It is also an area that we tend to struggle with usually because we imagine that the subject is more complex than it is in reality.
This is an important subject, however, as with a little bad planning and careless programming it is possible to adversely and dangerous affect the behavior of your application from both a user interface point of view and a performance and "physical" database point of view.
Transaction Scope Overview
A transaction scope is logical grouping of changes being carried out to data that Progress guarantees will either be committed as a whole or
UNDOne as a whole. Reasons for undoing a transaction will range from a user deciding to abort the current set of changes (pressing F4 for example), to applications errors, to hardware failure.Each completed transaction is guaranteed to have been written permanently to the database whilst any incomplete transactions will not be written to the database of they are interrupted.
If you are unsure whether a transaction is currently active in your program use the
TRANSACTION function which will return true if a transaction is currently active.Progress code is structured in blocks. These take the form of procedures, triggers,
DO blocks (including IF/THEN DO), FOR EACH and REPEAT. Of these the procedures, triggers, FOR EACH, DO ON ERROR and REPEAT will start a transation if there is a find with an EXCLUSIVE-LOCK in their block.This meanes that should a change be made to the data or a record found with an exclusive lock within these blocks, a transaction will be scoped to the bounds of those blocks.
DO
blocks have weak transaction scoping. If a change to data is made to a DO block then the transaction is scoped to the next outer transaction block.Example #1
REPEAT: /* transaction starts here */
/* some code ....... */
FIND FIRST Order EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
/* Check locked status ....... */
UPDATE Order.
/* some code ....... */
END. /*transaction ends here */
Example #2
REPEAT: /* transaction DOES NOT start here */
/* some code ....... */
REPEAT: /* transaction starts here */
FIND FIRST Order EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
/* Check locked status ....... */
UPDATE Order.
END. /* transaction ends here */
/* some code ....... */
END. /* repeat */
Example #3
REPEAT: /* transaction starts here */
/* some code */
DO WHILE TRUE:
FIND FIRST Order EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
/* Check locked status ....... */
UPDATE Order.
END. /* DO WHILE */
/* some code ....... */
END. /*transaction ends here */
Example #4
/* start of a procedure transaction starts here */
FIND FIRST Customer EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
/* Check locked status ....... */
UPDATE Customer.
/* Some code */
REPEAT:
/* some code.... */
FIND NEXT Order OF Customer EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
/* Check locked status ....... */
UPDATE Order.
/* some code.... */
END.
/*end of the procedure transaction ends here */
It is possible to force any block to start a transaction by using the
TRANSACTION keyword (note this differs from the TRANSACTION function mentioned above).This method can be used to both increase and decrease the size of a transaction. Take Example 4 from above:
Example #4
/* start of a procedure transaction starts here */
DO TRANSACTION: /* Transaction starts here */
FIND FIRST Customer EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
/* Check locked status ....... */
UPDATE Customer.
/* Some code */
END. /* Transaction ends here */
/* some code */
REPEAT:
/* some code.... */
DO TRANSACTION: /* Another Transaction starts here */
FIND NEXT Order OF Customer EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
/* Check locked status ....... */
UPDATE Order.
/* some code.... */
END. /* Second Transaction ends here */
END.
/*end of the procedure */
The transactions in this example have now been scoped in a much more granular fashion.
You may want to increase the scope of a transaction boundary:
Example #5
DO TRANSACTION: /* Transaction starts here */
FOR EACH Order EXCLUSIVE-LOCK:
DELETE Order.
END.
END. /* Transaction End */
If a system crash occurs here none of the Order records will be deleted. Of course this scenario also holds locks on all the records until the end of the transaction.
With Progress it is only possible to have one active transaction at a time. If a transaction block becomes part of an existing transaction it will form a sub-transaction.
Sub-transactions can be used to UNDO parts of a transaction, however, the changes made in a sub-transaction do not get committed until the end of the main transaction.
One of the most common ways of starting a sub-transaction is by calling another procedure whilst a transaction is active.
Example #6
DO TRANSACTION ON ERROR UNDO, RETRY: /* transaction starts here */
IF RETRY THEN
MESSAGE "You've undone your customer changes as well."
IF NOT CAN-FIND Customer OF WHERE Customer-Name = "Test") THEN
RUN newcust.p.
FIND FIRST Order EXCLUSIVE-LOCK.
UPDATE Order WITH 2 COLUMN.
END.
/**** newcust.p *****
/* Program to add a new customer */
/* some code */
DO TRANSACTION ON-ERROR UNDO, LEAVE: /* start a sub transaction */
FIND FIRST Customer EXCLUSIVE-LOCK.
UPDATE Customer WITH 2 COLUMNS.
END. /* end a sub transaction */
The program newcust.p may not be expecting to be called within another transaction. Any update made in newcust.p can be rolled back in the calling routine if the user presses F4.
Another side effect of this scenario is that a lock will be maintained on the Customer record until the end of the main transaction in the calling procedure.
Understanding Record Locking and Record Scope are essential in order to produce an effective program design.
Record Locks
Progress has row-level locking. This means it locks individual records at a time (as opposed to pages or tables).
Two types of lock are supported, they are the
SHARE-LOCK and the EXCLUSIVE-LOCK. In addition, Progress can read records with NO-LOCK.A
SHARE-LOCK allows a record to be read by more than one user so long as that record is not being modified. A SHARE-LOCK gets upgraded to an EXCLUSIVE-LOCK if the modification on that record begins.An
EXCLUSIVE-LOCK prevents any other user modifying or placing a SHARE-LOCK on a record.NO-LOCK
allows the record to be read no matter what state another user has it in. It cannot however be modified. NO-LOCK allows dirty reads. In other words, the data that is being read may not have been committed to the database.The Progress default for a record is
SHARE-LOCK as this provides the user with the most protection. However, this option is not recommended unless absolutely required. In general, the developer should know what is going to happen to the record and read it with a NO-LOCK or an EXCLUSIVE-LOCK.This subject relates to how long information in a Progress record buffer remains active and by association how long Progress keeps a lock on the record.
Record locks are held for the longest of the transaction scope or record buffer scope. It is possible for a record buffer to have a wider scope that the transaction scope.
Locks are held on a record not a buffer (remember it is possible to have multiple buffers pointing to one record).
Example #7
FIND FIRST Customer.
/* Some code */
DO TRANSACTION: /* transaction start */
UPDATE Customer WITH 2 COLUMNS.
END. /* transaction end */
/* Some code */
PAUSE.
The record buffer spans the whole of this procedure, including the
PAUSE statement. The first FIND will instigate a SHARE-LOCK on the customer record.When the
DO TRANSACTION is encountered the SHARE-LOCK gets upgraded to an EXCLUSIVE-LOCK. At the end of the transaction the changes to the record are committed and the lock on the record is down graded to a SHARED-LOCK.Example #8
FIND FIRST Customer NO-LOCK.
/* Some code */
DO TRANSACTION: /* transaction starts */
FIND FIRST Customer EXCLUSIVE-LOCK.
UPDATE Customer WITH 2 COLUMNS.
END. /*SHARE-LOCK on Customer record is active */
Note that in the above example the lock status after the end of the transaction is still
SHARE-LOCK. In other words, the record can only be read not modified by another user in this state.Note that finding another record in the same table will release the
SHARE-LOCK.The
RELEASE statement can also be used to clear out a record buffer and release the locks on that record at the end of the transaction.Example #9
FIND FIRST Customer NO-LOCK.
/* Some code */
DO TRANSACTION: /* transaction start */
FIND FIRST Customer EXCLUSIVE-LOCK.
UPDATE Customer WITH 2 COLUMNS.
RELEASE Customer.
END. /* transaction end, No more locks held */
/* Some code */
PAUSE.
In this example the lock is released at the end of the transaction but the Customer buffer is no longer available.
RELEASE
is a bit of a cop-out indicating a failure to properly control your record and transaction scopes. In addition it behaves differently if it is used in a sub-procedure that has been called within an existing record scope. In this case the lock is not released but downgraded to a SHARE-LOCK at the end of the procedure.Finally, it is possible to narrow (or increase) the scope of your record by using the
DO FOR tablename construct. This strongly scopes the record to the block. No references to the buffer can be made outside the block without a subsequent FIND.Example #10
DO TRANSACTION: /* transaction start */
FIND FAST Customer EXCLUSIVE-LOCK.
UPDATE Customer WITH 2 COLUMNS.
RELEASE Customer.
END. /* transaction end, No more locks held */
IF AVAILABLE Customer THEN
MESSAGE "Customer record is available"
The free reference to Customer outside the transaction boundary effectively increase the scope of that record (but not the transaction).
The following example ensures that the record is not available outside the bounds of the transaction. In fact, it should not compile.
Example #11
DO FOR Customer TRANSACTION: /* transaction start *?
FIND FIRST Customer EXCLUSIVE-LOCK.
UPDATE Customer WITH 2 COLUMNS.
RELEASE Customer.
END. /* transaction end, No more locks held */
IF AVAILABLE Customer THEN
MESSAGE "Customer record is available".
Using the Progress XREF and Listings compiler options can give invaluable insight into a program and are often useful in debugging.
When a program has been completed, it should be compiled to produce two extra files. These are the
LISTING file and the XREF (cross-reference) file. These files contain vital information, which will help ensure that your programs behave as expected. Use these files to help with unit testing and code reviews.The listing file expands include files and numbers the program lines by block. At the end of the listing is a summary of each block within the procedure, the procedure itself being the main block.
The record buffers and transaction that are scoped to the block are shown, as are the frame scopes.
Use the
COMPILE filename.p LISTING filename.lst option form the PROGRESS editor of the Compile option in the Application Compiler to produce the output file.Example Listing
1 showlist.p
2
3 {} Line Blk
4 -- ---- ---
5 1 1 DO FOR Account:
6 2 2 DO TRANSACTION: /* start of a transaction */
7 3 2 FIND FIRST account EXCLUSIVE-LOCK
8 4 2 MESSAGE TRANSACTION. /* "yes" or "no" */
9 5 2 PAUSE.
10 6 1 END.
11 7 1
12 8 2 REPEAT: /* start of a transaction */
13 9 2 FIND FIRST policy_header EXCLUSIVE-LOCK.
14 10 2 LEAVE.
15 11 1 END.
16 12 1
17 13 1 /* shows the transaction is complete */
18 14 1 MESSAGE TRANSACTION.
19 15 1 PAUSE
20 16 END. /* DO FOR Account */
21
23 File Name Line Blk Type Tran Blk. Label
24 ---------- ---- ------------ ---- -------------------
25 showlist.p 0 Procedure No
26 showlist.p 1 Do No
27 Buffers: policypl.Account
28
29 showlist.p 2 Do Yes
30 showlist.p 8 Repeat Yes
31 Buffers:policypl.Office_Header
32
Each block start has been highlighted along with the lines of interest at the bottom of the report.
The above listing shows that the record buffer for Account is scoped to the outer
DO FOR Account: block. That there is no transaction scoped to this block. The inner DO TRANSACTION block has a transaction scoped to it.The
REPEAT block has both a record buffer (Office_Header) and a transaction scoped to it.The cross reference compile option writes cross reference information between procedures and PROGRESS objects, including procedures, include files, tables, fields indices, variables frames and character strings.
Developers must use this facility to check the efficiency of their queries. Whenever a query is used, a
SEARCH entry will be made in the XREF file. This is followed by the table name and the name of the index used to resolve the query. If the query is unbracketed (i.e. there are no criteria given to narrow down the search) or PROGRESS is unable to use an index to resolve the query then the phrase WHOLE-INDEX appears after the index name.In the latter instance, the index name is the primary index, which only gives the order in which the records are returned. The rest of the query will be resolved by searching through the whole table. This scenario should be avoided.
Example Xref Program
FIND FIRST Account NO-LOCK. /* unbracketed search - no conditions */
/* this next one is bracketed by a non-indexed field */
FIND FIRST Office_Header.
WHERE Office_Header.Effective_Date = TODAY NO-LOCK
/* this one is bracketed by a field indexed using a 2nd index */
FIND FIRST Office_Header.
WHERE Office_Header.Account_No = Account.Account_No NO-LOCK.
This produces a number of lines, for index usage look for lines with
SEARCH in them. As these XREF lines show:Example Xref Output
Showref.p showxref.p 1 COMPILE showxref.p
Showref.p showxref.p 1 SEARCH Account main_key WHOLE-INDEX
Showref.p showxref.p 3 SEARCH Office_Header main_key WHOLE-INDEX
Showref.p showxref.p 6 SEARCH Office_Header account
The second line relates to the unbracketed
FIND statement, this is a valid scenario.The third line shows a bracketed search (using Effective_Date) that cannot be resolved using an index. The primary index name (main_key) indicates the order in which the records will be returned and the
WHOLE-INDEX phrases indicates that the whole table will have to be searched to resolve this query. This should be considered a bug as it will cause severe performance problems.The final highlighted line shows a bracketed query that has been resolved using an index (account). This index was inspected and looks suitable as Account_no (the bracketing field) is also the first component in that index.