PostgreSQL: Pengembangan ekstensi (fungsi) dalam bahasa C.

Artikel ini ditulis beberapa tahun yang lalu, dan tidak tahu di mana bisa diletakkan, dan kemudian lupa.

Arti menggunakan bahasa C saat mengembangkan ekstensi untuk PostgreSQL dibandingkan dengan bahasa yang ditafsirkan (dituliskan) dapat dikurangi menjadi dua poin: kinerja dan fungsionalitas. Tapi sederhananya, kode yang ditulis dalam C akan bekerja lebih cepat, misalnya, jika fungsinya disebut sejuta kali dalam permintaan untuk setiap catatan. Lebih khusus lagi, beberapa fitur PostgreSQL tidak dapat dilakukan sama sekali kecuali dalam C, misalnya, dalam bahasa lain, tipe (terutama jika Anda mengembalikan nilai dari suatu fungsi) ANYELEMENT , ANYARRAY dan VARIADIC yang sangat penting tidak didukung .

Fungsi C sederhana


Sebagai contoh, kami menulis fungsi yang mengambil dua argumen dan menambahkannya. Contoh ini dijelaskan dalam dokumentasi untuk PostgreSQL, tetapi kami akan sedikit memperbaikinya dan mengumpulkannya. Kemudian muat ke PostgreSQL dan tulis panggilan dari fungsi SQL.

#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);
}

File add_func.c dapat digunakan sebagai templat untuk mengembangkan fungsionalitas yang lebih kompleks. Juga, buka kode

  • #include "postgresql.h": file header ini yang harus selalu Anda sertakan, berisi berbagai tipe dan fungsi dasar.
  • #include "fmgr.h": file header berisi berbagai makro PG_ *.
  • PG_MODULE_MAGIC: makro yang menentukan bahwa kami sedang mengembangkan modul untuk PostgreSQL di atas versi 8.2.
  • PG_FUNC_INFO_V1: Konvensi pemanggilan fungsi mendefinisikan makro di dalam PostgreSQL. Jika Anda mendeklarasikan fungsi tanpa itu, maka akan ada kesepakatan untuk memanggil Versi 0, jika tidak, Versi 1.
  • Datum: . , PostgreSQL. - VARIANT Microsoft. ยซยป - . , .
  • add_ab(PG_FUNCTION_ARGS): . - . , .
  • int32 arg_a = PG_GETARG_INT32(0): ( ).
  • PG_RETURN_INT32(arg_a + arg_b): .

Sekarang kita perlu kompilasi dan kompilasi ini dengan benar. Outputnya akan menjadi shared library yang dapat dimuat secara dinamis (* .so). Untuk ini, akan lebih mudah untuk melakukannya melalui Makefile . Dokumentasi menjelaskan kunci dan jalur yang perlu didaftarkan, tetapi kami akan mengumpulkan menggunakan PGXS . Ini adalah sebuah lingkungan untuk pengembang ekstensi, yang berarti bahwa semua yang diperlukan -dev dan -devel paket untuk PostgreSQL harus diinstal pada sistem Anda .

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

Kami membuat fungsi SQL


Sekarang kita akan menulis fungsi SQL yang akan dipanggil dari pustaka ekstensi yang dibuat sebelumnya.
CREATE FUNCTION add(int, int)
  RETURNS int
AS '/usr/lib/postgresql/9.4/lib/add_func', 'add_ab'
LANGUAGE C STRICT;

Itu saja! Sekarang kita bisa menggunakan fungsi ini begitu

SELECT add(1, 2);

Instalasi otomatis


Sekarang kami mengotomatiskan instalasi sedikit. Ini akan sangat berguna ketika Anda tidak tahu sebelumnya versi PostgreSQL mana yang digunakan, dan bagaimana cara menginstalnya. Untuk melakukan ini, buat file berikut,

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

dan tambahkan baris ke Makefile ,

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

Tingkatkan sedikit


Kami akan membuat beberapa perubahan pada contoh kami dan melakukan beberapa perbaikan.

Memeriksa Argumen


Ingat dalam definisi SQL fungsi add (int, int) yang kami gunakan kata kunci STRICT ? Ini berarti bahwa jika setidaknya salah satu argumen adalah NULL , fungsi tidak akan berfungsi, dan hanya akan mengembalikan NULL . Ini mirip dengan perilaku pernyataan SQL, misalnya, jika ada setidaknya satu argumen NULL di operator + , hasilnya akan menjadi NULL . Kami menambahkan pemeriksaan argumen ke fungsi kami, mirip dengan jumlah fungsi agregasi SQL () , yang mengabaikan nilai NULL dan terus bekerja. Untuk ini yang perlu kita lakukan



  • Pastikan bahwa fungsi tersebut berfungsi bahkan jika salah satu argumennya adalah NULL
  • Jika kedua argumen NULL , maka kembalikan NULL

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();
}

Dan sekarang periksa,

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

Dan di sini adalah bagaimana hal yang sama dapat dicapai dengan alat PostgreSQL standar,
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

Sejumlah argumen dalam suatu fungsi


Seperti yang sudah Anda perhatikan, kami menggunakan makro untuk mendapatkan nilai argumen. Oleh karena itu, kita dapat melewati sejumlah argumen, dan kemudian hanya membaca nilainya dalam satu lingkaran,

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

Anda mungkin langsung memiliki pertanyaan, tetapi Anda dapat menggunakan array untuk meneruskan nilai argumen. Dan sebenarnya, ini adalah apa yang perlu dilakukan, tetapi sayangnya karena kehadiran manajer memori sendiri di PostgreSQL, ini bukan tugas yang sepele. Tetapi cobalah untuk menyelesaikannya. Ada contoh dalam dokumentasi PostgreSQL di mana array karakter teks [] diteruskan, tetapi ini bukan yang kita butuhkan. Mari kita coba beradaptasi untuk tujuan kita,

#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();
}

Seperti biasa, mari kita lihat kodenya,

  • Tidak ada tipe khusus untuk array integer, jadi kami menggunakan tipe generik ArrayType , yang berfungsi untuk semua jenis array
  • Untuk menginisialisasi array dengan argumen pertama, kami menggunakan PG_GETARG_ARRAYTYPE_P makro khusus
  • Ada juga tanda centang untuk melihat apakah array tersebut benar-benar satu-dimensi ARR_NDIM
  • Jenis OID untuk int4 (= 23) didefinisikan sebagai INT4OID. Untuk melihat definisi untuk tipe lain, Anda bisa menggunakan SQL,

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


Sekarang kita hanya perlu mengajarkan PostgreSQL untuk menggunakan ini dengan mendeklarasikan fungsi yang mengambil argumen int [],

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

Dan cek

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 |

Rekomendasi


Sekarang mari kita rangkum beberapa rekomendasi utama untuk membuat ekstensi.

Bekerja dengan memori


Penulis dan pembuat DBMS PostgreSQL memberi perhatian khusus untuk bekerja dengan memori dan mencegah kebocoran. Ini adalah salah satu aturan dasar untuk mengembangkan DBMS dan ekstensinya. Ini didasarkan pada keputusan mereka sendiri - konteks memori.

Menggunakan palloc () dan pfree ()


Semua bekerja dengan memori di PostgreSQL terdiri dari menggunakan fungsi palloc () dan pfree () yang tidak standar.

Inisialisasi Struktur


Selalu inisialisasi struktur baru yang dinyatakan. Misalnya, panggil memset () setelah palloc ().

File yang terhubung


Setiap ekstensi Anda harus menyertakan setidaknya dua file: postgres.h dan fmgr.h.

tautan yang bermanfaat


PostgreSQL: Antarmuka Pemrograman Server .

PostgreSQL: Fungsi yang Ditentukan Pengguna di C

Jaringan Ekstensi PostgreSQL

All Articles