Skip to content
PYD ENTERPRISES

4GL anti-pattern

Informix 4GL screen logic

Business rules embedded in INPUT statement control blocks (AFTER FIELD, BEFORE FIELD, ON KEY) and form attributes, interleaved with SQL, so validation and silent corrections exist only in the terminal screen.

FAMILY
4GL
CUTOVER RISK
Medium
Adds migration effort and contract risk, but behavior is visible in the code once you look.
MODERN MAPPING
Request schema plus server-side validation and correction rules
GENERATED ARTIFACT
OrderRequest.schema.json

Before and after

legacy source → modern mapping

Legacy: exhibits the pattern

ordent.4glText
1INPUT BY NAME p_ord.cust_no, p_ord.ship_date, p_ord.qty
2 AFTER FIELD cust_no
3 SELECT credit_limit, hold_flag
4 INTO l_limit, l_hold
5 FROM customer
6 WHERE cust_no = p_ord.cust_no
7 IF STATUS = NOTFOUND THEN
8 ERROR "Unknown customer"
9 NEXT FIELD cust_no
10 END IF
11 IF l_hold = "Y" THEN
12 ERROR "Customer on credit hold"
13 NEXT FIELD cust_no
14 END IF
15 AFTER FIELD ship_date
16 IF p_ord.ship_date < TODAY + 2 THEN
17 LET p_ord.ship_date = TODAY + 2
18 DISPLAY BY NAME p_ord.ship_date
19 END IF
20 ON KEY (F5)
21 CALL show_credit(p_ord.cust_no)
22END INPUT

Modern: Request schema plus server-side validation and correction rules

orders.tsTypeScript
1export const OrderRequest = z.object({
2 custNo: z.string().min(1), // REQUIRED
3 shipDate: z.string().date(),
4 qty: z.number().int().min(1).max(999), // INCLUDE
5});
6
7export async function createOrder(req: OrderInput) {
8 const cust = await customers.find(req.custNo);
9 if (!cust) return fieldError("custNo", "UNKNOWN_CUSTOMER");
10 if (cust.holdFlag === "Y")
11 return fieldError("custNo", "CREDIT_HOLD");
12
13 const earliest = addDays(today(), 2);
14 // R-ORD-03: legacy silently moved the date forward
15 const shipDate = maxDate(req.shipDate, earliest);
16 return orders.insert({ ...req, shipDate });
17}

Why it is dangerous in modernization

Informix 4GL applications handle data entry with INPUT and INPUT ARRAY statements whose control blocks run as the cursor moves between fields. AFTER FIELD blocks query the database, show an ERROR, and force the user back with NEXT FIELD; others quietly correct the entered value with LET and redisplay it. Form specification files (.per) add more rules as field attributes: REQUIRED, INCLUDE ranges, DEFAULT, UPSHIFT, VERIFY. Errors are often handled with WHENEVER ERROR CONTINUE and a check of STATUS. None of this is in a service layer: it is the screen. When the terminal UI is replaced with a web front end and an API, the rules that rejected or corrected input at the keyboard vanish, and the database begins receiving values the old system could never have stored.

How PYD ENTERPRISES detects it

PYD ENTERPRISES parses 4GL modules (.4gl), GLOBALS files and form specifications (.per) together. Each INPUT statement becomes a model of the fields it binds, the program variables behind them, and its control blocks in firing order. Inside each block, SQL statements are analysed for the tables and columns they read, ERROR plus NEXT FIELD sequences are classified as validation rules, and LET assignments to input variables followed by DISPLAY are classified as corrections. Form attributes are merged in as field constraints. The output is a per-screen rule list linked to the .4gl and .per lines, plus a request schema for the operation the screen performs.

Maps to: Request schema plus server-side validation and correction rules

Field attributes and simple checks become JSON Schema constraints on the API request. Checks that need the database, such as credit holds, become server-side validations returning field-level errors (422 with problem details). Silent corrections, such as moving a ship date forward, become explicit rules in the service with a decision on whether the API corrects or rejects, since a web client will not see a redisplayed field.

Generated specification fragment

OrderRequest.schema.jsonJSON
1{
2 "$schema": "https://json-schema.org/draft/2020-12/schema",
3 "title": "OrderRequest",
4 "type": "object",
5 "required": ["custNo", "shipDate", "qty"],
6 "properties": {
7 "custNo": { "type": "string", "minLength": 1 },
8 "shipDate": {
9 "type": "string", "format": "date",
10 "x-pyd-rule": "R-ORD-03 corrected to today + 2 if earlier"
11 },
12 "qty": { "type": "integer", "minimum": 1, "maximum": 999 }
13 },
14 "x-pyd-source": ["ordent.4gl:1-22", "ordent.per:14-16"]
15}

Every generated element links back to the source file and line range shown in the legacy pane. Review and sign off before building against it.

Half the rules are in the form file

The INPUT statement is only part of the screen. The compiled form adds constraints that 4GL enforces before any AFTER FIELD block runs:

ordent.per (ATTRIBUTES excerpt)Text
1ATTRIBUTES
2f001 = orders.cust_no, REQUIRED, UPSHIFT;
3f002 = orders.ship_date, DEFAULT = TODAY,
4 FORMAT = "mm/dd/yyyy";
5f003 = orders.qty, INCLUDE = (1 TO 999);

Corrections are rules too

The AFTER FIELD ship_date block does not reject an early date; it replaces it with the earliest allowed date and redisplays the field. The operator sees the change and carries on. Through an API, the client sees nothing unless the response says so. PYD ENTERPRISES classifies LET-then-DISPLAY sequences on input variables as corrections and exports them as rules with their own identifiers, so the API design can choose between correcting and returning the corrected value, or rejecting with an explanation. Either is defensible; silently dropping the rule is not.

UPSHIFT on the customer number is a smaller correction of the same kind: the API must uppercase the value or lookups against existing customers will fail for lowercase input that the terminal never allowed.

Error handling and globals

  • WHENEVER ERROR CONTINUE turns SQL errors into a STATUS value the code may or may not check. Unchecked regions are reported like VB6 Resume Next regions.
  • GLOBALS files share variables across modules, including the current user's warehouse or price list. Those become request context, as with VB6 module state.
  • ON KEY blocks often open lookups or secondary screens. They are navigation, classified as plumbing, unless they change data.

The result is a per-screen inventory that tells the front-end team what to rebuild and the API team what to enforce on the server.

Find every Informix 4GL screen logic in your codebase.

Run PYD ENTERPRISES in your private VPC and get each occurrence with file, line range, modern mapping and equivalence fixtures, ready for your architects to review.