一个帮助您从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 并不支持 PL/SQL,而是让用户用 JavaScript 构建用户定义的函数(自 2021 年起支持)。

JavaScript UDF 允许对变体和 JSON 数据进行简单直观的操作。传递给 UDF 的变体对象会被转换为原生 JavaScript 类型和值。

这样做的意外后果是,过去十年中存在的大多数关系数据库管理系统强烈鼓励开发人员使用它们的SQL过程扩展(如PL/pgSQL、PL/SQL)访问数据库,这些过程扩展支持过程构造、与SQL集成、错误处理、函数和过程、触发器和游标,或者至少支持函数和过程(如Sakila)。任何试图远离它们的尝试都需要重写所有脚本。

重写代码通常是一项繁琐的任务,特别是在处理自2000年以来编写并一直维护的PL/SQL脚本时。这些脚本可能很复杂,通常延伸到数千行,这对于普通企业用户来说可能会很压倒。

解决方案

理想的方法是开发一个全新的PL/SQL评估器,但这将需要大量的工程小时数,而对于相同的用例,我们已经有了一个现代、稳定且快速的JsEvaluator — 那么为什么要支持另一个评估器呢?

这使得问题成为利用人工智能和LLM不断进步的理想用例 — 这就是我们在这里所做的。我们利用生成式人工智能模型自动将PL/SQL转换为JSUDF。

截至2024年6月,模型有一个有限的上下文窗口,这意味着较长的PL/SQL会出现错误:

“该模型的最大上下文长度为8192个标记。但是,您的消息导致了个标记。请缩短消息的长度。”

  • 请注意,这是针对GPT4的。

那么,我们是不是要等待人工智能变得更强大,允许更多标记(就像摩尔定律,但是针对人工智能的上下文长度与精度)?

不是:这就是ANTLR解析器生成工具发挥作用的地方。ANTLR被广泛用于编译器和解释器开发。这样,我们可以将大型脚本分解为可以独立翻译的较小单元。

那么现在我们正在构建转译器吗?是的和不是。

转译器中的阶段如下:

  1. 词法分析(标记化)
  2. 句法分析(解析)
  3. 语义分析
  4. 中间表示(IR)生成
  5. 优化(可选)
  6. 目标代码生成

AI翻译器的工作原理

第1步和第2步使用ANTLR完成。

我们使用ANTLR的Listener接口来获取单独的过程/函数/匿名块,因为它们是独立的代码块。如果过程/函数/匿名块本身超过上下文窗口,我们将在语句级别进行翻译(LLM假定在此之前的某个地方存在变量/函数调用的使用,但这里没有定义)。

随后,第3、4、5和6步留给了LLM(GPT),即尽其所能地将每个PL/SQL块翻译为JavaScript函数,同时保持块的操作语义并确保语法准确。

结果令人惊讶地相当积极:翻译的准确率为80-85%。

该解决方案的另一个好处是通过一次专注于一个任务来减少幻觉,从而得到更准确的翻译。

可视化:

如何使用该工具

可执行文件期望以下命令行参数:

  • -u : Capella 登录邮箱
  • -p : Capella 登录密码
  • -cpaddr: 用于聊天完成 API 的 Capella-url
  • -orgid: 聊天完成 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 UDFs中不支持。

解决方法

用户应该创建一个 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» 从其他包中使用。然而,在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}

}

这会报错,但没关系——我们可以手动修复。

阅读原因和异常:无法插入非字符串键 1,类型为 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}

}

再次,我们在生成的代码中遇到了错误。查看原因和异常,我们可以确认翻译后的代码将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 UDF在Couchbase中工作。是的,这个例子相当琐碎,但你明白了如何使用这个工具以较少的人工监督迁移你的PL/SQL脚本的要点。

请记住,这个工具应该为你完成80%的工作:剩下的20%仍然需要你自己完成,但比自己编写所有代码要好得多!

未来

这个项目是开源的,所以欢迎贡献。一些建议包括:

  1. 可以批评生成代码的批判AI,以确保完全不需要人工干预
  2. 目前,源代码是能够正常工作的代码;没有考虑并行性或代码重用。

并且还包括之前讨论的限制。

最后,我要感谢Kamini Jagtiani的指导和Pierre Regazzoni的帮助,感谢他帮助我测试转换工具。

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