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 |
이 모델's 최대 컨텍스트 길이 는 8192 토큰. 그러나, 당신의 메시지 결과 in <자세히 보기-보다-8192> 토큰. 제발 감소 의 길이 의 의 메시지. |
이는 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 |
선언 x 번호 := 0; 카운터 번호 := 0; 시작 FOR i IN 1..4 LOOP x := x + 1000; 카운터 := 카운터 + 1; 삽입 INTO temp 가치 (x, 카운터, '외부 루프에서'); --시작 an 내부 블록 선언 x 번호 := 0; -- 이 는 a local 버전 의 x 시작 FOR i IN 1..4 LOOP x := x + 1; -- 이 증분 의 local x 카운터 := 카운터 + 1; 삽입 INTO temp 가치 (x, 카운터, '내부 루프'); END LOOP; END; END LOOP; 커밋; END; |
위의 스크립트를 간단히 설명합니다:
-
- 외부 루프는 x를 1000씩 증가시키고 카운터를 1씩 증가시키면서 4회 반복 실행됩니다.
- 내부 루프는 x를 1씩 증가시키고 카운터를 1씩 증가시키면서 4회 반복 실행됩니다.
- 외부 루프는 x를 1000씩 증가시키고 카운터를 1씩 증가시키면서 4회 반복 실행됩니다.
테스트 PL/SQL에서 번역기를 실행합니다:
1 |
./plsql-에-jsudf -u "아카펠라-로그인-mailid" -p "아카펠라-로그인-비밀번호" -cpaddr https://api.cloud.couchbase.com -orgid "capella-organisation-id" -cbhost "데이터 노드 호스트 이름" -cbuser "cbcluster 사용자 이름" -cbpassword "cbcluster 비밀번호" -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 |
함수 nestedloop(){ var x = 0; var 카운터 = 0; var 쿼리 시작 = 시작 WORK; 쿼리 시작.닫기(); 에 대한 (var i = 1; i <= 4; i++){ x = x + 1000; 카운터 = 카운터 + 1; var 매개변수 = [x, 카운터]; var 쿼리 = N1QL('INSERT INTO test.testscope.temp VALUES (uuid(),{"val1":$1,"val2":$2,"val3":"in OUTER loop"})',매개변수); 쿼리.닫기(); var x_inner = 0; 에 대한 (var j = 1; j <= 4; j++){ x_inner = x_inner + 1; 카운터 = 카운터 + 1; var params_inner = [x_inner, 카운터]; var 쿼리_내부 = N1QL('INSERT INTO test.testscope.temp VALUES (uuid(),{"val1":$1,"val2":$2,"val3":"내부 루프"})',params_inner); 쿼리_내부.닫기(); } } var 쿼리 커밋 = 커밋 WORK; 쿼리 커밋.닫기(); } |
번역된 스크립트에는 다음과 같은 함수가 있습니다. 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-이름", "log": "인수 에 콘솔.로그", // 실제 로그 라인 "time": "현재 ISO 시간 문자열" } |
사용자는 로깅을 선택하여 자신의 로그를 조회할 수 있습니다:
1 2 |
선택 * FROM 로깅 어디 udf= ""func-name""; 선택 * FROM 로깅 어디 시간 사이 ""date1"" AND ""date2""; |
예시입니다:
오리지널 PL/SQL
1 2 3 |
시작 DBMS.출력.PUT("안녕하세요!"); END; |
JavaScript UDF로 번역됨
1 2 3 4 5 6 7 8 |
함수 헬로월드() { // 콘솔 로그("Hello world!")에 대한 해결 방법; var currentDate = new 날짜(); var utcISOString = currentDate.toISOString(); var 매개변수 = [utcISOString,'anonymousblock1',"안녕하세요!"]; var 로그 쿼리 = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2,"log":$3,"time":$1}, {"expiration": 5*24*60*60 })', 매개변수); 로그 쿼리.닫기(); } |
이는 이미 도구에 구현되어 있습니다.
로그를 보려면 다음과 같이 하세요:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
실행 기능 헬로월드(); "결과": [ null ] 만들기 기본 INDEX 켜기 로깅; "결과": [ ] 선택 * FROM 로깅; "결과": [ {"logging":{"log":"안녕하세요!","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 |
만들기 또는 교체 패키지 수학_유틸 AS -- 공개 함수 에 추가 두 숫자 기능 add_numbers(p_num1 번호, p_num2 번호) 반환 번호; END 수학_유틸; / 만들기 또는 교체 패키지 BODY 수학_유틸 AS 기능 add_numbers(p_num1 번호, p_num2 번호) 반환 번호 IS 시작 반환 p_num1 + p_num2; END add_numbers; END 수학_유틸; / |
show_sum 패키지
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
만들기 또는 교체 패키지 show_sum AS -- 공개 절차 에 디스플레이 의 합계 의 두 숫자 절차 display_sum(p_num1 번호, p_num2 번호); END show_sum; / 만들기 또는 교체 패키지 BODY show_sum AS 절차 display_sum(p_num1 번호, p_num2 번호) IS v_sum 번호; 시작 -- 통화 의 add_numbers 함수 에서 수학_유틸 패키지 v_sum := 수학_유틸.add_numbers(p_num1, p_num2); -- 표시 의 합계 사용 DBMS_OUTPUT.PUT_LINE DBMS_OUTPUT.PUT_LINE('의 합계 || p_num1 || ' 및 ' || p_num2 || ' 는 ' || 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 |
함수 show_sum(a, b) { var 합계_결과; // 크로스 라이브러리 함수 호출에 대한 해결 방법 math_utils.add_numbers(a, b) var 크로스펑크 = N1QL("실행 함수 add_numbers($1,$2)",[a, b]) var 크로스 펀크레스 = [] 에 대한(const doc 의 크로스펑크) { 크로스 펀크레스.push(doc); } // math_utils.add_numbers(a, b)를 실제 대체합니다. 합계_결과 = 크로스 펀크레스[0]; // console.log('' + a + ' 및 ' + b + ' 의 합계는: ' + sum_result)에 대한 해결 방법; var currentDate = new 날짜(); var utcISOString = currentDate.toISOString(); var 매개변수 = [utcISOString,'SHOW_SUM','의 합계 + a + ' 및 ' + b + ': ' + 합계_결과]; var 로그 쿼리 = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2,"log":$3,"time":$1}, {"expiration": 5*24*60*60 })', 매개변수); 로그 쿼리.닫기(); } |
프로그램에 의해 자동으로 처리되며, 사람의 눈으로 확인해야 한다는 경고가 표시됩니다!
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 |
만들기 또는 교체 패키지 global_vars_pkg AS -- 글로벌 변수 선언 g_counter 번호 := 0; g_message VARCHAR2(100) := '초기 메시지'; -- 공개 절차 선언 절차 increment_counter; 절차 set_message(p_message VARCHAR2); 절차 show_global; END global_vars_pkg; / 만들기 또는 교체 패키지 BODY global_vars_pkg AS -- 절차 에 증분 의 카운터 절차 increment_counter IS 시작 g_counter := g_counter + 1; END increment_counter; -- 절차 에 set 의 글로벌 메시지 절차 set_message(p_message VARCHAR2) IS 시작 g_message := p_message; END set_message; -- 절차 에 디스플레이 의 현재 값 의 글로벌 변수 절차 show_global IS 시작 DBMS_OUTPUT.PUT_LINE('g_counter = ' || g_counter); DBMS_OUTPUT.PUT_LINE('g_message = ' || g_message); END show_global; END global_vars_pkg; / |
전역 변수를 수정하는 모든 함수는 이를 인수로 받아 호출자에게 반환해야 합니다.
increment_counter:
1 2 3 4 |
함수 increment_counter(카운터){ 카운터 = 카운터 + 1; 반환 카운터 } |
전역만 읽는 모든 함수는 이를 인수로 받아들일 수 있습니다.
show_global:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
함수 show_global(카운터, 메시지){ // 콘솔 로그(카운터)에 대한 해결 방법; var currentDate = new 날짜(); var utcISOString = currentDate.toISOString(); var 매개변수 = [utcISOString,'SHOW_GLOBALS',couter]; var 로그 쿼리 = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2,"log":$3,"time":$1}, {"expiration": 5*24*60*60 })', 매개변수); 로그 쿼리.닫기(); // console.log(message)에 대한 해결 방법; var currentDate = new 날짜(); var utcISOString = currentDate.toISOString(); var 매개변수 = [utcISOString,'SHOW_GLOBALS',메시지]; var 로그 쿼리 = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2,"log":$3,"time":$1}, {"expiration": 5*24*60*60 })', 매개변수); 로그 쿼리.닫기(); } |
라이브러리로 패키지 보내기
이 섹션에서는 도구를 사용하여 엔드투엔드 패키지에서 라이브러리로 변환하는 방법을 보여 줍니다.
샘플 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 |
만들기 또는 교체 패키지 emp_pkg IS 절차 삽입_직원( p_emp_id IN 직원.emp_id%유형, p_first_name IN 직원.first_name%유형, p_last_이름 IN 직원.last_name%유형, p_급여 IN 직원.급여%유형 ); 절차 update_employee( p_emp_id IN 직원.emp_id%유형, p_first_name IN 직원.first_name%유형, p_last_이름 IN 직원.last_name%유형, p_급여 IN 직원.급여%유형 ); 절차 delete_employee( p_emp_id IN 직원.emp_id%유형 ); 절차 get_employee( p_emp_id IN 직원.emp_id%유형, p_first_name OUT 직원.first_name%유형, p_last_이름 OUT 직원.last_name%유형, p_급여 OUT 직원.급여%유형 ); END emp_pkg; / 만들기 또는 교체 패키지 BODY emp_pkg IS 절차 삽입_직원( p_emp_id IN 직원.emp_id%유형, p_first_name IN 직원.first_name%유형, p_last_이름 IN 직원.last_name%유형, p_급여 IN 직원.급여%유형 ) IS 시작 삽입 INTO 직원 (emp_id, first_name, last_name, 급여) 가치 (p_emp_id, p_first_name, p_last_name, p_salary); END 삽입_직원; 절차 update_employee( p_emp_id IN 직원.emp_id%유형, p_first_name IN 직원.first_name%유형, p_last_이름 IN 직원.last_name%유형, p_급여 IN 직원.급여%유형 ) IS 시작 업데이트 직원 SET first_name = p_first_name, 마지막_이름 = p_last_name, 급여 = p_급여 어디 emp_id = p_emp_id; END update_employee; 절차 delete_employee( p_emp_id IN 직원.emp_id%유형 ) IS 시작 삭제 FROM 직원 어디 emp_id = p_emp_id; END delete_employee; 절차 get_employee( p_emp_id IN 직원.emp_id%유형, p_first_name OUT 직원.first_name%유형, p_last_이름 OUT 직원.last_name%유형, p_급여 OUT 직원.급여%유형 ) IS 시작 선택 first_name, last_name, 급여 INTO p_first_name, p_last_name, p_급여 FROM 직원 어디 emp_id = p_emp_id; END get_employee; END emp_pkg; / |
번역:
1 |
./plsql-에-jsudf -u "아카펠라-로그인-mailid" -p "아카펠라-로그인-비밀번호" -cpaddr https://api.cloud.couchbase.com -orgid "capella-organisation-id" -cbhost "데이터 노드 호스트 이름" -cbuser "cbcluster 사용자 이름" -cbpassword "cbcluster 비밀번호" -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 |
함수 삽입_직원(p_emp_id, p_first_name, p_last_name, p_salary){ var 매개변수 = [p_emp_id, p_first_name, p_last_name, p_salary]; var 쿼리 = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {"emp_id":$1, "first_name":$2, "last_name":$3, "salary":$4})', 매개변수); 쿼리.닫기(); } 함수 update_employee(p_emp_id, p_first_name, p_last_name, p_salary){ var 매개변수 = [p_first_name, p_last_name, p_salary, p_emp_id]; var 쿼리 = N1QL('UPDATE test.testscope.employees SET first_name = $1, last_name = $2, salary = $3 WHERE emp_id = $4', 매개변수); 쿼리.닫기(); } 함수 delete_employee(p_emp_id){ var 쿼리 시작=시작 WORK; var 매개변수 = [p_emp_id]; var 쿼리= N1QL('DELETE FROM test.testscope.employees WHERE emp_id = $1',매개변수); 쿼리.닫기(); var 쿼리 커밋=커밋 WORK; 쿼리 커밋.닫기(); } 함수 get_employee(p_emp_id){ var 쿼리 = N1QL('SELECT first_name, last_name, salary FROM test.testscope.employees WHERE emp_id = $1', [p_emp_id]); var rs = []; 에 대한 (const 행 의 쿼리) { rs.push(행); } 쿼리.닫기(); var p_first_name = rs[0]['first_name']; var p_last_name = rs[0]['last_name']; var p_salary = rs[0]['salary']; 반환 {first_name: p_first_name, last_name: p_last_name, 급여: p_salary}; } |
새 직원 문서를 삽입해 보겠습니다.
직원 컬렉션을 만듭니다:
1 |
curl -u 관리자:비밀번호 http://127.0.0.1:8091/pools/default/버킷/테스트/범위/테스트범위/컬렉션 -d name=employees |
직원을 삽입합니다:
1 2 3 4 5 6 7 8 9 10 |
curl -u 관리자:비밀번호 https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION insert_employee(1, "joe", "briggs", 10000)' -k { "요청ID": "2c0854c1-d221-42e9-af47-b6aa0801a46c", "서명": null, "결과": [ ], "오류": [{"code":10109,"msg":"'insert_employee' 함수 실행 중 오류 발생(blog_test:insert_employee)","reason":{"세부 정보":{"코드":" var query = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {\"emp_id\":$1, \"first_name\":$2, \"last_name\":$3, \"salary\":$4})', params);","예외":{"_레벨":"예외","발신자":"INSERT_SEND:207","code":5070,"key":"execution.insert_key_type_error","메시지":"value.intValue 유형의 문자열이 아닌 키 1을 삽입할 수 없습니다."},"위치":"functions/blog_test.js:5","스택":"(functions/blog_test.js:5:17)에서 삽입_직원""},"type":"JS 코드의 예외"}}], "status": "치명적", "metrics": {"elapsedTime": "104.172666ms","실행 시간": "104.040291ms","resultCount": 0,"결과 크기": 0,"serviceLoad": 2,"errorCount": 1} } |
이 오류가 발생하고 괜찮습니다. 수동으로 수정할 수 있습니다.
그 이유를 읽고 예외입니다: value.intValue 유형의 문자열이 아닌 키 1을 삽입할 수 없습니다., 아! 키는 항상 문자열로 예상됩니다. insert_employee("1", "joe", "briggs", 10000) 가 효과가 있을 것이라고 기대하는 것은 직관적이지 않습니다. employee_id 를 문자열로 설정합니다.
생성된 코드를 변경해 보겠습니다:
1 2 3 4 5 |
함수 삽입_직원(p_emp_id, p_first_name, p_last_name, p_salary){ var 매개변수 = [p_emp_id.toString(), p_emp_id, p_first_name, p_last_name, p_salary]; var 쿼리 = N1QL('INSERT INTO test.testscope.employees VALUES ($1, {"emp_id":$2, "first_name":$3, "last_name":$4, "salary":$5})', 매개변수); 쿼리.닫기(); } |
그리고 UDF를 다시 생성합니다:
1 2 3 4 5 6 7 8 9 |
curl -u 관리자:비밀번호 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) LANGAGE JAVASCRIPT AS "insert_employee" AT "blog_test"' -k { "요청ID": "89df65ac-2026-4f42-8839-b1ce7f0ea2be", "서명": null, "결과": [ ], "status": "성공", "metrics": {"elapsedTime": "27.730875ms","실행 시간": "27.620083ms","resultCount": 0,"결과 크기": 0,"serviceLoad": 2} } |
다시 삽입하려고 합니다:
1 2 3 4 5 6 7 8 9 10 |
curl -u 관리자:비밀번호 https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION insert_employee(1, "joe", "briggs", 10000)' -k { "요청ID": "41fb76bf-a87f-4472-b8ba-1949789ae74b", "서명": null, "결과": [ null ], "status": "성공", "metrics": {"elapsedTime": "62.431667ms","실행 시간": "62.311583ms","resultCount": 1,"결과 크기": 4,"serviceLoad": 2} } |
직원을 업데이트합니다:
쏴! 직원 1이 조가 아니라 에밀리입니다.
직원 1을 업데이트해 보겠습니다.
1 2 3 4 5 6 7 8 9 10 |
curl -u 관리자:비밀번호 https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION update_employee(1, "Emily", "Alvarez", 10000)' -k { "요청ID": "92a0ca70-6d0d-4eb1-bf8d-0b4294ae987d", "서명": null, "결과": [ null ], "status": "성공", "metrics": {"elapsedTime": "100.967708ms","실행 시간": "100.225333ms","resultCount": 1,"결과 크기": 4,"serviceLoad": 2} } |
직원 보기
1 2 3 4 5 6 7 8 9 10 11 |
curl -u 관리자:비밀번호 https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION get_employee(1)' -k { "요청ID": "8f180e27-0028-4653-92e0-606c80d5dabb", "서명": null, "결과": [ {"first_name":"Emily","last_name":"알바레즈","급여":10000} ], "status": "성공", "metrics": {"elapsedTime": "101.995584ms","실행 시간": "101.879ms","resultCount": 1,"결과 크기": 59,"serviceLoad": 2} } |
직원을 삭제합니다:
에밀리가 떠났습니다.
1 2 3 4 5 6 7 8 9 10 11 |
curl -u 관리자:비밀번호 https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION delete_employee(1)' -k { "요청ID": "18539991-3d97-40e2-bde3-6959200791b1", "서명": null, "결과": [ ], "오류": [{"code":10109,"msg":"'delete_employee' 함수 실행 중 오류 발생(blog_test:delete_employee)","reason":{"세부 정보":{"코드":" var querycommit=N1QL('COMMIT WORK;', {}, false); ","예외":{"_레벨":"예외","발신자":"txcouchbase:240","원인":{"원인":{"bucket":"test","컬렉션":"_default","document_key":"_txn:atr-988-#1b0","오류_설명":"내구성 요구 사항을 달성하는 것은 불가능합니다","오류_이름":"내구성 불가능","LAST_CONNECTION_ID":"eda95f8c35df6746/d275e8398a49e515","마지막_발송_발신자":"127.0.0.1:50069","마지막_발송_대상":"127.0.0.1:11210","msg":"내구성 불가능","불투명":7,"범위":"_default","status_code":161},"raise":"실패","다시 시도":false,"롤백":false},"code":17007,"key":"transaction.statement.commit","메시지":"트랜잭션 커밋 문 오류"},"위치":"functions/blog_test.js:29","스택":"에서 삭제_직원 (functions/blog_test.js:29:21)"},"type":"JS 코드의 예외"}}], "status": "치명적", "metrics": {"elapsedTime": "129.02975ms","실행 시간": "128.724ms","resultCount": 0,"결과 크기": 0,"serviceLoad": 2,"errorCount": 1} } |
다시 생성된 코드에 오류가 발생했는데, 그 이유와 예외를 살펴보면 번역된 코드가 트랜잭션에서 삭제를 포함하지만 원본에서는 그렇지 않다는 것을 확인할 수 있습니다.
트랜잭션의 경우 버킷에는 다음이 필요합니다. 내구성 를 설정하려고 했지만 둘 이상의 데이터 서버가 필요하므로 오류가 발생했습니다.
여기서 해결 방법은 둘러싸는 번역을 제거하도록 코드를 변경하는 것입니다:
1 2 3 4 5 |
함수 delete_employee(p_emp_id){ var 매개변수 = [p_emp_id]; var 쿼리= N1QL('DELETE FROM test.testscope.employees WHERE emp_id = $1',매개변수); 쿼리.닫기(); } |
1 2 3 4 5 6 7 8 9 10 |
curl -u 관리자:비밀번호 https://127.0.0.1:18093/query/service -d 'statement=CREATE OR REPLACE FUNCTION delete_employee(p_emp_id) 언어 자바 스크립트 AS "delete_employee" AT "blog_test"' -k { "요청ID": "e7432b82-1af8-4dc4-ad94-c34acea59334", "서명": null, "결과": [ ], "status": "성공", "metrics": {"elapsedTime": "31.129459ms","실행 시간": "31.022ms","resultCount": 0,"결과 크기": 0,"serviceLoad": 2} } |
1 2 3 4 5 6 7 8 9 10 11 |
curl -u 관리자:비밀번호 https://127.0.0.1:18093/query/service -d 'statement=EXECUTE FUNCTION delete_employee(1)' -k { "요청ID": "d440913f-58ff-4815-b671-1a72b75bb7eb", "서명": null, "결과": [ null ], "status": "성공", "metrics": {"elapsedTime": "33.8885ms","실행 시간": "33.819042ms","resultCount": 1,"결과 크기": 4,"serviceLoad": 2} } |
이제 원래 PL/SQL의 모든 함수는 JavaScript UDF를 통해 Couchbase에서 작동합니다. 예, 예제는 매우 사소하지만 도구를 사용하여 수동 감독을 거의 하지 않고도 PL/SQL 스크립트를 마이그레이션하는 방법에 대한 요점을 알 수 있습니다.
이 도구는 80%를 사용하도록 되어 있지만 나머지 20%는 여전히 사용자가 직접 수행해야 하지만 모든 코드를 직접 작성하는 것보다 훨씬 낫다는 것을 기억하세요!
미래
이 프로젝트는 오픈 소스이므로 자유롭게 기여해 주세요. 몇 가지 아이디어가 제안되었습니다:
-
- 생성된 코드를 비평하여 수동 개입이 전혀 필요하지 않도록 하는 비평 AI
- 현재 소스 코드는 작동만 하는 코드이며 병렬 처리나 코드 재사용에 대한 고려는 전혀 없었습니다.
또한 앞서 설명한 제한 사항도 포함됩니다.
리소스
-
- 카우치베이스 랩 깃허브 - PL/SQL에서 JSUDF로
- ANTLR 파서 생성기
마지막으로 저를 안내해준 카미니 자그티아니에게 감사의 말씀을 전하고 싶습니다. 피에르 레가조니 변환 도구를 테스트할 수 있도록 도와주셨습니다.