تشتهر 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=on
strict
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 / PerlDO $$
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 معلمات بأنواع للدالة:كيف يدخلون 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 للتصحيحات التي اقترحوها.