DL/I examples

These DL/I examples use the same example DL/I database as described in Example DL/I database. Following are some examples of typical techniques for DL/I database I/O:

Searching within a parent segment

The default get next statement starts at the current position in the database and searches through the entire database for the next occurrence of the target segment under any parent. To limit the scope of the search to the currently established dependent chain, use the get next inParent statement.

Consider the situation in which you want to retrieve the items for a particular order. You can use the following to retrieve the order:
get myOrder with #dli {
   GU STSCCST (STQCCNO = :myCustomer.customerNo)
      STSCLOC (STQCLNO = :myLocation.locationNo)
      STSOCORD (STQCODN = :myOrder.orderDateno) };
Then you can use the following to retrieve the line segments within the order:
get next inParent myItem;
  while (myItem not noRecordFound)
  // process the current item
  get next inParent myItem;
end
EGL creates the following pseudo-DL/I code for the get next inParent statement:
GNP  STLCITM 

Searching with another non-key field

You can use any field in a segment as a search argument on a DL/I call by modifying the search arguments (SSAs) for the call. For example, if you wanted to read through the customer database and retrieve the customer segment and credit segment for each customer with a credit balance greater than a specified amount, you would define the DL/I call search arguments as follows:
  1. You want to search on the creditBalance field (STFCSBL) in the credit segment (STSCSTA). To do this, define a variable of type decimal(12,2) (for example, "targetBalance") that contains the specified amount that you want to search for. The definition for targetBalance should match the definition for the creditBalance field in the credit segment.
  2. Write a get next statement for the myCrStatus record.
  3. Add a #dli directive to the line, modifying the default code. Add a qualified SSA that looks for a segment where the amount in the creditBalance field is greater than targetBalance.
  4. Include a path command code (*D) to retrieve the customer segment (STSCCST) that corresponds to the credit segment.
The following sample code illustrates this process:
	targetBalance decimal(12,2);
	targetBalance = 10,000.00;

	get next myCustomer,myCrStatus with #dli{
		GU STSCCST*D STSCSTA (STFCSBL >= :targetBalance) };

Searching based on information in another record

You might want to search based on information in another record. Consider the situation in which the customer number is passed to the program in a parameter record as follows:
Record CustomerData type basicRecord 
  10 customerNo char(6) 
  ... 
end

Program myProgram 
  (customerParm  CustomerData)
  { @DLI{ psb = "myCustomerPSB" }} 
  //declare variables 
  myCustomerPSB     CustomerPSBRecordPart; 
  myCustomer        CustomerRecordPart;
You want to retrieve the customer segment based on the value in customerParm.customerNo. There are three ways to code this as follows:
  • Assign the value of customerParm.customerNo to myCustomer.customerNo and then use implicit DL/I I/O as follows:
    myCustomer.customerNo = CustomerParm.customerNo;
    get myCustomer;
    In this case, EGL creates the normal default SSAs, resulting in the following pseudo-DL/I code:
    GU STSCCST (STQCCNO = :myCustomer.customerNo)
    The advantage of this technique is that it is very simple. The disadvantage is the very slight performance overhead of moving the customer number to the segment record.
  • Use the #dli directive and explicit DL/I I/O so that you can use the customerParm.customerNo field as the host variable as follows:
    get myCustomer with #dli {
       GU  STSCCST (STQCCNO = :customerParm.customerNo) } ;
    This technique avoids the performance overhead of moving the customer number. However, it does take slightly longer to paste in the default #dli directive and then modify the code to use the correct record variable name for the parameter record.
  • Add the hostVarQualifier property in the DLISegment record to specify the qualifier for the host variable as follows:
    Record CustomerRecordPart type DLISegment 
      {segmentName="STSCCST", keyItem="customerNo", 
       hostVarQualifier="customerParm" }
      10 customerNo char(6) { dliFieldName = "STQCCNO" }; //key field 
      ... 
    end
    Then you can use implicit DL/I I/O without having to first assign customerParm.customerNo to myCustomer.customerNo as follows:
    get myCustomer;
    The pseudo-DL/I code that EGL creates in this case is:
    GU STSCCST (STQCCNO = :customerParm.customerNo)

    The advantage of this technique is that it results in the same pseudo-DL/I code that you coded for the #dli directive, without you actually having to code the #dli directive. The disadvantage is that EGL now uses customerParm as the qualifier for every implicit DL/I database I/O statement that uses the CustomerRecordPart.

Searching for a non-root segment

You can use implicit DL/I database I/O to retrieve a non-root segment. EGL creates the entire chain of SSAs for you based on the hierarchical position of the target segment in the PCB. However, for the higher level segments in the hierarchy, EGL cannot automatically determine the name of the program variable to use as the qualifier for the segment.

Consider the situation where the customer segment STSCCST is the parent of the location segment STSCLOC and you want to retrieve only the data for the location segment. You have defined the following record variables in your program:
myCustomer CustomerRecordPart; 
myLocation LocationRecordPart;
If you use the following get statement:
get myLocation;
EGL will create the following pseudo-DL/I code:
GU STSCCST (STQCCNO = :CustomerRecordPart.customerNo) 
  STSCLOC (STQCLNO = :myLocation.locationNo)

Notice that while EGL correctly associated the variable myLocation with the segment STSCLOC, EGL was unable to find the variable myCustomer that is based on the CustomerRecordPart. EGL only knows that myLocation is of type LocationRecordPart, whose parent segment is CustomerRecordPart, and that the keyItem for CustomerRecordPart is customerNo.

You can solve this problem in several ways:
  • You can name your record variables with the same name as the record parts on which they are based. For example, change the variable declaration for myCustomer to CustomerRecordPart.
    CustomerRecordPart CustomerRecordPart;
    EGL creates the same pseudo-DL/I code:
    GU STSCCST (STQCCNO = :CustomerRecordPart.customerNo) 
      STSCLOC (STQCLNO = :myLocation.locationNo)

    Because EGL creates the default SSAs from the PCB hierarchy information, this is an easy way of ensuring that the variable names EGL uses match your program's record variable declarations. The disadvantage is that this technique does not follow the general practice of using different names for parts and variables.

  • Use the #dli directive and explicit DL/I database I/O as follows:
    get myLocation with #dli {
      GU STSCCST (STQCCNO = :myCustomer.customerNo)
        STSCLOC (STQCLNO = :myLocation.locationNo)

    The advantage of this technique is that it is very clear which host variables are being used. It is an easy technique to use if the host variable qualifier or field name is different from the record variable that is based on the DL/I segment record. The disadvantage is the same as for any explicit I/O -- if the hierarchy or the key fields change, the explicit DL/I database I/O statement does not change automatically.

  • Modify your definition for CustomerRecordPart to include the hostVarQualifier property as follows:
    Record CustomerRecordPart type DLISegment 
      { segmentName="STSCCST", keyItem="customerNo",
        hostVarQualifier = "myCustomer" } 
      10 customerNo char(6) { dliFieldName = "STQCCNO" }; //key field 
      ... 
    end
    Now if you use the following get statement:
    get myLocation;
    EGL will produce the correct pseudo-DL/I code:
    GU STSCCST (STQCCNO = :myCustomer.customerNo) 
      STSCLOC (STQCLNO = :myLocation.locationNo)

    The advantage of this technique is that you can use implicit DL/I database I/O and have different names for the record variables and the DL/I segment records. The disadvantage is that EGL now uses myCustomer as the qualifier for every implicit DL/I database I/O statement that uses the CustomerRecordPart.

Searching with a secondary index

Sometimes the DL/I PSB indicates that a database structure is to be accessed with a secondary index. When this situation occurs, your database administrator enters an index key name next to the root segment in the runtime PCB definition. You notify EGL about the use of a secondary index by including the secondaryIndex property in the PCB record within the PSBRecord. For example, if you have another view of the customer database in which customerName is the secondary index, then you can add an additional PCB record to the PSB Record part as follows:
// database PCB for customer by Name
customerNamePCB DB_PCBRecord 
  { @PCB { pcbType = DB, pcbName = "STDCNAM", 
    secondaryIndex = "STUCCNM",
    hierarchy = [ @relationship { segmentRecord = "CustomerRecordPart" },
    ... ] }
  }

The pcbName property must match an actual DL/I database PCB in the runtime PSB. The secondaryIndex property must provide the same field name as your database administrator specifies in the XDFLD statement of the runtime PCB. There are now two PCB records in the PSB record that include the CustomerRecordPart in their hierarchy.

If you issue a get statement to locate a customer, as in the following example:
myCustomer CustomerRecordPart;
get myCustomer;
EGL will create the pseudo-DL/I code based on the first PCB record in the PSB record that includes the CustomerRecord part.
If the secondary index database is the second PCB record in the PSB record, you must include the usingPCB keyword, as in the following example so that EGL will use the correct PCB:
get myCustomer usingPCB customerNamePCB;
EGL will create the following pseudo-DL/I code:
GU STSCCST (STUCCNM = :myCustomer.customerName)
Notice that EGL used the record variable name from the get statement (myCustomer) and the PCB specified by the usingPCB keyword (customerNamePCB) to find the following information:
  • The DL/I name of the segment (property segmentName = "STSCCST").
  • The DL/I name of the secondary index field (property secondaryIndex = "STUCCNM").
  • The EGL field name for the comparison value (property dliFieldName = "STUCCNM" and then the corresponding field name customerName).
In some cases, there might be a secondary index on a lower level segment in the physical database. In this case, the database structure is inverted. For example, if there is a secondary index on the orderReference field in the order segment, the database hierarchy as viewed by your program needs to be reflected in the corresponding PCB record is as follows:
// orders view of customer database 
ordersByReferencePCB DB_PCBRecord 
  { @PCB { pcbType = DB, pcbName = "STDCDBL", 
    secondaryIndex = "STFCORF",   //use DL/I name 
    hierarchy = [ 
        @relationship { segmentRecord = "OrderRecordPart" }, 
        @relationship { segmentRecord = "LocationRecordPart", 
                                    parentRecord = "OrderRecordPart" }, 
        @relationship { segmentRecord = "CustomerRecordPart", 
                                    parentRecord = "LocationRecordPart" }, 
        @relationship { segmentRecord = "ItemRecordPart", 
                                    parentRecord = "OrderRecordPart" }
        ]}
  }; 
end 
Assuming the order reference number is unique to each customer and order, the you do not need to use keys for the location and customer segments. Assuming the ordersByReferencePCB is now your default PCB, you can retrieve both the order and the customer by modifying the SSA to delete the comparison for the location and customer segments as follows:
get myOrder, myCustomer with #dli{ 
  GU STPCORD*D (STQCODN = :myOrder.orderReference) 
    STSCLOC 
    STSCCST };

Using path calls to access multiple segments

If you invoke an EGL I/O statement with a dependent segment record object, you can read in any of the segments on the path from the root to the object at the same time. You can do this simply by adding the records for the path call to the statement. For example, when retrieving an order segment from our example customer database, you can read in the customer, location, and order segments on the same call:
get myCustomer, myLocation, myOrder;
This statement generates the following DL/I pseudocode:
GU STSCCST*D (STQCCNO = :myCustomer.customerNo) 
   STSCLOC*D (STQCLNO = :myLocation.locationNo)
   STPCORD   (STQCDDN = :myOrder.orderDateNo)
If you use the D command code on a get...forUpdate statement, the subsequent replace statement affects every segment retrieved. You can prevent replacement of a selected segment by specifying an explicit N command code in the SSA for the replace keyword, as in the following example, which replaces only the location and order segments:
get myCustomer, myLocation, myOrder forUpdate;
replace myOrder with #dli{
	REPL STSCCST*N
	     STSCLOC*N
       STPCORD };
The default DL/I call EGL builds for a delete function that follows a get forUpdate statement with D command codes does not delete each segment retrieved. It deletes only the target segment specified on the delete statement.

Reading all segments with a single I/O statement

You can use a single I/O statement to read all segments in a database. If you issue a DL/I get next statement with no SSAs, DL/I returns the next segment in the database regardless of its type. Follow these steps to use this technique:
  1. Write a get next statement for the record that represents the largest segment in the database. This ensures that any segment you read will not exceed allocated memory.
  2. Add the default DL/I call to your code. Edit the #dli directive to delete the single SSA.
  3. Create records matching the other segments in the database. Declare the records in the program and specify that each of the records redefines the record used in step 1 above so that all the records occupy the same area of memory.
  4. Check dliVar.segmentName after the get next statement to determine the type of segment that was retrieved.
  5. Access the retrieved segment from the corresponding record structure.
Here is an example of code that will print everything in the customer database. In this example, HistoryRecordPart is the largest DLISegment record:
myHistory HistoryRecordPart
redefCustomer CustomerRecordPart {redefines=myHistory};
redefLocation LocationRecordPart {redefines=myHistory};
...


//read next segment, whatever type it is, into history record
while (myHistory not EOF)
	get next myHistory with #dli{
		GN };

	//so what type was it?
	case (dliVar.segmentName)
		when "STSCCST"                   // it was a customer
			printCustomer();
		when "STSCLOC"                   // it was a location
			printLocation();
		...
	end
end

Using dynamic arrays

You can use the get and get next statements to retrieve DL/I segments to a dynamic array. Because there is no key field for the array itself (only for members of the array), you must use some special techniques so that EGL will create the correct code.

Consider the situation in which you have defined record variables and a dynamic array as follows:
myCustomer CustomerRecordPart;
myLocation LocationRecordPart;
myOrder OrderRecordPart;
myOrderArray OrderRecordPart [] {maxsize = 20};  // array of orders
You can use a get statement to fill the array the first time and a get next statement to retrieve a second group of 20 orders as follows:
myCustomer.customerNo = "123456";
myLocation.locationNo = "ABCDEF";
myOrderDateNo = "20050730A003";
get myOrderArray;                  // fill the array the first time
... do some processing
get next myOrderArray;          // get the next batch of 20 orders
EGL creates the following pseudo-DL/I code for the get statement:
get myOrderArray with #dli{
  GU STSCCST (STQCCNO = :CustomerRecordPart.customerNo)
    STSCLOC (STQCLNO = :LocationRecordPart.locationNo)
    STPCORD (STQCODN = :OrderRecordPart.orderDateNo)
  GN STPCORD };
EGL creates the following pseudo-DLI code for the get next statement:
get next myOrderArray with #dli{
  GN STPCORD };

Filling the dynamic array for the first batch of 20 orders with a get statement requires an initial GU call followed by a loop of GN calls until the array is full or DL/I runs out of segment occurrences. In the pseudo-DL/I code that EGL creates, the GU call retrieves the first order segment. EGL treats the GN call as a loop and provides the logic to loop until the array is full or DL/I runs out of segment occurrences. Similarly, EGL treats the get next statement as a loop and provides the loop control logic for you.

The get next statement provides the correct pseudo-DL/I code to retrieve the second batch of orders. However, the pseudo-DL/I code for the get statement is not exactly correct -- the names of the qualifiers for the host variables are not the names of the record variables in your program. You can solve this problem in several ways:
  • Name your DL/I segment records with the same name as the segments in the DL/I database. Also change your record variable names to be the same name as the segments in the DL/I database. For example, change the customer record part definition from the following:
    Record CustomerRecordPart type DLISegment 
      { segmentName="STSCCST", keyItem="customerNo" } 
      10 customerNo char(6) { dliFieldName = "STQCCNO" }; //key field 
      ... 
    end
    to the following:
    Record STSCCST type DLISegment 
      { segmentName="STSCCST", keyItem="customerNo" } 
      10 customerNo char(6) { dliFieldName = "STQCCNO" }; //key field 
      ... 
    end
    Make similar changes for the location segment (STSCLOC) and the order segment (STPCORD). Then change your PSB record to use the new names as follows:
    Record CustomerPSBRecordPart type PSBRecord { defaultPSBName="STBICLG" } 
      // database PCB 
      customerPCB DB_PCBRecord { @PCB { pcbType = DB, pcbName = "STDCDBL", 
      hierarchy = [ @relationship { segmentRecord = "STSCCST" },
           @relationship {segmentRecord="STSCLOC",
                          parentRecord="STSCCST"},
           @relationship {segmentRecord="STPCORD",
                          parentRecord="STSCLOC"}
           ]}};
    
    end
    Change the declaration of your record variables and myOrderArray so that they reference the new DL/I segment record parts as follows:
    STSCCST STSCCST;
    STSCLOC STSCLOC;
    STPCORD STPCORD;
    myOrderArray STPCORD [] {maxsize = 20};  // array of orders
    Now when you issue the following get statement:
    get myOrderArray;             // fill the array the first time
    EGL creates the following pseudo-DL/I code for the get statement and the host variable qualifiers use the correct record variable names:
    get myOrderArray with #dli{
      GU STSCCST (STQCCNO = :STSCCST.customerNo)
        STSCLOC (STQCLNO = :STSCLOC.locationNo)
        STPCORD (STQCODN = :STPCORD.orderDateNo)
      GN STPCORD };

    Because EGL creates the default SSAs from the PCB hierarchy information, this is an easy way of ensuring that the variable names EGL uses match your program's record variable declarations. The disadvantage is that this technique does not follow the general practice of using different names for parts and variables.

  • Use the #dli directive and explicit DL/I database I/O as follows:
    get myOrderArray with #dli{
      GU STSCCST (STQCCNO = :myCustomer.customerNo)
        STSCLOC (STQCLNO = :myLocation.locationNo)
        STPCORD (STQCODN = :myOrder.orderDateNo)
      GN STPCORD };
    The advantage of this technique is that it is very clear which host variables are being used. This technique is particularly useful if you must change the default DL/I code -- for example if you do not know the first order number for the customer and want to use the following DL/I code:
    myOrder.orderDateNo = "";
    get myOrderArray with #dli{
      GU STSCCST (STQCCNO = :myCustomer.customerNo)
        STSCLOC (STQCLNO = :myLocation.locationNo)
        STPCORD (STQCODN >= :myOrder.orderDateNo)   // using >= instead of =
      GN STPCORD };

    The disadvantage of this technique is the same as for any explicit I/O -- if the hierarchy or the key fields change, the explicit DL/I database I/O statement does not change automatically.

  • Modify the definition for each of your DL/I segment records to include the hostVarQualifier property with the name of your program record variable. For example, change the CustomerRecordPart to the following:
    Record CustomerRecordPart type DLISegment 
      { segmentName="STSCCST", keyItem="customerNo",
        hostVarQualifier = "myCustomer" } 
      10 customerNo char(6) { dliFieldName = "STQCCNO" }; //key field 
      ... 
    end
    Now if you use the following record declarations and get statement:
    myCustomer CustomerRecodPart;
    myLocation LocationRecordPart;
    myOrder OrderRecordPart;
    myOrderArray OrderRecordPart [] {maxsize = 20};  // array of orders
    get myOrderArray;                  // fill the array the first time
    EGL will produce the correct pseudo-DL/I code:
    get myOrderArray with #dli{
      GU STSCCST (STQCCNO = :myCustomer.customerNo)
        STSCLOC (STQCLNO = :myLocation.locationNo)
        STPCORD (STQCODN = :myOrder.orderDateNo)
      GN STPCORD };

    The advantage of this technique is that you can use implicit DL/I database I/O and have different names for the record variables and the DL/I segment records. The disadvantage is that EGL now uses myCustomer as the qualifier for every implicit DL/I database I/O statement that uses the CustomerRecordPart, LocationRecordPart, and OrderRecordPart.


Feedback