Skreddersydd REST API med ORDS

27. april 2024

API, integrasjon, oracle, REST

Artikkel to om Oracle REST Data Services viser hvordan du lett kan lage et tilpasset REST API på egne data i Oracle.

Skreddersydd REST API med ORDS

Første del i denne serien introduserte Oracle REST Data Services (ORDS) og viste hvordan man lager et REST API på tabeller og views med noen få klikk i SQL Developer. I denne delen brukes PL/SQL-pakken ORDS til å definere et API med relativt få linjer med PL/SQL. Det som vises her forutsetter at man har ORDS installert og kjørende som vist i første del.

Til senere når du trenger å laste ned ORDS, dokumentasjon, ideer eller hjelp i et brukerforum er det lettest å huske URL oracle.com/ords .

To tabeller er hentet og forenklet fra et timeregistreringssystem:

Enkel datamodell med to tabeller, aktivitet og prosjekt.

SQL for tabellene

CREATE TABLE prosjekt (
    id          NUMBER
        GENERATED ALWAYS AS IDENTITY,
    CONSTRAINT prosjekt_pk PRIMARY KEY ( id ),
    navn        VARCHAR2(100) NOT NULL,
    beskrivelse VARCHAR2(2000),
    starttid    DATE DEFAULT sysdate NOT NULL,
    avsluttet   DATE,
    guid        RAW(16) DEFAULT sys_guid()
);

CREATE TABLE aktivitet (
    id          NUMBER
        GENERATED ALWAYS AS IDENTITY,
    CONSTRAINT aktivitet_pk PRIMARY KEY ( id ),
    navn        VARCHAR2(100) NOT NULL,
    beskrivelse VARCHAR2(2000),
    prosjekt_id NUMBER,
    CONSTRAINT aktivitet_prosjekt_fk FOREIGN KEY ( prosjekt_id )
        REFERENCES prosjekt,
    guid        RAW(16) DEFAULT sys_guid()
);

Hvorfor ikke AutoREST?

Det er selvsagt mulig å legge et enkelt REST API på disse tabellene som vist i forrige post, men typisk vil man ha mer kontroll på sakene og tilpasninger ved å legge et API foran (eller på toppen, alt etter hvordan du ser for deg dette.) Det bør også sies at man kan se for seg mange veier inn til disse tabellene og et REST API er bare en av flere. Med et felles API som alle andre integrasjoner må gå gjennom vil vi få god kontroll. Derfor lages det her et skall i form av PL/SQL-pakker som vårt REST API kaller på. Det er anbefalt en mer avansert sikkerhetsarkitektur med flere skjema, hvor tabeller ligger et sted, og kode i et annet med utvalgte rettigheter på tabellene i det første. Det blir ikke vist her.

Det er også mulig å benytte AutoREST på PL/SQL-pakker, men da blir det automatisk generert endepunkter og kun POST operasjoner tillatt. Metoden som vises her gir god fleksibilitet for relativt lite ekstra kode.

API fra Oracle i form av PL/SQL-pakken ORDS er beskrevet i Developer’s Guide , dvs i kapittel 4

Aktivere REST for et skjema

For et nytt skjema (databasebruker) må ORDS aktiveres, for den brukeren man er pålogget som kan det gjøres med:

begin
    ords.enable_schema;
    commit;
end;
/

Da blir brukernavn en del av URL og det vil man gjerne ikke vise, så vi bruker admin her:

begin
    ords.enable_schema(p_url_mapping_pattern => 'admin');
    commit;
end;
/

Det er flere parametre man kan se på, blant annet kan en bruker med DBA-rolle aktivere for annen bruker ved hjelp av parameter p_schema og man kan deaktivere det helt ved å sette p_enabled til false.

Module, Template, Handler og Parameter

Fire begreper er lurt å lære seg, de har en naturlig hierarkisk orden:

  • module - dette vil typisk være navn på API, feks timereg i dette tilfellet og utgjør del av url etter /ords/.
  • template - tilsvarer del av url som angår et objekt, feks prosjekt, aktivitet, etc
  • handler - For hvert objekt har man HTTP operasjoner (verb) man tillater (GET, POST, PUT, DELETE). Handler er koden som svarer på en HTTP request mot en URL.
  • parameter - Tilknyttes en handler. Disse mapper innsendt JSON til parametre i kode som skal kjøres, og fra retur-parametre som sendes ut i JSON.

Hele REST API som bygges opp her defineres i en anonym PL/SQL blokk, men her vises de ulike trinnene individuelt etter hvert som vi går frem. Koden er idempotent, dvs du kan kjøre den flere ganger og den vil overskrive eventuelle tidlige konfigurasjoner for samme modul:

Vi lager en modul som kalles timereg ved hjelp av ords.define_module.

BEGIN
    ords.define_module(
                      p_module_name => 'timereg',
                      p_base_path => '/timereg/',
                      p_items_per_page => 25,
                      p_status => 'PUBLISHED',
                      p_comments => 'REST API til timeregistrering'
    );
END;

I API-et defineres en template for håndtering av prosjekt med rutinen ords.define_template:

    ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/',
        p_comments    => 'Administrasjon av prosjekt');

Senere vil man lage egne templates for å håndtere individuelle prosjekt og aktiviteter til hvert prosjekt.

Til slutt defineres handlers som håndterer HTTP-kall som kommer inn. I første omgang definerer vi kun for GET:

Kode for å definere handler til GET på alle prosjekter kan se slik ut:

begin
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_feed,
        p_source       => 'select navn, beskrivelse,starttid, avsluttet,guid from prosjekt;',
        p_comments     => 'Returnerer alle prosjekt.');
end;

Komplett kode så langt er:

begin
    ords.enable_schema(p_url_mapping_pattern => 'admin');
    ords.define_module(
                      p_module_name => 'timereg',
                      p_base_path => '/timereg/',
                      p_comments => 'REST API til timeregistrering'
    );
    ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/',
        p_comments    => 'Administrasjon av prosjekt');
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_feed,
        p_source       => 'select navn, beskrivelse,starttid, avsluttet, rawtohex(guid) "uid" from prosjekt',
        p_comments     => 'Returnerer alle prosjekt.');
    
    commit;
end;
/

Om vi legger inn en rad manuelt i tabellen kan vi teste at en vanlig GET fungerer:

curl  rio:8080/ords/admin/timereg/prosjekt/ | jq

En forklaring på url over: ORDS kjøres på en lab-server rio på port 8080. ORDS servlet er satt opp med context root /ords som er standard. /admin er base path tilknyttet skjema som definert i ords.enable_schema, /timereg er definert i ords.define_module og /prosjekt er definert i ords.define_template

Eksempel på svar fra ORDS:

{
  "items": [
    {
      "navn": "Timeregistrering applikasjon",
      "beskrivelse": "Ny applikasjon for registrering av timer",
      "starttid": "2024-04-27T14:36:27Z",
      "avsluttet": null,
      "guid": "FxQWW/CnCdTgY2QCAArGLg=="
    }
  ],
  "hasMore": false,
  "limit": 25,
  "offset": 0,
  "count": 1,
  "links": [
    {
      "rel": "self",
      "href": "http://rio:8080/ords/admin/timereg/prosjekt/"
    },
    {
      "rel": "describedby",
      "href": "http://rio:8080/ords/admin/metadata-catalog/timereg/prosjekt/"
    },
    {
      "rel": "first",
      "href": "http://rio:8080/ords/admin/timereg/prosjekt/"
    }
  ]
}

PL/SQL API

En pakke er laget for å legge inn, oppdatere og slette data. Den har p.t. følgende publiserte rutiner:

CREATE OR REPLACE PACKAGE prosjekt_adm AS
    FUNCTION nytt_prosjekt ( -- Denne funksjonen brukes ikke fra REST
        p_navn        IN prosjekt.navn%TYPE,
        p_beskrivelse IN prosjekt.beskrivelse%TYPE,
        p_starttid    IN prosjekt.starttid%TYPE DEFAULT sysdate,
        p_avsluttet   IN prosjekt.avsluttet%TYPE DEFAULT NULL
    ) RETURN prosjekt.id%TYPE;

    PROCEDURE nytt_prosjekt (
        p_navn        IN prosjekt.navn%TYPE,
        p_beskrivelse IN prosjekt.beskrivelse%TYPE,
        p_starttid    IN prosjekt.starttid%TYPE DEFAULT sysdate,
        p_avsluttet   IN prosjekt.avsluttet%TYPE DEFAULT NULL,
        p_guid        OUT VARCHAR2
    );

    PROCEDURE oppdater_prosjekt (
        p_guid        IN VARCHAR2,
        p_navn        IN prosjekt.navn%TYPE,
        p_beskrivelse IN prosjekt.beskrivelse%TYPE,
        p_starttid    IN prosjekt.starttid%TYPE,
        p_avsluttet   IN prosjekt.avsluttet%TYPE DEFAULT NULL
    );

    PROCEDURE slett_prosjekt (
        p_guid IN VARCHAR2
    );

END prosjekt_adm;

Resten av koden til pakkene (package body) ligger nederst. Det må bemerkes at dette er langt fra produksjonsklart. Ved å bruke PL/SQL her kan man forbedre feilsjekk og implementasjon forøvrig selv om et fast API ivaretas. Man kan også skreddersy feilmeldinger som blir sendt tilbake i JSON-format.

Flere handlers

En handler for POST-operasjon opprettes slik:

    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/',
        p_method    => 'POST',
        p_source_type => ords.source_type_plsql,
        p_source       => q'[
        begin
            prosjekt_adm.nytt_prosjekt (
                p_navn   => :navn,
                p_beskrivelse => :beskrivelse,
                p_starttid  => :starttid,
                p_avsluttet  => :avsluttet,
                p_guid     => :uid);
        end;]',
        p_comments     => 'Oppretter et prosjekt.');
    
        ords.define_parameter(
            p_module_name  => 'timereg',
            p_pattern => '/prosjekt/',
            p_method      => 'POST',
            p_name  => 'uid' ,
            p_bind_variable_name => 'uid',
            p_source_type  => 'RESPONSE',
            p_param_type   => 'STRING',
            p_access_method =>  'OUT',
            p_comments  =>'UID returned from database.');

Siden vi ønsker å få ut uid må den defineres som vist med ords.define_parameter. De andre bind-variablene blir implisitt definert.

Første POST

Følgende json dokument legges i en fil med navn prosjekt.json:

{
"navn": "Testprosjekt",
"beskrivelse": "Prosjekt for all testing"
}

Dette kan sendes inn med feks curl:

curl -X POST -H 'Content-Type: application/json' -d@prosjekt.json rio:8080/ords/admin/timereg/prosjekt/

Verdt å merke seg at header med Content-Type: application/json må være med som vist over, ellers får man feilmelding som kan være vanskelig å finne ut av; innsendt JSON blir ikke riktig tolket og bind-variablene blir ikke definert.

Respons er en kort JSON som inneholder uid:

{"uid":"1716D650145C0DF0E0636402000AE8A2"}

uid fra respons bruker vi senere når vi skal opprette aktiviteter.

Parametre og datohåndtering

Merk at ikke alle parametre er obligatoriske, PL/SQL-rutinene er laget for å håndtere parametre med standardverdier når de mangler. For de som blir sendt i JSON-format vil ORDS definere dem automagisk. Unntak er om det benyttes navn som ikke er lovlige i PL/SQL.

Om vi ønsker å sende inn starttid, så håndterer ORDS ISO-8601 på strak arm:

{
"navn": "Testprosjekt II",
"beskrivelse": "Prosjekt for enda mer testing",
"starttid": "2024-04-27T14:36:27Z"
}

Det er ikke nødvendig med konvertering med to_date noe sted i koden for at dette skal fungere.

ORDS gjør commit

I koden som betjener hvert endepunkt og operasjon ligger det ikke eksplisitt commit; ORDS gjør en commit tilslutt om alt går bra - noe som gir mening siden HTTP er en stateless protokoll.

Operasjoner på individuelt prosjekt

Neste trinn er å definere operasjoner for å hente, oppdatere og slette et individuelt prosjekt. De fleste som bruker et REST API vil forvente å kunne gjøre det mot en URL som vist over med UID lagt til. Følgende oppretter en ny template og tre handlers for GET, PUT og DELETE:

ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/:uid',
        p_comments    => 'Administrasjon av prosjekt');
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_item,
        p_source       => q'[
            select navn, beskrivelse,starttid, avsluttet, rawtohex(guid) "uid" 
            from prosjekt
            where guid=hextoraw(:uid)]',
        p_comments     => 'Returnerer et prosjekt.');
        
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid',
        p_method    => 'PUT',
        p_source_type => ords.source_type_plsql,
        p_source       => q'[
            begin
                prosjekt_adm.oppdater_prosjekt(
                    p_guid     => :uid,
                    p_navn   => :navn,
                    p_beskrivelse => :beskrivelse,
                    p_starttid  => :starttid,
                    p_avsluttet  => :avsluttet);
            end;]',
        p_comments => 'Oppdaterer et prosjekt.');
    
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid',
        p_method    => 'DELETE',
        p_source_type => ords.source_type_plsql,
        p_source       => q'[
            begin
                prosjekt_adm.slett_prosjekt (
                    p_guid     => :uid);
            end;]',
        p_comments => 'Sletter et prosjekt.');    

Med dette lagt til kan man hente et enkelt prosjekt, oppdatere det og slette det med hhv:

curl rio:8080/ords/admin/timereg/prosjekt/1716D650145E0DF0E0636402000AE8A2
curl -X PUT -H "Content-Type: application/json" -d@prosjekt_put.json \
    rio:8080/ords/admin/timereg/prosjekt/1716D650145E0DF0E0636402000AE8A
curl -X DELETE rio:8080/ords/admin/timereg/prosjekt/1716D650145E0DF0E0636402000AE8A2

Parameter uid er implisitt definert gjennom p_pattern i ords.define_handler.

Source type

De ulike handlers har forskjellig p_source_type som må settes iht koden som kjøres:

  • ords.source_type_collection_feed - Select som returnerer flere rader blir kjørt, ORDS returnerer JSON array
  • ords.source_type_collection_item - Select som returnerer en rad, ORDS returnerer et objekt.
  • ords.source_type_plsql - PL/SQL kjøres og ORDS mapper IN og OUT parametre.

Aktiviteter til et prosjekt

For å håndtere aktiviteter er det laget en egen forenklet pakke for det. Her inneholder den kun en rutine for å opprette en ny aktivitet:

CREATE OR REPLACE PACKAGE aktivitet_adm AS
    PROCEDURE ny_aktivitet (
        p_navn          IN aktivitet.navn%TYPE,
        p_beskrivelse   IN aktivitet.beskrivelse%TYPE,
        p_prosjekt_guid IN VARCHAR2,
        p_guid          OUT VARCHAR2
    );

END aktivitet_adm;
/

Siden en aktivitet er knyttet til et bestemt prosjekt vil det være naturlig å legge /aktivitet/ bakerst på URL til et bestemt prosjekt. Følgende kode oppretter template og to handlers for å liste ut alle aktiviteter til et prosjekt og opprette en ny aktivitet knyttet til et bestemt prosjekt:

ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/:uid/aktivitet/',
        p_comments    => 'Administrasjon av aktiviteter til et prosjekt');
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid/aktivitet/',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_feed,
        p_source       => q'[
            select p.navn prosjektnavn, a.navn aktivitetsnavn, 
            a.beskrivelse aktivitetsbeskrivelse,
            rawtohex(p.guid) prosjekt_uid, rawtohex(a.guid) aktivitet_uid
            from prosjekt p join aktivitet a on p.id = a.prosjekt_id
            where p.guid=hextoraw(:uid)]',
        p_comments     => 'Returnerer alle aktiviteter for prosjekt.');
    
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid/aktivitet/',
        p_method    => 'POST',
        p_source_type => ords.source_type_plsql,
        p_source       => q'[
            begin
                aktivitet_adm.ny_aktivitet (
                p_navn     => :navn,
                p_beskrivelse => :beskrivelse,
                p_prosjekt_guid => :uid,
                p_guid   => :aktivitet_uid);
            end;]',
        p_comments     => 'Oppretter en aktivitet for prosjekt.');
     
    ords.define_parameter(
            p_module_name  => 'timereg',
            p_pattern => '/prosjekt/:uid/aktivitet/',
            p_method      => 'POST',
            p_name  => 'aktivitet_uid' ,
            p_bind_variable_name => 'aktivitet_uid',
            p_source_type  => 'RESPONSE',
            p_param_type   => 'STRING',
            p_access_method =>  'OUT',
            p_comments  =>'UID til opprettet aktivitet.');

Eksempel på POST som oppretter en ny aktivitet for et prosjekt:

curl -X POST -H 'Content-Type: application/json' -d@aktivitet.json rio:8080/ords/admin/timereg/prosjekt/1714165BF0A709D4E0636402000AC62E/aktivitet/

Siste REST - individuell aktivitet

Denne koden definerer GET på en bestemt aktivitet:

ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/:uid/aktivitet/:aktivitet_uid',
        p_comments    => 'Administrasjon av enkelt aktivitet til et prosjekt');
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid/aktivitet/:aktivitet_uid',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_item,
        p_source       => q'[
            select p.navn prosjektnavn, a.navn aktivitetsnavn, 
            a.beskrivelse aktivitetsbeskrivelse,
            rawtohex(p.guid) prosjekt_uid, rawtohex(a.guid) aktivitet_uid
            from prosjekt p join aktivitet a on p.id = a.prosjekt_id
            where p.guid=hextoraw(:uid)
                and a.guid = hextoraw(:aktivitet_uid)]',
        p_comments     => 'Returnerer alle aktiviteter for prosjekt.');

Eksempel på GET mot en aktivitet:

curl http://rio:8080/ords/admin/timereg/prosjekt/1714165BF0A709D4E0636402000AC62E/aktivitet/1716D650145F0DF0E0636402000AE8A2

Det bør være en overkommelig oppgave å definere handlers for PUT og DELETE mot en enkeltaktivitet.

405 Method Not Allowed

Så langt har vi ikke definert hvordan PUT og DELETE mot en aktivitet skal håndteres; derfor får man HTTP 405 - Method Not Allowed når man forsøker seg på det. ORDS sjekker automatisk for hvilke operasjoner som er definert. Om en URL ikke kan håndteres i definerte handlers så vil man naturligvis få HTTP 404.

Hva er greia med UID?

Om du lurer på hvorfor UID brukes og ikke primærnøkkel (ID) så er det for å unngå scanning og masseuthenting via ID. ID stiger sekvensielt og om den var en del av URL i stedet for UID vil API-et bli mer sårbart for slikt misbruk.

Tips til slutt

Dersom du bruker verktøy som Postman så sjekk hvilke headere som sendes inn. Om det sendes inn User-Agent type nettleser vil ORDS gjerne svare med html når feil oppstår. Fra Curl som vist her returnerer den JSON også ved feil.

Det er mye funksjonalitet som ikke er dekket her. Blant annet sikkerhet. Vårt API er vid åpent. OAuth 2.0, OIDC et al er tema i neste post.

Sjekk dokumentasjon og søk etter tips fra blant annet Jeff Smith

Forhåpentligvis har denne gjennomgangen vist hvordan man gradvis kan bygge opp et REST API i en logisk struktur og tilpasses etter hvert.

REST API så langt

Komplett anonym PL/SQL-kode for REST API som er definert så langt, denne blokken er blitt så lang at den kunne vært splittet opp i en blokk pr template for eksempel:

begin
    ords.enable_schema(p_url_mapping_pattern => 'admin');
    ords.define_module(
                      p_module_name => 'timereg',
                      p_base_path => '/timereg/',
                      p_comments => 'REST API til timeregistrering'
    );
    ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/',
        p_comments    => 'Administrasjon av prosjekt');
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_feed,
        p_source       => 'select navn, beskrivelse,starttid, avsluttet, rawtohex(guid) "uid" from prosjekt',
        p_comments     => 'Returnerer alle prosjekt.');
    
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/',
        p_method    => 'POST',
        p_source_type => ords.source_type_plsql,
        p_source       => q'[
        begin
            prosjekt_adm.nytt_prosjekt (
                p_navn   => :navn,
                p_beskrivelse => :beskrivelse,
                p_starttid  => :starttid,
                p_avsluttet  => :avsluttet,
                p_guid     => :uid);
        end;]',
        p_comments     => 'Oppretter et prosjekt.');
    
        ords.define_parameter(
            p_module_name  => 'timereg',
            p_pattern => '/prosjekt/',
            p_method      => 'POST',
            p_name  => 'uid' ,
            p_bind_variable_name => 'uid',
            p_source_type  => 'RESPONSE',
            p_param_type   => 'STRING',
            p_access_method =>  'OUT',
            p_comments  =>'UID returned from database.');
            
            
    ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/:uid',
        p_comments    => 'Administrasjon av prosjekt');
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_item,
        p_source       => q'[
            select navn, beskrivelse,starttid, avsluttet, rawtohex(guid) "uid" 
            from prosjekt
            where guid=hextoraw(:uid)]',
        p_comments     => 'Returnerer et prosjekt.');
        
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid',
        p_method    => 'PUT',
        p_source_type => ords.source_type_plsql,
        p_source       => q'[
            begin
                prosjekt_adm.oppdater_prosjekt(
                    p_guid     => :uid,
                    p_navn   => :navn,
                    p_beskrivelse => :beskrivelse,
                    p_starttid  => :starttid,
                    p_avsluttet  => :avsluttet);
            end;]',
        p_comments => 'Oppdaterer et prosjekt.');
    
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid',
        p_method    => 'DELETE',
        p_source_type => ords.source_type_plsql,
        p_source       => q'[
            begin
                prosjekt_adm.slett_prosjekt (
                    p_guid     => :uid);
            end;]',
        p_comments => 'Sletter et prosjekt.');    
        
    ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/:uid/aktivitet/',
        p_comments    => 'Administrasjon av aktiviteter til et prosjekt');
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid/aktivitet/',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_feed,
        p_source       => q'[
            select p.navn prosjektnavn, a.navn aktivitetsnavn, 
            a.beskrivelse aktivitetsbeskrivelse,
            rawtohex(p.guid) prosjekt_uid, rawtohex(a.guid) aktivitet_uid
            from prosjekt p join aktivitet a on p.id = a.prosjekt_id
            where p.guid=hextoraw(:uid)]',
        p_comments     => 'Returnerer alle aktiviteter for prosjekt.');
    
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid/aktivitet/',
        p_method    => 'POST',
        p_source_type => ords.source_type_plsql,
        p_source       => q'[
            begin
                aktivitet_adm.ny_aktivitet (
                p_navn     => :navn,
                p_beskrivelse => :beskrivelse,
                p_prosjekt_guid => :uid,
                p_guid   => :aktivitet_uid);
            end;]',
        p_comments     => 'Oppretter en aktivitet for prosjekt.');
     
    ords.define_parameter(
            p_module_name  => 'timereg',
            p_pattern => '/prosjekt/:uid/aktivitet/',
            p_method      => 'POST',
            p_name  => 'aktivitet_uid' ,
            p_bind_variable_name => 'aktivitet_uid',
            p_source_type  => 'RESPONSE',
            p_param_type   => 'STRING',
            p_access_method =>  'OUT',
            p_comments  =>'UID til opprettet aktivitet.');
            
    ords.define_template(
        p_module_name  => 'timereg',
        p_pattern      => '/prosjekt/:uid/aktivitet/:aktivitet_uid',
        p_comments    => 'Administrasjon av enkelt aktivitet til et prosjekt');
        
    ords.define_handler(
        p_module_name      => 'timereg',
        p_pattern  => '/prosjekt/:uid/aktivitet/:aktivitet_uid',
        p_method    => 'GET',
        p_source_type => ords.source_type_collection_item,
        p_source       => q'[
            select p.navn prosjektnavn, a.navn aktivitetsnavn, 
            a.beskrivelse aktivitetsbeskrivelse,
            rawtohex(p.guid) prosjekt_uid, rawtohex(a.guid) aktivitet_uid
            from prosjekt p join aktivitet a on p.id = a.prosjekt_id
            where p.guid=hextoraw(:uid)
                and a.guid = hextoraw(:aktivitet_uid)]',
        p_comments     => 'Returnerer alle aktiviteter for prosjekt.');
        
    commit;
end;
/

Package bodies

Package body til rutinene som brukes fra ORDS vises under.

CREATE OR REPLACE PACKAGE BODY prosjekt_adm AS

    FUNCTION nytt_prosjekt (
        p_navn        IN prosjekt.navn%TYPE,
        p_beskrivelse IN prosjekt.beskrivelse%TYPE,
        p_starttid    IN prosjekt.starttid%TYPE DEFAULT sysdate,
        p_avsluttet   IN prosjekt.avsluttet%TYPE DEFAULT NULL
    ) RETURN prosjekt.id%TYPE AS
        l_id prosjekt.id%TYPE;
    BEGIN
        INSERT INTO prosjekt (
            navn,
            beskrivelse,
            starttid,
            avsluttet
        ) VALUES (
            p_navn,
            p_beskrivelse,
            nvl(p_starttid,sysdate),
            p_avsluttet
        ) RETURNING id INTO l_id;

        RETURN l_id;
    END nytt_prosjekt;

    PROCEDURE nytt_prosjekt (
        p_navn        IN prosjekt.navn%TYPE,
        p_beskrivelse IN prosjekt.beskrivelse%TYPE,
        p_starttid    IN prosjekt.starttid%TYPE DEFAULT sysdate,
        p_avsluttet   IN prosjekt.avsluttet%TYPE DEFAULT NULL,
        p_guid        OUT VARCHAR2
    ) AS
    BEGIN
        INSERT INTO prosjekt (
            navn,
            beskrivelse,
            starttid,
            avsluttet
        ) VALUES (
            p_navn,
            p_beskrivelse,
            nvl(p_starttid,sysdate),
            p_avsluttet
        ) RETURNING rawtohex(guid) INTO p_guid;

    END nytt_prosjekt;

    PROCEDURE oppdater_prosjekt (
        p_guid        IN VARCHAR2,
        p_navn        IN prosjekt.navn%TYPE,
        p_beskrivelse IN prosjekt.beskrivelse%TYPE,
        p_starttid    IN prosjekt.starttid%TYPE,
        p_avsluttet   IN prosjekt.avsluttet%TYPE DEFAULT NULL
    ) AS
    BEGIN
        UPDATE prosjekt
        SET
            navn = p_navn,
            beskrivelse = p_beskrivelse,
            avsluttet = p_avsluttet
        WHERE
            guid = hextoraw(p_guid);

    END oppdater_prosjekt;

    PROCEDURE slett_prosjekt (
        p_guid IN VARCHAR2
    ) AS
    BEGIN
        DELETE FROM prosjekt
        WHERE
            guid = hextoraw(p_guid);

    END slett_prosjekt;

END prosjekt_adm;
/

CREATE OR REPLACE PACKAGE BODY aktivitet_adm AS

    PROCEDURE ny_aktivitet (
        p_navn          IN aktivitet.navn%TYPE,
        p_beskrivelse   IN aktivitet.beskrivelse%TYPE,
        p_prosjekt_guid IN VARCHAR2,
        p_guid          OUT VARCHAR2
    ) AS
        l_prosjekt_id prosjekt.id%TYPE;
    BEGIN
        SELECT
            id
        INTO l_prosjekt_id
        FROM
            prosjekt
        WHERE
            guid = hextoraw(p_prosjekt_guid);

        INSERT INTO aktivitet (
            navn,
            beskrivelse,
            prosjekt_id
        ) VALUES (
            p_navn,
            p_beskrivelse,
            l_prosjekt_id
        ) RETURNING guid INTO p_guid;

    END ny_aktivitet;

END aktivitet_adm;
/