abl-v10-learning

star 4

"Reference guide for Progress OpenEdge ABL 10.1A (2005) — data types" variable declaration, procedures, functions, OOP basics, error handling, database access, transaction handling, control flow

paulpas By paulpas schedule Updated 6/4/2026

name: abl-v10-learning compatibility: opencode completeness: 95 content-types:

  • code
  • guidance
  • examples
  • diagrams description: '"Reference guide for Progress OpenEdge ABL 10.1A (2005) — data types" variable declaration, procedures, functions, OOP basics, error handling, database access, transaction handling, control flow' license: MIT maturity: stable metadata: domain: programming output-format: code related-skills: abl-v12-learning, algorithms role: reference scope: implementation triggers: abl, abl programming, abl v10, openedge, openedge 10, progress 4gl, progress abl archetypes:
    • educational anti_triggers:
    • brainstorming
    • vague ideation response_profile: verbosity: medium directive_strength: medium abstraction_level: tactical version: "1.0.0"

Progress OpenEdge ABL 10.1A — Reference Guide

Version: Progress OpenEdge 10.1A (December 2005)
Language: ABL — Advanced Business Language (formerly Progress 4GL)
Purpose: Comprehensive reference for developers working with OpenEdge v10 codebases.


TL;DR Checklist

  • ? is the universal unknown/null value across all data types
  • INT64 does NOT exist in v10.1A — use DECIMAL for large integers
  • NO-UNDO is recommended for most variables (faster, not rolled back on UNDO)
  • ERROR-STATUS is reset on every NO-ERROR statement — check immediately
  • No CATCH/THROW/FINALLY — only ON ERROR, NO-ERROR, RETURN ERROR
  • FOR EACH ... NO-LOCK is required for read-only database access
  • UNDO, LEAVE exits a transaction block with rollback
  • RELEASE commits and releases locks immediately
  • OOP exists but is limited — no ENUM, no PACKAGE-PRIVATE, no generic collections
  • JSON support does NOT exist — use XML or temp-table READ-XML/WRITE-XML

When to Use

Use this skill when:

  • Working with legacy OpenEdge 10.1A (December 2005) codebases
  • Maintaining or refactoring ABL procedures written for v10
  • Migrating v10 code to later versions (v11, v12)
  • Understanding ABL fundamentals: data types, variable declaration, procedures
  • Learning the v10 error handling model (pre-CATCH/THROW)
  • Understanding ABL's implicit transaction model
  • Working with ABL's OOP constructs as they existed in v10
  • Implementing database access patterns specific to v10

When NOT to Use

Avoid this skill for:

  • New development — use programming-abl-v12-learning for modern ABL
  • Questions about ENUM, VAR, compound operators (+=, -=) — added in v12.3+
  • JSON support — added in v11.x
  • Generic collections (List<T>, HashMap<K,V>) — added in v12.5+
  • Safe navigation operator (?:) — added in v12.5
  • CATCH/THROW/FINALLY — added in v10.2B

Core Reference Content

Data Types in v10.1A

Data Type v10 Range / Notes
CHARACTER Max 32,767 bytes. Default "". General-purpose string.
LONGCHAR Up to 2 GB − 1 byte. Code-page aware. For large text. Added v10.0B.
INTEGER 32-bit signed: −2,147,483,648 to +2,147,483,647. Default 0.
INT64 Not in v10.1A. Added in v10.1B. Use DECIMAL for large integers.
DECIMAL Up to 50 significant digits, up to 10 decimal places. Default 0.
LOGICAL TRUE/YES / FALSE/NO / Unknown (?). Default NO.
DATE 1/1/32768 BC to 12/31/32767 AD. Default ?.
DATETIME DATE + milliseconds from midnight. Local time. Default ?. Added v10.0B.
DATETIME-TZ DATETIME + UTC offset in minutes. Stored in UTC. Default ?. Added v10.0B.
RAW Binary data, ≤ 32K bytes.
MEMPTR In-memory binary buffer, up to 2 GB − 1 byte. Requires explicit allocation.
ROWID Unique opaque database record identifier. Preferred over RECID.
RECID Legacy 32-bit integer record ID. Use ROWID instead in new code.
HANDLE Pointer to any ABL object or UI widget. Default ?.
WIDGET-HANDLE Alias for HANDLE. Interchangeable in v10.
COM-HANDLE ActiveX/COM automation object reference. Windows only.
BLOB Binary Large Object. Database/temp-table fields only. Up to 1 GB. Added v10.0B.
CLOB Character Large Object. Database/temp-table fields only. Up to 1 GB. Added v10.0B.
CLASS Reference to a user-defined class instance. Default ?.
ENUM Not in v10. Added in v12. Use INTEGER constants or CHARACTER literals instead.

⚠️ Critical: INT64 does not exist in v10.1A. Use DECIMAL for values that exceed 2,147,483,647. Assigning a large value to INTEGER causes a runtime overflow error.

Data Type Examples

CHARACTER and LONGCHAR:

DEFINE VARIABLE cName     AS CHARACTER NO-UNDO.
DEFINE VARIABLE cFullName AS CHARACTER NO-UNDO INITIAL "Unknown".
DEFINE VARIABLE lcDocument AS LONGCHAR  NO-UNDO.  /* for large text content */

cName     = "John Smith".
lcDocument = "This is a very long text content that could be gigabytes...".

/* Case-insensitive by default */
IF cName = "john smith" THEN  /* evaluates to TRUE */
    MESSAGE "Names match (case-insensitive)" VIEW-AS ALERT-BOX.

/* CASE-SENSITIVE modifier */
DEFINE VARIABLE cSensitive AS CHARACTER CASE-SENSITIVE NO-UNDO.

INTEGER and DECIMAL:

DEFINE VARIABLE iCount   AS INTEGER NO-UNDO INITIAL 0.
DEFINE VARIABLE dAmount  AS DECIMAL NO-UNDO INITIAL 0.00.

/* v10 must use DECIMAL for large integers — no INT64 available */
DEFINE VARIABLE dBigNum AS DECIMAL NO-UNDO.
dBigNum = 5000000000.   /* exceeds INTEGER max — must be DECIMAL in v10 */

/* INTEGER arithmetic */
iCount = iCount + 1.

/* DECIMAL formatting */
dAmount = 1234567.89.
MESSAGE STRING(dAmount, "->>,>>>,>>9.99") VIEW-AS ALERT-BOX.
/* Displays: 1,234,567.89 */

LOGICAL:

DEFINE VARIABLE lActive  AS LOGICAL NO-UNDO INITIAL FALSE.
DEFINE VARIABLE lUnknown AS LOGICAL NO-UNDO.  /* default: NO */

lActive  = TRUE.     /* or YES */
lUnknown = ?.        /* unknown value */

/* All three states */
IF lActive = TRUE  THEN MESSAGE "Active".
IF lActive = NO    THEN MESSAGE "Inactive".
IF lActive = ?     THEN MESSAGE "Unknown state".

/* Shorthand: IF lActive checks for TRUE */
IF lActive THEN MESSAGE "Active flag is set".

DATE, DATETIME, DATETIME-TZ:

DEFINE VARIABLE dOrderDate  AS DATE        NO-UNDO.
DEFINE VARIABLE dtTimestamp AS DATETIME    NO-UNDO.
DEFINE VARIABLE dtzUtcTime  AS DATETIME-TZ NO-UNDO.

dOrderDate  = TODAY.
dtTimestamp = NOW.                          /* DATETIME-TZ — current time with tz offset */
dtzUtcTime  = NOW.

/* Construct a specific date */
dOrderDate = DATE(12, 31, 2005).            /* December 31, 2005 */

/* Extract components */
MESSAGE DAY(dOrderDate)   VIEW-AS ALERT-BOX.  /* 31 */
MESSAGE MONTH(dOrderDate) VIEW-AS ALERT-BOX.  /* 12 */
MESSAGE YEAR(dOrderDate)  VIEW-AS ALERT-BOX.  /* 2005 */

/* Date arithmetic — add integer days */
dOrderDate = dOrderDate + 30.   /* 30 days later */
dOrderDate = dOrderDate - 7.    /* 7 days earlier */

/* ISO date formatting */
MESSAGE ISO-DATE(dtTimestamp) VIEW-AS ALERT-BOX.

HANDLE and MEMPTR:

DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
DEFINE VARIABLE mBlock AS MEMPTR NO-UNDO.

/* HANDLE — points to an ABL object */
CREATE QUERY hQuery.
/* ... use hQuery ... */
DELETE OBJECT hQuery.

/* MEMPTR — raw binary buffer */
SET-SIZE(mBlock) = 1024.    /* allocate 1024 bytes */
PUT-BYTE(mBlock, 1) = 65.   /* write ASCII 'A' at byte 1 */
MESSAGE GET-BYTE(mBlock, 1) VIEW-AS ALERT-BOX.  /* 65 */
SET-SIZE(mBlock) = 0.       /* free the memory */

Variable Declaration

Full DEFINE VARIABLE Syntax:

DEFINE
  { [ [ NEW [ GLOBAL ] ] SHARED ] | [ PRIVATE | PROTECTED | PUBLIC ] }
  VARIABLE variable-name
  { AS datatype
  | AS [ CLASS ] { type-name }
  | LIKE field }
  [ NO-UNDO ]
  [ INITIAL { constant | { [ constant [ , constant ] ... ] } } ]
  [ FORMAT string ]
  [ EXTENT [ expression ] ]
  [ LABEL string ]
  [ DECIMALS n ]
  [ CASE-SENSITIVE ]

NO-UNDO Explained:

NO-UNDO tells the ABL Virtual Machine (AVM) not to roll back this variable's value when an UNDO statement executes. This is recommended for most variables:

  • Without NO-UNDO: The AVM tracks the "before image" of the variable for potential rollback. This adds overhead.
  • With NO-UNDO: The variable is excluded from transaction rollback. Faster and more predictable.
/* With NO-UNDO (recommended) — not rolled back on UNDO */
DEFINE VARIABLE iCounter AS INTEGER NO-UNDO.

/* Without NO-UNDO — value will be rolled back if block UNDOs */
DEFINE VARIABLE iRollback AS INTEGER.

/* Demonstration: iRollback gets rolled back, iCounter does not */
DO TRANSACTION:
    iCounter  = 100.
    iRollback = 100.
    UNDO, LEAVE.   /* only iRollback reverts to 0 */
END.
MESSAGE "Counter: "  iCounter  VIEW-AS ALERT-BOX.  /* 100 — not rolled back */
MESSAGE "Rollback: " iRollback VIEW-AS ALERT-BOX.  /* 0   — was rolled back */

INITIAL Value:

/* Simple initial values */
DEFINE VARIABLE cStatus   AS CHARACTER NO-UNDO INITIAL "ACTIVE".
DEFINE VARIABLE iRetry    AS INTEGER   NO-UNDO INITIAL 3.
DEFINE VARIABLE lEnabled  AS LOGICAL   NO-UNDO INITIAL TRUE.
DEFINE VARIABLE dRate     AS DECIMAL   NO-UNDO INITIAL 0.15.

/* Unknown (null) initial — the default for DATE, DATETIME, DATETIME-TZ */
DEFINE VARIABLE dStartDate AS DATE NO-UNDO INITIAL ?.

EXTENT — Arrays:

/* Fixed-size array */
DEFINE VARIABLE arrMonths  AS CHARACTER EXTENT 12 NO-UNDO.
DEFINE VARIABLE arrScores  AS INTEGER   EXTENT 5  NO-UNDO INITIAL [100, 95, 88, 72, 60].

/* Assign by index (1-based) */
arrMonths[1]  = "January".
arrMonths[2]  = "February".
arrMonths[12] = "December".

/* Read by index */
MESSAGE arrMonths[1] VIEW-AS ALERT-BOX.  /* January */

/* Indeterminate (dynamic) extent — size set later */
DEFINE VARIABLE arrDynamic AS CHARACTER EXTENT NO-UNDO.
EXTENT(arrDynamic) = 20.    /* set size at runtime */

/* EXTENT() function returns array size */
DEFINE VARIABLE iSize AS INTEGER NO-UNDO.
iSize = EXTENT(arrMonths).  /* 12 */

LIKE Field:

The LIKE option inherits FORMAT, LABEL, DECIMALS, and CASE-SENSITIVE from a database field:

/* Inherits all format attributes from the Customer.Name field */
DEFINE VARIABLE cCustName AS CHARACTER NO-UNDO LIKE Customer.Name.

/* Inherits format but overrides label */
DEFINE VARIABLE dBalance AS DECIMAL NO-UNDO LIKE Customer.Balance
    LABEL "Current Balance".

Procedures and Functions

Internal PROCEDURE Syntax:

Internal procedures are defined within the same .p file and called with RUN:

PROCEDURE validateCustomer:
    DEFINE INPUT  PARAMETER piCustNum  AS INTEGER   NO-UNDO.
    DEFINE OUTPUT PARAMETER pcError    AS CHARACTER NO-UNDO.

    /* Guard clause — check preconditions immediately */
    IF piCustNum <= 0 THEN DO:
        pcError = "Customer number must be positive".
        RETURN.
    END.

    FIND Customer WHERE Customer.CustNum = piCustNum NO-LOCK NO-ERROR.
    IF NOT AVAILABLE Customer THEN DO:
        pcError = "Customer " + STRING(piCustNum) + " not found".
        RETURN.
    END.
    IF ERROR-STATUS:ERROR THEN DO:
        pcError = "Database error: " + ERROR-STATUS:GET-MESSAGE(1).
        RETURN.
    END.

    pcError = "".  /* empty string means success */
END PROCEDURE.

User-Defined FUNCTION Syntax:

Functions must be declared (or forward-declared) before their first use:

/* Forward declaration — required when function body appears later in the file */
FUNCTION formatCurrency RETURNS CHARACTER (INPUT dAmount AS DECIMAL) FORWARD.

/* Function that calculates a discount tier */
FUNCTION getDiscountRate RETURNS DECIMAL (INPUT dBalance AS DECIMAL):
    IF dBalance >= 50000 THEN RETURN 0.20.
    IF dBalance >= 10000 THEN RETURN 0.10.
    IF dBalance >= 5000  THEN RETURN 0.05.
    RETURN 0.00.
END FUNCTION.

/* Implementation of forward-declared function */
FUNCTION formatCurrency RETURNS CHARACTER (INPUT dAmount AS DECIMAL):
    IF dAmount = ? THEN RETURN "N/A".
    RETURN "$" + STRING(dAmount, "->>,>>>,>>9.99").
END FUNCTION.

/* Usage */
DEFINE VARIABLE cFormatted AS CHARACTER NO-UNDO.
cFormatted = formatCurrency(INPUT 1234567.89).
MESSAGE cFormatted VIEW-AS ALERT-BOX.  /* $1,234,567.89 */

Parameter Modes:

Mode Direction Description
INPUT Caller → Procedure Read-only in called procedure
OUTPUT Procedure → Caller Caller provides uninitialized variable
INPUT-OUTPUT Both directions Caller provides value; procedure can modify it
RETURN Procedure → Caller Special return parameter
BUFFER Reference Passes a database buffer by reference
TABLE By value or ref Passes a temp-table
TABLE-HANDLE Handle Passes a dynamic temp-table handle
DATASET By value or ref Passes a ProDataSet
DATASET-HANDLE Handle Passes a dynamic ProDataSet handle
/* All standard parameter modes */
PROCEDURE demonstrateParams:
    DEFINE INPUT        PARAMETER pcInput   AS CHARACTER NO-UNDO.
    DEFINE OUTPUT       PARAMETER pcOutput  AS INTEGER   NO-UNDO.
    DEFINE INPUT-OUTPUT PARAMETER pdInOut   AS DECIMAL   NO-UNDO.
    DEFINE RETURN       PARAMETER prReturn  AS LOGICAL   NO-UNDO.

    /* Buffer parameter — passes actual DB buffer by reference */
    DEFINE PARAMETER BUFFER bCust FOR Customer.

    /* Temp-table parameters */
    DEFINE INPUT  PARAMETER TABLE FOR ttOrder.
    DEFINE OUTPUT PARAMETER TABLE FOR ttResult APPEND.
    DEFINE INPUT  PARAMETER TABLE-HANDLE httDynamic.

    /* Dataset parameters */
    DEFINE INPUT  PARAMETER DATASET FOR dsOrders.
    DEFINE OUTPUT PARAMETER DATASET-HANDLE hdsResult.

    pcOutput = LENGTH(pcInput).
    pdInOut  = pdInOut * 2.
    RETURN TRUE.
END PROCEDURE.

Object-Oriented Programming (v10)

OOP was formally introduced in v10. While powerful, it is more limited than the v12 model.

CLASS Statement Syntax:

/* Basic class structure */
CLASS class-type-name
    [ INHERITS super-type-name ]
    [ IMPLEMENTS interface1 [, interface2]... ]
    [ USE-WIDGET-POOL ]
    [ ABSTRACT | FINAL ]
    [ SERIALIZABLE ]
    :

    /* Data members, properties, constructors, methods, events, destructor */

END CLASS.

Full v10 class example:

CLASS acme.BankAccount:

    /* Private data members */
    DEFINE PRIVATE VARIABLE dBalance     AS DECIMAL   NO-UNDO INITIAL 0.
    DEFINE PRIVATE VARIABLE cAccountId   AS CHARACTER NO-UNDO.
    DEFINE PRIVATE VARIABLE cOwnerName   AS CHARACTER NO-UNDO.

    /* Public property */
    DEFINE PUBLIC PROPERTY Balance AS DECIMAL NO-UNDO
        GET():
            RETURN dBalance.
        END GET.
        PRIVATE SET.    /* read-only externally */

    DEFINE PUBLIC PROPERTY AccountId AS CHARACTER NO-UNDO
        GET.            /* auto-property: AVM manages backing store */
        PRIVATE SET.

    /* Constructor */
    CONSTRUCTOR PUBLIC BankAccount(INPUT pcOwner AS CHARACTER,
                                   INPUT pcAccId  AS CHARACTER):
        cOwnerName = pcOwner.
        cAccountId = pcAccId.
    END CONSTRUCTOR.

    /* Public method */
    METHOD PUBLIC LOGICAL Deposit(INPUT pdAmount AS DECIMAL):
        IF pdAmount <= 0 THEN
            RETURN FALSE.
        dBalance = dBalance + pdAmount.
        RETURN TRUE.
    END METHOD.

    METHOD PUBLIC LOGICAL Withdraw(INPUT pdAmount AS DECIMAL):
        IF pdAmount <= 0       THEN RETURN FALSE.
        IF pdAmount > dBalance THEN RETURN FALSE.
        dBalance = dBalance - pdAmount.
        RETURN TRUE.
    END METHOD.

    /* Destructor — called on DELETE OBJECT */
    DESTRUCTOR PUBLIC BankAccount():
        /* cleanup resources if needed */
    END DESTRUCTOR.

END CLASS.

Using the class:

DEFINE VARIABLE rAccount AS CLASS acme.BankAccount NO-UNDO.

rAccount = NEW acme.BankAccount("John Smith", "ACC-001").

IF rAccount:Deposit(1000.00) THEN
    MESSAGE "Deposit successful. Balance: " rAccount:Balance
            VIEW-AS ALERT-BOX.

IF NOT rAccount:Withdraw(5000.00) THEN
    MESSAGE "Insufficient funds" VIEW-AS ALERT-BOX.

/* Clean up — explicit in v10 */
DELETE OBJECT rAccount.

INTERFACE Syntax:

INTERFACE acme.IPayable:

    /* Property prototypes — no implementation */
    DEFINE PUBLIC PROPERTY AccountId AS CHARACTER NO-UNDO
        GET.
        SET.

    /* Method prototypes */
    METHOD PUBLIC LOGICAL Deposit(INPUT pdAmount AS DECIMAL).
    METHOD PUBLIC LOGICAL Withdraw(INPUT pdAmount AS DECIMAL).
    METHOD PUBLIC DECIMAL GetBalance().

END INTERFACE.

Rules for interfaces in v10:

  • All members are implicitly PUBLIC
  • No PRIVATE, STATIC, or ABSTRACT on interface members
  • No constructors, destructors, or storage
  • A class can implement multiple interfaces: IMPLEMENTS IFaceA, IFaceB
  • Interfaces can inherit from other interfaces: INTERFACE ISavings INHERITS IPayable:

Inheritance — INHERITS (Single):

ABL v10 supports single inheritance only. A class can inherit from one parent:

CLASS acme.SavingsAccount INHERITS acme.BankAccount:

    DEFINE PRIVATE VARIABLE dInterestRate AS DECIMAL NO-UNDO INITIAL 0.035.

    /* Override or extend constructor */
    CONSTRUCTOR PUBLIC SavingsAccount(INPUT pcOwner AS CHARACTER,
                                      INPUT pcAccId  AS CHARACTER,
                                      INPUT pdRate   AS DECIMAL):
        SUPER(pcOwner, pcAccId).     /* call parent constructor */
        dInterestRate = pdRate.
    END CONSTRUCTOR.

    /* Add new method not in parent */
    METHOD PUBLIC VOID ApplyInterest():
        DEFINE VARIABLE dInterest AS DECIMAL NO-UNDO.
        dInterest = THIS-OBJECT:Balance * dInterestRate.
        THIS-OBJECT:Deposit(dInterest).
    END METHOD.

END CLASS.

Access Modifiers in v10:

Modifier v10 Accessible From
PRIVATE Defining class only (all instances of the same class can access each other's privates)
PROTECTED Defining class + any subclass in the hierarchy
PUBLIC Anywhere with a valid object reference or type name
PACKAGE-PRIVATE Not in v10 — added in v12.2
PACKAGE-PROTECTED Not in v10 — added in v12.2

Default access by member type:

Member Type Default Access
Data members (variables) PRIVATE
Properties PUBLIC
Methods PUBLIC
Events PUBLIC
Constructors PUBLIC

Error Handling (v10 Model)

⚠️ Critical: v10.1A does not have CATCH, THROW, or FINALLY. Those structured exception handling constructs were introduced in v10.2B. This section covers the only error handling mechanisms available in v10.

The Three v10 Error Mechanisms:

  1. ON ERROR phrase on blocks
  2. NO-ERROR option on statements + ERROR-STATUS system handle
  3. RETURN ERROR to propagate errors to callers

ON ERROR Phrase on Blocks:

The ON ERROR phrase attaches to any block statement (DO, REPEAT, FOR EACH):

/* ON ERROR UNDO, LEAVE — undo block and exit it on error */
DO ON ERROR UNDO, LEAVE:
    FIND Customer WHERE Customer.CustNum = 1001 EXCLUSIVE-LOCK.
    Customer.Balance = Customer.Balance + 500.
    /* On error: undo the find/update and exit this DO block */
END.

/* ON ERROR UNDO, RETRY — undo and retry the current iteration */
REPEAT ON ERROR UNDO, RETRY:
    PROMPT-FOR Customer.CustNum.
    FIND Customer USING CustNum NO-ERROR.
    IF NOT AVAILABLE Customer THEN NEXT.
    DISPLAY Customer.Name Customer.Balance.
END.

/* ON ERROR UNDO, NEXT — undo and skip to next iteration */
FOR EACH Customer ON ERROR UNDO, NEXT:
    ASSIGN Customer.LastUpdated = TODAY NO-ERROR.
    IF ERROR-STATUS:ERROR THEN
        DISPLAY "Failed to update: " Customer.CustNum.
END.

/* ON ERROR UNDO, RETURN ERROR — propagate to caller */
DO TRANSACTION ON ERROR UNDO, RETURN ERROR:
    FIND Order WHERE Order.OrderNum = iNum EXCLUSIVE-LOCK.
    Order.Status = "CLOSED".
END.

NO-ERROR Option:

NO-ERROR suppresses the error condition and stores error information in ERROR-STATUS:

/* NO-ERROR on FIND */
FIND FIRST Customer WHERE Customer.CustNum = 9999 NO-LOCK NO-ERROR.
IF ERROR-STATUS:ERROR THEN DO:
    MESSAGE "Error finding customer: " ERROR-STATUS:GET-MESSAGE(1)
            VIEW-AS ALERT-BOX.
    RETURN.
END.
IF NOT AVAILABLE Customer THEN DO:
    MESSAGE "Customer not found" VIEW-AS ALERT-BOX.
    RETURN.
END.

/* NO-ERROR on ASSIGN */
ASSIGN Customer.Balance = dNewBalance NO-ERROR.
IF ERROR-STATUS:ERROR THEN DO:
    MESSAGE "Assignment error: " ERROR-STATUS:GET-MESSAGE(1)
            VIEW-AS ALERT-BOX.
    UNDO, RETURN ERROR.
END.

/* NO-ERROR on RUN */
RUN validateOrder (INPUT iOrderNum, OUTPUT cError) NO-ERROR.
IF ERROR-STATUS:ERROR THEN
    cError = ERROR-STATUS:GET-MESSAGE(1).

ERROR-STATUS System Handle:

ERROR-STATUS is a built-in system handle that contains information about the last error:

Attribute / Method Type Description
ERROR-STATUS:ERROR LOGICAL TRUE if the last NO-ERROR statement raised an error
ERROR-STATUS:NUM-MESSAGES INTEGER Count of error messages in the error stack
ERROR-STATUS:GET-MESSAGE(n) CHARACTER Error message text at index n (1-based)
ERROR-STATUS:GET-NUMBER(n) INTEGER Error number/code at index n (1-based)
/* Check all messages in the error stack */
DEFINE VARIABLE ix AS INTEGER NO-UNDO.

FIND Customer WHERE Customer.CustNum = 9999 NO-LOCK NO-ERROR.
IF ERROR-STATUS:ERROR THEN DO:
    DO ix = 1 TO ERROR-STATUS:NUM-MESSAGES:
        MESSAGE "Error " ix ": [" ERROR-STATUS:GET-NUMBER(ix) "] "
                ERROR-STATUS:GET-MESSAGE(ix)
                VIEW-AS ALERT-BOX.
    END.
END.

⚠️ WARNING: ERROR-STATUS is reset on every NO-ERROR statement. If you call two NO-ERROR statements in a row, only the second one's error info is preserved. Always check ERROR-STATUS immediately after the statement that could fail.

❌ BAD — ERROR-STATUS may be reset by the second NO-ERROR:

FIND Customer WHERE Customer.CustNum = 1 NO-LOCK NO-ERROR.
ASSIGN Customer.Balance = 500 NO-ERROR.
IF ERROR-STATUS:ERROR THEN  /* This reflects only the ASSIGN, not the FIND */
    MESSAGE "Error" VIEW-AS ALERT-BOX.

✅ GOOD — Check immediately after each NO-ERROR statement:

FIND Customer WHERE Customer.CustNum = 1 NO-LOCK NO-ERROR.
IF ERROR-STATUS:ERROR THEN DO:
    MESSAGE "FIND error: " ERROR-STATUS:GET-MESSAGE(1) VIEW-AS ALERT-BOX.
    RETURN.
END.
IF NOT AVAILABLE Customer THEN RETURN.

ASSIGN Customer.Balance = 500 NO-ERROR.
IF ERROR-STATUS:ERROR THEN DO:
    MESSAGE "ASSIGN error: " ERROR-STATUS:GET-MESSAGE(1) VIEW-AS ALERT-BOX.
    UNDO, RETURN ERROR.
END.

v10 Combined Error + Transaction Pattern:

The canonical v10 pattern for safe database updates:

/* Reliable v10 update pattern */
DEFINE VARIABLE lSuccess AS LOGICAL NO-UNDO.

lSuccess = FALSE.
DO TRANSACTION ON ERROR UNDO, LEAVE:
    FIND Customer WHERE Customer.CustNum = iCustNum
        EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
    IF ERROR-STATUS:ERROR THEN DO:
        MESSAGE "Record locked or error: " ERROR-STATUS:GET-MESSAGE(1)
                VIEW-AS ALERT-BOX.
        UNDO, LEAVE.
    END.
    IF NOT AVAILABLE Customer THEN DO:
        MESSAGE "Customer not found" VIEW-AS ALERT-BOX.
        UNDO, LEAVE.
    END.

    ASSIGN
        Customer.Balance    = Customer.Balance + dAdjustment
        Customer.LastUpdate = TODAY   NO-ERROR.
    IF ERROR-STATUS:ERROR THEN DO:
        UNDO, LEAVE.
    END.

    lSuccess = TRUE.
END.  /* transaction commits here if lSuccess = TRUE */

IF lSuccess THEN
    MESSAGE "Update successful" VIEW-AS ALERT-BOX.

Database Access

FOR EACH Statement:

The FOR EACH statement is ABL's primary record iteration construct:

/* Basic iteration — NO-LOCK is required for read-only access */
FOR EACH Customer NO-LOCK:
    DISPLAY Customer.CustNum Customer.Name Customer.Balance.
END.

/* WHERE clause filtering */
FOR EACH Customer
    WHERE Customer.Balance > 1000
    AND   Customer.Country = "USA"
    NO-LOCK:
    DISPLAY Customer.Name Customer.Balance.
END.

/* ORDER BY clause */
FOR EACH Customer
    WHERE Customer.Country = "USA"
    NO-LOCK
    BY Customer.Name:
    DISPLAY Customer.Name.
END.

/* Descending sort */
FOR EACH Customer
    NO-LOCK
    BY Customer.Balance DESCENDING:
    DISPLAY Customer.Name Customer.Balance.
END.

/* Multi-table join */
FOR EACH Order NO-LOCK,
    EACH OrderLine OF Order NO-LOCK,
    FIRST Product OF OrderLine NO-LOCK:
    DISPLAY Order.OrderNum OrderLine.Qty Product.Name.
END.

BREAK BY and Accumulation:

/* BREAK BY enables group processing and ACCUM functions */
FOR EACH Customer
    NO-LOCK
    BREAK BY Customer.State
    BY Customer.Name:

    /* Fires once at the start of each State group */
    IF FIRST-OF(Customer.State) THEN
        MESSAGE "=== State: " Customer.State " ===" VIEW-AS ALERT-BOX.

    DISPLAY Customer.Name Customer.Balance.

    /* Fires once at the end of each State group */
    IF LAST-OF(Customer.State) THEN
        DISPLAY "Subtotal for " Customer.State ":"
                ACCUM SUM Customer.Balance.
END.

/* Accumulate function types: SUM, COUNT, MAX, MIN, AVERAGE */
FOR EACH OrderLine NO-LOCK BREAK BY OrderLine.OrderNum:
    IF LAST-OF(OrderLine.OrderNum) THEN
        DISPLAY "Order " OrderLine.OrderNum
                " Lines: " ACCUM COUNT OrderLine.LineNum
                " Total:  " ACCUM SUM   OrderLine.ExtPrice.
END.

Lock Modes:

Lock Mode Description
NO-LOCK Read-only. No lock held. Other users can read and write. Best for reports.
SHARE-LOCK Shared read lock. Others can share-lock but not exclusive-lock.
EXCLUSIVE-LOCK Full write access. Other users must wait until the lock is released.
NO-WAIT Do not block if lock unavailable — immediately return without the record.
/* NO-LOCK — reporting and read-only */
FOR EACH Customer NO-LOCK:
    DISPLAY Customer.Name.
END.

/* EXCLUSIVE-LOCK — for updates */
FIND Customer WHERE Customer.CustNum = 1001 EXCLUSIVE-LOCK NO-ERROR.

/* SHARE-LOCK — read with notification to others */
FIND Customer WHERE Customer.CustNum = 1001 SHARE-LOCK NO-ERROR.

/* NO-WAIT — try to lock but don't wait if unavailable */
FIND Customer WHERE Customer.CustNum = 1001 EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
IF LOCKED(Customer) THEN
    MESSAGE "Record is locked by another user" VIEW-AS ALERT-BOX.

/* Downgrade from EXCLUSIVE to NO-LOCK after update committed */
FIND CURRENT Customer NO-LOCK.

FIND Statement:

/* FIND FIRST — first record matching criteria */
FIND FIRST Customer WHERE Customer.Balance > 5000 NO-LOCK NO-ERROR.
IF NOT AVAILABLE Customer THEN
    MESSAGE "No customers found" VIEW-AS ALERT-BOX.

/* FIND LAST — last record matching criteria */
FIND LAST Customer BY Customer.Balance DESCENDING NO-LOCK.

/* FIND using frame field value */
FIND Customer USING Customer.CustNum.     /* matches frame input value */

/* FIND OF — uses index match to related table */
FOR EACH Order NO-LOCK:
    FIND Customer OF Order NO-LOCK NO-ERROR.
    IF AVAILABLE Customer THEN
        DISPLAY Order.OrderNum Customer.Name.
END.

/* FIND CURRENT — reposition existing buffer (change lock) */
FIND FIRST Customer NO-LOCK.
/* ... check the record ... */
FIND CURRENT Customer EXCLUSIVE-LOCK.   /* upgrade to exclusive for update */
Customer.Balance = Customer.Balance + 100.
FIND CURRENT Customer NO-LOCK.          /* downgrade lock when done */

/* CAN-FIND — boolean check without loading record into buffer */
IF CAN-FIND(Customer WHERE Customer.CustNum = iNum) THEN
    MESSAGE "Customer exists" VIEW-AS ALERT-BOX.

Transaction Handling

ABL Implicit Transaction Model:

ABL uses an implicit, scope-based transaction model. There is no explicit COMMIT or ROLLBACK statement in the 4GL. Transactions are defined by block boundaries:

  • REPEAT block: Each iteration is a separate transaction. The transaction commits at the bottom of each iteration or when RELEASE is called.
  • DO TRANSACTION block: The entire DO block is one transaction. Commits at END. if no error occurred.
  • No block: Without a transaction block, database writes create "chained" transactions that commit when the record buffer is released.
/* REPEAT — each iteration is auto-committed */
REPEAT:
    CREATE Order.
    Order.OrderNum = NEXT-VALUE(orderSeq).
    Order.CustNum  = iCustNum.
    RELEASE Order.    /* commits this iteration's transaction immediately */
END.

/* DO TRANSACTION — entire block is one atomic transaction */
DO TRANSACTION:
    FIND Customer WHERE Customer.CustNum = 1001 EXCLUSIVE-LOCK.
    ASSIGN
        Customer.Balance    = Customer.Balance + 500
        Customer.LastUpdate = TODAY.
END.   /* transaction commits here if no error */

/* Check if currently inside an active transaction */
IF TRANSACTION THEN
    MESSAGE "Inside a transaction" VIEW-AS ALERT-BOX.

UNDO Statement:

/* Syntax */
UNDO [ label ] [ , action ]

/* Actions: RETRY, LEAVE, NEXT, RETURN ERROR */

/* UNDO, RETRY — undo current iteration and try again */
REPEAT ON ERROR UNDO, RETRY:
    IF RETRY THEN MESSAGE "Retrying...".
    FIND Customer WHERE Customer.CustNum = iNum EXCLUSIVE-LOCK NO-ERROR.
    IF LOCKED(Customer) THEN DO:
        PAUSE 1 NO-MESSAGE.
        UNDO, RETRY.     /* wait 1 second and retry the lock */
    END.
    /* ... proceed with update ... */
END.

/* UNDO, LEAVE — undo and exit the current block */
DO TRANSACTION ON ERROR UNDO, LEAVE:
    FIND Order WHERE Order.OrderNum = iNum EXCLUSIVE-LOCK NO-ERROR.
    IF NOT AVAILABLE Order THEN UNDO, LEAVE.
    Order.Status = "PROCESSED".
END.

/* UNDO, NEXT — undo and skip to next iteration */
FOR EACH Customer EXCLUSIVE-LOCK ON ERROR UNDO, NEXT:
    ASSIGN Customer.Balance = Customer.Balance * 1.05 NO-ERROR.
    IF ERROR-STATUS:ERROR THEN
        UNDO, NEXT.
END.

/* UNDO, RETURN ERROR — undo and signal error to caller */
PROCEDURE updateBalance:
    DEFINE INPUT PARAMETER piCustNum AS INTEGER.
    DEFINE INPUT PARAMETER pdAmount  AS DECIMAL.

    DO TRANSACTION ON ERROR UNDO, RETURN ERROR:
        FIND Customer WHERE Customer.CustNum = piCustNum
            EXCLUSIVE-LOCK NO-ERROR.
        IF NOT AVAILABLE Customer THEN
            UNDO, RETURN ERROR "Customer not found: " + STRING(piCustNum).
        Customer.Balance = Customer.Balance + pdAmount.
    END.
END PROCEDURE.

RELEASE Statement:

RELEASE commits the transaction and releases locks on a record buffer:

/* RELEASE frees the buffer and commits — same as reaching end of REPEAT iteration */
FOR EACH Order EXCLUSIVE-LOCK:
    Order.Status = "PROCESSED".
    RELEASE Order.     /* commits the update and releases the lock immediately */
END.

Control Flow

IF / THEN / ELSE:

/* Statement form — single action per branch */
IF Customer.Balance > 10000 THEN
    MESSAGE "Gold customer" VIEW-AS ALERT-BOX.
ELSE IF Customer.Balance > 5000 THEN
    MESSAGE "Silver customer" VIEW-AS ALERT-BOX.
ELSE
    MESSAGE "Standard customer" VIEW-AS ALERT-BOX.

/* Block form — multiple actions per branch */
IF Customer.Balance > 10000 THEN DO:
    ASSIGN Customer.Tier = "GOLD"
           Customer.Discount = 0.15.
    RUN notifyGoldStatus(INPUT Customer.CustNum).
END.
ELSE DO:
    ASSIGN Customer.Tier = "STANDARD"
           Customer.Discount = 0.00.
END.

Inline IF Expression (Ternary):

/* Inline IF evaluates to a value — like a ternary operator */
cTier    = IF Customer.Balance > 10000 THEN "GOLD" ELSE "STANDARD".
dRate    = IF lIsEmployee THEN 0.20 ELSE 0.10.
cStatus  = IF lActive THEN "Active" ELSE "Inactive".

/* Can be nested */
cTier = IF Customer.Balance > 50000 THEN "PLATINUM"
   ELSE IF Customer.Balance > 10000 THEN "GOLD"
   ELSE IF Customer.Balance > 5000  THEN "SILVER"
   ELSE "STANDARD".

CASE Statement:

/* Basic CASE */
CASE Customer.Tier:
    WHEN "GOLD"     THEN dDiscount = 0.15.
    WHEN "SILVER"   THEN dDiscount = 0.10.
    WHEN "BRONZE"   THEN dDiscount = 0.05.
    OTHERWISE            dDiscount = 0.00.
END CASE.

/* Multiple values per branch using OR WHEN */
CASE Order.Status:
    WHEN "NEW" OR WHEN "PENDING" THEN DO:
        MESSAGE "Order needs processing" VIEW-AS ALERT-BOX.
        RUN queueOrder(INPUT Order.OrderNum).
    END.
    WHEN "SHIPPED" OR WHEN "DELIVERED" THEN
        MESSAGE "Order is on its way" VIEW-AS ALERT-BOX.
    WHEN "CANCELLED" THEN
        RUN archiveOrder(INPUT Order.OrderNum).
    OTHERWISE
        MESSAGE "Unknown status: " Order.Status VIEW-AS ALERT-BOX.
END CASE.

DO Loop:

/* Counted loop */
DEFINE VARIABLE ix AS INTEGER NO-UNDO.
DO ix = 1 TO 10:
    DISPLAY ix.
END.

/* Counted loop with step */
DO ix = 10 TO 1 BY -1:   /* countdown */
    DISPLAY ix.
END.

/* While loop */
DEFINE VARIABLE lContinue AS LOGICAL NO-UNDO INITIAL TRUE.
DO WHILE lContinue:
    /* ... do work ... */
    IF iCount >= 100 THEN lContinue = FALSE.
END.

/* Transaction block */
DO TRANSACTION:
    CREATE Customer.
    Customer.CustNum = NEXT-VALUE(custSeq).
END.

/* Error-handling block */
DO ON ERROR UNDO, LEAVE:
    FIND Customer WHERE Customer.CustNum = 9999 EXCLUSIVE-LOCK NO-ERROR.
    IF NOT AVAILABLE Customer THEN LEAVE.
    Customer.Balance = 0.
END.

REPEAT Statement:

/* Infinite loop — exits via LEAVE or RETURN */
REPEAT:
    PROMPT-FOR Customer.CustNum.
    FIND Customer USING Customer.CustNum NO-LOCK NO-ERROR.
    IF NOT AVAILABLE Customer THEN DO:
        MESSAGE "Customer not found. Try again." VIEW-AS ALERT-BOX.
        NEXT.
    END.
    DISPLAY Customer.Name Customer.Balance.
    LEAVE.   /* exit after successful find */
END.

/* Counted REPEAT */
DEFINE VARIABLE ix AS INTEGER NO-UNDO.
REPEAT ix = 1 TO NUM-ALIASES:
    MESSAGE ALIAS(ix) VIEW-AS ALERT-BOX.
END.

/* Transaction per iteration */
REPEAT TRANSACTION:
    /* each pass through this loop is an auto-committed transaction */
    CREATE Order.
    Order.OrderNum = NEXT-VALUE(orderSeq).
    /* ... */
    RELEASE Order.
END.

Named Blocks and LEAVE/NEXT:

/* Named blocks allow LEAVE and NEXT to target specific blocks */
outerLoop:
DO ix = 1 TO 100:
    innerLoop:
    DO iy = 1 TO 100:
        IF ix = 50 AND iy = 50 THEN
            LEAVE outerLoop.    /* exit the outer DO */
        IF iy = 30 THEN
            NEXT outerLoop.     /* skip to next outer iteration */
    END.   /* innerLoop */
END.   /* outerLoop */

LEAVE, NEXT, RETURN:

LEAVE.              /* exit innermost block (like break in other languages) */
LEAVE blockLabel.   /* exit named block */

NEXT.               /* skip to next iteration (like continue) */
NEXT blockLabel.    /* skip to next in named block */

RETURN.             /* exit procedure/function normally */
RETURN "value".     /* return a string value — accessible via RETURN-VALUE */
RETURN ERROR.       /* exit and raise error condition in caller */
RETURN ERROR "message".  /* exit with error message string */

What v10 Does NOT Have

This section is critical for developers working with v10 code or migrating to a later version.

Missing Language Features Table

Feature Status in v10 When Added
INT64 data type ❌ Not in v10.1A v10.1B
ENUM statement v12
VAR shorthand declaration v12.3
Compound operators (+=, -=, *=, /=) v12.3
CATCH / THROW / FINALLY v10.2B
BLOCK-LEVEL ON ERROR UNDO, THROW directive Post-v10
JSON support (JsonObject, JsonArray, READ-JSON, WRITE-JSON) v11.x
Generic collections (List<T>, SortedSet<T>, HashMap<K,V>) v12.5–12.7
Safe navigation operator ?: v12.5
PACKAGE-PRIVATE access modifier v12.2
PACKAGE-PROTECTED access modifier v12.2
Override non-abstract properties v12.5
FINAL on properties v12.5
Server-side joins (FOR+NO-LOCK up to 10 tables) v12.0
Class-based SAX callbacks (EVENT-HANDLER-OBJECT) v12
DYNAMIC-INVOKE() for class methods ⚠️ Limited Improved v12

How to Work Around Missing Features in v10

Instead of INT64 — use DECIMAL:

/* v10: no INT64 — use DECIMAL for large numbers */
DEFINE VARIABLE dBigCounter AS DECIMAL NO-UNDO.
dBigCounter = 9000000000.   /* exceeds INTEGER max, fine as DECIMAL */

Instead of ENUM — use INTEGER constants or CHARACTER literals:

/* v10: no ENUM — use &DEFINE preprocessor constants */
&DEFINE STATUS_PENDING  "PENDING"
&DEFINE STATUS_ACTIVE   "ACTIVE"
&DEFINE STATUS_CLOSED   "CLOSED"

/* Or use INTEGER constants */
&DEFINE ORDER_NEW       1
&DEFINE ORDER_PENDING   2
&DEFINE ORDER_SHIPPED   3

DEFINE VARIABLE iStatus AS INTEGER NO-UNDO INITIAL {&ORDER_NEW}.
IF iStatus = {&ORDER_SHIPPED} THEN
    MESSAGE "Order shipped" VIEW-AS ALERT-BOX.

Instead of CATCH/THROW — use NO-ERROR + ERROR-STATUS:

/* v10: error handling without CATCH */
FIND Customer WHERE Customer.CustNum = iNum EXCLUSIVE-LOCK NO-ERROR.
IF ERROR-STATUS:ERROR THEN DO:
    /* handle database error */
    RETURN ERROR "DB error: " + ERROR-STATUS:GET-MESSAGE(1).
END.
IF NOT AVAILABLE Customer THEN DO:
    RETURN ERROR "Customer not found: " + STRING(iNum).
END.

Instead of generic collections — use comma-delimited strings or temp-tables:

/* v10: simulate a list with a comma-delimited string */
DEFINE VARIABLE cGoldCustomers AS CHARACTER NO-UNDO.
FOR EACH Customer NO-LOCK WHERE Customer.Balance > 10000:
    IF cGoldCustomers <> "" THEN
        cGoldCustomers = cGoldCustomers + ",".
    cGoldCustomers = cGoldCustomers + STRING(Customer.CustNum).
END.

/* Access items */
DEFINE VARIABLE ix AS INTEGER NO-UNDO.
DO ix = 1 TO NUM-ENTRIES(cGoldCustomers):
    MESSAGE "Gold customer: " ENTRY(ix, cGoldCustomers) VIEW-AS ALERT-BOX.
END.

/* Or use a temp-table as a typed collection */
DEFINE TEMP-TABLE ttGoldCustomers NO-UNDO
    FIELD CustNum AS INTEGER
    FIELD Name    AS CHARACTER
    INDEX ixCustNum IS PRIMARY CustNum.

FOR EACH Customer NO-LOCK WHERE Customer.Balance > 10000:
    CREATE ttGoldCustomers.
    ASSIGN ttGoldCustomers.CustNum = Customer.CustNum
           ttGoldCustomers.Name    = Customer.Name.
END.

Instead of safe navigation ?: — use explicit VALID-OBJECT checks:

/* v10: must check for null/invalid objects explicitly */
IF VALID-OBJECT(rOrder) THEN
    IF VALID-OBJECT(rOrder:Customer) THEN
        MESSAGE rOrder:Customer:Name VIEW-AS ALERT-BOX.

Related Skills

Skill Purpose
programming-abl-v12-learning Modern ABL (v12+) with ENUM, CATCH/THROW, JSON, generics
programming-abl-v10-ui OpenEdge v10 UI patterns (frames, widgets, event handling)
coding-code-review General code review methodology, OWASP security patterns

Version History Context

Version Release Key Language Additions
v10.0B ~2005 DATETIME, DATETIME-TZ, BLOB, CLOB, LONGCHAR, NOW
v10.1A Dec 2005 Formal OOP (CLASS, INTERFACE, METHOD, events)
v10.1B 2006 INT64 data type
v10.2B ~2007 CATCH / THROW / FINALLY structured error handling
v11.x ~2011+ JSON support (JsonObject, JsonArray, READ-JSON, WRITE-JSON)
v12.0 2019 Server-side joins, STOP catchable by default, 64-bit sequences
v12.2 2020 PACKAGE-PRIVATE, PACKAGE-PROTECTED
v12.3 2021 VAR shorthand, compound operators (+=, -=), CHAR/INT synonyms
v12.5 2022 Safe navigation ?:, generic List<T>, override non-abstract properties
v12.7 2023 HashMap<K,V>, HASH-CODE, signed archive libraries

Core Workflow

  1. Understand the Problem — Identify inputs, outputs, constraints, and edge cases. Checkpoint: Clearly define the problem statement and expected behavior.

  2. Select Algorithm — Choose the most appropriate algorithm based on constraints (time/space complexity, data size, ordering requirements). Checkpoint: Justify the algorithm choice with complexity analysis.

  3. Implement & Test — Write clean, readable code with type hints, docstrings, and edge case handling. Checkpoint: Verify correctness with multiple test cases including edge cases.

  4. Analyze & Optimize — Review time/space complexity, identify optimization opportunities, and validate against benchmarks. Checkpoint: Confirm complexity bounds match analysis and all test cases pass.


Constraints

MUST DO

  • Include time and space complexity analysis for all algorithms
  • Show at least one working example with input/output
  • Include diagrams for non-trivial algorithm flows

MUST NOT DO

  • Present an algorithm without its complexity bounds
  • Use recursive solutions without discussing tail-call or memoization alternatives
  • Omit edge case handling in examples

Live References

Authoritative documentation links for this domain. The model follows markdown links at load time to resolve external references and inline content.

Install via CLI
npx skills add https://github.com/paulpas/agent-skill-router --skill abl-v10-learning
Repository Details
star Stars 4
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator