DBMS_PIPE.RECEIVE_MESSAGE followed immediately by DBMS_PIPE.SEND_MESSAGE. After all, forwarding should be fast −− so why bother with the overhead of unpacking and repacking? Well, it turns out that you cannot just receive and immediately send a message using DBMS_PIPE unless you have previously called the DBMS_PIPE.PACK_MESSAGE procedure. Why? I have no idea; it just seems to be another one of those mysteries of DBMS_PIPE that I happened to discover during my experimentation. I don't like the fact that it's mysterious, but I do like the fact that it works, so I used this "feature" to implement the cool forwarding mode. /* Filename on companion disk: dbpipe.sql. */* PROCEDURE forward (from_pipename_IN IN VARCHAR2 ,to_pipename_IN IN VARCHAR2 ,timeout_secs_IN IN INTEGER := 10 ,safe_mode_IN IN BOOLEAN := FALSE) IS call_status INTEGER; message_tbl message_tbltype; BEGIN /* initialize buffer */ DBMS_PIPE.RESET_BUFFER; IF NOT safe_mode_IN THEN /* || do an initial pack so COOL mode forwarding will work, || why this is necessary is unknown */ DBMS_PIPE.PACK_MESSAGE('bogus message'); END IF; /* || receive the message on from_pipename, if success || then forward on to_pipename */ call_status := DBMS_PIPE.RECEIVE_MESSAGE (pipename=>from_pipename_IN ,timeout=>timeout_secs_IN); IF call_status = 0 THEN /* || safe mode does full unpack and repack */ IF safe_mode_IN THEN unpack_to_tbl(message_tbl); pack_from_tbl(message_tbl); END IF; /* || OK, now send on to_pipename */ call_status := DBMS_PIPE.SEND_MESSAGE (pipename=>to_pipename_IN ,timeout=>timeout_secs_IN); END IF; END forward; [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 176 3.1.7.4 Implementing a server program One common application of DBMS_PIPE is to implement an external service interface, as mentioned previously. This interface allows Oracle users to communicate with host operating system programs and receive data from them into their session context. What about writing a service provider program internal to Oracle? That is, what about writing a PL/SQL program that will listen on a database pipe and provide certain Oracle−based services to client sessions connected to the same Oracle database? There are a number of possible applications of such internal service programs, including: • Complex calculation engines • Debug message logging • Audit message logging • Transaction concentrators • Batch program scheduling 3.1.7.5 The pipesvr package I have written a package that demonstrates how to use DBMS_PIPE to implement a basic PL/SQL server program and associated client programs. The package implements basic client−server communications, as well as a simple server−side debugger. Here is the specification for the pipesvr package: /* Filename on companion disk: pipesvr.sql */* CREATE OR REPLACE PACKAGE pipesvr AS /* || Illustrates the use of DBMS_PIPE to implement || communications between a PL/SQL background server || program and client programs. || || Clients communicate requests over a database pipe || on which the server listens and receive responses || on pipes unique to each session. || || The server can be set to place debugging info into a || table. || || Author: John Beresniewicz, Savant Corp || || 10/04/97: created || || Compilation Requirements: || || EXECUTE on DBMS_PIPE || || Execution Requirements: || */ /* || simple server program which listens indefinitely on [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 177 || database pipe for instructions */ PROCEDURE server; /* || Client programs */ /* stop the server */ PROCEDURE server_stop; /* turn server debug mode toggle on or off */ PROCEDURE server_debug_on; PROCEDURE server_debug_off; /* get and display server status using DBMS_OUTPUT */ PROCEDURE server_status; END pipesvr; Once the server is running, it listens on a database pipe for client service requests. When a request is received, the server processes the request and goes back to listening on the pipe. In the case of the server_status client procedure call, the server sends its current status back to the client over a pipename unique to the session. The following record types and variables, declared in the package body of pipesvr, are used to implement the client−server communications: /* used as a tag for this application */ app_id VARCHAR2(10) := 'OPBIP$'; /* identifiers for message protocols */ request_protocol VARCHAR2(20) := app_id||'REQUEST$'; status_protocol VARCHAR2(20) := app_id||'STATUS$'; /* server listens on this pipe */ request_pipe VARCHAR2(30) := app_id||'SERVER$'; /* client responses come on this pipe, unique to each client */ my_response_pipe VARCHAR2(100) := app_id|| DBMS_PIPE.UNIQUE_SESSION_NAME; /* || requests to server made in this format, || should never need to override response_pipe */ TYPE request_rectype IS RECORD (response_pipe VARCHAR2(100) := my_response_pipe ,service stop_req%TYPE ); /* || server reports status in this format */ TYPE status_rectype IS RECORD (start_date DATE ,total_requests INTEGER := 0 ,debug_status VARCHAR2(5) := 'OFF' ); /* private global for server current status */ status_rec status_rectype; [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 178 3.1.7.5.1 Message types Two record types have been declared for the two kinds of messages that will be handled: service request messages (sent from client to server) and server status messages (sent from server to client). Corresponding to each record (message) type is a protocol identifier to use when unpacking messages. 3.1.7.5.2 Pipenames The following pipenames are established for proper message separation: • request_pipe, into which all client requests are placed for receipt by the server • my_response_pipe, from which each session receives its response from the server 3.1.7.5.3 Pack/send, receive/unpack encapsulation In keeping with the best practices for safe pipe communications, the following four (package private) procedures are implemented in the body of pipesvr (only the specifications are shown below): /* Filename on companion disk: pipesvr.sql */* /* || private program to put service request on pipe, || called by client programs */ PROCEDURE pack_send_request (request_rec_IN IN request_rectype ,return_code_OUT OUT NUMBER); /* || private program to receive request on the || request pipe */ PROCEDURE receive_unpack_request (timeout_IN IN INTEGER ,request_rec_OUT OUT request_rectype ,return_code_OUT OUT NUMBER); /* || private program to put request on pipe, || called by client programs */ PROCEDURE pack_send_status (status_rec_IN IN status_rectype ,response_pipe_IN IN my_response_pipe%TYPE ,return_code_OUT OUT NUMBER); /* || private program to receive status on unique || session pipe */ PROCEDURE receive_unpack_status (timeout_IN IN INTEGER ,status_rec_OUT OUT status_rectype ,return_code_OUT OUT NUMBER); 3.1.7.5.4 The server procedure The server procedure itself is quite straightforward. It begins by creating the request pipe and initializing its private status record. Then it loops forever (or until the terminate_TF boolean is TRUE) on request_pipe for [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 179 client requests using receive_unpack_request. Valid requests are passed on to the process_request procedure, which encapsulates the inelegant IF THEN logic required to handle various types of requests. Finally, when the loop terminates, due to setting terminate_TF to TRUE, the pipe is removed and the program ends. The code for the server is surprisingly simple. /* Filename on companion disk: pipesvr.sql */* PROCEDURE server IS request_rec request_rectype; temp_return_code NUMBER; BEGIN /* create pipe */ temp_return_code := DBMS_PIPE.CREATE_PIPE(request_pipe); /* initialize status rec */ status_rec.start_date := SYSDATE; status_rec.total_requests := 0; status_rec.debug_status := 'OFF'; /* || loop forever and process requests */ WHILE NOT terminate_TF LOOP receive_unpack_request (timeout_IN => DBMS_PIPE.maxwait ,request_rec_OUT=> request_rec ,return_code_OUT => temp_return_code); IF temp_return_code != 0 THEN DBMS_PIPE.PURGE(request_pipe); debug('REQUEST PIPE STAT: '||temp_return_code); ELSE process_request(request_rec); debug('REQUEST PROCESSED'); END IF; END LOOP; /* || terminating: remove pipe and exit */ temp_return_code := DBMS_PIPE.REMOVE_PIPE(request_pipe); EXCEPTION WHEN OTHERS THEN debug('SERVER EXCP: '||SQLERRM, force_TF_IN=>TRUE); temp_return_code := DBMS_PIPE.REMOVE_PIPE(request_pipe); END server; 3.1.7.5.5 The process_request procedure When the server procedure receives a valid service request, it calls the process_request procedure. This procedure has the responsibility of interpreting the service request and performing the requested action. Note that this procedure sets the terminate_TF Boolean, which stops the server. You must always code a stop routine into this type of service program, or you will have to kill the process running the procedure. Other services performed by process_request include setting debugging to on or off, and sending the server's current status_rec back to the requesting session on a database pipe using pack_send_status. /* Filename on companion disk: pipesvr.sql */* [Appendix A] What's on the Companion Disk? 3.1.7 DBMS_PIPE Examples 180 . allows Oracle users to communicate with host operating system programs and receive data from them into their session context. What about writing a service provider program internal to Oracle? . program that will listen on a database pipe and provide certain Oracle based services to client sessions connected to the same Oracle database? There are a number of possible applications of