Dienstag, 29. September 2015

Dateien in Tabellen speichern: Komplette REST-API mit ORDS

Heute geht es doch nochmals um ORDS - das angekündigte Posting zu Node.js muss noch etwas warten. Ich möchte heute zeigen, wie Ihr mit der Oracle-Datenbank und ORDS einen REST-Service aufsetzt, der Dateien in einer Tabelle speichert und aus dieser abrufen kann. PUT und POST Requests sollen Dateien (Bilder, Dokumente) in eine Tabelle laden, GET-Requests sollen sie abrufen; DELETE logischerweise löschen. Dazu soll genau ein REST-Modul entstehen, welches diese vier Fälle (und noch einen mehr) komplett abdeckt.
Im letzten Blog-Posting wurde ja das neue rest-Kommando im neuen SQL Developer Command Line Interface (sqlcl vorgestellt - die Ausgabe dieses Tools ist die Vorlage für das Erzeugen unseres neuen REST-Service, denn ich möchte euch gerne die Skripte zeigen; die lassen sich doch wesentlich leichter nachvollziehen als das Klicken in einem Werkzeug. Wir werden in diesem Posting viel mit dem (derzeit undokumentierten) PL/SQL-Paket ORDS_SERVICES arbeiten.
Dieses Beispiel lässt sich übrigens auch ganz wunderbar im Oracle Database Cloud Service nachvollziehen; das ist sicherlich einer der einfachsten und schnellsten Wege, an einen laufenden Datenbank-Service im Internet zu kommen.
Zuerst braucht es, wie immer in einer Datenbank, eine Tabelle. Die legen wir auch genz einfach an. Dieses Blog-Posting geht davon aus, dass euer Datenbankschema DOCUMENTS heißt - passt die Skripte bei Bedarf einfach an.
create table my_documents(
  id           number(10)   generated always as identity
 ,mimetype     varchar2(200) 
 ,content      blob
 ,created      date         default sysdate not null
 ,last_updated date         default sysdate not null
 ,constraint  pk_mydocuments primary key (id)
)
/
Danach geht es an die Erstellung der REST-Services. Der erste Service soll zum Hochladen von Dokumenten dienen. Ihr könnt die REST-Services mit dem SQL Developer anlegen, in diesem Blog-Posting zeige ich aber den Skriptbasierten Ansatz; Ihr könnt also in eurem SQL-Tool mit Copy & Paste arbeiten. Zuerst sollte das Datenbankschema für REST-Zugriffe freigegeben werden (ORDS.ENABLE_SCHEMA). Führt diesen Call nicht als DBA; sondern als User DOCUMENTS aus. Ich gehe davon aus, dass euer ORDS bereits läuft - wenn nicht, könnt Ihr hier Details zum initiale Setup nachlesen.
BEGIN
  ORDS.ENABLE_SCHEMA(
    p_enabled             => TRUE,
    p_schema              => 'DOCUMENTS',
    p_url_mapping_type    => 'BASE_PATH',
    p_url_mapping_pattern => 'documents',
    p_auto_rest_auth      => FALSE
  );
END;
/

COMMIT
/
Als nächstes erzeugen wir den REST-Service. Das folgende Skript löscht zuerst ein etwaiges bereits definiertes REST-Modul docmodule; danach wird es neu angelegt. Ein Module ist als "Container" für mehrere zusammengehörige REST-Dienste zu verstehen. Direkt danach kommt die Definition der URL-Templates. Einem URL-Template können dann mit ORDS_SERVICES.ADD_HANDLER die REST-Dienste zugeordnet werden - und zwar genau ein Dienst pro HTTP-Methode (GET, PUT, POST, DELETE und andere).
declare
  l_moduleid   number;
  l_templateid number;
  l_handlerid  number;
begin
  -- evtl. vorhandenes REST-Modul löschen
  ords_services.delete_module('docmodule');

  -- REST Modul anlegen
  l_moduleid := ords_services.create_module(
    p_name       => 'docmodule'
   ,p_uri_prefix => 'doc/'
   ,p_status     => 'PUBLISHED'
  );

  -- 1. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/'
  );

  -- POST-Handler für das URL Template. Der REQUEST führt
  -- PL/SQL-Code aus und speichert die per Request hochgeladene Datei (:body) in die
  -- Tabelle ab.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'declare
                         l_id my_documents.id%type;
                       begin 
                        insert into my_documents(
                          mimetype, content
                        ) values (
                          :content_type, :body
                        ) returning id into l_id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "created", "id": ''||l_id||''}'');
                       end;'
   ,p_method       => 'POST'
  );
end;
/

commit
/
Der entscheidende Code befindet sich im Aufruf von ORDS_SERVICES.ADD_HANDLER. Hier wird als Argument für P_SOURCE ein PL/SQL-Block übergeben. Der macht im Grunde nichts weiter als ein SQL INSERT in die oben erzeugte Tabelle MY_DOCUMENTS. Wenn Ihr hier PL/SQL-Code schreibt, stellt ORDS euch auch einige Umgebungsvariablen bereit: Zwei seht ihr hier: :body enthält den konkreten Request Body des POST-Requests und :content_type enthält den Mime-Type, den der REST-Client im HTTP-Header Content-Type mitgeschickt hat. Es gibt aber noch ein paar mehr - dieses Blog-Posting von Jon Dixon listet sie auf. Wenn der INSERT erfolgt ist, wird die neu erzeugte ID des Dokument in einem JSON-Format zurückgegeben. Probiert den neuen REST-Service mal aus - mit dem Chrome Advanced REST Client sieht das ganze so aus ...
... und mit dem Kommandozeilentool curl so (hier sind noch einige Statusmeldungen dabei). Ich persönlich habe eine Vorliebe für curl zum Testen von REST-Services entwickelt - damit ist man einfach nicht mehr den "Launen" der verschiedenen GUI-Werkzeuge ausgeliefert (und irgendeine Marotte haben sie alle).
D:\>curl -v -X POST "http://localhost:8081/ords/documents/doc/documents/" --data-binary "@ar.png" -H "Content-Type:image/png"
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8081 (#0)
> POST /ords/documents/doc/documents/ HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.44.0
> Accept: */*
> Content-Type:image/png
> Content-Length: 46098
> Expect: 100-continue
>
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Transfer-Encoding: chunked
<
{"status": "created", "id": 2}
* Connection #0 to host localhost left intact
Schauen wir uns die Daten in der Tabelle an - das geschieht im SQL-Client.
SQL> select * from my_documents;

  ID MIMETYPE   CONTENT                        CREATED             LAST_UPDATED
---- ---------- ------------------------------ ------------------- -------------------
   3 image/png  89504E470D0A1A0A0000000D494844 29.09.2015 11:01:22 29.09.2015 11:01:22
                52000003850000021B0802000000D3
                A9AC70000000097048597300000EC4
                00000EC401952B0E1B000020004944
                4154789CEC9D796055C5D9FF9F67CE
                0D9B408424

   4 image/png  89504E471A01DFDA562FD82A0151AA 29.09.2015 11:01:58 29.09.2015 11:01:58
                C43D8A8822EE0BB1EFFB6B53B1AE68
                5B3535EDAB2501AD5A2D4A6D956A5B
                631537625D63DFAA80D1BEF5B56025
                B26917D942EE39F3FCFE983973E62C
                F7DE736F2E
Das ist doch ein sehr einfacher und angenehmer Weg, Dateien in eine Tabelle zu laden, oder? Und damit ist der erste REST-Service (Upload) auch schon fertig. Die nächsten drei Services liegen nahe. Für das URL-Pattern /doc/documents/{id} soll das Dokument mit der jeweiligen ID ...
  • mit der Methode GET abgerufen
  • mit der Methode PUT aktualisiert
  • mit der Methode DELETE gelöscht
... werden. Das Skript dafür sieht so aus (damit Ihr einfaches Copy & Paste machen könnt, hier nochmals das vollständige Skript. Der bereits vorhandene erste Teil ist ausgegraut.
declare
  l_moduleid   number;
  l_templateid number;
  l_handlerid  number;
begin
  -- evtl. vorhandenes REST-Modul löschen
  ords_services.delete_module('docmodule');

  -- REST Modul anlegen
  l_moduleid := ords_services.create_module(
    p_name       => 'docmodule'
   ,p_uri_prefix => 'doc/'
   ,p_status     => 'PUBLISHED'
  );

  -- 1. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/'
  );

  -- POST-Handler für das URL Template. Der REQUEST führt
  -- PL/SQL-Code aus und speichert die per Request hochgeladene Datei (:body) in die
  -- Tabelle ab.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'declare
                         l_id my_documents.id%type;
                       begin 
                        insert into my_documents(
                          mimetype, content
                        ) values (
                          :content_type, :body
                        ) returning id into l_id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "created", "id": ''||l_id||''}'');
                       end;'
   ,p_method       => 'POST'
  );

  -- 2. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/{id}'
  );

  -- GET-Handler für das URL Template. Das angefragte Dokument ({id}) wird
  -- per SQL Query abgerufen und ausgegeben. Dazu bietet ORDS einen fertigen
  -- "Source Type" MEDIA QUERY an.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_media
   ,p_source       => 'select mimetype, content from my_documents where id = :id'
   ,p_method       => 'GET'
  );

  -- DELETE-Handler für das URL Template. Es wird (per PL/SQL-Block) eine 
  -- SQL DELETE-Anweisung ausgeführt.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'begin 
                        delete from my_documents where id = :id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "deleted"}'');
                       end;'
   ,p_method       => 'DELETE'
  );

  -- PUT-Handler für das URL Template. Es wird (per PL/SQL-Block) eine 
  -- SQL UPDATE-Anweisung ausgeführt.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'begin 
                        update my_documents set content = :body, mimetype = :content_type, last_updated = sysdate where id = :id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "updated", "id": ''||:id||''}'');
                       end;'
   ,p_method       => 'PUT'
  );
end;
/

commit
/
Testen wir die neuen REST-Dienste.
  • Beginnen wir mit GET (das Dokument soll in die Datei file.bin) gespeichert werden:
    D:\>curl -v -X GET http://localhost:8081/ords/documents/doc/documents/3 -o file.bin
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1...
    * Connected to localhost (127.0.0.1) port 8081 (#0)
    > GET /ords/documents/doc/documents/5 HTTP/1.1
    > Host: localhost:8081
    > User-Agent: curl/7.44.0
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Content-Type: image/png
    < ETag: "ZuV0opwWReMeFm8NwmK8IPxuZfldcE+uB7n95A2Bwg6VE9z89b31m2VMNiKGWqTeD+IZ41hpPHAcZppbuOw/yA=="
    < Transfer-Encoding: chunked
    <
    { [16214 bytes data]
    100 46098    0 46098    0     0   261k      0 --:--:-- --:--:-- --:--:--  288k
    * Connection #0 to host localhost left intact
    
    An der Ausgabe (Content-Type) könnte der Client auch erkennen, was da konkret vom REST-Service ausgeliefert wird. In diesem Fall wäre es ein Bild (image/png).
  • Dann löschen wir ein Dokument ...
    D:\>curl -v -X DELETE http://localhost:8081/ords/documents/doc/documents/3
    *   Trying 127.0.0.1...
    * Connected to localhost (127.0.0.1) port 8081 (#0)
    > DELETE /ords/documents/doc/documents/3 HTTP/1.1
    > Host: localhost:8081
    > User-Agent: curl/7.44.0
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    < Transfer-Encoding: chunked
    <
    {"status": "deleted"}
    * Connection #0 to host localhost left intact
    
  • Und schließlich noch ein Update ...
    D:\>curl -v -X PUT "http://localhost:8081/ords/documents/doc/documents/4" --data-binary "@index.html" -H "Content-Type:text/html"
    *   Trying 127.0.0.1...
    * Connected to localhost (127.0.0.1) port 8081 (#0)
    > PUT /ords/documents/doc/documents/4 HTTP/1.1
    > Host: localhost:8081
    > User-Agent: curl/7.44.0
    > Accept: */*
    > Content-Type:text/html
    > Content-Length: 381
    >
    * upload completely sent off: 381 out of 381 bytes
    < HTTP/1.1 200 OK
    < Content-Type: text/html; charset=UTF-8
    < Transfer-Encoding: chunked
    <
    {"status": "updated", "id": 4}
    * Connection #0 to host localhost left intact
    
Fehlt zum Abschluß nur noch eine Übersicht über die bereits vorhandenen Dokumente - hierzu bietet sich wiederum ein GET-Request auf die Collection selbst an - also das URL-Pattern /doc/documents/. Wenn eine ID angehägt wird, soll das konkrete Dokument ausgeliefert werden (das ist bereits fertig), wenn keine ID angehängt wird, soll eine Übersicht - im JSON-Format mit Links zu den einzelnen Dokumenten - ausgegeben werden. Hier wiederum das komplette Skript - die Neuerungen sind farblich hervorgehoben.
declare
  l_moduleid   number;
  l_templateid number;
  l_handlerid  number;
begin
  -- evtl. vorhandenes REST-Modul löschen
  ords_services.delete_module('docmodule');

  -- REST Modul anlegen
  l_moduleid := ords_services.create_module(
    p_name       => 'docmodule'
   ,p_uri_prefix => 'doc/'
   ,p_status     => 'PUBLISHED'
  );

  -- 1. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/'
  );

  -- POST-Handler für das URL Template. Der REQUEST führt
  -- PL/SQL-Code aus und speichert die per Request hochgeladene Datei (:body) in die
  -- Tabelle ab.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'declare
                         l_id my_documents.id%type;
                       begin 
                        insert into my_documents(
                          mimetype, content
                        ) values (
                          :content_type, :body
                        ) returning id into l_id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "created", "id": ''||l_id||''}'');
                       end;'
   ,p_method       => 'POST'
  );

  -- GET-Handler für das URL Template "documents/" (ohne ID). Es wird (Source Type "feed")
  -- eine Übersicht mit allen Dokumenten ausgegeben.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_feed
   ,p_source       => 'select 
                         id, 
                         mimetype, 
                         dbms_lob.getlength(content) as doc_size, 
                         created, 
                         last_updated 
                       from my_documents'
   ,p_method       => 'GET'
  );

  -- 2. URL-Template
  l_templateid := ords_services.add_template(
    p_module_id    => l_moduleid
   ,p_uri_template => 'documents/{id}'
  );

  -- GET-Handler für das URL Template. Das angefragte Dokument ({id}) wird
  -- per SQL Query abgerufen und ausgegeben. Dazu bietet ORDS einen fertigen
  -- "Source Type" MEDIA QUERY an.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_media
   ,p_source       => 'select mimetype, content from my_documents where id = :id'
   ,p_method       => 'GET'
  );

  -- DELETE-Handler für das URL Template. Es wird (per PL/SQL-Block) eine 
  -- SQL DELETE-Anweisung ausgeführt.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'begin 
                        delete from my_documents where id = :id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "deleted"}'');
                       end;'
   ,p_method       => 'DELETE'
  );

  -- PUT-Handler für das URL Template. Es wird (per PL/SQL-Block) eine 
  -- SQL UPDATE-Anweisung ausgeführt.
  l_handlerid := ords_services.add_handler(
    p_template_id  => l_templateid
   ,p_source_type  => ords.source_type_plsql
   ,p_source       => 'begin 
                        update my_documents set content = :body, mimetype = :content_type, last_updated = sysdate where id = :id; 
                        owa_util.mime_header(''application/json'', true);
                        htp.p(''{"status": "updated", "id": ''||:id||''}'');
                       end;'
   ,p_method       => 'PUT'
  );
end;
/

commit
/
Probieren wir auch diesen Service aus ... erst Mal mit curl ...
D:\>curl -X GET -v http://localhost:8081/ords/documents/doc/documents/
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8081 (#0)
> GET /ords/documents/doc/documents/ HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.44.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< ETag: "Fr7HbBdEKCjVbKL+qMgCZu5WTPRr7dRMvrsdVzUskvYWkqM/OCEVrCeqYEbwUZ7Ki4u3deYNXSxIVJhIidX5Uw=="
< Transfer-Encoding: chunked
<
{"items":[{"uri":{"$ref":"http://localhost:8081/ords/documents/doc/documents/3"},"id":3,"mimetype":"image/png","doc_size
":46098,"created":"2015-09-29T09:01:22Z","last_updated":"2015-09-29T09:01:22Z"},{"uri":{"$ref":"http://localhost:8081/or
ds/documents/doc/documents/4"},"id":4,"mimetype":"text/html","doc_size":381,"created":"2015-09-29T09:01:58Z","last_updat
ed":"2015-09-29T09:01:58Z"}],"first":{"$ref":"http://localhost:8081/ords/documents/doc/documents/"}}
* Connection #0 to host localhost left intact
Dieser einfache GET-Request kann auch im Browser getestet werden ...
Auf diese Art und Weise könnt Ihr nun noch weitere Dienste hinzufügen und so einen komplette REST-Schnittstelle für die Tabelle schaffen - in der Praxis braucht es sicherlich noch ein paar Tabellen mehr. Die REST-Dienste können dann von beliebigen Anwendungen genutzt werden; ob Node.js, PHP, Java oder .NET: Alle greifen über die einheitliche REST-Schnittstelle zu; der konkrete Datenbanktreiber wird bedeutungslos und die Tabellenzugriffe werden so einfach wie noch nie. Viel Spaß damit.

Mittwoch, 23. September 2015

Oracle Rest Data Services (ORDS) jetzt auch im SQLcl

Heute gibt es nur ein sehr kurzes Blog-Posting (wieder von Rainer Willems), was aber gut zu den bereits vorhandenen Postings zu Oracle REST Data Services passt. In den nächsten Tagen kommt dann nochmals was umfangreicheres - zu Node.js - versprochen ;-)
Mit der aktuellen Version des neuen command line Interfaces SQLcl  (Release Candidate 4.2.0.15.254.0747) gibt es nun auch eine direkte Unterstützung des ORDS und der REST Data Services. Eine kleine, aber sehr nützliche Hilfestellung.
SQL> help rest

REST ------ REST allows to export ORDS 3.X services. REST export - All modules REST export <module_name> - Export a specific module REST export <module_prefix> - Export a specific module related to a given prefix REST modules - List the available modules REST privileges - List the existing privileges REST schemas - List the avaiable schemas
So bekommt man einen schnellen Überblick über die vorhandenen Einstellungen und eine sehr schnelle Export-Möglichkeit der Module.REST modules zeigt schön die vorhandenen Module (vorher set sqlformat ansiconsole absetzten, dann sieht es auch hübsch aus).


Mit REST EXPORT <module_name> bekommt man ein ein kleines PL/SQL-Skript generiert, mit dem das genannte Modul per Kommandozeile angelegt werden kann.


Damit erhält man also auch gleich ein kleines anschauliches Beispiel zum Entwickeln von Services mit dem Package ORDS_METADATA geliefert. In den vorhergehenden Posts wurde das Package ORDS mit der Methode CREATE_SERVICE zum Anlegen solcher Services verwendet. Mit diesem kann man aber aktuell nur einen Handler pro Modul anlegen.

Dienstag, 1. September 2015

REST-Services - völlig ohne Schema - mit "SODA": So geht's.

Ich freue mich, dass heutige Blog-Posting von Rainer Willems vorstellen zu können. Rainer zeigt, wie man SODA (Simple Oracle Document Access) einrichtet und damit REST-Services in der Oracle-Datenbank - völlig ohne Schema bereitstellen kann. Jedes beliebige JSON wird entgegengenommen. Ihr könnt Rainer auf Twitter folgen: @josifabr.

Als Abwechslung zur skriptbasierten Vorgehensweise, die ich in den letzten Blog-Postings verwendet habe, seht Ihr hier die Vorgehensweise mit dem SQL Developer. Viel Spaß beim Lesen und Ausprobieren.



Heute wollen wir uns mit SODA beschäftigen. Hierbei handelt es sich nicht um eine dem aktuellen Wetter angepasste Erfrischung, sondern um Simple Oracle Document Access. Carsten hat im Blog schon gezeigt, wie einfach eine bestehende Tabelle mit ORDS REST-fähig gemacht werden kann oder wie ein solcher Service mit PL/SQL aufgebaut werden kann.

Dies erfüllt aber noch nicht die Anforderungen von Entwicklern, vollständig flexibel - also ohne vorgegebenes Schema - arbeiten so können. NoSQL-Datenbanken preisen dies als Vorteil an. Auch Oracle wartet hier mit einer NoSQL-Datenbank auf. Habe ich aber ohnehin Oracle 12c im Einsatz, wäre es doch viel einfacher, diese eine (unter vielen, vielen anderen) Anforderung mit der gleichen Datenbank abzudecken. Und genau hier kommt SODA ins Spiel. SODA bietet die Möglichkeit die Datenbank als flexiblen schemaless Document-Store zu verwenden: Oracle DB als NoSQL JSON document store

Wir betrachten betrachten im Folgenden nun SODA for REST, ebenfalls verfügbar ist SODA for Java.

Voraussetzung auf Datenbankseite ist das Release 12.1.0.2 und der Patch 20885778 Ist dieser installiert und der ORDS wie beschrieben installiert, kann es losgehen; oder besser gesagt, sind wir eigentlich schon fertig.

Der Vollständigkeit halber hier die Beschreibung der Installation des ORDS mit dem SQL Developer. Unter Tools-REST data Services findet man den Punkt Install

Wir wählen entweder den in der aktuellen SQL Developer Version mitkommenden ORDS oder einen anderen, ggf. aktuelleren (Im momentan aktuellen SQL Developer 4.1.0.19 befindet sich auch der momentan aktuellste ORDS). Dazu wählen wir eine Lokation für die Konfigurationsfiles:


Wir verbinden uns mit der Datenbank und legen einen Public User an, welcher vergleicbar mit dem APEX_PUBLIC_USER ist, nur eben für die REST-Services.


Dies muss als sys geschehen, da in der Datenbank das Metadaten-Repository für die Services angelegt wird (Schema ORDS_METADATA)


Wir legen die Tablespaces fest.


und ignorieren in den kommenden beiden Steps die Einstellungen zum PL/SQL Gateway bzw. APEX.



Wir möchten die Services zum Testen Standalone laufen lassen und definieren den HTTP Port.



Achtung. Wer hier in Konflikt mit einem bereits verwendeten Port kommt und die Installation dann ein weiteres mal versuchen möchte, muss erst die Konfigurationsdatei im Filesystem löschen, da sonst weiterhin die beim ersten Versuch angegebene Port-Nummer verwendet wird.

Für Entwicklungszwecke definieren wir noch einen Admin- und einen Entwicklungsuser (keine Datenbankuser).




Ist der ORDS dann gestartet, sollten wir auch die dementsprechende Anzeige im Log bekommen.



Läuft der ORDS dann, erkennt man das auch an einem roten Quadrat in der Menuleiste des SQL Developer. Clickt man dieses an, wird der ORDS beendet ...


... und die Installation kann unter Tools - REST Data Services - Run wieder gestartet werden (wobei man wieder ein paar Fragen beantworten muss)





Jetzt braucht man nur noch das gewünschte Datenbankschema (das was nachher herauskommt ist trotzdem "schema-less"!) "REST-fähig" machen. Das kann man einfach über das Kontextmenu in den Verbindungen machen.


Der Schemaalias wird nachher in den URLs der REST Services verwendet.



Letztendlich wird hier die Prozedur ORDS.ENABLE_SCHEMA ausgeführt:
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN

    ORDS.ENABLE_SCHEMA(p_enabled => TRUE,
                       p_schema => 'SCOTT',
                       p_url_mapping_type => 'BASE_PATH',
                       p_url_mapping_pattern => 'scott',
                       p_auto_rest_auth => FALSE);
    
    commit;
END;

FERTIG !!!! Die Datenbank kann nun via REST als JSON Document Store verwendet werden. Zum Testen deaktivieren wir nur noch die Security (als User scott in diesem Fall hier). Aber bitte nur zum Testen:
begin
     ords.delete_privilege_mapping('oracle.soda.privilege.developer','/soda/*');
end;
 /

Ich verwende übrigens gerne den Postman, eine Extension für den Chrome-Browser, um REST-Zugriffe zu testen.

Mittels eines GET auf http://mymachine:port/myords/myschema/soda/latest/ kann man sich nun anschauen, welche Collections in unserem Schema vorhanden sind:



Es sind natürlich noch keine da. Eine neue Collection läßt sich mittels eines PUT unter Angabe des gewünschten Collection-Namens erzeugen



Diese erscheint direkt als Datenbank-Tabelle im dementsprechenden Schema.


Neben der ID und dem JSON Dokument als solchem werden noch die Version (von SODA), das Erstelldatum und die letzte Modifikation in der Tabelle gepflegt.



Frage ich nun nochmal alle Collections ab, erhalte ich meine gerade angelegte Collection:
{
  "items": [
    {
      "name": "mycollection",
      "properties": {
        "schemaName": "SCOTT",
        "tableName": "mycollection",
        "keyColumn": {
          "name": "ID",
          "sqlType": "VARCHAR2",
          "maxLength": 255,
          "assignmentMethod": "UUID"
        },
        "contentColumn": {
          "name": "JSON_DOCUMENT",
          "sqlType": "BLOB",
          "compress": "NONE",
          "cache": true,
          "encrypt": "NONE",
          "validation": "STANDARD"
        },
        "versionColumn": {
          "name": "VERSION",
          "type": "String",
          "method": "SHA256"
        },
        "lastModifiedColumn": {
          "name": "LAST_MODIFIED"
        },
        "creationTimeColumn": {
          "name": "CREATED_ON"
        },
        "readOnly": false
      },
      "links": [
        {
          "rel": "canonical",
          "href": "http://localhost:8080/ords/scott/soda/latest/mycollection"
        }
      ]
    }
  ],
  "more": false
}

Die Dokumente werden hier letztendlich in einem BLOB abgelegt. Dieses BLOB wird mittels eines Check-Constraints mit der Bedingung
      "JSON_DOCUMENT" is json format json
überwacht, damit dort auch nur JSON Dokumente abgelegt werden können. Spätestens hier wird klar, weshalb wir für SODA 12.1.0.2 benötigen, da die JSON-Funktionalität hier erst eingeführt wurde.

Mit einem einfachen POST kann man nun ein JSON Dokument in die Collection schieben ...


... und bekommt in der Antwort auch die ID zurückgeliefert, die dieses Dokument bekommen hat.


Fragt man nun die Collection ab (also ein GET auf http://localhost:8080/ords/scott/soda/latest/mycollection), bekommt man eine Aufstellung der Daten:
{
  "items": [
    {
      "id": "CBD1D6D35A66419390F4DB73F0B842E7",
      "etag": "5B4B2052A0927083D1C835CAD4DC59BA4AA6D6C8F99264775EEABE9E6F9BCE49",
      "lastModified": "2015-07-01T15:58:37.925000Z",
      "created": "2015-07-01T15:58:37.925000Z",
      "links": [
        {
          "rel": "self",
          "href": "http://localhost:8080/ords/scott/soda/latest/mycollection/CBD1D6D35A66419390F4DB73F0B842E7"
        }
      ],
      "value": {
        "first": "Guenther",
        "last": "Stuerner",
        "address": {
          "Street": "Liebknechtstrasse",
          "City": "Stuttgart",
          "PLZ": "70565"
        }
      }
    },
    {
      "id": "E4D885A4B3F04FBC8457482E1DA0C01E",
      "etag": "511BAD96CC7C4037E8B20E5E65EBC29E7A034A273684AC2EA9029157C4EF1812",
      "lastModified": "2015-07-01T15:58:20.779000Z",
      "created": "2015-07-01T15:58:20.779000Z",
      "links": [
        {
          "rel": "self",
          "href": "http://localhost:8080/ords/scott/soda/latest/mycollection/E4D885A4B3F04FBC8457482E1DA0C01E"
        }
      ],
      "value": {
        "first": "Rainer",
        "last": "Willems",
        "address": {
          "Street": "Robert-Bosch-Strasse",
          "City": "Dreieich",
          "PLZ": "63303"
        }
      }
    },
    {
      "id": "27200D52F15C4A8F82868C353112E350",
      "etag": "51948BD2C13AE6A0962304FCC325743D1A5F976D687D0A03BFB732D2436CF316",
      "lastModified": "2015-07-01T15:58:29.077000Z",
      "created": "2015-07-01T15:58:29.077000Z",
      "links": [
        {
          "rel": "self",
          "href": "http://localhost:8080/ords/scott/soda/latest/mycollection/27200D52F15C4A8F82868C353112E350"
        }
      ],
      "value": {
        "first": "Paul",
        "last": "Wehner",
        "address": {
          "Street": "Robert-Bosch-Strasse",
          "City": "Dreieich",
          "PLZ": "63303"
        }
      }
    }
  ],
  "hasMore": false,
  "count": 3,
  "offset": 0,
  "limit": 100,
  "totalResults": 3,
  "links": []
}
Hierbei lassen sich natürlich Parameter mitgeben, um die Ausgabe zu Filtern (darüber werden wir noch berichten). Für die Abfrage eines einzelnen JSON-Dokumentes hänge ich einfach die ID an das GET an.



Folgende Befehle sind nun also möglich
PUT    - http://localhost:8080/ords/scott/soda/latest/mycollection     Anlegen einer Collection
GET    - http://localhost:8080/ords/scott/soda/latest/                 Anzeige aller Collections
GET    - http://localhost:8080/ords/scott/soda/latest/mycollection     Anzeige des Inhaltes einer Collection
GET    - http://localhost:8080/ords/scott/soda/latest/mycollection/id  Anzeige eines JSON-Dokumentes
POST   - http://localhost:8080/ords/scott/soda/latest/mycollection     Übergabe und Anlage eines Dokumentes
DELETE - http://localhost:8080/ords/scott/soda/latest/mycollection/id  Löschen eines Dokumentes
  ... besser nicht verwechseln mit
DELETE - http://localhost:8080/ords/scott/soda/latest/mycollection     ... denn dann ist die Collection weg

Weitere Möglichkeiten wie Bulk-Insert und Delete sowie verfügbare Actions sind in der Dokumentation ausführlich beschrieben.
Der riesige Vorteil, den wir nun erhalten, findet sich in der Datenbank. Da diese per SQL mit JSON-Dokumenten umgehen kann, sind der Suche in diesen Dokumenten oder der Verknüpfung dieser mit relationalen Inhalten keine Grenzen gesetzt. Und das mit ein paar einfachen Schritten. Wer will da noch eine zusätzliche Datenbank installieren, verwalten, sichern, ....

Noch ein kleiner Hinweis, falls die Restservices wieder entfernt werden sollen. Da die Metaobjekte aus der Datenbank wieder entfernt werden muss man sich mit SYSDBA Privilegien anmelden. Der Zusatz "as sysdba" wird aber aktuell (SQL Developer 4.1.0.19) im Gegensatz zur Installation vom Tool nicht automatisch angehängt, sondern muss manuel eingetragen werden.