PostgreSQL: البرمجة من جانب الخادم في اللغة البشرية (PL / Perl ، PL / Python ، PL / v8)

تشتهر Postgres بقابليتها للتوسع ، والتي تنطبق أيضًا على دعم اللغات الإجرائية (PL). لا يمكن لأحد أن يتباهى بلغة بقائمة لغات بهذا الطول ، ومن المحتمل أن تكون هذه القائمة غير محدودة على الإطلاق: من أجل توصيل اللغة بالخادم ، لا يلزم بذل جهد إضافي. يمكنك حتى التوصل إلى لغتك الخاصة وجعلها لغة إجرائية للخادم. لن تتطلب التعديلات في نظام إدارة قواعد البيانات (DBMS) ذلك. مثل الكثير ، تم بناء هذه القابلية للتوسعة في بنية Postgres منذ البداية.

من الممكن وضروري أحيانًا كتابة لغات PL للمهام. والأفضل من ذلك ، إذا كتب شخص ما إطار عمل لكتابة اللغات بحيث لا يمكنك الكتابة بلغة C ، ولكن لاختيار لغة أكثر راحة لمطور اللغة. كما هو الحال مع FDW ، والتي يمكن كتابتها في Python .

تمت كتابة هذه المقالة على أساس عدد من التقارير والفصول الرئيسية حول هذا الموضوع التي قدمها المؤلف في المؤتمرات PgConf.Russia 2019 و PgConf.Russia 2018 و DevConf 2017 .

لا يتعلق الأمر بغرابة ، بل حول أكثر اللغات الإجرائية شيوعًا PL / Perl و PL / Python و PL / V8 (أي JavaScript) ومقارنة إمكاناتها بـ PL / pgSQL.

متى تستحق هذه اللغات استخدامها؟ متى تكون SQL و PL / pgSQL مفقودة؟

  • ثم ، عندما تحتاج إلى العمل مع الهياكل المعقدة ، مع الخوارزميات: اجتياز الأشجار ، على سبيل المثال ، أو عندما يكون تحليل HTML أو XML مطلوبًا ، خاصة عند استخراجها من الأرشيف ؛
  • عندما تحتاج إلى إنشاء SQL معقدة (تقارير ، ORM) ديناميكيًا. على PL / pgSQL ، ليس الأمر غير مريح فحسب ، بل سيعمل أيضًا بشكل أبطأ في بعض الحالات ؛
  • Perl Python, C/C++, Perl Python . . , Oracle. , Postgres . Perl Python .
  • — . , , untrusted- ( — . ), Perlu Python(3)u, PL/V8. Postgres , , FDW, , . . !
  • وشيء آخر: إذا كنت ستكتب شيئًا في لغة C ، فيمكنك إنشاء نموذج أولي بهذه اللغات أكثر تكيفًا مع التطور السريع.

كيفية تضمين لغة في Postgres


لتطبيق اللغة التي تحتاجها: اكتب في C من وظيفة إلى ثلاث وظائف:

  • HANDLER - معالج المكالمات الذي سينفذ وظيفة في اللغة (هذا جزء مطلوب) ؛
  • INLINE - معالج كتلة مجهول (إذا كنت تريد أن تدعم اللغة الكتل المجهولة) ؛
  • VALIDATOR - وظيفة التحقق من الرمز عند إنشاء وظيفة (إذا كنت تريد أن يتم هذا التحقق).

ويمكن وصف هذا بالتفصيل في وثائق هنا و هنا .

"لغات خارج الصندوق" ولغات أخرى


هناك أربع لغات فقط مدعومة "من خارج الصندوق": PL / pgSQL و PL / Perl و PL / Python و PL / Tcl ، لكن دغدغة تكريمًا للتاريخ: قلة من الناس يستخدمونه الآن ، لن نتحدث عنه بعد الآن.
PL / Perl و PL / Python وبالطبع PL / pgSQL مدعوم من مجتمع Postgres. يقع دعم اللغات الأخرى غير المربوطة على مسؤوليها - الشركات أو المجتمعات أو المطورين المحددين المهتمين بجعل اللغة تعمل داخل DBMS. يروج PL / V8 لـ Google. ولكن من وقت لآخر هناك أسبابشك في مستقبل PL / V8 صافٍ. يدرس جيري سيفرت ، مشرف مشروع PL / V8 الحالي من Google ، دعم JS القائم على خادم postgres استنادًا إلى محرك مختلف (مثل QuickJS) ، حيث يصعب إنشاء PL / V8 ويتطلب 3-5 غيغابايت جميع أنواع الأشياء على لينكس عند البناء ، وهذا يؤدي في كثير من الأحيان إلى مشاكل في أنظمة تشغيل مختلفة. ولكن يتم استخدام PL / V8 على نطاق واسع واختبارها بدقة. من الممكن أن يظهر PL / JS كبديل لمحرك JS آخر ، أو في الوقت الحالي كاسم ، والذي سوف نعتاد عليه خلال الفترة الانتقالية. نادرًا ما يتم استخدام

PL / Java . أنا شخصياً لم يكن لدي حاجة للكتابة في PL / Java لأنه في PL / Perl وفي PL / V8 هناك وظائف كافية لجميع المهام تقريبًا. حتى Python لا تضيف ميزات بشكل خاص. PL / Rمفيد لأولئك الذين يحبون الإحصائيات ويحبون هذه اللغة. لن نتحدث عنه هنا أيضًا.

اللغات الشائعة ليست شائعة بالضرورة مع كتابة التخزين: هناك PL / PHP ، ولكن الآن غير مدعوم عمليًا من قبل أي شخص - هناك عدد قليل ممن يرغبون في كتابة إجراءات الخادم عليه. بالنسبة للغة PL / Ruby ، ​​فإن الصورة هي نفسها إلى حد ما ، على الرغم من أن اللغة تبدو أكثر حداثة.

يتم تطوير لغة إجرائية قائمة على Go ، انظر PL / Go ، وكذلك ، على ما يبدو ، PL / Lua . سيكون من الضروري دراستها. بالنسبة لعشاق الصدفة ، هناك حتى PL / Sh ، من الصعب حتى تخيل ما قد يكون من أجله.

هناك لغة إجرائية واحدة على الأقل خاصة بالمجال (DSL) متخصصة على نطاق ضيق لمهمتها - PL / Proxy، التي كانت شائعة جدًا في تحميل الخادم الوكيل وموازنته.

في هذه المقالة ، سنتناول اللغات الرئيسية الأكثر استخدامًا. هذا بالطبع هو PL / PgSQL و PL / Perl و PL / Python و PL / V8 ، وسنسميهم PL / * أدناه .

اللغات "خارج الصندوق" يتم تثبيتها فعليًا تقريبًا خارج الصندوق - عادةً ما يكون التثبيت غير مؤلم. ولكن لتثبيت PL / V8 ، إذا لم تجد حزمة مع الإصدار الضروري في مستودع نظام التشغيل الخاص بك ، فهذا يكاد يكون إنجازًا رائعًا ، لأنه سيتعين عليك في الواقع بناء V8 بالكامل ، أو بعبارة أخرى Chromium. في الوقت نفسه ، سيتم تنزيل البنية التحتية للتطوير بالكامل من google.com جنبًا إلى جنب مع V8 نفسه - يعتمد على بضع غيغابايت من حركة المرور. بالنسبة لـ Postgres 11 تحت Ubuntu ، لم تظهر حزمة PL / V8 حتى الآن ، فقط V8 لـ PG 10 متاح في المستودع حتى الآن. إذا أردت ، قم بتجميعها يدويًا. من المهم أيضًا أن تكون النسخة التي ستجدها في المستودع قديمة جدًا على الأرجح. في وقت نشر المقال ، الإصدار الأخير هو 2.3.14.

بعد تثبيت اللغة نفسها ، يجب عليك أيضًا "إنشاء" اللغة - تسجيلها في دليل النظام. يجب أن يتم ذلك من قبل الفريق.

CREATE EXTENSION plperl;

(بدلاً من plperl ، يمكنك استبدال اسم لغة أخرى ، وهناك فروق دقيقة معينة ، انظر أدناه).
ننظر إلى ما حدث:

test_langs=# \x
test_langs=# \dL+
List of languages
-[ RECORD 1 ]-----+---------------------------------
Name              | plperl
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plperl_call_handler()
Validator         | plperl_validator(oid)
Inline handler    | plperl_inline_handler(internal)
Access privileges |
Description       | PL/Perl procedural language
-[ RECORD 2 ]-----+---------------------------------
Name              | plpgsql
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plpgsql_call_handler()
Validator         | plpgsql_validator(oid)
Inline handler    | plpgsql_inline_handler(internal)
Access privileges |
Description       | PL/pgSQL procedural language
[ RECORD 3 ]-----+---------------------------------
Name              | plv8
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plv8_call_handler()
Validator         | plv8_call_validator(oid)
Inline handler    | plv8_inline_handler(internal)
Access privileges |
Description       |

لا يحتاج PL / pgSQL إلى الإنشاء بشكل خاص ؛ فهو موجود دائمًا في قاعدة البيانات.

انتباه! لا يجب الخلط بين PL / pgSQL و SQL. هذه لغة مختلفة. ومع ذلك ، يمكن لـ Postgres أيضًا كتابة الوظائف في SQL العادي.

المعايير


في عالم DBMS ، غالبًا ما يتحدثون عن الامتثال لمعايير SQL. تحتوي اللغات الإجرائية أيضًا على معايير ، على الرغم من عدم التحدث عنها كثيرًا. يتوافق معيار SQL / PSM إلى حد كبير مع اللغة الإجرائية DB2. تنفيذه بعيد عن PL / pgSQL ، على الرغم من أنهما قريبان من الناحية النظرية.

SQL / JRT هو المعيار لإجراءات Java ، و PL / Java يطابقها جيدًا.

اللغات الموثوقة وغير الموثوق بها


اللغات الإجرائية Postgres موثوقة (موثوق بها) وغير موثوق بها (غير موثوق بها).
في اللغات الموثوقة ، لا توجد إمكانية للعمل المباشر مع I / O ، بما في ذلك الشبكة ، وفي الواقع مع موارد النظام. لذلك ، يمكن إنشاء مثل هذه الوظائف من قبل أي مستخدم لقاعدة البيانات ، يفسد شيئًا ولن يكون قادرًا على تعلم الكثير. لا يمكن إنشاء الوظائف في اللغات غير الموثوق بها إلا من قبل المشرف.

إذا كان مترجم اللغة يدعم مثل هذه القيود ، فيمكن استخدامه لإنشاء كل من اللغات الموثوقة وغير الموثوق بها. حتى مع بيرل ، لذلك هناك لغات مختلفة plperlو plperlu. حرف شفي النهاية يعطي الطابع الغير موثوق به للغة. Python موجود فقط في إصدار غير موثوق به. PL / v8 - على العكس من ذلك ، فقط موثوق به. نتيجة لذلك ، لا يستطيع PL / v8 تحميل أي وحدات أو مكتبات من القرص ، فقط من قاعدة البيانات.

يمكن للدالة في اللغة غير الموثوق بها أن تفعل أي شيء: إرسال بريد إلكتروني ، ping موقع ، تسجيل الدخول إلى قاعدة بيانات أجنبية ، وتنفيذ طلب HTTP. تقتصر اللغات الموثوقة على معالجة البيانات من قاعدة البيانات.

بواسطة يوثق ما يلي: plpgsql, plperl, plv8, pljava.

بواسطة غير موثوق بها ما يلي: plperlu, pljavau, plpython2u, plpython3u.

يرجى ملاحظة ما يلي: لا يوجد PL / Python مثل TRUSTED (حيث لا يمكنك تعيين قيود على الوصول إلى الموارد هناك) ، و PLpgSQL و PL / V8 هي العكس: فهي غير موثوق بها.

لكن بيرل وجافا متاحان في كلا الإصدارين.

PL / pgSQL مقابل PL / *


يعمل رمز PL / pgSQL في الأصل مع جميع أنواع البيانات الموجودة في Postgres. لا تحتوي اللغات الأخرى على العديد من أنواع Postgres ، ويهتم مترجم اللغة بتحويل البيانات إلى تمثيل داخلي للغة ، واستبدال الأنواع الغامضة بالنص. ومع ذلك ، يمكن مساعدته بمساعدة TRANSFORM ، والتي سأتحدث عنها أقرب إلى نهاية المقالة.

غالبًا ما تكون مكالمات الوظائف في PL / pgSQL أكثر تكلفة. يمكن للوظائف بلغات أخرى الوصول إلى مكتباتها دون النظر إلى كتالوج النظام. لا يمكن أن يعمل PL / pgSQL على هذا النحو. تعمل بعض الاستفسارات في PL / pgSQL لفترة طويلة بسبب حقيقة أن الكثير من الأنواع مدعومة: لإضافة اثنين من الأعداد الصحيحة ، يحتاج المترجم إلى إدراك أنه يتعامل مع الأعداد الصحيحة وليس بعض الأنواع الغريبة الأخرى ، ثم يقرر كيفية طيها ، وبعد ذلك فقط قم بطيها.

بما أن PL / pgSQL موثوق به ، فلا يمكنك العمل مع الشبكة والأقراص منه.

عندما يتعلق الأمر بالعمل مع تراكيب البيانات المتداخلة ، فإن PL / pgSQL لديه فقط أدوات Postgres للعمل مع JSON ، والتي تعد معقدة للغاية وغير منتجة ، في لغات أخرى ، فإن العمل مع الهياكل المتداخلة هو أبسط بكثير وأكثر اقتصادا.

PL / * لديها إدارة ذاكرة خاصة بها ، وتحتاج إلى مراقبة الذاكرة ، أو ربما تقييدها.

يجب عليك مراقبة معالجة الخطأ بعناية ، والتي تختلف أيضًا من شخص لآخر.

ولكن في PL / * يوجد سياق مترجم عالمي ، ويمكن استخدامه ، على سبيل المثال ، لتخزين البيانات مؤقتًا ، بما في ذلك خطط الاستعلام. إذا كانت اللغة غير موثوق بها ، فإن الشبكة ومحرك (محركات) الأقراص متوفرة. تعمل جميع هذه اللغات مع قاعدة البيانات ، كقاعدة عامة ، من خلال SPI ، ولكن المزيد عن ذلك لاحقًا.

دعونا نلقي نظرة فاحصة على ميزات لغات PL / *.

PL / Perl


مترجم Perl هو جزء كبير من التعليمات البرمجية في الذاكرة ، ولكن لحسن الحظ لم يتم إنشاؤه عند فتح الاتصال ، ولكن فقط عند تشغيل أول إجراء / وظيفة مخزنة PL / Perl. عندما تتم تهيئته ، يتم تنفيذ التعليمات البرمجية المحددة في معلمات تكوين Postgres. عادة ، يتم تحميل الوحدات ويتم إجراء عمليات المعالجة المسبقة. إذا أضفت إلى ملف التكوين أثناء تشغيل قاعدة البيانات ، فاجعل Postgres يعيد قراءة التكوين. في هذه المقالة ، تستخدم الأمثلة وحدة نمطية لتصور هياكل البيانات. هناك معلمات لتهيئة منفصلة لكل من TRUSTED و UNTRUSTED Perl وبالطبع معلمة . أولئك الذين يبرمجون في بيرل يعرفون أنه بدونها ليست لغة ، بل سوء فهم واحد.

plperl.on_init= 'use Data::Dumper;'
plperl.on_plperl_init= ' ... '
plperl.on_plperlu_init= ' ... '
plperl.use_strict= on


Data::Dumper

use_strict=onstrict

PL / Python


في ذلك ، يتم إنشاء المترجم بنفس الطريقة التي يتم الوصول إليها في المرة الأولى. وهنا من المهم أن تقرر على الفور الثعبان الذي تريده: الثانية أو الثالثة. كما تعلم ، فإن Python موجود في نسختين شائعتين (Python 2 و Python 3) ، لكن المشكلة هي أن so-shki الخاص بهم لا يجتمعون معًا في عملية واحدة: هناك صراع بالاسم. إذا كنت تعمل مع v2 في جلسة واحدة ثم اتصلت بـ v3 ، فسوف يتعطل Postgres ، وبالنسبة لعملية الخادم (الخلفية) ، فسيكون هذا خطأ فادحًا. للوصول إلى إصدار مختلف ، تحتاج إلى فتح جلسة أخرى.

على عكس Perl ، لا يمكن إخبار الثعبان بما يجب فعله أثناء التهيئة. إزعاج آخر: الخطوط المفردة غير مناسبة للقيام بذلك.

في جميع وظائف Python ، تم تحديد قواميس - ثابتة SDوعالمية GD. يسمح العالميةتبادل البيانات مع جميع الوظائف داخل خلفية واحدة - وهو أمر جذاب وخطير في نفس الوقت. كل وظيفة لها قاموس ثابت.

في PL / Python ، يمكنك إجراء معاملات فرعية ، والتي سنناقشها أدناه.

PL / V8


موثوق به فقط.

بشكل ملائم ، يتم تحويل بيانات JSON تلقائيًا إلى بنية JS. في PL / V8 ، كما هو الحال في PL / Python ، يمكنك إجراء معاملات فرعية. هناك واجهة لاستدعاءات الوظائف المبسطة. هذه هي اللغة الإجرائية الوحيدة التي يمكن فيها تحديد وظائف النافذة . يقترحون أنه يمكن تعريفهم على PL / R ، لكن هذه اللغة خارج نطاق هذه المقالة.

وفقط في PL / V8 هناك مهلة تنفيذ. صحيح ، لا يتم تشغيله افتراضيًا ، وإذا قمت بإنشاء PL / V8 يدويًا ، يجب أن تقول أثناء التجميع أنه قيد التشغيل ، وبعد ذلك يمكنك تعيين مهلات لاستدعاءات الوظائف باستخدام معلمة التكوين.

تبدو التهيئة في PL / V8 مثيرة للاهتمام: نظرًا لأنها موثوقة ، فلا يمكنها قراءة المكتبة من القرص ، ولا يمكنها تحميل أي شيء من أي مكان. يمكنه أن يأخذ كل ما يحتاجه فقط من القاعدة. لذلك ، يتم تعريف وظيفة المُهيئ المخزنة التي يتم استدعاؤها عند بدء تشغيل مترجم اللغة. يتم تحديد اسم الوظيفة في معلمة تكوين خاصة:

plv8.start_proc=my_init # ( PL/V8-)

أثناء التهيئة ، يمكن إنشاء المتغيرات والوظائف العامة عن طريق تعيين قيمها لسمات هذا المتغير. على سبيل المثال ، مثل هذا:

CREATE OR REPLACE FUNCTION my_init()
RETURNS void LANGUAGE plv8 AS $$
     this.get_57 = function() { return 57; }; //   
     this.pi_square = 9.8696044;  //   
$$;
SET plv8.start_proc = 'my_init';
DO LANGUAGE plv8 $$
     plv8.elog(NOTICE, pi_square, get_57() );
$$;

مقارنة PL / Perl مقابل PL / Python مقابل PL / V8 في الممارسة


مرحبا بالعالم!


لنقم بتمرين بسيط مع إخراج هذه العبارة بجميع اللغات الثلاث ، أولاً في PL / Perl . ودعه يفعل شيئًا آخر مفيدًا ، على سبيل المثال ، يخبر روايته:

DO $$
     elog(NOTICE,"Hello World! $]");
$$ LANGUAGE plperl;

NOTICE:  Hello World!
DO

يمكنك أيضا استخدام وظائف warnو Perl المعتادة die.

الآن في PL / Python . بتعبير أدق على PL / Python3u (غير موثوق) - من أجل التحديد.

DO $$
     import sys
     plpy.notice('Hello World! ' , hint=" ", detail=sys.version_info)
$$ LANGUAGE plpython3u;


NOTICE:  Hello World! 
DETAIL:  sys.version_info(major=3, minor=6, micro=9, releaselevel='final', serial=0)
HINT:   
DO

يمكن استخدامها throw 'Errmsg'. هناك العديد من الأشياء التي يمكنك استخراجها من رسائل Postgres: فهي تحتوي على تلميح وتفاصيل ورقم السطر والعديد من المعلمات الأخرى. في PL / Python ، يمكن تمريرها ، ولكن ليس في اللغات الأخرى قيد النظر: لا يمكن لعابتها إلا بسطر نص عادي.

في PL / Python ، لكل مستوى تسجيل postgres وظيفته الخاصة: إشعار ، تحذير ، تصحيح ، تسجيل ، معلومات ، قاتلة. إذا كان خطأ ، فقد انخفضت المعاملة ، إذا سقطت فاتال ، فإن الخلفية بالكامل قد انخفضت. لحسن الحظ ، لم تصل المسألة إلى الذعر. يمكنك أن تقرأ هنا .

PL / V8

في هذه اللغة ، يشبه Hello world للغاية اللؤلؤة. يمكنك إنهاء exceptionاستخدام throw، وهذا سيكون أيضا معالجة الأخطاء، على الرغم من أن الأدوات ليست على النحو المتقدم كما في بايثون. إذا كنت تكتبplv8.elog(ERROR)، سيكون التأثير ، بالمناسبة ، هو نفسه.

DO $$
     plv8.elog(NOTICE, 'Hello World!', plv8.version);
$$ LANGUAGE plv8;

NOTICE:  Hello World! 2.3.14
DO

اعمل مع القاعدة


الآن دعنا نرى كيفية العمل مع قاعدة بيانات من الإجراءات المخزنة. Postgres لديه SPI (واجهة برمجة الخادم). هذه مجموعة من وظائف C متاحة لجميع مؤلفي الامتدادات. توفر جميع لغات PL تقريبًا برامج تغليف لـ SPI ، ولكن كل لغة تقوم بذلك بشكل مختلف قليلاً.

دالة مكتوبة بلغة C ولكن من غير المحتمل أن تعطي SPI مكسبًا كبيرًا مقارنة بـ PL / PgSQL ولغات إجرائية أخرى. ولكن وظيفة C التي تتجاوز SPI وتعمل مع البيانات بدون وسطاء (على سبيل المثال table_beginscan/heap_getnext) ستعمل بترتيب من حيث الحجم بشكل أسرع.

يستخدم PL / Java أيضًا SPI. لكن العمل مع قاعدة البيانات لا يزال يحدث بأسلوب JDBC ومعيار JDBC. بالنسبة لمنشئ الشفرة في PL / Java ، يحدث كل شيء كما لو كنت تعمل من تطبيق عميل ، لكن JNI (واجهة جافا الأصلية) تترجم المكالمات إلى قاعدة البيانات إلى نفس وظائف SPI. إنه ملائم ، ولا توجد عقبات أساسية لترجمة هذا المبدأ إلى PL / Perl و PL / Python ، ولكن لسبب ما لم يتم ذلك ، وحتى الآن لم يتم رؤيته في الخطط.

بالطبع ، إذا كنت ترغب ، يمكنك الذهاب إلى قواعد أجنبية بالطريقة المعتادة - من خلال DBI أو Psycopg . من الممكن لقاعدة البيانات المحلية ، ولكن لماذا.

إذا لم تدخل في الموضوع الشامل "العملية في الأساس مقابل العملية على العميل" ، وانتقل فورًا من أقصى معالجة أقرب إلى البيانات (على الأقل حتى لا تدفع عينات عملاقة عبر الشبكة) ، فإن الحل لاستخدام الوظائف المخزنة على الخادم يبدو بطبيعة الحال.

الأداء : ضع في اعتبارك أن SPI لديها بعض النفقات العامة ، وقد تكون استعلامات SQL في الوظائف أبطأ من دون الوظائف. تضمنت postgres 13 تصحيحًا من قبل Konstantin Knizhnik ، مما يقلل من هذه التكاليف. ولكن ، بالطبع ، لا تتطلب معالجة نتائج الاستعلام في وظيفة مخزنة نقل النتيجة إلى العميل ، وبالتالي يمكن أن تكون مفيدة من حيث الأداء.

سلامة: مجموعة من الوظائف المصححة والمختبرة تعزل بنية قاعدة البيانات عن المستخدم ، وتحمي من حقن SQL وغيرها من الأذى. خلاف ذلك ، سيبقى صداعا لكل مطور التطبيق. إعادة استخدام

الكود : إذا كان عدد كبير من التطبيقات المعقدة يعمل مع قاعدة البيانات ، فمن الملائم تخزين الوظائف المفيدة على الخادم ، بدلاً من كتابتها مرة أخرى في كل تطبيق.

كيف وبأي شكل نحصل على البيانات من قاعدة البيانات


في Perl ، كل شيء بسيط وواضح. تقوم المكالمة spi_exec_queryبإرجاع عدد الصفوف التي تمت معالجتها والحالة ومجموعة الصفوف المحددة بواسطة استعلام SQL:

DO $$ 
     warn Data::Dumper::Dumper(
          spi_exec_query('SELECT 57 AS x')
     )
$$ LANGUAGE plperl;

WARNING:  $VAR1 = {
          'rows' => [
                    {
                      'x' => '57'
                    }
                  ],
          'processed' => 1,
          'status' => 'SPI_OK_SELECT'
        };

في Python ، يبدو الاستعلام والنتيجة شيئًا مثل هذا ، ولكن هنا لا تُرجع الدالة بنية بيانات ، ولكن كائنًا خاصًا يمكنك العمل معه بشكل مختلف. عادة ما يتظاهر بأنه صفيف ، وبالتالي ، يمكنك استخراج السلاسل منه.

DO $$ 
     plpy.notice(
          plpy.execute('SELECT 57 AS x')
     )
$$ LANGUAGE plpython3u;

NOTICE:  <PLyResult status=5 nrows=1 rows=[{'x': 57}]>
DO

والآن نأخذ السطر الأول ، نخرج من هناك X ونحصل على القيمة - الرقم.

DO $$ 
     plpy.notice(
          plpy.execute('SELECT 57 AS x')[0]['x']
      )
$$ LANGUAGE plpython3u;

NOTICE:  57
DO

في PL / V8 :

DO $$ 
     plv8.elog(NOTICE, JSON.stringify(
          plv8.execute('SELECT 57 as x'))
     );
$$ LANGUAGE plv8;

NOTICE:  [{"x":57}]
DO

من أجل رؤية الهيكل ، استخدمنا وظيفة المكتبة JSON.stringify ، والتي لا تحتاج إلى تحميلها على وجه التحديد ، فهي بالفعل جاهزة للاستخدام كجزء من PL / v8 بشكل افتراضي.

التدريع


لتجنب عمليات إدخال SQL الضارة ، يجب تجنب بعض الأحرف في الاستعلامات. للقيام بذلك ، أولاً ، هناك وظائف SPI والوظائف المقابلة (المكتوبة في C) باللغات التي تعمل مثل أغلفة SPI. على سبيل المثال ، في PL / Perl:

quote_literal- يأخذ الفواصل العليا والزوجية و \. مصممة لفرز البيانات النصية.
quote_nullable- نفس ، ولكن تم undefتحويله إلى NULL.
quote_ident- اقتباس اسم الجدول أو الحقل ، إذا لزم الأمر. مفيد في الحالة عندما تقوم بإنشاء استعلام SQL واستبدال أسماء كائنات قاعدة البيانات فيه.

PL / Perl

DO $$
     warn "macy's";
     warn quote_literal("macy's");
$$ LANGUAGE plperl;

WARNING:  macy's at line 2.
WARNING:  'macy''s' at line 3.
DO

ضع في اعتبارك: يجب عدم تجاوز اسم الجدول مثل سطر نص. هذا هو السبب في وجود وظيفة quote_ident.

ولكن في PL / Perl ، هناك وظائف أخرى لحماية البيانات من أنواع ما بعد gres الفردية: يجب أن تقبل الوظيفة أي نوع وتحويل الأحرف المشكوك فيها إلى شيء آمن بشكل واضح. يعمل مع عدد كبير من الأنواع ، ولكن ، مع ذلك ، ليس مع الجميع. على سبيل المثال ، لن تفهم أنواع النطاق وتدركها ببساطة كسلاسل نصية.

encode_bytea
decode_bytea
encode_array_literal
encode_typed_literal
encode_array_constructor


quote_typed_literal

DO $$
     warn encode_typed_literal(
          ["", " "], "text[]"
     );
$$ LANGUAGE plperl;

WARNING:  {," "} at line 2.
DO

هناك ثلاث وظائف متشابهة في PL / Python ، وهي تعمل بنفس الطريقة تقريبًا:

plpy.quote_literal
plpy.quote_nullable
plpy.quote_ident


DO $$ plpy.notice(
     plpy.quote_literal("Macy's"));
$$ LANGUAGE plpython3u;
NOTICE:  'Macy''s'
DO

هل الوظائف في PL / V8 هي نفسها ؟

بالطبع بكل تأكيد! كل شيء هو نفسه بالنسبة إلى الميزات النحوية.

plv8.quote_literal
plv8.quote_nullable
plv8.quote_ident


DO $$
    plv8.elog(NOTICE, plv8.quote_nullable("Macy's"));
$$ LANGUAGE plv8;

NOTICE:  'Macy''s'

أداء


أي لغة هي الأسرع؟ عادة ما يجيبون: C. لكن الإجابة الصحيحة هي C أو SQL. لماذا SQL؟ والحقيقة هي أن الوظيفة في هذه اللغة لا تتم دائمًا بشكل صريح. يمكن أن تصبح مضمنة في الطلب (سيقوم المجدول بتضمين الوظيفة في نص الطلب الرئيسي) ، ويحسن بشكل جيد مع الطلب ، وستكون النتيجة أسرع. ولكن ما هي الشروط التي يمكن تضمينها في الطلب؟ هناك بعض الشروط البسيطة التي يمكنك أن تقرأ عنها ، على سبيل المثال ، هنا . على سبيل المثال ، لا يجب تنفيذ وظيفة بحقوق المالك (لتكون محددًا للأمان). معظم الوظائف البسيطة تناسب هذه الظروف.

في هذه المقالة سنقيس "على الركبة" ، ليس بجدية. نحن بحاجة إلى مقارنة تقريبية. قم أولاً بتشغيل التوقيت:

\timing

دعنا نجرب SQL (أوقات تنفيذ الأوامر أدناه هي قيم المتوسط ​​التقريبي التي تلقاها المؤلف على جهاز كمبيوتر غير محمّل يبلغ من العمر ست سنوات. ويمكن مقارنتها مع بعضها البعض ، لكنها لا تدعي أنها علمية):

SELECT count(*) FROM pg_class;
0.5 ms

يعمل بسرعة كبيرة. في لغات أخرى ، يضيع الوقت وظائف الاتصال من اللغة. بالطبع ، في المرة الأولى التي يعمل فيها الطلب بشكل أبطأ بسبب تهيئة المترجم. ثم تستقر.

دعنا نجرب PL / pgSQL :

DO $$
     DECLARE a int;
     BEGIN
          SELECT count(*) INTO a FROM pg_class;
     END;
$$ LANGUAGE plpgsql;
0.7 ms

PL / Perl :

DO $$
     my $x = spi_exec_query('SELECT count(*) FROM pg_class');
$$ LANGUAGE plperl;
0.7 ms

PL / Python:

DO $$
     x = plpy.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plpythonu;
0.8 ms

كان Python 2. الآن Python 3 (تذكر: Python2 و Python3 لا يعيشون بسلام في نفس الجلسة ، يمكن تضارب الأسماء):

DO $$
     x = plpy.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plpython3u;
0.9ms

وأخيرًا ، PL / V8 :

DO $$
     var x = plv8.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plv8 ;
0.9 ms

لكنها بطريقة ما سريعة للغاية. دعونا نحاول تنفيذ الاستعلام 1000 مرة أو مليون مرة ، وفجأة سيكون الفرق أكثر وضوحًا:

PL / pgSQL :

DO $$
     DECLARE a int; i int;
     BEGIN FOR i IN 0..999999 LOOP
          SELECT count(*) INTO a FROM pg_class;
    END LOOP;
END;
$$ LANGUAGE plpgsql;
53s

PL / Perl :

DO $$
     for (0..999999) {
          spi_exec_query('SELECT count(*) FROM pg_class');
     }
$$ LANGUAGE plperl;
102s

PL / Python 3 :

DO $$
     for i in range (0,1000000) :
          plpy.execute('SELECT count(*) FROM pg_class')
$$ LANGUAGE plpython3u;
98s

PL / V8 :

DO $$
     for(var i=0;i<1000;i++)
          plv8.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plv8;
100ms

لاحظ أنه باستخدام PL / V8 ، تم إجراء التجربة باستخدام ألف وليس مليون تكرار. مع موارد معتدلة ، سيأكل PL / V8 في دورة من مليون عملية كل الذاكرة ويعلق السيارة بالكامل. بالفعل في آلاف التكرارات ، تحدد عملية postgres ذاكرة 3.5 جيجا بايت وتكتب 100٪ على القرص. في الواقع ، تطلق postgres بيئة V8 ، وبالطبع تأكل الذاكرة. بعد تنفيذ الطلب ، لن يعيد هذا الوحش التوربينى الذاكرة. لتحرير الذاكرة ، عليك إنهاء الجلسة.

نرى أن PL / pgSQL أسرع مرتين من PL / Perl و PL / Python. لا يزال PL / V8 وراءهم قليلاً ، ولكن في نهاية المقال تم إعادة تأهيله جزئيًا.

بشكل عام ، تُظهر Perl مع Python في هذه التجارب نفس النتائج تقريبًا. اعتاد بيرل أن يكون أقل قليلاً من Python ؛ في الإصدارات الحديثة ، يكون أسرع قليلاً. الثعبان الثالث أبطأ قليلاً من الثانية. الفارق كله في حدود 15٪.

الأداء مع الاستعداد


الناس الذين يعرفون سيفهمون: هناك خطأ ما. يمكن PL / pgSQL تخزين خطط الاستعلام مؤقتًا تلقائيًا ، وفي PL / * ، في كل مرة تتم جدولة الاستعلام مرة أخرى. بطريقة جيدة ، تحتاج إلى إعداد الطلبات ، وبناء خطة طلب ، ومن ثم وفقًا لهذه الخطة ، يجب تنفيذها عدة مرات حسب الحاجة. في PL / * ، يمكنك العمل بشكل صريح مع خطط الاستعلام ، والتي سنحاول البدء بها مع PL / Perl :

DO $$
     my $h = spi_prepare('SELECT count(*) FROM pg_class');
     for (0..999999) {
          spi_exec_prepared($h);
     }
     spi_freeplan($h);
$$ LANGUAGE plperl;
60s

PL / Python 3 :

DO $$
     h = plpy.prepare('SELECT count(*) FROM pg_class')
     for i in range (0,1000000): plpy.execute(h)
$$ LANGUAGE plpython3u;
62s

PL / V8 :

DO $$
     var h=plv8.prepare('SELECT count(*) FROM pg_class');
     for(var i=0;i<1000;i++) h.execute();
$$ LANGUAGE plv8;
53ms

مع prepareلغتينا ، كنا على وشك اللحاق بـ PL / pgSQL ، بينما أرادت الثالثة أيضًا ، ولكن لم تصل إلى خط النهاية بسبب الطلب المتزايد على الذاكرة.

ولكن إذا كنت لا تأخذ في الاعتبار الذاكرة ، فمن الواضح أن جميع اللغات تتقارب بشكل مباشر - وليس عن طريق الصدفة. اختناقها شائع الآن - العمل مع قاعدة البيانات من خلال SPI.

أداء الحوسبة


نرى أن أداء اللغة قد استقر في العمل مع قاعدة البيانات. لمقارنة اللغات مع بعضها البعض ، دعنا نحاول حساب شيء ما دون اللجوء إلى قاعدة البيانات ، على سبيل المثال ، مجموع المربعات.

PL / pgSQL :

DO $$
     DECLARE i bigint; a bigint;
     BEGIN a=0;
     FOR i IN 0..1000000 LOOP
          a=a+i*i::bigint;
     END LOOP;
END;
$$ LANGUAGE plpgsql;
280ms

PL / Perl :

DO $$
     my $a=0;
     for my $i (0..1000000) { $a+=$i*$i; };
     warn $a;
$$ LANGUAGE plperl;
63ms

PL / Python 3 :

DO $$
a=0
for i in range(1,1000001): a=a+i*i
$$ LANGUAGE plpython3u;
73ms

PL / V8 :

DO $$
     var a=0;
     for(var i=0;i<=1000000;i++) a+=i*i;
     plv8.elog(NOTICE, a);
$$ language plv8;
7.5ms

نرى أن PL / Perl و PL / Python تجاوزوا وتجاوزوا PL / pgSQL ، فهم أسرع 4 مرات. والثمانية تمزق الجميع! ولكن هل حقا من أجل لا شيء؟ أم نحصل عليه للرأس؟ نعم نحن سوف.

الرقم في JavaScript عائم ، والنتيجة سريعة ، ولكنها ليست دقيقة: 333333833333127550 بدلاً من 333333833333500000.

إليك الصيغة التي يتم من خلالها حساب النتيجة الدقيقة:

∑ = n*(n+1)*(2n+1)/6

كتمرين ، يمكنك إثبات ذلك باستخدام الحث الرياضي.

بترتيب الضحك

DO LANGUAGE plv8 $$
plv8.elog(NOTICE, parseInt(33333383333312755033)) $$;

NOTICE:
33333383333312754000

في جافاسكريبت ، parseIntما زالت تطفو ، وليس Int.

ومع ذلك ، ظهر BigInt في V8 في عام 2018 ، ويمكن الآن حسابه بالتأكيد ، ولكن على حساب السرعة ، لأنه ليس عددًا صحيحًا 64 بت ، ولكنه عدد صحيح لعمق البت التعسفي. ومع ذلك ، في PL / V8 لم يحدث هذا الابتكار حتى الآن. في اللغات الإجرائية الأخرى ، numericيتم دعم أرقام البت التعسفية (نظائر SQL ) من خلال مكتبات خاصة.

في Perl ، هناك وحدة Math :: BigFloat للحساب بدقة تعسفية ، وفي Python ، حزمة Bigfloat عبارة عن غلاف Cython حول مكتبة GNU MPFR .

وظائف الأداء للفرز


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

1
2
3
4-5
6
6A
6
11
12

أولئك. إنها في الواقع سلسلة ، لكنها تبدأ برقم ، وتحتاج إلى الفرز حسب هذه الأرقام. لذلك ، من أجل الفرز بشكل صحيح كسلاسل ، نكمل الجزء الرقمي بالأصفار على اليسار للحصول على:

0000000001
0000000002
0000000003
0000000004-5
0000000006
0000000006A
0000000006
0000000011
0000000012

نعم ، أعلم أن هذا ليس الحل الوحيد للمشكلة (ولا حتى صحيح تمامًا). ولكن على سبيل المثال ، ستفعل.

لطلب نوع ، SELECT ... ORDER BY nsort(n)نكتب دالات في PL / Perl و SQL و PL / Python و PL / V8 التي تحول أرقام دفتر اليومية إلى هذا النموذج:

CREATE OR REPLACE FUNCTION nsort(text) RETURNS text 
   LANGUAGE PLPERL IMMUTABLE AS $$
    my $x = shift;
    return ($x =~ /^\s*(\d+)(.*)$/)
        ? sprintf("%010d", $1).$2
        : $x;
$$;

CREATE OR REPLACE FUNCTION _nsort(x text) RETURNS text
     LANGUAGE SQL  IMMUTABLE  AS $$
 WITH y AS (
    SELECT regexp_match(x,'^\s*(\d*)(.*)$') as z
 )
 SELECT CASE WHEN z[1] = '' THEN x ELSE lpad(z[1],10,'0') || z[2] END FROM y;
$$;

CREATE OR REPLACE FUNCTION py_nsort(x text) RETURNS text 
   LANGUAGE plpython2u IMMUTABLE AS $$
import re
r = re.match('^\s*(\d+)(.*)$', x)
return x if r == None else ('%010d' % int(r.group(1))) + r.group(2)
$$;

CREATE OR REPLACE FUNCTION js_nsort(x text) RETURNS text 
   LANGUAGE plv8 IMMUTABLE AS $$
var m = x.match(/^\s*(\d+)(.*)$/);
if(m) { return m[1].padStart(10-m[1].length,'0') + m[2]; }
else { return x; } 
$$;

في مكتبتي التي تحتوي على 15.5 ألف مقال صحفي ، يستغرق الاستعلام باستخدام دالة في PL / Perl حوالي 64 مللي ثانية مقابل 120 مللي ثانية في PL / Python و 200 مللي ثانية في PL / PgSQL. ولكن الأسرع - PL / v8: 54ms.

ملاحظة: عند تجربة الفرز ، قم بتوفير المقدار اللازم من الذاكرة العاملة حتى يتم الفرز في الذاكرة (سيظهر بعد ذلك شرح Sort Method: quicksort). يتم تعيين مقدار الذاكرة بواسطة المعلمة work_mem:

set work_mem = '20MB';

ذاكرة


لا يحب بيرل الهياكل المخروطية ، ولا يعرف كيف ينظفها. إذا كان aلديك مؤشر b، bومؤشر إلى a، فلن تتم إعادة تعيين العداد المرجعي ولن يتم تحرير الذاكرة.

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

ولكن هناك أيضًا ميزات إدارة الذاكرة المرتبطة مباشرةً بـ Postgres. هناك هياكل تخصصها SPI ، ولا يدرك بيرل دائمًا أنه يجب تحريرها.

PL / Perl

ليست هذه هي الطريقة:

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     return spi_exec_query(
           'SELECT count(*) FROM pg_class'
     )->{rows}->[0]->{count};
$$;

وهكذا تكون:

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     my $h = spi_prepare(
          'SELECT count(*) FROM pg_class'
     );
     return spi_exec_prepared($h)->{rows}->[0]->{count};
$$;

بعد الإعدام ، $hسيظل المعالج على قيد الحياة ، على الرغم من أنه لا توجد صلة حية معه.

لا بأس ، ما عليك سوى تذكر الحاجة إلى الإفراج عن الموارد بشكل صريح باستخدام spi_freeplan($h):

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     my $h = spi_prepare(
          'select count(*) from pg_class'
     );
     my $res = spi_exec_prepared($h)->{rows}->[0]->{count};
     spi_freeplan($h);
     return $res;
$$;

PL / Python: لا تتدفق

Python مطلقًا ، يتم إصدار الخطة تلقائيًا:

CREATE OR REPLACE function cr3() RETURNS int
LANGUAGE plpythonu as
$$
     return plpy.execute(
           'select count(*) from pg_class'
     )[0]['count']
$$;

PL / V8

نفس قصة Perl. لا يتدفق مثل هذا:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     return plv8.execute(
          'select count(*) from pg_class‘
     )[0].count;
$$;

وهكذا تكون:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     var h = plv8.prepare(
          'select count(*) from pg_class'
     );
     return h.execute()[0].count;
$$;

مرة أخرى: لا تنسى تحرير الموارد. ها هو ذا. h.free();

إنه لا يتدفق:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     var h = plv8.prepare(
          'select count(*) from pg_class'
     );
     var r = h.execute()[0].count;
     h.free();
     return r;
$$;

المعلمات


حان الوقت لفهم كيفية تمرير الحجج إلى الوظائف. في الأمثلة ، سنمرر 4 معلمات بأنواع للدالة:

  • كامل؛
  • مجموعة؛
  • bytea و
  • jsonb

كيف يدخلون PL / Perl ؟

CREATE OR REPLACE FUNCTION crq(a int, b
bytea, c int[], d jsonb ) RETURNS void
LANGUAGE plperl AS
$$
    warn Dumper(@_);
$$;

SELECT crq(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  $VAR1 = '1';
$VAR2 = '\\x61626364';
$VAR3 = bless( {
                 'array' => [
                              '1',
                              '2',
                              '3'
                            ],
                 'typeoid' => 1007
               }, 'PostgreSQL::InServer::ARRAY' );
$VAR4 = '{"a": 2, "b": 3}';
 crq 
-----
(1 row)

هل سيكون JSON أو JSONB - في هذه الحالة ، لا فرق: لا يزالون في شكل سلسلة. هذه رسوم متعددة الاستخدامات: Postgres لديها الكثير من الأنواع بدرجات متفاوتة من "مدمجة". لطلب من المطور أنه مع النوع الجديد الذي يوفره على الفور ، ستكون وظائف التحويل لجميع PL / * أكثر من اللازم. بشكل افتراضي ، يتم تمرير العديد من الأنواع كسلاسل. لكن هذا ليس مناسبًا دائمًا ، يجب عليك تحليل هذه المصطلحات. بالطبع ، أود أن تتحول بيانات Postgres على الفور إلى هياكل Perl المناسبة. بشكل افتراضي ، لا يحدث هذا ، ولكن بدءًا من 9.6 ، ظهرت آلية TRANSFORM - القدرة على تحديد وظائف تحويل النوع: CREATE TRANSFORM .

لإنشاء TRANSFORM ، تحتاج إلى كتابة دالتين في C: إحداها ستقوم بتحويل البيانات من نوع معين إلى جانب واحد ، والآخر إلى الخلف. يرجى ملاحظة أن TRANSFORM يعمل في أربعة أماكن:

  • عند تمرير المعلمات إلى دالة ؛
  • عند إرجاع قيمة دالة ؛
  • عند تمرير المعلمات لمكالمة SPI داخل دالة ؛
  • عند استلام نتيجة مكالمة SPI داخل الوظيفة.

ظهر Transform JSONB لـ Perl و Python ، الذي طوره أنطون بيكوف ، في الإصدار الحادي عشر من Postgres. الآن لا تحتاج إلى تحليل JSONB ، فإنه يدخل إلى Perl على الفور باعتباره البنية المقابلة. يجب عليك إنشاء امتداد jsonb_plperl ، ثم يمكنك استخدام TRANSFORM:

CREATE EXTENSION jsonb_plperl;
CREATE OR REPLACE FUNCTION crq2(d jsonb)
RETURNS void LANGUAGE plperl
TRANSFORM FOR TYPE jsonb AS $$
     warn Dumper(@_);
$$;

يمكنك استدعاء هذه الوظيفة للتحقق من أن JSONB تحول إلى تجزئة لؤلؤة:

SELECT crq2( '{"a":2,"b":3}');


WARNING:  $VAR1 = {
          'a' => '2',
          'b' => '3'
        };
 crq2 
------
(1 row)

مسألة مختلفة تماما!

مؤلف هذا المقال كان له يد في تطوير التحولات. اتضح أن مثل هذا النوع من البيانات البسيطة ، كما booleanتم تمريره إلى PL / Perl في شكل غير مريح ، كسلاسل نصية 't'أو 'f'. لكن في فهم بيرل ، فإن السلسلة 'f' صحيحة. للقضاء على الإزعاج ، تم اختراع التصحيح الذي حدد التحويل للنوع المنطقي . وصل هذا التصحيح إلى PostgreSQL 13 وسيكون متاحًا قريبًا. نظرًا لبساطتها ، يمكن أن يعمل bool_plperl كنموذج بداية بسيط لكتابة أي تحويل آخر.

آمل أن يقوم شخص ما بتطوير TRANSFORM لأنواع البيانات الأخرى (البايت ، والمصفوفات ، والتواريخ ، والأرقام).

لنر الآن كيف يتم تمرير المعلمات في Python .

CREATE EXTENSION jsonb_plpython3u;
CREATE OR REPLACE FUNCTION pdump(a int, b bytea, c int[], d jsonb ) RETURNS void
LANGUAGE plpython3u
TRANSFORM FOR TYPE jsonb AS $$
      plpy.warning(a,b,c,d)
$$;

SELECT pdump(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  (1, b'abcd', [1, 2, 3], {'a': Decimal('2'), 'b': Decimal('3')})
 pdump 
-------
(1 row)

يتم تحويل المصفوفة إلى صفيف - هذا أمر جيد (حيث يتم أيضًا نقل المصفوفات متعددة الأبعاد الإصدار PG10 بشكل صحيح إلى الثعبان). في Perl ، تم تحويل صفيف إلى كائن من فئة خاصة. حسنا ، jsonbتحولت. بدون TRANSFORM ، سيتم تمرير jsonb كسلسلة.

الآن دعنا نرى في شكل المعلمات في JS .

CREATE OR REPLACE FUNCTION jsdump(a int, b bytea, c int[], d jsonb) RETURNS void
LANGUAGE plv8 AS $$
     plv8.elog(WARNING,a,b,c,d)
$$;

SELECT jsdump(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  1 97,98,99,100 1,2,3 [object Object]
jsdump 
-------
(1 row)

تم تحويل JSONB إلى كائن JavaScript بدون أي تحويل! يتم أيضًا تحويل أنواع Postgres المؤقتة إلى نوع Date JS. نفس الشيء المنطقي. جميع التحويلات مضمنة بالفعل في PL / V8.

اعمل مع اللانهاية


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

يحتوي Python على وحدة Numpy التي تحدد اللانهاية العددية:

import numpy as nm
a = nm.inf
b = -nm.inf
print(a, b)

inf -inf

لدى Perl أيضًا ما لا نهاية ، فهي تستخدم سلسلة "infinity"يمكن اختصارها "inf". على سبيل المثال ، يمكنك أن تقول:

perl -e 'print 1 * "inf"'

Inf

أو

perl -e 'print 1/"inf"'

0

في PL / Perl ، PL / Python ، PL / v8 ، يتم تمرير اللانهاية العددية من Postgres بشكل صحيح ، ولكن التاريخ اللانهائي ليس صحيحًا تمامًا. بدلاً من ذلك ، في PL / Perl و PL / Python لا يوجد نوع بيانات مدمج للوقت ، تأتي السلسلة هناك. في PL / V8 ، يوجد تاريخ مضمّن من النوع ، ويتحول التاريخ المعتاد من postgres إليه. لكن V8 لا يعرف التاريخ اللانهائي ، وعند نقله ، يتحول إلى Invalid Date.

تمرير المعلمات إلى الطلبات المعدة


عودة إلى prepareالنظر في كيفية تمرير المعلمات هناك. هناك الكثير من القواسم المشتركة بين اللغات المختلفة ، نظرًا لأنها تعتمد جميعها على SPI.

عندما تقوم بإعداد استعلام في PL / Perl ، تحتاج إلى تحديد نوع المعلمات التي تم تمريرها ، وعندما تقوم بتشغيل الاستعلام ، فإنك تحدد فقط قيم هذه المعلمات (يتم تمرير المعلمات إلى PL / pgSQL بنفس الطريقة).

DO LANGUAGE plperl $$
     my $h= spi_prepare('SELECT * FROM pg_class WHERE
          relname ~ $1', 'text' );                     #   
     warn Dumper(spi_exec_prepared($h, 'pg_language')); #   
     spi_freeplan($h);
$$;

في PL / Python ، الجوهر هو نفسه ، لكن بناء الجملة مختلف قليلاً:

DO LANGUAGE plpython3u $$
     h= plpy.prepare('SELECT relname FROM pg_class WHERE relname ~ $1', ['text'] )
     plpy.notice(.execute (['pg_language']))
$$;

في PL / V8 ، الاختلافات ضئيلة:

DO LANGUAGE plv8 $$
    var h= plv8.prepare('SELECT relname FROM pg_class WHERE relname ~ $1', ['text'] );
    plv8.elog(NOTICE, h.execute (['pg_language']));
    h.free();
$$;

في PL / Java ، كل شيء مختلف. هناك ، من الواضح أنه لم يتم استخدام SPI ، ولكن تم تكوين اتصال زائف JDBC بقاعدة البيانات. بالنسبة لمبرمج PL / Java ، يحدث كل شيء كما لو كان يقوم بإنشاء تطبيق عميل. هذا مناسب ، ويمكن للمرء أيضًا الاقتراب من تصميم PL / Perl و PL / Python ، ولكن لسبب ما لم يتم ذلك (ومع ذلك ، لا أحد يمنع إنشاء تطبيقين آخرين لـ PL / Perl و PL / Python).

العمل مع المؤشر


تحتوي جميع وظائف SPI التي استخدمناها عندما ذهبنا إلى قاعدة البيانات - spi_exec_query()وغيرها - على معلمة تحد من عدد الصفوف التي يتم إرجاعها. إذا كنت بحاجة إلى الكثير من الصفوف المرتجعة ، فلا يمكنك الاستغناء عن المؤشر لسحبها قليلاً.

تعمل المؤشرات بجميع هذه اللغات. في PL / Perl ،
spi_exec_query يُرجع المؤشر الذي يمكنك من خلاله استخراج السلاسل في وقت واحد. ليس من الضروري إغلاق المؤشر ، بل سيغلق نفسه. ولكن إذا كنت تريد إعادة اكتشافه مرة أخرى ، فيمكنك إغلاقه صراحة باستخدام أمر close().

DO LANGUAGE plperl $$
    my $cursor = spi_query('SELECT * FROM pg_class');
    my $row;
    while(defined($row = spi_fetchrow($cursor))) {
         warn $row->{relname};
    }
$$;

WARNING:  pg_statistic at line 5.
WARNING:  pg_toast_2604 at line 5.
WARNING:  pg_toast_2604_index at line 5.
WARNING:  pg_toast_2606 at line 5.
WARNING:  pg_toast_2606_index at line 5.
WARNING:  pg_toast_2609 at line 5.
WARNING:  pg_toast_2609_index at line 5.
...

في PL / Python ، كل شيء مشابه جدًا ، ولكن يتم تقديم المؤشر ككائن يمكنك تدويره من خلال:

h = plpy.prepare('SELECT ...');
cursor = plpy.cursor(h);
for row in cursor:
...
cursor.close() //  

في PL / v8 ، كل شيء مشابه جدًا أيضًا ، ولكن لا تنسَ تحرير خطة الاستعلام المعدة:

var h = plv.prepare('SELECT ...');
var cursor = h.cursor();
var row;
while(row = cursor.fetch()) {
...
}
cursor.close();
h.free();

PL / V8: الوصول السريع إلى الميزات


في PL / V8 ، يمكنك استدعاء وظيفة ليست من SELECT العادية ، ولكن يمكنك العثور عليها بالاسم وتشغيلها على الفور plv8.find_function(name);. ولكن ضع في اعتبارك أنه في JS لا يمكن أن تكون الوظيفة متعددة الأشكال ، كما هو الحال في PostgreSQL ، حيث يمكن أن تتعايش وظائف بنفس الاسم ولكن مع معلمات مختلفة. في PL / v8 ، بالطبع ، يمكننا إنشاء وظائف متعددة الأشكال ، ولكن find_functionسيكون هناك خطأ عند محاولة استخدامه .

ERROR:  Error: more than one function named "jsdump"

إذا كانت الوظيفة بالاسم غير غامضة ، فيمكن استدعاؤها بدون SPI وكتابة التحويلات ، أي أسرع بكثير. على سبيل المثال ، مثل هذا:

DO LANGUAGE plv8 $$
plv8.find_function('jsdump')(1, 'abc');
$$;

المعاملات


يتمتع Postgres 11 بالكثير من المرح: فقد ظهرت إجراءات حقيقية . تستخدم Postgres أن يكون لها ميزات فقط. الفرح ليس فقط بسبب التوافق والامتثال لمعيار SQL ، ولكن لماذا: ضمن الإجراءات يمكنك الالتزام بالمعاملات واسترجاعها.

لدى PL / Perl و PL / Python بالفعل وظائف SPI لإدارة المعاملات ، بينما لا يوجد PL / V8 بعد. في PL / Perl ، يتم استدعاء هذه الوظائف spi_commit()و spi_rollback()، ومثال للاستخدام في الوثائق . في PL / Python ، هذا هو plpy.commit()و plpy.rollback().

المعاملة من الباطن


تعتبر المعاملات الفرعية ملائمة لمعالجة الأخطاء الصحيحة في المنطق المعقد متعدد المستويات.

في PL / pgSQL داخل المعاملة ، تعتبر كل كتلة تحتوي على كلمة EXCEPTION تحويلة فرعية. يمكنك القراءة عن بعض مشكلات الأداء والموثوقية التي قد تنشأ في هذه الحالة ، على سبيل المثال ، هنا .

لا توجد معاملات فرعية صريحة في PL / Perl ، ولكن يمكن محاكاتها من خلال نقاط savaepoints. على ما يبدو ، إذا كنت ترغب في ذلك ، فمن السهل كتابة وحدة لؤلؤة تنفذ المعاملات الفرعية في شكل صريح.

في PL / Python ، ظهرت المعاملات الفرعية منذ فترة طويلة: من 9.5 صريحة ، قبل ذلك كانت هناك معاملات ضمنية . يمكنك تحديد المعاملة ، ولفهاtry-وتنفيذها. إذا سقطت المعاملة الفرعية ، فإننا نسقط في الكتلة except، إذا لم تسقط ، ثم في الكتلة elseونتحرك.

try:
     with plpy.subtransaction():
          plpy.execute("...")
          plpy.execute("...")
except plpy.SPIError, e:
. . .
else:
. . .

يوجد تصميم مماثل في PL / V8 ، فقط في بناء جملة JS.

try {
plv8.subtransaction(function() {
plv8.execute('UPDATE...');
plv8.execute('UPDATE...');
});
}
catch(e) {
...
}

استنتاج


حاول ، ولكن لا تسيء استخدامها :) المعرفة PL / * يمكن أن تجلب بعض الفوائد. مثل أي أداة ، فهم يحبون استخدامها للغرض المقصود.

PL / v8 واعدة للغاية ، ولكن في بعض الأحيان يتصرف بشكل غير متوقع ولديه عدد من المشاكل. لذلك ، من الأفضل إخراج اللغات من العلبة إذا كانت مناسبة لمهمتك.

أريد أن أشكر إيجور ليفشين (Igor_Le) ، الذي ساعدني كثيرًا في إعداد المادة للمقال ، وألقى ببعض الأفكار المفيدة ، بالإضافة إلى Evgeny Sergeev و Alexey Fadeev للتصحيحات التي اقترحوها.

All Articles