DL/I database support

EGL-generated code can access a Data Language/I (DL/I) database on any of following target systems: EGL allows this access in two ways:

Note that the implicit and explicit DL/I code uses a powerful pseudo-DL/I syntax with some differences from actual DL/I code. For more information, see #dli directive.

If you are not familiar with DL/I, see Basic DL/I concepts.

EGL keywords and DL/I

The next table lists the EGL keywords that you can use to access a DL/I database. Included in this table are sample DL/I calls that each keyword might generate. When you code an EGL add statement, for example, you generate a DL/I ISRT call. You can preview an unformatted version of the DL/I calls that EGL will generate by placing your cursor in a line of code that contains a keyword and right-clicking the line. From the popup menu, select DLI Statement > View.

You can also code DL/I calls directly, using the with #dli{ statement } syntax. This syntax allows you to further specify how your program should access the database, as is necessary if the default DL/I calls that EGL generates from I/O keywords are not sufficient.

The following table shows the EGL I/O statements that support DL/I databases, and some examples of COBOL code those EGL statements might generate. The examples assume you have used the @DLI complex property to set your call interface to CBLTDLI. You can just as easily use the default AIBTDLI interface, in which case the COBOL will contain an AIB mask instead of a PCB mask. The AIB mask allows the program to specify a PCB by name rather than address. For details on the @DLI property, see @DLI.

I/O keyword/purpose Example DL/I call
add

Places a record of type DLISegment into a DL/I database; or (if you use a dynamic array of DLISegment records), places a set of records under a single parent segment, based on the content of each element of the array.

CALL 'CBLTDLI' USING EZEDLI-ISRT
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA
                     EZECUST-QUALIFIED-SSA
                     EZELOC-QUALIFIED-SSA
                     EZEORSSA-EZEUNQ-UNQUALIFIED-SSA
delete

Deletes a segment from a database. You must first use a get...forUpdate, get next...forUpdate, or get next inParent...forUpdate statement (generating a DL/I GHU, GHN, or GHNP statement) to locate and hold the record.

CALL 'CBLTDLI' USING EZEDLI-DLET
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA
get

Reads a unique segment from a database; or (if you use a dynamic array of DLISegment records), reads successive segments into successive elements in the array. If you use get to read successive records into an array, the COBOL program will loop through a series of GN calls following the initial GU.

CALL 'CBLTDLI' USING EZEDLI-GU
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA
                     EZECUST-QUALIFIED-SSA
                     EZELOC-QUALIFIED-SSA
get...forUpdate

Reads and holds a unique segment from a database; or (if you use a dynamic array of DLISegment records), reads successive segments into successive elements in the array and holds them (by issuing a series of GHN calls after the initial GHU).

CALL 'CBLTDLI' USING EZEDLI-GHU
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA
                     EZECUST-QUALIFIED-SSA
                     EZELOC-QUALIFIED-SSA
get next

Reads the next segment based on the current position in the database.

CALL 'CBLTDLI' USING EZEDLI-GN
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA
                     EZEUNQ-UNQUALIFIED-SSA
get next...forUpdate

Reads and holds the next segment based on the current position in the database.

CALL 'CBLTDLI' USING EZEDLI-GHN
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA
                     EZEUNQ-UNQUALIFIED-SSA
get next inParent

Reads the next segment that has the same parent segment, based on the current position in the database.

CALL 'CBLTDLI' USING EZEDLI-GNP
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA
                     EZEUNQ-UNQUALIFIED-SSA
get next inParent...forUpdate

Reads and holds the next segment that has the same parent segment, based on the current position in the database.

CALL 'CBLTDLI' USING EZEDLI-GHNP
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA
                     EZEUNQ-UNQUALIFIED-SSA
replace

Puts a changed segment back into a database. You must first use a get...forUpdate, get next...forUpdate, or get next inParent...forUpdate statement (generating a DL/I GHU, GHN, or GHNP statement) to locate and hold the record.

Under no circumstances can you update multiple database segments with a single default EGL replace statement.

CALL 'CBLTDLI' USING EZEDLI-REPL
                     EZEPCB-MASK01
                     EZEORSEG-IO-AREA

The EGL set keyword (specifically the set record position statement) also supports DL/I. This keyword performs no I/O, but effectively locates a specified segment within a database. For details on how the keyword operates, see set.

Record parts for DL/I support

You create the following kinds of record parts:
DLISegment
The DLISegment record part corresponds to a segment in a DL/I database. You create a record that is based on that part, then use that record to access the segment.
PSBRecord
The PSBRecord part contains information on a runtime program specification block (PSB) and includes a set of records that each include details on a runtime program control block (PCB).

You create a record that is based on the PSBRecord part, then assign the record name to the program psb property. EGL uses the record to generate the code that creates and validates DL/I calls.

When working with DL/I, you also create records that are based on the following, predefined record parts, each of which matches the layout of a runtime PCB:
IO_PCBRecord
A record of this type lets a program communicate with a user through a terminal
ALT_PCBRecord
A record of this type lets a program change the destination for an outgoing message
DB_PCBRecord
A record of this type allows access to a DL/I database
GSAM_PCBRecord
A record of this type is used for accessing a file by way of the Generalized Sequential Access Method (GSAM), in a program generated for z/OS batch or BMP. The file acts as a root-only DL/I database, but input and output is by way of a serial record. Commits and rollbacks affect the file.

Within each PCB record, you use the complex property @PCB to define the type and name of the PCB, and in some cases the hierarchy of the segments visible to that PCB. For details on the record types, see PCB record part propertiesand Record types and properties.

Finally, you may create a fixed record that is based on the predefined record part PSBDataRecord. You can use the record to interact with the system variable DLILib.psbData, which contains both the name of the runtime PSB and an address with which that PSB is accessed. The record is useful if you need to pass the PSB (really, a name and address) to another program or to receive the PSB from another program.

DLISegment part

Each segment type that you want to access in a DL/I database must have an equivalent record of type DLISegment in your program.

Consider the sample customer database and related code in Example DL/I database. For each customer there are segments for credit status, history, and individual locations. Each location has order segments, and each order has line item segments. In this case you will create DLISegment type records for Customer, Credit, History, Location, Order, and Line Item.

For reasons of clarity, because of migration issues, or because of conflicts with EGL naming conventions, you may want to use different names in EGL than in DL/I for the segment name or for field names. You can define one or more of the following properties in a set-value block:
  • segmentName char(8) If you did not use the DL/I segment name for the name of your DLISegment record, provide the segment name from the database here. For example, if you had a DL/I database that kept track of students and one of the segment types was named TRANSFER, you would not be able to use that name for a DLISegment record in EGL, as EGL uses "transfer" as a reserved keyword. Instead you could name the DLISegment record "xferStudents" and set segmentName="TRANSFER". EGL converts all names to upper case when generating the COBOL source code, so custName in your EGL source is equivalent to CUSTNAME in COBOL.
  • hostVarQualifier String EGL allows you to use host variables (variables defined in your EGL host program rather than in DL/I) when you construct #dli directives. These directives override the default DL/I calls that EGL generates from I/O keywords like add or get.

    In the absence of a #dli directive, EGL builds default segment search arguments (SSAs) to locate specific DL/I segments. As shown in the next example, the hostVarQualifier property of a DLISegment record part identifies a record that will contain a key value used in a default SSA .

    Consider the case in which the customer segment STSCCST is the parent of the location segment STSCLOC, and you want to retrieve only the data in the location segment. When you define the three record parts to set up this situation, you omit (for now) the hostVarQualifier property:
    Record myCustomerRecordPart type DLISegment
    { segmentName="STSCCST", keyItem="customerNo" }
    	10 customerNo char(6)      { dliFieldName = "STQCCNO" };  //key field
    	...
    end
    
    Record myLocationRecordPart type DLISegment 
    { segmentName="STSCLOC", keyItem="locationNo" }
    	10 locationNo char(6)      { dliFieldName = "STQCLNO" };  //key field
    	...
    end
    
    Record myCustomerPSBRecordPart type PSBRecord { defaultPSBName="STBICLG" }
    	// database PCB
    	customerPCB DB_PCBRecord { @PCB {
    		pcbType = DB,
    		pcbName = "STDCDBL",
    		hierarchy = [
    			@Relationship { segmentRecord = "myCustomerRecordPart" },
    			@Relationship { 
    				segmentRecord = "myLocationRecordPart", parentRecord="myCustomerRecordPart" },
    			...]}};
    end
    Next, you associate the program with a record that is based on the PSBRecord part and define variables based on the record parts:
    Program myProgram {
    	@DLI{ psb = "myCustomerPSB" }}
    
    	//define variables
     myCustomerPSB myCustomerPSBRecordPart;
     myCustomer myCustomerRecordPart;
     myLocation myLocationRecordPart;
    Finally, in a function, you attempt to retrieve the first location segment in the database without first retrieving any customer data:
    	get myLocation;
    From this get statement, EGL will generate the following pseudo-DL/I code:
    GU STSCCST (STQCCNO = :myCustomerRecordPart.customerNo) 
       STSCLOC (STQCLNO = :myLocation.locationNo)

    Notice that while EGL correctly associated the variable myLocation with the segment STSCLOC, EGL was unable to find the myCustomer variable. EGL knew only that myLocation is of type myLocationRecordPart, whose parent segment is myCustomerRecordPart, and that the keyItem for myCustomerRecordPart is customerNo.

    You could solve this problem by giving your variables the same name as the record parts on which they are based. Doing so could easily create confusion, however, and preferred practice is to use different names for parts and variables. You could also code a #dli directive in which you copied the above pseudo-DL/I code and replaced ":myCustomreRecordPart" with ":myCustomer". In a third, simpler approach, you can add a hostVarQualifier to the declaration for myCustomreRecordPart, as follows:
    Record myCustomerRecordPart type DLISegment
    { segmentName="STSCCST", hostVarQualifier="myCustomer", keyItem="customerNo" }
    Now the same get statement will produce the correct pseudo-DL/I code:
    GU STSCCST (STQCCNO = :myCustomer.customerNo) 
       STSCLOC (STQCLNO = :myLocation.locationNo)

    You also could use the property hostVarQualifier to refer to a field in a record that is not based on a segment in the database. For example, if you want EGL to look for the key value of the customer segment in a basic record (such as a transaction record), you must assign the name of that record to the hostVarQualifier property.

  • lengthItem FieldReference If the DLISegment record part defines a variable-length record, name the field within the record that specifies the record length.
  • keyItem FieldReference If the database specifies a key for this segment, identify the field that contains the key value. As noted earlier, the field can be in any record you choose, but is usually in a record that is based on the DLISegment record part being defined. EGL uses the keyItemproperty for creating default SSAs.

If you are working with a DLISegment record field whose name does not match the name of the equivalent database-segment field, set the record-field property dliFieldName to the name of the segment field.

PSBRecord part

The PSBRecord part contains information about your program's access to one or more databases and is used to provide access to the runtime PSB. Each record of that part type includes the following field:
  • defaultPSBName char(8). Identifies the runtime PSB by name. EGL assigns the value of defaultPSBName to the field psbName is the system variable DLILib.psbData. In CICS, that assignment causes the program to use the specified runtime PSB. The default value of defaultPSBNameis the name of the PSBRecord part.

PSBDataRecord part

This predefined record part is the type of data that is found in the system variable DLILib.psbData. A record that is based on the PSBDataRecord part contains the following fields:
psbName char(8)
CICS uses this name to determine the next PSB to schedule. IMS does not use this name.
psbRef int
The field psbRef provides access to the list of runtime PCBs. Do not modify this field directly.

In CICS, you can schedule the PSB identified in psbName; and the scheduling occurs after psbRef is cleared by a commit. To use this capability, set psbRef to a 0.

Declaring record parts

The following example shows one way to declare a DLISegment record that reflects a segment in a DL/I database:
Record myCustomerRecordPart type DLISegment 
{ segmentName="STSCCST", keyItem="customerNo" }
	10 customerNo char(6)      { dliFieldName = "STQCCNO" };  //key field
	10 customerName char(25)   { dliFieldName = "STUCCNM" };
	10 customerAddr1 char(25)  { dliFieldName = "STQCCA1" };
	10 customerAddr2 char(25)  { dliFieldName = "STQCCA2" };
	10 customerAddr3 char(25)  { dliFieldName = "STQCCA3" };
end

For the complete record layouts in this example database, see Example DL/I database.

Such record declarations describe the structure of the database segments as visible to this program. At run time, the runtime PCB determines what an individual program can see.

The structure within a record must match the structure of the segment as DL/I presents it to the program. Define the keyItem and lengthItem fields with the same length and position that they have in the DL/I segment. If the segment is a logical child, the structure should include the concatenated key of the destination parent as well as the intersection data. If the segment is a concatenated segment in a logical database, the structure should include the concatenated key, the intersection data, and the destination parent segment.

Database I/O

Because DL/I databases are hierarchical, your program must keep track of the current location within the database. To locate a specific child segment, you must specify the parent segment for that child (if any), the parent segment for that parent (if any), and so on all the way to the root of the database.

Using the customer data base as an example, in order to work with an item record, you must set the customer, location, and order numbers for that item. Fortunately, this is very simple in EGL. Assuming you have correctly defined key fields for all of these records, you could get an item record that you want to update as simply as this:
	//create instances of the records
	myCustomer myCustomerRecordPart;
	myLocation myLocationRecordPart;
	myOrder    myOrderRecordPart;
	myItem     myItemRecordPart;

	//build a segment search argument
	myCustomer.customerNo = "5001";
	myLocation.locationNo = "22";
	myOrder.orderDateNo = "20050730A003";
	myItem.itemInventoryNo = "CHAIR";   //1st part compound key
	myItem.itemLineNo = 27;
	
	//get the item and hold it
	try
		get myItem forUpdate;
		onException
			myErrorHandler(2);
	end

At run time, DL/I will perform a GU (get unique) call with a 30-byte concatenated segment search argument that includes the key fields for customer (6 bytes), location (6 bytes), order (12 bytes), and item (6 + 2 bytes).

Customizing the DL/I statements

Given an EGL statement that uses a DL/I record as the I/O object, you can deal with that statement in one of several ways:
  • You can accept the implicit DL/I statement. In this case, changes you make to the DL/I record part affect the DL/I statements that EGL will generate from your code. If you later indicate that you want to use a different record item as the key of a DL/I segment, for example, EGL will automatically pick up that change at generation time.
  • You can make the statement explicit, extending the EGL I/O statement with the with #dli{ statement } syntax. In this case, the details of that DL/I statement are isolated from the DL/I record part, and any subsequent changes made to the DL/I record part have no effect on the DL/I statement that is used at run time. If you remove an explicit DL/I statement from the source, the implicit DL/I statement (if any) is again available at generation time. For more details, see #dli directive.
  • You can change the name of the PCB by including the usingPCB keyword and specifying a substitute PCB name in your EGL I/O statement. With two PCBs you can, for example, keep two different pointers into the same database simultaneously, letting you read a segment twice.
  • You can replace the statement entirely and code explicit DL/I calls using the library functions DLILib.AIBTDLI(), DLILib.EGLTDLI(), or VGLib.VGTDLI().
Feedback
(C) Copyright IBM Corporation 2000, 2005. All Rights Reserved.