DBMS_PIPE.UNPACK_MESSAGE(message_out); DBMS_OUTPUT.PUT_LINE ('message unpacked: '||message_out); END; / message unpacked: This is my message PL/SQL procedure successfully completed. The only difference in the second test was that RESET_BUFFER was called prior to packing and unpacking the message. Furthermore, subsequent executions of the first test block completed successfully even though RESET_BUFFER was not explicitly called. So the real answer to the first question appears to be that packed messages can be unpacked prior to sending as long as RESET_BUFFER has been previously called in the session. This confuses me, and I don't like the fact that Oracle does not expose more details about the inner workings of the local message buffer in relation to packing and unpacking messages. The second question ("Do PACK_MESSAGE and UNPACK_MESSAGE use a common buffer?") is a little trickier. It actually occurred to me only after exploring the first question about unpacking a packed buffer before sending. I wondered whether packing and unpacking messages could happen independently of each other in the message buffer, or whether the session message buffer was essentially a single slot with room for only one message. I expanded my earlier test script into the following: /* Filename on companion disk: pipe2.sql */* DECLARE test_pipename VARCHAR2(30):='OPBIP_TEST_PIPE2'; call_status INTEGER; message1_out VARCHAR2(2000); message2_out VARCHAR2(2000); BEGIN DBMS_PIPE.RESET_BUFFER; /* make sure pipe is empty */ call_status := DBMS_PIPE.CREATE_PIPE(test_pipename); DBMS_PIPE.PURGE(test_pipename); /* pack and send first message */ DBMS_PIPE.PACK_MESSAGE('This is message one'); call_status := DBMS_PIPE.SEND_MESSAGE(test_pipename); DBMS_OUTPUT.PUT_LINE('call status send1: '||TO_CHAR(call_status)); /* now pack second message without sending */ DBMS_PIPE.PACK_MESSAGE('This is message two'); /* receive, unpack and print message */ call_status := DBMS_PIPE.RECEIVE_MESSAGE(test_pipename); DBMS_OUTPUT.PUT_LINE('call status receive1: '||TO_CHAR(call_status)); DBMS_PIPE.UNPACK_MESSAGE(message1_out); DBMS_OUTPUT.PUT_LINE('message unpacked: '||message1_out); /* now send message two is it still there? */ call_status := DBMS_PIPE.SEND_MESSAGE(test_pipename); DBMS_OUTPUT.PUT_LINE('call status send2: '||TO_CHAR(call_status)); /* receive, unpack and print message */ call_status := DBMS_PIPE.RECEIVE_MESSAGE(test_pipename); DBMS_OUTPUT.PUT_LINE('call status receive2: '||TO_CHAR(call_status)); DBMS_PIPE.UNPACK_MESSAGE(message2_out); DBMS_OUTPUT.PUT_LINE('message unpacked: '||message2_out); END; / call status send1: 0 call status receive1: 0 [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 166 message unpacked: This is message one call status send2: 0 call status receive2: 0 message unpacked: This is message one PL/SQL procedure successfully completed. Notice that all calls to SEND_MESSAGE and RECEIVE_MESSAGE returned 0, indicating success. However, message two was never sent or received; instead, message one was sent and received twice. This indicates that the message buffer can contain only one message at a time for either sending or receiving. Receiving message one from the pipe overlaid message two in the buffer. It is interesting that the second call to SEND_MESSAGE sent the message that was just unpacked into the buffer, not the last message packed. It seems that a message that has been received and unpacked can also be sent without being repacked. As with the first question, this is somewhat confusing and counterintuitive, and again begs for more detailed documentation from Oracle on DBMS_PIPE. One idea this second test gave me was forwarding messages from one pipe to another without consuming them. I developed a procedure to do just that; it's discussed later in "Section 3.1.7.3, "The dbpipe utility package"." The concept of "packing" message items into a buffer suggested that perhaps the items were also being compressed somehow. If this were true, then message items containing strings of repeating characters should pack tightly into the 4096−byte buffer, and the buffer could contain more than 4096 bytes worth of messages. In order to test this theory, I developed a procedure to stuff as many copies as possible of an input string into the message buffer and count exactly how big the resulting message is. Here is the source code for the pipe1 procedure: /* Filename on companion disk: pipe1.sql */* CREATE OR REPLACE PROCEDURE pipe1 (message_item_IN IN VARCHAR2) /* || Tests whether DBMS_PIPE compresses || string message items on packing by || stuffing buffer full and counting total || size of message. || || Author: John Beresniewicz, Savant Corp || Created: 09/16/97 || */ IS test_pipename VARCHAR2(30):='OPBIP_TEST_PIPE'; call_status INTEGER; item_counter INTEGER :=0; total_msg_size INTEGER :=0; buffer_full EXCEPTION; PRAGMA EXCEPTION_INIT(buffer_full,−6558); BEGIN /* make sure pipe is empty and buffer initialized */ call_status := DBMS_PIPE.CREATE_PIPE(test_pipename); DBMS_PIPE.PURGE(test_pipename); DBMS_PIPE.RESET_BUFFER; BEGIN /* buffer_full exception ends the loop */ LOOP DBMS_PIPE.PACK_MESSAGE(message_item_IN); [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 167 /* || increment total size: 1 byte for datatype and || 2 bytes for item length */ total_msg_size := total_msg_size+3+LENGTHB(message_item_IN); item_counter := item_counter +1; END LOOP; EXCEPTION WHEN buffer_full THEN /* test if message can send OK on buffer_full */ call_status := DBMS_PIPE.SEND_MESSAGE(test_pipename); IF call_status = 0 THEN /* OK, display results for this message item */ DBMS_OUTPUT.PUT_LINE ('Items Packed: '||TO_CHAR(item_counter)); DBMS_OUTPUT.PUT_LINE ('Total Msg Size: '||TO_CHAR(total_msg_size+1)); ELSE DBMS_OUTPUT.PUT_LINE ('Pipe Send Error, return code: '|| TO_CHAR(call_status)); END IF; WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Oracle Error: '||TO_CHAR(SQLCODE)); END; END pipe1; There are a couple of useful techniques demonstrated in pipe1. For one, the EXCEPTION_INIT pragma is used to define an exception to trap the buffer full condition. This exception is then used to exit the message packing loop, which is somewhat unusual but precisely what we need in this case. Also, the pipe is created and immediately purged to ensure that it is empty for the test. The purge is done because the DBMS_PIPE.CREATE_PIPE call will succeed if the pipe already exists, and it may contain messages, which could interfere with the test. Since the test is designed to measure how much can be packed into the local buffer, DBMS_PIPE.RESET_BUFFER is called to make sure that the buffer starts off completely empty. The pipe1 procedure is not particularly useful, except to answer the question about whether message items are compressed. Well, here are the results from several calls to pipe1 using different message items: SQL> execute pipe1('This is a long text message'); Items Packed: 136 Total Msg Size: 4081 PL/SQL procedure successfully completed. SQL> execute pipe1(RPAD(' ',2000)); Items Packed: 2 Total Msg Size: 4007 PL/SQL procedure successfully completed. SQL> execute pipe1('1'); Items Packed: 1023 Total Msg Size: 4093 PL/SQL procedure successfully completed. [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 168 The tests show there is no data compression taking place when message items are packed into the buffer. This is most clearly seen in the second test, where only two strings of 2000 blanks could be packed into the buffer. Also note the inefficiency of packing many small items into a message (third test) since the three bytes of per−item overhead account for most of the space used. The conclusion I've drawn from all these tests: the inner workings of DBMS_PIPE are not at all intuitive or obvious. I'm still somewhat confused by some of the test results, and the lack of clear documentation by Oracle is frustrating. The good news is that reliable pipe−based communications can be achieved by following the simple guidelines and best practices discussed previously. Doing so will help avoid programs that enter those murky areas which my testing purposely explored. 3.1.7.3 The dbpipe utility package While conducting my experiments on DBMS_PIPE, I had a couple of ideas for some utility programs. One thing I wanted was a kind of "sniffer" program that could show me the contents of any pipe. Since I was not following safe pipe programming practices (on purpose) −− I kept stuffing all kinds of messages into all kinds of pipes −− I often did not know what had gotten where. I needed a generic program that could show me the contents of any pipe without knowing message specifics such as number of items and their datatypes. Another idea was to forward a message from one pipe to another. This seemed potentially useful, perhaps as the basis for a kind of pipe−based broadcasting or chain−letter application. It turns out that one key to both of these utilities was creating utility programs that could unpack and repack any message without knowing the form of its contents in advance. These ideas became the dbpipe package. Here is the package specification: /* Filename on companion disk: dbpipe.sql */* CREATE OR REPLACE PACKAGE dbpipe /* || Package of interesting utilities illustrating use of || DBMS_PIPE programs. Includes a forwarding program to || pass pipe messages from one pipe to another, a peek || program to inspect and replace pipe messages, and || generic unpack and pack programs. || || Author: John Beresniewicz, Savant Corp || || 10/10/97: added purge_all_pipes || 10/10/97: made cannot_use_pipe a public || exception || 10/05/97: added makepipe and closepipe || 09/28/97: added invalid_item_type exception to || unpack_to_tbl || 09/25/97: added safe or cool mode to forward || 09/21/97: created || || Compilation Requirements: || || EXECUTE on DBMS_PIPE || EXECUTE on DBMS_SESSION || SELECT on SYS.V_$DB_PIPES || || Execution Requirements: || */ AS /* || declare exceptions raised by various DBMS_PIPE || programs when user cannot access a private pipe || or pipename is null */ [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 169 cannot_use_pipe EXCEPTION; PRAGMA EXCEPTION_INIT(cannot_use_pipe,−23322); null_pipename EXCEPTION; PRAGMA EXCEPTION_INIT(null_pipename,−23321); /* || message_rectype records can capture any single || item which can be packed into a DBMS_PIPE message */ TYPE message_rectype IS RECORD (item_type INTEGER ,Mvarchar2 VARCHAR2(4093) ,Mdate DATE ,Mnumber NUMBER ,Mrowid ROWID ,Mraw RAW(4093) ); /* || message_tbltype tables can hold an ordered list of || message items, thus any message can be captured */ TYPE message_tbltype IS TABLE OF message_rectype INDEX BY BINARY_INTEGER; /* || unpacks message buffer into table, || optionally displays message to screen */ PROCEDURE unpack_to_tbl (message_tbl_OUT OUT message_tbltype ,display_TF IN BOOLEAN := FALSE); /* || packs message buffer from message table */ PROCEDURE pack_from_tbl (message_tbl_IN IN message_tbltype); /* || forward a message from one pipe to another, || supports two techniques (safe and cool) */ PROCEDURE forward (from_pipename_IN IN VARCHAR2 ,to_pipename_IN IN VARCHAR2 ,timeout_secs_IN IN INTEGER := 10 ,safe_mode_IN IN BOOLEAN := FALSE); /* || takes sample message from a pipe and displays the || contents, replaces message back into pipe if || boolean parameter is TRUE */ PROCEDURE peek (pipename_IN IN VARCHAR2 ,timeout_secs_IN IN INTEGER := 60 ,replace_message_TF IN BOOLEAN := TRUE); /* || encapsulates DBMS_PIPE.CREATE_PIPE and returns || FALSE if ORA−23322 is raised, indicating || the pipename is already used and not accessible || to the caller */ FUNCTION makepipe (pipename_IN IN VARCHAR2 ,maxsize_bytes_IN IN INTEGER DEFAULT 8192 [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 170 . has been previously called in the session. This confuses me, and I don't like the fact that Oracle does not expose more details about the inner workings of the local message buffer in relation. is somewhat confusing and counterintuitive, and again begs for more detailed documentation from Oracle on DBMS_PIPE. One idea this second test gave me was forwarding messages from one pipe to. code: '|| TO_CHAR(call_status)); END IF; WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(&apos ;Oracle Error: '||TO_CHAR(SQLCODE)); END; END pipe1; There are a couple of useful techniques