by George Stachnik
In the previous article in this series, we continued to explore IMAGE/SQL applications, and the intrinsics that they use to interact with databases. We saw examples of how IMAGE/SQL uses the DBGET intrinsic to retrieve records using a variety of different access methods (chained, serial, direct). This time around, we're going to begin examining intrinsics that are used to write records to a database. There are two of them: DBUPDATE and DBPUT. In this article, we'll focus on DBUPDATE. To begin with, you might be wondering why IMAGE/SQL has two different intrinsics for the same thing. DBUPDATE and DBPUT are both used for essentially the same purpose--to write records (or data entries, if you prefer) to an IMAGE/SQL database. So what's the difference? The answer is that the two intrinsics actually serve quite different purposes--or at least they did when the IMAGE DBMS was first designed. Over time, HP has enhanced IMAGE/SQL in response to user demands, and in the process, the differences between DBUPDATE and DBPUT have become somewhat blurred. In this article, we'll take a historical point of view, and explain what the differences were and what they are today. Let's begin by explaining what the difference originally was.
Chains and PointersIn the last article, we explored the concept of chains in some detail. From a programmatic point of view, we saw that the DBFIND intrinsic can be used to locate chains of records. Once a chain has been located, the DBGET intrinsic can be used to retrieve records in the chain. Using chained access, DBGET can retrieve either the next record or the previous record in a chain. Because of the way pointers are structured, DBGET can navigate these chains very quickly. Let's take a closer look at how DBGET works to understand how. Suppose that you've used DBGET to retrieve the first Greg record (ORDER-NO = 0001) shown in Figure 1. In addition to the data shown in the figure (CUST-NAME, ORDER-NO and so forth), DBGET will also retrieve a pointer to the second Greg record. To better understand how these pointers are stored in the database, and represented in an application program, refer to Figure 2. Figure 2: Pointers in the STATUS-ARRAY002380 01 STATUS-ARRAY. 002390 05 CONDITION-CODE PIC S9(4) COMP. 002400 05 ENTRY-LENGTH PIC S9(4) COMP. 002410 05 RECORD-NUMBER PIC S9(9) COMP. 002420 05 CHAIN-LENGTH PIC S9(9) COMP. 002430 05 BACKWARD-POINTER PIC S9(9) COMP. 002440 05 FORWARD-POINTER PIC S9(9) COMP. 002450 054900 055000 CALL INTRINSIC "DBGET" USING DB-NAME, 055100 SET-NAME, 055200 DB-MODE, 055300 STATUS-ARRAY, 055400 ITEMS-LIST, 055500 ORDER-DETAIL-BUFFER, 055600 IGNORED-PARM 055700Figure 2 shows a fragment of COBOL code that calls the DBGET intrinsic (beginning at line 5500). Note that one of the parameters being passed to DBGET is the STATUS-ARRAY. This parameter is shared among almost all the IMAGE/SQL intrinsics. It's an array of data items that is used by IMAGE/SQL to track errors and to pass information from intrinsic to intrinsic. (It was discussed in detail in part 26 of this series). The COBOL code that defines the status array is shown at the top of Figure 2 (beginning with line 2380). Up until now, our discussions of the status array have focused upon the CONDITION-CODE. Now we're going to turn our attention to the items called CHAIN-LENGTH, BACKWARD-POINTER, and FORWARD-POINTER:
DBUPDATE and PointersThe downside of IMAGE/SQL's system of pointers is that it can get in the way when you need to change the contents of a record. In the original version of IMAGE/SQL, the rule was as follows: You can use DBUPDATE to change the contents of any data item in a record except for "critical items." Critical items are items that can't be changed without affecting the pointers specifically, sort items and search items. Let's return to Figure 1 for a moment to see why critical items were originally considered "hands off" for DBUPDATE. Imagine that an application program was working its way down the Greg Chain shown in the figure. Suppose that you had just used DBGET to retrieve the 2nd Greg record in the chain (order number 0009). Using DBUPDATE, you are allowed to change the price, or the quantity, or even the order number in this record, because none of these are critical items. That is, none of them has any bearing on the record's location in the chain, or in the dataset. However, you could not change the contents of the CUST-NAME item from "Greg" to something different, say, to "Ken." CUST-NAME is considered a critical item because the previous and the next record in the chain contain pointers to the record that we're working on. If DBUPDATE allowed you to change Greg's name to Ken, it would also have to make a number of other changes in the database. The length of the Greg chain (which was 3) would have to be changed to 2. This entails changes to the master dataset CUSTOMER-MASTER, where the chain lengths are stored. Also the records for order number 0001 and 0004 would have to be changed. Currently, these records contain pointers to order number 0009, but if we're going to change the contents of the critical CUST-NAME item from Greg to something else, then this record will no longer be a member of the Greg chain. So the forward pointer from order number 0001 would have to be changed to point to 0004, and, similarly, the backward pointer in 0004 would have to be changed to reference 0001. If all that seems like a lot of work, well, "you ain't seen nothin' yet." If we allow DBUPDATE to change the name from "Greg" to Ken" in order number 0009, then it will no longer be a part of the Greg chain. But which chain will it be a part of? DBUPDATE will now literally have to do a new DBFIND to see if there is a record in CUSTOMER-MASTER that matches the new name "Ken." If not (assuming that this is a manual master), it would have to return an error. In Figure 1, we can see that there is a "Ken" chain that already exists. So order number 0009 would then be added to that chain, which is currently made up of orders number 0003, 0005, 0008, and 0011. You can see that allowing DBUPDATE to make changes to search items like CUST-NAME takes an intrinsic that's blazingly fast (so long as it only writes to non-critical items) and changes it to an intrinsic that could be numbingly slow (because of all the work it now would now have to do). This is especially true if you're operating on a comparatively slow hardware platform, like one of the early versions of the HP 3000, which were all that was available when IMAGE/SQL was originally designed. Consequently, early versions of IMAGE/SQL did not allow DBUPDATE to change critical items. If you wanted to change the CUST-NAME field in Figure 1, you'd have to physically delete the record (using DBDELETE) and then replace it with an entirely new record (using DBPUT).Critical Item UpdateOver the years, HP brought newer and faster hardware to HP 3000 customers. Larger memory configurations made it possible to cache entire databases in memory. This greatly reduced the performance impact of changes to critical items. By the early 1990s, the performance penalty had been reduced to milliseconds, simply because the hardware had gotten so fast. Customers told HP that it was time to "take the gloves off" of DBUPDATE. So, in the mid 1990s, HP finally made it possible for DBUPDATE to change critical items. This so called "Critical Item Update" feature meant that application programs could use DBUPDATE to change any item in a data entry. In order to ensure compatibility with earlier versions, the new feature was designed so that it must be enabled using DBUTIL. If it isn't enabled, then DBUPDATE works just as it always has. This ensured that application programs that assumed that DBUPDATE could not change critical items would not be "broken" by the enhancement. But if the Critical Item Update feature is enabled, then DBUPDATE will allow an application program to change the contents of any item (including critical items), and automatically make the corresponding changes to the pointers and so forth that we've just described. Because most legacy applications were written prior to the mid 90s, most IMAGE/SQL applications that use DBUPDATE behave as if they could not change critical items. Instead, when critical items must be changed, they use DBDELETE and DBPUT to replace the entire record. Clearly, if an application does a lot of this sort of thing, there could now be a significant performance payoff from enabling Critical Item Update and enhancing the program logic to use DBUPDATE instead of DBDELETE and DBPUT.Sort ItemsWe've seen that search items (like CUST-NAME in Figure 1) cannot be changed using DBUPDATE unless Critical Item Update is enabled. Search items are therefore referred to as critical items. There is another kind of critical item, and that is a sort item. Suppose that the CUST-NAME chain shown in Figure 1 were defined in the schema to be a sorted chain. Ordinarily, chains of records are not sorted; they are built in chronological sequence. That is, each record that is added to a chain is simply tacked on to the end of the chain. But when a chain is defined to be a sorted chain, then a specific data item must be identified in the schema as a sort item, and the chain is maintained in sequence by the sort item. It's important to understand that defining a chain to be a sorted chain is not free from a performance perspective. You must pay (and pay dearly) for the convenience of having every chain maintained in sorted order. The payments are in the form of additional overhead every time you make a change to a chain of records. Let's see how. First of all, in order to define a sorted chain, you name the sort item in the schema. For example, suppose we wanted the CUST-NAME chains to be sorted by the contents of DESC. When we defined that data item in the schema, we'd specify the sort field as follows:ENTRY: CUST-NAME(CUSTOMER-MASTER(DESC)),The data entry CUST-NAME is a search item, which defines a chain. We can tell because it's followed by the name of a corresponding master dataset (CUSTOMER-MASTER). The CUST-NAME chain of records is sorted by the DESC data item, and we can tell that because it appears in parenthesis after the master dataset name. This is how we know that DESC is a sort item. In the case of Figure 1, assume that the CUST-NAME chain is sorted by the description (DESC). As a result, the order for a "Do-job" appears at the head of the chain, followed by the order for a "thingumbob," followed in turn by the order for the "Widget." There are a number of important things to note about sorted chains:
Sample CodeWe'll wrap this article up by looking at some sample COBOL code that uses DBGET and DBUPDATE to change a record in place. (Note that this sample code does not use the same database that we've been showing in Figure 1.) Look at the sample code fragment in Figure 3. DBGET is used to retrieve a record from a dataset called PARTS. The record is stored in a data item called PARTS-BUFFER. We are using mode 7, which retrieves a record using a search item value. In this example, we prompt the user for the search item beginning at line 59400. The COBOL ACCEPT verb allows the user to type the desired part number. The actual call to DBGET follows at line 60400. Lines 61300 through 61800 contain error handling code. Assuming that there has been no error (that is, that the part number entered by the user actually exists in the database), we will fall through to line 62100. Here we display the information that we've retrieved from the database, including the part number, the description (line 62700), and the current quantity in stock (line 63200). The sample program fragment shown in Figure 3 gives the user opportunities to change each of these three values using the COBOL ACCEPT verb. These opportunities are at line 62500, 63000, and 63500. The user can either accept the existing values or type new ones. Either way, the final values are placed in PARTS-BUFFER. Then, at line 64100, we call the DBUPDATE intrinsic to update the information in the database using the new data in PARTS-BUFFER. The error handling routine that begins at line 64900 is instructive. If CONDITION-CODE contains a value of zero after we return from DBUPDATE, we will fall through to line 65900, which displays a message that the part was successfully updated. On the other hand, if we determine that there has been an error (that is, that CONDITION-CODE was non-zero), we must test for specific values. Figure 3: Using DBGET and DBUPDATE058500 UPDATE-PART-INFORMATION. 058800 059400 DISPLAY "Please enter the Part Number to update: " 059500 WITH NO ADVANCING. 059600 ACCEPT SEARCH-ITEM-VALUE FREE. 059800 059900 MOVE 7 TO DB-MODE. 060000 MOVE "@;" TO ITEMS-LIST. 060100 MOVE "PARTS;" TO SET-NAME. 060200 060400 CALL INTRINSIC "DBGET" USING DB-NAME, 060500 SET-NAME, 060600 DB-MODE, 060700 STATUS-ARRAY, 060800 ITEMS-LIST, 060900 PARTS-BUFFER, 061000 SEARCH-ITEM-VALUE. 061200 061300 IF CONDITION-CODE NOT = 0 061500 IF CONDITION-CODE = 17 061600 DISPLAY "The Part does not exist." 061700 ELSE 061800 PERFORM IMAGE-ERROR 061900 ELSE 062100 DISPLAY "Current Part Number = ",PART-NO-VALUE 062200 OF PARTS-BUFFER 062300 DISPLAY " New Value : " 062400 WITH NO ADVANCING 062500 ACCEPT PART-NO-VALUE OF PARTS-BUFFER FREE 062700 DISPLAY "Current Description = ",PART-DESCRIPTION-VALUE 062800 DISPLAY " New Value : " 062900 WITH NO ADVANCING 063000 ACCEPT PART-DESCRIPTION-VALUE FREE 063200 DISPLAY "Current Quantity in Stock = ",QTY-IN-STOCK-VALUE 063300 DISPLAY " New Value : " 063400 WITH NO ADVANCING 063500 ACCEPT QTY-IN-STOCK-VALUE FREE 063700 063800 MOVE 1 TO DB-MODE 063900 064100 CALL INTRINSIC "DBUPDATE" USING DB-NAME, 064200 SET-NAME, 064300 DB-MODE, 064400 STATUS-ARRAY, 064500 ITEMS-LIST, 064600 PARTS-BUFFER 064800 064900 IF CONDITION-CODE NOT= 0 065100 IF CONDITION-CODE = 41 065200 DISPLAY "The value of the Part Number", 065201 " cannot be changed" 065210 ELSE 065300 IF CONDITION-CODE = 42 065400 DISPLAY "You do not have the ability", 065500 " to update the data item." 065600 ELSE 065700 PERFORM IMAGE-ERROR 065800 ELSE 065900 DISPLAY "The Part was successfully updated". 066000 066100 DISPLAY SPACE.A condition code of 41 (line 65100) means that we tried to change the part number, which is a critical item. Don't forget that this error will not occur if Critical Item Update has been enabled. In that case, IMAGE/SQL would have changed the part number (paying the performance penalty to re-chain the record if necessary), and returned a condition code of zero (assuming that nothing else went wrong.) Changing Non-Critical ItemsIn Figure 3, Line 65300 reminds us that there is at least one other reason why DBUPDATE might not let us change a particular item in a record. Suppose that a user who is running the program shown in Figure 3 tries to change a non-critical item, such as QTY-IN-STOCK. Ordinarily, you wouldn't expect DBUPDATE to give you any trouble. But DBUPDATE is in the unenviable position of having to report trouble from another quarter. Line 65300 of our program tests for a condition code of 42. The error message indicates that "You do not have the ability to update the data item." This is a trap for a security violation. Recall that the database schema contains a read/write list for every item in the database. This list defines who can read or write each individual data item in the database. Depending on the database password used to open the database, a user may or may not be allowed to update a particular item. If IMAGE/SQL detects that the user is trying to update an item that he's not allowed to change, the error code of 42 will be returned from DBUPDATE. There's one more error check in the program code shown in Figure 3. If the error that we've trapped is neither 41 nor 42, then some unforeseen error has occurred. To handle that contingency, the program transfers control to a routine called IMAGE-ERROR, which closes the database and aborts the application program. (The code that makes up IMAGE-ERROR is not shown in Figure 3.) This kind of generic test for a non-zero condition code should be made after every IMAGE/SQL intrinsic call. We've seen how DBUPDATE is used to update records in a database. Next time, we'll turn our attention to DBPUT.George Stachnik works in Hewlett-Packard's Commercial Systems Division in Cupertino, California. He is responsible for the development and delivery of e3000 training. |