Antecedentes
¿Qué es PL/SQL?
PL/SQL es un lenguaje procedural diseñado específicamente para incluir declaraciones SQL en su sintaxis. Incluye elementos de lenguaje procedural como condiciones y bucles, y puede manejar excepciones (errores en tiempo de ejecución).
PL/SQL es nativo de las bases de datos de Oracle, y bases de datos como IBM DB2, PostgreSQL y MySQL admiten constructos PL/SQL a través de funciones de compatibilidad.
¿Qué es un JavaScript UDF?
UDF de JavaScript es la alternativa de Couchbase al PL/SQL.
Los UDF de JavaScript aportan la flexibilidad de scripting de propósito general de JavaScript a las bases de datos, permitiendo operaciones dinámicas y potentes en los sistemas de bases de datos modernos y mejorando la flexibilidad en la consulta, procesamiento y transformación de datos.
La mayoría de las bases de datos modernas como Couchbase, MongoDB, Snowflake y Google BigQuery admiten UDF de JavaScript.
El Problema
Un problema común visto por los usuarios que migran de Oracle a Couchbase es trasladar sus scripts de PL/SQL. En lugar de admitir PL/SQL, Couchbase permite a los usuarios construir funciones definidas por el usuario en JavaScript (compatible desde 2021).
Los UDF de JavaScript permiten la manipulación fácil e intuitiva de datos variantes y JSON. Los objetos variantes pasados a un UDF se transforman en tipos y valores nativos de JavaScript.
La consecuencia no intencionada de esto es que la mayoría de los RDBMS que han existido durante los últimos diez años han alentado fuertemente a los desarrolladores a acceder a la base de datos utilizando sus extensiones procedimentales de SQL (PL/pgSQL, PL/SQL), que soportan construcciones procedimentales, integración con SQL, manejo de errores, funciones y procedimientos, disparadores y cursores, o al menos, funciones y procedimientos (como Sakila). Para cualquier intento de alejamiento de ellos, todos sus scripts tendrían que ser reescritos.
Reescribir código es a menudo una tarea tediosa, especialmente al tratar con scripts PL/SQL que han sido escritos en los años 2000 y mantenidos desde entonces. Estos scripts pueden ser complejos, a menudo extendiéndose a miles de líneas, lo que puede ser abrumador para el usuario medio de una empresa.
Solución
El enfoque ideal sería desarrollar un nuevo evaluador de PL/SQL, pero eso requeriría una cantidad excesiva de horas de ingeniería, y para el mismo caso de uso, ya tenemos un JsEvaluator moderno, estable y rápido, así que ¿por qué apoyar otro evaluador?
Esto convierte el problema en un caso de uso perfecto para aprovechar los avances en IA y LLMs que están en curso, y eso es lo que hemos hecho aquí. Hemos utilizado modelos de IA Generativa para automatizar la conversión de PL/SQL a JSUDF.
A partir de junio de 2024, los modelos tienen una ventana de contexto limitada, lo que significa que PL/SQL más largos reciben el error:
“La longitud máxima de contexto de este modelo es de 8192 tokens. Sin embargo, sus mensajes resultaron en <Más-de-8192> tokens. Por favor, reduzca la longitud de los mensajes.”
- Nota que esto es para GPT4.
¿Entonces esperamos a que la IA se vuelva más poderosa y permita más tokens (como la Ley de Moore, pero para la longitud de contexto vs precisión de la IA)?
No: ahí es donde entra en juego la herramienta generadora de analizadores ANTLR. ANTLR es conocido por ser utilizado en el desarrollo de compiladores e intérpretes. De esta manera podemos dividir el gran script en unidades más pequeñas que pueden ser traducidas de manera independiente.
Entonces, ¿estamos construyendo un transpilador? Bueno, sí y no.
Las etapas en un transpilador son las siguientes:
- Análisis léxico (tokenización)
- Análisis sintáctico (parsing)
- Análisis semántico
- Generación de Representación Intermedia (IR)
- Optimización (opcional)
- Generación de código objetivo
Cómo funciona el Traductor de IA
Los pasos 1 y 2 se realizan utilizando ANTLR.
Utilizamos la interfaz Listener de ANTLR para capturar bloques de Procedimientos/Funciones/Anónimos individuales, ya que son bloques de código independientes. En un caso en el que los bloques de Procedimiento/Función/Anónimos mismos excedan la ventana de contexto, traducimos a nivel de declaración (donde el LLM asume la existencia de uso de variables/llamadas a funciones que no están definidas aquí pero en algún lugar anterior).
Posteriormente, los pasos 3, 4, 5 y 6 quedan a cargo del LLM (GPT), es decir, traducir cada bloque PL/SQL en una función de JavaScript lo mejor posible, que también preserve la semántica operativa del bloque y sea sintácticamente precisa.
Los resultados son sorprendentemente bastante positivos: la traducción es precisa en un 80-85%.
Otro beneficio de la solución es que reducimos la alucinación al enfocarnos en una tarea a la vez, lo que resulta en traducciones más precisas.
Para visualizar:
Cómo utilizar la herramienta
El ejecutable espera los siguientes argumentos de línea de comandos:
-u
: Correo de inicio de sesión de Capella-p
: Contraseña de inicio de sesión de Capella-cpaddr
: URL de Capella para la API de completado de chat-orgid
: ID de organización en la ruta de la API de completado de chat-cbhost: node-ip
: Nodocbcluster
-cbuser: cluster-user-name
: Usuario decbcluster
, agregado a través del acceso a la base de datos-cbpassword: cluster-password
: Contraseña decbcluster
, agregada a través del acceso a la base de datos-cbport
: Puerto TLS dequery-service
(generalmente 18093)filepath
, es decir, la ruta al script PL/SQL que se debe traduciroutput->
: En el directorio de salida, se genera un archivo con el mismo nombre que el archivo plsql con el código de la Biblioteca de JavaScript traducido.
Por ejemplo, cat example1.sql:
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');
--comenzar un bloque 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 el script anterior, un bucle externo se ejecuta durante 4 iteraciones, incrementando x en 1000, contador en 1. El bucle interno se ejecuta durante 4 iteraciones, incrementando x en 1, contador en 1.
Ejecutando el traductor en el PL/SQL de prueba:
./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
Salida JsUDF: cat output/example1.js:
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();
}
El script traducido tiene una función nestedloop
(nombre generado por LLM) que hace exactamente lo que especifica el bloque PL/SQL anónimo original.
- Nota: Para funciones/procedimientos con nombre, las funciones de JS traducidas tendrán el mismo nombre. Para bloques anónimos, LLM utiliza un nombre que se le ocurre.
Problemas conocidos
PL/SQL y JS son dos lenguajes diferentes, y la forma en que son compatibles en Oracle y Couchbase no permite una asignación directa y limpia entre los dos. A continuación se presentan algunas limitaciones que descubrimos y las soluciones alternativas que hemos implementado para lo mismo:
1. console.log no es compatible
El procedimiento integrado DBMS_OUTPUT.PUT
y otros dos similares, DBMS_OUTPUT.PUT_LINE
y DBMS_OUTPUT.NEW_LINE
, se traducen a console.log()
, pero console.log es una API del navegador y no es compatible con la implementación de evaluación JavaScript de Couchbase. Esto ha sido una pregunta frecuente, considerando que la función de eventos de Couchbase admite declaraciones print()
pero no en UDFs de JavaScript.
Solución alternativa
Se espera que los usuarios creen un bucket de logging
.
Los registros se insertan como parte de un documento INSERT
en la colección `default`.`default`
. El documento se vería algo así:
{
"udf": «func-name»,
"log": «argument to console.log», // the actual log line
"time": «current ISO time string»
}
El usuario puede consultar sus registros seleccionando logging
:
SELECT * FROM logging WHERE udf= "«func-name»";
SELECT * FROM logging WHERE time BETWEEN "«date1»" AND "«date2»";
Ejemplo:
- Original
BEGIN
DBMS.OUTPUT.PUT("Hello world!");
END;
/
- Traducción
function helloWorld() {
// solución alternativa para console.log("¡Hola 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();
}
Esto ya está implementado en la herramienta.
Para ver el registro:
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. Llamadas de Funciones entre Paquetes
Los Procedimientos/Funciones enumerados en la especificación del paquete son globales y pueden ser utilizados desde otros paquetes a través de «nombre_del_paquete».«procedimiento/función_pública»
. Sin embargo, lo mismo no es cierto para una Biblioteca de JavaScript en Couchbase, ya que las construcciones de importación-exportación no son compatibles con la implementación de evaluación de JavaScript de Couchbase.
Solución alternativa
- En caso de una llamada a función interbiblioteca
«lib_name».«function»()
, se espera que el usuario ya tenga creada la biblioteca referenciada«lib_name»
; puedes verificar esto medianteGET /evaluator/v1/libraries
. - Se espera que la función referenciada
«function»
también esté creada como una UDF global; esto se puede verificar medianteGET /admin/functions_cache
o seleccionando el espacio de clavessystem:functions
. De esta manera, podemos acceder a la función medianten1ql
.
Ejemplo:
Paquete math_utils
CREATE OR REPLACE PACKAGE math_utils AS
-- Función pública para sumar dos 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;
/
paquete show_sum
CREATE OR REPLACE PACKAGE show_sum AS
-- Procedimiento público para mostrar la suma de dos 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
-- Llamando a la función add_numbers del paquete math_utils
v_sum := math_utils.add_numbers(p_num1, p_num2);
-- Mostrando la suma 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 traducido:
function show_sum(a, b) {
var sum_result;
// Solución alternativa para la llamada a función de biblioteca cruzada 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);
}
// reemplazo real para math_utils.add_numbers(a, b)
sum_result = crossfuncres[0];
// solución alternativa para console.log('La suma de ' + a + ' y ' + b + ' es: ' + 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();
}
Es manejado automáticamente por el programa — con una advertencia de que debe ser verificado por un ojo humano.
3. Variables Globales
PL/SQL admite variables globales a nivel de paquete y de sesión, pero las variables globales no son compatibles en JsUDF deliberadamente por diseño, ya que esto causa preocupación por fugas de memoria.
Solución alternativa
La solución sugerida requiere ajustes manuales de la traducción generada. Por ejemplo:
CREATE OR REPLACE PACKAGE global_vars_pkg AS
-- Declaraciones de variables globales
g_counter NUMBER := 0;
g_message VARCHAR2(100) := 'Initial Message';
-- Declaraciones de procedimientos 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
-- Procedimiento para incrementar el contador
PROCEDURE increment_counter IS
BEGIN
g_counter := g_counter + 1;
END increment_counter;
-- Procedimiento para establecer el mensaje global
PROCEDURE set_message(p_message VARCHAR2) IS
BEGIN
g_message := p_message;
END set_message;
-- Procedimiento para mostrar los valores actuales de las variables globales
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;
/
Cualquier función que modifique una variable global debe aceptarla como argumento y devolverla al llamante.
incrementar_contador
:
function increment_counter(counter){
counter = counter + 1;
return counter
}
Cualquier función que solo lea una variable global puede aceptarla como argumento.
mostrar_globales
:
function show_globals(counter, message){
// solución temporal 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();
// solución temporal para console.log(mensaje);
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();
}
Paquete a Biblioteca
Esta sección muestra una conversión de paquete a biblioteca de extremo a extremo utilizando la herramienta.
- Ejemplo de paquete 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;
/
- Traducción:
./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 a insertar un nuevo documento de empleado.
- Crear colección de empleados:
curl -u Administrator:password http://127.0.0.1:8091/pools/default/buckets/test/scopes/testscope/collections -d name=employees
Insertar un empleado
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}
}
Esto genera un error, y está bien — podemos solucionarlo manualmente.
Leer la razón y la excepción: No se puede INSERTAR una clave no string 1 de tipo valor.intValue
, ¡ah! Siempre se espera que la clave sea un string: pasar insertar_empleado("1", "joe", "briggs", 10000)
funcionaría, pero es poco intuitivo esperar que id_empleado
sea un string.
Vamos a modificar el código generado:
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();
}
Y recrear la 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}
}
Intentando insertarlo de nuevo:
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}
}
Actualizar un Empleado
¡Vaya! Hay un error: el empleado 1 no es Joe, es Emily.
Actualicemos al empleado 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 al Empleado
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}
}
Eliminar al Empleado
Emily se fue.
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}
}
Nuevamente, tenemos un error con el código generado. Al ver la razón y la excepción, podemos confirmar que el código traducido encierra delete
en una transacción, lo cual no era el caso en el original.
Para las transacciones, los buckets necesitan tener durabilidad establecida, pero esto requiere más de un servidor de datos; de ahí el error.
La solución aquí es alterar el código para eliminar la traducción 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}
}
Ahora, todas las funciones en el PL/SQL original funcionan en Couchbase a través de UDFs de JS. Sí, el ejemplo es bastante trivial, pero entiendes la idea de cómo usar la herramienta para migrar tus scripts de PL/SQL con poca supervisión manual.
Recuerda que se supone que la herramienta debe llevarte el 80%: el otro 20% aún necesita ser hecho por ti, ¡pero es mucho mejor que escribir todo ese código tú mismo!
El Futuro
Este proyecto es de código abierto, así que siéntete libre de contribuir. Algunas ideas que se nos ocurrieron incluyeron:
- Crítica AI que puede criticar el código generado para asegurar que no se necesite intervención manual en absoluto
- Actualmente, el código fuente es un código que simplemente funciona; no se pensó en paralelismo o reutilización de código.
Y también incluye las limitaciones discutidas anteriormente.
Finalmente, me gustaría agradecer a Kamini Jagtiani por guiarme y a Pierre Regazzoni por ayudarme a probar la herramienta de conversión.
Source:
https://dzone.com/articles/oracle-plsqls-to-couchbase-jsudfs-tool