Kapitel 26. MySQL erweitern

Inhaltsverzeichnis

26.1. Hinzufügen neuer Funktionen zu MySQL
26.1.1. MySQL-Threads
26.1.2. MySQL-Testsystem
26.2. Die MySQL-Plug-In-Schnittstelle
26.2.1. Charakteristiken der Plug-In-Schnittstelle
26.2.2. Volltext-Parser-Plug-Ins
26.2.3. INSTALL PLUGIN
26.2.4. UNINSTALL PLUGIN
26.2.5. Schreiben von Plug-Ins
26.3. Hinzufügen neuer Funktionen zu MySQL
26.3.1. Features der Schnittstelle für benutzerdefinierte Funktionen (UDF)
26.3.2. CREATE FUNCTION/DROP FUNCTION
26.3.3. DROP FUNCTION
26.3.4. Hinzufügen einer neuen benutzerdefinierten Funktion
26.3.5. Hinzufügen einer neuen nativen Funktion
26.4. Hinzufügen neuer Prozeduren zu MySQL
26.4.1. PROCEDURE ANALYSE
26.4.2. Schreiben einer Prozedur

26.1. Hinzufügen neuer Funktionen zu MySQL

In diesem Kapitel wird vieles beschrieben, was Sie wissen müssen, wenn Sie am Code von MySQL arbeiten möchten. Wenn Sie selbst zu der Entwicklung von MySQL beitragen wollen, Zugriff auf den allerneuesten Entwicklungsstand des Codes zwischen zwei Versionen benötigen oder einfach nur die Entwicklung mitverfolgen möchten, befolgen Sie bitte die Anleitungen unter Abschnitt 2.8.3, „Installation vom Entwicklungs-Source-Tree“. Wenn Sie sich für die Interna von MySQL interessieren, sollten Sie auch unsere internals-Mailingliste abonnieren. Auf dieser Liste herrscht relativ wenig Verkehr. Wie Sie sie abonnieren können, erfahren Sie unter Abschnitt 1.7.1, „Die MySQL-Mailinglisten“. Alle Entwickler bei MySQL AB sind auf der internals-List und wir helfen gerne auch anderen weiter, die am MySQL-Code arbeiten. Bitte nutzen Sie diese Liste, um Fragen über den Code zu stellen und Patches zu übermitteln, falls Sie gerne selbst zu dem MySQL-Projekt beitragen möchten!

26.1.1. MySQL-Threads

Der MySQL Server erzeugt folgende Threads:

  • Der TCP/IP-Verbindungs-Thread behandelt alle Verbindungsanfragen und erzeugt für jede Verbindung einen neuen dedizierten Thread zur Authentifizierung und Verarbeitung von SQL-Anfragen.

  • Auf Windows NT gibt es einen Handler-Thread für die Named Pipe, der für Named-Pipe-Verbindungsanfragen dieselbe Arbeit wie der TCP/IP-Verbindungs-Thread leistet.

  • Der Signal-Thread kümmert sich um Signale und normalerweise auch um Alarmsignale und process_alarm()-Aufrufe, um Verbindungen, die zu lange ungenutzt waren, per Timeout zu beenden.

  • Wenn mysqld mit -DUSE_ALARM_THREAD kompiliert wird, wird ein spezieller Thread für den Umgang mit Alarmen erzeugt. Das wird aber nur bei Systemen getan, auf denen es Probleme mit sigwait() gibt, oder wenn Sie den Code der Funktion thr_alarm() in Ihrer Anwendung ohne einen dedizierten Thread zur Signalbehandlung einsetzen möchten.

  • Wenn die Option --flush_time=val gesetzt ist, wird ein dedizierter Thread erzeugt, der in den gegebenen Abständen alle Tabellen auf die Platte zurückschreibt.

  • Jede Verbindung besitzt ihren eigenen Thread.

  • Jede Tabelle, für die INSERT DELAYED gilt, bekommt ihren eigenen Thread.

  • Wenn Sie die Option --master-host setzen, wird ein Thread für die Slave-Replikation gestartet, um Updates vom Master zu lesen und anzuwenden.

Der Befehl mysqladmin processlist zeigt nur den Verbindungs-Thread, den INSERT DELAYED-Thread und den Replikations-Thread an.

26.1.2. MySQL-Testsystem

Mit dem in den Unix-Quell- und Binärdistributionen enthaltenen Testsystem können Nutzer und Entwickler Regressionstests mit dem MySQL-Code durchführen. Diese Tests kann man auf Unix, aber zurzeit noch nicht in einer nativen Windows-Umgebung laufen lassen.

Die momentan vorhandenen Testfälle testen nicht alles in MySQL, sollten aber die offensichtlichsten Fehler im Code zur SQL-Verarbeitung sowie OS/Bibliothek-Probleme finden, und leisten ziemlich gründliche Arbeit beim Testen von Replikation. Unser Ziel ist es, letztlich 100 % des Codes mit unseren Tests abzudecken. Wir freuen uns über Beiträge zu unserer Testreihe. Insbesondere können Sie uns Tests für systemkritische Funktionalitäten schicken, um zu gewährleisten, dass alle zukünftigen MySQL-Releases auch mit Ihren Anwendungen harmonieren.

26.1.2.1. Benutzung der MySQL-Testsuite

Das Testsystem besteht aus einem Testsprachen-Interpreter (mysqltest), einem Shell-Skript, um alle Tests auszuführen (mysql-test-run), den eigentlichen Testfällen, die in einer speziellen Testsprache geschrieben sind, sowie aus den erwarteten Ergebnissen. Um die Testreihe nach einem Build auf Ihrem System auszuführen, geben Sie make test oder mysql-test/mysql-test-run im Wurzelverzeichnis des Quellcodes ein. Wenn Sie eine Binärdistribution installiert haben, wechseln Sie in das Wurzelverzeichnis der Installation (beispielsweise /usr/local/mysql) und führen scripts/mysql-test-run aus. Alle Tests sollten erfolgreich laufen. Ist das nicht der Fall, müssen Sie die Gründe herausfinden und einen Bugreport übermitteln, wenn es sich um einen Fehler in MySQL handelt. Siehe Abschnitt 26.1.2.3, „Berichten von Bugs in der MySQL-Test-Suite“.

Wenn eine Instanz von mysqld auf dem Computer läuft, auf dem Sie die Testreihe ausführen möchten, müssen Sie diese nur dann herunterfahren, wenn sie den Port 9306 oder 9307 belegt. Wenn einer dieser Ports nicht frei ist, müssen Sie in mysql-test-run die Werte des Master- oder Slave-Ports umändern und einen Port einsetzen, der frei ist.

Sie können einen einzelnen Testfall mit mysql-test/mysql-test-run test_name ausführen.

Wenn ein Test scheitert, führen Sie mysql-test-run mit der Option --force aus, um zu prüfen, ob auch andere Tests fehlschlagen.

26.1.2.2. Erweiterung der MySQL-Testsuite

Mit der Sprache mysqltest können Sie auch Ihre eigenen Testfälle schreiben. Allerdings haben wir leider die Dokumentation dieser Sprache noch nicht ganz fertig gestellt. Sie können sich jedoch unsere aktuellen Testfälle anschauen und als Beispiele heranziehen. Die folgenden Hinweise müssten Ihnen bei den ersten Schritten eine Hilfe sein:

  • Sie finden die Tests in mysql-test/t/*.test.

  • Ein Testfall besteht aus Anweisungen, die mit ; abgeschlossen werden, und ähnelt der Eingabe für den mysql-Kommandozeilen-Client. Eine Anweisung ist standardmäßig eine SQL-Anweisung, die an den MySQL Server gesandt wird, es sei denn, sie wird als interner Befehl erkannt (wie beispielsweise sleep).

  • Vor allen Anfragen, die Ergebnisse produzieren, wie beispielsweise SELECT, SHOW oder EXPLAIN, muss ein @/path/to/result/file stehen. Diese Datei muss die erwarteten Ergebnisse enthalten. Ein einfaches Mittel, um die Ergebnisdatei zu generieren, besteht darin, mysqltest -r < t/test-case-name.test im Verzeichnis mysql-test auszuführen und dann die generierten Ergebnisdateien wenn nötig zu bearbeiten, um sie an die erwartete Ausgabe anzupassen. In diesem Fall müssen Sie sehr genau darauf achten, keine unsichtbaren Zeichen zu löschen oder hinzuzufügen. Sie dürfen nur den Text ändern oder Zeilen löschen. Wenn Sie eine Zeile einfügen müssen, achten Sie darauf, dass die Felder durch einen Tabulator getrennt werden, und setzen Sie auch am Ende einen Tabulator. Mit od -c können Sie sich vergewissern, dass Ihr Editor bei der Bearbeitung keinen Unsinn gemacht hat. Wir hoffen, dass Sie niemals die Ausgabe von mysqltest -r bearbeiten müssen, da dies nur nötig wird, wenn Sie einen Fehler finden.

  • Um Ihr System wie unseres einzurichten, legen Sie die Ergebnisdateien in das Verzeichnis mysql-test/r und nennen sie test_name.result. Wenn der Test mehr als ein Ergebnis produziert, verwenden Sie die Namen test_name.a.result, test_name.b.result und so weiter.

  • Wenn eine Anweisung einen Fehler zurückgibt, sollten Sie diesen mit --error error-number auf der Zeile vor dieser Anweisung angeben. Die Fehlernummer kann auch eine kommagetrennte Liste von Fehlernummern sein.

  • Wenn Sie einen Replikationstestfall schreiben, setzen Sie in die erste Zeile der Testdatei den Text source include/master-slave.inc;. Mit connection master; und connection slave; schalten Sie zwischen Master und Slave um. Wenn Sie auf einer anderen Verbindung arbeiten müssen, können Sie für den Master connection master1; und für den Slave connection slave1; eingeben.

  • Falls Sie eine Schleifenverarbeitung ausführen müssen, können Sie Folgendes tun:

    let $1=1000;
    while ($1)
    {
     # Hier Anfragen einfügen
     dec $1;
    }
    
  • Mit sleep schläft das System zwischen zwei Anfragen. Da in diesem Befehl auch Sekundenbruchteile eingestellt werden können, bedeutet beispielsweise sleep 1.3;, dass das System 1,3 Sekunden schläft.

  • Um den Slave mit Zusatzoptionen für den Testfall auszuführen, setzen Sie die Optionen in einem Kommandozeilenformat in die Datei mysql-test/t/test_name-slave.opt bzw. für den Master in die Datei mysql-test/t/test_name-master.opt.

  • Wenn Sie Fragen zu der Testreihe haben oder selbst einen Test beisteuern möchten, schicken Sie eine E-Mail an die MySQL-Mailingliste internals (siehe Abschnitt 1.7.1, „Die MySQL-Mailinglisten“). Da diese Liste keine Anhänge akzeptiert, laden Sie alle relevanten Dateien per FTP auf folgende Adresse hoch: ftp://ftp.mysql.com/pub/mysql/upload/.

26.1.2.3. Berichten von Bugs in der MySQL-Test-Suite

Wenn Ihre MySQL-Version die Testreihe nicht besteht, tun Sie Folgendes:

  • Senden Sie uns bitte erst dann einen Bugreport, wenn Sie so viel wie möglich über das Problem herausgefunden haben! Schauen Sie bitte auch in die Anleitungen unter Abschnitt 1.8, „Wie man Bugs oder Probleme meldet“.

  • Speichern Sie die Ausgabe von mysql-test-run sowie die Inhalte sämtlicher .reject-Dateien im Verzeichnis mysql-test/r.

  • Wenn ein Test in der Testreihe fehlschlägt, überprüfen Sie, ob der Test auch dann scheitert, wenn er isoliert ausgeführt wird:

    cd mysql-test
    mysql-test-run --local test-name
    

    Wenn dies scheitert, sollten Sie MySQL mit der Option --with-debug konfigurieren und mysql-test-run mit der Option --debug ausführen. Wenn auch das fehlschlägt, senden Sie die Trace-Datei var/tmp/master.trace an ftp://ftp.mysql.com/pub/mysql/upload/, damit wir sie untersuchen können. Bitte senden Sie uns außerdem eine vollständige Beschreibung Ihres Systems, die Version Ihrer mysqld-Binary sowie Informationen, wie Sie sie kompiliert haben.

  • Lassen Sie mysql-test-run mit der --force-Option laufen, um zu erfahren, ob auch ein anderer Test scheitert.

  • Wenn Sie MySQL selbst kompiliert haben, schlagen Sie in unserem Handbuch nach, wie MySQL auf Ihrer Plattform kompiliert wird, oder – noch besser – verwenden Sie eine der Binaries, die wir für Sie bereits vokompiliert haben (siehe http://dev.mysql.com/downloads/). Alle unsere Standardbinaries dürften die Testreihe überstehen!

  • Wenn Sie eine Fehlermeldung wie Result length mismatch oder Result content mismatch bekommen, so bedeutet dies, dass die Testausgabe nicht mit der erwarteten Ausgabe übereinstimmt. Dies könnte ein Bug in MySQL sein oder aber daran liegen, dass Ihre mysqld-Version unter bestimmten Umständen abweichende Ergebnisse bringt.

    Die Ergebnisse von gescheiterten Tests werden in eine Datei gespeichert, die genau wie die Ergebnisdatei heißt, aber die Erweiterung .reject hat. Wenn Ihr Testfall scheitert, lassen Sie einen Diff-Befehl auf den beiden Dateien laufen. Ist nicht zu erkennen, worin sie sich unterscheiden, so untersuchen Sie beide mit od -c und vergleichen auch ihre Längen.

  • Scheitert ein Test vollständig, suchen Sie in den Logdateien im Verzeichnis mysql-test/var/log nach Gründen.

  • Haben Sie MySQL mit Debugging kompiliert, so können Sie versuchen, mysql-test-run mit der Option --gdb und/oder --debug auszuführen. Siehe auch Abschnitt E.1.2, „Trace-Dateien erzeugen“.

    Wurde MySQL nicht mit Debugging kompiliert, so sollten Sie dies nun nachholen. Dazu müssen Sie lediglich configure mit den --with-debug-Optionen ausführen. Siehe Abschnitt 2.8, „Installation der Quelldistribution“.

26.2. Die MySQL-Plug-In-Schnittstelle

MySQL 5.1 und höher unterstützt eine Plug-In-API, mit der Serverkomponenten zur Laufzeit ge- oder entladen werden können, ohne den Server neu zu starten. Zurzeit unterstützt diese API die Erstellung von Volltext-Parser-Plug-Ins. Durch ein solches Plug-In lässt sich der eingebaute Volltext-Parser ersetzen oder verbessern. So kann beispielsweise ein Plug-In andere Regeln als der eingebaute Parser verwenden, um Text in Wörter zu parsen. Das kann nützlich sein, wenn Sie Text parsen möchten, der andere Merkmale hat, als sie der eingebaute Parser erwartet.

Die Plug-In-API soll Nachfolger der älteren Schnittstelle für benutzerdefinierte Funktionen (UDFs) sein. Im Endausbau wird sie auch eine API für die Erstellung von UDFs enthalten und die ältere, nicht als Plug-In geschriebene UDF-API ersetzen. Von da an wird es möglich sein, UDFs so zu überarbeiten, dass sie als Plug-In-UDFs benutzt werden können und somit auch von den besseren Sicherheits- und Versionierungsfähigkeiten der Plug-In-API profitieren. Im Anschluss daran wird der Support für die alte UDF-API auslaufen.

Die Plug-In-API benötigt die plugin-Tabelle in der mysql-Datenbank. Diese Tabelle wird im Installationsprozess von MySQL angelegt. Wenn Sie von einer älteren Version auf MySQL 5.1 aufrüsten, erstellen Sie diese Tabelle mit dem Befehl mysql_fix_privilege_tables. Siehe Abschnitt 5.6, „mysql_fix_privilege_tables — Upgrade von MySQL-Systemtabellen“.

26.2.1. Charakteristiken der Plug-In-Schnittstelle

In mancher Hinsicht ähnelt die Plug-In-API der älteren UDF-API, die sie ersetzen soll, aber sie bietet eine Reihe von Vorteilen gegenüber ihrer Vorläuferin:

  • Das Plug-In-Framework ist erweiterbar, sodass es verschiedene Arten von Plug-Ins aufnehmen kann.

    Manche Aspekte hat die Plug-In-API mit allen Plug-Ins gemeinsam, aber sie gestattet darüber hinaus auch typspezifische Schnittstellenelemente, sodass unterschiedliche Arten von Plug-Ins erstellt werden können. Ein Plug-In, das einem bestimmten Zweck dient, kann somit die für seine Erfordernisse geeignetste Schnittstelle haben, anstatt sich an den Bedürfnissen eines anderen Plug-In-Typs zu orientieren.

    Auch wenn zurzeit nur die Schnittstelle für Volltext-Parser-Plug-Ins implementiert ist, können noch andere hinzukommen, wie beispielsweise eine Schnittsstelle für UDF-Plug-Ins.

  • Zur Plug-In-API gehören auch Versionsinformationen.

    Durch die Versionsinformationen der Plug-In-API kann sich eine Plug-In-Bibliothek sowie jedes in ihr enthaltene Plug-In selbst identifizieren, und zwar mithilfe der API-Version, die zur Erstellung der Bibliothek benutzt wurde. Wenn sich die API mit der Zeit ändert, ändern sich auch die Versionsnummern, aber der Server kann immer anhand der Versionsinformationen einer konkreten Plug-In-Bibliothek herausfinden, ob er die Plug-Ins dieser Bibliothek unterstützt.

    Es gibt zwei Arten von Versionsnummern. Die erste ist die Version des allgemeinen Plug-In-Frameworks selbst. Jede Plug-In-Bibliothek enthält diese Art von Versionsnummer. Die zweite ist die Nummer des einzelnen Plug-Ins. Jeder spezifische Typ von Plug-In in einer Bibliothek hat für seine Schnittstelle eine Versionsnummer, sodass jedes Plug-In in einer Bibliothek eine typspezifische Versionsnummer besitzt. So hat beispielsweise eine Bibliothek, in der ein Volltext-Parser-Plug-In liegt, eine allgemeine Plug-In-API-Versionsnummer und das einzelne Plug-In hat seinerseits eine Versionsnummer, die für ebendiese Volltext-Plug-In-Schnittstelle spezifisch ist.

  • Die Sicherheit der Plug-Ins wurde gegenüber der alten UDF-Schnittstelle verbessert.

    Die ältere Schnittstelle zur Erstellung von UDFs ohne Plug-In ermöglichte es, Bibliotheken aus jedem Verzeichnis zu laden, das der dynamische Linker des Systems durchsuchte, und die Symbole zur Identifikation der UDF-Bibliothek waren relativ unspezifisch. Die neueren Regeln sind strenger. Eine Plug-In-Bibliothek muss in einem speziellen dedizierten Verzeichnis installiert sein, dessen Speicherort vom Server kontrolliert wird und zur Laufzeit nicht geändert werden kann. Außerdem muss die Bibliothek bestimmte Symbole enthalten, die sie als Plug-In-Bibliothek kennzeichnen. Der Server wird nichts als Plug-In laden, das nicht als Plug-In gebaut wurde.

    Die neuere Plug-In-Schnittstelle löst die Sicherheitsprobleme der älteren UDF-Schnittstelle. Wenn ein UDF-Plug-In-Typ implementiert wird, können Nicht-Plug-In-UDFs in das neue Framework überführt werden und die alte Schnittstelle kann auslaufen.

Die Plug-In-Implementierung besteht aus folgenden Komponenten:

Quelldateien (die angegebenen Speicherorte gelten für eine MySQL-Quelldistribution):

  • include/plugin.h stellt die öffentliche Plug-In-API zur Verfügung. Jeder, der eine Plug-In-Bibliothek schreiben möchte, sollte sich diese Datei anschauen.

  • sql/sql_plugin.h und sql/sql_plugin.cc enthalten die interne Plug-In-Implementierung. Diese Dateien müssen Verfasser von Plug-Ins sich nicht anschauen. Nur wenn Sie mehr darüber erfahren möchten, wie der Server mit Plug-Ins umgeht, sind diese Dateien für Sie interessant.

Systemtabelle:

  • Die plugin-Tabelle in der mysql-Datenbank listet alle installierten Plug-Ins auf und ist für die Nutzung von Plug-Ins erforderlich. Neuere MySQL-Versionen legen diese Tabelle bei der Installation an. Wenn Sie von einer älteren Version als MySQL 5.1 aufrüsten, sollten Sie mit mysql_fix_privilege_tables Ihre Systemtabellen aktualisieren und die plugin-Tabelle anlegen.

SQL-Anweisungen:

  • INSTALL PLUGIN registriert ein Plug-In in der plugin-Tabelle und lädt den Plug-In-Code.

  • UNINSTALL PLUGIN deregistriert ein Plug-In bei der plugin-Tabelle und und entlädt den Plug-In-Code.

  • Die WITH PARSER-Klausel für die Erstellung von Volltextindizes verbindet ein Volltext-Parser-Plug-In mit einem gegebenen FULLTEXT-Index.

  • SHOW PLUGIN zeigt Informationen über bekannte Plug-Ins an. Die PLUGINS-Tabelle in INFORMATION_SCHEMA liefert ebenfalls Informationen über Plug-Ins.

Systemvariable:

  • plugin_dir zeigt an, wo im Verzeichnis alle Plug-Ins installiert werden müssen. Den Wert dieser Variablen können Sie beim Serverstart mit der Option --plugin_dir=path angeben.

26.2.2. Volltext-Parser-Plug-Ins

MySQL verfügt über einen eingebauten Parser, der nach Voreinstellung für Volltextoperationen eingesetzt wird (Text parsen, der indiziert werden soll, oder einen Anfrage-String parsen, um die Suchbegriffe herauszufinden). In der Volltextverarbeitung bedeutet „Parsen“, dass Wörter aus einem Text oder Anfrage-String anhand von Regeln extrahiert werden, die definieren, aus welcher Zeichenfolge ein Wort besteht und wo die Wortgrenzen liegen.

Beim Parsen für Indizierungszwecke übergibt der Parser jedes Wort an den Server, und dieser fügt das Wort einem Volltextindex hinzu. Beim Parsen eines Anfrage-Strings übergibt der Parser ebenfalls jedes Wort an den Server, und dieser sammelt die Wörter, um sie für eine Suchoperation zusammenzustellen.

Die Parsing-Eigenschaften des eingebauten Volltext-Parsers werden in Abschnitt 12.7, „MySQL-Volltextsuche“, beschrieben. Hierzu gehören auch die Regeln, nach denen Wörter aus einem Text herausgezogen werden. Der Parser wird von bestimmten Systemvariablen wie ft_min_word_len und ft_max_word_len beeinflusst, die kürzere oder längere Wörter ausschließen können, und durch die Liste der Stoppwörter (diese sind häufig vorkommende Wörter, die übergangen werden).

Durch die Plug-In-API sind Sie in der Lage, einen eigenen Volltext-Parser zu verwenden und somit die Grundaufgaben eines Parsers unter Kontrolle zu haben. Ein Parser-Plug-In kann zwei Rollen spielen:

  • Es kann den eingebauten Parser ersetzen. In dieser Rolle liest das Plug-In die zu parsende Eingabe, zerlegt sie in Wörter und übergibt diese Wörter an den Server (entweder zum Indizieren oder zum Sammeln von Wörtern).

    Sie könnten einen Parser auf diese Weise einsetzen, wenn Sie die Eingabe nach anderen Regeln als der eingebaute Parser in Wörter zerlegen möchten. Für den eingebauten Parser besteht beispielsweise der Text „case-sensitive“ aus zwei Wörtern, nämlich „case“ und „sensitive“, während eine Anwendung diesen Text möglicherweise als ein einziges Wort ansehen sollte.

  • Das Plug-In kann auch mit dem eingebauten Parser zusammenarbeiten, indem es als Frontend für diesen dient. In dieser Rolle extrahiert das Plug-In Text aus der Eingabe und übergibt ihn an den Parser, der seinerseits den Text nach seinen normalen Parsing-Regeln in Wörter zerlegt. Diese Art von Parsing wird besonders von den Systemvariablen ft_xxx und der Stoppwörterliste beeinflusst.

    Auf diese Weise könnten Sie einen Parser einsetzen, wenn Sie beispielsweise PDF- oder XML-Dokumente oder .doc-Dateien indizieren müssen. Der eingebaute Parser ist nicht für diese Arten von Dokumenten gedacht, aber ein Plug-In-Parser kann Text aus diesen Quellen extrahieren und an den eingebauten Parser übergeben.

Ein Parser-Plug-In kann auch in beiden Rollen aktiv sein. Es kann Text aus einer Eingabe holen, die kein einfaches Textdokument ist (die Frontend-Rolle), und diesen Text auch in Wörter zerlegen (also den eingebauten Parser ersetzen).

Ein Volltext-Plug-In ist mit Volltextindizes indexweise verbunden: Wenn Sie ein Parser-Plug-In installieren, wird es deswegen noch nicht für Volltextoperationen benutzt, sondern steht zunächst einmal nur zur Verfügung. Ein Volltext-Parser kann dann beispielsweise in einer WITH PARSER-Klausel bei der Erstellung einzelner FULLTEXT-Indizes angegeben werden. Um einen solchen Index gleichzeitig mit der Tabelle zu erstellen, tun Sie Folgendes:

CREATE TABLE t
(
  doc CHAR(255),
  FULLTEXT INDEX (doc) WITH PARSER my_parser
);

Oder Sie fügen den Index nach der Erstellung der Tabelle hinzu:

ALTER TABLE t ADD FULLTEXT INDEX (doc) WITH PARSER my_parser;

Um den Parser mit dem Index zu verbinden, müssen Sie lediglich die WITH PARSER-Klausel in die SQL-Anfrage einfügen. Suchoperationen werden wie immer formuliert, ohne die Anfragen in irgendeiner Weise zu ändern.

Wenn Sie ein Parser-Plug-In mit einem FULLTEXT-Index verbinden, ist dieses Plug-In erforderlich, um den Index nutzen zu können. Wird es gelöscht, wird jeder mit ihm verbundene Index unbenutzbar. Jeder Versuch, ihn in einer Tabelle zu verwenden, für die kein Plug-In zur Verfügung steht, löst einen Fehler aus. Nur DROP TABLE ist nach wie vor möglich.

26.2.3. INSTALL PLUGIN

INSTALL PLUGIN plugin_name SONAME 'plugin_library'

Mit dieser Anweisung wird ein Plug-In installiert.

plugin_name ist der Name des Plug-Ins, wie er in der Plug-In-Deklarationsstruktur in der Bibliotheksdatei festgelegt ist. Ob in Plug-In-Namen die Groß- und Kleinschreibung beachtet wird, hängt von der Dateinamensemantik des Hostsystems ab.

plugin_library ist der Name der Shared Library, die den Plug-In-Code enthält. Dieser Name umfasst auch die Dateinamenserweiterung (beispielsweise libmyplugin.so oder libmyplugin.dylib).

Die Shared Library muss im Plug-In-Verzeichnis liegen (also in dem Verzeichnis, das die Systemvariable plugin_dir angibt). Die Bibliothek muss in dem Plug-In-Verzeichnis selbst und nicht in einem Unterverzeichnis liegen. Nach Voreinstellung ist plugin_dir das Verzeichnis, welches die Konfigurationsvariable pkglibdir vorgibt, aber es kann auch durch Einstellen des Werts von plugin_dir beim Serverstart geändert werden. Das folgende Beispiel zeigt, wie der Wert in einer my.cnf-Datei gesetzt wird:

[mysqld]
plugin_dir=/path/to/plugin/directory

Ist der Wert von plugin_dir ein relativer Pfadname, wird er mit Bezug auf das MySQL-Basisverzeichnis interpretiert (dieses ist in der Systemvariablen basedir festgelegt).

INSTALL PLUGIN fügt der mysql.plugin-Tabelle eine Zeile mit einer Beschreibung des plugins hinzu. Diese Tabelle enthält den Namen des Plug-Ins und der Bibliotheksdatei.

INSTALL PLUGIN lädt und initialisiert auch den Plug-In-Code, um das Plug-In nutzbar zu machen. Um ein Plug-In zu initialisieren, wird seine Initialisierungsfunktion ausgeführt, die alle Einstellungen vornimmt, welche zur Benutzung des Plug-Ins erforderlich sind.

Für INSTALL PLUGIN benötigen Sie das INSERT-Recht für die mysql.plugin-Tabelle.

Beim Serverstart lädt und initialisiert der Server alle in der mysql.plugin-Tabelle aufgelisteten Plug-Ins. Dies bedeutet, dass ein Plug-In mit INSTALL PLUGIN nur einmalig installiert wird und nicht bei jedem Serverstart. Das Laden von Plug-Ins beim Hochfahren des Servers funktioniert nicht, wenn der Server mit der Option --skip-grant-tables gestartet wird.

Wenn der Server herunterfährt, führt er die Deinitialisierungsfunktion für jedes geladene Plug-In aus, damit dieses Gelegenheit zu eventuellen Aufräumarbeiten bekommt.

Um ein Plug-In vollständig zu entfernen, führen Sie die UNINSTALL PLUGIN-Anweisung aus.

Die SHOW PLUGIN-Anweisung verrät Ihnen, welche Plug-Ins installiert sind.

Wenn Sie eine Plug-In-Bibliothek neu kompilieren und neu installieren müssen, können Sie aus folgenden Methoden wählen:

  • Sie deinstallieren alle Plug-Ins der Bibliothek mit UNINSTALL PLUGIN, installieren die neue Plug-In-Bibliotheksdatei in das Plug-In-Verzeichnis und installieren dann mit INSTALL PLUGIN alle Plug-Ins in der Bibliothek. Diese Vorgehensweise hat den Vorteil, dass man den Server nicht anhalten muss. Wenn jedoch die Plug-In-Bibliothek viele Plug-Ins enthält, müssen Sie viele INSTALL PLUGIN- und UNINSTALL PLUGIN-Anweisungen geben.

  • Alternativ können Sie den Server anhalten, die neue Plug-In-Bibliotheksdatei in das Plug-In-Verzeichnis installieren und dann den Server neu starten.

26.2.4. UNINSTALL PLUGIN

UNINSTALL PLUGIN plugin_name

Diese Anweisung entfernt ein installiertes Plug-In. Sie können ein Plug-In nicht entfernen, wenn es von einer Tabelle, die offen ist, noch gebraucht wird.

Der plugin_name muss der Name eines in der mysql.plugin-Tabelle aufgeführten Plug-Ins sein. Der Server führt die Deinitialisierungsfunktion des Plug-Ins aus und entfernt die Zeile für dieses Plug-In aus der mysql.plugin-Tabelle, damit es künftig beim Hochfahren des Servers nicht mehr geladen und initialisiert wird. UNINSTALL PLUGIN entfernt allerdings nicht die Dateien des Plug-Ins aus der Shared Library.

Um UNINSTALL PLUGIN benutzen zu können, benötigen Sie das DELETE-Recht für die mysql.plugin-Tabelle.

Die Entfernung von Plug-Ins hat Folgen für die Benutzung von Tabellen, die mit diesem Plug-In verbunden sind. Wenn beispielsweise ein Volltext-Parser-Plug-In mit einem FULLTEXT-Index auf der Tabelle verbunden ist, wird die Tabelle durch Deinstallation des Plug-Ins unbenutzbar. Jeder Versuch, auf sie zuzugreifen, löst einen Fehler aus. Sie kann noch nicht einmal mehr geöffnet werden, um den Index zu entfernen, der das Plug-In benutzt. Also sollten Sie bei der Deinstallation von Plug-Ins vorsichtig sein, wenn Ihnen Ihre Tabelleninhalte wichtig sind. Wenn Sie ein Plug-In deinstallieren, das Sie nicht wieder benutzen möchten, aber sich noch für den Inhalt der zugehörigen Tabelle interessieren, sollten Sie die Tabelle mit mysqldump kopieren und die WITH PARSER-Klausel aus der gespeicherten CREATE TABLE-Anweisung entfernen, damit Sie die Tabelle später erneut laden können. Wenn Ihnen die Tabelle egal ist, können Sie sie mit DROP TABLE auch dann noch löschen, wenn Ihre Plug-Ins nicht mehr vorhanden sind.

26.2.5. Schreiben von Plug-Ins

Dieser Abschnitt beschreibt die allgemeinen und typspezifischen Teile der Plug-In-API. Außerdem wird Schritt für Schritt erklärt, wie man eine Plug-In-Bibliothek erstellt. Ein Beispiel für Plug-In-Quellcode finden Sie im plugin/fulltext-Verzeichnis einer MySQL-Quelldistribution.

Sie können Plug-Ins in C oder C++ schreiben. Da die Plug-Ins dynamisch ge- und entladen werden, muss Ihr Betriebssystem dynamisches Laden unterstützen und auch mysqld dynamisch (nicht statisch) kompiliert worden sein.

Ein Plug-In enthält Code, der Teil des laufenden Servers wird. Daher gelten für das Schreiben von Plug-Ins dieselben Einschränkungen wie für Servercode. Sie bekommen beispielsweise Schwierigkeiten, wenn Sie versuchen, Funktionen aus der libstdc++-Bibliothek zu benutzen. Beachten Sie, dass sich diese Einschränkungen in zukünftigen Versionen des Servers noch ändern können, sodass bei einem Server-Upgrade unter Umständen auch Plug-Ins überarbeitet werden müssen, die ursprünglich für ältere Server erstellt wurden. Informationen über diese Einschränkungen finden Sie in Abschnitt 2.8.2, „Typische configure-Optionen“, und Abschnitt 2.8.4, „Probleme beim Kompilieren?“.

26.2.5.1. Allgemeine Plug-In-Strukturen und -Funktionen

Jedes Plug-In muss eine allgemeine Plug-In-Deklaration besitzen. Die Deklaration entspricht der st_mysql_plugin-Struktur in der plugin.h-Datei:

struct st_mysql_plugin
{
  int type;             /* Der Plug-In-Typ (ein MYSQL_XXX_PLUGIN-Wert)   */
  void *info;           /* Zeiger auf den typspezifischen Plug-In-Deskriptor   */
  const char *name;     /* Plug-In-Name                                  */
  const char *author;   /* Plug-In-Autor (für SHOW PLUGINS)             */
  const char *descr;    /* Allgemeine Beschreibung (für SHOW PLUGINS ) */
  int (*init)(void);    /* Die beim Laden des Plug-Ins aufgerufene Funktion */
  int (*deinit)(void);  /* Die beim Entladen des Plug-Ins aufgerufene Funktion */
};

Die st_mysql_plugin-Struktur ist allen Plug-In-Typen gemein. Ihre Bestandteile sollten folgendermaßen ausgefüllt werden:

  • type

    Der Plug-In-Typ. Dieser muss einer der Plug-In-Typwerte aus plugin.h sein. Für ein Volltext-Parser-Plug-In ist der type-Wert MYSQL_FTPARSER_PLUGIN.

  • info

    Ein Zeiger auf den Deskriptor für das Plug-In. Anders als die allgemeine Plug-In-Deklarationsstruktur hängt die Struktur dieses Deskriptors von dem konkreten Plug-In-Typ ab. Jeder Deskriptor hat eine Versionsnummer, die auf die API-Version für diesen Plug-In-Typ verweist, sowie andere erforderliche Bestandteile. Der Deskriptor für Volltext-Plug-Ins ist in Abschnitt 26.2.5.2, „Typspezifische Plug-In-Strukturen und -Funktionen“, beschrieben.

  • name

    Der Name des Plug-Ins. Dieser wird in der plugin-Tabelle aufgeführt und in SQL-Anweisungen wie INSTALL PLUGIN und UNINSTALL PLUGIN zur Benennung des Plug-Ins verwendet.

  • author

    Der Autor des Plug-Ins. Kann alles sein, was Sie wollen.

  • desc

    Eine allgemeine Beschreibung des Plug-Ins. Kann alles sein, was Sie wollen.

  • init

    Eine nur einmalig benutzte Initialisierungsfunktion. Diese wird ausgeführt, wenn das Plug-In geladen wird, was bei INSTALL PLUGIN oder für Plug-Ins aus der plugin-Tabelle beim Serverstart geschieht. Diese Funktion hat keine Argumente und gibt bei Erfolg null und bei Misserfolg einen von null verschiedenen Wert zurück.

  • deinit

    Eine nur einmalig benutzte Deinitialisierungsfunktion. Diese wird ausgeführt, wenn das Plug-In entladen wird, was bei UNINSTALL PLUGIN oder für Plug-Ins aus der plugin-Tabelle beim Server-Shutdown geschieht. Diese Funktion hat keine Argumente und gibt bei Erfolg null und bei Misserfolg einen von null verschiedenen Wert zurück

Die Funktionen init und deinit in der allgemeinen Plug-In-Deklaration werden nur beim Laden und Entladen des Plug-Ins aufgerufen. Sie haben nichts mit einer Benutzung des Plug-Ins zu tun, wie sie vorliegt, wenn eine SQL-Anweisung das Plug-In aufruft.

Wenn eine init- oder deinit-Funktion für ein Plug-In nicht notwendig ist, kann sie in der st_mysql_plugin-Struktur mit 0 angegeben werden.

26.2.5.2. Typspezifische Plug-In-Strukturen und -Funktionen

In der st_mysql_plugin-Struktur, die eine allgemeine Plug-In-Deklaration definiert, verweist der Bestandteil info auf einen typspezifischen Plug-In-Deskriptor. Bei einem Volltext-Parser-Plug-In entspricht dieser Deskriptor der st_mysql_ftparser-Struktur in der plugin.h-Datei:

struct st_mysql_ftparser
{
  int interface_version;
  int (*parse)(MYSQL_FTPARSER_PARAM *param);
  int (*init)(MYSQL_FTPARSER_PARAM *param);
  int (*deinit)(MYSQL_FTPARSER_PARAM *param);
};

Wie die Strukturdefinition zeigt, hat der Deskriptor eine Versionsnummer (MYSQL_FTPARSER_INTERFACE_VERSION für Volltext-Parser-Plug-Ins) und enthält Zeiger auf drei Funktionen. Die Bestandteile init und deinit sollten auf eine Funktion verweisen oder auf 0 gesetzt werden, wenn die Funktion nicht gebraucht wird. Der Bestandteil parse muss auf die Parse-Funktion verweisen.

Ein Volltext-Parser-Plug-In wird für zwei Dinge verwendet: Indizierung und Suchoperationen. In beiden Fällen ruft der Server die Initialisierungs- und Deinitialisierungsfunktion am Anfang und am Ende der Verarbeitung jeder SQL-Anweisung auf, in der das Plug-In benutzt wird. Während der Verarbeitung der Anweisung ruft der Server die Parsing-Hauptfunktion jedoch kontextabhängig auf:

  • Zum Indizieren ruft der Server den Parser für jeden zu indizierenden Spaltenwert auf.

  • In Suchoperationen ruft der Server den Parser auf, um den Such-String zu parsen. Ebenso kann der Parser für Zeilen aufgerufen werden, die von der Anweisung verarbeitet werden. Im Modus für natürliche Sprache braucht der Server den Parser nicht aufzurufen. Ini Phrasensuchen im booleschen Modus oder Suchen in natürlicher Sprache mit Abfrageerweiterung (Query Expansion) wird der Parser eingesetzt, um in den Spaltenwerten Informationen zu parsen, die nicht im Index vorliegen. Wenn eine Suche nach einer Spalte mit FULLTEXT-Index im booleschen Modus durchgeführt wird, wird ebenfalls der eingebaute Parser aufgerufen. (Plug-Ins sind mit konkreten Indizes verbunden. Ist kein Index vorhanden, wird auch kein Plug-In verwendet.)

Beachten Sie, dass die Plug-In-Deklaration im Plug-In-Bibliotheksdeskriptor Initialisierungs- und Deinitialisierungsfunktionen hat, ebenso wie der Plug-In-Deskriptor, auf den sie verweist. Diese Funktionspaare dienen unterschiedlichen Zwecken und werden aus unterschiedlichen Gründen aufgerufen:

  • Für die Plug-In-Deklaration im Plug-In-Bibliotheksdeskriptor werden die Initialisierungs- und Deinitialisierungsfunktionen beim Laden und Entladen des Plug-Ins aufgerufen.

  • Für den Plug-In-Deskriptor werden die Initialisierungs- und Deinitialisierungsfunktionen für jede SQL-Anweisung aufgerufen, in der das Plug-In benutzt wird.

Jede API-Funktion, die im Plug-In-Deskriptor genannt ist, sollte null bei einem Erfolg und einen von null verschiedenen Wert bei einem Misserfolg zurückgeben, und jede sollte ein Argument entgegennehmen, das auf eine MYSQL_FTPARSER_PARAM-Struktur verweist, die den Parsing-Kontext enthält. Die Struktur ist wie folgt definiert:

typedef struct st_mysql_ftparser_param
{
  int (*mysql_parse)(void *param, byte *doc, uint doc_len);
  int (*mysql_add_word)(void *param, byte *word, uint word_len,
                        MYSQL_FTPARSER_BOOLEAN_INFO *boolean_info);
  void *ftparser_state;
  void *mysql_ftparam;
  CHARSET_INFO *cs;
  byte *doc;
  uint length;
  int mode;
} MYSQL_FTPARSER_PARAM;

Die Bestandteile der Struktur werden folgendermaßen benutzt:

  • mysql_parse

    Ein Zeiger auf eine Callback-Funktion, die den eingebauten Parser des Servers aufruft. Diesen Callback verwenden Sie, wenn das Plug-In als Frontend des eingebauten Parsers fungiert. Wenn also die Plug-In-Parsing-Funktion aufgerufen wird, soll sie die Eingabe verarbeiten, um den Inhalt zu extrahieren, und diesen dann an den mysql_parse-Callback übergeben.

    Der erste Parameter dieser Callback-Funktion sollte der mysql_ftparam-Teil der Struktur des Parsing-Kontextes sein. Das heißt: Wenn param auf die Struktur verweist, wird der Callback folgendermaßen aufgerufen:

    param->mysql_parse(param->mysql_ftparam, ...);
    

    Ein Frontend-Plug-In kann Text extrahieren und komplett oder häppchenweise an den eingebauten Parser übergeben. Im zweiten Fall behandelt der eingebaute Parser die Textstücke, als befänden sich implizite Wortgrenzen zwischen ihnen.

  • mysql_add_word

    Ein Zeiger auf eine Callback-Funktion, die einem Volltextindex oder der Liste der Suchbegriffe ein Wort hinzufügt. Diesen Callback verwenden Sie, wenn das Parser-Plug-In den eingebauten Parser ersetzen soll. Das bedeutet: Wenn die Parsing-Funktion des Plug-Ins aufgerufen wird, soll sie die Eingabe in Wörter parsen und für jedes Wort den mysql_add_word-Callback aufrufen.

    Der erste Parameter dieser Callback-Funktion soll der mysql_ftparam-Teil der Struktur des Parsing-Kontexts sein. Das heißt: Wenn param auf die Struktur verweist, wird der Callback wie folgt aufgerufen:

    param->mysql_add_word(param->mysql_ftparam, ...);
    
  • ftparser_state

    Dies ist ein generischer Zeiger. Das Plug-In kann ihn auf die Informationen verweisen lassen, die es intern für seine eigenen Zwecke benutzt.

  • mysql_ftparam

    Wird durch den Server eingestellt und als erstes Argument an den mysql_parse- oder mysql_add_word-Callback übergeben.

  • cs

    Verweist auf den Zeichensatz für den Text, oder auf 0, wenn diese Information nicht zur Verfügung steht.

  • doc

    Ein Zeiger auf den zu parsenden Text.

  • length

    Die Länge des zu parsenden Texts in Bytes.

  • mode

    Der Parsing-Modus. Dieser Wert ist eine der folgenden Konstanten:

    • MYSQL_FTPARSER_SIMPLE_MODE

      Parsen im schnellen, einfachen Modus. Dies wird für Indizes und für Anfragen mit natürlichen Sprachen verwendet. Der Parser sollte an den Server nur die zu indizierenden Wörter übergeben. Wenn er Längenbeschränkungen oder eine Liste mit zu ignorierenden Stoppwörtern verwendet, soll er die hierdurch ausgeschlossenen Wörter nicht an den Server übergeben.

    • MYSQL_FTPARSER_WITH_STOPWORDS

      Parsen im Stoppwortmodus. Dies wird bei booleschen Suchoperationen für den Abgleich von Begriffen verwendet. Der Parser sollte alle Wörter an den Server übergeben, auch Stoppwörter oder Wörter, die die normalen Längenbeschränkungen übersteigen.

    • MYSQL_FTPARSER_FULL_BOOLEAN_INFO

      Parsen im booleschen Modus. Wird zum Parsen von booleschen Anfrage-Strings genutzt. Der Parser soll nicht nur Wörter, sondern auch Operatoren für den booleschen Modus erkennen, und diese als Token mit dem mysql_add_word-Callback an den Server übergeben. Um dem Server zu sagen, welche Art von Token übergeben wird, muss das Plug-In eine MYSQL_FTPARSER_BOOLEAN_INFO-Struktur ausfüllen und einen Zeiger auf diese Struktur mit übergeben.

Wenn der Parser im booleschen Modus aufgerufen wird, hat param->mode den Wert MYSQL_FTPARSER_FULL_BOOLEAN_INFO. Die MYSQL_FTPARSER_BOOLEAN_INFO-Struktur, die der Parser zur Übergabe von Token-Informationen an den Server benutzt, sieht folgendermaßen aus:

typedef struct st_mysql_ftparser_boolean_info
{
  enum enum_ft_token_type type;
  int yesno;
  int weight_adjust;
  bool wasign;
  bool trunc;
  /* Diese sind im Parser-Zustand und müssen entfernt werden. */
  byte prev;
  byte *quot;
} MYSQL_FTPARSER_BOOLEAN_INFO;

Der Parser sollte die Bestandteile der Struktur folgendermaßen ausfüllen:

  • type

    Der Token-Typ. Dies sollte einer der Werte der folgenden Tabelle sein:

    TypBedeutung
    FT_TOKEN_EOFEnde der Daten
    FT_TOKEN_WORDEin normales Wort
    FT_TOKEN_LEFT_PARENBeginn einer Gruppe oder eines Teilausdrucks
    FT_TOKEN_RIGHT_PARENEnde einer Gruppe oder eines Teilausdrucks
    FT_TOKEN_STOPWORDEin Stoppwort
  • yesno

    Gibt an, ob das Wort vorhanden sein muss, damit eine Übereinstimmung festgestellt wird. 0 bedeutet, dass das Wort optional ist, sein Vorhandensein jedoch die Relevanz der Übereinstimmung erhöht. Werte größer 0 bedeuten, dass das Wort obligatorisch ist, und Werte kleiner 0, dass es nicht vorhanden sein darf.

  • weight_adjust

    Ein Gewichtsfaktor, der festlegt, wie viel eine Übereinstimmung für das Wort zählt. Indem man diesen Faktor herauf- oder heruntersetzt, beeinflusst man die Bedeutung, die dieses Wort in Relevanzberechnungen hat. Ist der Wert null, so erfolgt keine Anpassung der Gewichtung. Werte größer oder kleiner null bedeuten ein höheres oder geringeres Gewicht. Die Beispiele unter Abschnitt 12.7.1, „Boolesche Volltextsuche“, die <- und >-Operatoren verwenden, zeigen, wie Gewichtung funktioniert.

  • wasign

    Das Vorzeichen des Gewichtungsfaktors. Ein negativer Wert verhält sich wie der boolesche Suchoperator ~, der dafür sorgt, dass das Wort in negativer Weise zur Relevanz beiträgt.

  • trunc

    Zeigt an, ob der Abgleich in derselben Weise durchgeführt wird, als ob der Kappungsoperator * im booleschen Modus angegeben worden wäre.

Plug-Ins sollten nicht die Bestandteile prev und quot der MYSQL_FTPARSER_BOOLEAN_INFO-Struktur benutzen.

26.2.5.3. Erzeugen einer Plug-In-Bibliothek

Dieser Abschnitt erklärt die Erstellung einer Plug-In-Bibliothek Schritt für Schritt. Er zeigt, wie man eine Bibliothek entwickelt, die ein Volltext-Parsing-Plug-In namens simple_parser enthält. Dieses Plug-In wendet einfachere Regeln als der eingebaute Volltext-Parser von MySQL an: Wörter sind nichtleere Folgen von Whitespace-Zeichen.

Jede Plug-In-Bibliothek enthält Folgendes:

  • Ein Plug-In-Bibliotheksdeskriptor mit der Versionsnummer der allgemeinen Plug-In-API der Bibliothek und einer allgemeinen Deklaration für jedes Plug-In in der Bibliothek.

  • Jede allgemeine Plug-In-Deklaration enthält Informationen, die allen Arten von Plug-Ins gemeinsam sind: einen Wert, der den Plug-In-Typ anzeigt, den Namen, den Autor und die Beschreibung des Plug-Ins sowie Zeiger auf die Initialisierungs- und die Deinitialisierungfunktionen, die der Server beim Laden und Entladen des Plug-Ins aufrufen muss.

  • Außerdem enthält die allgemeine Plug-In-Deklaration einen Zeiger auf einen typspezifischen Plug-In-Deskriptor. Die Struktur dieser Deskriptoren kann je nach Plug-In-Typ unterschiedlich sein, da jede Art von Plug-In ihre eigene API haben kann. Ein Plug-In-Deskriptor enthält eine typspezifische API-Versionsnummer und Zeiger auf die Funktionen, die zur Implementierung dieses Plug-In-Typs erforderlich sind. So hat beispielsweise ein Volltext-Parser-Plug-In Initialisierungs- und Deinitialisierungsfunktionen und eine Parsing-Hauptfunktion. Der Server ruft diese Funktionen auf, wenn er das Plug-In zum Parsen von Text einsetzt.

  • Die Plug-In-Bibliothek enthält die Schnittstellenfunktionen, auf die der Bibliotheksdeskriptor und die Plug-In-Deskriptoren verweisen.

Eine Plug-In-Bibliothek wird folgendermaßen angelegt:

  1. Zuerst binden Sie die von der Plug-In-Bibliothek benötigten Header-Dateien ein. Die Datei plugin.h ist auf jeden Fall notwendig, allerdings kann die Bibliothek auch noch andere Dateien erfordern. Zum Beispiel:

    #include <my_global.h>
    #include <m_string.h>
    #include <m_ctype.h>
    #include <plugin.h>
    
  2. Dann richten Sie den Deskriptor für die Plug-In-Bibliotheksdatei ein.

    Jede Plug-In-Bibliothek muss einen Bibliotheksdeskriptor besitzen, der zwei Symbole definiert:

    • _mysql_plugin_interface_version_ ist die Versionsnummer des allgemeinen Plug-In-Frameworks. Sie wird durch das Symbol MYSQL_PLUGIN_INTERFACE_VERSION angegeben, das in der Datei plugin.h definiert ist.

    • _mysql_plugin_declarations_ definiert ein Array von Plug-In-Deklarationen, wobei am Ende eine Deklaration steht, in der alle Bestandteile auf 0 gesetzt sind. Jede Deklaration ist eine Instanz der Struktur st_mysql_plugin (ebenfalls in plugin.h definiert). Für jedes Plug-In in der Bibliothek muss eine solche Deklaration vorhanden sein.

    Wenn der Server diese beiden Symbole nicht in einer Bibliothek vorfindet, akzeptiert er sie nicht als gültige Plug-In-Bibliothek und weist sie mit einer Fehlermeldung zurück. So wird verhindert, dass eine Bibliothek für Plug-In-Zwecke benutzt wird, die nicht speziell als Plug-In-Bibliothek ausgelegt wurde.

    Die üblichste (und bequemste) Art, die beiden notwendigen Symbole zu definieren, bieten die beiden Makros mysql_declare_plugin und mysql_declare_plugin_end aus der Datei plugin.h:

    mysql_declare_plugin
     ... eine oder mehr Plug-In-Deklarationen ...
    mysql_declare_plugin_end;
    

    Der Bibliotheksdeskriptor für eine Bibliothek mit einem einzigen Plug-In namens simple_parser sähe beispielsweise folgendermaßen aus:

    mysql_declare_plugin
    {
      MYSQL_FTPARSER_PLUGIN,      /* Typ                      */
      &simple_parser_descriptor,  /* Deskriptor                */
      "simple_parser",            /* Name                      */
      "MySQL AB",                 /* Autor                    */
      "Simple Full-Text Parser",  /* Beschreibung               */
      simple_parser_plugin_init,  /* Initialisierungsfunktion   */
      simple_parser_plugin_deinit /* Deinitialisierungsfunktion */
    }
    mysql_declare_plugin_end;
    

    Der Typ eines Volltext-Parser-Plug-Ins müsste MYSQL_FTPARSER_PLUGIN sein. Dieser Wert kennzeichnet das Plug-In als zulässig zur Benutzung in einer WITH PARSER-Klausel, wenn ein FULLTEXT-Index angelegt werden soll. (Kein anderer Plug-In-Typ ist für diese Klausel erlaubt.)

    Die Makros mysql_declare_plugin und mysql_declare_plugin_end sind in plugin.h folgendermaßen definiert:

    #define mysql_declare_plugin                                          \
    int _mysql_plugin_interface_version_= MYSQL_PLUGIN_INTERFACE_VERSION; \
    struct st_mysql_plugin _mysql_plugin_declarations_[]= {
    #define mysql_declare_plugin_end ,{0,0,0,0,0,0,0}}
    

    In der oben gezeigten Verwendung werden die Makros zu folgendem Code expandiert, der beide erforderlichen Symbole definiert (_mysql_plugin_interface_version_ und _mysql_plugin_declarations_):

    int _mysql_plugin_interface_version_= MYSQL_PLUGIN_INTERFACE_VERSION;
    struct st_mysql_plugin _mysql_plugin_declarations_[]= {
    {
      MYSQL_FTPARSER_PLUGIN,      /* Typ                      */
      &simple_parser_descriptor,  /* Deskriptor                */
      "simple_parser",            /* Name                      */
      "MySQL AB",                 /* Autor                    */
      "Simple Full-Text Parser",  /* Beschreibung               */
      simple_parser_plugin_init,  /* Initialisierungsfunktion   */
      simple_parser_plugin_deinit /* Deinitialisierungsfunktion */
    }
      ,{0,0,0,0,0,0,0}
    };
    

    Im obigen Beispiel wird nur ein einzelnes Plug-In im Bibliotheksdeskriptor deklariert, aber es ist ebenso gut möglich, mehrere Plug-Ins zu deklarieren. Hierzu führen Sie die Deklarationen – durch Kommata getrennt – in mysql_declare_plugin und mysql_declare_plugin_end auf.

  3. Nun richten Sie den Plug-In-Deskriptor ein.

    Jede Plug-In-Deklaration im Bibliotheksdeskriptor verweist auf einen typspezifischen Deskriptor für das zugehörige Plug-In. In der Deklaration von simple_parser wird dieser Deskriptor von &simple_parser_descriptor angezeigt. Der Deskriptor gibt die Versionsnummer für die Volltext-Plug-In-Schnittstelle (wie sie in MYSQL_FTPARSER_INTERFACE_VERSION steht) sowie die Parsing-, Initialisierungs- und Deinitialisierungsfunktionen des Plug-Ins an:

    static struct st_mysql_ftparser simple_parser_descriptor=
    {
      MYSQL_FTPARSER_INTERFACE_VERSION, /* Schnittstellenversion      */
      simple_parser_parse,              /* Parsing-Funktion       */
      simple_parser_init,               /* Parser-Initialisierungsfunktion   */
      simple_parser_deinit              /* Parser-Deinitialisierungsfunktion */
    };
    
  4. Jetzt richten Sie die Plug-In-Schnittstellenfunktionen ein.

    In der allgemeinen Plug-In-Deklaration des Bibliotheksdeskriptors werden die Initialisierungs- und Deinitialisierungsfunktionen angegeben, die der Server zum Laden und Entladen des Plug-Ins aufrufen muss. Für den simple_parser geben diese Funktionen lediglich null zurück, um anzuzeigen, dass sie Erfolg hatten:

    static int simple_parser_plugin_init(void)
    {
      return(0);
    }
    
    static int simple_parser_plugin_deinit(void)
    {
      return(0);
    }
    

    Da diese Funktionen eigentlich nichts tun, könnten Sie sie ebenso gut weglassen und in der Plug-In-Deklaration jeweils durch 0 ersetzen.

    Der typspezifische Plug-In-Deskriptor für den simple_parser gibt die Initialisierungs-, Deinitialisierungs- und Parsing-Funktionen an, die der Server bei Benutzung dieses Plug-Ins aufrufen muss. Beim simple_parser tun die Initialisierungs- und Deinitialisierungsfunktionen gar nichts:

    static int simple_parser_init(MYSQL_FTPARSER_PARAM *param)
    {
      return(0);
    }
    
    static int simple_parser_deinit(MYSQL_FTPARSER_PARAM *param)
    {
      return(0);
    }
    

    Da diese Funktionen nichts tun, könnte man sie hier ebenfalls weglassen und im Plug-In-Deskriptor 0 für sie angeben.

    Da die Parsing-Hauptfunktion simple_parser_parse() den eingebauten Volltext-Parser ersetzen soll, muss sie Text in Wörter zerlegen und diese Wörter an den Server übergeben. Das erste Argument der Parsing-Funktion ist ein Zeiger auf eine Struktur, die den Parsing-Kontext enthält. Diese Struktur besitzt einen doc-Bestandteil, der auf den zu parsenden Text verweist, und einen length-Bestandteil, der die Länge dieses Texts anzeigt. Da das Plug-In ganz einfache Parsing-Regeln verwendet, wonach nichtleere Folgen von Whitespace-Zeichen als Wörter betrachtet werden, erkennt es Wörter folgendermaßen:

    static int simple_parser_parse(MYSQL_FTPARSER_PARAM *param)
    {
      char *end, *start, *docend= param->doc + param->length;
    
      for (end= start= param->doc;; end++)
      {
        if (end == docend)
        {
          if (end > start)
            add_word(param, start, end - start);
          break;
        }
        else if (isspace(*end))
        {
          if (end > start)
            add_word(param, start, end - start);
          start= end + 1;
        }
      }
      return(0);
    }
    

    Während der Parser ein Wort nach dem anderen erkennt, ruft er die Funktion add_word() auf, um es an den Server zu übergeben. add_word() ist nur eine Hilfsfunktion, die nicht zur Plug-In-Schnittstelle gehört. Der Parser übergibt den Zeiger auf den Parsing-Kontext, den Zeiger auf das Wort und einen Längenwert an add_word().

    static void add_word(MYSQL_FTPARSER_PARAM *param, char *word, size_t len)
    {
      MYSQL_FTPARSER_BOOLEAN_INFO bool_info=
        { FT_TOKEN_WORD, 0, 0, 0, 0, ' ', 0 };
    
      if (param->mode == MYSQL_FTPARSER_FULL_BOOLEAN_INFO)
        param->mysql_add_word(param->mysql_ftparam, word, len, &bool_info);
      else
        param->mysql_add_word(param->mysql_ftparam, word, len, 0);
    }
    

    Beim Parsen im booleschen Modus füllt add_word() die Bestandteile der bool_info-Struktur aus, wie in Abschnitt 26.2.5.2, „Typspezifische Plug-In-Strukturen und -Funktionen“ beschrieben.

  5. Kompilieren Sie die Plug-In-Bibliothek als Shared Library und installieren Sie sie in das Plug-In-Verzeichnis.

    Die Prozedur zum Kompilieren von Shared-Objekten ist von System zu System unterschiedlich. Wenn Sie Ihre Bibliothek mithilfe der GNU-Autotools bauen, müsste libtool in der Lage sein, die richtigen Kompilierbefehle für Ihr System zu generieren. Wenn die Bibliothek mypluglib heißt, müssten Sie zum Schluss mit einer Shared Object-Datei dastehen, die so ähnlich wie libmypluglib.so heißt. (Der Dateiname kann auf Ihrem System eine andere Erweiterung haben.)

    Die Stelle, an der Sie die Bibliothek in Ihrem Plug-In-Verzeichnis installieren müssen, verrät Ihnen die Systemvariable plugin_dir. Zum Beispiel:

    mysql> SHOW VARIABLES LIKE 'plugin_dir';
    +---------------+----------------------------+
    | Variable_name | Value                      |
    +---------------+----------------------------+
    | plugin_dir    | /usr/local/mysql/lib/mysql |
    +---------------+----------------------------+
    

    Wenn Sie die Plug-In-Bibliothek installieren, achten Sie bitte darauf, dass Ihre Berechtigungen eine Ausführung durch den Server erlauben.

  6. Registrieren Sie das Plug-In beim Server.

    Die INSTALL PLUGIN-Anweisung lässt den Server das Plug-In in die plugin-Tabelle übernehmen und seinen Code aus der Bibliotheksdatei laden. Registrieren Sie mit dieser Anweisung den simple_parser beim Server und vergewissern Sie sich dann, dass er in die plugin-Tabelle aufgenommen wurde:

    mysql> INSTALL PLUGIN simple_parser SONAME 'libmypluglib.so';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT * FROM mysql.plugin;
    +---------------+-----------------+
    | name          | dl              |
    +---------------+-----------------+
    | simple_parser | libmypluglib.so |
    +---------------+-----------------+
    1 row in set (0.00 sec)
    
  7. Nun probieren Sie das Plug-In aus.

    Legen Sie eine Tabelle mit einer String-Spalte an und verbinden Sie das Parser-Plug-In mit einem FULLTEXT-Index auf der Spalte:

    mysql> CREATE TABLE t (c VARCHAR(255),
        ->   FULLTEXT (c) WITH PARSER simple_parser);
    Query OK, 0 rows affected (0.01 sec)
    

    Fügen Sie Text in die Tabelle ein und versuchen Sie einige Suchoperationen. Dabei sollte ausprobiert werden, ob das Parser-Plug-In wirklich alle Nicht-Whitespace-Zeichen als Wortzeichen betrachtet:

    mysql> INSERT INTO t VALUES
        ->   ('latin1_general_cs is a case-sensitive collation'),
        ->   ('I\'d like a case of oranges'),
        ->   ('this is sensitive information'),
        ->   ('another row'),
        ->   ('yet another row');
    Query OK, 5 rows affected (0.02 sec)
    Records: 5  Duplicates: 0  Warnings: 0
    
    mysql> SELECT c FROM t;
    +-------------------------------------------------+
    | c                                               |
    +-------------------------------------------------+
    | latin1_general_cs is a case-sensitive collation |
    | I'd like a case of oranges                      |
    | this is sensitive information                   |
    | another row                                     |
    | yet another row                                 |
    +-------------------------------------------------+
    5 rows in set (0.00 sec)
    
    mysql> SELECT MATCH(c) AGAINST('case') FROM t;
    +--------------------------+
    | MATCH(c) AGAINST('case') |
    +--------------------------+
    |                        0 |
    |          1.2968142032623 |
    |                        0 |
    |                        0 |
    |                        0 |
    +--------------------------+
    5 rows in set (0.00 sec)
    
    mysql> SELECT MATCH(c) AGAINST('sensitive') FROM t;
    +-------------------------------+
    | MATCH(c) AGAINST('sensitive') |
    +-------------------------------+
    |                             0 |
    |                             0 |
    |               1.3253291845322 |
    |                             0 |
    |                             0 |
    +-------------------------------+
    5 rows in set (0.01 sec)
    
    mysql> SELECT MATCH(c) AGAINST('case-sensitive') FROM t;
    +------------------------------------+
    | MATCH(c) AGAINST('case-sensitive') |
    +------------------------------------+
    |                    1.3109166622162 |
    |                                  0 |
    |                                  0 |
    |                                  0 |
    |                                  0 |
    +------------------------------------+
    5 rows in set (0.01 sec)
    
    mysql> SELECT MATCH(c) AGAINST('I\'d') FROM t;
    +--------------------------+
    | MATCH(c) AGAINST('I\'d') |
    +--------------------------+
    |                        0 |
    |          1.2968142032623 |
    |                        0 |
    |                        0 |
    |                        0 |
    +--------------------------+
    5 rows in set (0.01 sec)
    

Beachten Sie, dass weder „case“ noch „insensitive“ auf „case-insensitive“ passen, wie es bei dem eingebauten Parser der Fall wäre.

26.3. Hinzufügen neuer Funktionen zu MySQL

Es gibt zwei Möglichkeiten, neue Funktionen zu MySQL hinzuzufügen:

  • Die erste Möglichkeit ist die Schnittstelle für benutzerdefinierte Funktionen (User-Defined Functions, UDFs). Benutzerdefinierte Funktionen werden als Objektdateien kompiliert und dann dynamisch mit den Anweisungen CREATE FUNCTION und DROP FUNCTION auf den Server geladen oder von ihm entfernt. Siehe auch Abschnitt 26.3.2, „CREATE FUNCTION/DROP FUNCTION.

  • Die zweite Möglichkeit: Sie fügen die Funktionen als native (eingebaute) MySQL-Funktionen hinzu. Native Funktionen werden in den mysqld-Server kompiliert und stehen dann dauerhaft zur Verfügung.

Beide Methoden haben ihre Vor- und Nachteile:

  • Wenn Sie benutzerdefinierte Funktionen schreiben, müssen Sie zusätzlich zum Server auch noch Objektdateien installieren. Wenn Sie Ihre Funktion in den Server kompilieren, entfällt das.

  • Um native Funktionen zu erstellen, müssen Sie eine Quelldistribution ändern; für UDFs müssen Sie das nicht. UDFs können Sie auch einer Binärdistribution von MySQL hinzufügen, ohne in den Quellcode von MySQL eingreifen zu müssen.

  • Wenn Sie Ihre MySQL-Distribution aufrüsten, können Sie die zuvor installierten UDFs auch weiterhin nutzen, es sei denn, die neue Version ist eine, bei der sich die UDF-Schnittstelle geändert hat. Für native Funktionen müssen Sie Ihre Modifikationen bei jedem Upgrade wiederholen.

Ganz gleich, welchen Weg Sie einschlagen, um neue Funktionen hinzuzufügen: Aufgerufen werden sie in SQL-Anweisungen nicht anders als native Funktionen, wie zum Beispiel ABS() oder SOUNDEX().

Auch gespeicherte Funktionen bieten die Möglichkeit, Funktionen hinzuzufügen. Gespeicherte Funktionen schreiben Sie in SQL-Anweisungen, anstatt sie in den Objektcode zu kompilieren. Die Syntax für gespeicherte Funktionen wird in Kapitel 19, Gespeicherte Prozeduren und Funktionen, beschrieben.

Die folgenden Abschnitte beschreiben Features der UDF-Schnittstelle, geben Anleitungen zum Schreiben von UDFs, erläutern Sicherheitsmaßnahmen, mit denen MySQL den Missbrauch von UDFs verhindert, und erklären, wie native MySQL-Funktionen hinzugefügt werden.

Quellcode, der zeigt, wie man UDFs schreibt, finden Sie in der Datei sql/udf_example.cc, die in den MySQL-Quelldistributionen enthalten ist.

26.3.1. Features der Schnittstelle für benutzerdefinierte Funktionen (UDF)

Die MySQL-Schnittstelle für benutzerdefinierte Funktionen stellt folgende Features und Fähigkeiten zur Verfügung:

  • Funktionen können String-, Integer- oder Real-Werte zurückgeben.

  • Einfache Funktionen, die immer nur auf einer einzigen Zeile operieren, können ebenso definiert werden wie Aggregatfunktionen, die auf Zeilengruppen arbeiten.

  • Die Funktionen erhalten Informationen, die sie in die Lage versetzen, die Anzahl und den Typ der an sie übergebenen Argumente zu überprüfen.

  • Sie können MySQL anweisen, Argumenten einen bestimmten Typ aufzuzwingen, bevor sie an eine Funktion übergeben werden.

  • Sie können verlangen, dass eine Funktion NULL zurückgibt oder dass ein Fehler ausgelöst wird.

26.3.2. CREATE FUNCTION/DROP FUNCTION

CREATE [AGGREGATE] FUNCTION function_name RETURNS {STRING|INTEGER|REAL|DECIMAL}
    SONAME shared_library_name

Mit benutzerdefinierten Funktionen (UDFs) können Sie MySQL um neue Funktionen erweitern, die wie native (eingebaute) MySQL-Funktionen wie etwa ABS() oder CONCAT() funktionieren.

function_name ist der Name, mit dem die Funktion in SQL-Anweisungen aufgerufen wird. Die RETURNS-Klausel zeigt den Rückgabetyp der Funktion an. DECIMAL ist zwar hinter RETURNS zulässig, aber gegenwärtig geben DECIMAL-Funktionen String-Werte zurück und sollten daher wie STRING-Funktionen geschrieben werden.

shared_library_name ist der Basisname der Shared Object-Datei, die den Code zur Implementierung der Funktion enthält. Diese Datei muss im Plug-In-Verzeichnis liegen, welches durch den Wert der Systemvariablen plugin_dir vorgegeben ist. (Hinweis: Dies ist eine Änderung in MySQL 5.1. In früheren Versionen von MySQL konnte die Shared Object-Datei in jedem beliebigen Verzeichnis liegen, das der dynamische Linker des Systems untersuchte.)

Zur Erstellung einer Funktion benötigen Sie das INSERT-Recht für die mysql-Datenbank, da CREATE FUNCTION in die Systemtabelle mysql.func eine Zeile mit dem Namen, Typ und Shared Library-Namen der Funktion einfügt. Wenn diese Tabelle bei Ihnen fehlt, müssen Sie sie mit dem Skript mysql_fix_privilege_tables anlegen. Siehe Abschnitt 5.6, „mysql_fix_privilege_tables — Upgrade von MySQL-Systemtabellen“.

Eine aktive Funktion ist eine Funktion, die mit CREATE FUNCTION geladen, aber nicht mit DROP FUNCTION wieder gelöscht wurde. Alle aktiven Funktionen werden bei jedem Serverstart neu geladen, es sei denn, Sie starten mysqld mit der Option --skip-grant-tables. In diesem Fall wird die UDF-Initialisierung übersprungen und die UDFs stehen nicht zur Verfügung.

Anleitungen zum Schreiben benutzerdefinierter Funktionen finden Sie unter Abschnitt 26.3.4, „Hinzufügen einer neuen benutzerdefinierten Funktion“. Damit der UDF-Mechanismus funktioniert, müssen die Funktionen in C oder C++ geschrieben sein, Ihr Betriebssystem muss dynamisches Laden unterstützen, und Sie müssen mysqld dynamisch (nicht statisch) kompiliert haben.

Eine AGGREGATE-Funktion funktioniert genau wie eine native MySQL-Aggregatfunktion (Summenfunktion), wie beispielsweise SUM oder COUNT(). Damit AGGREGATE funktioniert, muss Ihre mysql.func-Tabelle eine type-Spalte enthalten. Wenn Ihre mysql.func-Tabelle diese Spalte nicht hat, müssen Sie sie mit dem Skript mysql_fix_privilege_tables erzeugen.

26.3.3. DROP FUNCTION

DROP FUNCTION function_name

Diese Anweisung löscht eine benutzerdefinierte Funktion (UDF) namens function_name.

Um eine Funktion löschen zu dürfen, benötigen Sie das DELETE-Recht für die mysql-Datenbank, denn DROP FUNCTION löscht aus der Systemtabelle mysql.func die Zeile, die den Namen, Typ und Shared Library-Namen der Funktion enthält.

26.3.4. Hinzufügen einer neuen benutzerdefinierten Funktion

Damit der UDF-Mechanismus funktioniert, müssen die Funktionen in C oder C++ geschrieben sein, und Ihr Betriebssystem muss dynamisches Laden unterstützen. Die MySQL-Quelldistribution enthält eine Datei namens sql/udf_example.cc, die fünf neue Funktionen definiert. In dieser Datei können Sie sehen, wie die Aufrufkonventionen für UDFs funktionieren.

Da eine UDF Code enthält, der zum Teil des laufenden Servers gehört, müssen Sie beim Schreiben von UDFs alle Einschränkungen beachten, die auch sonst für das Schreiben von Servercode gelten. Sie können beispielsweise Probleme bekommen, wenn Sie versuchen, Funktionen aus der libstdc++-Bibliothek zu benutzen. Beachten Sie, dass sich diese Einschränkungen in zukünftigen Versionen des Servers noch ändern können. Daher kann es sein, dass Sie bei einem Server-Upgrade auch Ihre für ältere Serverversionen geschriebenen UDFs überarbeiten müssen. Informationen über diese Einschränkungen erhalten Sie unter Abschnitt 2.8.2, „Typische configure-Optionen“, und Abschnitt 2.8.4, „Probleme beim Kompilieren?“.

Um UDFs nutzen zu können, müssen Sie mysqld dynamisch verlinken. Bitte konfigurieren Sie MySQL nicht mit der Option --with-mysqld-ldflags=-all-static. Wenn Sie eine UDF benutzen möchten, die auf Symbole aus mysqld zugreifen muss (beispielsweise die metaphone-Funktion in sql/udf_example.cc, die default_charset_info braucht), müssen Sie das Programm mit der Option -rdynamic verlinken (siehe man dlopen). Wenn Sie den Einsatz von UDFs planen, sollten Sie MySQL grundsätzlich mit --with-mysqld-ldflags=-rdynamic konfigurieren, wenn nichts Wichtiges dagegen spricht.

Wenn Sie eine vorkompilierte Distribution von MySQL einsetzen müssen, so verwenden Sie MySQL-Max: Diese Version enthält einen dynamisch verlinkten Server, der dynamisches Laden unterstützt.

Für jede Funktion, die Sie in SQL-Anweisungen nutzen möchten, sollten Sie zugehörige C- oder C++-Funktionen definieren. In den folgenden Ausführungen steht der Name „xxx“ für den Namen einer Beispielfunktion. Um zwischen dem Einsatz in SQL und C/C++ zu unterscheiden, wird ein Funktionsaufruf in SQL mit XXX() (Großbuchstaben) und ein Funktionsaufruf in C/C++ mit xxx() (Kleinbuchstaben) angegeben.

Sie schreiben folgende C-/C++-Funktionen, um die Schnittstelle für XXX() zu implementieren:

  • xxx() (obligatorisch)

    Die Hauptfunktion. Hier wird das Ergebnis der Funktion berechnet. Die Entsprechung zwischen dem Datentyp der SQL-Funktion und dem Rückgabetyp der C-/C++-Funktion sehen Sie hier:

    SQL-TypC-/C++-Typ
    STRINGchar *
    INTEGERlong long
    REALdouble

    Es ist auch möglich, eine DECIMAL-Funktion zu deklarieren. Da deren Wert jedoch zurzeit noch als String zurückgeliefert wird, sollten Sie eine solche UDF wie eine STRING-Funktion auslegen.

  • xxx_init() (optional)

    Die Initialisierungsfunktion für xxx(). Sie kann zu folgenden Zwecken benutzt werden:

    • Um zu prüfen, wie viele Argumente XXX() hat.

    • Um nachzuschauen, ob die Argumente den verlangten Typ haben oder, alternativ, um MySQL anzuweisen, den Argumenten die Typen aufzuzwingen, die für den Aufruf der Hauptfunktion erforderlich sind.

    • Um den von der Hauptfunktion benötigten Speicher zu reservieren.

    • Um die maximale Länge der Ergebnismenge anzugeben.

    • Um (für REAL-Funktionen) die Höchstzahl der Dezimalstellen in der Ergebnismenge anzugeben.

    • Um mitzuteilen, ob das Ergebnis auch NULL sein darf.

  • xxx_deinit() (optional)

    Die Deinitialisierungsfunktion für xxx(). Diese sollte den von der Initialisierungsfunktion reservierten Speicher wieder freigeben.

Wenn eine SQL-Anweisung XXX() aufruft, ruft MySQL die Initialisierungsfunktion xxx_init() auf, damit sie die notwendigen Vorbereitungen trifft, wie beispielsweise eine Überprüfung der Argumente oder die Speicherzuweisung. Wenn xxx_init() einen Fehler zurückliefert, bricht MySQL die SQL-Anweisung mit einer Fehlermeldung ab und ruft weder die Haupt- noch die Deinitialisierungsfunktion auf. Ansonsten ruft MySQL nun die Hauptfunktion xxx() einmal pro Zeile auf. Wenn alle Zeilen verarbeitet wurden, ruft MySQL die Deinitialisierungsfunktion xxx_deinit() auf, damit diese die notwendigen Aufräumarbeiten übernehmen kann.

Für Aggregatfunktionen, die wie SUM() arbeiten, müssen Sie außerdem folgende Funktionen bereitstellen:

  • xxx_clear() (obligatorisch für 5.1)

    Setzt den aktuellen Aggregatwert zurück, setzt aber das Argument nicht als Aggregatanfangswert für eine neue Gruppe ein.

  • xxx_add() (obligatorisch)

    Addiert das Argument zum aktuellen Aggregatwert.

MySQL geht folgendermaßen mit Aggregat-UDFs um:

  1. Es ruft xxx_init() auf, damit die Aggregatfunktion den Speicher reserviert, den sie für ihre Ergebnisse benötigt.

  2. Es sortiert die Tabelle entsprechend dem GROUP BY-Ausdruck.

  3. Es ruft für die erste Zeile jeder neuen Gruppe xxx_clear() auf.

  4. Es ruft für jede neue Zeile, die zu derselben Gruppe gehört, xxx_add() auf.

  5. Es ruft xxx() auf, um das Aggregatergebnis zu erhalten, wenn die Gruppe wechselt oder nachdem die letzte Zeile verarbeitet wurde.

  6. Es wiederholt die Schritte 3 bis 5, bis alle Zeilen verarbeitet sind.

  7. Es ruft xxx_deinit() auf, damit die UDF den von ihr reservierten Speicher wieder freigibt.

Alle Funktionen müssen Thread-sicher sein, also nicht nur die Hauptfunktion, sondern auch die Initialisierungs- und Deinitialisierungsfunktionen und alle sonstigen Funktionen, die für Aggregatfunktionen erforderlich sind. Dies hat zur Folge, dass Sie keine globalen oder statischen Variablen zuweisen dürfen, die sich ändern! Wenn Sie Arbeitsspeicher benötigen, weisen Sie ihn mit xxx_init() zu und geben ihn mit xxx_deinit() frei.

26.3.4.1. UDF-Aufrufsequenzen

Dieser Abschnitt beschreibt die verschiedenen Funktionen, die Sie definieren müssen, um eine einfache UDF anzulegen. Abschnitt 26.3.4, „Hinzufügen einer neuen benutzerdefinierten Funktion“, beschreibt die Reihenfolge, in der MySQL diese Funktionen aufruft.

Die xxx()-Hauptfunktion sollte so deklariert werden, wie es in diesem Abschnitt gezeigt wird. Beachten Sie, dass der Rückgabetyp und die Parameter unterschiedlich sind, je nachdem, ob Sie die SQL-Funktion XXX() in der CREATE FUNCTION-Anweisung mit dem Rückgabetyp STRING, INTEGER oder REAL deklarieren:

Für STRING-Funktionen gilt:

char *xxx(UDF_INIT *initid, UDF_ARGS *args,
          char *result, unsigned long *length,
          char *is_null, char *error);

Für INTEGER-Funktionen gilt:

long long xxx(UDF_INIT *initid, UDF_ARGS *args,
              char *is_null, char *error);

Für REAL-Funktionen gilt:

double xxx(UDF_INIT *initid, UDF_ARGS *args,
              char *is_null, char *error);

Die Initialisierungs- und Deinitialisierungsfunktionen werden wie folgt deklariert:

my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message);

void xxx_deinit(UDF_INIT *initid);

Der Parameter initid wird an alle drei Funktionen übergeben. Er verweist auf eine UDF_INIT-Struktur, die Informationen zwischen Funktionen übergibt. Die Bestandteile der UDF_INIT-Struktur werden im Folgenden aufgeführt. Die Initialisierungsfunktion sollte alle diejenigen Bestandteile ausfüllen, die geändert werden sollen. (Um einen Standardwert beizubehalten, ändern Sie einfach nichts.)

  • my_bool maybe_null

    xxx_init() sollte maybe_null auf 1 einstellen, wenn xxx() den Wert NULL zurückliefern kann. Der Standardwert ist 1, wenn irgendwelche Argumente als maybe_null deklariert sind.

  • unsigned int decimals

    Die Anzahl der Dezimalstellen rechts vom Dezimalpunkt (also salopp ausgedrückt: die Nachkommastellen). Der Standardwert ist die Höchtzahl der Dezimalstellen in den an die Hauptfunktion übergebenen Argumenten. (Wenn beispielsweise 1.34, 1.345 und 1.3 an die Funktion übergeben werden, wäre der Standardwert 3, da 1.345 3 Nachkommastellen hat.

  • unsigned int max_length

    Die Maximallänge des Ergebnisses. Der Standardwert max_length richtet sich nach dem Ergebnistyp der Funktion. Bei String-Funktionen ist er die Länge des längsten Arguments; bei Integer-Funktionen ist er 21 Ziffern. Bei Real-Funktionen ist der Standardwert 13 plus die Anzahl der durch initid->decimals vorgegebenen Dezimalstellen. (Bei numerischen Funktionen sind in der Länge auch Vorzeichen und Dezimalpunkt inbegriffen.)

    Wenn Sie einen Blob-Wert zurückliefern möchten, können Sie max_length auf 65 Kbyte oder 16 Mbyte einstellen. Der Speicherplatz wird zwar nicht reserviert, aber anhand der Länge entscheidet das System, welcher Datentyp für eine eventuell erforderliche temporäre Speicherung der Daten verwendet wird.

  • char *ptr

    Ein Zeiger, den die Funktion für ihre eigenen Zwecke verwenden kann. So können sich die Funktionen beispielsweise mit initid->ptr gegenseitig mitteilen, wie viel Speicher sie reservieren müssen. xxx_init() sollte den Speicher reservieren und diesem Zeiger zuweisen.

    initid->ptr = allocated_memory;
    

    In xxx() und xxx_deinit() verweisen Sie auf initid->ptr, um Speicher zu benutzen oder freizugeben.

  • my_bool const_item

    xxx_init() sollte const_item auf 1 setzen, wenn xxx() immer denselben Wert zurückgibt, und auf 0, wenn das nicht der Fall ist.

26.3.4.2. UDF-Aufrufsequenzen für Aggregatfunktionen

In diesem Abschnitt erfahren Sie, welche Funktionen Sie definieren müssen, um eine Aggregatfunktion als UDF anzulegen. Abschnitt 26.3.4, „Hinzufügen einer neuen benutzerdefinierten Funktion“, beschreibt die Reihenfolge, in der MySQL diese Funktionen aufruft.

  • xxx_reset()

    Diese Funktion wird aufgerufen, wenn MySQL die erste Zeile einer neuen Gruppe findet. Sie sollte eventuell vorhandene interne Summenvariablen zurücksetzen und dann das gegebene UDF_ARGS-Argument als ersten Wert des neuen internen Summenwerts für diese Gruppe verwenden. xxx_reset() wird folgendermaßen deklariert:

    char *xxx_reset(UDF_INIT *initid, UDF_ARGS *args,
                    char *is_null, char *error);
    

    xxx_reset() wird in MySQL 5.1 weder benötigt noch benutzt. Die UDF-Schnittstelle verwendet stattdessen xxx_clear(). Sie können jedoch xxx_reset() zusätzlich zu xxx_clear() definieren, wenn Ihre UDF auch mit älteren Serverversionen laufen soll. (Wenn Sie beide Funktionen einbeziehen möchten, können Sie xxx_reset() intern oft implementieren, indem Sie zunächst xxx_clear() alle Variablen zurücksetzen lassen und dann xxx_add() aufrufen, um das Argument UDF_ARGS als ersten Wert der Gruppe hinzuzufügen.)

  • xxx_clear()

    Diese Funktion wird aufgerufen, wenn MySQL die Summenergebnisse zurücksetzen muss. Sie wird für jede neue Gruppe ganz am Anfang aufgerufen, kann aber auch verwendet werden, um die Werte für eine Anfrage zurückzusetzen, wenn keine übereinstimmenden Zeilen gefunden wurden. xxx_clear() wird folgendermaßen deklariert:

    char *xxx_clear(UDF_INIT *initid, char *is_null, char *error);
    

    is_null wird auf CHAR(0) eingestellt, bevor xxx_clear() aufgerufen wird.

    Wenn etwas schief gegangen ist, können Sie einen Wert in der Variablen speichern, auf die das Argument error verweist. error zeigt auf eine Singlebyte-Variable, nicht auf einen String-Puffer.

    xxx_clear() ist für MySQL 5.1 obligatorisch.

  • xxx_add()

    Diese Funktion wird für alle Zeilen außer der ersten aufgerufen, die zu derselben Gruppe gehören. Mit ihr addieren Sie den Wert des Arguments UDF_ARGS zu Ihrer internen Summenvariablen.

    char *xxx_add(UDF_INIT *initid, UDF_ARGS *args,
                  char *is_null, char *error);
    

Die xxx()-Funktion für eine Aggregat-UDF sollte genauso definiert werden wie die für eine einfache UDF. Siehe hierzu Abschnitt 26.3.4.1, „UDF-Aufrufsequenzen“.

Für eine Aggregat-UDF ruft MySQL die Funktion xxx() auf, nachdem alle Zeilen der Gruppe abgearbeitet sind. Im Normalfall greifen Sie hier niemals auf ihr UDF_ARGS-Argument zu, sondern geben einen Wert zurück, der auf Ihren internen Summenvariablen beruht.

Rückgabewerte sollten in xxx() genau wie bei einer UDF behandelt werden, die keine Aggregatfunktion ist. Siehe Abschnitt 26.3.4.4, „Rückgabewerte und Fehlerbehandlung“.

Die Funktionen xxx_reset() und xxx_add() gehen mit ihrem UDF_ARGS-Argument genauso um wie Funktionen für Nicht-Aggregat-UDFs. Siehe Abschnitt 26.3.4.3, „Verarbeitung von Argumenten“.

Die Zeigerargumente für is_null und error sind dieselben wie bei allen Aufrufen von xxx_reset(), xxx_clear(), xxx_add() und xxx(). Das können Sie nutzen, um sich zu merken, dass ein Fehler ausgelöst wurde oder ob die xxx()-Funktion NULL zurückgeben sollte. Bitte speichern Sie keinen String in *error! error verweist auf eine Single-Byte-Variable, nicht auf einen String-Puffer.

*is_null wird für jede Gruppe (vor dem Aufruf von xxx_clear()) zurückgesetzt, *error nie.

Sind *is_null oder *error gesetzt, wenn xxx() zurückkehrt, liefert MySQL NULL als Ergebnis der Gruppenfunktion zurück.

26.3.4.3. Verarbeitung von Argumenten

Der Parameter args verweist auf eine UDF_ARGS-Struktur mit folgenden Bestandteilen:

  • unsigned int arg_count

    Die Anzahl der Argumente. Diesen Wert muss die Initialisierungsfunktion prüfen, wenn Ihre Funktion mit einer bestimmten Anzahl von Argumenten aufgerufen werden muss. Zum Beispiel:

    if (args->arg_count != 2)
    {
        strcpy(message,"XXX() requires two arguments");
        return 1;
    }
    
  • enum Item_result *arg_type

    Ein Zeiger auf ein Array, das die Argumenttypen enthält. Mögliche Typwerte sind STRING_RESULT, INT_RESULT und REAL_RESULT.

    Um sicherzustellen, dass die Argumente einen bestimmten Typ haben, und um einen Fehler auszulösen, wenn der Typ nicht stimmt, muss die Initialisierungsfunktion das Array arg_type betrachten. Zum Beispiel:

    if (args->arg_type[0] != STRING_RESULT ||
        args->arg_type[1] != INT_RESULT)
    {
        strcpy(message,"XXX() requires a string and an integer");
        return 1;
    }
    

    Anstatt zu verlangen, dass die Funktionsargumente bestimmte Typen haben, können Sie auch mit der Initialisierungsfunktion die arg_type-Elemente auf die gewünschten Typen einstellen. Dies veranlasst MySQL, den Argumenten bei jedem Aufruf von xxx() die richtigen Typen aufzuzwingen. Um beispielsweise aus dem ersten Argument einen String und aus dem zweiten einen Integer zu machen, muss Ihre xxx_init()-Funktion Folgendes tun:

    args->arg_type[0] = STRING_RESULT;
    args->arg_type[1] = INT_RESULT;
    
  • char **args

    args->args informiert die Initialisierungsfunktion über das Wesen der an Ihre Funktion übergebenen Argumente. Bei einem Konstantenargument i verweist args->args[i] auf den Argumentwert. (Wie Sie richtig auf diesen Wert zugreifen, erfahren Sie weiter unten.) Bei einem Nichtkonstantenargument ist args->args[i] gleich 0. Ein Konstantenargument ist ein Ausdruck, der nur Konstanten verwendet, wie beispielsweise 3 oder 4*7-2 oder SIN(3.14). Ein Nichtkonstantenargument ist ein Ausdruck, der auf Werte verweist, die sich von Zeile zu Zeile ändern können, wie etwa Spaltennamen oder Funktionen, die mit Nichtkonstantenargumenten aufgerufen werden.

    Für jeden Aufruf der Hauptfunktion enthält args->args die Argumente, die tatsächlich an die in Bearbeitung befindliche Zeile übergeben werden.

    Funktionen können folgendermaßen auf ein Argument i verweisen:

    • Ein Argument vom Typ STRING_RESULT wird als String-Zeiger mit einer Längenangabe übergeben, damit auch Binärdaten oder Daten von beliebiger Länge verarbeitet werden können. Der String-Inhalt ist als args->args[i] und die Länge des Strings als args->lengths[i] angegeben. Bitte setzen Sie nicht voraus, dass Strings mit null beendet werden.

    • Bei Argumenten vom Typ INT_RESULT müssen Sie args->args[i] in einen long long-Wert umwandeln:

      long long int_val;
      int_val = *((long long*) args->args[i]);
      
    • Bei Argumenten vom Typ REAL_RESULT müssen Sie args->args[i] in einen double-Wert umwandeln:

      double    real_val;
      real_val = *((double*) args->args[i]);
      
  • unsigned long *lengths

    Für die Initialisierungsfunktion zeigt das Array lengths die maximale String-Länge für jedes Argument an. Diese sollten Sie nicht ändern. Für jeden Aufruf der Hauptfunktion enthält lengths die tatsächlichen Längen der String-Argumente, die an die in Bearbeitung befindliche Zeile übergeben werden. Für Argumente der Typen INT_RESULT oder REAL_RESULT enthält lengths immer noch die maximale Länge des Arguments (wie für die Initialisierungsfunktion).

26.3.4.4. Rückgabewerte und Fehlerbehandlung

Die Initialisierungsfunktion sollte 0 zurückgeben, wenn kein Fehler auftritt, und ansonsten 1. Kommt es zu einem Fehler, so sollte xxx_init() eine auf null endende Fehlermeldung im Parameter message speichern. Die Meldung wird an den Client zurückgegeben. Der Puffer für Fehlermeldungen ist zwar MYSQL_ERRMSG_SIZE Zeichen lang, aber Sie sollten dennoch versuchen, die Meldungen auf maximal 80 Zeichen zu begrenzen, damit sie auf die Breite eines normalen Bildschirms passen.

Der Rückgabewert der Hauptfunktion xxx() ist für long long- und double-Funktionen der Funktionswert. Eine String-Funktion sollte einen Zeiger auf das Ergebnis zurückliefern und *result und *length auf den Inhalt und die Länge des Ergebniswerts einstellen. Zum Beispiel:

memcpy(result, "result string", 13);
*length = 13;

Der result-Puffer, der an die xxx()-Funktion übergeben wird, ist 255 Byte lang. Wenn Ihr Ergebnis hineinpasst, müssen Sie sich um die Speicherzuweisung für Ergebnisse keine Gedanken machen.

Muss Ihre String-Funktion jedoch einen String zurückliefern, der länger als 255 Byte ist, so müssen Sie den Speicher dafür reservieren, indem Sie malloc() in Ihrer xxx_init()- oder xxx()-Funktion aufrufen und den Speicher dann in der xxx_deinit()-Funktion wieder freigeben. Sie können den reservierten Speicher auch in dem ptr-Slot in der UDF_INIT-Struktur speichern, um ihn für zukünftige xxx()-Aufrufe wiederverwenden zu können. Siehe Abschnitt 26.3.4.1, „UDF-Aufrufsequenzen“.

Um den Rückgabewert NULL in der Hauptfunktion anzuzeigen, setzen Sie *is_null auf 1:

*is_null = 1;

Um anzuzeigen, dass die Hauptfunktion einen Fehler zurückgibt, setzen Sie *error auf 1:

*error = 1;

Wenn xxx() den Wert von *error für irgendeine Zeile auf 1 setzt, ist der Funktionswert für die aktuelle Zeile und alle folgenden Zeilen, die in der Anweisung verarbeitet werden, in welcher XXX() aufgerufen wird, gleich NULL. (xxx() wird für die nachfolgenden Zeilen noch nicht einmal aufgerufen.)

26.3.4.5. Kompilieren und Installieren benutzerdefinierter Funktionen

Dateien, die UDFs implementieren, müssen auf dem Serverhost kompiliert und installert werden. Dies wird weiter unten für die Beispiel-UDF-Datei sql/udf_example.cc aus der MySQL-Quelldistribution beschrieben.

Die nachfolgenden Instruktionen gelten für Unix. Anleitungen für Windows finden Sie weiter unten in diesem Abschnitt.

Die Datei udf_example.cc enthält die folgenden Funktionen:

  • Die Funktion metaphon() gibt einen Metaphon-String aus dem String-Argument zurück. Dieser ist so etwas wie ein Soundex-String, allerdings besser auf die englische Sprache zugeschnitten.

  • Die Funktion myfunc_double() gibt die Summe der ASCII-Werte der in ihren Argumenten enthaltenen Zeichen geteilt durch die Summe der Längen dieser Argumente zurück.

  • Die Funktion myfunc_int() gibt die Summe der Längen ihrer Argumente zurück.

  • Die Funktion sequence([const int]) gibt eine Sequenz zurück, die ab der gegebenen Zahl oder, wenn keine gegeben wurde, ab 1 beginnt.

  • Die Funktion lookup() gibt die IP-Nummer eines Hostnamens zurück.

  • Die Funktion reverse_lookup() gibt den Hostnamen zu einer IP-Nummer zurück. Diese Funktion kann entweder mit einem einzigen String-Argument der Form 'xxx.xxx.xxx.xxx' oder mit vier Zahlen aufgerufen werden.

Eine Datei, die dynamisch zu laden sein soll, sollte als Sharable Object-Datei mit einem Befehl wie diesem kompiliert werden:

shell> gcc -shared -o udf_example.so udf_example.cc

Wenn Sie gcc benutzen, müsste sich udf_example.so auch mit einem einfacheren Befehl anlegen lassen:

shell> make udf_example.so

Die richtigen Compiler-Optionen für Ihr System finden Sie einfach heraus, indem Sie folgenden Befehl im sql-Verzeichnis Ihres MySQL-Quellbaums laufen lassen:

shell> make udf_example.o

Sie sollten einen ähnlichen Compile-Befehl geben, wie ihn make anzeigt, allerdings ohne die Option -c am Zeilenende. Stattdessen fügen Sie die Option -o udf_example.so hinzu. (Auf manchen Systemen müssen Sie eventuell die Option -c im Befehl beibehalten.)

Nachdem Sie ein Shared Object für UDFs kompiliert haben, müssen Sie es installieren und MySQL mitteilen, dass es da ist. Wenn Sie ein Shared Object aus udf_example.cc kompilieren, entsteht eine Datei, die ungefähr udf_example.so heißt (der genaue Name ist plattformabhängig). Diese Datei kopieren Sie in das Plug-In-Verzeichnis des Servers, das Sie anhand der Systemvariablen plugin_dir herausfinden können. (Hinweis: Dies ist eine Änderung in MySQL 5.1. In früheren Versionen von MySQL konnte das Shared Object in jedem Verzeichnis liegen, das der dynamische Linker Ihres Systems untersuchte.)

Auf manchen Systemen erkennt das Programm ldconfig, das den dynamischen Linker konfiguriert, ein Shared Object nur dann, wenn sein Name mit lib anfängt. In solchen Fällen sollten Sie eine Datei wie udf_example.so in libudf_example.so umbenennen.

Auf Windows können Sie benutzerdefinierte Funktionen wie folgt kompilieren:

  1. Zuerst benötigen Sie das BitKeeper-Quellarchiv für MySQL 5.1. Siehe Abschnitt 2.8.3, „Installation vom Entwicklungs-Source-Tree“.

  2. In diesem Quellarchiv suchen Sie im Verzeichnis VC++Files/examples/udf_example nach den Dateien udf_example.def, udf_example.dsp und udf_example.dsw.

  3. Außerdem schauen Sie im Quellarchiv in das Verzeichnis sql und kopieren daraus die Datei udf_example.cc in das VC++Files/examples/udf_example-Verzeichnis. Dann benennen Sie die Datei in udf_example.cpp um.

  4. Öffnen Sie die Datei udf_example.dsw mit Visual Studio VC++ und kompilieren Sie damit die UDFs als ganz normales Projekt.

Sobald die Shared Object-Datei installiert ist, teilen Sie mysqld die neuen Funktionen mit folgenden Anweisungen mit:

mysql> CREATE FUNCTION metaphon RETURNS STRING SONAME 'udf_example.so';
mysql> CREATE FUNCTION myfunc_double RETURNS REAL SONAME 'udf_example.so';
mysql> CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME 'udf_example.so';
mysql> CREATE FUNCTION lookup RETURNS STRING SONAME 'udf_example.so';
mysql> CREATE FUNCTION reverse_lookup
    ->        RETURNS STRING SONAME 'udf_example.so';
mysql> CREATE AGGREGATE FUNCTION avgcost
    ->        RETURNS REAL SONAME 'udf_example.so';

Zum Löschen von Funktionen verwenden Sie DROP FUNCTION:

mysql> DROP FUNCTION metaphon;
mysql> DROP FUNCTION myfunc_double;
mysql> DROP FUNCTION myfunc_int;
mysql> DROP FUNCTION lookup;
mysql> DROP FUNCTION reverse_lookup;
mysql> DROP FUNCTION avgcost;

Die Anweisungen CREATE FUNCTION und DROP FUNCTION nehmen Änderungen in der Systemtabelle func in der mysql-Datenbank vor. Der Name, Typ und Shared Library-Name werden in dieser Tabelle gespeichert. Daher benötigen Sie INSERT- und DELETE-Rechte für die mysql-Datenbank, um dort Funktionen anzulegen und zu löschen.

Bitte verwenden Sie CREATE FUNCTION nicht, um eine Funktion hinzuzufügen, die bereits angelegt ist. Wenn Sie eine Funktion neu installieren müssen, entfernen Sie sie mit DROP FUNCTION und installieren sie dann mit CREATE FUNCTION neu. Das müssen Sie beispielsweise tun, wenn Sie eine neue Version Ihrer Funktion installieren, damit auch mysqld von der neuen Version weiß. Ansonsten würde der Server weiterhin die alte Version verwenden.

Eine aktive Funktion ist eine Funktion, die mit CREATE FUNCTION geladen, aber nicht mit DROP FUNCTION entfernt wurde. Alle aktiven Funktionen werden bei jedem Serverstart neu geladen, es sei denn, Sie fahren mysqld mit der Option --skip-grant-tables hoch. In diesem Fall wird die UDF-Initialisierung übersprungen und die UDFs stehen nicht zur Verfügung.

26.3.4.6. Vorsichtsmaßnahmen bei benutzerdefinierten Funktionen (UDF)

MySQL verhindert durch folgende Maßnahmen den Missbrauch benutzerdefinierter Funktionen:

Sie benötigen das INSERT-Recht, um CREATE FUNCTION, und das DELETE-Recht, um DROP FUNCTION sagen zu dürfen. Die Berechtigungen sind erforderlich, weil diese Anweisungen Zeilen der mysql.func-Tabelle anlegen und löschen.

Für UDFs sollte zusätzlich zu dem xxx-Symbol für die Hauptfunktion xxx() mindestens ein weiteres Symbol definiert sein. Diese Hilfssymbole stehen für die Funktionen xxx_init(), xxx_deinit(), xxx_reset(), xxx_clear() und xxx_add(). mysqld unterstützt überdies eine --allow-suspicious-udfs-Option, die steuert, ob UDFs, die nur das eine xxx-Symbol haben, überhaupt geladen werden dürfen. Nach Voreinstellung ist die Option ausgeschaltet, um zu verhindern, dass aus Shared Object-Dateien Funktionen geladen werden, die keine gültigen UDFs sind. Wenn Sie noch mit älteren UDFs arbeiten, die lediglich das xxx-Symbol haben und nicht mit einem Hilfssymbol rekompiliert werden können, kann es notwendig werden, die Option --allow-suspicious-udfs anzugeben. Ansonsten sollten Sie diese Option möglichst nicht einschalten.

Objektdateien für UDFs dürfen nicht in jedes beliebige Verzeichnis gelegt werden, sondern nur in das Plug-In-Verzeichnis des Servers, das Sie am Wert der Systemvariablen plugin_dir erkennen können. (Hinweis: Dies ist neu in MySQL 5.1. In früheren Versionen von MySQL konnte das Shared Object in jedem Verzeichnis platziert werden, das der dynamische Linker des Systems untersuchte.)

26.3.5. Hinzufügen einer neuen nativen Funktion

Hier wird erklärt, wie Sie vorgehen müssen, um eine neue native Funktion hinzuzufügen. Beachten Sie, dass Sie native Funktionen nicht in eine Binärdistribution einfügen können, da ein Eingriff in den Quellcode von MySQL notwendig ist. Sie müssen MySQL also selbst aus einer Quelldistribution kompilieren. Außerdem müssen Sie bei einer Umstellung auf eine andere MySQL-Version (beispielsweise wenn eine neue Version herauskommt) daran denken, die gesamte Prozedur mit der neuen Version zu wiederholen.

Um eine neue native MySQL-Funktion hinzuzufügen, verfahren Sie folgendermaßen:

  1. Sie binden in lex.h eine neue Zeile ein, die den Namen der Funktion im Array sql_functions[] definiert.

  2. Wenn der Funktionsprototyp einfach ist (nur null, ein, zwei oder drei Argumente hat), geben Sie in lex.h als zweites Argument im Array sql_functions[] den Wert SYM(FUNC_ARGN) an (wobei N die Anzahl der Argumente ist) und fügen eine Funktion hinzu, die ein Funktionsobjekt in item_create.cc erzeugt. Als Beispiele dafür können Sie sich "ABS" und create_funcs_abs() anschauen.

    Ist der Funktionsprototyp komplizierter (wenn beispielsweise die Funktion eine variable Anzahl Argumente entgegennimmt), müssen Sie zwei neue Zeilen in sql_yacc.yy einfügen: Die eine gibt ein Präprozessorsymbol an, das yacc definieren sollte (diese sollte an den Anfang der Datei eingefügt werden). Dann definieren Sie die Funktionsparameter und fügen ein „item“ mit diesen Parametern zu der Parsing-Regel für simple_expr Parametern hinzu. Als Beispiel dafür, wie dies getan wird, können Sie die diversen Exemplare von ATAN in sql_yacc.yy anschauen.

  3. Deklarieren Sie in item_func.h eine Klasse, die aus Item_num_func oder Item_str_func erbt, je nachdem, ob Ihre Funktion eine Zahl oder einen String zurückgibt.

  4. In item_func.cc fügen Sie eine der folgenden Deklarationen ein, je nachdem, ob Sie eine numerische oder eine String-Funktion definieren:

    double   Item_func_newname::val()
    longlong Item_func_newname::val_int()
    String  *Item_func_newname::Str(String *str)
    

    Wenn Sie Ihr Objekt aus einem der Standardelemente erben (wie etwa Item_num_func), müssen Sie wahrscheinlich nur eine dieser Funktionen definieren und es dann dem Parent-Objekt überlassen, sich um die übrigen Funktionen zu kümmern. So definiert zum Beispiel die Klasse Item_str_func eine val()-Funktion, die atof() auf dem Rückgabewert von ::str() ausführt.

  5. Außerdem müssen Sie wahrscheinlich folgende Objektfunktion definieren:

    void Item_func_newname::fix_length_and_dec()
    

    Diese Funktion sollte zumindest max_length anhand der gegebenen Argumente berechnen. max_length gibt an, wie viele Zeichen die Funktion höchstens zurückgeben kann. Diese Funktion sollte überdies maybe_null = 0 einstellen, wenn die Hauptfunktion keinen NULL-Wert zurückgeben kann. Ob irgendwelche Funktionsargumente NULL zurückgeben könnten, prüft die Funktion anhand der maybe_null-Variablen dieser Argumente. Ein typisches Beispiel dafür finden Sie in Item_func_mod::fix_length_and_dec.

Alle Funktionen müssen Thread-sicher sein. Mit anderen Worten: Sie dürfen keine globalen oder statischen Variablen in diesen Funktionen benutzen, ohne sie durch Mutex-Objekte zu schützen.

Wenn Sie NULL aus einer ::val()-, ::val_int()- oder ::str()-Funktion zurückgeben möchten, müssen Sie null_value auf 1 setzen und 0 zurückliefern.

Für ::str()-Objektfunktionen müssen Sie sich darüber hinaus Folgendes merken:

  • Das String *str-Argument liefert einen String-Puffer, der genutzt werden kann, um das Ergebnis zu speichern. (Um mehr über den Typ String zu erfahren, werfen Sie einen Blick in die Datei sql_string.h.)

  • Die ::str()-Funktion sollte den String zurückgeben, der das Resultat speichert, oder (char*) 0, wenn das Resultat NULL ist.

  • Alle gegenwärtigen String-Funktionen versuchen, nur dann Speicher zu reservieren, wenn es absolut unumgänglich ist!

26.4. Hinzufügen neuer Prozeduren zu MySQL

In MySQL können Sie mit der Sprache C++ eine Prozedur definieren, die Daten in einer Anfrage betrachten und ändern kann, ehe sie sie an den Client sendet. Diese Änderung kann zeilenweise oder auf der Ebene von GROUP BY erfolgen.

Wir haben eine Beispielprozedur geschrieben, um zu zeigen, was alles möglich ist.

Außerdem empfehlen wir Ihnen, einen Blick auf mylua zu werfen. Damit können Sie nämlich die Sprache LUA einsetzen, um eine Prozedur zur Laufzeit in mysqld zu laden.

26.4.1. PROCEDURE ANALYSE

analyse([max_elements,[max_memory]])

Diese Prozedur ist in der Datei sql/sql_analyse.cc definiert. Sie untersucht ein Anfrageergebnis und gibt eine Analyse der Resultate zurück:

  • max_elements (Standardwert 256) ist die Höchstzahl unterschiedlicher Werte, die analyse pro Spalte erkennen kann. Diesen Wert verwendet analyse, um zu prüfen, ob ENUM der optimale Datentyp wäre.

  • max_memory (Standardwert 8192) ist der größtmögliche Speicher, den analyse pro Spalte zuweisen kann, während die Prozedur versucht, alle unterschiedlichen Werte zu finden.

SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]])

26.4.2. Schreiben einer Prozedur

Die einzige Dokumentation hierzu ist zurzeit der Quellcode.

Alle Informationen über Prozeduren finden Sie in den folgenden Dateien:

  • sql/sql_analyse.cc

  • sql/procedure.h

  • sql/procedure.cc

  • sql/sql_select.cc


Dies ist eine Übersetzung des MySQL-Referenzhandbuchs, das sich auf dev.mysql.com befindet. Das ursprüngliche Referenzhandbuch ist auf Englisch, und diese Übersetzung ist nicht notwendigerweise so aktuell wie die englische Ausgabe. Das vorliegende deutschsprachige Handbuch behandelt MySQL bis zur Version 5.1.