Uma Ferramenta para Facilitar sua Transição de Oracle PL/SQL para Funções Definidas pelo Usuário em JavaScript no Couchbase

Fundo

O que é PL/SQL?

PL/SQL é uma linguagem procedural projetada especificamente para incorporar instruções SQL dentro de sua sintaxe. Inclui elementos de linguagem procedural, como condições e loops, e pode lidar com exceções (erros em tempo de execução).

PL/SQL é nativo dos bancos de dados Oracle, e bancos de dados como IBM DB2, PostgreSQL e MySQL suportam construções PL/SQL por meio de recursos de compatibilidade.

O que é uma UDF JavaScript?

A UDF JavaScript é a alternativa do Couchbase ao PL/SQL.

A UDF JavaScript traz a flexibilidade de script de propósito geral do JavaScript para bancos de dados, permitindo operações dinâmicas e poderosas em sistemas de bancos de dados modernos e aumentando a flexibilidade na consulta, processamento e transformação de dados.

A maioria dos bancos de dados modernos, como Couchbase, MongoDB, Snowflake e Google BigQuery, suportam UDF JavaScript.

O problema

Um problema comum observado por usuários migrando do Oracle para o Couchbase é a portabilidade de seus scripts PL/SQL. Em vez de suportar PL/SQL, o Couchbase permite que os usuários construam funções definidas pelo usuário em JavaScript (suportado desde 2021).

As UDFs JavaScript permitem uma manipulação fácil e intuitiva de dados variant e JSON. Objetos variant passados para uma UDF são transformados em tipos e valores nativos do JavaScript.

A consequência não intencional disso é que a maioria dos SGBDs que estão em existência nos últimos dez anos incentivaram fortemente os desenvolvedores a acessar o banco de dados usando suas extensões procedurais ao SQL (PL/pgSQL, PL/SQL), que suportam construções procedurais, integração com SQL, tratamento de erros, funções e procedimentos, gatilhos e cursores, ou pelo menos, funções e procedimentos (como Sakila). Para qualquer tentativa de se afastar deles, todos os seus scripts precisariam ser reescritos.

Reescrever código é muitas vezes uma tarefa tediosa, especialmente ao lidar com scripts PL/SQL que foram escritos nos anos 2000 e mantidos desde então. Esses scripts podem ser complexos, muitas vezes se estendendo por milhares de linhas, o que pode ser esmagador para o usuário médio de uma empresa.

Solução

A abordagem ideal seria desenvolver um novo avaliador PL/SQL, mas isso exigiria uma quantidade excessiva de horas de engenharia, e para o mesmo caso de uso, já temos um JsEvaluator moderno, estável e rápido — então por que apoiar outro avaliador?

Isso torna o problema um caso de uso perfeito para aproveitar os avanços contínuos em IA e LLMs — e é isso que fizemos aqui. Usamos modelos de IA Generativa para automatizar a conversão de PL/SQL para JSUDF.

Até junho de 2024, modelos têm uma janela de contexto limitada, o que significa que PL/SQLs mais longos enfrentam o erro:

“O comprimento máximo do contexto deste modelo é de 8192 tokens. No entanto, as suas mensagens resultaram em <Mais-de-8192> tokens. Por favor, reduza o comprimento das mensagens.”

  • Note que isso é para o GPT4. 

Então, esperamos que a IA se torne mais poderosa e permita mais tokens (como a Lei de Moore, mas para o comprimento do contexto da IA em relação à precisão)? 

Não: aí que entra a ferramenta geradora de analisadores ANTLR. ANTLR é conhecido por ser utilizado no desenvolvimento de compiladores e interpretadores. Dessa forma, podemos dividir o grande script em unidades menores que podem ser traduzidas independentemente.

Então, estamos construindo um transpiler? Bem, sim e não.

As etapas em um transpiler são as seguintes:

  1. Análise léxica (tokenização)
  2. Análise sintática (parsing)
  3. Análise semântica
  4. Geração de Representação Intermediária (IR)
  5. Otimização (opcional)
  6. Geração de código-alvo

Como o Tradutor de IA Funciona

As etapas 1 e 2 são feitas usando o ANTLR.

Nós usamos a interface Listener do ANTLR para capturar blocos individuais de Procedimento/Função/Anônimos, pois são blocos de código independentes. No caso em que os blocos de Procedimento/Função/Anônimos em si excedem a janela de contexto, traduzimos a nível de declaração (onde o LLM assume a existência do uso de variáveis/chamadas de função que não estão definidas aqui, mas em algum lugar antes).

Posteriormente, os passos 3, 4, 5 e 6 são deixados para o LLM (GPT), ou seja, traduzir cada bloco PL/SQL em uma função JavaScript da melhor maneira possível, que também preserve a semântica operacional do bloco e seja sintaticamente precisa.

Os resultados são surpreendentemente bastante positivos: a tradução é 80-85% precisa.

Outro benefício da solução é que reduzimos a especulação ao nos concentrarmos em uma tarefa de cada vez, resultando em traduções mais precisas.

Para visualizar:

Como usar a ferramenta

O executável espera os seguintes argumentos da linha de comando:

  • -u : Email de login do Capella
  • -p : Senha de login do Capella
  • -cpaddr: URL do Capella para API de preenchimento de chat
  • -orgid: ID da organização no caminho da API de preenchimento de chat
  • -cbhost: node-ip: nó cbcluster
  • -cbuser: cluster-user-name: usuário cbcluster, adicionado através do acesso ao banco de dados
  • -cbpassword: cluster-password: senha cbcluster, adicionada através do acesso ao banco de dados
  • -cbport: query-service porta TLS (geralmente 18093)
  • filepath, ou seja, caminho para o script PL/SQL que deve ser traduzido
  • output->: No diretório de saída, um arquivo com o mesmo nome que o arquivo plsql é gerado com o código da Biblioteca JavaScript traduzido.

Por exemplo, 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');

       --iniciar um bloco interno 

      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;

Para explicar brevemente o script acima, um loop externo é executado por 4 iterações, incrementando x em 1000, contador em 1. O loop interno é executado por 4 iterações, incrementando x em 1, contador em 1.

Executando o tradutor no PL/SQL de teste:

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

Saída 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();

}

O script traduzido possui uma função nestedloop (nome gerado pelo LLM) que faz exatamente o que o bloco PL/SQL Anônimo original especifica.

  • Observação: Para funções/procedimentos nomeados, as funções JS traduzidas terão o mesmo nome. Para blocos anônimos, o LLM usa um nome que ele cria.

Problemas Conhecidos

PL/SQL e JS são duas linguagens diferentes, e a forma como são suportadas no Oracle e Couchbase não permite um mapeamento direto limpo entre as duas. Abaixo estão algumas limitações que descobrimos e as soluções alternativas que implementamos para as mesmas:

1. console.log não é suportado

O procedimento embutido DBMS_OUTPUT.PUT e outros dois procedimentos semelhantes, DBMS_OUTPUT.PUT_LINE e DBMS_OUTPUT.NEW_LINE, são traduzidos para console.log(), mas console.log é uma API do navegador e não é suportado pela implementação de avaliação JavaScript do Couchbase. Isso tem sido uma solicitação frequente, considerando que a função de eventos do Couchbase suporta declarações print(), mas não em UDFs JavaScript.

Solução alternativa

Espera-se que os usuários criem um bucket de logging.

Os logs são inseridos como parte de um documento INSERT na coleção `default`.`default`. O documento se pareceria com algo assim:

 

{

   "udf": «func-name»,

   "log": «argument to console.log», // the actual log line

   "time": «current ISO time string»

}

O usuário pode visualizar seus logs selecionando logging:

 

SELECT * FROM logging WHERE udf= "«func-name»";

SELECT * FROM logging WHERE time BETWEEN "«date1»" AND "«date2»";

Exemplo:

  • Original
 

BEGIN

   DBMS.OUTPUT.PUT("Hello world!");

END;

/

  • Tradução
JavaScript

 

function helloWorld() {

 

   // solução alternativa para console.log("Olá mundo!");

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

}

Já está implementado na ferramenta.

Para visualizar o log:

 

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. Chamadas de Funções entre Pacotes

Procedimentos/Funções listados na especificação do pacote são globais e podem ser usados a partir de outros pacotes via «nome_do_pacote».«procedimento/função_pública». No entanto, o mesmo não é verdade para uma Biblioteca JavaScript no Couchbase, já que construções de importação-exportação não são suportadas pela implementação de avaliação JavaScript do Couchbase.

Solução alternativa

  • Em caso de uma chamada de função interbiblioteca «lib_name».«function»(), espera-se que o usuário já tenha criado a biblioteca referenciada «lib_name»; você pode verificar isso através de GET /evaluator/v1/libraries.
  • A função referenciada «function» também deve ser criada como uma UDF global; isso pode ser verificado através de GET /admin/functions_cache ou selecionando o espaço de chave system:functions. Desta forma, podemos acessar a função via n1ql.

Exemplo:

  • Pacote math_utils
 

CREATE OR REPLACE PACKAGE math_utils AS

   -- Função pública para adicionar dois números

   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 pacote
 

CREATE OR REPLACE PACKAGE show_sum AS

   -- Procedimento público para exibir a soma de dois números

   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

      -- Chamando a função add_numbers do pacote math_utils

      v_sum := math_utils.add_numbers(p_num1, p_num2);

 

      -- Exibindo a soma usando 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;

/

  • Código traduzido:
 

function show_sum(a, b) {

 var sum_result;



// Solução alternativa para chamada de função entre bibliotecas 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);

}



// substituição real para math_utils.add_numbers(a, b)

    sum_result = crossfuncres[0];



 

// solução alternativa para console.log('A soma de ' + a + ' e ' + b + ' é: ' + 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();

}

É gerenciado automaticamente pelo programa — com um aviso de que deve ser verificado por um ser humano!

3. Variáveis Globais

PL/SQL suporta variáveis globais em nível de pacote e em nível de sessão, mas variáveis globais não são suportadas em JsUDF deliberadamente por design, pois isso causa preocupação com vazamentos de memória.

Solução alternativa

A solução alternativa sugerida requer ajustes manuais na tradução gerada. Por exemplo:

 

   CREATE OR REPLACE PACKAGE global_vars_pkg AS

     -- Declarações de variáveis globais

     g_counter NUMBER := 0;

     g_message VARCHAR2(100) := 'Initial Message';



     -- Declarações de procedimentos públicos

     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



     -- Procedimento para incrementar o contador

     PROCEDURE increment_counter IS

     BEGIN

       g_counter := g_counter + 1;

     END increment_counter;



     -- Procedimento para definir a mensagem global

     PROCEDURE set_message(p_message VARCHAR2) IS

     BEGIN

       g_message := p_message;

     END set_message;



     -- Procedimento para exibir os valores atuais das variáveis globais

     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;

   /

Qualquer função que modifique uma variável global deve aceitá-la como argumento e retorná-la ao chamador.

  • incrementar_contador:
 

function increment_counter(counter){

   counter = counter + 1;

   return counter

}

Qualquer função que apenas leia um global pode aceitá-lo como argumento.

  • mostrar_globais:
 

function show_globals(counter, message){

   // solução alternativa para console.log(contador);

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



   // solução alternativa para console.log(mensagem);

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

}

Pacote para Biblioteca

Esta seção mostra uma conversão de pacote para biblioteca de ponta a ponta usando a ferramenta.

  • Pacote PL/SQL de exemplo:
 

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;

/

  • Tradução:
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

  • Código:
 

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};

}

Vamos inserir um novo documento de funcionário.

  • Criar coleção de funcionários:
 
curl -u  Administrator:password http://127.0.0.1:8091/pools/default/buckets/test/scopes/testscope/collections -d name=employees     

Inserir um Funcionário

 

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}

}

Isso gera um erro, e tudo bem — podemos corrigi-lo manualmente.

Lendo a razão e a exceção: Não é possível INSERIR chave não-string 1 do tipo valor.intValue, ah! Sempre se espera que a chave seja uma string: passar inserir_funcionario("1", "joe", "briggs", 10000) resolveria, mas é pouco intuitivo esperar que o id_funcionario seja uma string.

Vamos alterar o código gerado:

 

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

}

E recriar a 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}

}

Tentando inserir novamente:    

 


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}

}

Atualizar um Funcionário

Opa! Houve um erro: o funcionário 1 não é Joe, é Emily. 

Vamos atualizar o funcionário 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}

}

Ver o Funcionário

 

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}

}

Excluir o Funcionário

Emily saiu.

 

 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}

}

Novamente, há um erro com o código gerado. Ao analisar o motivo e a exceção, podemos confirmar que o código traduzido envolve o delete em uma transação, o que não era o caso no original.

Para transações, os buckets precisam ter a durabilidade definida, mas isso requer mais de um servidor de dados; daí o erro.

A correção aqui é alterar o código para remover a tradução envolvente.

 

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}

}

Agora, todas as funções no PL/SQL original funcionam no Couchbase via JS UDFs. Sim, o exemplo é bastante trivial, mas você entende como usar a ferramenta para migrar seus scripts PL/SQL com pouca supervisão manual.

Lembre-se de que a ferramenta deve te levar 80% do caminho: os outros 20% ainda precisam ser feitos por você, mas é muito melhor do que escrever todo esse código sozinho!

O Futuro

Este projeto é de código aberto, então sinta-se à vontade para contribuir. Algumas ideias que nos foram sugeridas incluem:

  1. IA Crítica que pode criticar o código gerado para garantir que a intervenção manual não seja necessária
  2. Atualmente, o código fonte é apenas um código que funciona; não houve pensamentos em paralelismo ou reutilização de código.

E também incluir as limitações discutidas anteriormente.

Por fim, gostaria de agradecer a Kamini Jagtiani por me orientar e a Pierre Regazzoni por me ajudar a testar a ferramenta de conversão.

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