PostgreSQL: Entwicklung von Erweiterungen (Funktionen) in C-Sprache

Dieser Artikel wurde vor ein paar Jahren geschrieben und wusste nicht, wo er abgelegt werden konnte, und vergaß dann.

Die Bedeutung der Verwendung der C-Sprache bei der Entwicklung von Erweiterungen für PostgreSQL im Vergleich zu interpretierten (Skript-) Sprachen kann auf zwei Punkte reduziert werden: Leistung und Funktionalität. Aber einfach, der in C geschriebene Code funktioniert viel schneller, zum Beispiel, wenn die Funktion in der Anforderung für jeden Datensatz millionenfach aufgerufen wird. Insbesondere können einige PostgreSQL-Funktionen nur in C ausgeführt werden, z. B. in anderen Sprachen. Typen (insbesondere wenn Sie einen Wert von einer Funktion zurückgeben) ANYELEMENT , ANYARRAY und besonders wichtige VARIADIC werden nicht unterstützt .

Einfache C-Funktion


Zum Beispiel schreiben wir eine Funktion, die zwei Argumente akzeptiert und hinzufügt. Dieses Beispiel wird in der Dokumentation zu PostgreSQL beschrieben, aber wir werden es leicht verbessern und sammeln. Laden Sie dann in PostgreSQL und schreiben Sie einen Aufruf von der SQL-Funktion.

#include "postgres.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(add_ab);
Datum add_ab(PG_FUNCTION_ARGS)
{
    int32 arg_a = PG_GETARG_INT32(0);
    int32 arg_b = PG_GETARG_INT32(1);
    PG_RETURN_INT32(arg_a + arg_b);
}

Die Datei add_func.c kann als Vorlage für die Entwicklung komplexerer Funktionen verwendet werden. Gehen Sie auch den Code durch

  • #include "postgresql.h": Diese Header-Datei, die Sie immer einschließen müssen, enthält verschiedene grundlegende Typen und Funktionen.
  • #include "fmgr.h": Die Header-Datei enthält verschiedene PG_ * -Makros.
  • PG_MODULE_MAGIC: Ein Makro, das feststellt, dass wir ein Modul für PostgreSQL über Version 8.2 entwickeln.
  • PG_FUNC_INFO_V1: Ein Makro, das Funktionsaufrufkonventionen in PostgreSQL definiert. Wenn Sie eine Funktion ohne diese deklarieren, besteht eine Vereinbarung über den Aufruf von Version 0, andernfalls Version 1.
  • Datum: . , PostgreSQL. - VARIANT Microsoft. «» - . , .
  • add_ab(PG_FUNCTION_ARGS): . - . , .
  • int32 arg_a = PG_GETARG_INT32(0): ( ).
  • PG_RETURN_INT32(arg_a + arg_b): .

Jetzt müssen wir dies richtig kompilieren und kompilieren. Die Ausgabe ist eine dynamisch ladbare gemeinsam genutzte Bibliothek (* .so). Zu diesem Zweck ist es bequemer, dies über das Makefile zu tun . In der Dokumentation werden die Schlüssel und Pfade beschrieben, die registriert werden müssen . Wir werden sie jedoch mit PGXS erfassen . Dies ist eine Umgebung für Erweiterungsentwickler. Dies bedeutet, dass alle für PostgreSQL erforderlichen -dev- und -devel- Pakete auf Ihrem System installiert sein müssen .

MODULES = add_func
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

Wir erstellen eine SQL-Funktion


Jetzt schreiben wir eine SQL-Funktion, die aus unserer zuvor erstellten Erweiterungsbibliothek aufgerufen wird.
CREATE FUNCTION add(int, int)
  RETURNS int
AS '/usr/lib/postgresql/9.4/lib/add_func', 'add_ab'
LANGUAGE C STRICT;

Das ist alles! Jetzt können wir diese Funktion also nutzen

SELECT add(1, 2);

Automatisieren Sie die Installation


Jetzt automatisieren wir die Installation ein wenig. Dies ist sehr nützlich, wenn Sie nicht im Voraus wissen, welche Version von PostgreSQL verwendet und auf welche Weise sie installiert wird. Erstellen Sie dazu die folgende Datei:

CREATE FUNCTION add(int, int)
  RETURNS int
AS 'MODULE_PATHNAME', 'add_ab'
LANGUAGE C STRICT;

und füge eine Zeile zum Makefile hinzu ,

MODULES = add_func
DATA_built = add_funcs.sql
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

Ein wenig verbessern


Wir werden einige Änderungen an unserem Beispiel vornehmen und einige Verbesserungen vornehmen.

Argumente überprüfen


Denken Sie daran , dass wir in der SQL-Definition der Funktion add (int, int) das Schlüsselwort STRICT verwendet haben ? Dies bedeutet, dass wenn mindestens eines der Argumente NULL ist , die Funktion nicht funktioniert und einfach NULL zurückgibt . Dies ähnelt dem Verhalten von SQL-Anweisungen. Wenn der Operator + mindestens ein NULL- Argument enthält , ist das Ergebnis NULL .

Wir fügen unserer Funktion eine Argumentprüfung hinzu, ähnlich der SQL-Aggregationsfunktion sum () , die NULL- Werte ignoriert und weiterhin funktioniert. Dafür müssen wir tun

  • Stellen Sie sicher, dass die Funktion auch dann funktioniert, wenn eines der Argumente NULL ist
  • Wenn beide Argumente NULL sind , geben Sie NULL zurück

PG_FUNCTION_INFO_V1(add_ab_null);
Datum add_ab_null(PG_FUNCTION_ARGS)
{
    int32 not_null = 0;
    int32 sum = 0;
    if (!PG_ARGISNULL(0))
    {
        sum += PG_GETARG_INT32(0);
        not_null = 1;
    }
    if (!PG_ARGISNULL(1))
    {
        sum += PG_GETARG_INT32(1);
        not_null = 1;
    }
    if (not_null)
    {
        PG_RETURN_INT32(sum);
    }
    PG_RETURN_NULL();
}

Und jetzt schau es dir an,

CREATE FUNCTION add(int, int)
 RETURNS int
AS '$libdir/add_func', 'add_ab_null'
LANGUAGE C;

SELECT add(NULL, NULL) AS must_be_null, add(NULL, 1) AS must_be_one;
-[ RECORD 1 ]+--
must_be_null |
must_be_one  | 1

Und so kann dasselbe mit Standard-PostgreSQL-Tools erreicht werden:
SELECT (CASE WHEN (a IS null) AND (b IS null)
(THEN null
ELSE coalesce(a, 0) + coalesce(b,0)
END)
FROM (SELECT 1::int AS a, null::int AS b) s;
-[ RECORD 1 ]
 case | 1

Beliebig viele Argumente in einer Funktion


Wie Sie bereits bemerkt haben, haben wir Makros verwendet, um den Wert der Argumente zu ermitteln. Daher können wir eine beliebige Anzahl von Argumenten übergeben und dann einfach deren Werte in einer Schleife lesen.

if (!PG_ARGISNULL(i))
{
    sum += PG_GETARG_INT32(i);
    not_null = 1;
}

Möglicherweise haben Sie sofort eine Frage, aber Sie können ein Array verwenden, um den Wert der Argumente zu übergeben. Tatsächlich muss dies getan werden, aber leider ist dies aufgrund des Vorhandenseins eines eigenen Speichermanagers in PostgreSQL keine so triviale Aufgabe. Aber versuchen Sie es zu lösen. In der PostgreSQL-Dokumentation gibt es ein Beispiel, in dem das Zeichenarray text [] übergeben wird, aber dies ist nicht genau das, was wir brauchen. Versuchen wir, uns an unsere Zwecke anzupassen.

#include "utils/array.h"     //       
#include "catalog/pg_type.h" // INT4OID
PG_MODULE_MAGIC;
Datum add_int32_array(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(add_int32_array);
Datum add_int32_array(PG_FUNCTION_ARGS)
{
    //     .
    //          ,       int.
    ArrayType *input_array;
    int32 sum = 0;
    bool not_null = false;

    Datum *datums;
    bool *nulls;
    int count;
    int i;
    input_array = PG_GETARG_ARRAYTYPE_P(0); //     .  *_P  
                                            //        ,   INT32

    //          INT32 (INT4)
    Assert(ARR_ELEMTYPE(input_array) == INT4OID);

    //      
    if (ARR_NDIM(input_array) > 1)
        ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("1-dimensional array needed")));

    deconstruct_array(input_array, //  
                      INT4OID,     //  
                      4,           //    
                      true,        // int4   
                      'i',         //  'i'
                      &datums, &nulls, &count); //   

    for(i = 0; i < count; i++)
    {
        //     
        if (nulls[i])
            continue;

        // ,       
        sum += DatumGetInt32(datums[i]);
        not_null = true;
    }
    if (not_null)
        PG_RETURN_INT32(sum);

    PG_RETURN_NULL();
}

Wie immer gehen wir den Code durch,

  • Es gibt keinen speziellen Typ für ganzzahlige Arrays, daher verwenden wir den generischen Typ ArrayType , der für jeden Array- Typ funktioniert
  • Um das Array mit dem ersten Argument zu initialisieren, haben wir das spezielle Makro PG_GETARG_ARRAYTYPE_P verwendet
  • Es wird auch überprüft, ob das Array wirklich eindimensional ARR_NDIM ist
  • Der OID-Typ für int4 (= 23) ist als INT4OID definiert. Um Definitionen für andere Typen anzuzeigen, können Sie SQL verwenden:

    select oid, typlen, typbyval, typalign from pg_type
    where typname = 'int4';
    -[ RECORD 1 ]
    oid | 23
    typlen | 4
    typbyval | t
    typalign | i
    


Jetzt müssen wir PostgreSQL nur noch beibringen, dies zu verwenden, indem wir eine Funktion deklarieren, die ein int [] -Argument akzeptiert.

CREATE OR REPLACE FUNCTION add_arr(int[]) RETURNS int
AS '$libdir/add_func', 'add_int32_array'
LANGUAGE C STRICT;

Und prüfe

SELECT add_arr('{1,2,3,4,5,6,7,8,9}');
-[ RECORD 1 ]
add_arr | 45
SELECT add_arr(ARRAY[1,2,NULL]);
-[ RECORD 1 ]
add_arr | 3
SELECT add_arr(ARRAY[NULL::int]);
-[ RECORD 1 ]
add_arr |

Empfehlungen


Lassen Sie uns nun einige der wichtigsten Empfehlungen zum Erstellen von Erweiterungen zusammenfassen.

Arbeite mit dem Gedächtnis


Die Autoren und Entwickler des PostgreSQL-DBMS legen besonderes Augenmerk auf die Arbeit mit dem Speicher und die Vermeidung von Lecks. Dies ist eine der Grundregeln für die Entwicklung eines DBMS und seiner Erweiterungen. Dies basiert auf ihrer eigenen Entscheidung - dem Kontext der Erinnerung.

Mit palloc () und pfree ()


Alle Arbeiten mit Speicher in PostgreSQL bestehen aus der Verwendung von Nicht-Standardfunktionen palloc () und pfree ().

Initialisierung von Strukturen


Initialisieren Sie immer neue deklarierte Strukturen. Rufen Sie beispielsweise memset () nach palloc () auf.

Verbundene Dateien


Jede Ihrer Erweiterungen muss mindestens zwei Dateien enthalten: postgres.h und fmgr.h.

Nützliche Links


PostgreSQL: Server-Programmierschnittstelle .

PostgreSQL: Benutzerdefinierte Funktionen in C

PostgreSQL Extension Network

All Articles