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
1INPUT BY NAME p_ord.cust_no, p_ord.ship_date, p_ord.qty2 AFTER FIELD cust_no3 SELECT credit_limit, hold_flag4 INTO l_limit, l_hold5 FROM customer6 WHERE cust_no = p_ord.cust_no7 IF STATUS = NOTFOUND THEN8 ERROR "Unknown customer"9 NEXT FIELD cust_no10 END IF11 IF l_hold = "Y" THEN12 ERROR "Customer on credit hold"13 NEXT FIELD cust_no14 END IF15 AFTER FIELD ship_date16 IF p_ord.ship_date < TODAY + 2 THEN17 LET p_ord.ship_date = TODAY + 218 DISPLAY BY NAME p_ord.ship_date19 END IF20 ON KEY (F5)21 CALL show_credit(p_ord.cust_no)22END INPUT
Modern: Request schema plus server-side validation and correction rules
1export const OrderRequest = z.object({2 custNo: z.string().min(1), // REQUIRED3 shipDate: z.string().date(),4 qty: z.number().int().min(1).max(999), // INCLUDE5});67export 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");1213 const earliest = addDays(today(), 2);14 // R-ORD-03: legacy silently moved the date forward15 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
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:
1ATTRIBUTES2f001 = 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.
Related patterns
Usually found in the same codebase.
- Java EEHigh
Struts god Action
Maps to Thin controller per command, domain service underneath
- VB6High
VB6 global module state
Maps to Explicit request context and injected services
- .NETHigh
WebForms ViewState and code-behind logic
Maps to SPA front end over a stateless API that rereads authoritative data
- COBOLMedium
88-level condition names
Maps to Enum with category predicates
- Next in catalog
ALTER statement
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.