Oracle PL/SQL에서 Couchbase JavaScript UDF로의 전환을 용이하게 하는 도구

배경
PL/SQL이란?

PL/SQL은 SQL 문을 구문 내에서 포함시키기 위해 특별히 설계된 절차적 언어입니다.

조건문 및 루프와 같은 절차적 언어 요소를 포함하며 예외(런타임 오류)를 처리할 수 있습니다.

PL/SQL은 오라클 데이터베이스에 기본적으로 내장되어 있으며, 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 유형과 값으로 변환됩니다.

이로 인해 발생한 부작용은 지난 열 년 동안 존재해온 대부분의 RDBMS가 SQL의 절차적 확장인 PL/pgSQL, PL/SQL을 사용하여 데이터베이스에 접근하도록 강력히 장려했다는 것이다. 이는 절차적 구조, SQL 통합, 오류 처리, 함수 및 프로시저, 트리거, 커서를 지원하거나 적어도 Sakila와 같은 함수 및 프로시저를 지원한다. 이들을 벗어나려는 모든 시도에 대해, 모든 스크립트를 다시 작성해야 한다.

코드를 다시 작성하는 것은 종종 지루한 작업이며, 특히 2000년대에 작성되고 그 이후 유지보수된 PL/SQL 스크립트를 다룰 때 그렇다. 이러한 스크립트는 복잡할 수 있으며 종종 수천 줄에 이르기 때문에 일반 기업 사용자에게는 압도적일 수 있다.

해결책

이상적인 접근 방식은 전체적으로 새로운 PL/SQL 평가자를 개발하는 것이지만, 그렇게 하려면 과도한 엔지니어링 시간이 필요하며, 동일한 사용 사례에 대해 이미 현대적이고 안정적이며 빠른 JsEvaluator가 있기 때문에 다른 평가자를 지원해야 하는 이유가 없다.

이는 AI 및 LLMs의 지속적인 발전을 활용한 완벽한 사용 사례로 만들었으며, 그것이 우리가 여기서 한 일이다. 우리는 생성 모델을 사용하여 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: 노드 IP: cbcluster 노드
  • -cbuser: 클러스터 사용자 이름: 데이터베이스 액세스를 통해 추가된 cbcluster 사용자
  • -cbpassword: 클러스터 암호: 데이터베이스 액세스를 통해 추가된 cbcluster 암호
  • -cbport: query-service TLS 포트 (보통 18093)
  • filepath, 즉, 번역해야 하는 PL/SQL 스크립트의 경로
  • output->: 출력 디렉토리에 PL/SQL 파일과 동일한 이름을 가진 파일이 생성되어 번역된 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는 두 가지 다른 언어이며 Oracle과 Couchbase에서 지원하는 방식으로 두 언어 간에 깔끔한 직접 매핑이 불가능합니다. 발견한 몇 가지 제한 사항과 동일한 문제에 대한 우회 방법은 다음과 같습니다:

1. console.log은 지원되지 않습니다.

DBMS_OUTPUT.PUT 내장 프로시저와 두 가지 다른 유사한 내장 프로시저, DBMS_OUTPUT.PUT_LINEDBMS_OUTPUT.NEW_LINEconsole.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() {

 

   // 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 라이브러리에서는 import-export 구조가 Couchbase의 JavaScript 평가 구현에서 지원되지 않기 때문에 동일한 것은 아닙니다.

해결책

  • 인터라이브러리 함수 호출의 경우 «lib_name».«function»(), 사용자는 참조된 라이브러리 «lib_name»이 이미 생성되어 있어야 합니다; 이를 GET /evaluator/v1/libraries를 통해 확인할 수 있습니다.
  • 참조된 함수 «function»도 글로벌 UDF로 생성되어 있어야 합니다; 이는 GET /admin/functions_cache 또는 select system:functions keyspace를 통해 확인할 수 있습니다. 이렇게 하면 우리는 n1ql을 통해 함수에 접근할 수 있습니다.

예시:

  • math_utils Package
 

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}

}

이 오류가 발생하며, 괜찮습니다 — 수동으로 수정할 수 있습니다.

이유 및 예외를 읽어봅시다: Cannot INSERT non-string key 1 of type value.intValue, 아! 키는 항상 문자열로 예상됩니다: insert_employee("1", "joe", "briggs", 10000)를 전달하면 문제가 해결되지만, employee_id가 문자열이어야 한다는 것은 직관적이지 않습니다.

생성된 코드를 수정하고 UDF를 다시 만들어봅시다:

 

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();

}

 

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은 Joe가 아니라 Emily입니다. 

직원 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}

}

직원 삭제

Emily가 떠났습니다.

 

 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. 현재 소스 코드는 단순히 작동하는 코드이며, 병렬 처리나 코드 재사용에 대한 생각은 적용되지 않았습니다.

그리고 이전에 논의된 제한 사항도 포함해 주세요.

마지막으로, 저를 안내해 준 카미니 자그티안니와 변환 도구 테스트를 도와준 피에르 레가조니에게 감사의 말씀을 전하고 싶습니다.

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