Sfondo
Cos’è PL/SQL?
PL/SQL è un linguaggio procedurale progettato specificamente per integrare le istruzioni SQL nella propria sintassi. Include elementi del linguaggio procedurale come condizioni e cicli e può gestire eccezioni (errori in fase di esecuzione).
PL/SQL è nativo dei database Oracle e database come IBM DB2, PostgreSQL e MySQL supportano costrutti PL/SQL attraverso funzionalità di compatibilità.
Cos’è una UDF JavaScript?
UDF JavaScript è l’alternativa di Couchbase a PL/SQL.
UDF JavaScript porta la flessibilità dello scripting generale di JavaScript ai database, consentendo operazioni dinamiche e potenti su moderni sistemi di database e migliorando la flessibilità nella query, elaborazione e trasformazione dei dati.
La maggior parte dei database moderni come Couchbase, MongoDB, Snowflake e Google BigQuery supportano le UDF JavaScript.
Il Problema
Un problema comune riscontrato dagli utenti che migrano da Oracle a Couchbase è il porting dei loro script PL/SQL. Invece di supportare PL/SQL, Couchbase consente agli utenti di costruire funzioni definite dall’utente in JavaScript (supportato dal 2021).
Le UDF JavaScript consentono una manipolazione facile e intuitiva dei dati varianti e JSON. Gli oggetti varianti passati a una UDF vengono trasformati in tipi e valori nativi di JavaScript.
La conseguenza non intenzionale di ciò è che la maggior parte dei RDBMS che sono stati in esistenza negli ultimi dieci anni ha fortemente incoraggiato gli sviluppatori ad accedere al database utilizzando le loro estensioni procedurali a SQL (PL/pgSQL, PL/SQL), che supportano costrutti procedurali, integrazione con SQL, gestione degli errori, funzioni e procedure, trigger e cursori, o almeno, funzioni e procedure (come Sakila). Per qualsiasi tentativo di allontanarsi da esse, tutti i loro script dovrebbero essere riscritti.
Riscrivere codice è spesso un compito noioso, specialmente quando si tratta di script PL/SQL che sono stati scritti negli anni 2000 e mantenuti da allora. Questi script possono essere complessi, spesso estendendosi a migliaia di righe, il che può essere opprimente per l’utente medio dell’impresa.
Soluzione
L’approccio ideale sarebbe sviluppare un nuovo evaluatore PL/SQL, ma ciò richiederebbe un’eccessiva quantità di ore di ingegneria, e per lo stesso caso d’uso, abbiamo già un JsEvaluator moderno, stabile e veloce — quindi perché supportare un altro evaluatore?
Questo rende il problema un caso d’uso perfetto per sfruttare i progressi in corso nell’AI e negli LLM — ed è ciò che abbiamo fatto qui. Abbiamo utilizzato modelli di AI Generativa per automatizzare la conversione di PL/SQL in JSUDF.
A partire da giugno 2024, i modelli hanno una finestra di contesto limitata, il che significa che i PL/SQL più lunghi ricevono l’errore:
“La lunghezza massima del contesto di questo modello è di 8192 token. Tuttavia, i tuoi messaggi hanno prodotto più di 8192 token. Si prega di ridurre la lunghezza dei messaggi.”
- Si noti che questo riguarda GPT4.
Allora dobbiamo aspettare che l’IA diventi più potente e permetta più token (come la legge di Moore, ma per la lunghezza del contesto dell’IA rispetto alla precisione)?
No: è qui che entra in gioco lo strumento generatore di parser ANTLR. ANTLR è ben noto per essere utilizzato nello sviluppo di compilatori e interpreti. In questo modo possiamo suddividere il grande script in unità più piccole che possono essere tradotte indipendentemente.
Quindi stiamo costruendo un transpiler? Beh, sì e no.
Le fasi in un transpiler sono le seguenti:
- Analisi lessicale (tokenizzazione)
- Analisi sintattica (parsing)
- Analisi semantica
- Generazione di Rappresentazione Intermedia (IR)
- OTTimizzazione (opzionale)
- Generazione del codice target
Come funziona il Traduttore IA
Le fasi 1 e 2 vengono eseguite utilizzando ANTLR.
Utilizziamo l’interfaccia Listener di ANTLR per acquisire singoli blocchi di Procedure/Funzioni/Anonimi, poiché sono blocchi di codice indipendenti. Nel caso in cui i blocchi di Procedure/Funzioni/Anonimi stessi superino la finestra di contesto, traduciamo a livello di istruzione (dove il LLM assume l’esistenza dell’uso di variabili/chiamate di funzioni che non sono qui definite ma da qualche parte prima).
Successivamente, i passaggi 3, 4, 5 e 6 sono lasciati al LLM (GPT), cioè tradurre ciascun blocco PL/SQL in una funzione JavaScript nel migliore dei modi che preservi anche la semantica operativa del blocco ed sia sintatticamente corretta.
I risultati sono sorprendentemente abbastanza positivi: la traduzione è accurata all’80-85%.
Un altro beneficio della soluzione è che riduciamo l’errore concentrandoci su un compito alla volta, ottenendo traduzioni più precise.
Per visualizzare:
Come utilizzare lo strumento
L’eseguibile si aspetta i seguenti argomenti da riga di comando:
-u
: Email di accesso a Capella-p
: Password di accesso a Capella-cpaddr
: URL di Capella per l’API di completamento chat-orgid
: ID dell’organizzazione nel percorso dell’API di completamento chat-cbhost: node-ip
: nodocbcluster
-cbuser: cluster-user-name
: utentecbcluster
, aggiunto tramite accesso al database-cbpassword: cluster-password
: passwordcbcluster
, aggiunta tramite accesso al database-cbport
:query-service
porta TLS (di solito 18093)filepath
, cioè il percorso dello script PL/SQL che deve essere tradottooutput->
: Nella directory di output, viene generato un file con lo stesso nome del file plsql con il codice della libreria JavaScript tradotto
Per esempio, 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');
--avvia un blocco 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;
Per spiegare brevemente lo script sopra, un ciclo esterno viene eseguito per 4 iterazioni, incrementando x di 1000, il contatore di 1. Il ciclo interno viene eseguito per 4 iterazioni, incrementando x di 1, il contatore di 1.
Eseguendo il traduttore sul PL/SQL di test:
./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
Output 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();
}
Lo script tradotto ha una funzione nestedloop
(nome generato da LLM) che fa esattamente ciò che specifica il blocco PL/SQL anonimo originale.
- Nota: Per funzioni/procedure con nome, le funzioni JS tradotte avranno lo stesso nome. Per i blocchi anonimi, LLM utilizza un nome che inventa.
Problemi noti
PL/SQL e JS sono due linguaggi diversi, e il modo in cui sono supportati in Oracle e Couchbase non consente una mappatura diretta pulita tra i due. Di seguito sono riportate alcune limitazioni che abbiamo scoperto e i metodi alternativi che abbiamo implementato per risolverle:
1. console.log non è supportato
Il procedimento integrato DBMS_OUTPUT.PUT
e altri due procedimenti simili, DBMS_OUTPUT.PUT_LINE
e DBMS_OUTPUT.NEW_LINE
, sono tradotti in console.log()
, ma console.log è un’API del browser e non è supportato dall’implementazione dell’evaluazione JavaScript di Couchbase. Questo è stato richiesto frequentemente, considerando che la funzione di eventing di Couchbase supporta le istruzioni print()
ma non nelle UDF JavaScript.
Soluzione alternativa
Si prevede che gli utenti creino un bucket logging
.
I log vengono inseriti come parte di un documento INSERT
nella collezione `default`.`default`
. Il documento avrà un aspetto simile a questo:
{
"udf": «func-name»,
"log": «argument to console.log», // the actual log line
"time": «current ISO time string»
}
L’utente può visionare i suoi log selezionando logging
:
SELECT * FROM logging WHERE udf= "«func-name»";
SELECT * FROM logging WHERE time BETWEEN "«date1»" AND "«date2»";
Esempio:
- Originale
BEGIN
DBMS.OUTPUT.PUT("Hello world!");
END;
/
- Traduzione
function helloWorld() {
// soluzione alternativa per console.log("Ciao mondo!");
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();
}
Questo è già implementato nello strumento.
Per visualizzare il 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. Chiamate di Funzioni tra Pacchetti
Le Procedure/Funzioni elencate nella specifica del pacchetto sono globali e possono essere utilizzate da altri pacchetti tramite «nome_pacchetto».«procedura/funzione_pubblica»
. Tuttavia, lo stesso non vale per una Libreria JavaScript in Couchbase, poiché le strutture di import-export non sono supportate dall’implementazione dell’evaluazione JavaScript di Couchbase.
Soluzione alternativa
- In caso di una chiamata di funzione tra librerie
«lib_name».«funzione»()
, ci si aspetta che l’utente abbia già creato la libreria di riferimento«lib_name»
; questo può essere verificato tramiteGET /evaluator/v1/libraries
. - La funzione di riferimento
«funzione»
deve anche essere creata come UDF globale; ciò può essere verificato tramiteGET /admin/functions_cache
o selezionando il namespacesystem:functions
. In questo modo possiamo accedere alla funzione tramiten1ql
.
Esempio:
Pacchetto math_utils
CREATE OR REPLACE PACKAGE math_utils AS
-- Funzione pubblica per aggiungere due numeri
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
pacchetto
CREATE OR REPLACE PACKAGE show_sum AS
-- Procedura pubblica per visualizzare la somma di due numeri
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
-- Chiamata alla funzione add_numbers dal pacchetto math_utils
v_sum := math_utils.add_numbers(p_num1, p_num2);
-- Visualizzazione della somma utilizzando 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;
/
- Codice tradotto:
function show_sum(a, b) {
var sum_result;
// Soluzione temporanea per la chiamata tra librerie 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);
}
// effettiva sostituzione per math_utils.add_numbers(a, b)
sum_result = crossfuncres[0];
// soluzione temporanea per console.log('La somma di ' + 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();
}
Viene gestito automaticamente dal programma — con un avvertimento che dovrebbe essere verificato da un insieme di occhi umani!
3. Variabili Globali
PL/SQL supporta variabili globali a livello di pacchetto e di sessione, ma le variabili globali non sono supportate in JsUDF deliberatamente per design, poiché ciò potrebbe causare preoccupazioni per le perdite di memoria.
Soluzione alternativa
Il workaround suggerito richiede modifiche manuali alla traduzione generata. Ad esempio:
CREATE OR REPLACE PACKAGE global_vars_pkg AS
-- Dichiarazioni di variabili globali
g_counter NUMBER := 0;
g_message VARCHAR2(100) := 'Initial Message';
-- Dichiarazioni di procedure pubbliche
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
-- Procedura per incrementare il contatore
PROCEDURE increment_counter IS
BEGIN
g_counter := g_counter + 1;
END increment_counter;
-- Procedura per impostare il messaggio globale
PROCEDURE set_message(p_message VARCHAR2) IS
BEGIN
g_message := p_message;
END set_message;
-- Procedura per visualizzare i valori attuali delle variabili globali
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;
/
Qualsiasi funzione che modifichi una variabile globale deve accettarla come argomento e restituirla al chiamante.
increment_counter
:
function increment_counter(counter){
counter = counter + 1;
return counter
}
Qualsiasi funzione che legga solo una variabile globale può accettarla come argomento.
show_globals
:
function show_globals(counter, message){
// workaround per console.log(counter);
var currentDate = new Date();
var utcISOString = currentDate.toISOString();
var params = [utcISOString,'SHOW_GLOBALS',couter];
var logquery = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', params);
logquery.close();
// workaround per 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();
}
Pacchetto per Libreria
Questa sezione mostra una conversione da pacchetto a libreria completa usando lo strumento.
- Esempio di pacchetto 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;
/
- Traduzione:
./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
- Codice:
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};
}
Inseriamo un nuovo documento dipendente.
- Crea raccolta dipendenti:
curl -u Administrator:password http://127.0.0.1:8091/pools/default/buckets/test/scopes/testscope/collections -d name=employees
Inserisci un Dipendente
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}
}
Questo genera un errore, e va bene così — possiamo correggerlo manualmente.
Leggendo il motivo e l’eccezione: Non è possibile INSERIRE la chiave non stringa 1 di tipo value.intValue
, ah! La chiave è sempre attesa come stringa: passare inserisci_dipendente("1", "joe", "briggs", 10000)
risolverebbe il problema, ma non è intuitivo aspettarsi che employee_id
sia una stringa.
Modifichiamo il codice generato:
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 ricreiamo 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}
}
Sto cercando di reinserirlo:
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}
}
Aggiorna un dipendente
Accidenti! C’è stato un errore: il dipendente 1 non è Joe, è Emily.
Aggiorniamo il dipendente 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}
}
Visualizza il dipendente
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}
}
Elimina il dipendente
Emily se n’è andata.
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}
}
Di nuovo, abbiamo un errore con il codice generato. Guardando il motivo e l’eccezione, possiamo confermare che il codice tradotto racchiude delete
in una transazione, il che non era il caso nell’originale.
Per le transazioni, i bucket devono avere impostata la durabilità, ma questo richiede più di un server di dati; quindi, l’errore.
La soluzione qui è modificare il codice per rimuovere la traduzione che lo racchiude.
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}
}
Ora, tutte le funzioni nel PL/SQL originale funzionano in Couchbase tramite JS UDFs. Sì, l’esempio è piuttosto banale, ma capisci il senso di come utilizzare lo strumento per migrare i tuoi script PL/SQL con poca supervisione manuale.
Ricorda che lo strumento dovrebbe portarti all’80%: il restante 20% deve ancora essere fatto da te, ma è molto meglio che scrivere tutto quel codice da solo!
Il Futuro
Questo progetto è open-source, quindi sentiti libero di contribuire. Alcune idee che ci sono state proposte includono:
- Un AI critico che può criticare il codice generato per garantire che non sia necessaria alcuna intevrento manuale
- Attualmente, il codice sorgente è solo funzionante; non sono state considerate parallelismo o riuso del codice.
E includere anche le limitazioni discusse in precedenza.
Infine, vorrei ringraziare Kamini Jagtiani per avermi guidato e Pierre Regazzoni per avermi aiutato a testare lo strumento di conversione.
Source:
https://dzone.com/articles/oracle-plsqls-to-couchbase-jsudfs-tool