أداة لتسهيل انتقالك من Oracle PL/SQL إلى وظائف JavaScript UDF في Couchbase

الخلفية

ما هو PL/SQL؟

PL/SQL هو لغة إجرائية مصممة خصيصًا لاستيعاب عبارات SQL ضمن بناء جملتها اللغوية. تتضمن عناصر لغة البرمجة الإجرائية مثل الشروط والحلقات ويمكنها التعامل مع الاستثناءات (أخطاء التشغيل).

PL/SQL هي لغة أصلية لـ قواعد بيانات أوراكل، وتدعم قواعد البيانات مثل IBM DB2، PostgreSQL، وMySQL بناء على ميزات التوافق مع PL/SQL.

ما هي وظيفة JavaScript UDF؟

JavaScript UDF هي بديل Couchbase لـ PL/SQL.

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

معظم قواعد البيانات الحديثة مثل Couchbase، MongoDB، Snowflake، وGoogle BigQuery تدعم Javascript UDF.

المشكلة

المشكلة الشائعة التي يواجهها المستخدمون الذين يقومون بترحيل من Oracle إلى Couchbase هي نقل البرامج النصية PL/SQL الخاصة بهم. بدلاً من دعم PL/SQL، يسمح للمستخدمين Couchbase ببناء وظائف محددة من قبل المستخدم في JavaScript (مدعومة منذ عام 2021).

تسمح وظائف JavaScript UDF بالتلاعب بيانيًا وبشكل سهل وبديهي. يتم تحويل الكائنات المتغيرة التي تمر إلى UDF إلى أنواع جافا سكريبت الأصلية والقيم.

النتيجة الغير مقصودة لذلك هو أن غالبية أنظمة إدارة قواعد البيانات العلائقية التي كانت موجودة على مدى العشر سنوات الماضية حثت المطورين بشدة على الوصول إلى قاعدة البيانات باستخدام توسيعاتهم الإجرائية لـ SQL (PL/pgSQL، PL/SQL)، والتي تدعم البنى الإجرائية، والتكامل مع SQL، والتعامل مع الأخطاء، والوظائف والإجراءات، والمشغلات، والمؤشرات، أو على الأقل، الوظائف والإجراءات (مثل Sakila). ولأي محاولة للابتعاد عنها، سيحتاج على كل نصوصهم أن تُعاد كتابتها.

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

الحل

النهج المثالي سيكون تطوير مقيم PL/SQL جديد بالكامل، ولكن ذلك سيتطلب كمية زائدة من ساعات الهندسة، ولنفس حالة الاستخدام، لدينا بالفعل JsEvaluator حديث، مستقر، وسريع — فلماذا دعم مقيم آخر؟

هذا ما يجعل المشكلة حالة استخدام مثالية للاستفادة من التطورات المستمرة في الذكاء الاصطناعي ونماذج LLMs — وهذا ما فعلناه هنا. لقد استخدمنا نماذج الذكاء الاصطناعي التوليدية لتلقين تحويل PL/SQL إلى JSUDF.

بحلول يونيو 2024، النماذج لديها نافذة سياقية محدودة، مما يعني أن النصوص الطويلة PL/SQL تواجه مشكلة بالخطأ:

تتمثل أقصى طول سياق لهذا النموذج في 8192 رمزًا. ومع ذلك، أسفرت رسائلك عن <More-than-8192> رمزًا. يرجى تقليل طول الرسائل.

  • يرجى ملاحظة أن هذا يتعلق بـ GPT4.

فهل ننتظر حتى يصبح الذكاء الاصطناعي أكثر قوة ويسمح بمزيد من الرموز (مثل قانون مور، ولكن بالنسبة لطول السياق مقابل الدقة للذكاء الاصطناعي)؟

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

إذاً، هل نقوم الآن ببناء مترجم؟ حسنًا، نعم ولا.

المراحل في مترجم اللغة هي كما يلي:

  1. تحليل محلل (توكينة)
  2. تحليل نحوي (تحليل جمل)
  3. تحليل دلالي
  4. إنتاج التمثيل المتوسط (IR)
  5. التحسين (اختياري)
  6. إنتاج الكود المستهدف

كيف يعمل مترجم الذكاء الاصطناعي

يتم إجراء الخطوتين 1 و2 باستخدام ANTLR.

نستخدم واجهة الاستماع في ANTLR لالتقاط كتل الإجراء/الدالة/المجهولة الفردية، حيث أنها كتل مستقلة من الكود. في حالة تجاوز كتل الإجراء/الدالة/المجهولة أنفسها نافذة السياق، نترجم على مستوى البيان (حيث يفترض النموذج اللغوي الذهني وجود استخدام للمتغيرات/استدعاءات الدوال التي لم تحدد هنا ولكن في مكان ما قبل ذلك).

بعد ذلك، تُترك الخطوات 3 و4 و5 و6 لـ LLM (GPT)، أي ترجمة كل كتلة PL/SQL إلى دالة JavaScript بأفضل ما يمكنها مع الحفاظ على الدلالات التشغيلية للكتلة وأن تكون دقيقة نحويًا.

النتائج كانت إيجابية بشكل مدهش: الترجمة دقيقة بنسبة 80-85%.

فائدة أخرى للحل هي أننا نخفض من الهلوسة من خلال التركيز على مهمة واحدة في كل مرة، مما يؤدي إلى ترجمة أكثر دقة.

لتصوير:

كيفية استخدام الأداة

يتوقع البرنامج التنفيذي المعطيات التالية من سطر الأوامر:

  • -u : بريد تسجيل الدخول إلى Capella
  • -p : كلمة مرور تسجيل الدخول إلى Capella
  • -cpaddr: عنوان URL لـ Capella لواجهة برمجة التطبيقات chat-completions
  • -orgid: معرف المنظمة في مسار واجهة برمجة التطبيقات chat-completions
  • -cbhost: node-ip: عقدة cbcluster
  • -cbuser: cluster-user-name: مستخدم cbcluster، تمت إضافته من خلال الوصول إلى قاعدة البيانات
  • -cbpassword: cluster-password: كلمة مرور cbcluster، تمت إضافتها من خلال الوصول إلى قاعدة البيانات
  • -cbport: منفذ TLS لـ query-service (عادة 18093)
  • filepath، أي، المسار إلى سكربت PL/SQL الذي يجب ترجمته
  • output->: في دليل المخرجات، يتم إنشاء ملف بنفس اسم ملف plsql مع كود مكتبة JavaScript المترجم.

على سبيل المثال، cat example1.sql:

PLSQL

 

DECLARE

   x NUMBER := 0;

   counter NUMBER := 0;

BEGIN

   FOR i IN 1..4 LOOP

      x := x + 1000;

      counter := counter + 1;

      INSERT INTO temp VALUES (x, counter, 'in OUTER loop');

       --بدء كتلة داخلية 

      DECLARE

         x NUMBER := 0;  -- this is a local version of x

      BEGIN

         FOR i IN 1..4 LOOP

            x := x + 1;  -- this increments the local x

            counter := counter + 1;

            INSERT INTO temp VALUES (x, counter, 'inner loop');

         END LOOP;

      END;

   END LOOP;

   COMMIT;

END;

لتوضيح السكربت أعلاه باختصار، يتم تشغيل حلقة خارجية لمدة 4 تكرارات، مع زيادة x بمقدار 1000، والعداد بمقدار 1. تعمل الحلقة الداخلية لمدة 4 تكرارات، مع زيادة x بمقدار 1، والعداد بمقدار 1.

تشغيل المترجم على PL/SQL الاختباري:

Shell

 

./plsql-to-jsudf -u «capella-signin-mailid» -p «capella-signin-password» -cpaddr https://api.cloud.couchbase.com -orgid «capella-organisation-id» -cbhost «hostname of data node» -cbuser «cbcluster username» -cbpassword «cbcluster password» -cbport 18093 ./translator/test/plsql/example1.sql

مخرجات JsUDF: cat output/example1.js:

JavaScript

 

function nestedloop(){

    var x = 0;

    var counter = 0;

    var querybegin = BEGIN WORK;

    querybegin.close();

    for (var i = 1; i <= 4; i++){

        x = x + 1000;

        counter = counter + 1;

        var params = [x, counter];

        var query = N1QL('INSERT INTO test.testscope.temp VALUES (uuid(),{"val1":$1,"val2":$2,"val3":"in OUTER loop"})',params);

        query.close();

        var x_inner = 0;

        for (var j = 1; j <= 4; j++){

            x_inner = x_inner + 1;

            counter = counter + 1;

            var params_inner = [x_inner, counter];

            var query_inner = N1QL('INSERT INTO test.testscope.temp VALUES (uuid(),{"val1":$1,"val2":$2,"val3":"inner loop"})',params_inner);

            query_inner.close();

        }

    }

    var querycommit = COMMIT WORK;

    querycommit.close();

}

السكربت المترجم يحتوي على دالة nestedloop (اسم يتم إنشاؤه بواسطة LLM) التي تفعل بالضبط ما يحدده كتلة PL/SQL المجهولة الأصلية.

  • ملاحظة جانبية: بالنسبة للدوال/الإجراءات المسماة، سيكون للدوال JS المترجمة نفس الاسم. بالنسبة للكتل المجهولة، يستخدم LLM اسمًا يبتكره.

المشاكل المعروفة

PL/SQL و JS هما لغتان مختلفتان، والطريقة التي يتم دعمهما بها في Oracle وCouchbase لا تسمح بوجود تطابق مباشر نظيف بين الاثنين. فيما يلي بعض القيود التي اكتشفناها والحلول البديلة التي قمنا بتنفيذها لنفس الشيء:

1. console.log غير مدعوم

DBMS_OUTPUT.PUT إجراء مدمج واثنان آخران مشابهان، DBMS_OUTPUT.PUT_LINE و DBMS_OUTPUT.NEW_LINE يتم ترجمتهما إلى console.log()، ولكن console.log هو واجهة برمجة تطبيقات المتصفح ولا تدعمه تنفيذ تقييم JavaScript في Couchbase. كان هذا طلبًا متكررًا، بالنظر إلى أن دالة الأحداث في Couchbase تدعم عبارات print() ولكن ليس في UDFs الخاصة بـ JavaScript.

حل بديل

يتوقع من المستخدمين إنشاء دلو logging.

يتم إدراج السجلات كجزء من مستند INSERT في مجموعة `default`.`default`. سيبدو المستند شيئًا مثل هذا:

 

{

   "udf": «func-name»,

   "log": «argument to console.log», // the actual log line

   "time": «current ISO time string»

}

يمكن للمستخدم عرض سجلاته عن طريق اختيار logging:

 

SELECT * FROM logging WHERE udf= "«func-name»";

SELECT * FROM logging WHERE time BETWEEN "«date1»" AND "«date2»";

مثال: 

  • الأصلي
 

BEGIN

   DBMS.OUTPUT.PUT("Hello world!");

END;

/

  • الترجمة
JavaScript

 

function helloWorld() {

 

   // حل بديل لـ console.log("Hello world!");

   var currentDate = new Date();

   var utcISOString = currentDate.toISOString();

   var params = [utcISOString,'anonymousblock1',"Hello world!"];

   var logquery = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', params);

   logquery.close();

}

تم تنفيذ هذا بالفعل في الأداة.

لعرض السجل:

 

EXECUTE FUNCTION helloWorld();

"results": [

   null

]



CREATE PRIMARY INDEX ON logging;

"results": [

]



SELECT * FROM logging;

"results": [

   {"logging":{"log":"Hello world!","time":"2024-06-26T09:20:56.000Z","udf":"anonymousblock1"}}

]

2. استدعاءات الدوال عبر الحزم

الإجراءات/الدوال المدرجة في مواصفات الحزمة هي عالمية ويمكن استخدامها من حزم أخرى عبر «package_name».«public_procedure/function». ومع ذلك، فإن نفس الشيء غير صحيح لمكتبة JavaScript في Couchbase، حيث لا تدعم هياكل الاستيراد والتصدير من قبل تنفيذ تقييم JavaScript في Couchbase.

حل بديل

  • في حالة استدعاء دالة بين المكتبات «lib_name».«function»()، يُفترض أن يكون للمستخدم المكتبة المشار إليها «lib_name» قد تم إنشاؤها بالفعل؛ يمكنك التحقق من ذلك من خلال GET /evaluator/v1/libraries.
  • يُفترض أيضًا أن تكون الدالة المشار إليها «function» قد تم إنشاؤها كدالة عالمية UDF؛ يمكن التحقق من ذلك من خلال GET /admin/functions_cache أو تحديد مساحة مفاتيح system:functions. بهذه الطريقة يمكننا الوصول إلى الدالة عبر n1ql.

مثال:

  • مجموعة math_utils
 

CREATE OR REPLACE PACKAGE math_utils AS

   -- دالة عامة لجمع رقمين

   FUNCTION add_numbers(p_num1 NUMBER, p_num2 NUMBER) RETURN NUMBER;

END math_utils;

/

CREATE OR REPLACE PACKAGE BODY math_utils AS

   FUNCTION add_numbers(p_num1 NUMBER, p_num2 NUMBER) RETURN NUMBER IS

      BEGIN

         RETURN p_num1 + p_num2;

      END add_numbers;

END math_utils;

/

  • show_sum حزمة
 

CREATE OR REPLACE PACKAGE show_sum AS

   -- إجراء عام لعرض مجموع رقمين

   PROCEDURE display_sum(p_num1 NUMBER, p_num2 NUMBER);

END show_sum;

/

CREATE OR REPLACE PACKAGE BODY show_sum AS

   PROCEDURE display_sum(p_num1 NUMBER, p_num2 NUMBER) IS

      v_sum NUMBER;

   BEGIN

      -- استدعاء دالة add_numbers من حزمة math_utils

      v_sum := math_utils.add_numbers(p_num1, p_num2);

 

      -- عرض المجموع باستخدام DBMS_OUTPUT.PUT_LINE

      DBMS_OUTPUT.PUT_LINE('The sum of ' || p_num1 || ' and ' || p_num2 || ' is ' || v_sum);

   END display_sum;

END show_sum;

/

  • الشيفرة المترجمة:
 

function show_sum(a, b) {

 var sum_result;



// حل مؤقت لاستدعاء دالة بين المكتبات math_utils.add_numbers(a, b)

var crossfunc = N1QL("EXECUTE FUNCTION add_numbers($1,$2)",[a, b])

var crossfuncres = []

for(const doc of crossfunc) {

   crossfuncres.push(doc);

}



// الاستبدال الفعلي لدالة math_utils.add_numbers(a, b)

    sum_result = crossfuncres[0];



 

// حل مؤقت ل console.log('The sum of ' + a + ' and ' + b + ' is: ' + sum_result);

var currentDate = new Date();

var utcISOString = currentDate.toISOString();

var params = [utcISOString,'SHOW_SUM','The sum of ' + a + ' and ' + b + ' is: ' + sum_result];

var logquery = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', params);

logquery.close();

}

يتم التعامل معه تلقائيًا من قبل البرنامج — مع تحذير بأنه يجب التحقق منه بواسطة مجموعة عيون بشرية!

3. المتغيرات العامة

تدعم PL/SQL المتغيرات العامة على مستوى الحزمة ومستوى الجلسة، ولكن لا تدعم المتغيرات العامة في JsUDF عمدًا بتصميم، حيث يثير هذا الأمر مخاوف بشأن تسرب الذاكرة.

حل بديل

الحل المقترح يتطلب ضبطًا يدويًا للترجمة المولدة. على سبيل المثال:

 

   CREATE OR REPLACE PACKAGE global_vars_pkg AS

     -- تعريفات المتغيرات العامة

     g_counter NUMBER := 0;

     g_message VARCHAR2(100) := 'Initial Message';



     -- تعريفات الإجراءات العامة

     PROCEDURE increment_counter;

     PROCEDURE set_message(p_message VARCHAR2);

     PROCEDURE show_globals;

   END global_vars_pkg;

   /



   CREATE OR REPLACE PACKAGE BODY global_vars_pkg AS



     -- إجراء لزيادة العداد

     PROCEDURE increment_counter IS

     BEGIN

       g_counter := g_counter + 1;

     END increment_counter;



     -- إجراء لتعيين الرسالة العامة

     PROCEDURE set_message(p_message VARCHAR2) IS

     BEGIN

       g_message := p_message;

     END set_message;



     -- إجراء لعرض القيم الحالية للمتغيرات العامة

     PROCEDURE show_globals IS

     BEGIN

       DBMS_OUTPUT.PUT_LINE('g_counter = ' || g_counter);

       DBMS_OUTPUT.PUT_LINE('g_message = ' || g_message);

     END show_globals;



   END global_vars_pkg;

   /

أي وظيفة تقوم بتعديل متغير عام يجب أن تقبله كوسيط وتُعيد قيمته إلى المُستدعي.

  • increment_counter:
 

function increment_counter(counter){

   counter = counter + 1;

   return counter

}

أي وظيفة تقرأ فقط متغيرًا عامًا يمكنها قبوله كوسيط.

  • show_globals:
 

function show_globals(counter, message){

   // حل بديل لـ console.log(counter);

   var currentDate = new Date();

   var utcISOString = currentDate.toISOString();

   var params = [utcISOString,'SHOW_GLOBALS',couter];

   var logquery = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', params);

   logquery.close();



   // حل بديل لـ console.log(message);

   var currentDate = new Date();

   var utcISOString = currentDate.toISOString();

   var params = [utcISOString,'SHOW_GLOBALS',message];

   var logquery = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', params);

   logquery.close();

}

تحويل الحزمة إلى مكتبة

تُظهر هذه القسم عملية تحويل حزمة إلى مكتبة من البداية إلى النهاية باستخدام الأداة.

  • نموذج حزمة PL/SQL:
 

CREATE OR REPLACE PACKAGE emp_pkg IS

  PROCEDURE insert_employee(

    p_emp_id     IN employees.emp_id%TYPE,

    p_first_name IN employees.first_name%TYPE,

    p_last_name  IN employees.last_name%TYPE,

    p_salary     IN employees.salary%TYPE

  );



  PROCEDURE update_employee(

    p_emp_id     IN employees.emp_id%TYPE,

    p_first_name IN employees.first_name%TYPE,

    p_last_name  IN employees.last_name%TYPE,

    p_salary     IN employees.salary%TYPE

  );



  PROCEDURE delete_employee(

    p_emp_id IN employees.emp_id%TYPE

  );



  PROCEDURE get_employee(

    p_emp_id     IN employees.emp_id%TYPE,

    p_first_name OUT employees.first_name%TYPE,

    p_last_name  OUT employees.last_name%TYPE,

    p_salary     OUT employees.salary%TYPE

  );

END emp_pkg;

/



CREATE OR REPLACE PACKAGE BODY emp_pkg IS

  PROCEDURE insert_employee(

    p_emp_id     IN employees.emp_id%TYPE,

    p_first_name IN employees.first_name%TYPE,

    p_last_name  IN employees.last_name%TYPE,

    p_salary     IN employees.salary%TYPE

  ) IS

  BEGIN

    INSERT INTO employees (emp_id, first_name, last_name, salary)

    VALUES (p_emp_id, p_first_name, p_last_name, p_salary);

  END insert_employee;



  PROCEDURE update_employee(

    p_emp_id     IN employees.emp_id%TYPE,

    p_first_name IN employees.first_name%TYPE,

    p_last_name  IN employees.last_name%TYPE,

    p_salary     IN employees.salary%TYPE

  ) IS

  BEGIN

    UPDATE employees

    SET first_name = p_first_name,

        last_name  = p_last_name,

        salary     = p_salary

    WHERE emp_id = p_emp_id;

  END update_employee;



  PROCEDURE delete_employee(

    p_emp_id IN employees.emp_id%TYPE

  ) IS

  BEGIN

    DELETE FROM employees

    WHERE emp_id = p_emp_id;

  END delete_employee;



  PROCEDURE get_employee(

    p_emp_id     IN employees.emp_id%TYPE,

    p_first_name OUT employees.first_name%TYPE,

    p_last_name  OUT employees.last_name%TYPE,

    p_salary     OUT employees.salary%TYPE

  ) IS

  BEGIN

    SELECT first_name, last_name, salary

    INTO p_first_name, p_last_name, p_salary

    FROM employees

    WHERE emp_id = p_emp_id;

  END get_employee;

END emp_pkg;

/

  • الترجمة:
Shell

 

./plsql-to-jsudf -u «capella-signin-mailid» -p «capella-signin-password» -cpaddr https://api.cloud.couchbase.com -orgid «capella-organisation-id» -cbhost «hostname of data node» -cbuser «cbcluster username» -cbpassword «cbcluster password»  -cbport 18093 translator/test/plsql/blog_test.sql

  • الكود:
 

function insert_employee(p_emp_id, p_first_name, p_last_name, p_salary){

    var params = [p_emp_id, p_first_name, p_last_name, p_salary];

    var query = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {"emp_id":$1, "first_name":$2, "last_name":$3, "salary":$4})', params);

    query.close();

}

  

function update_employee(p_emp_id, p_first_name, p_last_name, p_salary){

    var params = [p_first_name, p_last_name, p_salary, p_emp_id];

    var query = N1QL('UPDATE test.testscope.employees SET first_name = $1, last_name = $2, salary = $3 WHERE emp_id = $4', params);

    query.close();

}



function delete_employee(p_emp_id){

    var querybegin=BEGIN WORK; 

    var params = [p_emp_id];

    var query= N1QL('DELETE FROM test.testscope.employees WHERE emp_id = $1',params); 

    query.close(); 

    var querycommit=COMMIT WORK; 

    querycommit.close(); 

}









  

function get_employee(p_emp_id){

    var query = N1QL('SELECT first_name, last_name, salary FROM test.testscope.employees WHERE emp_id = $1', [p_emp_id]);

    var rs = [];

    for (const row of query) { 

        rs.push(row); 

    }

    query.close();

    var p_first_name = rs[0]['first_name'];

    var p_last_name = rs[0]['last_name'];

    var p_salary = rs[0]['salary'];

    return {first_name: p_first_name, last_name: p_last_name, salary: p_salary};

}

لنُدخل وثيقة موظف جديدة.

  • إنشاء مجموعة موظفين:
 
curl -u  Administrator:password http://127.0.0.1:8091/pools/default/buckets/test/scopes/testscope/collections -d name=employees     

إدراج موظف

 

curl -u  Administrator:password https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION insert_employee(1, "joe", "briggs", 10000)' -k     

{

"requestID": "2c0854c1-d221-42e9-af47-b6aa0801a46c",

"signature": null,

"results": [

],

"errors": [{"code":10109,"msg":"Error executing function 'insert_employee' (blog_test:insert_employee)","reason":{"details":{"Code":"    var query = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {\"emp_id\":$1, \"first_name\":$2, \"last_name\":$3, \"salary\":$4})', params);","Exception":{"_level":"exception","caller":"insert_send:207","code":5070,"key":"execution.insert_key_type_error","message":"Cannot INSERT non-string key 1 of type value.intValue."},"Location":"functions/blog_test.js:5","Stack":"   at insert_employee (functions/blog_test.js:5:17)"},"type":"Exceptions from JS code"}}],

"status": "fatal",

"metrics": {"elapsedTime": "104.172666ms","executionTime": "104.040291ms","resultCount": 0,"resultSize": 0,"serviceLoad": 2,"errorCount": 1}

}

هذا ما يُعطي خطأ، وهذا مقبول — يمكننا إصلاحه يدويًا.

قراءة السبب والاستثناء: Cannot INSERT non-string key 1 of type value.intValue، آه! دائمًا يُتوقع أن يكون المفتاح سلسلة: تمرير insert_employee("1", "joe", "briggs", 10000) سيكون الحيلة، لكن من غير المنطقي توقع employee_id أن يكون سلسلة.

لنُعدل الكود المولد:

 

function insert_employee(p_emp_id, p_first_name, p_last_name, p_salary){

    var params = [p_emp_id.toString(), p_emp_id, p_first_name, p_last_name, p_salary];

    var query = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {"emp_id":$2, "first_name":$3, "last_name":$4, "salary":$5})', params);

    query.close();

}

ثم نعيد إنشاء UDF:

 

curl -u  Administrator:password https://127.0.0.1:18093/query/service -d 'statement=CREATE OR REPLACE FUNCTION insert_employee(p_emp_id, p_first_name, p_last_name, p_salary) LANGUAGE JAVASCRIPT AS "insert_employee" AT "blog_test"' -k 

{

"requestID": "89df65ac-2026-4f42-8839-b1ce7f0ea2be",

"signature": null,

"results": [

],

"status": "success",

"metrics": {"elapsedTime": "27.730875ms","executionTime": "27.620083ms","resultCount": 0,"resultSize": 0,"serviceLoad": 2}

}

محاولة إدراجه مرة أخرى:    

 


curl -u  Administrator:password https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION insert_employee(1, "joe", "briggs", 10000)' -k

{

"requestID": "41fb76bf-a87f-4472-b8ba-1949789ae74b",

"signature": null,

"results": [

null

],

"status": "success",

"metrics": {"elapsedTime": "62.431667ms","executionTime": "62.311583ms","resultCount": 1,"resultSize": 4,"serviceLoad": 2}

}

تحديث موظف

يا للأسف! هناك خطأ: الموظف 1 ليس جو، بل هو إميلي. 

لنقم بتحديث الموظف 1:

 

curl -u  Administrator:password https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION update_employee(1, "Emily", "Alvarez", 10000)' -k

{

"requestID": "92a0ca70-6d0d-4eb1-bf8d-0b4294ae987d",

"signature": null,

"results": [

null

],

"status": "success",

"metrics": {"elapsedTime": "100.967708ms","executionTime": "100.225333ms","resultCount": 1,"resultSize": 4,"serviceLoad": 2}

}

عرض الموظف

 

curl -u  Administrator:password https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION get_employee(1)' -k 

{

"requestID": "8f180e27-0028-4653-92e0-606c80d5dabb",

"signature": null,

"results": [

{"first_name":"Emily","last_name":"Alvarez","salary":10000}

],

"status": "success",

"metrics": {"elapsedTime": "101.995584ms","executionTime": "101.879ms","resultCount": 1,"resultSize": 59,"serviceLoad": 2}

}

حذف الموظف

غادرت إميلي.

 

 curl -u  Administrator:password https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION delete_employee(1)' -k                      

{

"requestID": "18539991-3d97-40e2-bde3-6959200791b1",

"signature": null,

"results": [

],

"errors": [{"code":10109,"msg":"Error executing function 'delete_employee' (blog_test:delete_employee)","reason":{"details":{"Code":"    var querycommit=N1QL('COMMIT WORK;', {}, false); ","Exception":{"_level":"exception","caller":"txcouchbase:240","cause":{"cause":{"bucket":"test","collection":"_default","document_key":"_txn:atr-988-#1b0","error_description":"Durability requirements are impossible to achieve","error_name":"DurabilityImpossible","last_connection_id":"eda95f8c35df6746/d275e8398a49e515","last_dispatched_from":"127.0.0.1:50069","last_dispatched_to":"127.0.0.1:11210","msg":"durability impossible","opaque":7,"scope":"_default","status_code":161},"raise":"failed","retry":false,"rollback":false},"code":17007,"key":"transaction.statement.commit","message":"Commit Transaction statement error"},"Location":"functions/blog_test.js:29","Stack":"   at delete_employee (functions/blog_test.js:29:21)"},"type":"Exceptions from JS code"}}],

"status": "fatal",

"metrics": {"elapsedTime": "129.02975ms","executionTime": "128.724ms","resultCount": 0,"resultSize": 0,"serviceLoad": 2,"errorCount": 1}

}

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

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

الحل هنا هو تعديل الكود لإزالة الترجمة المحيطة.

 

function delete_employee(p_emp_id){

    

    var params = [p_emp_id];

    var query= N1QL('DELETE FROM test.testscope.employees WHERE emp_id = $1',params); 

    query.close(); 

}

 

curl -u  Administrator:password https://127.0.0.1:18093/query/service -d 'statement=CREATE OR REPLACE FUNCTION delete_employee(p_emp_id) LANGUAGE JAVASCRIPT AS "delete_employee" AT "blog_test"' -k 

{

"requestID": "e7432b82-1af8-4dc4-ad94-c34acea59334",

"signature": null,

"results": [

],

"status": "success",

"metrics": {"elapsedTime": "31.129459ms","executionTime": "31.022ms","resultCount": 0,"resultSize": 0,"serviceLoad": 2}

}

 

curl -u  Administrator:password https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION delete_employee(1)' -k

{

"requestID": "d440913f-58ff-4815-b671-1a72b75bb7eb",

"signature": null,

"results": [

null

],

"status": "success",

"metrics": {"elapsedTime": "33.8885ms","executionTime": "33.819042ms","resultCount": 1,"resultSize": 4,"serviceLoad": 2}

}

الآن، تعمل جميع الوظائف في PL/SQL الأصلية في Couchbase عبر JS UDFs. نعم، المثال بسيط جدًا، لكنك تفهم الفكرة حول كيفية استخدام الأداة لنقل نصوص PL/SQL الخاصة بك مع إشراف يدوي قليل.

تذكر أن الأداة من المفترض أن تأخذك بنسبة 80%: بينما لا يزال يجب القيام بـ 20% من قبلك، لكن ذلك أفضل بكثير من كتابة كل ذلك الكود بنفسك!

المستقبل

هذا المشروع مفتوح المصدر، لذا لا تتردد في المساهمة. بعض الأفكار التي تم طرحها علينا تشمل:

  1. ذكاء اصطناعي ناقد يمكنه انتقاد الكود المُولد لضمان عدم الحاجة إلى تدخل يدوي على الإطلاق
  2. حاليًا، الكود المصدر هو كود يعمل فقط؛ لم يتم التفكير في التوازي أو إعادة استخدام الكود.

وأيضًا قد تكون هناك قيود كما تم مناقشتها سابقًا.

وفي النهاية، أود أن أشكر كاميني جاجتياني على توجيهي وبيير ريجازوني على مساعدتي في اختبار أداة التحويل.

Source:
https://dzone.com/articles/oracle-plsqls-to-couchbase-jsudfs-tool