背景
什麼是 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被廣泛用於編譯器和解釋器的開發。這樣我們可以將大型腳本拆分為可以獨立翻譯的小單元。
那麼現在我們是在構建一個轉譯器嗎?嗯,是的也是不。
轉譯器的階段如下:
- 詞法分析(標記化)
- 語法分析(解析)
- 語義分析
- 中間表示(IR)生成
- 優化(可選)
- 目標代碼生成
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:
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 上運行翻譯器:
./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:
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 是瀏覽器 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;
/
- 翻譯
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;
/
- 翻譯:
./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% 還是需要你來完成,但總比自己寫所有代碼要好得多!
未來
這個項目是開源的,所以隨時可以貢獻。一些提出的想法包括:
- 能夠批評生成代碼的批評 AI,以確保完全不需要人工干預
- 目前,源代碼是運行的代碼;沒有考慮並行性或代碼重用。
並且還要包括之前討論過的限制。
最後,我想感謝Kamini Jagtiani指導我,以及Pierre Regazzoni幫助我測試轉換工具。
Source:
https://dzone.com/articles/oracle-plsqls-to-couchbase-jsudfs-tool