Heute möchte ich mal zwei Möglichkeiten vorstellen „externen“ Code in der Oracle DB laufen zu lassen. Dabei gibt es diverse Unterschiede und Feinheiten zu beachten, ich versuche es aber einfach zu halten und so wenig wie möglich theoretischen Ballast zu liefern.
Als Beispiel kommen drei Funktionen zum Einsatz welche verschiedene Parameter entgegen nehmen sowie unterschiedliche Rückgabewerte haben.
- square_number liefert die Quadratzahl einer ganzzahligen Zahl.
- square_root liefert die Quadratwurzel einer beliebigen Zahl.
- full_name verbindet den Vor- und Nachnamen zum vollständigen Familiennamen
Java
Bei Java kann man ein Objekt erzeugen welches entweder eine Referenz auf eine Java-Datei ist oder auf den konkreten Code.
Ersteres würde so aussehen:
create java class using bfile (java_dir, 'jex_util.class');
Letzteres so:
create or replace and compile java source named jex_src
as
public class jex_util {
public static int square_number (int val) {
return val*val;
}
public static double square_root (double val) {
return java.lang.Math.sqrt(val);
}
public static String full_name (String given_name, String family_name) {
return given_name+" "+family_name;
}
};
/
Nachdem der Klassencode in der DB ist kann er „angesprochen“ werden. Hier der Code dafür:
create or replace function j_square_number(
p_val pls_integer)
return pls_integer authid definer
as
language java
name 'jex_util.square_number(int) return int';
/
create or replace function j_square_root(
p_val number)
return number authid definer
as
language java
name 'jex_util.square_root(double) return double';
/
create or replace function j_full_name(
p_given_name varchar2,
p_family_name varchar2)
return varchar2 authid definer
as
language java
name 'jex_util.full_name(java.lang.String, java.lang.String) return java.lang.String';
/
Das reicht um nun endlich auch alles testen zu können:
select
j_square_number(3),
j_square_root(9.25),
j_full_name('foo', 'bar')
from
dual;
Zum Schluß natürlich das Aufräumen nicht vergessen…
drop function j_square_number;
drop function j_square_root;
drop function j_full_name;
drop java source jex_src;
C
Nun das gleiche in C. Hier muss zunächst eine Bibliothek bzw. „Library“ erstellt werden (Dateiendung *dll* bei Windows und *so* bei Linux). Der Sourcecode ist unabhängig vom Betriebssystem und verwendeten Compiler für die Funktionen zunächst identisch (mal abgesehen vom C Präprozessor der hier im Beispiel für das Erstellen der DLL in Windows mit Visual Studio benötigt wird…).
Die Quellcodedatei cex_util.c könnte etwa so aussehen:
#include
#include
#include
#if defined(_WIN64)
__declspec(dllexport)
#endif
char* full_name(char* given_name, char* family_name)
{
char* space = " ";
char* result = malloc(strlen(given_name) + strlen(space) + strlen(family_name) + 1);
strcpy(result, given_name);
strcat(result, space);
strcat(result, family_name);
return result;
}
#if defined(_WIN64)
__declspec(dllexport)
#endif
int square_number(int val)
{
return val*val;
}
#if defined(_WIN64)
__declspec(dllexport)
#endif
double square_root(double val)
{
return sqrt(val);
}
Wenn man unter Windows arbeitet kompiliert man den Code nun z.B. mit Visual Studio. In der „Visual Studio x64 Win64 Command Prompt (2010)“ oder auch „Visual Studio x64 Cross Tools Command Prompt (2010)“ wird dazu zunächst eine Objektdatei erzeugt, um anschließend aus dieser die gewünschte Bibliothek (cex_util.dll) zu erstellen:
cl /LD cex_util.c
Unter Linux kompiliert man am einfachsten mit der GNU Compiler Collection (gcc) die quasi bei jeder Distribution dabei ist. Dazu direkt im Terminal eingeben:
gcc -shared -o libcex_util.so -fPIC cex_util.c
Nun muss die dll bzw. so Datei noch dorthin gelegt werden, wo Oracle Zugriff darauf hat bzw. von wo überhaupt DLLs geladen werden dürfen. Laut der extproc.ora ist der Default Ordner von wo aus immer geladen werden darf $ORACLE_HOME/lib und $ORACLE_HOME/bin. Alternativ kann man auch die exproc.ora editieren und einen speziellen Pfad festlegen. Gehen wir mal von letzterem aus und erzeugen ein Objekt in der Datenbank welches in dieses (fiktive) Verzeichnis auf die entsprechende Bibliothek referenziert. Das sieht z.B. in Windows bei einem (angenommenen) Pfad C:\ora_lib\ so aus:
create or replace library lib_cex
as
'C:\ora_lib\cex_util.dll';
Und bei Linux – die Datei in /opt/ora_lib/ vorausgesetzt – dann so:
create or replace library lib_cex
as
'/opt/ora_lib/cex_util.so';
Nachdem die Referenz auf die Bibliothek erfolgreich erstellt wurde, kann nun der Code „angesprochen“ werden. Das geht so:
create or replace function c_square_number(
p_val in pls_integer)
return pls_integer authid definer
as
language c
name "square_number"
library lib_cex;
/
create or replace function c_square_root(
p_val in double precision)
return double precision authid definer
as
language c
name "square_root"
library lib_cex;
/
create or replace function c_full_name(
p_given_name varchar2,
p_family_name varchar2)
return varchar2 authid definer
as
language c
name "full_name"
library lib_cex;
/
Nun geht es endlich ans testen:
select
c_square_number(4),
c_square_root(20.25),
c_full_name('hello', 'world')
from
dual;
Zum Schluß das Aufräumen nicht vergessen:
drop function c_square_number;
drop function c_square_root;
drop function c_full_name;
drop library lib_cex;
Ein Satz noch zu den Typen bzw. Konvertierungen. Oracle nimmt eine Menge implizite Konvertierungen vor (z.B. wird varchar2 zu string und string wiederrum entspricht in C einem char pointer). Man kann aber die Konvertierungen auch explizit mitgeben indem man die „PARAMETERS“ clause hinzufügt.
create or replace function c_square_number(
p_val in pls_integer)
return pls_integer authid definer
as
language c
name "square_number"
library lib_cex;
parameters(p_val int, return int);
/
Eine sehr gute Übersicht über das Hin und Her bei den Datentypen gibt eine etwas ältere (aber scheinbar immer noch aktuelle) Doku von Oracle: [https://docs.oracle.com/cd/B10501_01/appdev.920/a96590/adg11rtn.htm#1005528](https://docs.oracle.com/cd/B10501_01/appdev.920/a96590/adg11rtn.htm#1005528)
Und nun bin ich gespannt aufs Feedback!