Skip to content
PYD ENTERPRISES

Data stores anti-pattern

IMS hierarchical navigation

IMS DB programs navigate a segment hierarchy with DL/I GU, GN and GNP calls whose results depend on current database position, so the query is a sequence of calls, not a statement.

FAMILY
Data stores
CUTOVER RISK
Critical
Changes runtime behavior in ways a line-by-line reading misses. Resolve before any cutover plan.
MODERN MAPPING
Relational aggregate with foreign keys
GENERATED ARTIFACT
CustomerAggregate.schema.json

Before and after

legacy source → modern mapping

Legacy: exhibits the pattern

PRMRATE.cblCOBOL
1 01 DLI-GU PIC X(04) VALUE 'GU '.
2 01 DLI-GNP PIC X(04) VALUE 'GNP '.
3 01 SSA-CUSTOMER.
4 05 FILLER PIC X(09) VALUE 'CUSTOMER('.
5 05 FILLER PIC X(10) VALUE 'CUSTNO ='.
6 05 SSA-CUSTNO PIC X(08).
7 05 FILLER PIC X(01) VALUE ')'.
8 01 SSA-POLICY PIC X(09) VALUE 'POLICY '.
9 ...
10 MOVE IN-CUSTNO TO SSA-CUSTNO
11 CALL 'CBLTDLI' USING DLI-GU, CUST-PCB,
12 CUST-IO, SSA-CUSTOMER
13 IF CUST-PCB-STATUS = 'GE'
14 GO TO 4999-EXIT
15 END-IF
16 PERFORM UNTIL CUST-PCB-STATUS NOT = SPACES
17 CALL 'CBLTDLI' USING DLI-GNP, CUST-PCB,
18 POL-IO, SSA-POLICY
19 IF CUST-PCB-STATUS = SPACES
20 PERFORM 5000-RATE-POLICY
21 END-IF
22 END-PERFORM.

Modern: Relational aggregate with foreign keys

policy_rating.sqlSQL
1CREATE TABLE customer (
2 cust_no CHAR(8) PRIMARY KEY
3);
4CREATE TABLE policy (
5 cust_no CHAR(8) NOT NULL REFERENCES customer,
6 policy_no CHAR(10) NOT NULL, -- sequence field
7 PRIMARY KEY (cust_no, policy_no)
8);
9
10-- path NP-03: GU CUSTOMER, then GNP POLICY
11SELECT p.*
12FROM policy p
13WHERE p.cust_no = :custNo
14ORDER BY p.policy_no; -- hierarchical order

Why it is dangerous in modernization

An IMS database is a tree of segment types: a customer root, policies under it, coverages under each policy. Programs read it through DL/I calls against a PCB: GU (get unique) positions on a segment that matches the segment search arguments, GN (get next) moves forward in hierarchical order, and GNP (get next within parent) moves forward but only under the current parent. What a GN returns depends on where the previous call left the position pointer, and on the status code it set, such as GE (not found), GB (end of database), GA and GK (crossed a level or segment type). The meaning of a program is a walk over the tree that the code never states as a query. Relational conversions that flatten segments into tables without reconstructing those walks change which children are processed, in which order, and under which parent.

How PYD ENTERPRISES detects it

PYD ENTERPRISES reads DBDs and PSBs to build the segment hierarchy, field definitions and the program's sensitive segments. Every CALL 'CBLTDLI' is decoded into function code, PCB, I/O area and SSA list, with qualified SSAs parsed into segment, field, operator and value. A position-tracking analysis then simulates database position across the control-flow graph, so each GN and GNP is resolved to the segment types it can return and the parent it is bounded by. Status-code tests are linked to the calls that set them. The result is a set of navigation paths, each equivalent to a parent-child query.

Maps to: Relational aggregate with foreign keys

Each segment type becomes a table keyed by its parent's key plus its own sequence field. Each navigation path becomes a join or an aggregate query with an explicit ORDER BY matching IMS hierarchical sequence, which is what the legacy program relied on. Where the target is a document store, the hierarchy maps directly to nested documents, and the navigation paths become projections.

Generated specification fragment

CustomerAggregate.schema.jsonJSON
1{
2 "$schema": "https://json-schema.org/draft/2020-12/schema",
3 "title": "CustomerAggregate",
4 "description": "From DBD CUSTDBD: CUSTOMER > POLICY > COVERAGE",
5 "type": "object",
6 "required": ["custNo", "policies"],
7 "properties": {
8 "custNo": { "type": "string", "maxLength": 8 },
9 "policies": {
10 "type": "array",
11 "description": "IMS hierarchical sequence by POLICYNO",
12 "items": { "$ref": "#/$defs/Policy" }
13 }
14 }
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.

Position is state

After the GU in the example, database position sits on one CUSTOMER segment. Each GNP returns the next POLICY under that customer and sets a blank status; when there are no more, it returns GE and the loop ends. If 5000-RATE-POLICY issued its own call against the same PCB, for example a GU to read a reference segment, position would move, and the next GNP would continue from somewhere else entirely. This is why the engine tracks position through called paragraphs and through CALLs to other programs that receive the PCB, not only within the loop.

Status codes as control flow

  • Blank: the call succeeded.
  • GE: no segment satisfied the call (for GNP, no more children under the parent).
  • GB: end of database reached during a GN.
  • GA: an unqualified GN moved up to a higher level in the hierarchy.
  • GK: an unqualified GN returned a different segment type at the same level.

Programs that use unqualified GN and branch on GA or GK are walking the whole tree and reacting to its shape. Those walks become the most complex queries in the migration, and PYD ENTERPRISES reports them separately so they are planned, not discovered in testing.

Logical relationships and secondary indexes

IMS logical relationships let a program see segments from another database as if they were children, and secondary indexes let it enter the hierarchy through a field other than the root key. Both are declared in the DBD, not in the program. The engine resolves them from the DBD and PSB, so a navigation path that crosses databases is shown with its real physical source and becomes a join between the right tables.

Find every IMS hierarchical navigation 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.