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 *

Document Overview

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.

This Manual is intended to

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.

Definition

Tables (or Files)

Fields (or Columns)

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).

Indexes

Example

Index-A Index-B
Field-A Field-A
Field-B Field-B
Field-C

Index-A would be considered redundant in this example, unless if Index-A was unique and Index-B was not.

Referential Integrity

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.

Naming Conventions

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.

General Naming Conventions

Procedures

File Name Extensions

.p Procedure
.i Include File
.w Window

Variables

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.

Widgets

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.

Frames

Frame Naming

DEFINE FRAME fMain.

Buffers

Buffer Naming

DEFINE BUFFER bCustomer FOR Customer.

Streams

Stream Naming

DEFINE STREAM sReport.

Blocks

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.

External References

Procedure Structure

Following these Procedure Structure guidelines will allow for being able to track the program’s flow more easily.

General Outline

The general outline or flow of a program should be as follows:

General Outline

  1. Header
  2. Variables
  3. Other Definitions
  4. Forms
  5. Procedure Body
  6. Exit Point

Program Header

The header of each program shall be organized as follows:

Program Header

  1. Program Name
  2. Application Name / Module
  3. Program Location / Directory
  4. Copyright notice
  5. Modification History / Revisions / Initials / Date
  6. Reasons for each change
  7. Include file list
  8. Files read (Input)
  9. Files written (Output)
  10. Purpose and general description of program

Variables

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 Defines

Other definitions will be described in the following order:

Other Defines

  1. new global shared stream
  2. new shared stream
  3. shared stream
  4. stream
  5. new shared buffer
  6. shared buffer
  7. buffer
  8. new shared workfile
  9. shared workfile
  10. workfile
  11. new shared frame
  12. shared frame
  13. input parameter
  14. output parameter
  15. input-output parameter

Forms

Forms definitions will be listed in the following order:

Forms

  1. New shared frame include file forms
  2. Shared frame include file forms
  3. Include file forms
  4. Local forms

Startup Routines

Procedure Style

Procedure Style is unique to each programmer. However, following these suggestions will allow the programs that you write to be more easily maintained.

Indentation

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: */

Statements Per Line

Capitalization

Block Labels

Punctuation

Comments

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.

Dictionary Formats and Labels

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.

Include Files

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! */
&param = "value"
}

If / Then / Else

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.

Case Statement

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.

Program Exits

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.

Listing Field Names

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.

Listing Format Phrase Options

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".

Listing Frame Phrase Options

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 Use

Keywords to Avoid

Abbreviations

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
CHARACTER CHAR
DECIMAL DEC
DESCENDING DESCEND
INTEGER INT
LOGICAL LOG

Miscellaneous

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
a <> b a NE b
a <= b a LE b
a >= b a GE b
a > b a GT b
a < b a LT b
a = b a EQ b

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.

Frames

Frame Widths

10 CPI: 75/125
12 CPI: 90/140
etc.

Transactions

Error Handling

Record Reading

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

  1. Fields of the index being used by the FIND/FOR EACH statement. The comparisons should be in the same order as the fields occur in the index.
  2. Fields from indexes not being used by the FIND/FOR EACH.
  3. Non-indexed fields.
  4. Variables.
  5. Expressions.

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.

Record Locking

Portability

Since Progress programs can be deployed on many different platforms, it is important to keep some of these Portability issues in mind when programming.

Terminals

Screen Checking Functions

SCREEN-LINES
FRAME-DOWN
FRAME-ROW
FRAME-LINE

In the Frame Phrase:

expression DOWN
ROW expression

Names

DOS Limitations

File Names
Procedure Names
Include File Names

Workfiles

Operating Systems

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.

Color

Operating System Specific

Performance

Although Performance is often not the first priority when programming, it is important to keep some of these basic concepts in mind.

Coding Techniques

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

  1. The variable represents a constant that will never change.
  2. The variable is always initialized before it is used.
  3. UNDO processing serves no useful purpose.
  4. The variable is changed in a loop that an error may not occur in.

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.

Multi-Database

Working in a Multiple Database environment can bring up some more issues to be concerned about.

Using Multiple Databases

Understanding Transactions

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.

Default Block Scope

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 */

Controlling Transactions

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.

Sub-transactions

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.

Record Locks and Scope

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.

Record Scope

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 XREF and Listings

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.

Listings

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.

XREF

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.