Donnerstag, 23. Juli 2015

ORDS und Javascript im Einsatz: D3.js-Charts für Tabellendaten

Die ersten beiden Blog-Postings ('Auto-REST' für Tabellen und 'REST-Enabling' für 'custom' SQL und PL/SQL 0Auto-Rest' für Tabellen) haben einige wesentliche Grundlagen gelegt - nun wisst Ihr, wie Ihr mit Oracle REST Data Services (ORDS) sehr einfach REST-Services für Tabellen, Views, SQL-Queries oder PL/SQL-Objekte erzeugen könnt. Nun ist es Zeit, das einmal praktisch einzusetzen. In diesem Blog Posting wollen wir einen so generieren REST-Service nutzen, um einen D3.js Chart mit Daten zu versorgen.
D3.js ist eine sehr populäre, Diagrammtechnologie, die allein auf Standards wie HTML5, CSS und Javascript basiert. D3.js ist aber keine Chart-Engine, sondern vielmehr ein Javascript-Programmierframework, welches gängige Aufgaben, die beim Programmieren eines Diagramms entstehen, übernimmt bzw. leichter macht. Tatsächlich programmieren muss man den Chart aber immer noch. Der Autor, Charles Bostock hat neben dem Framework an sich auch eine Reihe von Beispielen ins Internet gestellt, anhand dieser kann man gut nachvollziehen, wie D3.js Charts funktionieren und wie man das Framework nutzt.
Eben solche Charts hätten wir gerne für unsere Tabellendaten. Ein Bubble Chart auf die Tabelle EMP sollte dann so aussehen.
Schauen wir uns den Javascript-Code und die Daten des Bubblechart-Beispiels einmal näher an. Die Daten liegen im JSON-Format vor und sehen wie folgt aus.
Im Javascript-Code ist vor allem das markierte Fragment interessant - der Aufruf von d3.json lädt die JSON-Daten aus der angegebenen Datei - anstelle eines einfachen Dateinamens kann auch eine HTTP-URL angegeben werden - und dann könnte man die URL eines ORDS Rest Service angeben. So könnte man den Chart mit Daten als der Oracle-Datenbank versorgen.
Bevor wir aber jetzt loslegen, einen REST-Service bauen, der die im Beispiel angegebene hierarchische Struktur generiert, schauen wir noch etwas genauer in den Javascript-Code hinein - der enthält auch noch diese Javascript-Funktion hier ...
Offensichtlich werden die hierarchischen JSON-Daten mit dieser Funktion "flachgeklopft" - und wenn das so ist, dann brauchen wir im REST-Service nicht erst großartig eine Hierarchie zu erzeugen; die flache Ausgabe, die wir ohnehin bekommen, reicht völlig aus. Zuerst also die Skripte für unseren REST Service - dass die Tabelle EMP in eurem Datenbankschema vorhanden und ORDS installiert ist, setzte ich jetzt mal voraus. Analog zur Vorgehensweise im letzten Blog-Posting erzeugen wir den Service wie folgt.
/*
 * Etwaige vorhandene Service-Definition löschen 
 */

declare
  l_int_module_id user_ords_modules.id%TYPE;
begin
  select id into l_int_module_id 
  from user_ords_modules
  where name = 'd3bubble';

  ords_services.delete_module(p_id => l_int_module_id);
end;
/
sho err

/*
 * Neuen Service erzeugen
 */

begin
  ords.create_service(
    p_module_name => 'd3bubble',
    p_base_path =>   '/d3bubble/',
    p_pattern =>     'getdata',
    p_method =>      'GET',
    p_source_type => ords.source_type_query,
    p_source =>      q'#select ename as name, job as packageName, sal as value from emp#'
  );
end;
/
sho err

commit
/
Danach testen ...
Als nächstes gehen wir in den Javascript-Code des Charts und nehmen dort ein paar Änderungen vor.
  • Der Zugriff von d3.json auf die Datei flare.json wird durch eine URL auf unseren neuen REST-Service ersetzt.
    :
    d3.json("/ords/scott/d3bubble/getdata"function(error, root) {
    :
  • Der Aufruf der Javascript-Funktion classes, welche die Hierarchie in ein flaches Format wandelt, wird eliminiert.
  • JSON-Attributnamen müssen an der einen oder anderen Stelle an das vom REST-Service ausgelieferte JSON angepasst werden
Wenn das alles gemacht wurde, kommt der folgende Javascript-Code heraus. Denkt daran, die URL zum REST-Service in Zeile 28 ggfs. an eure Umgebung anzupassen.
<!DOCTYPE html>
<html>
  <head>
    <style> text { font: 10px sans-serif; } </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  </head>
  <body>
    <script>
var diameter = 600,
    format = d3.format(",d"),
    color = d3.scale.category20c();

var bubble = d3.layout.pack()
    .sort(null)
    .size([diameter, diameter])
    .padding(1.5);

var svg = d3.select("body").append("svg")
    .attr("width", diameter)
    .attr("height", diameter)
    .attr("class", "bubble");

d3.json("/ords/scott/d3bubble/getdata", function(error, root) {
  if (error) throw error;

  var node = svg.selectAll(".node")
    .data(bubble.nodes({"children":root.items})
    .filter(function(d) { return !d.children; }))
    .enter().append("g")
    .attr("class", "node")
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

  node.append("title")
      .text(function(d) { return d.name + ": " + format(d.value); });

  node.append("circle")
      .attr("r", function(d) { return d.r; })
      .style("fill", function(d) { return color(d.packagename); });
  node.append("text")
      .attr("dy", ".3em")
      .style("text-anchor", "middle")
      .text(function(d) { return d.name.substring(0, d.r / 3); });
});

d3.select(self.frameElement).style("height", diameter + "px");

    </script>
  </body>
</html>
Speichert diesen HTML- und Javascript-Code in einer Datei ab (d3bubble.html). Diese Datei müsste als nächstes ebenfalls über den ORDS erreichbar sein. Dazu nutzen wir die Tatsache aus, dass ORDS auch als Webserver für APEX dienen kann und für diesen Zweck auch statische Dateien ausliefern kann. Legt also eine Ordner für statische Dateien (bspw. D:\oracle\ords-3.0.1\files an und stoppt den ORDS. Startet ihn dann wie folgt neu.
java -jar ords.war standalone --apex-images "D:\oracle\ords-3.0.1\files"
Nun könnt Ihr eure HTML-Datei ganz einfach mit dem URL http://{ords-host}:{port}/i/d3bubble.html aufrufen. Der HTML-Code wird in den Browser geladen, das Javascript startet und lädt die Tabellendaten per REST-Request nach. Fertig.
Nach gleichem Schema lässt sich auch das D3-Tutorial für einen Bar Chart mit ORDS kombinieren. Wir brauchen nicht einmal einen neuen REST-Service - der vorhandene tut es auch. Nur muss der Javascript-Code ein wenig angepasst werden.
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
    .bar { fill: steelblue; }
    .bar:hover { fill: brown; }
    
    .axis { font: 10px sans-serif; }
    .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; }
    .x.axis path { display: none; }
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  <head>
  <body>
    <script>

var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(10, "");

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

d3.json("/ords/scott/d3bubble/getdata", function(error, data) {
  if (error) throw error;
  x.domain(data.items.map(function(d) { return d.name; }));
  y.domain([0, d3.max(data.items, function(d) { return d.value; })]);

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Salary");

  svg.selectAll(".bar")
      .data(data.items)
    .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.name); })
      .attr("width", x.rangeBand())
      .attr("y", function(d) { return y(d.value); })
      .attr("height", function(d) { return height - y(d.value); });
});

function type(d) {
  d.value = +d.value;
  return d;
}

    </script>
  </body>
</html>
Den Javascript-Code packt Ihr in eine Datei namens d3bar.html und legt diese ins gleiche Verzeichnis wie schon zuvor d3bubble.html. Ein Aufruf von http://{ords-host}:{port}/i/d3bar.html sollte dann zu folgendem Bild führen.
Viel Spaß damit.

Donnerstag, 9. Juli 2015

REST Services für SQL und PL/SQL: ORDS. PL/SQL. Sonst nix.

Im zweiten Blog-Posting dieses neuen Blogs geht es nochmals um die Oracle Rest Data Services (ORDS) - nachdem im ersten Blog-Posting eine Tabelle mit wenig Aufwand 'Rest-Enabled' wurde, geht es heute darum, REST-Services für eigene SQL-Queries oder PL/SQL-Code zu generieren. Wieder nehme ich zum Einrichten nicht den SQL Developer, sondern das PL/SQL-Interface mit dem Package ORDS. Ich finde das einfach eine smarte Lösung, weil sich ein REST-Interface auf diesem Weg, wie ein Datenbankschema, skripten lässt. Die Einrichtung auf einer anderen Datenbank - bzw. der Move von Development zu Produktion ist damit ein Kinderspiel.
Los geht's also - wir fangen mit einer einfachen SQL-Query an.
select 'Hello World' as greeting from dual
Die Prozedur CREATE_SERVICE erlaubt es uns, einen REST-Service anzulegen, der beliebigen Code (SQL oder PL/SQL) ausführt. ORDS ordnet die eingerichteten REST-Services in Module ein. Das ist übrigens auch beim Auto-Rest Feature, was im ersten Blog-Posting behandelt wurde, der Fall - der Modulname entspricht dann dem Tabellennamen. Heute werden wir den Modulnamen explizit festlegen. Unter anderem müssen wir folgende Angaben machen.
  • Ein Modulname (P_MODULE_NAME) muss festgelegt werden - wir nehmen plsql.
  • Die HTTP-Methode (GET, POST, PUT, DELETE und andere) muss festgelegt werden (P_METHOD). Wir nehmen GET.
  • Die URL-Schemata für Modul (P_BASE_PATH) und Methode (P_PATTERN) müssen bestimmt werden - /ords/{schema}/{modul-alias}/{service-pattern}.
  • Schließlich müssen Quelltyp (P_SOURCE_TYPE) und konkreter Quellcode (P_SOURCE) festgelegt werden.
begin
  ords.create_service(
    p_module_name => 'myplsql',
    p_base_path =>   '/plsql/',
    p_pattern =>     'hello',
    p_method =>      'GET',
    p_source_type => ords.source_type_query_one_row,
    p_source =>      q'#select 'Hello World' as greeting from dual#'
  );
end;
/
sho err
Die konkrete Implementierung kann nun eine SQL-Query sein, die eine oder mehrere Zeilen zurückliefert, oder aber PL/SQL-Code, der gar nichts zurückgibt. ORDS stellt bestimmte Source Types bereit; der Parameter P_SOURCE_TYPE legt fest, welcher es sein soll. Leider enthält die Dokumentation keine Auflistung - ein Blick in die View ALL_SOURCE für das PACKAGE ORDS hilft aber schnell weiter ...
SQL> select text from all_source where name='ORDS' and type='PACKAGE' order by line;

PACKAGE               ORDS AUTHID current_user
AS
  source_type_query           CONSTANT varchar(255) := 'json/query';
  source_type_csv_query       CONSTANT varchar(255) := 'csv/query';
  source_type_query_one_row   CONSTANT varchar(255) := 'json/query;type=single'; 
  source_type_feed            CONSTANT varchar(255) := 'json/query;type=feed';
  source_type_media           CONSTANT varchar(255) := 'resource/lob';
  source_type_plsql           CONSTANT varchar(255) := 'plsql/block';
  source_type_collection_feed CONSTANT varchar(255) := 'json/collection';
  source_type_collection_item CONSTANT varchar(255) := 'json/item';
:
Im Beispiel oben haben wir ORDS.SOURCE_TYPE_QUERY_ONE_ROW verwendet, die SQL-Query, die als Parameter P_SOURCE übergeben wird, muss also genau eine Zeile zurückliefern. Vergesst zum Abschluss das COMMIT nicht und danach könnt Ihr den REST-Service ausprobieren: Ruft mit eurem Browser die URL http://{ord-host}:{ords-port}/ords/{schema}/plsql/hello auf.
Als nächstes wäre es natürlich gut, wenn man einen solchen GET-Service parametrisieren könnte. Und das ist sehr einfach. Der folgende PL/SQL-Call richtet den Service so ein, dass der Parameter einfach ein Teil der verwendeten URL ist - in der SQL-Query oder dem PL/SQL-Code verwendet man dann einfach die Bindevariablen-Syntax.
begin
  ords.create_service(
    p_module_name => 'myplsql',
    p_base_path =>   '/plsql/',
    p_pattern =>     'hello/{name}',
    p_method =>      'GET',
    p_source_type => ords.source_type_query_one_row,
    p_source =>      q'#select 'Hello World, ' || :name as greeting from dual#'
  );
end;
/
sho err
Da wir den gleichen Modulnamen und die gleiche HTTP-Methode verwenden, sieht es so aus, als ob wir den bestehenden Service überschreiben. Das geht so aber nicht.
begin
*
FEHLER in Zeile 1:
ORA-00001: Unique Constraint (ORDS_METADATA.ORDS_MODULES_UNIQUE1) verletzt
ORA-06512: in "ORDS_METADATA.ORDS_SERVICES_INTERNAL", Zeile 27
ORA-06512: in "ORDS_METADATA.ORDS_SERVICES", Zeile 42
ORA-06512: in "ORDS_METADATA.ORDS_SERVICES", Zeile 24
:
Diesen Service gibt es schon, man kann ihn also nicht nochmal anlegen. Am besten wäre es also, den bestehenden Service oder gar das bestehende Modul zu löschen. Leider bietet das Paket ORDS keine Prozedur zum Löschen eines Service oder eines Moduls an; das wurde wohl schlicht vergessen. Zum Löschen des Moduls muss man, zumindest derzeit, noch etwas mit dem undokumentierten Paket ORDS_SERVICES tricksen - ich gehe aber davon aus, dass die fehlenden Prozeduren auf absehbare Zeit in das Paket ORDS aufgenommen und dokumentiert werden. Der folgende PL/SQL-Code löscht das Modul myplsql.
declare
  l_int_module_id user_ords_modules.id%TYPE;
begin
  select id into l_int_module_id 
  from user_ords_modules
  where name = 'myplsql';

  ords_services.delete_module(p_id => l_int_module_id);
end;
/
sho err
Nun könnt Ihr die neue Version der Implementierung - mit dem Parameter einspielen. Vergesst das Commit nicht. Wenn Ihr dann den Service ausprobiert und die URL http://{ord-host}:{ords-port}/ords/{schema}/plsql/hello/ORDS aufruft ...
Probieren wir nun mal die anderen Quelltypen für Queries aus ... zunächst wollen wir einen REST-Service, der eine SQL-Query ausführt und mehrere Zeilen zurückgibt. Für ein Subset der Tabelle EMP nehmt Ihr SOURCE_TYPE_QUERY als Source Type. Für alle folgenden Beispiele gilt, dass Ihr entweder einen anderen Modulnamen hernehmt oder das vorhandene Modul vorher löscht.
begin
  ords.create_service(
    p_module_name => 'myplsql',
    p_base_path =>   '/plsql/',
    p_pattern =>     'emp/{deptno}',
    p_method =>      'GET',
    p_source_type => ords.source_type_query,
    p_source =>      q'#select empno, ename, job, sal from emp where deptno = :deptno#'
  );
end;
/
sho err
Das Ergebnis des GET-Requests ...
Probiert als nächstes SOURCE_TYPE_CSV_QUERY aus.
begin
  ords.create_service(
    p_module_name => 'myplsql',
    p_base_path =>   '/plsql/',
    p_pattern =>     'emp/{deptno}',
    p_method =>      'GET',
    p_source_type => ords.source_type_csv_query,
    p_source =>      q'#select empno, ename, job, sal from emp where deptno = :deptno#'
  );
end;
/
sho err
Das Ergebnis des GET-Requests ...
Und so könnte man das Spiel nun weiter treiben. Als nächstes wollen wir PL/SQL-Code per REST-Request ausführen. Es soll ein Raise Salary-Service eingerichtet werden; per URL müssen also Parameter für die EMPNO und für die "Gehaltserhöhung" mitgegeben werden. Hierfür verwenden wir die HTTP-Methode POST - und das hat auch Konsequenzen auf die Parameterübergabe - denn nun sind wir nicht mehr auf die URL angewiesen ...
begin
  ords.create_service(
    p_module_name => 'myplsql',
    p_base_path =>   '/plsql/',
    p_pattern =>     'raise-sal',
    p_method =>      'POST',
    p_source_type => ords.source_type_plsql,
    p_source =>      q'#begin update emp set sal = sal + :raise where empno = :empno; htp.p('{"status": "success"}'); end;#'
  );
end;
/
sho err
Wie Ihr seht, ist das URL-Schema für den neuen Raise-Salary Service nur noch /plsql/raise-sal. Von Parametern ist hier nichts zu sehen. Als HTTP-Methode ist POST eingetragen der Source Type ist nun SOURCE_TYPE_PLSQL. Die Quelle selbst ist logischerweise ein anonymer PL/SQL-Block, in dem die Parameter einfach als Bindevariablen verwendet werden. Zum Ausprobieren reicht der einfache Browser nun nicht mehr aus - es braucht einen REST-Client. Das folgende Bild zeigt den Advanced REST Client für Chrome.
Mit dem REST Client wurde ein POST-Request ausgeführt - passend zur Definition des Services. Die Parameter sind nun nicht mehr in der URL enthalten, vielmehr werden sie als Request Body (Payload) gesendet. Wichtig ist, dass der HTTP-Header Content-Type auf application/json gesetzt wird, dann parst ORDS das übergebene JSON automatisch und stellt die Attribute dem PL/SQL-Code als Bind-Variablen zur Verfügung.
Für heute soll das genug sein - zwar sind noch einige Fragen offen, diese werden dann aber in einem der nächsten Blog-Postings behandelt - Stay tuned.

Mittwoch, 1. Juli 2015

"REST-Enabling" einer Tabelle mit Oracle12c und ORDS 3.0

Ab sofort starte ich dieses neue Blog zu "modernen" Entwicklerthemen wie REST, Node.js, JSON und anderen - im Zusammenspiel mit der Oracle-Datenbank. Teilweise werde ich Postings aus meinem anderen Blog SQL und PL/SQL in Oracle oder der deutschsprachigen APEX-Community hier wiederverwenden, wenn sie zum Thema passen (so auch das folgende, erste Blog-Posting). Der Fokus dieses Blogs wird aber auf jeden Fall auf den genannten Themen liegen; hier ist also eure Anlaufstelle für Modern Application Developent - mit der Oracle-Datenbank.
Viel Spaß beim Lesen!
In diesem ersten Blog Posting möchte ich mich den Oracle Rest Data Services (ORDS) widmen. ORDS hat seine Ursprünge im APEX Listener, dem javabasierten Webserver für Application Express. Dieser wurde Stück für Stück um Funktionen zum Bereitstellen von REST-Webservices erweitert - und in diesem Zusammenhang wurde der Name von "APEX Listener" auf "Oracle Rest Data Services geändert". ORDS bietet folgende Funktionen an.
  • Webserver für Application Express
  • REST-Endpoint für relationale Tabellen und Views
  • REST-Endpoint für PL/SQL Funktionen, Prozeduren und anonyme Blöcke
  • REST-Endpoint für die JSON-Funktionen in der Oracle Datenbank 12c (SODA)
  • REST-Endpoint für die Oracle NoSQL DB
Man sieht, dass der Namenswechsel hin zum Thema "REST" absolut gerechtfertigt ist; "APEX Listener" würde den Möglichkeiten nicht wirklich gerecht werden. Im ersten Blog-Posting zu ORDS möchte ich den zweiten Punkt herausgreifen: ORDS bietet wirklich sehr schöne Möglichkeiten an, mit sehr wenig Aufwand REST-Endpoints für Tabellen und Views in einem Datenbankschema bereitzustellen.
Ladet euch ORDS zunächst herunter und packt das ZIP-Archiv aus. Haltet euch Verbindungdaten zur Datenbank, mit der Ihr arbeiten möchtet bereit - Ihr müsst euch als SYS anmelden. Für das heutige Blog Posting muss es nicht zwingend eine 12c-Datenbank sein, eine 11g tut es auch. Nach dem Herunterladen und Auspacken solltet Ihr einen Ordner mit folgenden Dateien haben.
O:\>dir
 Volume in drive O is Data
 Volume Serial Number is 5054-5D26

 Directory of O:\

24.06.2015  15:34    <DIR>          .
24.06.2015  15:34    <DIR>          ..
24.06.2015  15:15    <DIR>          docs
24.06.2015  15:15    <DIR>          examples
12.05.2015  17:04        46.105.880 ords.war
01.05.2015  10:28            23.806 readme.html
               2 File(s)     46.129.686 bytes
               4 Dir(s)  195.599.679.488 bytes free
Nun geht es daran, ORDS erstmalig zu starten - ORDS wird dabei ein Metadaten-Schema in die Datenbank installieren, denn die Definitionen der REST-Services werden in der Datenbank gespeichert. Startet die Installation also mit java -jar ords.war install. Der Installer fragt euch nun nach den Angaben zur Datenbank, also Hostnamen, Listener Port, Service Name und schließlich auch das DBA-Password - das wird zur Installation des Metadaten-Schemas gebraucht.
O:\>java -jar ords.war install
Enter the name of the database server [localhost]:sccloud034
Enter the database listen port [1521]:1521
Enter 1 to specify the database service name, or 2 to specify the database SID [1]:1
Enter the database service name:pdb01.de.oracle.com
Enter 1 if you want to verify/install Oracle REST Data Services schema or 2 to skip this step [1]:1
Enter the database password for ORDS_PUBLIC_USER:******
Confirm password:******

Please login with SYSDBA privileges to verify Oracle REST Data Services schema. Installation may be required.


Enter the username with SYSDBA privileges to verify the installation [SYS]:SYS
Enter the database password for SYS:******
Confirm password:******
Jun 24, 2015 3:42:57 PM oracle.dbtools.rt.config.setup.SchemaSetup addSchemaParams
INFO:
Oracle REST Data Services schema does not exist and will be created.


Enter the default tablespace for ORDS_METADATA [SYSAUX]:SYSAUX
Enter the temporary tablespace for ORDS_METADATA [TEMP]:TEMP
Enter the default tablespace for ORDS_PUBLIC_USER [USERS]:USERS
Enter the temporary tablespace for ORDS_PUBLIC_USER [TEMP]:TEMP
:
Danach geht es mit einer Frage zum APEX Listener weiter - diese ist nur interessant, wenn der ORDS gleichzeitig als APEX-Webserver dienen soll. Das steht heute nicht im Mittelpunkt, daher könnt Ihr den Punkt überspringen.
Enter 1 if you want to use PL/SQL Gateway or 2 to skip this step [1]:2
Enter 1 to specify passwords for Application Express RESTful Services database users (APEX_LISTENER, APEX_REST_PUBLIC_USER) or 2 to skip this step [1]:2
Dann seht Ihr einige Statusmeldungen ...
Jun 24, 2015 3:43:04 PM oracle.dbtools.common.config.file.ConfigurationFilesBase update
INFO: Updated configurations: defaults, apex_pu
Jun 24, 2015 3:43:04 PM oracle.dbtools.installer.Installer installORDS
INFO:
Installing Oracle REST Data Services version 3.0.0.121.10.23
... Log file written to C:\Users\cczarski\ordsinstall_2015-06-24_154305_00043.log
... Verified database prerequisites
... Created Oracle REST Data Services schema
... Granted privileges to Oracle REST Data Services
... Created Oracle REST Data Services database objects
... Created Oracle REST Data Services proxy user
Jun 24, 2015 3:43:50 PM oracle.dbtools.installer.Installer installORDS
INFO: Completed installation for Oracle REST Data Services version 3.0.0.121.10.23. Elapsed time: 00:00:45.343
Zum Abschluß kommt noch die Frage, ob ORDS im Standalone Modus starten soll oder ob Ihr diesen in einen Java-Server wie Weblogic oder Tomcat deployen wollt. Für heute reicht uns der Standalone-Modus aus; den HTTP-Port, nach dem er uns dann fragen wird, legen wir mit 8081 fest.
Enter 1 if you wish to start in standalone mode or 2 to exit [1]:1
Enter the HTTP port [8080]:8081
Soweit ist die Installation fertig; die letzte Statusmeldung sagt euch, dass ORDS jetzt läuft.
:
Jun 24, 2015 3:46:35 PM oracle.dbtools.common.config.db.DatabasePools validatePool
INFO: Pool: apex_pu is correctly configured
2015-06-24 15:46:35.505:INFO:/ords:main: INFO: Oracle REST Data Services initialized|Oracle REST Data Services version :
 3.0.0.121.10.23|Oracle REST Data Services server info: jetty/9.2.z-SNAPSHOT|
2015-06-24 15:46:35.508:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@4bb4de6a{/ords,null,AVAILA
BLE}
2015-06-24 15:46:35.588:INFO:oejs.ServerConnector:main: Started ServerConnector@17c386de{HTTP/1.1}{0.0.0.0:8081}
2015-06-24 15:46:35.589:INFO:oejs.Server:main: Started @307536ms
Doch was macht man damit? Wenn man nun die URL {hostname}:8081/ords aufruft, passiert noch gar nichts: Es gibt eine HTTP-404-Fehlermeldung (Not Found) und weiter sieht man nichts.
Grund ist, dass noch keine REST-Services definiert wurden. ORDS bringt dafür keine Web-Oberfläche mit, vielmehr kann man die REST-Services mit dem Oracle SQL Developer oder auf dem SQL-Prompt mit PL/SQL Calls einrichten. Für dieses Blog-Posting nehmen wir letzteren Ansatz. Verbindet euch also (mit dem SQL-Werkzeug eurer Wahl) auf das Schema SCOTT in der Datenbank, die Ihr beim Installieren von ORDS angegeben habt. Schaut euch darin das PL/SQL Paket ORDS an.
SQL> sho user
USER ist "SCOTT"

SQL> desc ords
PROCEDURE CREATE_PRIVILEGE
 Argument Name                  Typ                     In/Out Defaultwert?
 ------------------------------ ----------------------- ------ --------
 P_NAME                         VARCHAR2                IN
 P_ROLES                        TABLE OF VARCHAR2(32000) IN
 P_LABEL                        VARCHAR2                IN     DEFAULT
 P_DESCRIPTION                  VARCHAR2                IN     DEFAULT
PROCEDURE CREATE_PRIVILEGE
 Argument Name                  Typ                     In/Out Defaultwert?
 ------------------------------ ----------------------- ------ --------
 P_NAME                         VARCHAR2                IN
 P_ROLE_NAME                    VARCHAR2                IN
 P_LABEL                        VARCHAR2                IN     DEFAULT
 P_DESCRIPTION                  VARCHAR2                IN     DEFAULT
PROCEDURE CREATE_PRIVILEGE_MAPPING
 Argument Name                  Typ                     In/Out Defaultwert?
 ------------------------------ ----------------------- ------ --------
 P_PRIVILEGE_NAME               VARCHAR2                IN
 P_PATTERNS                     TABLE OF VARCHAR2(32000) IN
:
Besonders interessant sind die Prozeduren ENABLE_SCHEMA und ENABLE_OBJECT. Diese machen das Auto-Rest Feature aus, mit dem sich in wenigen Handgriffen ein REST-Endpoint für eine Tabelle erstellen lässt. Und das machen wir nun. Zuerst müssen wir REST grundsätzlich für das Schema SCOTT freischalten.
begin
  ords.enable_schema (
    P_ENABLED        => true,
    P_SCHEMA         => 'SCOTT',
    P_AUTO_REST_AUTH => false
  );
end;
/
sho err
Die Benutzung von ENABLE_SCHEMA ist wirklich einfach. Der erste Parameter (P_ENABLED) legt fest, ob das Schema freigegeben oder gesperrt sein soll; danach kommt das Schema selbst (P_SCHEMA). Der letzte Parameter P_AUTO_REST_AUTH legt fest, ob sich ein REST-Client authentizieren muss, wenn er die REST-Endpoints verwenden möchte. Der Default ist true, was das Sicherheitslevel etwas erhöht. Für unsere Tests setzen wir es jedoch auf false; Authentifizierung bleibt in diesem Blog-Posting zunächst außen vor. Setzt noch ein COMMIT ab; wir sind ja in der Datenbank. Allerdings steht nun immer noch kein REST-Endpoint bereit, denn wir haben noch keine Tabellen oder Views freigegeben. Das kommt jetzt - mit einem Aufruf von ENABLE_OBJECT.
begin
  ords.enable_object (
    P_ENABLED        => true,
    P_SCHEMA         => 'SCOTT',
    P_OBJECT         => 'EMP',
    P_OBJECT_TYPE    => 'TABLE',
    P_OBJECT_ALIAS   => 'the-emp-table',
    P_AUTO_REST_AUTH => false
  );
end;
/
sho err
Die Parameter P_ENABLED, P_SCHEMA und P_AUTO_REST_AUTH haben die gleiche Bedeutung wie bei ENABLE_SCHEMA. Zusätzlich muss man hier natürlich noch den Namen der Tabelle oder View (P_OBJECT) und (optional) einen URL-Alias (P_OBJECT_ALIAS) angeben. Nach dem obligatorischen COMMIT steht ein REST-Endpoint für die Tabelle EMP unter der URL /ords/scott/the-emp-table bereit. Probiert es aus, und ruft die URL mit dem Browser auf - ihr solltet die Inhalte der Tabelle EMP im JSON-Format sehen.
Stellt nun sicher, dass die Spalte EMPNO der Tabelle EMP als Primärschlüssel definiert ist; oft ist das nicht der Fall.
SQL> alter table EMP add constraint PK_EMP primary key (EMPNO);
Jetzt könnt Ihr per URL auch einzelne Zeilen ansteuern; während die URL /ords/scott/the-emp-table alle Zeilen der Tabelle zurückliefert, liefert die URL /ords/scott/the-emp-table/7839 nur die eine Zeile mit der EMPNO 7839 zurück. Darüber hinaus unterstützt ORDS auch eine "JSON-Query"-Syntax. Probiert mal folgende URLs aus:
  • /ords/scott/the-emp-table/?q={"sal":{"$lt":1000}}
  • /ords/scott/the-emp-table/?q={"ename":{"$like":"S%25"}}
  • /ords/scott/the-emp-table/?q={"ename":{"$eq":"KING"}}
Die komplette Beschreibung der Query-Syntax findet Ihr in der Dokumentation. Zum Lesen der Tabelle gibt es also schon eine sehr elegante REST-Schnittstelle - aber ORDS kann noch mehr.
Setzt man anstelle des HTTP-GET-Requests einen PUT, POST oder DELETE-Request ab, so können die Inhalte der Tabelle auch verändert werden. Hierzu reicht der einfache Browser aber nicht aus; Ihr braucht einen REST-Client. Diese sind auch als Browser-Plugins erhältlich - so gibt es für Chrome die App Advanced REST Client; für Firefox ist das Addon REST-Client verfügbar und als Standalone-Anwendung kommen Kandidaten wie Postman in Frage. Das folgende Bild zeigt die Chrome-App Advanced REST Client.
Nun wollen wir per REST-Request eine Zeile in die Tabelle einfügen. Macht im Chrome Advanced Rest Client folgende Angaben (andere REST-Clients sehen ähnlich aus).
  • Legt PUT als Request-Type fest.
  • Als URL legt Ihr /ords/scott/the-emp-table/8999 fest; hier geben wir den Wert für die EMPNO schon in der URL an, weil die Tabelle EMP keine Sequence und keinen Trigger hat, um die ID automatisch zu generieren. Weiter unten erfahrt Ihr, wie man eine neue Tabelle sofort so erzeugt, dass man eine neue Zeile per POST-Request einfügen kann.
  • Als Payload oder Request Body tragt Ihr die Daten der neuen Zeile im JSON-Format ein, also wie folgt:
    {"empno": 8999, "ename": "CZARSKI", "job": "BLOGGER", "sal": 0, "comm": 0, "mgr": 7839, "deptno": 30}
    
  • Achtet schließlich darauf, dass der HTTP-Header Content-Type auf application/json gesetzt ist; bei der Chrome-App ist das der Default; anderswo muss man es explizit einstellen.
Wenn Ihr den Request dann absendet, bekommt Ihr eine Antwort, welche nochmals die neue Zeile im JSON-Format enthält. Eine Prüfung der Tabelle EMP im SQL-Tool zeigt euch, dass tatsächlich eine Zeile eingefügt wurde.
SQL> select * from emp;

EMPNO ENAME      JOB         MGR HIREDATE              SAL  COMM DEPTNO
----- ---------- --------- ----- ------------------- ----- ----- ------
 7369 SMITH      CLERK      7902 17.12.1980 00:00:00   800           20
 7499 ALLEN      SALESMAN   7698 20.02.1981 00:00:00  1600   300     30
 
 : 

 7934 MILLER     CLERK      7782 23.01.1982 00:00:00  1300           10
 8999 CZARSKI    BLOGGER    7839                         0     0     30
Ein erneutes HTTP-PUT auf die gleiche URL (mit der 8999) führt zu einem Update der Zeile. Um sie zu löschen, braucht es einen DELETE-Request.
  • Legt DELETE als Request-Type fest.
  • Als URL legt Ihr /ords/scott/the-emp-table/8999 fest.
  • Als Payload oder Request Body tragt Ihr nichts ein
  • Stellt sicher, dass der HTTP-Header Content-Type nun auf text/plain gesetzt ist; wenn er noch auf application/json steht, müsst Ihr ihn umstellen.
Ihr solltet die Antwort bekommen, dass eine Zeile gelöscht wurde; eine Kontrolle der EMP-Tabelle sollte dann ergeben, dass die neue Zeile tatsächlich weg ist. Für DELETE-Requests könnt Ihr auch die JSON-Query-Syntax verwenden, die weiter oben bei den GET-Requests beschrieben wurde. Diese Syntax erlaubt euch auch, mehrere Zeilen auf einmal zu löschen.
Um das Bild vollständig zu machen, ein weiteres Beispiel - dieses Mal legen wir eine neue Tabelle an, die wir per REST-Requests bearbeiten wollen - die Tabelle soll sich anfühlen wie eine "JSON-Document-Collection"; tatsächlich ist es aber eine relationale Tabelle. Zuerst also die Tabelle anlegen: Dazu nutzen wir natürlich Oracle12c-Features.
create table tab_customer_collection(
  cust_id    number(10) generated by default on null as identity,
  cust_name  varchar2(200) not null,
  cust_city  varchar2(200) not null, 
  cust_email varchar2(200) not null,
  cust_lon   number,
  cust_lat   number,
  constraint pk_customer_collection primary key (cust_id)
)
/
Nun die Tabelle mit ORDS.ENABLE_SCHEMA freigeben - wir nehmen diesmal aber einen anderen URL-Alias: customers-collection...
begin
  ords.enable_object (
    P_ENABLED        => true,
    P_SCHEMA         => 'SCOTT',
    P_OBJECT         => 'TAB_CUSTOMER_COLLECTION',
    P_OBJECT_TYPE    => 'TABLE',
    P_OBJECT_ALIAS   => 'customers-collection',
    P_AUTO_REST_AUTH => false
  );
end;
/
sho err
Und schon kann's losgehen ...
  • Um eine Zeile einzufügen (INSERT macht Ihr einen POST-Request an die URL /ords/scott/customers-collection/. Verwendet folgendes JSON im Request Body:
    {
      "cust_name": "Carsten Czarski",
      "cust_city": "Munich",
      "cust_email": "email@restmail.com",
      "cust_lon": 11.5,
      "cust_lat": 48.2
    }
    
    Als Ergebnis bekommt Ihr die URL zum neu generierten Dokument bzw. zur neuen Zeile zurück; daran erkennt Ihr auch die generierte ID (sollte eine "1" sein): /ords/scott/customers-collection/1.
  • Um eure Daten zu sehen, macht Ihr einen GET-Request. Wenn Ihr die URL der Collection (/ords/scott/customers-collection/) verwendet, seht Ihr alle Daten, wenn Ihr die (generierte) CUST_ID anhängt (/ords/scott/customers-collection/1), nur eine Zeile.
  • Wenn Ihr eine Zeile ändern wollt (UPDATE), macht Ihr einen PUT Request auf ein konkretes Dokument (/ords/scott/customers-collection/1). Das JSON im Request-Body muss neue Werte für alle Tabellenzeilen beinhalten.
  • Das Löschen einer Zeile (DELETE) erfolgt, wie oben schon gesehen, mit einem DELETE-Request auf das konkrete Dokument: (/ords/scott/customers-collection/1). Achtet darauf, dass der Request Body leer ist und der Content-Type auf text/plain steht.
Und das ist es: Mit ganz wenig Aufwand habt Ihr eine REST-Schnittstelle zur Oracle-Datenbank geschaffen. Die REST-Schnittstelle fühlt sich an, wie ein Document-Store - die Details der Tabelle sind komplett verborgen. Einfacher geht es wirklich nicht.