Another usability issue with SET_SESSION_LONGOPS is that any values not set in the procedure call will be set to zero. Thus, if you want to increment different counter columns at different times in an application (for the same row in V$SESSION_LONGOPS), you must keep track of all counter values and pass them all in each time the procedure is called. Adding to the cumbersome nature of the long parameter list are the extremely long names of the package and procedure themselves. You really have to want, or, as is more likely, need to call SET_SESSION_LONGOPS in order to use it! These usability issues seemed to provide an opportunity to improve ease−of−use through encapsulation. I decided to build a package called longops to offer some relief. Here is the package specification for longops: /* Filename on companion disk: longops.sql */* CREATE OR REPLACE PACKAGE longops IS /* || Enhances DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS || by allowing individual columns to be updated without || passing all parameter values. || || Author: John Beresniewicz, Savant Corp || Created: 09/08/97 || || Compilation Requirements: || SELECT on SYS.V_$SESSION_LONGOPS || || Execution Requirements: || || */ /* returns a new V$SESSION_LONGOPS row index */ FUNCTION new_row RETURN BINARY_INTEGER; /* returns the last row index used */ FUNCTION current_row RETURN BINARY_INTEGER; /* makes a new row the current row */ PROCEDURE set_current_row (row_idx_IN IN BINARY_INTEGER); /* || Covers DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS */ PROCEDURE set_row (hint_IN IN BINARY_INTEGER ,context_IN IN NUMBER DEFAULT 0 ,stepid_IN IN NUMBER DEFAULT 0 ,stepsofar_IN IN NUMBER DEFAULT 0 ,steptotal_IN IN NUMBER DEFAULT 0 ,sofar_IN IN NUMBER DEFAULT 0 ,totalwork_IN IN NUMBER DEFAULT 0 ,appdata1_IN IN NUMBER DEFAULT 0 ,appdata2_IN IN NUMBER DEFAULT 0 ,appdata3_IN IN NUMBER DEFAULT 0); /* || Updates a single row in V$SESSION_LONGOPS || preserving values in columns corresponding || to parameters passed as NULL. */ PROCEDURE update_row (hint_IN IN BINARY_INTEGER DEFAULT current_row ,context_IN IN NUMBER DEFAULT NULL ,stepid_IN IN NUMBER DEFAULT NULL ,stepsofar_IN IN NUMBER DEFAULT NULL ,steptotal_IN IN NUMBER DEFAULT NULL [Appendix A] What's on the Companion Disk? 7.3.9 Tracking Long−Running Processes 386 ,sofar_IN IN NUMBER DEFAULT NULL ,totalwork_IN IN NUMBER DEFAULT NULL ,appdata1_IN IN NUMBER DEFAULT NULL ,appdata2_IN IN NUMBER DEFAULT NULL ,appdata3_IN IN NUMBER DEFAULT NULL); END longops; The real key to the package is the update_row procedure. This procedure allows the user to update individual columns in V$SESSION_LONGOPS for a given row without zeroing out the other columns. It does this by keeping a copy of the V$SESSION_LONGOPS rows that have been modified in a private PL/SQL table called my_longops_tab. Here is the definition of my_longops_tab: TYPE longops_tabtype IS TABLE OF sys.v_$session_longops%ROWTYPE INDEX BY BINARY_INTEGER; my_longops_tab longops_tabtype; The current_row function and set_current_row procedure are used to maintain a context of which row is currently being modified. The presumption is that most users of SET_SESSION_LONGOPS will concentrate on a single row in V$SESSION_LONGOPS at a time. The set_row procedure covers SET_SESSION_LONGOPS but additionally saves the data to my_longops_tab. The body of the update_row procedure looks like this: PROCEDURE update_row (hint_IN IN BINARY_INTEGER DEFAULT current_row ,context_IN IN NUMBER DEFAULT NULL ,stepid_IN IN NUMBER DEFAULT NULL ,stepsofar_IN IN NUMBER DEFAULT NULL ,steptotal_IN IN NUMBER DEFAULT NULL ,sofar_IN IN NUMBER DEFAULT NULL ,totalwork_IN IN NUMBER DEFAULT NULL ,appdata1_IN IN NUMBER DEFAULT NULL ,appdata2_IN IN NUMBER DEFAULT NULL ,appdata3_IN IN NUMBER DEFAULT NULL) IS temp_hint_IN BINARY_INTEGER := hint_IN; BEGIN /* || First update saved row in my_longops_tab, any || parameters which are NULL will not change the || saved row. */ my_longops_tab(hint_IN).context := NVL(context_IN, my_longops_tab(hint_IN).context); my_longops_tab(hint_IN).stepid := NVL(stepid_IN, my_longops_tab(hint_IN).stepid); my_longops_tab(hint_IN).stepsofar := NVL(stepsofar_IN, my_longops_tab(hint_IN).stepsofar); my_longops_tab(hint_IN).steptotal := NVL(steptotal_IN, my_longops_tab(hint_IN).steptotal); my_longops_tab(hint_IN).sofar := NVL(sofar_IN, my_longops_tab(hint_IN).sofar); my_longops_tab(hint_IN).totalwork := NVL(totalwork_IN, my_longops_tab(hint_IN).totalwork); my_longops_tab(hint_IN).application_data_1 := NVL(appdata1_IN, my_longops_tab(hint_IN).application_data_1); my_longops_tab(hint_IN).application_data_2 := NVL(appdata2_IN, my_longops_tab(hint_IN).application_data_2); my_longops_tab(hint_IN).application_data_3 := NVL(appdata3_IN, my_longops_tab(hint_IN).application_data_3); /* || Now call DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS [Appendix A] What's on the Companion Disk? 7.3.9 Tracking Long−Running Processes 387 || passing all parameters from the row in my_longops_tab. */ DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS (hint=>temp_hint_IN ,context=>my_longops_tab(hint_IN).context ,stepid=>my_longops_tab(hint_IN).stepid ,stepsofar=>my_longops_tab(hint_IN).stepsofar ,steptotal=>my_longops_tab(hint_IN).steptotal ,sofar=>my_longops_tab(hint_IN).sofar ,totalwork=>my_longops_tab(hint_IN).totalwork ,application_data_1=> my_longops_tab(hint_IN).application_data_1 ,application_data_2=> my_longops_tab(hint_IN).application_data_2 ,application_data_3=> my_longops_tab(hint_IN).application_data_3 ); /* set the current row */ set_current_row(hint_IN); END update_row; The update_row procedure is pretty straightforward. One subtlety is that the hint_IN parameter defaults to the function current_row. This allows us to call update_row without even passing in a row identifier as long as we want to modify the same row as last time. Using the longops package, the example for DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS can be rewritten as follows: /* Filename on companion disk: apinex3.sql */* BEGIN −− get new row in V$SESSION_LONGOPS and set totalwork longops.set_row(longops.new_row,totalwork_IN=>1000); −− Do operation 1000 times and record FOR i IN 1 1000 LOOP −− update sofar each time longops.update_row(sofar_IN=>i); −− update stepsofar every 100 iterations IF MOD(i,100) = 0 THEN longops.update_row(stepsofar_IN=>i/100); END IF; END LOOP; END; This code is much more readable than the earlier example. The calls are shorter in length, yet easier to understand. Overall readability is also improved by being able to update columns individually and not being forced to overload each call with a long list of saved parameter values. 7.2 DBMS_APPLICATION_INFO Interface 8. Managing Large Objects Copyright (c) 2000 O'Reilly & Associates. All rights reserved. [Appendix A] What's on the Companion Disk? 7.3.9 Tracking Long−Running Processes 388 Chapter 8 389 8. Managing Large Objects Contents: Getting Started with DBMS_LOB LOB Concepts DBMS_LOB Interface Oracle8 and PL/SQL8 support the storage and manipulation of large objects (a.k.a. LOBs). A LOB, which can be a column in a table or an attribute of an object type, may store up to four gigabytes of data, such as character text, graphic images, video, or "raw" data. The DBMS_LOB package (new to Oracle8) provides a set of procedures and functions to access and manipulate LOBs from within PL/SQL programs. You can also manipulate LOBs from within SQL; refer to the Oracle documentation for these SQL−specific aspects of LOB management. 8.1 Getting Started with DBMS_LOB The DBMS_LOB package is created when the Oracle8 database is installed. The dbmslob.sql script (found in the built−in packages source directory, as described in Chapter 1, Introduction) contains the source code for this package's specification. This script is called by catproc.sql, which is normally run immediately after database creation. The script creates the public synonym DBMS_LOB for the package and grants EXECUTE privilege on the package to public. All Oracle users can reference and make use of this package. 8.1.1 DBMS_LOB Programs Table 8.1 summarizes the programs available in DBMS_LOB. Table 8.1: DBMS_LOB Programs Name Description Use in SQL APPEND Appends the contents of a source internal LOB to a destination internal LOB No COMPARE Compares two LOBs of the same type; parts of LOBs can also be compared Yes COPY Copies all or part of the contents of a source internal LOB to a destination internal LOB No ERASE Erases all or part of an internal LOB No FILECLOSE Closes an open BFILE No FILECLOSEALL Closes all open BFILEs No FILEEXISTS Checks if a given file exists Yes FILEGETNAME Returns directory alias and filename of given file locator No FILEOPEN Opens a BFILE for read−only access No FILEISOPEN Determines if a BFILE was opened with the given file locator Yes GETLENGTH Returns the length of the input LOB; length is in bytes for BFILEs and BLOBs; length is in characters for CLOBs and NCLOBs Yes INSTR Returns matching offset location in the input LOB of the Nth occurrence of a Yes 8. Managing Large Objects 390 . package (new to Oracle8 ) provides a set of procedures and functions to access and manipulate LOBs from within PL/SQL programs. You can also manipulate LOBs from within SQL; refer to the Oracle documentation. Started with DBMS_LOB The DBMS_LOB package is created when the Oracle8 database is installed. The dbmslob.sql script (found in the built−in packages source directory, as described in Chapter 1,. Managing Large Objects Contents: Getting Started with DBMS_LOB LOB Concepts DBMS_LOB Interface Oracle8 and PL/SQL8 support the storage and manipulation of large objects (a.k.a. LOBs). A LOB,