Mit Oracle-8 ist es erstmals möglich, aus PL/SQL sogenannte Callouts
durchzuführen. Die Motivation dazu ist einfach und offensichtlich. Zugriffe auf das
Betriebssystem, rechenintensive Berechungen in effiziente Sprachen auslagern. Dazu werden
Shared Libraries verwendet, welche beispielsweise unter WIN-NT als DLL implementiert sind.
Unter UNIX (Solaris, Linux) sind Shared Libraries ebenfalls nichts Neues, sie tragen die
Dateiendung xxx.so. Oracle-8 kreiert beim Aufruf eines externen Programms
einen eigenen Prozess. Die Aufrufe werden dann von einem SQL*Net Listener abgefangen. Der
normale Listener kann dazu verwendet werden, dabei ist zu beachten, dass der externe
Prozess die Privilegien desjenigen Betriebssystem Users hat, welcher den Listener gestartet
hat. Die Verbindung von PL/SQL zum externen Prozess erfolgt ebenfalls über das SQL*Net
Konfigurationsfile Tnsnames.ora.
Architektur
Um eine externe Procedure aufzurufen, muss die Calling PL/SQL Procedure
wissen, in welcher DLL (oder Shared Library) sich diese Procedure befindet. Dazu wird in
der Alias Library, welche im Data Dictionary gespeichert ist, nach der DLL gesucht. Wird
der Name der DLL gefunden, so wird via SQL*NET der Listener kontaktiert, welcher den Oracle
Agent extproc startet. Von nun an kommuniziert der Oracle Agent direkt mit
der PL/SQL Engine. Die PL/SQL Engine übergibt mittels der PL/SQL Wrapper Function den
Namen der DLL an den Oracle Agent. Dieser startet die DLL und übergibt das Resultat an
die PL/SQL Engine zurück. Die Wrapper Function dient dazu, ein Mapping zwischen der
DLL und der Calling PL/SQL Procedure zu erstellen. Sie wird mit einer speziellen Syntax
definiert (siehe folgende Beispiele).
Im folgenden Artikel, wird die Vorgehensweise für WIN-NT4 und Linux an einem
Beispiel gezeigt.
WIN-NT4 Variante
Im NT4 Beispiel, soll der Hostname des lokalen oder externen Hosts abgefragt werden.
Dazu dient die Standard NT4 DLL Kernel32.dll, welche die C-Procedure "GetComputerNameA"
enthält.
Auf dem Host Arkum (Client) wird die notwendige Library und die external
Procedure angelegt und das entsprechend angepasste Tnsnames.ora File definiert. Auf dem
Host Dorint (Server) muss nur der Listener (Listener.ora) modifiziert werden.
Die Oracle Library stellt den Link zur Shared Library auf dem
Betriebssystem her. Die erstellte Library wird im Oracle Enterprise Manager Version 1.6
noch nicht ausgewiesen, sie kann jedoch in der View USER_LIBRARIES verifiziert
werden.
CREATE OR REPLACE LIBRARY Kernel32 AS
'C:\WINNT\system32\Kernel32.dll';
Damit ein externes Programm aufgerufen werden kann, muss eine Wrapper
Funktion in PL/SQL geschrieben werden. Der Wrapper bestimmt die Art und Weise wie
Parameter an die externe Funktion übergeben werden und welches die Datentypen sind.
Folgender Wrapper wird auf Arkum definiert. Man beachte, dass der Functionsname des
Wrappers mit der aufgerufenen externen Function in der Shared Library übereinstimmen
muss.
Function GETCOMPUTERNAME (lpbufer IN OUT VARCHAR2,
nsize IN OUT BINARY_INTEGER)
RETURN BINARY_INTEGER
IS EXTERNAL
LIBRARY Kernel32
NAME "GetComputerNameA"
LANGUAGE C
CALLING STANDARD PASCAL;
Procedure MY_EXT_PROC IS
rwert BINARY_INTEGER;
slen BINARY_INTEGER := 255; /* Must be length of sbuf */
sbuf VARCHAR2(255) := ' '; /* May NOT be NULL */
BEGIN
rwert := getcomputername(sbuf,slen);
dbms_output.put_line(sbuf);
END;
-
SQLNET.AUTHENTICATION_SERVICES = (NONE)
USE_PLUG_AND_PLAY_LISTENER = OFF
USE_CKPFILE_LISTENER = OFF
LISTENER =
(ADDRESS_LIST =
(ADDRESS=
(PROTOCOL=IPC)
(KEY= DOR1.world)
)
(ADDRESS=
(PROTOCOL=IPC)
(KEY= DOR1)
)
(ADDRESS =
(COMMUNITY = tcp.world)
(PROTOCOL = TCP)
(Host = dorint)
(Port = 1526)
)
)
STARTUP_WAIT_TIME_LISTENER = 0
CONNECT_TIMEOUT_LISTENER = 10
LOG_DIRECTORY_LISTENER = D:\ORANT\NET80\log
LOG_FILE_LISTENER = listener
TRACE_LEVEL_LISTENER = OFF
TRACE_DIRECTORY_LISTENER = D:\ORANT\NET80\log
TRACE_FILE_LISTENER = listener.trc
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = DOR1.world)
(SID_NAME = DOR1)
(PRESPAWN_MAX = 10)
)
(SID_DESC =
(SID_NAME = extproc)
(PROGRAM = extproc)
)
)
-
Der Connect-Descriptor muss in diesem Fall extproc_connection_data
heissen.
#
# External Procedure Usage ####################################
#
extproc_connection_data.world =
(DESCRIPTION = (ADDRESS = (COMMUNITY = tcp.world)
(PROTOCOL = TCP) (Host = dorint) (Port = 1526))
(CONNECT_DATA = (SID = extproc)))
-
SQL> set serveroutput on
SQL> execute my_ext_proc;
DORINT
PL/SQL procedure successfully completed.
Linux Variante
Im Linux Beispiel, soll ein beliebiges Unix Kommando aus der PL/SQL-Procedure aufgerufen
werden können. Dazu erstellen wir die Shared Library libosint.so.
Auf dem Host Arkum (Client) wird die notwendige Library und die external
Procedure angelegt und das entsprechend angepasste Tnsnames.ora File definiert. Auf dem
Host Dorint (Server) muss nur der Listener (Listener.ora) modifiziert werden.
Der C-Code wird erstellt, compiliert und als Shared Library
gelinkt, die Shared Library kann mit dem folgenden Makefile unter Linux erstellt werden.
Die Oracle Library stellt den Link zur Shared Library auf dem
Betriebssystem her. Die erstellte Library wird im Oracle Enterprise Manager Version 1.6
noch nicht ausgewiesen, sie kann jedoch in der View USER_LIBRARIES verifiziert
werden.
CREATE OR REPLACE LIBRARY libosint AS
'/oracle/product/8.0.5/plsql/demo/libosint.so';
Damit ein externes Programm aufgerufen werden kann, muss eine Wrapper
Funktion in PL/SQL geschrieben werden. Der Wrapper bestimmt die Art und Weise wie
Parameter an die externe Funktion übergeben werden und welches die Datentypen sind.
Folgender Wrapper wird auf Arkum definiert. Man beachte, dass der Functionsname des
Wrappers mit der aufgerufenen externen Function in der Shared Library übereinstimmen
muss. Die Wrapper Function und die aufrufende Funktion haben wir in ein PL/SQL Package
Osutil
verpackt.
PROCEDURE RunOsCmdReg(cmdin IN VARCHAR2, cmdoutput OUT VARCHAR2)
IS EXTERNAL
NAME "RunOsCmd" /* Function in Shared libosint.so */
LIBRARY libosint
WITH CONTEXT
PARAMETERS (CONTEXT,
cmdin
STRING,
cmdin
INDICATOR SHORT,
cmdin
LENGTH INT,
cmdoutput
STRING,
cmdoutput
INDICATOR SHORT,
cmdoutput
LENGTH INT);
Der obige Wrapper kann nun aus einem PL/SQL Programm einfach aufgerufen
werden.
PROCEDURE RunOsCmd(acmd IN VARCHAR2) IS
cmd_out VARCHAR2(32767); -- output of OS command
i INTEGER; -- counter variabl
j INTEGER; -- counter variable
len INTEGER; -- length of command output
cnl CHAR(1) := chr(10); -- "newline" character
BEGIN
dbms_output.enable(32767); -- enable a large output buffer
RunOsCmdReg(acmd, cmd_out); -- call the external procedure
IF (cmd_out IS NULL) THEN
dbms_output.put_line('ERROR, bad command or
no output returned...');
ELSE -- output one line at a time
len := length(cmd_out);
i := 1; -- start at first char
j := instr(cmd_out,cnl,i,1); -- find first newline char
LOOP
dbms_output.put_line(substr(cmd_out,i,j-i));
IF ( j = len ) THEN
EXIT;
END IF;
i := j + 1; -- start after last newline
j := instr(cmd_out,cnl,i,1); -- find the next newline
END LOOP;
END IF;
END RunOsCmd;
CONNECT_TIMEOUT_LSNRRAB3 = 60
LOG_DIRECTORY_LSNRRAB3 = /oracle/product/8.0.5/sqlnet
LOG_FILE_LSNRRAB3 = listener.log
SQLNET.AUTHENTICATION_SERVICES = (NONE)
STARTUP_WAIT_TIME_LSNRRAB3 = 0
TRACE_DIRECTORY_LSNRRAB3 = /oracle/product/8.0.5/sqlnet
TRACE_FILE_LSNRRAB3 = listener.trc
TRACE_LEVEL_LSNRRAB3 = ADMIN
USE_CKPFILE_LSNRRAB3 = OFF
USE_DEDICATED_SERVER = ON
USE_PLUG_AND_PLAY_LSNRRAB3 = OFF
LSNRRAB3 =
(ADDRESS_LIST =
(ADDRESS =
(COMMUNITY = TCP.world)
(PROTOCOL = TCP)
(Host = rabbit)
(Port = 1532)
)
)
SID_LIST_LSNRRAB3 =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = RAB3.world)
(SID_NAME = extproc)
(ORACLE_HOME = /oracle/product/8.0.5)
(PROGRAM = /oracle/product/8.0.5/bin/extproc)
)
)
Der Connect-Descriptor muss in diesem Fall extproc_connection_data
heissen.
#
# External Procedure Usage ####################################
#
extproc_connection_data.world =
(DESCRIPTION = (ADDRESS
= (COMMUNITY = tcp.world)
(PROTOCOL = TCP) (Host
= rabbit) (Port = 1532))
(CONNECT_DATA = (SID =
extproc)))
SQL> execute osutil.runoscmd('/bin/ls -l /');
total 53
drwxr-xr-x 2 root root 2048 Apr 22 22:15 bin
drwxr-xr-x 3 root root 1024 Apr 22 22:24 boot
drwxr-xr-x 3 root root 21504 May 17 20:09 dev
drwxr-xr-x 31 root root 3072 May 17 20:09 etc
drwxr-xr-x 18 root root 1024 May 15 13:06 home
drwxr-xr-x 4 root root 2048 Apr 22 22:13 lib
drwxr-xr-x 2 root root 12288 Apr 22 22:07 lost+found
drwxr-xr-x 2 root root 0 May 17 20:09 misc
drwxr-xr-x 4 root root 1024 Apr 22 22:09 mnt
drwxr-xr-x 6 root root 1024 May 14 12:53 opt
dr-xr-xr-x 5 root root 0 May 17 20:07 proc
drwxr-x--- 7 root root 1024 May 15 15:03 root
drwxr-xr-x 3 root root 2048 Apr 22 22:15 sbin
drwxrwxrwt 3 root root 1024 May 19 14:01 tmp
drwxr-xr-x 21 root root 1024 Apr 22 22:12 usr
drwxr-xr-x 19 root root 1024 Apr 22 22:15 var
PL/SQL procedure successfully completed.
|