Инструмент для упрощения перехода от Oracle PL/SQL к Couchbase JavaScript UDF

Фон

Что такое PL/SQL?

PL/SQL – это процедурный язык, специально разработанный для интеграции операторов SQL в своем синтаксисе. Он включает элементы процедурного языка, такие как условия и циклы, и может обрабатывать исключения (ошибки времени выполнения).

PL/SQL является встроенным в базы данных Oracle, а базы данных, такие как IBM DB2, PostgreSQL и MySQL, поддерживают конструкции PL/SQL через совместимые функции.

Что такое JavaScript UDF?

JavaScript UDF – это альтернатива PL/SQL от Couchbase.

JavaScript UDF приносит общую гибкость сценариев JavaScript в базы данных, позволяя выполнять динамические и мощные операции в современных системах управления базами данных и улучшая гибкость в запросах, обработке и преобразовании данных.

Большинство современных баз данных, таких как Couchbase, MongoDB, Snowflake и Google BigQuery, поддерживают Javascript UDF.

Проблема

Одной из распространенных проблем, с которой сталкиваются пользователи при миграции с Oracle на Couchbase, является перенос их скриптов PL/SQL. Вместо поддержки PL/SQL Couchbase позволяет пользователям создавать пользовательские функции на JavaScript (поддерживается с 2021 года).

JavaScript UDF позволяют легко и интуитивно манипулировать вариантными и JSON данными. Объекты варианта, переданные в UDF, преобразуются в собственные типы и значения JavaScript.

Непреднамеренным следствием этого является то, что большинство СУБД, которые существуют уже последние десять лет, категорически поощряют разработчиков обращаться к базе данных с использованием их процедурных расширений к SQL (PL/pgSQL, PL/SQL), которые поддерживают процедурные конструкции, интеграцию с SQL, обработку ошибок, функции и процедуры, триггеры и курсоры, или по крайней мере, функции и процедуры (как в Sakila). Для любой попытки отказаться от них, все их скрипты пришлось бы переписать.

Переписывание кода часто является утомительной задачей, особенно когда речь идет о скриптах PL/SQL, написанных в 2000-х годах и поддерживаемых с тех пор. Эти скрипты могут быть сложными, часто простираются на тысячи строк, что может быть подавляющим для обычного пользователя предприятия.

Решение

Идеальным подходом было бы разработать совершенно новый оценщик PL/SQL, но это потребовало бы чрезмерного количества часов инженерных работ, и для того же случая использования у нас уже есть современный, стабильный и быстрый JsEvaluator — почему же поддерживать еще один оценщик?

Это делает проблему идеальным случаем использования текущих достижений в области искусственного интеллекта и языков моделирования на основе больших моделей — и именно это мы сделали здесь. Мы использовали генеративные модели искусственного интеллекта для автоматизации преобразования PL/SQL в JSUDF.

На июнь 2024 года модели имеют ограниченное окно контекста, что означает, что более длинные PL/SQL-запросы сталкиваются с ошибкой:

“Максимальная длина контекста этой модели составляет 8192 токена. Однако ваши сообщения содержат <более-8192> токенов. Пожалуйста, сократите длину сообщений.”

  • Обратите внимание, что это для GPT4.

Так стоит ли нам ждать, пока ИИ станет мощнее и позволит больше токенов (как закон Мура, но для длины контекста ИИ по сравнению с точностью)?

Нет: здесь на помощь приходит генератор парсеров ANTLR. ANTLR хорошо известен как инструмент для разработки компиляторов и интерпретаторов. Таким образом, мы можем разбить большой скрипт на меньшие единицы, которые могут быть переведены независимо.

Так мы сейчас строим транспайлер? Что ж, да и нет.

Этапы в транспайлере следующие:

  1. Лексический анализ (токенизация)
  2. Синтаксический анализ (парсинг)
  3. Семантический анализ
  4. Генерация промежуточного представления (IR)
  5. Оптимизация (по желанию)
  6. Генерация целевого кода

Как работает ИИ-переводчик

Этапы 1 и 2 выполняются с использованием ANTLR.

Мы используем интерфейс Listener ANTLR, чтобы захватить отдельные блоки Процедуры/Функции/Анонимные блоки, так как это независимые блоки кода. В случае, если сами блоки Процедуры/Функции/Анонимные блоки превышают окно контекста, мы переводим на уровне операторов (где LLM предполагает наличие использования переменных/вызовов функций, которые не определены здесь, но где-то ранее).

В дальнейшем шаги 3, 4, 5 и 6 оставлены для LLM (GPT), т.е. перевод каждого блока PL/SQL в функцию JavaScript наилучшим образом, сохраняя операционную семантику блока и обеспечивая синтаксическую точность.

Результаты удивительно положительные: перевод точен на 80-85%.

Еще одно преимущество решения заключается в том, что мы уменьшаем галлюцинации, сосредоточившись на одной задаче за раз, что приводит к более точным переводам.

Для визуализации:

Как использовать инструмент

Исполняемый файл ожидает следующие аргументы командной строки:

  • -u : Email для входа в Capella
  • -p : Пароль для входа в Capella
  • -cpaddr: URL Capella для API chat-completions
  • -orgid: Идентификатор организации в пути API chat-completions
  • -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();

}

Переведенный скрипт имеет функцию 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 браузера и не поддерживается реализацией оценки JavaScript Couchbase. Это часто запрашивается, учитывая, что функция событий 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». Однако это не относится к библиотеке JavaScript в Couchbase, так как конструкции импорта-экспорта не поддерживаются реализацией оценки JavaScript Couchbase.

Обходной путь

  • В случае вызова функции между библиотеками «lib_name».«function»() ожидается, что у пользователя уже создана ссылочная библиотека «lib_name»; вы можете проверить это с помощью GET /evaluator/v1/libraries.
  • Также ожидается, что вызываемая функция «function» будет создана как глобальная UDF; это можно проверить с помощью GET /admin/functions_cache или выбрав пространство ключей system:functions. Таким образом мы можем обращаться к функции через 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

      -- Вызов функции add_numbers из пакета math_utils

      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 в транзакцию, чего не было в оригинале.

Для транзакций ведра должны иметь долговечность, но это требует более одного сервера данных; отсюда и ошибка.

Исправление здесь состоит в том, чтобы изменить код, удалив заключающую транзакцию.

 

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 работают в Couchbase через JS UDF. Да, пример довольно тривиален, но вы понимаете суть использования инструмента для миграции ваших скриптов PL/SQL с минимальным ручным контролем.

Помните, что инструмент должен выполнить 80% работы: остальные 20% все еще нужно выполнить вам, но это намного лучше, чем писать весь этот код самостоятельно!

Будущее

Этот проект с открытым исходным кодом, так что не стесняйтесь вносить свой вклад. Некоторые идеи, которые нам предложили, включали:

  1. Критик AI, который может критиковать сгенерированный код, чтобы гарантировать, что ручное вмешательство вообще не требуется
  2. В настоящее время исходный код — это просто работающий код; мысли о параллелизме или повторном использовании кода не были использованы.

И также учтите ранее обсужденные ограничения.

Наконец, я бы хотел поблагодарить Камини Джагтиани за руководство и Пьера Регаццони за помощь в тестировании инструмента конвертации.

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