一個幫助您從 Oracle PL/SQL 轉換為 Couchbase JavaScript UDF 的工具

背景

什麼是 PL/SQL?

PL/SQL 是一種專門設計用於在其語法中包含 SQL 語句的過程式語言。它包括條件和循環等過程式語言元素,並能處理異常(運行時錯誤)。

PL/SQL 是 Oracle 數據庫的原生語言,而 IBM DB2、PostgreSQL 和 MySQL 等數據庫則通過相容性功能支持 PL/SQL 構造。

什麼是 JavaScript UDF?

JavaScript UDF 是 Couchbase 對 PL/SQL 的替代方案。

JavaScript UDF 將 JavaScript 的通用腳本靈活性引入數據庫,允許在現代數據庫系統中進行動態和強大的操作,並增強數據查詢、處理和轉換的靈活性。

大多數現代數據庫如 Couchbase、MongoDB、Snowflake 和 Google BigQuery 都支持 JavaScript UDF。

問題

從 Oracle 遷移到 Couchbase 的用戶常見的一個問題是移植他們的 PL/SQL 腳本。Couchbase 允許用戶使用 JavaScript 構建用戶定義函數(自 2021 年以來支持)。

JavaScript UDF 允許對變體和 JSON 數據進行簡單、直觀的操作。傳遞給 UDF 的變體對象會轉換為原生 JavaScript 類型和值。

這種方法的意外後果是,過去十年中存在的 大多數關聯式數據庫 已經大力鼓勵開發人員使用它們的SQL程序擴展(如PL/pgSQL、PL/SQL)來訪問數據庫,這些擴展支持過程式結構、與SQL的整合、錯誤處理、函數和過程、觸發器以及游標,或至少支持函數和過程(如Sakila)。如果有任何試圖遠離這些擴展的舉措,所有相應的腳本都需要重寫。

重寫代碼通常是一項繁瑣的任務,特別是當處理那些自2000年代以來一直被維護的PL/SQL腳本時。這些腳本可能會很複雜,通常延伸到數千行,對於普通企業用戶來說可能會讓人感到不知所措。

解決方案

理想的方法應該是開發一個全新的PL/SQL評估器,但這將需要大量的工程時間,而對於相同的用例,我們已經擁有一個現代、穩定且快速的JsEvaluator — 那麼為什麼要支持另一個評估器呢?

這使得這個問題成為一個完美的使用案例,可以利用人工智慧和LLMs持續進步的優勢 — 這也是我們在這裡所做的。我們使用生成式AI模型來自動轉換PL/SQL為JSUDF。

截至2024年6月,模型具有有限的上下文窗口,這意味著較長的PL/SQL會出現錯誤:

「此模型的最大上下文長度為8192個標記。然而,您的消息結果為<超過8192>個標記。請減少消息的長度。」

  • 請注意這是針對GPT4。

那麼我們是等著AI變得更強大,允許更多的標記(就像摩爾定律,但針對AI的上下文長度與精度)嗎?

不:這就是ANTLR解析器生成工具的用武之地。ANTLR被廣泛用於編譯器和解釋器的開發。這樣我們可以將大型腳本拆分為可以獨立翻譯的小單元。

那麼現在我們是在構建一個轉譯器嗎?嗯,是的也是不。

轉譯器的階段如下:

  1. 詞法分析(標記化)
  2. 語法分析(解析)
  3. 語義分析
  4. 中間表示(IR)生成
  5. 優化(可選)
  6. 目標代碼生成

AI翻譯器的工作原理

步驟1和2是使用ANTLR完成的。

我們使用ANTLR的監聽器介面來抓取單個程序/函數/匿名塊,因為它們是獨立的代碼塊。在程序/函數/匿名塊本身超過上下文窗口的情況下,我們在語句級別進行翻譯(LLM假定在這裡不存在但在之前某處定義的變量/函數調用的存在)。

隨後,步驟 3、4、5 和 6 將交給 LLM(GPT),即盡其所能將每個 PL/SQL 區塊翻譯成 JavaScript 函數,並保持該區塊的操作語義,同時確保語法的準確性。

結果令人驚訝地相當正面:翻譯準確率達到 80-85%。

解決方案的另一個好處是通過專注於一次一個任務來減少幻覺,從而實現更準確的翻譯。

可視化:

如何使用該工具

可執行文件期望以下命令行參數:

  • -u : Capella 登錄電子郵件
  • -p : Capella 登錄密碼
  • -cpaddr: 用於 chat-completions API 的 Capella-url
  • -orgid: 在 chat-completions API 路徑中的組織 ID
  • -cbhost: node-ip: cbcluster 節點
  • -cbuser: cluster-user-name: cbcluster 用戶,通過數據庫訪問添加
  • -cbpassword: cluster-password: cbcluster 密碼,通過數據庫訪問添加
  • -cbport: query-service TLS 端口(通常為 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_LINEDBMS_OUTPUT.NEW_LINE 被翻譯為 console.log(),但 console.log 是瀏覽器 API,並不被 Couchbase 的 JavaScript 評估實現所支持。這一直是個常見的需求,考慮到 Couchbase 事件功能支持 print() 語句,但不支持 JavaScript UDF。

解決方法

用戶被期望創建一個 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() {

 

   // 解決方法 for 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» 從其他包中使用。然而,對於 Couchbase 中的 JavaScript 庫來說,情況並非如此,因為 Couchbase 的 JavaScript 評估實現不支持導入-導出構造。

解決方法

  • 在進行跨資料庫函數調用 «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

      -- 從 math_utils 套件調用 add_numbers 函數

      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}

}

這會出錯,沒關係——我們可以手動修復。

閱讀原因和例外:無法插入類型為value.intValue的非字符串鍵1,啊! 鍵總是預期為字符串:傳遞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}

}

再次,我們發現生成的代碼存在錯誤。根據原因和異常,我們可以確認翻譯的代碼將delete包裹在一個事務中,而這在原始代碼中並不是這樣。

對於事務,桶需要設置持久性,但這需要不止一個數據伺服器;因此出現了錯誤。

這裡的修正是修改代碼以刪除包裹的翻譯。

 

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 中的所有功能都可以通過 JS UDFs 在 Couchbase 中運行。是的,這個例子相當簡單,但你可以理解如何使用這個工具來遷移你的 PL/SQL 腳本,幾乎不需要人工干預。

記住,這個工具應該能幫你完成 80% 的工作:剩下的 20% 還是需要你來完成,但總比自己寫所有代碼要好得多!

未來

這個項目是開源的,所以隨時可以貢獻。一些提出的想法包括:

  1. 能夠批評生成代碼的批評 AI,以確保完全不需要人工干預
  2. 目前,源代碼是運行的代碼;沒有考慮並行性或代碼重用。

並且還要包括之前討論過的限制。

最後,我想感謝Kamini Jagtiani指導我,以及Pierre Regazzoni幫助我測試轉換工具。

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