15. Juni 2009

"Magic" in der Datenbank: Ein Beispiel für externe Prozeduren

English title: "magic" - an example for external procedures

Ich wollte schon länger mal ausprobieren, wie man eigentlich externe C-Programme mit PL/SQL verbinden kann ... und nun habe ich auch endlich ein brauchbares Beispiel gefunden. Wenn man in einer Datenbankapplikation einen BLOB hat, hat man ja nicht zwingenderweise auch den Dateinamen - es kann also die Frage aufkommen, was da eigentlich drin ist. Auf UNIX/Linux-Systemen gibt es ja das praktische Kommando file, welches anhand des Inhalts den Dateityp bestimmen kann. Und diese Funktionalität steht auch in einer Bibliothek zur Verfügung: der libmagic.so. Also könnte man ja mal versuchen, diese als PL/SQL-Prozedur bereitzustellen:
Today I'd like to play a little bit with using external native code from PL/SQL programs. I've found a quite useful example: When a database (PL/SQL) application works with RAW or BLOB content it not necessarily has a file name or a mimetype information in order to determine which kind of content this particular BLOB is. On UNIX/Linux systems there is the file utility which can determine the kind of binary content just by examining the bytes. And this functionality is also contained in the library libmagic.so (which the file utility actually uses I'd think). Therefore I'd like to provide this functionality to PL/SQL.
Zunächst ein kleines C-Programm ... zum Testen ...
First a litte C program ... to test ...
#include <magic.h>
#include <stdio.h>

int main(int argsc, char *args[]) {
  magic_t    g_handle;
  const char *text;

  g_handle = magic_open(MAGIC_MIME);
  magic_load(g_handle, NULL);
  text = magic_file(g_handle, args[1]);
  magic_close(g_handle);
  puts(text);
}
Das ist das file-Kommando - nachprogrammiert. Man sieht also sehr schön, dass man zunächst eine Art "Kontext" öffnet (magic_open), dann die "Datenbank" lädt (NULL lädt das Standard-File; siehe dazu auch die Linux-manpage von "libmagic"), dann kommt das eigentliche Bestimmen des Dateityps (magic_file) und schließlich wird der Kontext geschlossen und das Ergebnis ausgegeben.
This is basically the file utility - reinvented. When using the magic library we need to create a "context" first (magic_open). Then we load the "database" (NULL loads the standard file - see the Linux-manpage of "libmagic" for more information) with magic_load and after that we can determine the file content with magic_file. Finally the context has to be closed with magic_close.
Nun, das möchte ich eigentlich nicht alles in PL/SQL machen; dort reichte mir eine einfache Funktion, die wie das file-Kommando arbeitet. Im Gegensatz dazu solle sie aber ein paar Bytes als RAW entgegennehmen und den Typ eben dieser Bytes bestimmen - wir sind ja in der Datenbank. Und auch hierfür bietet die Bibliothek eine Funktion an: magic_buffer.
I don't want to do all these calls from PL/SQL - I just want to have an equivalent to file utility. But unlike the file utility I want to determine the content type of a byte array (as RAW datatype). The magic library provides a function for that: magic_buffer.
Also: nun geht es los: Zunächst wieder ein kleines C-Programm (oramagic.c), welches zwei Funktionen anbietet: Eine bestimmt den Typ einer Datei zum testen (mimetype_file), die andere bestimmt den "Dateityp" eines Byte-Arrays (magic_bytes) - letztere ist das eigentliche Ziel der Übung.
Now we start. First we code a simple C program (oramagic.c) which encapsulated the libmagic functionality in two C functions: mimetype_bytes (which is the lesson target) and mimetype_file (for testing purposes).
#include <magic.h>
#include <stdio.h>

const char *mimetype_file(const char* filename) {
  magic_t g_handle;
  const char*   text;

  g_handle = magic_open(MAGIC_MIME);
  magic_load(g_handle, NULL);
  text = magic_file(g_handle,filename);
  magic_close(g_handle);
  return text;
}

const char *mimetype_bytes(const void *buffer, size_t len) {
  magic_t g_handle;
  const char*   text;

  g_handle = magic_open(MAGIC_MIME);
  magic_load(g_handle, NULL);
  text = magic_buffer(g_handle,buffer,len);
  magic_close(g_handle);
  return text;
}
Dieses Ding wird nun kompiliert und direkt als Shared Library gelinkt.
This code now gets compiled and linked as shared library.
$ gcc -shared -lmagic ora_magic.c -o ora_magic.so 
Die Datei ora_magic.so könnte man nun nach $ORACLE_HOME/lib kopieren und dann als DB-User (Achtung: das CREATE LIBRARY-Privileg wird benötigt) wie folgt einbinden:
Now copy the file ora_magic.so to $ORACLE_HOME/lib and then log in as a database user (you need the CREATE LIBRARY privilege in order to execute the following script).
drop library magic_lib
/

create or replace library magic_lib
as '{$ORACLE_HOME}/lib/ora_magic.so'
/

create or replace package magic 
is
  function magic_file(filename in varchar2) return varchar2;
  function magic_byte(buffer in raw, buffersize in pls_integer) return varchar2;
end magic;
/
sho err

create or replace package body magic is
  function magic_file(filename in varchar2) return varchar2
  is external name "mimetype_file" library magic_lib language c;
  function magic_byte(buffer in raw, buffersize in pls_integer) return varchar2
  is external name "mimetype_bytes" library magic_lib language c;
end magic;
/
sho err
Das war's: Nun kann man das neue Paket MAGIC testen: Zunächst mit einem Dateinamen:
Now we can test the new "PL/SQL package" MAGIC.
select magic.magic_file('{$ORACLE_HOME}/lib/ora_magic.so') file_type from dual;

FILE_TYPE
-------------------------------------------------
application/x-sharedlib, not stripped
Und wenn man die Bytes in einem BLOB hat, könnte man das wie folgt machen (in diesem Beispiel werden die ersten 200 Byte an die Bibliothek übergeben
And if we have the contents in a BLOB variable we can determine the content type as follows. Note that we don't need to pass the whole BLOB to the library - the first few (here: 200) bytes are sufficient.
declare
  v_text        varchar2(2000);
  v_dateiinhalt blob;
  v_bytes       raw(200);
begin
  v_dateiinhalt := -- BLOB hier holen (aus Tabelle o.ä.)
  v_bytes := dbms_lob.substr(v_dateiinhalt, 200, 1) ; 
  v_text := magic.magic_byte(v_bytes, 200);
  dbms_output.put_line('Dateityp: '||v_text);
end;
/

Dateityp: application/x-sharedlib, not stripped
Das ist eigentlich eine ganz nette Sache ... hat aber auch ein paar Nachteile: Der wichtigste ist, dass es plattformabhängig ist. Das Beispiel hier ist auf Linux gemacht worden. Auf einer anderen Plattform muss man zunächst mal die magic-Bibliothek suchen, dann das C-Programm schreiben, eine Shared-Library erzeugen und dann einbinden. Des weiteren liegt der Code außerhalb der Datenbank - das muss bei Backup & Recovery stets in Betracht gezogen werden.
Basically this is pretty nice ... but there are also some serious disadvantages of linking PL/SQL to external native code. The most important one is that this is platform-dependant. This example was created on a linux system and this code runs unchanged only on a linux system. On other platforms we first have to lookup the magic library, then code a c program (maybe slightly different), run the compiler (slightly different parameters) and link to PL/SQL (unchanged). The second disadvantage is that the code is outside the database which has to be taken in to account for the backup & recovery process.
Aus diesen Gründen ist mir beim Einbinden von externem Code der Ansatz mit Java in der Datenbank weit lieber (und dazu gibt es ja auch den einen oder anderen Post). Bevor man nun aber auf die Suche nach eine Java-Implementierung der "magic"-Bibliothek geht, sollte man sich diesen Artikel ansehen, welcher das Problem mit Tabellen und reinem PL/SQL löst.
For those reasons I'd prefer Java in the database as the method for embedding external code. For the actual problem of having the "magic" functionality in the database this article describes another very interesting approach.

1 Kommentar:

Anja Hildebrandt hat gesagt…

Danke für den Tipp... Java hab ich schon öfter genutzt, aber bei C fehlte auch mir bisher ein simples Beispiel...

Anja

Beliebte Postings