An Open Data Path (ODP) in IBM i is a system-managed structure that represents the path between a program and a database file once the file is opened. It controls how records are read, updated, and positioned — including:
-
File pointer position
-
Record format
-
Access path (keyed/sequential)
-
Locking and sharing info
๐ง Simple Definition:
The ODP is the "live connection" between your program and the file — it's what IBM i uses to track where you are in the file, how you opened it, and who else is using it.
โ Key Responsibilities of ODP
| Function | Description |
|---|---|
| Track file position | Keeps current location for READ, SETLL, CHAIN, etc. |
| Maintain access path | Manages index used for keyed access (K fields in DDS) |
| File sharing | Shared across programs within the same activation group |
| Manage open mode | Tracks if file is open for input, output, update |
๐งพ Example: ODP Behavior with READ
setll 'C100' CUSTOMERF;
read CUSTOMERF; // ODP now positioned at next record
read CUSTOMERF; // Continues from last position
Each READ advances the ODP's cursor.
๐ Types of ODP
| Type | Description |
|---|---|
| Shared ODP | Used by multiple programs in the same activation group |
| Private ODP | Used by one program only (e.g., in *NEW actgrp or DFTACTGRP) |
| Reused ODP | Reused across program calls if file and actgrp are the same |
๐งจ When ODP Causes Issues
| Symptom | Root Cause |
|---|---|
READ returns wrong record |
ODP already positioned by another program |
CHAIN fails after recompilation |
Format mismatch or shared stale ODP |
| File already open error | File open in shared ODP with different params |
| Level check errors | ODP referencing outdated format |
โ Best Practices
| Recommendation | Reason |
|---|---|
Always SETLL before READ |
Resets ODP position |
Use *NEW activation group when needed |
Forces new ODP |
Use CLOSE to reset ODP cleanly |
Especially in reusable jobs/APIs |
| Avoid sharing files in long-running groups | Prevents ODP confusion across program calls |
๐ Inspecting ODPs (Indirectly)
While IBM i doesn't let you directly "see" ODPs, you can observe their behavior using:
-
Debug (
STRDBG) → see file pointer changes -
Job log and DSPJOB → monitor open file references
-
DSPPGMREF / DSPDBR → trace usage relationships
Let’s walk through a clear example where two programs — using different activation groups — each get their own Open Data Path (ODP) to the same file, leading to different read behavior even though the file and logic are the same.
๐ฏ Scenario: Two Programs, Same File, Different Activation Groups
Files:
-
File:
SALESF-
Has 4 records: S001, S002, S003, S004
-
๐งพ Program A (PGMA) — Uses *NEW Activation Group
ctl-opt actgrp(*new) dftactgrp(*no);
dcl-f SALESF usage(*input) keyed;
read SALESF;
if not %eof(SALESF);
dsply ('PGMA: ' + SALEID);
endif;
๐งพ Program B (PGMB) — Also Uses *NEW
ctl-opt actgrp(*new) dftactgrp(*no);
dcl-f SALESF usage(*input) keyed;
read SALESF;
if not %eof(SALESF);
dsply ('PGMB: ' + SALEID);
endif;
โ What Happens:
Each program has:
-
Its own activation group
-
Its own ODP
-
So even though both programs read
SALESF, they each start at the top
If you run:
CALL PGMA
CALL PGMB
Output:
PGMA: S001
PGMB: S001
โก Even though PGMA read record S001, PGMB also starts at S001 because it got a separate ODP when it opened the file.
๐ Now, Modify PGMB to Use Same Activation Group:
In PGMB:
ctl-opt actgrp('SALEGROUP');
And also recompile PGMA with the same activation group:
ctl-opt actgrp('SALEGROUP');
Now, run:
CALL PGMA
CALL PGMB
Output:
PGMA: S001
PGMB: S002
โก PGMB inherits the ODP from PGMA and continues from where PGMA left off!
๐ Takeaway: Activation Group Controls ODP Sharing
| Activation Group | ODP Behavior |
|---|---|
| Same group | Shared ODP |
*NEW or *DFTACTGRP |
Separate ODP |
| Mixed | Can cause conflicts or confusion |
๐ Debugging ODP Issues in Service Programs
If your service program is:
-
Using files without
CLOSE -
Called from programs with different actgrps
Then:
-
ODPs can stay open
-
Reads may act unpredictably
-
Files may show
already openorlevel checkerrors
๐ง Solution:
-
Use
*CALLERin service program:ctl-opt actgrp(*caller); -
Or explicitly
CLOSEfiles at end of procedure
Let’s build a full working example that shows:
-
A main program (
MAINPGM) calls a service program (SRVPGM) -
Both use a shared file (
SALESF) -
Improper activation group handling causes ODP confusion or even file already open errors
-
Then we'll fix it using
*CALLERandCLOSE
๐งพ Step 1: Physical File — SALESF
A R SALESREC
A SALEID 5A
A AMOUNT 9P 2
A K SALEID
Populate with:
INSERT INTO SALESF VALUES ('S001', 100.00);
INSERT INTO SALESF VALUES ('S002', 200.00);
๐งพ Step 2: Service Program Source — SRVREAD
ctl-opt actgrp('SALEGROUP') dftactgrp(*no); // โ bad for shared services
dcl-f SALESF usage(*input) keyed;
dcl-proc ReadNextSale export;
dcl-s sale char(5);
read SALESF;
if not %eof(SALESF);
sale = SALEID;
dsply ('SRVPGM: ' + sale);
else;
dsply 'SRVPGM: EOF';
endif;
// Optional fix: close file to reset ODP
// close SALESF;
end-proc;
๐งพ Step 3: Main Program — MAINPGM
ctl-opt actgrp('SALEGROUP') dftactgrp(*no);
dcl-pr ReadNextSale extproc(*dclcase);
end-pr;
dcl-f SALESF usage(*input) keyed;
read SALESF;
if not %eof(SALESF);
dsply ('MAINPGM: ' + SALEID);
endif;
// Now call service program
ReadNextSale();
๐งจ Problem: ODP Conflict
When you run MAINPGM, it opens SALESF and reads S001.
Then it calls ReadNextSale() from SRVPGM, which tries to open SALESF again — but:
-
ODP is already in use
-
File may be locked or skip to S002
-
You may get:
RNX1216 โ File already open RNX1217 โ File not found or not loaded -
Or unexpected read behavior (record skipping)
โ
Fix 1: Use *CALLER in Service Program
Change SRVREAD:
ctl-opt actgrp(*caller); // โ
Inherits activation group from MAINPGM
This allows the ODP to be shared safely — SALESF is opened once by MAINPGM and reused by SRVPGM.
โ
Fix 2: Use CLOSE inside service proc
If you want to force a clean read each time ReadNextSale() is called, add:
close SALESF;
But this is less preferred than managing activation groups properly.
๐ Lesson Summary
| Mistake | Result |
|---|---|
| Service PG in own actgrp | File re-opened, ODP conflict |
| Same file in both programs | READ may skip or error |
Using *CALLER |
Shares file, avoids conflicts |
Using *NEW in caller |
Resets everything clean |