Een tool om uw overgang van Oracle PL/SQL naar Couchbase JavaScript UDF’s te vergemakkelijken

Achtergrond

Wat is PL/SQL?

PL/SQL is een procedurele taal die speciaal is ontworpen om SQL-instructies binnen zijn syntaxis te omarmen. Het bevat elementen van procedurele talen, zoals voorwaarden en lussen, en kan uitzonderingen (runtime-fouten) afhandelen.

PL/SQL is native voor Oracle-databases, en databases zoals IBM DB2, PostgreSQL en MySQL ondersteunen PL/SQL-constructies via compatibiliteitsfuncties.

Wat is een JavaScript UDF?

JavaScript UDF is Couchbase’s alternatief voor PL/SQL.

JavaScript UDF brengt de veelzijdige scriptingflexibiliteit van JavaScript naar databases, waardoor dynamische en krachtige bewerkingen mogelijk zijn in moderne databasesystemen en de flexibiliteit in gegevensquery’s, verwerking en transformatie wordt verbeterd.

De meeste moderne databases zoals Couchbase, MongoDB, Snowflake en Google BigQuery ondersteunen JavaScript UDF.

Het Probleem

Een veelvoorkomend probleem dat gebruikers tegenkomen bij het migreren van Oracle naar Couchbase is het overzetten van hun PL/SQL-scripts. In plaats van PL/SQL te ondersteunen, laat Couchbase gebruikers gebruikersgedefinieerde functies in JavaScript construeren (ondersteund sinds 2021).

JavaScript UDF’s maken eenvoudige, intuïtieve manipulatie van variant- en JSON-gegevens mogelijk. Variantobjecten die naar een UDF worden doorgegeven, worden omgezet in native JavaScript-typen en -waarden.

De onbedoelde consequentie hiervan is dat de meeste RDBMS die de afgelopen tien jaar hebben bestaan ontwikkelaars sterk hebben aangemoedigd om de database te benaderen met behulp van hun procedurele extensies voor SQL (PL/pgSQL, PL/SQL), die procedurele constructies, integratie met SQL, foutafhandeling, functies en procedures, triggers en cursors ondersteunen, of op zijn minst, functies en procedures (zoals Sakila). Voor elke poging om hier vandaan te bewegen, zouden al hun scripts herschreven moeten worden.

Het herschrijven van code is vaak een vermoeiende taak, vooral wanneer het gaat om PL/SQL-scripts die in de jaren 2000 zijn geschreven en sindsdien zijn onderhouden. Deze scripts kunnen complex zijn, vaak uit duizenden regels bestaan, wat overweldigend kan zijn voor de gemiddelde bedrijfsgebruiker.

Oplossing

De ideale benadering zou zijn om een geheel nieuwe PL/SQL-evaluator te ontwikkelen, maar dat zou een buitensporige hoeveelheid engineering-uren vereisen, en voor hetzelfde gebruiksgeval hebben we al een moderne, stabiele en snelle JsEvaluator — dus waarom een andere evaluator ondersteunen?

Dit maakt het probleem een perfect gebruiksgeval om gebruik te maken van de voortdurende vooruitgangen in AI en LLM’s — en dat is wat we hier hebben gedaan. We hebben Generatieve AI-modellen gebruikt om de conversie van PL/SQL naar JSUDF te automatiseren.

Vanaf juni 2024 hebben modellen een beperkte contextvenster, wat betekent dat langere PL/SQL’s de foutmelding krijgen:

“De maximale lengte van de context van dit model is 8192 tokens. Uw berichten resulteerden echter in <Meer-dan-8192> tokens. Gelieve de lengte van de berichten te verkorten.”

  • Let op dat dit voor GPT4 is. 

Dus wachten we tot AI krachtiger wordt en meer tokens toestaat (zoals Moore’s Law, maar voor de contextlengte-vs-nauwkeurigheid van de AI)? 

Nee, daar komt de ANTLR parser generator tool om de hoek kijken. ANTLR staat bekend om zijn gebruik voor compiler- en interpreterontwikkeling. Op die manier kunnen we het grote script opsplitsen in kleinere eenheden die onafhankelijk kunnen worden vertaald.

Dus zijn we nu een transpiler aan het bouwen? Ja en nee.

De stappen in een transpiler zijn als volgt:

  1. Lexicale analyse (tokenisatie)
  2. Syntactische analyse (parsen)
  3. Semantische analyse
  4. Generatie van tussentijdse representatie (IR)
  5. Optimalisatie (optioneel)
  6. Generatie van doelcode

Hoe de AI Vertaler Werkt

Stappen 1 en 2 worden uitgevoerd met behulp van ANTLR.

We gebruiken de Listener-interface van ANTLR om individuele Procedure/Functie/Anonieme blokken vast te leggen, aangezien dit onafhankelijke codeblokken zijn. In het geval dat de Procedure/Functie/Anonieme blokken zelf de contextvenster overschrijden, vertalen we op statementniveau (waar de LLM ervan uitgaat dat het gebruik van variabelen/functieaanroepen die hier niet gedefinieerd zijn elders voorkomen).

Daarna worden stappen 3, 4, 5 en 6 overgelaten aan de LLM (GPT), dat wil zeggen, het vertalen van elk PL/SQL-blok naar een JavaScript-functie naar beste vermogen, waarbij ook de operationele semantiek van het blok behouden blijft en syntactisch nauwkeurig is.

De resultaten zijn verrassend positief: de vertaling is 80-85% nauwkeurig.

Een ander voordeel van de oplossing is dat we hallucinatie verminderen door ons op één taak tegelijk te concentreren, wat resulteert in nauwkeurigere vertalingen.

Om te visualiseren:

Hoe de tool te gebruiken

Het uitvoerbare bestand verwacht de volgende commandoregelargumenten:

  • -u : Capella aanmeld-e-mail
  • -p : Capella aanmeld-wachtwoord
  • -cpaddr: Capella-url voor chat-completions API
  • -orgid: Organisatie-ID in het pad van de chat-completions API
  • -cbhost: node-ip: cbcluster node
  • -cbuser: cluster-gebruikersnaam: cbcluster gebruiker, toegevoegd via database toegang
  • -cbpassword: cluster-wachtwoord: cbcluster wachtwoord, toegevoegd via database toegang
  • -cbport: query-service TLS-poort (meestal 18093)
  • bestandspad, dat wil zeggen het pad naar het PL/SQL-script dat vertaald moet worden
  • output->: In de uitvoermap wordt een bestand gegenereerd met dezelfde naam als het plsql-bestand met vertaalde JavaScript-bibliotheekcode.

Als voorbeeld, 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');

       --start een innerlijke blok 

      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;

Om het bovenstaande script kort toe te lichten, wordt een buitenste lus 4 keer uitgevoerd, waarbij x met 1000 wordt verhoogd, de teller met 1. De innerlijke lus wordt 4 keer uitgevoerd, waarbij x met 1 wordt verhoogd, de teller met 1.

Het uitvoeren van de vertaler op de test-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

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

}

Het vertaalde script bevat een functie nestedloop (naam gegenereerd door LLM) die precies doet wat het oorspronkelijke Anonieme PL/SQL-blok aangeeft.

  • Zijdelingse opmerking: Voor benoemde functies/procedures zullen vertaalde JS-functies dezelfde naam hebben. Voor anonieme blokken gebruikt LLM een naam die het zelf bedenkt.

Bekende problemen

PL/SQL en JS zijn twee verschillende talen, en de manier waarop ze worden ondersteund in Oracle en Couchbase staat geen schone directe mapping tussen de twee toe. Hieronder staan enkele beperkingen die we hebben ontdekt en de oplossingen die we hiervoor hebben geïmplementeerd:

1. console.log wordt niet ondersteund

DBMS_OUTPUT.PUT ingebouwde procedure en twee andere vergelijkbare ingebouwde procedures, DBMS_OUTPUT.PUT_LINE en DBMS_OUTPUT.NEW_LINE worden vertaald naar console.log(), maar console.log is een browser-API en wordt niet ondersteund door de JavaScript-evaluatie-implementatie van Couchbase. Dit is een veelgestelde vraag, aangezien de Couchbase-eventfunctie print() verklaringen ondersteunt, maar niet in JavaScript UDF’s.

Oplossing

Van gebruikers wordt verwacht dat ze een logging bucket aanmaken.

Logs worden ingevoegd als onderdeel van een document INSERT in de `default`.`default` collectie. Het document zou er ongeveer zo uitzien:

 

{

   "udf": «func-name»,

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

   "time": «current ISO time string»

}

De gebruiker kan zijn logs bekijken door logging te selecteren:

 

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

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

Voorbeeld: 

  • Origineel
 

BEGIN

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

END;

/

  • Vertaling
JavaScript

 

function helloWorld() {

 

   // oplossing voor console.log("Hallo wereld!");

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

}

Dit is al geïmplementeerd in de tool.

Om de log te bekijken:

 

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. Cross-Package Functie Aanroepen

Procedures/Functies die in de pakket specificatie zijn vermeld, zijn globaal en kunnen vanuit andere pakketten worden gebruikt via «pakket_naam».«publieke_procedure/functie». Echter, hetzelfde geldt niet voor een JavaScript-bibliotheek in Couchbase, aangezien import-export constructies niet worden ondersteund door de JavaScript-evaluatie-implementatie van Couchbase.

Oplossing

  • In geval van een interbibliothecaire functieaanroep «lib_name».«function»(), wordt verwacht dat de gebruiker de gerefereerde bibliotheek «lib_name» al heeft aangemaakt; dit kan worden geverifieerd via GET /evaluator/v1/libraries.
  • De gerefereerde functie «function» wordt ook verwacht te zijn aangemaakt als een globale UDF; dit kan worden geverifieerd via GET /admin/functions_cache of door te selecteren system:functions keyspace. Op deze manier kunnen we de functie benaderen via n1ql.

Voorbeeld:

  • math_utils Pakket
 

CREATE OR REPLACE PACKAGE math_utils AS

   -- Publieke functie om twee getallen op te tellen

   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 pakket
 

CREATE OR REPLACE PACKAGE show_sum AS

   -- Publieke procedure om de som van twee getallen weer te geven

   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

      -- Aufruf der add_numbers functie aus dem math_utils pakket

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

 

      -- Weergave van de som met behulp van 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;

/

  • Vertaalde code:
 

function show_sum(a, b) {

 var sum_result;



// Workaround voor het aanroepen van een functie vanuit een andere bibliotheek: 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);

}



// daadwerkelijke vervanging voor math_utils.add_numbers(a, b)

    sum_result = crossfuncres[0];



 

// workaround voor console.log('De som van ' + a + ' en ' + 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();

}

Het wordt automatisch afgehandeld door het programma — met een waarschuwing dat het gecontroleerd moet worden door een menselijk paar ogen!

3. Globale Variabelen

PL/SQL ondersteunt pakketniveau en sessieniveau globale variabelen, maar globale variabelen worden bewust niet ondersteund in JsUDF vanwege zorgen over geheugenlekken.

Workaround

De voorgestelde workaround vereist handmatige aanpassing van de gegenereerde vertaling. Bijvoorbeeld:

 

   CREATE OR REPLACE PACKAGE global_vars_pkg AS

     -- Globale variabele declaraties

     g_counter NUMBER := 0;

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



     -- Publieke procedure declaraties

     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 om de teller te verhogen

     PROCEDURE increment_counter IS

     BEGIN

       g_counter := g_counter + 1;

     END increment_counter;



     -- Procedure om het globale bericht in te stellen

     PROCEDURE set_message(p_message VARCHAR2) IS

     BEGIN

       g_message := p_message;

     END set_message;



     -- Procedure om de huidige waarden van globale variabelen weer te geven

     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;

   /

Elke functie die een globale variabele wijzigt, moet deze als argument accepteren en teruggeven aan de aanroeper.

  • increment_counter:
 

function increment_counter(counter){

   counter = counter + 1;

   return counter

}

Elke functie die alleen een globale variabele leest, kan deze als argument accepteren.

  • show_globals:
 

function show_globals(counter, message){

   // workaround voor 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 voor 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();

}

Package naar Bibliotheek

Deze sectie toont een end-to-end conversie van package naar bibliotheek met behulp van de tool.

  • Voorbeeld PL/SQL package:
 

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;

/

  • Vertaling:
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

  • Code:
 

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

}

Laten we een nieuw werknemer document invoegen.

  • Maak werknemer collectie aan:
 
curl -u  Administrator:password http://127.0.0.1:8091/pools/default/buckets/test/scopes/testscope/collections -d name=employees     

Voeg een Werknemer toe

 

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}

}

Dit geeft een foutmelding, en dat is oké — we kunnen het handmatig oplossen.

De reden en uitzondering lezen: Cannot INSERT non-string key 1 of type value.intValue, ah! De sleutel wordt altijd verwacht als een string: het doorgeven van insert_employee("1", "joe", "briggs", 10000) zou de truc doen, maar het is niet intuïtief om te verwachten dat employee_id een string is.

Laten we de gegenereerde code aanpassen:

 

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

}

En de UDF opnieuw maken:

 

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}

}

Proberen het opnieuw in te voegen:    

 


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}

}

Werk een werknemer bij

Verdorie! Er is een fout: werknemer 1 is niet Joe, het is Emily. 

Laten we werknemer 1 bijwerken:

 

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}

}

Bekijk de werknemer

 

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}

}

Verwijder de werknemer

Emily is vertrokken.

 

 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}

}

Opnieuw hebben we een fout met de gegenereerde code. Kijkend naar de reden en de uitzondering, kunnen we bevestigen dat de vertaalde code verwijder in een transactie plaatst, wat in de originele code niet het geval was.

Voor transacties moeten buckets duurzaamheid hebben ingesteld, maar dit vereist meer dan één gegevensserver; daarom de fout.

De oplossing hier is om de code te wijzigen om de omhullende vertaling te verwijderen.

 

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}

}

Nu werken alle functies in de originele PL/SQL in Couchbase via JS UDF’s. Ja, het voorbeeld is vrij triviaal, maar je begrijpt de essentie van hoe je het gereedschap kunt gebruiken om je PL/SQL-scripts met weinig handmatige supervisie te migreren.

Vergeet niet dat het gereedschap bedoeld is om 80% van het werk voor je te doen: de andere 20% moet nog steeds door jou worden gedaan, maar het is veel beter dan al die code zelf te schrijven!

De Toekomst

Dit project is open-source, dus voel je vrij om bij te dragen. Enkele ideeën die aan ons werden voorgelegd, waren:

  1. Critic AI die de gegenereerde code kan bekritiseren om ervoor te zorgen dat handmatige tussenkomst helemaal niet nodig is
  2. Momenteel is de broncode code die gewoon werkt; er zijn geen gedachten aan parallelisme of hergebruik van code in praktijk gebracht.

En neem ook de eerder besproken beperkingen op.

Ten slotte wil ik Kamini Jagtiani bedanken voor haar begeleiding en Pierre Regazzoni voor zijn hulp bij het testen van de conversietool.

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