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型と値に変換されます。

この結果として、過去10年間に存在していたRDBMSの大多数は、手続き型拡張SQL(PL/pgSQL、PL/SQL)を使用してデータベースにアクセスすることを開発者に強く奨励しており、これらは手続き型構文、SQLとの統合、エラーハンドリング、関数と手続き、トリガー、およびカーソルをサポートしています。少なくとも、関数と手続き(サキラのような)を使用する必要があります。これらから離れるための試みを行うには、すべてのスクリプトを再作成する必要があります。

コードの再作成はしばしば面倒な作業であり、特に2000年代に書かれ、その後維持されているPL/SQLスクリプトを扱う際にはそうです。これらのスクリプトは複雑であり、しばしば数千行に及ぶことがあり、平均的な企業ユーザーにとっては圧倒されることがあります。

解決策

理想的なアプローチは、全く新しいPL/SQL評価器を開発することですが、それには過剰な工数が必要となります。同じユースケースに対して、すでに現代的で安定した迅速なJsEvaluatorがあるため、なぜ別の評価器をサポートするのでしょうか?

この問題は、AIとLLMの進展を活用するための完璧なユースケースとなります — それが私たちがここで行ったことです。私たちは生成AIモデルを使用してPL/SQLをJSUDFに自動的に変換しました。

2024年6月現在、モデルは限られたコンテキストウィンドウを持っています。これは、長いPL/SQLがエラーに遭遇することを意味します:

“このモデルの最大コンテキスト長は8192トークンです。しかし、あなたのメッセージは<More-than-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: チャット補完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();

}

翻訳されたスクリプトには、元の匿名PL/SQLブロックが指定することと正確に同じことを行う関数nestedloop(LLMによって生成された名前)が含まれています。

  • 備考: 名前付き関数/プロシージャの場合、翻訳されたJS関数は同じ名前になります。匿名ブロックの場合、LLMは自分で考えた名前を使用します。

既知の問題

PL/SQLとJSは2つの異なる言語であり、OracleとCouchbaseでのサポート方法は、両者の間にクリーンな直接マッピングを許可しません。以下に、私たちが発見したいくつかの制限事項と、それに対して実装したワークアラウンドを示します:

1. console.logはサポートされていません

DBMS_OUTPUT.PUT の組み込みプロシージャと、DBMS_OUTPUT.PUT_LINE および DBMS_OUTPUT.NEW_LINE の2つの類似の組み込み関数は console.log() に翻訳されますが、console.log はブラウザAPIであり、CouchbaseのJavaScript評価実装ではサポートされていません。Couchbaseのイベント機能は print() ステートメントをサポートしていますが、JavaScriptのUDFではサポートされていないため、これは頻繁に求められてきました。

回避策

ユーザーは logging バケットを作成することが期待されています。

ログは、`default`.`default` コレクションへのドキュメント INSERT の一部として挿入されます。ドキュメントは次のようになります:

 

{

   "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」が既に作成されていることが期待されます。これはGET /evaluator/v1/librariesを使用して確認できます。
  • 参照される関数「function」もグローバルUDFとして作成されていることが期待されます。これはGET /admin/functions_cacheまたはsystem:functions keyspaceを選択して確認できます。これにより、n1qlを介して関数にアクセスできます。

例:

  • math_utils パッケージ
 

CREATE OR REPLACE PACKAGE math_utils AS

   -- 2つの数を加算するためのパブリック関数

   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

   -- 2つの数の合計を表示するためのパブリック手続き

   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}

}

これはエラーが発生しますが、それは問題ありません — 手動で修正できます。

理由と例外を読み取る: 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}

}

再度、生成されたコードにエラーがあります。理由と例外を見てみると、翻訳されたコードがdeleteをトランザクションで囲んでいることが確認でき、これは元のものではありませんでした。

トランザクションの場合、バケットには耐久性を設定する必要がありますが、これは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のすべての関数が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