PL/SQL이란 무엇인가요?
PL/SQL은 구문 내에서 SQL 문을 수용하도록 특별히 설계된 절차적 언어입니다. 조건 및 루프와 같은 절차적 언어 요소를 포함하며 예외(런타임 오류)를 처리할 수 있습니다.
PL/SQL은 오라클 데이터베이스에 기본으로 제공되며, IBM DB2, PostgreSQL, MySQL 등의 데이터베이스는 호환성 기능을 통해 PL/SQL 구성을 지원합니다.
자바스크립트 UDF란 무엇인가요?
JavaScript UDF는 PL/SQL을 대체하는 Couchbase의 대안입니다.
JavaScript UDF는 JavaScript의 범용 스크립팅 유연성을 데이터베이스에 도입하여 최신 데이터베이스 시스템에서 동적이고 강력한 작업을 수행할 수 있게 하고 데이터 쿼리, 처리 및 변환의 유연성을 향상시킵니다.
Couchbase, 몽고DB, 스노우플레이크, 구글 빅쿼리 등 대부분의 최신 데이터베이스는 자바스크립트 UDF를 지원합니다.
문제
오라클에서 카우치베이스로 마이그레이션하는 사용자들이 흔히 겪는 문제는 PL/SQL 스크립트를 포팅하는 것입니다. PL/SQL을 지원하는 대신 Couchbase에서는 사용자가 JavaScript로 사용자 정의 함수를 구성할 수 있습니다(2021년부터 지원됨).
JavaScript UDF를 사용하면 변형 및 JSON 데이터를 쉽고 직관적으로 조작할 수 있습니다. UDF로 전달된 변형 객체는 네이티브 JavaScript 유형과 값으로 변환됩니다.
이로 인한 의도치 않은 결과는 지난 10년 동안 존재해 온 대부분의 RDBMS가 개발자에게 절차적 구조, SQL과의 통합, 오류 처리, 함수 및 프로시저, 트리거, 커서를 지원하는 SQL에 대한 절차적 확장(PL/pgSQL, PL/SQL) 또는 최소한 함수 및 프로시저(Sakila 같은)를 사용하여 데이터베이스에 액세스하도록 강력히 권장해 왔다는 점입니다. 이러한 방식에서 벗어나려면 모든 스크립트를 다시 작성해야 합니다.
특히 2000년대 작성되어 그 이후로 유지 관리되고 있는 PL/SQL 스크립트를 다룰 때 코드를 다시 작성하는 것은 지루한 작업인 경우가 많습니다. 이러한 스크립트는 복잡하고 수천 줄에 이르는 경우가 많기 때문에 일반 기업 사용자에게는 부담스러울 수 있습니다.
솔루션
이상적인 접근 방식은 완전히 새로운 PL/SQL 평가기를 개발하는 것이지만, 그렇게 하려면 과도한 엔지니어링 시간이 필요하고 동일한 사용 사례에 대해 이미 현대적이고 안정적이며 빠른 JSEvaluator가 있는데 왜 다른 평가기를 지원해야 할까요?
따라서 이 문제는 AI와 LLM의 지속적인 발전을 활용하기에 완벽한 사용 사례입니다. 이것이 바로 우리가 여기서 한 일입니다. 유니티는 제너레이티브 AI 모델을 사용하여 PL/SQL을 JSUDF로 변환하는 작업 자동화.
2024년 6월 기준, 모델에는 제한된 컨텍스트 창이 있습니다., w즉, PL/SQL이 오류에 더 오래 걸리게 됩니다:
|
1 |
This model's maximum context length is 8192 tokens. However, your messages resulted in <More-than-8192> tokens. Please reduce the length of the messages. |
이는 GPT4용이라는 점에 유의하세요.
그렇다면 AI가 더 강력해지고 더 많은 토큰을 허용할 때까지 기다려야 할까요(무어의 법칙처럼 AI의 컨텍스트 길이 대 정밀도)?
아니요, 여기는 ANTLR, a 파서 생성기 도구가 들어옵니다. ANTLR은 컴파일러와 인터프리터 개발에 사용되는 것으로 잘 알려져 있습니다. 이렇게 하면 큰 스크립트를 독립적으로 번역할 수 있는 작은 단위로 나눌 수 있습니다.
그렇다면 이제 우리는 트랜스파일러? 글쎄요, 맞기도 하고 아니기도 합니다.
트랜스파일러의 스테이지:
-
- 어휘 분석(토큰화)
- 구문 분석(구문 분석)
- 시맨틱 분석
- 중간 표현(IR) 생성
- 최적화(선택 사항)
- 타겟 코드 생성
AI 번역기의 작동 방식
위의 1,2단계는 ANTLR을 사용하여 수행됩니다. ANTLR의 리스너 인터페이스를 사용하여 다음을 수행합니다. 개별 프로시저/함수/익명 블록을 가져옵니다.와 같이 독립적인 코드 블록으로 변환합니다. 프로시저/함수/익명 블록 자체가 컨텍스트 창을 초과하는 경우에는 문 수준에서 번역합니다(여기에는 정의되지 않았지만 이전 어딘가에 변수/함수 호출의 사용이 존재한다고 LLM이 가정하는 경우).
그 후 3, 4, 5, 6단계, 즉 각 PL/SQL 블록을 블록의 작동 의미를 보존하고 구문적으로 정확한 JavaScript 함수로 최대한 변환하는 작업은 LLM(예: GPT)에 맡깁니다.
결과는 놀랍게도 매우 긍정적이었습니다. 번역 정확도는 80-85%입니다.
이 솔루션의 또 다른 장점은 한 번에 한 가지 작업에 집중함으로써 잡념을 줄여 보다 정확한 번역을 할 수 있다는 점입니다.
시각화하려면:
도구 사용 방법
-
- 다음에서 실행 파일을 다운로드하세요. 카우치베이스 랩 깃허브 를 클릭하고 README.
- 다음에서 실행 파일을 다운로드하세요. 카우치베이스 랩 깃허브 를 클릭하고 README.
실행 파일은 다음과 같은 명령줄 인수를 기대합니다:
-u: 카펠라 로그인 이메일
-p: 카펠라 로그인 비밀번호
-cpaddr: 채팅-완성 API용 카펠라 URL
-orgid: 채팅 완료 API 경로의 조직 ID
-cbhost: 노드-ip: cbcluster 노드
-cbuser: 클러스터-사용자 이름: 데이터베이스 액세스를 통해 추가된 cbcluster 사용자
-cbpassword: 클러스터-암호: 데이터베이스 액세스를 통해 추가된 cb클러스터 암호
-cbport: 쿼리 서비스 tls 포트: 보통 18093
파일 경로 , 즉 번역해야 하는 PL/SQL 스크립트의 경로입니다.
출력-> 출력 디렉터리에서 이름이 같은 파일이 plsql 파일은 번역된 자바스크립트 라이브러리 코드로 생성됩니다.
예시입니다:
cat example1.sql
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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'); --start an inner block 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; |
위의 스크립트를 간단히 설명합니다:
-
- 외부 루프는 x를 1000씩 증가시키고 카운터를 1씩 증가시키면서 4회 반복 실행됩니다.
- 내부 루프는 x를 1씩 증가시키고 카운터를 1씩 증가시키면서 4회 반복 실행됩니다.
- 외부 루프는 x를 1000씩 증가시키고 카운터를 1씩 증가시키면서 4회 반복 실행됩니다.
테스트 PL/SQL에서 번역기를 실행합니다:
|
1 |
./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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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에서 지원되는 방식은 두 언어 간에 깔끔한 직접 매핑을 허용하지 않습니다.
다음은 저희가 발견한 몇 가지 제한 사항과 이를 해결하기 위해 구현한 해결 방법입니다:
콘솔 로그가 지원되지 않습니다.
dbms_output.put 기본 제공 프로시저 및 기타 2개의 유사한 기본 제공 프로시저가 있습니다, dbms_output.put_line 그리고 dbms_output.new_line 은 다음과 같이 번역됩니다. console.log()를 사용할 수 있지만 console.log는 브라우저 API이며 Couchbase의 JavaScript 평가 구현에서는 지원되지 않습니다. Couchbase 이벤트 함수가 다음을 지원한다는 점을 고려할 때 이 문제는 자주 요청되어 왔습니다. print() 문에는 있지만 자바스크립트 UDF에는 없습니다.
해결 방법:
사용자는 로깅 버킷.
로그는 다음에 삽입하는 문서의 일부로 삽입됩니다. 기본값.기본값 컬렉션에 추가합니다. 문서는 다음과 같이 보일 것입니다:
|
1 2 3 4 5 |
{ "udf": «func-name», "log": «argument to console.log», // the actual log line "time": «current ISO time string» } |
사용자는 로깅을 선택하여 자신의 로그를 조회할 수 있습니다:
|
1 2 |
SELECT * FROM logging WHERE udf= "«func-name»"; SELECT * FROM logging WHERE time BETWEEN "«date1»" AND "«date2»"; |
예시입니다:
오리지널 PL/SQL
|
1 2 3 |
BEGIN DBMS.OUTPUT.PUT("Hello world!"); END; |
JavaScript UDF로 번역됨
|
1 2 3 4 5 6 7 8 |
function helloWorld() { // workaround 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(); } |
이는 이미 도구에 구현되어 있습니다.
로그를 보려면 다음과 같이 하세요:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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. 교차 패키지 함수 호출
패키지 사양에 나열된 프로시저/함수는 전역이며, 다른 패키지에서 다음을 통해 사용할 수 있습니다. "패키지_이름"."공개_프로시저/함수". 그러나 가져오기-내보내기 구문은 CouchBase의 JavaScript 평가 구현에서 지원되지 않으므로 CouchBase의 JavaScript 라이브러리에서도 마찬가지입니다.
해결 방법:
라이브러리 간 함수 호출의 경우 "lib_name"."function"()를 사용하면 사용자는 참조된 라이브러리가 있어야 합니다. "lib_name" 이미 생성된 경우 다음을 통해 확인할 수 있습니다. GET /evaluator/v1/libraries
참조된 함수 "function" 또한 글로벌 UDF로 생성될 것으로 예상되며, 이는 GET /admin/functions_cache를 통해 확인하거나 system:functions 키스페이스를 선택하여 확인할 수 있습니다. 이렇게 하면 SQL++/N1QL을 통해 함수에 액세스할 수 있습니다.
예시입니다:
수학_유틸 패키지
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CREATE OR REPLACE PACKAGE math_utils AS -- Public function to add two numbers 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 패키지
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
CREATE OR REPLACE PACKAGE show_sum AS -- Public procedure to display the sum of two numbers 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 -- Calling the add_numbers function from math_utils package v_sum := math_utils.add_numbers(p_num1, p_num2); -- Displaying the sum using 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; / |
번역된 코드:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function show_sum(a, b) { var sum_result; // Workaround for cross library function call 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); } // actual replacement for math_utils.add_numbers(a, b) sum_result = crossfuncres[0]; // workaround for 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에서 의도적으로 지원되지 않습니다.
제안된 해결 방법을 사용하려면 생성된 번역을 수동으로 조정해야 합니다. 예를 들어
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
CREATE OR REPLACE PACKAGE global_vars_pkg AS -- Global variable declarations g_counter NUMBER := 0; g_message VARCHAR2(100) := 'Initial Message'; -- Public procedure declarations 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 to increment the counter PROCEDURE increment_counter IS BEGIN g_counter := g_counter + 1; END increment_counter; -- Procedure to set the global message PROCEDURE set_message(p_message VARCHAR2) IS BEGIN g_message := p_message; END set_message; -- Procedure to display the current values of global variables 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:
|
1 2 3 4 |
function increment_counter(counter){ counter = counter + 1; return counter } |
전역만 읽는 모든 함수는 이를 인수로 받아들일 수 있습니다.
show_global:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function show_globals(counter, message){ // workaround for 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(); // workaround for 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 패키지:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
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; / |
번역:
|
1 |
./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 |
Code:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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}; } |
새 직원 문서를 삽입해 보겠습니다.
직원 컬렉션을 만듭니다:
|
1 |
curl -u Administrator:password https://127.0.0.1:8091/pools/default/buckets/test/scopes/testscope/collections -d name=employees |
직원을 삽입합니다:
|
1 2 3 4 5 6 7 8 9 10 |
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 를 문자열로 설정합니다.
생성된 코드를 변경해 보겠습니다:
|
1 2 3 4 5 |
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를 다시 생성합니다:
|
1 2 3 4 5 6 7 8 9 |
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} } |
다시 삽입하려고 합니다:
|
1 2 3 4 5 6 7 8 9 10 |
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을 업데이트해 보겠습니다.
|
1 2 3 4 5 6 7 8 9 10 |
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} } |
직원 보기
|
1 2 3 4 5 6 7 8 9 10 11 |
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} } |
직원을 삭제합니다:
에밀리가 떠났습니다.
|
1 2 3 4 5 6 7 8 9 10 11 |
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} } |
다시 생성된 코드에 오류가 발생했는데, 그 이유와 예외를 살펴보면 번역된 코드가 트랜잭션에서 삭제를 포함하지만 원본에서는 그렇지 않다는 것을 확인할 수 있습니다.
트랜잭션의 경우 버킷에는 다음이 필요합니다. 내구성 를 설정하려고 했지만 둘 이상의 데이터 서버가 필요하므로 오류가 발생했습니다.
여기서 해결 방법은 둘러싸는 번역을 제거하도록 코드를 변경하는 것입니다:
|
1 2 3 4 5 |
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(); } |
|
1 2 3 4 5 6 7 8 9 10 |
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} } |
|
1 2 3 4 5 6 7 8 9 10 11 |
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의 모든 함수는 JavaScript UDF를 통해 Couchbase에서 작동합니다. 예, 예제는 매우 사소하지만 도구를 사용하여 수동 감독을 거의 하지 않고도 PL/SQL 스크립트를 마이그레이션하는 방법에 대한 요점을 알 수 있습니다.
이 도구는 80%를 사용하도록 되어 있지만 나머지 20%는 여전히 사용자가 직접 수행해야 하지만 모든 코드를 직접 작성하는 것보다 훨씬 낫다는 것을 기억하세요!
미래
이 프로젝트는 오픈 소스이므로 자유롭게 기여해 주세요. 몇 가지 아이디어가 제안되었습니다:
-
- 생성된 코드를 비평하여 수동 개입이 전혀 필요하지 않도록 하는 비평 AI
- 현재 소스 코드는 작동만 하는 코드이며 병렬 처리나 코드 재사용에 대한 고려는 전혀 없었습니다.
또한 앞서 설명한 제한 사항도 포함됩니다.
리소스
-
- 카우치베이스 랩 깃허브 - PL/SQL에서 JSUDF로
- ANTLR 파서 생성기
마지막으로 저를 안내해준 카미니 자그티아니에게 감사의 말씀을 전하고 싶습니다. 피에르 레가조니 변환 도구를 테스트할 수 있도록 도와주셨습니다.