Hintergrund
Was ist PL/SQL?
PL/SQL ist eine prozedurale Sprache, die speziell darauf ausgelegt ist, SQL-Anweisungen in ihrer Syntax zu integrieren. Sie umfasst prozedurale Sprachelemente wie Bedingungen und Schleifen und kann Ausnahmen (Laufzeitfehler) behandeln.
PL/SQL ist nativ für Oracle-Datenbanken und Datenbanken wie IBM DB2, PostgreSQL und MySQL unterstützen PL/SQL-Konstrukte durch Kompatibilitätsfunktionen.
Was ist eine JavaScript UDF?
JavaScript UDF ist die Alternative von Couchbase zu PL/SQL.
JavaScript UDF bringt die allgemeine Skriptflexibilität von JavaScript in Datenbanken, ermöglicht dynamische und leistungsstarke Operationen über moderne Datenbanksysteme und verbessert die Flexibilität bei der Datenabfrage, -verarbeitung und -umwandlung.
Die meisten modernen Datenbanken wie Couchbase, MongoDB, Snowflake und Google BigQuery unterstützen JavaScript UDF.
Das Problem
Ein häufiges Problem, das Benutzer beim Wechsel von Oracle zu Couchbase sehen, ist die Portierung ihrer PL/SQL-Skripte. Anstatt PL/SQL zu unterstützen, ermöglicht es Couchbase Benutzern, benutzerdefinierte Funktionen in JavaScript zu erstellen (seit 2021 unterstützt).
JavaScript UDFs ermöglichen eine einfache, intuitive Manipulation von Varianten- und JSON-Daten. Variantenobjekte, die an eine UDF übergeben werden, werden in native JavaScript-Typen und Werte umgewandelt.
Die unbeabsichtigte Konsequenz davon ist, dass die Mehrheit der RDBMS, die in den letzten zehn Jahren existieren, Entwickler stark dazu ermutigt haben, auf die Datenbank über ihre prozeduralen Erweiterungen von SQL (PL/pgSQL, PL/SQL) zuzugreifen, die prozedurale Konstrukte, Integration mit SQL, Fehlerbehandlung, Funktionen und Prozeduren, Trigger und Cursor unterstützen, oder zumindest Funktionen und Prozeduren (wie Sakila). Bei jedem Versuch, sich davon zu lösen, müssten alle ihre Skripte neu geschrieben werden.
Das Umschreiben von Code ist oft eine mühsame Aufgabe, insbesondere wenn es um PL/SQL-Skripte geht, die in den 2000er Jahren geschrieben wurden und seitdem gewartet werden. Diese Skripte können komplex sein und sich oft über Tausende von Zeilen erstrecken, was für den durchschnittlichen Unternehmensbenutzer überwältigend sein kann.
Lösung
Der ideale Ansatz wäre die Entwicklung eines völlig neuen PL/SQL-Auswerters, aber das würde eine übermäßige Menge an Entwicklungsstunden erfordern, und für denselben Anwendungsfall haben wir bereits einen modernen, stabilen und schnellen JsEvaluator — warum also einen weiteren Auswerter unterstützen?
Dies macht das Problem zu einem perfekten Anwendungsfall, um von den fortlaufenden Fortschritten in KI und LLMs zu profitieren — und genau das haben wir hier getan. Wir haben generative KI-Modelle verwendet, um die Konvertierung von PL/SQL in JSUDF zu automatisieren.
Stand Juni 2024 haben Modelle ein begrenztes Kontextfenster, was bedeutet, dass längere PL/SQLs mit dem Fehler konfrontiert werden:
„Die maximale Kontextlänge dieses Modells beträgt 8192 Token. Ihre Nachrichten haben jedoch zu <Mehr-als-8192> Token geführt. Bitte verkürzen Sie die Länge der Nachrichten.“
- Beachten Sie, dass dies für GPT4 gilt.
Warten wir also darauf, dass KI leistungsstärker wird und mehr Token zulässt (wie Moores Gesetz, aber für die Kontextlänge gegenüber der Präzision der KI)?
Nein: Hier kommt das Parser-Generator-Tool ANTLR ins Spiel. ANTLR ist bekannt dafür, bei der Entwicklung von Compilern und Interpretern verwendet zu werden. Auf diese Weise können wir das große Skript in kleinere Einheiten aufteilen, die unabhängig übersetzt werden können.
Bauen wir also jetzt einen Transpiler? Nun, ja und nein.
Die Phasen in einem Transpiler sind wie folgt:
- Lexikalische Analyse (Tokenisierung)
- Syntaktische Analyse (Parsing)
- Semantische Analyse
- Generierung einer Zwischenrepräsentation (IR)
- Optimierung (optional)
- Generierung des Zielcodes
Wie der KI-Übersetzer funktioniert
Schritte 1 und 2 werden mit ANTLR durchgeführt.
Wir verwenden ANTLRs Listener-Schnittstelle, um einzelne Prozedur/Funktion/Anonyme Blöcke zu erfassen, da sie unabhängige Codeblöcke darstellen. Falls die Prozedur/Funktion/Anonyme Blöcke selbst das Kontextfenster überschreiten, übersetzen wir auf Anweisungsebene (wobei das LLM das Vorhandensein von Variablen/Funktionsaufrufen annimmt, die hier nicht definiert sind, aber irgendwo zuvor).
Anschließend werden die Schritte 3, 4, 5 und 6 dem LLM (GPT) überlassen, d.h. jeder PL/SQL-Block wird nach bestem Können in eine JavaScript-Funktion übersetzt, die auch die operationale Semantik des Blocks bewahrt und syntaktisch korrekt ist.
Die Ergebnisse sind überraschend positiv: Die Übersetzung ist zu 80-85% genau.
Ein weiterer Vorteil der Lösung ist, dass wir Halluzinationen reduzieren, indem wir uns auf eine Aufgabe zur Zeit konzentrieren, was zu genaueren Übersetzungen führt.
Zur Veranschaulichung:
Wie man das Tool verwendet
Die ausführbare Datei erwartet die folgenden Befehlszeilenargumente:
-u
: Capella-Anmelde-E-Mail-p
: Capella-Anmelde-Passwort-cpaddr
: Capella-URL für die Chat-Vervollständigungs-API-orgid
: Organisations-ID im Pfad der Chat-Vervollständigungs-API-cbhost: node-ip
:cbcluster
Knoten-cbuser: cluster-user-name
:cbcluster
Benutzer, der über den Datenbankzugriff hinzugefügt wurde-cbpassword: cluster-password
:cbcluster
Passwort, das über den Datenbankzugriff hinzugefügt wurde-cbport
:query-service
TLS-Port (normalerweise 18093)dateipfad
, d.h., Pfad zum PL/SQL-Skript, das übersetzt werden mussoutput->
: Im Ausgabeverzeichnis wird eine Datei mit demselben Namen wie die plsql-Datei generiert, mit dem übersetzten JavaScript-Bibliothekscode.
Zum Beispiel, cat beispiel1.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');
--starten eines inneren Blocks
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;
Um das obige Skript kurz zu erklären, wird eine äußere Schleife für 4 Durchläufe ausgeführt, wobei x um 1000 und der Zähler um 1 erhöht wird. Die innere Schleife läuft für 4 Durchläufe, wobei x um 1 und der Zähler um 1 erhöht wird.
Ausführen des Übersetzers auf dem Test-PL/SQL:
./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
Ausgabe JsUDF: cat output/beispiel1.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();
}
Das übersetzte Skript enthält eine Funktion verschachtelteSchleife
(Name vom LLM generiert), die genau das macht, was der Original-Anonymous-PL/SQL-Block angibt.
- Anmerkung: Bei benannten Funktionen/Prozeduren haben die übersetzten JS-Funktionen denselben Namen. Für anonyme Blöcke verwendet der LLM einen Namen, den er sich ausdenkt.
Bekannte Probleme
PL/SQL und JS sind zwei verschiedene Sprachen, und die Art und Weise, wie sie in Oracle und Couchbase unterstützt werden, erlaubt keine saubere direkte Zuordnung zwischen den beiden. Nachstehend sind einige Einschränkungen aufgeführt, die wir festgestellt haben, sowie die Workarounds, die wir für dieselben implementiert haben:
1. console.log wird nicht unterstützt
DBMS_OUTPUT.PUT
eingebautes Verfahren und zwei weitere ähnliche eingebaute Verfahren, DBMS_OUTPUT.PUT_LINE
und DBMS_OUTPUT.NEW_LINE
werden als console.log()
übersetzt, aber console.log ist eine Browser-API und wird nicht von der JavaScript-Auswertungsimplementierung von Couchbase unterstützt. Dies wurde häufig angefragt, da die Couchbase-Eventing-Funktion print()
Anweisungen unterstützt, jedoch nicht in JavaScript-UDFs.
Workaround
Benutzer sollten einen logging
-Bucket erstellen.
Logs werden als Teil eines Dokuments INSERT
in die Sammlung `default`.`default`
eingefügt. Das Dokument würde ungefähr so aussehen:
{
"udf": «func-name»,
"log": «argument to console.log», // the actual log line
"time": «current ISO time string»
}
Der Benutzer kann seine Logs anzeigen, indem er logging
auswählt:
SELECT * FROM logging WHERE udf= "«func-name»";
SELECT * FROM logging WHERE time BETWEEN "«date1»" AND "«date2»";
Beispiel:
- Original
BEGIN
DBMS.OUTPUT.PUT("Hello world!");
END;
/
- Übersetzung
function helloWorld() {
// Workaround für console.log("Hello world!");
var currentDate = new Date();
var utcISOString = currentDate.toISOString();
var params = [utcISOString,'anonymousblock1',"Hello world!"];
var logquery = N1QL('INSERT INTO logging VALUES(UUID(),{"udf":$2, "log":$3, "time":$1}, {"expiration": 5*24*60*60 })', params);
logquery.close();
}
Dies ist bereits im Tool implementiert.
Um das Log anzusehen:
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. Aufrufe von Funktionen zwischen Paketen
Prozeduren/Funktionen, die in der Paketspezifikation aufgeführt sind, sind global und können aus anderen Paketen über «package_name».«public_procedure/function»
verwendet werden. Dies trifft jedoch nicht auf eine JavaScript-Bibliothek in Couchbase zu, da Import-Export-Konstrukte von der JavaScript-Auswertungsimplementierung von Couchbase nicht unterstützt werden.
Workaround
- Im Falle eines Aufrufs einer interbibliothekarischen Funktion
«lib_name».«function»()
wird erwartet, dass die referenzierte Bibliothek«lib_name»
bereits erstellt wurde; dies kann überGET /evaluator/v1/libraries
überprüft werden. - Es wird auch erwartet, dass die referenzierte Funktion
«function»
als globale UDF erstellt wurde; dies kann überGET /admin/functions_cache
oder die Auswahl des keyspacessystem:functions
überprüft werden. Auf diese Weise können wir auf die Funktion übern1ql
zugreifen.
Beispiel:
math_utils Package
CREATE OR REPLACE PACKAGE math_utils AS
-- Öffentliche Funktion zum Hinzufügen von zwei Zahlen
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
Package
CREATE OR REPLACE PACKAGE show_sum AS
-- Öffentliche Prozedur zum Anzeigen der Summe von zwei Zahlen
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-Funktion aus dem math_utils-Package
v_sum := math_utils.add_numbers(p_num1, p_num2);
-- Anzeige der Summe mithilfe von 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;
/
- Übersetzter Code:
function show_sum(a, b) {
var sum_result;
// Workaround für den Aufruf einer Funktion in einer anderen Bibliothek 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);
}
// tatsächliche Ersetzung für math_utils.add_numbers(a, b)
sum_result = crossfuncres[0];
// Workaround für console.log('Die Summe von ' + a + ' und ' + b + ' beträgt: ' + 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 wird automatisch vom Programm behandelt — mit dem Hinweis, dass es von einem menschlichen Auge überprüft werden sollte!
3. Globale Variablen
PL/SQL unterstützt globale Variablen auf Paketebene und Sitzungsebene, aber globale Variablen werden in JsUDF bewusst nicht unterstützt, da dies Bedenken hinsichtlich Speicherlecks verursacht.
Workaround
Der vorgeschlagene Workaround erfordert manuelle Anpassungen an der generierten Übersetzung. Zum Beispiel:
CREATE OR REPLACE PACKAGE global_vars_pkg AS
-- Globale Variablendeklarationen
g_counter NUMBER := 0;
g_message VARCHAR2(100) := 'Initial Message';
-- Öffentliche Prozedurdeklarationen
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
-- Prozedur zum Inkrementieren des Zählers
PROCEDURE increment_counter IS
BEGIN
g_counter := g_counter + 1;
END increment_counter;
-- Prozedur zum Setzen der globalen Nachricht
PROCEDURE set_message(p_message VARCHAR2) IS
BEGIN
g_message := p_message;
END set_message;
-- Prozedur zum Anzeigen der aktuellen Werte globaler Variablen
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;
/
Jede Funktion, die eine globale Variable ändert, muss sie als Argument akzeptieren und an den Aufrufer zurückgeben.
increment_counter
:
function increment_counter(counter){
counter = counter + 1;
return counter
}
Jede Funktion, die nur auf eine globale Variable zugreift, kann sie als Argument akzeptieren.
show_globals
:
function show_globals(counter, message){
// Workaround für 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 für 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();
}
Paket zur Bibliothek
In diesem Abschnitt wird eine End-to-End-Paket-zu-Bibliothek-Konvertierung mithilfe des Tools gezeigt.
- Beispiel PL/SQL-Paket:
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;
/
- Übersetzung:
./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};
}
Lassen Sie uns ein neues Mitarbeiterdokument einfügen.
- Erstellen Sie eine Mitarbeiterkollektion:
curl -u Administrator:password http://127.0.0.1:8091/pools/default/buckets/test/scopes/testscope/collections -d name=employees
Mitarbeiter einfügen
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}
}
Dies führt zu einem Fehler, und das ist in Ordnung — wir können es manuell beheben.
Lesen des Grundes und der Ausnahme: Kann nicht den nicht-string-Schlüssel 1 des Typs value.intValue EINFÜGEN
, ah! Der Schlüssel wird immer als String erwartet: die Übergabe von insert_employee("1", "joe", "briggs", 10000)
würde funktionieren, aber es ist nicht intuitiv zu erwarten, dass employee_id
ein String sein soll.
Lassen Sie uns den generierten Code ändern:
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();
}
Und die UDF neu erstellen:
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}
}
Versuch es erneut einzufügen:
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}
}
Aktualisiere einen Mitarbeiter
Verdammtes! Es gibt einen Fehler: Mitarbeiter 1 ist nicht Joe, es ist Emily.
Lass uns Mitarbeiter 1 aktualisieren:
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}
}
Mitarbeiter anzeigen
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}
}
Mitarbeiter löschen
Emily ist gegangen.
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}
}
Wieder haben wir einen Fehler im generierten Code. Anhand des Grundes und der Ausnahme können wir bestätigen, dass der übersetzte Code delete
in einer Transaktion einschließt, was im Original nicht der Fall war.
Für Transaktionen müssen Buckets Haltbarkeit haben, aber dies erfordert mehr als einen Datenserver; daher der Fehler.
Die Lösung hier ist, den Code zu ändern, um die umschließende Übersetzung zu entfernen.
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}
}
Jetzt funktionieren alle Funktionen im ursprünglichen PL/SQL in Couchbase über JS UDFs. Ja, das Beispiel ist ziemlich trivial, aber du bekommst das Wesentliche, wie du das Tool nutzen kannst, um deine PL/SQL-Skripte mit wenig manueller Aufsicht zu migrieren.
Denke daran, dass das Tool dich zu 80 % unterstützen soll: die anderen 20 % müssen immer noch von dir erledigt werden, aber es ist viel besser, als all diesen Code selbst zu schreiben!
Die Zukunft
Dieses Projekt ist Open Source, also fühl dich frei, beizutragen. Einige Ideen, die uns vorgeschlagen wurden, beinhalteten:
- Kritik-AI, die generierten Code kritisieren kann, um sicherzustellen, dass keine manuelle Intervention erforderlich ist
- Aktuell ist der Quellcode einfach funktional; es wurden keine Gedanken zu Parallelität oder Code-Wiederverwendung angestellt.
Und erwähnen Sie auch die zuvor besprochenen Einschränkungen.
Zuletzt möchte ich Kamini Jagtiani für ihre Anleitung und Pierre Regazzoni für die Unterstützung beim Testen des Konvertierungstools danken.
Source:
https://dzone.com/articles/oracle-plsqls-to-couchbase-jsudfs-tool