PostgreSQL: تطوير الامتدادات (الوظائف) بلغة C.

كتب هذا المقال قبل عامين ، ولم يعرف أين يمكن وضعه ، ثم نسي.

يمكن تقليل معنى استخدام لغة C عند تطوير امتدادات PostgreSQL مقارنة باللغات المفسرة (النصية) إلى نقطتين: الأداء والوظيفة. ولكن ببساطة ، يعمل الرمز المكتوب في C بشكل أسرع ، على سبيل المثال ، إذا تم استدعاء الوظيفة مليون مرة في طلب كل سجل. وبشكل أكثر تحديدا، وبعض الميزات كيو لا يمكن أن يتم على الإطلاق إلا في C، على سبيل المثال، في لغات أخرى، وأنواع (خاصة إذا كنت إرجاع قيمة من وظيفة) ANYELEMENT ، ANYARRAY وأهمية خاصة غير معتمدة VARIADIC .

دالة C بسيطة


على سبيل المثال ، نكتب دالة تأخذ حجتين وتضيفهما. هذا المثال موصوف في وثائق PostgreSQL ، لكننا سنحسنه قليلاً ونجمعه. ثم قم بالتحميل إلى PostgreSQL واكتب مكالمة من وظيفة 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);
}

يمكن استخدام ملف add_func.c كقالب لتطوير وظائف أكثر تعقيدًا. أيضا ، راجع التعليمات البرمجية

  • # تتضمن "postgresql.h": سيتعين عليك دائمًا تضمين ملف الرأس هذا ، فهو يحتوي على أنواع ووظائف أساسية متنوعة.
  • # تتضمن "fmgr.h": يحتوي ملف الرأس على وحدات ماكرو PG_ * مختلفة.
  • PG_MODULE_MAGIC: ماكرو يحدد أننا نقوم بتطوير وحدة نمطية لـ PostgreSQL أعلاه الإصدار 8.2.
  • PG_FUNC_INFO_V1: ماكرو يحدد اصطلاحات استدعاء دالة ضمن PostgreSQL. إذا أعلنت عن وظيفة بدونها ، فسيكون هناك اتفاق على استدعاء الإصدار 0 ، وإلا فإن الإصدار 1.
  • Datum: . , PostgreSQL. - VARIANT Microsoft. «» - . , .
  • add_ab(PG_FUNCTION_ARGS): . - . , .
  • int32 arg_a = PG_GETARG_INT32(0): ( ).
  • PG_RETURN_INT32(arg_a + arg_b): .

الآن نحن بحاجة إلى تجميع وتجميع هذا بشكل صحيح. سيكون الناتج مكتبة مشتركة قابلة للتحميل ديناميكيًا (* .so). لهذا ، سيكون أكثر ملاءمة للقيام بذلك من خلال Makefile . تصف الوثائق المفاتيح والمسارات التي يجب تسجيلها ، ولكننا سنجمعها باستخدام PGXS . هذه بيئة لمطوري الإضافات ، مما يعني أنه يجب تثبيت جميع الحزم -dev و -devel الضرورية لنظام PostgreSQL على نظامك .

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

نقوم بإنشاء دالة SQL


الآن سنكتب دالة SQL التي سيتم استدعاؤها من مكتبة الإضافات التي تم إنشاؤها مسبقًا.
CREATE FUNCTION add(int, int)
  RETURNS int
AS '/usr/lib/postgresql/9.4/lib/add_func', 'add_ab'
LANGUAGE C STRICT;

هذا كل شئ! الآن يمكننا استخدام هذه الوظيفة

SELECT add(1, 2);

أتمتة التثبيت


الآن نقوم بأتمتة التثبيت قليلاً. سيكون هذا مفيدًا جدًا عندما لا تعرف مسبقًا أي إصدار من PostgreSQL يتم استخدامه ، وبأي طريقة يتم تثبيته. للقيام بذلك ، قم بإنشاء الملف التالي ،

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

وإضافة خط إلى ملف Makefile ،

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

تحسن قليلا


سنقوم ببعض التغييرات على مثالنا وإجراء بعض التحسينات.

فحص الحجج


هل تذكر في تعريف SQL للدالة add (int، int) التي استخدمناها في الكلمة الأساسية STRICT ؟ هذا يعني أنه إذا كانت إحدى الوسيطات على الأقل NULL ، فلن تعمل الوظيفة ، وستعود ببساطة NULL . هذا مشابه لسلوك عبارات SQL ، على سبيل المثال ، إذا كان هناك وسيطة NULL واحدة على الأقل في عامل التشغيل + ، فستكون النتيجة NULL . نضيف فحص وسيطة إلى دالتنا ، على غرار مجموع دالة تجميع SQL () ، والذي يتجاهل قيم NULL ويستمر في العمل. لهذا علينا القيام به



  • تأكد من أن الوظيفة تعمل حتى إذا كانت إحدى الوسيطات فارغة
  • إذا كانت كلتا الوسيطتين فارغتين ، فعليك إرجاع 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();
}

والآن تحقق من ذلك ،

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

وإليك كيفية تحقيق ذلك باستخدام أدوات PostgreSQL القياسية ،
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

أي عدد من الحجج في دالة


كما لاحظت بالفعل ، استخدمنا وحدات الماكرو للحصول على قيمة الوسيطات. لذلك ، يمكننا تمرير أي عدد من الحجج ، ثم قراءة قيمها في حلقة ،

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

قد يكون لديك سؤال على الفور ، ولكن يمكنك استخدام مصفوفة لتمرير قيمة الوسيطات. وفي الواقع ، هذا هو ما يجب القيام به ، ولكن للأسف بسبب وجود مدير الذاكرة الخاص بها في PostgreSQL ، هذه ليست مهمة تافهة. لكن حاول حلها. يوجد مثال في وثائق PostgreSQL حيث يتم تمرير مصفوفة الأحرف النصية [] ، ولكن هذا ليس بالضبط ما نحتاج إليه. دعونا نحاول التكيف مع أغراضنا ،

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

كما هو الحال دائمًا ، لنستعرض الرمز ،

  • لا يوجد نوع خاص للصفائف الصحيحة ، لذلك نستخدم النوع العام ArrayType ، والذي يعمل مع أي نوع من المصفوفات
  • لتهيئة الصفيف بالوسيطة الأولى ، استخدمنا الماكرو الخاص PG_GETARG_ARRAYTYPE_P
  • هناك أيضًا فحص لمعرفة ما إذا كان الصفيف ARR_NDIM بالفعل أحادي البعد
  • يتم تعريف نوع OID لـ int4 (= 23) على أنه INT4OID. لعرض التعريفات للأنواع الأخرى ، يمكنك استخدام SQL ،

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


علينا الآن فقط أن نعلم PostgreSQL لاستخدام هذا عن طريق الإعلان عن وظيفة تأخذ وسيطة int [] ،

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

وتحقق

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 |

التوصيات


الآن دعونا نلخص بعض التوصيات الرئيسية لإنشاء الإضافات.

العمل مع الذاكرة


يولي مؤلفو ومنشئو نظام PostgreSQL DBMS اهتمامًا خاصًا للعمل مع الذاكرة ومنع التسرب. هذه إحدى القواعد الأساسية لتطوير نظام DBMS وملحقاته. ويستند هذا إلى قرارهم الخاص - سياق الذاكرة.

باستخدام palloc () و pfree ()


كل العمل مع الذاكرة في PostgreSQL يتكون من استخدام وظائف غير قياسية palloc () و pfree ().

تهيئة الهياكل


قم دائمًا بتهيئة الهياكل الجديدة المعلنة. على سبيل المثال ، استدعاء memset () بعد palloc ().

الملفات المتصلة


يجب أن يشتمل كل من الامتدادات على ملفين على الأقل: postgres.h و fmgr.h.

روابط مفيدة


PostgreSQL: واجهة برمجة الخادم .

PostgreSQL: الوظائف المحددة من قبل المستخدم في شبكة C

PostgreSQL Extension

All Articles