DBMS_PIPE.UNPACK_MESSAGEmessage_out; DBMS_OUTPUT.PUT_LINE 'message unpacked: '||message_out; END; / message unpacked: This is my message PL/SQL procedure successfully completed.. This c
Trang 1DBMS_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
Trang 2message 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);
Trang 3/*
|| 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
Trang 4The 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
*/
Trang 5cannot_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