Intoduction to Oracle Rest Data Services

Cyrille Modiano
Latest posts by Cyrille Modiano (see all)

Presentation

First, let’s talk about REST apis. REST apis are very popular nowdays because they are easily accessible from any client able to make http(s) requests. The beauty of this is that you can access different systems through these rest apis independantly of how the system is built in the backend. For example I can access with a simple curl command the twitter, youtube or any public api which will return me data.

Rest APIs usually present data as a JSON object. If your are not already familiar with JSON here is an example of a JSON object:

{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}

Oracle 12.1.0.2 introduced the support of JSON as a datatype and methods to query JSON objects. This make things a lot easier when you have to work with these data structures.

What the point of using a REST API for a database?

Activating a REST service for your database gives you the possibility to access and modify your data directly through a simple http(s) request which is an easy way to give access to your data for an existing application.

Using REST also alow to create a clear separation between database development and software development. Most of the time, when a software developer wants to use a Database through an application he will have to learn SQL and know the particularity of database specific functions, views, and procedural language. He also have to use specific drivers to access the database and learn how they work. Using REST, the software developer only have to call a web service when he wants to interact with the database which simplify his life, leaving people with database knowledge develop the service they will call.

REST

ORDS is free of charge and can be deployed on WebLogic, Tomcat and Glassfish application servers or as a standalone service.

Installing and configuring Oracle Rest Data Services

Install

First you will need to download ORDS from the following link:
Download

In this post I will deploy ORDS as a standalone service running directly on the database server. Like explained earlier, once configured ORDS can also be deployed on a compatible application server.

Upload the zip file to your server and unzip it to a temprary directory:

# unzip ords.3.0.11.180.12.34.zip -d /tmp/ords

Configure

Start the configuration using:

# java -jar ords.war

Here I chose not to use APEX and run the ORDS as a standalone service. To keep things simple I also used HTTP over HTTPS:

[oracle@srv-ora-01 ords]$ java -jar ords.war
This Oracle REST Data Services instance has not yet been configured.
Please complete the following prompts

Enter the location to store configuration data:/u01/app/oracle/product/ords
Enter the name of the database server [localhost]:192.168.1.50
Enter the database listen port [1521]:
Enter 1 to specify the database service name, or 2 to specify the database SID [1]:
Enter the database service name:orclpdb
Enter the database password for ORDS_PUBLIC_USER:
Confirm password:
Please login with SYSDBA privileges to verify Oracle REST Data Services schema.

Enter the username with SYSDBA privileges to verify the installation [SYS]:
Enter the database password for SYS:
Confirm password:
Enter 1 if you want to use PL/SQL Gateway or 2 to skip this step.
If using Oracle Application Express or migrating from mod_plsql then you must enter 1 [1]:2
Nov 05, 2017 10:41:23 PM
INFO: Updated configurations: defaults, apex_pu
Installing Oracle REST Data Services version 3.0.11.180.12.34
... Log file written to /tmp/ords/logs/ords_install_core_2017-11-05_224123_00591.log
... Verified database prerequisites
... Created Oracle REST Data Services schema
... Created Oracle REST Data Services proxy user
... Granted privileges to Oracle REST Data Services
... Created Oracle REST Data Services database objects
... Log file written to /tmp/ords/logs/ords_install_datamodel_2017-11-05_224138_00610.log
Completed installation for Oracle REST Data Services version 3.0.11.180.12.34. Elapsed time: 00:00:15.887

Enter 1 if you wish to start in standalone mode or 2 to exit [1]:
Enter 1 if using HTTP or 2 if using HTTPS [1]:
2017-11-05 22:42:07.401:INFO::main: Logging initialized @359829ms
Nov 05, 2017 10:42:07 PM
INFO: Disabling document root because the specified folder does not exist: /u01/app/oracle/product/ords/ords/standalone/doc_root
2017-11-05 22:42:08.232:INFO:oejs.Server:main: jetty-9.2.z-SNAPSHOT
Nov 05, 2017 10:42:08 PM
INFO: No encryption key found in configuration, generating key
Nov 05, 2017 10:42:08 PM
INFO: No mac key found in configuration, generating key
Nov 05, 2017 10:42:08 PM
INFO: Updated configurations: defaults
Nov 05, 2017 10:42:08 PM
INFO: Updated configuration with generated keys
2017-11-05 22:42:08.446:INFO:/ords:main: INFO: Using configuration folder: /u01/app/oracle/product/ords/ords
2017-11-05 22:42:08.448:INFO:/ords:main: FINEST: |ApplicationContext [configurationFolder=/u01/app/oracle/product/ords/ords, services=Application Scope]|
Nov 05, 2017 10:42:08 PM
INFO: Validating pool: |apex|pu|
Nov 05, 2017 10:42:08 PM
INFO: Pool: |apex|pu| is correctly configured
config.dir
2017-11-05 22:42:08.730:INFO:/ords:main: INFO: Oracle REST Data Services initialized|Oracle REST Data Services version : 3.0.11.180.12.34|Oracle REST Data Services server info: jetty/9.2.z-SNAPSHOT|
2017-11-05 22:42:08.732:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@289a0cdb{/ords,null,AVAILABLE}
2017-11-05 22:42:08.739:INFO:oejs.ServerConnector:main: Started ServerConnector@5cca1a7f{HTTP/1.1}{0.0.0.0:8080}
2017-11-05 22:42:08.740:INFO:oejs.Server:main: Started @361168ms

Now the service is started and you should be able to access it through a web browser. If you access to http://<ip_or_host_configured>:8080/ords.
You should have the following page:

RESTThe 404 is completly normal.

Getting Started

Now that we have configured ORDS, why not enable some services to access our data?
For my tests I will use the Oracle sample schema HR. The first thing we need to do is enable Restful services for the schema, this is done through the ORDS package:

SQL> DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
  BEGIN

    ORDS.ENABLE_SCHEMA(p_enabled => TRUE,
                       p_schema => 'HR',
                       p_url_mapping_type => 'BASE_PATH',
                       p_url_mapping_pattern => 'hr',
                       p_auto_rest_auth => TRUE);

    commit;

END;
/

PL/SQL procedure successfully completed.

The url_mapping_pattern for the BASE_PATH describe the url structure necessary to access the services served by this schema. Now we are all set to create our first service.
Here I will create a service to query my EMPLOYEES table:

SQL> BEGIN
  ORDS.define_service(
    p_module_name    => 'employees',
    p_base_path      => 'employees/',
    p_pattern        => 'GetAllEmployees/',
    p_method         => 'GET',
    p_source_type    => ORDS.source_type_collection_feed,
    p_source         => 'SELECT * FROM EMPLOYEES',
    p_items_per_page => 0);

  COMMIT;
END;
/

PL/SQL procedure successfully completed.

This service is now accessible through the following url : http://<ip_or_host_configured>:8080/ords/hr/employees/GetAllEmployees/
If you try to access this url through a web browser, you should obtain something like this:

Rest sample service
We have created a simple web service to access our table, now let’s go a little bit further and try to add a filter. We define the following service:

BEGIN
  ORDS.define_service(
    p_module_name    => 'employees',
    p_base_path      => 'employees/',
    p_pattern        => 'GetEmployee/:emp_no',
    p_method         => 'GET',
    p_source_type    => ORDS.source_type_collection_feed,
    p_source         => 'SELECT * FROM EMPLOYEES WHERE employee_id=:emp_no',
    p_items_per_page => 0);

  COMMIT;
END;
/ 

PL/SQL procedure successfully completed.

I will now try to access an employee based on the employee_id by using the following url: http://<ip_or_host_configured>:8080/ords/hr/employees/GetEmployee/102
And I get the following result:

Rest service

We just saw how to interact with a specific row, here we selected a row but this could have been a delete statement as well. Now let’s be a little bit more ambitious, let’s say an application wants  to send us data using our REST api, the data we will receive will be a JSON object containing the definition of the row(s) we will need to insert. For that purpose we will create a stored procedure to process the incoming data and insert it into the EMPLOYEES table. This time we will not use a GET but a POST message. By default ORDS treats the incoming data of a post as a blob object. In order to use the data we will need to convert it as clob. Here is the procedure:

create or replace PROCEDURE    insert_employee(p_lob blob)
IS
	-- Input
	l_clob clob;

	-- Input processing
	l_dest_offsset integer := 1;
    l_src_offsset  integer := 1;
    l_lang_context integer := dbms_lob.default_lang_ctx;
    l_warning      integer;

	-- JSON processing
	l_je JSON_ELEMENT_T;
    l_jo JSON_OBJECT_T;
	l_lo JSON_OBJECT_T;
	l_ja JSON_ARRAY_T;

	-- Table data
	l_first_name VARCHAR2(20);
	l_last_name VARCHAR2(25);
	l_email VARCHAR2(25);
	l_phone_num VARCHAR2(20);
	l_hire_date varchar2(50);
	l_job_id VARCHAR2(10);
	l_salary NUMBER;
	l_com NUMBER;
	l_man_id NUMBER;
	l_dpmt NUMBER;

BEGIN
	-- transform the lob data into a clob object
    dbms_lob.createTemporary(lob_loc => l_clob,cache   => false);
	dbms_lob.converttoclob(dest_lob     => l_clob
                            ,src_blob     => p_lob
                            ,amount       => dbms_lob.lobmaxsize
                            ,dest_offset  => l_dest_offsset
                            ,src_offset   => l_src_offsset
                            ,blob_csid    => dbms_lob.default_csid
                            ,lang_context => l_lang_context
                            ,warning      => l_warning);
                            

	-- create a JSON Object from our clob data
	l_jo := JSON_OBJECT_T.parse(l_clob);
	-- Get all items from our JSON
	l_ja := l_jo.get_Array('items');
	-- Loop into our JSON array
	FOR i IN 0 .. l_ja.get_size - 1 LOOP
		-- Get the current employee
		l_lo := JSON_OBJECT_T(l_ja.get(i));
		-- Extract column values from the JSON object
		l_first_name := l_lo.get_String('first_name');
		l_last_name := l_lo.get_String('last_name');
		l_email := l_lo.get_String('email');
		l_phone_num := l_lo.get_String('phone_number');
		l_hire_date := to_date(l_lo.get_String('hire_date'),'YYYY-MM-DD"T"hh24:mi:ss');
		l_job_id := l_lo.get_String('job_id');
		l_salary := l_lo.get_Number('salary');
		l_com := l_lo.get_Number('comission_pct');
		l_man_id := l_lo.get_Number('manager_id');
		l_dpmt := l_lo.get_Number('department_id');

		-- Insert the row
		INSERT INTO EMPLOYEES
		VALUES (EMPLOYEES_SEQ.NEXTVAL,
				l_first_name,
				l_last_name,
				l_email,
				l_phone_num,
				l_hire_date,
				l_job_id,
				l_salary,
				l_com,
				l_man_id,
				l_dpmt);
	END LOOP;


END insert_employee;
/

Now we need to create a service which will expose this procedure publicly. Take good attention to the type declared in the service:

BEGIN
  ORDS.define_service(
    p_module_name    => 'employees',
    p_base_path      => 'employees/',
    p_pattern        => 'insert_employees/',
    p_method         => 'POST',
    p_source_type    => ORDS.source_type_plsql,
    p_source         => 'BEGIN
                             hr.insert_employee(:body);
                         END;',
        p_mimes_allowed  => 'application/json',
    p_items_per_page => 0);

  COMMIT;
END;
/

PL/SQL procedure successfully completed.

Here we use ORDS.source_type_plsql because we call a PL/SQL procedure.
The :body variable is the default variable for data passed through a POST request to the REST service.

Let’s try to insert an employee, I will create a JSON file with the following content in /tmp:

{
        "items": [{
                "first_name": "Cyrille",
                "last_name": "MODIANO",
                "email": "example@example.com",
                "phone_number": "515.123.123",
                "hire_date": "2017-11-06T18:00:00",
                "job_id": "AD_VP",
                "salary": 1000,
                "commission_pct": null,
                "manager_id": 100,
                "department_id": 90
        }]
}

Let’s try to post ou data to the service. Here I will use curl with the following command:

[oracle@srv-ora-01 tmp]$ curl -i -X POST --data-binary @/tmp/employee.json -H "Content-Type: application/json" http://192.168.1.50:8080/ords/hr/employees/insert_employees/

and the output is:

HTTP/1.1 200 OK
Transfer-Encoding: chunked

An http 200 means everything went well, here is the verification:

SQL>  select * from employees where first_name='Cyrille';

EMPLOYEE_ID FIRST_NAME           LAST_NAME                 EMAIL                     PHONE_NUMBER         HIRE_DATE JOB_ID         SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
----------- -------------------- ------------------------- ------------------------- -------------------- --------- ---------- ---------- -------------- ---------- -------------
        221 Cyrille              MODIANO                   example@example.com       515.123.123          06-NOV-17 AD_VP            1000                       100            90

Here we just submited one employee, let’s try 3 at a time with the following JSON:

{
        "items": [{
                "first_name": "Cyrille",
                "last_name": "MODIANO",
                "email": "example@example.com",
                "phone_number": "515.123.123",
                "hire_date": "2017-11-06T18:00:00",
                "job_id": "AD_VP",
                "salary": 1000,
                "commission_pct": null,
                "manager_id": 100,
                "department_id": 90
        },{
                "first_name": "Tom",
                "last_name": "Kyte",
                "email": "example1@example.com",
                "phone_number": "515.124.124",
                "hire_date": "2005-01-06T18:00:00",
                "job_id": "AD_VP",
                "salary": 50000,
                "commission_pct": null,
                "manager_id": 100,
                "department_id": 90
        },{
                "first_name": "John",
                "last_name": "Doe",
                "email": "example2@example.com",
                "phone_number": "515.123.123",
                "hire_date": "2010-05-12T12:00:00",
                "job_id": "AD_VP",
                "salary": 1000,
                "commission_pct": null,
                "manager_id": 100,
                "department_id": 90
        }]
}
[oracle@srv-ora-01 tmp]$ curl -i -X POST --data-binary @/tmp/employee.json -H "Content-Type: application/json" http://192.168.1.50:8080/ords/hr/employees/insert_employees/
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Transfer-Encoding: chunked
SQL> select * from employees where last_name in ('MODIANO','Kyte','Doe');

EMPLOYEE_ID FIRST_NAME           LAST_NAME                 EMAIL                     PHONE_NUMBER         HIRE_DATE JOB_ID         SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
----------- -------------------- ------------------------- ------------------------- -------------------- --------- ---------- ---------- -------------- ---------- -------------
        226 John                 Doe                       example2@example.com      515.123.123          12-MAY-10 AD_VP            1000                       100            90
        225 Tom                  Kyte                      example1@example.com      515.124.124          06-JAN-05 AD_VP           50000                       100            90
        224 Cyrille              MODIANO                   example@example.com       515.123.123          06-NOV-17 AD_VP            1000                       100            90

This is the end of this introduction. Hope it helped you see the capabilities of Oracle Rest Data Services.

See you.

10 thoughts on “Intoduction to Oracle Rest Data Services

  1. Hi guy ! Thank you for this cool tutorial, do you know if it’s possible to reply with other formats, like xml, or others ? ^^
    Thanks in advance

    1. Hi Vincent,

      Thank you for reading my blog. I Know you can return csv by using:
      p_source_type => ORDS.source_type_csv_query
      For XML, I found that procedure:

      CREATE OR REPLACE PROCEDURE P_SHOW_XML_TABLE
      IS
      l_clob CLOB;

      BEGIN
      SELECT XMLELEMENT("article",
      XMLFOREST( e.id as "id",
      .....
      )
      ).getClobVal()
      INTO l_clob
      FROM article e
      where rownum = 1;

      OWA_UTIL.mime_header('text/xml');
      HTP.print(l_clob);

      EXCEPTION
      WHEN OTHERS THEN
      raise;
      END P_SHOW_XML_TABLE;

      Then just create your service and pass this newly created procedure in the p_source parameter:

      BEGIN
      ords.create_service (
      p_module_name => 'test.xmltable' ,
      p_base_path => '/test/',
      p_pattern => 'xmltable/',
      p_method => 'GET',
      p_source_type => ords.source_type_plsql,
      p_source => 'begin p_show_xml_table; end;'
      );
      commit;
      END;

      I found this example Here

      Thanks

      Cyrille

  2. Thank you Cyrille it’s a very good and clear introduction.
    It’s been a long time since I wanted to know how REST was used with Oracle. I see now many applications.
    Have a nice day my friend!

  3. Thanks Cyrille Modiano, I have same error: 404 when I install Oracle Rest Data Service . I find in metalink but can’t find anything or just explain not clearly . Thanks Modiano . Hopefully , you have many blog for Oracle Rest because it will be future of Oracle Database .

      1. Yes, luckily find your website, I understand and fix error. You have skype or something ? I can exchange about Oracle or something ?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.