Kapitel 26. MySQL erweitern
Zurück |  | Weiter

## Kapitel 26. MySQL erweitern

## 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 `plugin`s 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:

  Typ | Bedeutung
  `FT_TOKEN_EOF` | Ende der Daten
  `FT_TOKEN_WORD` | Ein normales Wort
  `FT_TOKEN_LEFT_PAREN` | Beginn einer Gruppe oder eines Teilausdrucks
  `FT_TOKEN_RIGHT_PAREN` | Ende einer Gruppe oder eines Teilausdrucks
  `FT_TOKEN_STOPWORD` | Ein 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-Typ | C-/C++-Typ
  `STRING` | `char *`
  `INTEGER` | `long long`
  `REAL` | `double`

  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_ARG`N`)` 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.

Zurück |  | Weiter
Kapitel 25. Connectors | Zum Anfang | Anhang A. Probleme und häufig auftretende Fehler