Transform your SQL Results with Mustache and ORDS JavaScript Handlers

When dealing with SQL data, format and structure is crucial. Raw data isn’t always the most readable or useful format, especially when you need to expose it through an API. This is where Oracle REST Data Services (ORDS) comes in handy. It provides a secure and convenient JSON representation of the SQL query results in a JSON format by default. In this article, we’ll walk through using ORDS JavaScript handlers with the Mustache template engine to format an XML response from SQL query results. What we cover here is based on the previous article Multilingual Engine (MLE) for JavaScript Handlers. That article introduced an ORDS Module called demo_mle_javascript and in this article we will add to it.

The high level flow

Introducing the key components

Oracle REST Data Services (ORDS)

Oracle REST Data Services (ORDS) is a powerful tool that allows you to expose database functionalities as RESTful services. It simplifies the process of creating APIs for your Oracle database, making it easier to integrate with other systems and applications.

Mustache Template Engine

Mustache is a logic-less template engine that helps you format data into any desired format. Its simplicity and flexibility make it a great choice for rendering text in a wide variety of formats: HTML, JSON, XML, etc. Mustache uses templates to define the structure of the output, which can then be populated with data.

Oracle 23ai Database and the Multilangual Engine

With Oracle Database Multilingual Engine (MLE), developers have the option to run JavaScript code through dynamic execution or with persistent MLE modules stored directly in the database.

Bringing all together

Just as with the previous article which showcased Multilingual Engine (MLE) for JavaScript Handlers in ORDS we will use the 23ai database available at Oracle Autonomous Database. There we have a /ords/hr/demojs/employee/:id GET handler which has a JavaScript implementation for running a SELECT query to get an employee record for a employee_id provided in the URI.

In this article we’ll implement in JavaScript a GET handler to return all employee records but in an XML format. It will be very similar to the previous handler but reuse a JavaScript library so there’s actually less code. The additional step is that the JavaScript Mustache template engine will be imported into an MLE Module and then referred to by our JavaScript source in our ORDS JavaScript handler to transform the JSON representation of a query result set into an XML document returned by the service.

Defining the Mustache MLE Module

Mustache JavaScript

To use Mustache in ORDS JavaScript handler, you need to define it as an MLE module so that it can be imported by the dynamic MLE JavaScript in the handler source. The simplest way to get this done is include it from a content delivery network like jsDelivr. Here’s the URL for Mustache version 4.2.0: https://cdn.jsdelivr.net/npm/mustache@4.2.0/mustache.mjs.

The availability of JavaScript components like this supports self-contained and reusable code, key to developing successful software projects. We will use this library unmodified. However, due to a difference in architecture, module imports behave slightly differently in the Oracle Database when compared to other development environments. For example, JavaScript source code used with Node.js is stored in a specific directory structure on disk. MLE modules are stored together with the database, rather than in a file system, so must be referenced in a different manner. Let’s get that Mustache MLE module defined

Creating the MLE Module in Autonomous Database

The simplest way to define a module, from an external source, in an 23ai Autonomous Database is to use ORDS Database Actions. Note that one must be using a 23ai database, otherwise the menu option in Database Actions does not appear. So, first step, login to Database Actions ( AKA SQL Developer Web ) https://my-autonomous-database.oraclecloudapps.com/ords/sql-developer and choose the Development->JavaScript menu option…

The Development->JavaScript section provides a handy UI for managing MLE Modules
If this is your first time defining an MLE Module the JavaScript area in Database Actions will look like this

In the MLE Editor there is a globe icon with an arrow pointing downwards. This is the Open from URL button. Press that, enter the URL https://cdn.jsdelivr.net/npm/mustache@4.2.0/mustache.mjs and press the Open button to load the text.

You should then see the source in the MLE Module editor. Enter MUSTACHE as the name ( leave the Environment info blank ) and press the Save icon to create the MLE Module.

Save the MLE Module source and call it MUSTACHE

The last part of that source, at line 764, the JavaScript exports a mustache object and that is what we will refer to in our own JavaScript: export default mustache;

Now, to use that MLE Module it must be included in an MLE Environment. Amongst other things, MLE environments are used for name resolution of JavaScript module imports. For this example we will define an MLE Environment called LIBRARY_ENV where we specify a namespace for modules we want to reuse.

Let’s switch to an SQL Worksheet environment for the rest of this article. Run this to create that environment…

CREATE MLE ENV LIBRARY_ENV IMPORTS ('mustache' module MUSTACHE);

With that in place, we’re now ready to create our JavaScript handler.

Developing the ORDS REST Service

Now, define a JavaScript handler that will query all records from HR.EMPLOYEES table and format the SQL results into an XML response using Mustache. The service will be part of the existing demo_mle_javascript module and available at employees/ so we will define a template and a handler:

begin
  ORDS.DEFINE_TEMPLATE(
      p_module_name    => 'demo_mle_javascript',
      p_pattern        => 'employees/');

  ORDS.DEFINE_HANDLER(
      p_module_name    => 'demo_mle_javascript',
      p_pattern        => 'employees/',
      p_method         => 'GET',
      p_source_type    => 'mle/javascript',
      p_mle_env_name   => 'LIBRARY_ENV',
      p_source         => 
q'~ 
 (req, resp) => {
    const query = 'select * from employees order by employee_id';
         const res = session.execute(query);
    const mustache = await import('mustache');

    var template = '<employees>{{#rows}} <employee id="{{EMPLOYEE_ID}}" first_name="{{FIRST_NAME}}" last_name="{{LAST_NAME}}" salary="{{SALARY}}"/>{{/rows}}</employees>';

    var output = mustache.default.render(template, res);
    resp.content_type('application/xml');
    resp.status(200);
    resp.send(output);
}
~'
      );
end;

Let’s break that down and talk about the distinct parts…

ORDS.DEFINE_TEMPLATE(
      p_module_name    => 'demo_mle_javascript',
      p_pattern        => 'employees/');

Not much to explain about the above. We’re defining a pattern for a URI to add to an existing ORDS REST Service module and will have one or more handlers defined.

The parameters to the DEFINE_HANDLER procedure are more interesting so let’s take a look at them:

p_module_name    => 'demo_mle_javascript',
This states which module the handler belongs to.

p_pattern => 'employees/',
This states which template in that module the handler belongs to.

p_method => 'GET',
Handlers are define for a specific HTTP method. This handler will be for GET requests.

p_source_type => 'mle/javascript',
Handler source can be of various types. Some simple such as a basic select query and some more complex such as pl/sql block. This handler's source is dynamic MLE JavaScript.

p_mle_env_name => 'LIBRARY_ENV',
When dynamic MLE JavaScript imports a module it must state the MLE Environment where that module's namespace is defined. This is important for this handler's source and MUST be specified.

The p_source parameter content is more complex and deserves it’s own explanation. At a high level the steps are:

  1. Define a query and execute it, storing the reference to the results in a variable called res
  2. Import the Mustache MLE module and refer to it as mustache. Note that import is asynchronous so we wait for it to complete.
  3. Defines a Mustache template for the XML structure. The curly bracket syntax is Mustache specific. Refer to Mustache documentation for more information.
  4. Formats the data using Mustache and save that as a variable called output. Note that we reference the default object because that is what was in the export definition in the imported mustache.mjs
  5. Sends the formatted XML as the HTTP response.

Remember that, as per ORDS REST JavaScript Handler developer guide documentation that the defined JavaScript code must be inside an anonymous function that receives the following two parameters: ORDS request object and ORDS response object. In the p_source parameter for this handler those two parameters are called req and resp respectively.

(req, resp) => {
// define the query
const query = 'select * from employees order by employee_id';
// execute it
const res = session.execute(query);
// import the mustache JavaScript library
const mustache = await import('mustache');

// define the template
var template = '<employees>{{#rows}} <employee id="{{EMPLOYEE_ID}}" first_name="{{FIRST_NAME}}" last_name="{{LAST_NAME}}" salary="{{SALARY}}"/>{{/rows}}</employees>';

// format the data from the result set
var output = mustache.default.render(template, res);

// send the formatter XML as the HTTP response
resp.content_type('application/xml');
resp.status(200);
resp.send(output);
}

Testing and Debugging

Testing the Endpoint

To test your new RESTful service, use tools like Postman or curl. Here’s an example using curl:

curl -X GET https://cust-23aidb.adb.eu-frankfurt-1.oraclecloudapps.com/ords/hr/demojs/employees/

Obviously your server hostname will be different. Also, you don’t have to be using the HR schema or have your ORDS REST Service module base path as demojs. So your URL could be way different but if you’ve gotten this far, you’ve already figured that out.

You should see an XML response formatted according to your Mustache template.

Debugging Common Issues

  1. JavaScript Errors: Check for syntax errors or issues with the JavaScript handler.
  2. SQL Query Problems: Ensure your SQL query is correct and returns the expected results.
  3. Template Errors: Verify the Mustache template syntax and placeholders.

Conclusion

In this article, we’ve shown how to use Mustache and ORDS JavaScript handlers to transform SQL query results into beautifully formatted XML. By following these steps, you can enhance the presentation of your data and make it more accessible and readable.

Additional Resources

Try implementing this solution in your projects and feel free to share your experiences or ask questions in the comments below!

Multilingual Engine (MLE) for JavaScript Handlers

The eagle eyed amongst you would have noticed this in the ORDS 24.1.1 release notes

ORDS now supports user-defined REST Resource Handlers powered by the Oracle Database Multilingual Engine (MLE) for JavaScript—the ORDS.DEFINE_HANDLER procedure now includes a new ‘mle/javascript’ p_source_type for MLE JavaScript modules stored directly in the Oracle database.

The Multilingual Engine (MLE) was a significant innovation introduced in Oracle Database 21 for providing In-Database JavaScript and now is a key feature of Oracle Database 23ai. In Data Magic: Oracle HR REST Service with Faker.js Sample Data I provided an example ORDS REST Services PL/SQL block handler which interacted with an MLE JavaScript module and an MLE Call Specification to generate sample data for the HR Employees table.

The new handler type mle/javascript in ORDS 24.1.1 now makes it possible to code business logic in JavaScript and make that available over HTTP(S). With Oracle Database 23ai and ORDS 24.1.1 available on cloud.oracle.com for free it is even easier to explore the capabilities of JavaScript-as-a-Service with ORDS.

Get a Database 23ai autonomous database at cloud.oracle.com

The goal for this article to walk you through defining a GET handler which uses JavaScript to run a select query based on a parameter passed and then return some JSON data. We’ll use the HR sample schema and the EMPLOYEES table in particular.

Permissions

Not every database user will have the database privileges to execute dynamic JavaScript in the database so we must first get those permissions in place.

GRANT EXECUTE ON JAVASCRIPT TO HR;
GRANT EXECUTE DYNAMIC MLE TO HR;

The above will have to be executed by a sufficiently privileged user. With the Autonomous Database at cloud.oracle.com that could be the ADMIN user.

Define the service

Assuming the HR schema is already REST Enabled simply connect to the database as that user and run the following…

BEGIN
  ORDS.DEFINE_MODULE(
      p_module_name    => 'demo_mle_javascript',
      p_base_path      => '/demojs/',
      p_items_per_page => 25,
      p_status         => 'PUBLISHED',
      p_comments       => 'Demonstrate MLE Javascript Handler functionality in ORDS 24.1.1.
Javascript-as-a-Service.');

  ORDS.DEFINE_TEMPLATE(
      p_module_name    => 'demo_mle_javascript',
      p_pattern        => 'employees/:id',
      p_priority       => 0,
      p_etag_type      => 'HASH',
      p_etag_query     => NULL,
      p_comments       => NULL);

  ORDS.DEFINE_HANDLER(
      p_module_name    => 'demo_mle_javascript',
      p_pattern        => 'employees/:id',
      p_method         => 'GET',
      p_source_type    => 'mle/javascript',
      p_items_per_page => 0,
      p_mimes_allowed  => NULL,
      p_comments       => NULL,
      p_source         => 
' 
 (req, resp) => {  
    const query = ''select employee_id, first_name, salary from employees where employee_id = :1'';
    const res = session.execute(query, [req.uri_parameters.id]);

    if (res.rows.length > 0) {
        var employee_item = {};
        employee_item[''employee_id''] = res.rows[0].EMPLOYEE_ID;
        employee_item[''first_name''] = res.rows[0].FIRST_NAME;
        employee_item[''salary''] = res.rows[0].SALARY;
        resp.content_type(''application/json'');
        resp.json(employee_item);
    } else {
        resp.status(404);
    }
}
');
        
COMMIT;

END;

We’ll take a look at that handler definition in detail a little later but for now, let’s try it out. Send a GET request to /ords/hr/demojs/employees/102 and you will see the results of the query executed:

{
"employee_id": 102,
"first_name": "Lex",
"salary": 17000
}

Specify an employee identifier that does not exist and you should get a HTTP 404 response.

Using the mle/javascript handler on Autonomous Database to indicate a record was not found

Handler in detail

The first thing to point out is the new handler source type: mle/javascript.

      p_source_type    => 'mle/javascript',

Note that this source type is not only applicable to the DEFINE_HANDLER procedure but also the DEFINE_SERVICE procedure too.

The source is a dynamic definition of JavaScript function which is passed a HTTP Request object and a HTTP Response object. In the source we can specify what variable names will be used for those two objects. It is fairly common to refer to them as req and resp but any names will do. In our case, we’ll stick to the convention.

(req, resp) => {
... JavaScript goes here ! ...
}

In-Database JavaScript references

The database session for the request can be referred through the variable session and functions can be invoked, such as running a query. In this snippet we define a query which takes a bind variable and provide a value from the HTTP Request URI when executing that query.

const query = 'select employee_id, first_name, salary from employees where employee_id = :1';
const res = session.execute(query, [req.uri_parameters.id]);

Recall that the id was defined as a parameter in the template definition. The res reference now contains the result set metadata and rows reference for the executed query.

Evaluate the query results and set the HTTP Response

Check the result set to see if there are any rows. If there are, construct a JSON object to return in the HTTP Response. Otherwise, just set the HTTP status code to 404.

if (res.rows.length > 0) {
var employee_item = {};
employee_item['employee_id'] = res.rows[0].EMPLOYEE_ID;
employee_item['first_name'] = res.rows[0].FIRST_NAME;
employee_item['salary'] = res.rows[0].SALARY;
resp.content_type('application/json');
resp.json(employee_item);
} else {
resp.status(404);
}

Note that for ease of readability I have removed the escaping single quotes from any string references.

Further reading

There are a lot of concepts that have been quickly skimmed over. ORDS initially introduced a limited JavaScript-as-a-Service product offering with javascript module plugins which required GraalVM. With MLE the JavaScript execution can be performed in the database so a GraalVM runtime environment is not required but more significantly, any REST Enabled database user can define their JavaScript business logic and make it available as a REST service.

Two important MLE related documents to discover more about what you can do with MLE are:

Through REST Enabling packages, procedures and functions, or supporting custom pl/sql handlers, ORDS makes it easy to access business logic associated with your data. Now you have another string to your bow: mle/javascript.

More detailed documentation and examples to come.

Java 11 – Diagnose JDBC proxy connection

A few years ago this Diagnose JDBC proxy connection article was really useful to point people to if they had an issue with JDBC/UCP connections in ORDS. Since then ORDS has moved to Java 11 so an update on the steps is long overdue…

…and it is now available at https://github.com/pobalopalous/ords-ucp-example. There you will find a class that does the same JDBC proxy test but getting it running is much simpler.

Simply clone the repository or download it as a zip file and follow the steps in the README.

This test could be used with the standard Oracle JDBC/UCP distributed jars but the original intention is use the jars that are distributed with ORDS. Your version of ORDS might be different so the README shows steps to find the exact jar names to use.

What you should see when the UCPProxyTest is successful

RESTful CSV file

An article from earlier this year by Ulrike Schwinn titled Oracle Object Storage Access for all Oracle Databases with DBMS_CLOUD got me thinking about doing more with CSV data because it can be treated as a table through DBMS_CLOUD.CREATE_EXTERNAL_TABLE. In essence, provide secure RESTful access to the data, and even visualise that data through charts.

Very quickly, we’ll go from a uploading a Salary.csv dataset to rendering a chart of Job Titles for the highest education level and permitting RESTful queries on data…

Example chart generated on a sharable URL
Query the CSV data through ORDS RESTful interface

Salary Data

Let’s start with a CSV dataset. For this exercise we’ll use a public domain Salary by Job Title and Country from kaggle.com. The download is a ZIP archive with two files. In this case it is just the Salary.csv which will be used.

This dataset provides a comprehensive collection of salary information from various industries and regions across the globe.
Oracle Object Storage Buckets will be used for storing the Salary.csv

I’m not going into all the details of downloading the archive file, unzipping it, creating a bucket, and uploading the Salary.csv but needless to say, those are important steps to get you this stage…

Dataset is uploaded to my ‘example-bucket’

Note that the file has a URL for accessing and downloading it but that URL is not publicly accessible.

The file can be accessed at this URL, but not publicly

External Table

In the database, the DBMS_CLOUD package can create a table which has it’s data loaded from the CSV file. To get access to object storage from the database we require a credential object through DBMS_CLOUD.CREATE_CREDENTIAL. Details on achieving that are already covered in Ulrike’s Oracle Object Storage Access for all Oracle Databases with DBMS_CLOUD article and my previous article Data move mojo with ORDS 21.3.0. In fact, I’m going to reuse that DATAPUMP_CRED_NAME credential. That’s just out of convenience for me. You be you and do your own thing.

To create the SALARY table we must specify a column list with data types and sizes. That requires figuring out, based on the data and also the description on kaggle.com, what data types makes sense. Some columns are obviously numeric, but it is not always clear how big or small those columns should be.

begin 
    DBMS_CLOUD.CREATE_EXTERNAL_TABLE(table_name=>'SALARY', 
                   credential_name => 'DATAPUMP_CRED_NAME',
                   file_uri_list => 'https://frckdrxj9oco.objectstorage.eu-frankfurt-1.oci.customer-oci.com/n/frckdrxj9oco/b/example-bucket/o/Salary.csv',
                   format => json_object('type' VALUE 'CSV', 'skipheaders' VALUE '1'),
                   column_list =>'Age NUMBER(4,1),
                                  Gender varchar2(6),
                                  Education_Level NUMBER(1),
                                  Job_Title varchar2(100),
                                  Experience NUMBER(4,1),
                                  Salary NUMBER(9,2),
                                  Country varchar2(200),
                                  Race varchar2(100),
                                  Senior NUMBER(1)'
                            );
end;
/

Note that the file_uri_list parameter came directly from the object details panel in Object Storage.

Unless there’s a syntax error, creating the table will always work. You’ll only find out about problems when you run a select query. That’s when the DB attempts to read the file.

If you have done this right…select * from salary returns data from the Salary.csv file.

Chart

ORDS Database Actions has a charts and dashboard feature that we’re going to use now. Charts enable you to create charts from the database. The chart is constructed using the input SQL command. Dashboards enable you to group charts together to create reports. Let’s define a bar chart showing the number of job titles for the highest education level. So that the chart is not too busy I’m limiting the chart to only show job titles that have more than 10 occurrences.

Defining a bar chart for job titles

For simplicity my chart definition says that it is unprotected, so it is publicly available and anyone can see it with the URL. That’s ok with this scenario because the data is in the public domain anyway but please consider the protection on your charts and dashboards.

That URL can be shared and it will render in the browser like this…

Note that the data can be viewed as a chart, or as a table.

Although the chart and its data is available to anyone with the URL, the data is just the data for the chart.

No direct access to the original Salary.csv data, just aggregation from a specific query

For many folks the only way they can create a chart based on a CSV file and share it is through using a spreadsheet and sharing that, which means sharing the underlying data. In this case the data is not shared, just the chart. So far so cool. Now let’s look at sharing that data and allowing clients to query it.

AutoREST

This will be a short section. It is as simple as:

EXEC ORDS.ENABLE_OBJECT(p_object => 'SALARY');

That’s it. The SALARY table is now accessible through a REST service at /ords/<my schema>/salary/. Moreover, the Filter Object query parameter provides a powerful search mechanism through the Salary.csv file. For example, let’s find people in the data set with more than 30 years experience: q={"experience":{"$gt":30}}

Records in the CSV can be search with relational operators!

You can’t do that so easily in a text editor 😁

See Filter Objects in the ORDS Developer Guide for more information. Of course this RESTful URL can be protected to require a authorisation for accessing it too while the chart is left unprotected.

Note that with standard REST Enabled tables you can perform DELETE, GET, POST and PUT methods but with external tables, the only functional method is GET. All others will get a HTTP 555 response with this message: ORA-30657: operation not supported on external organized table.

Conclusion

If you have followed the above steps you have opened up new ways of accessing, sharing and interrogating data in a CSV file.

To reiterate, when using an external table the data comes from files in the Cloud or from files in a directory. The data does not get loaded or copied into a table in the database. Drop in a new Salary.csv file with different data and it is immediately picked up on the next query.

The combination of Object Storage, Autonomous Database and Oracle REST Data Services provide some interesting options for doing more with CSV files. It has its limits though. To be realistic, large volumes of data and complex queries would require actually loading copies of the data into the database. For that one should consider Autonomous Database Data Studio to load the data. You can still use ORDS as above to share the data as charts or expose a RESTful interface to the data.

This article was inspired by Ulrike’s January blog post. More excellent articles on data management can be found at https://blogs.oracle.com/authors/ulrike-schwinn. Well worth a look.

ORDS 24.1.0 is faster

Updated for 23ai. The structure for Duality Views changed from 23free to 23ai release. In summary, the identifiers for rows are now _id rather than driverId, raceId, teamId.

ORDS 24.1.0 was released today and is available for download at https://oracle.com/rest. The release notes outline the enhancements and bug fixes. Many are notable. That includes performance improvements for JSON-Relational Duality Views and AutoREST in general.

Here’s an outline of test results using ORDS 24.1.0 running in my Developer DB VM on my laptop. It’s not a blindingly fast Oracle Exadata database setup so the absolute response times are nothing to write home about but the comparison of ORDS 24.1.0 over 2 previous ORDS releases is the point of the exercise here.

Noticeable improvement in the Duality View response time statistics for ORDS 24.1.0
Similarly, a noticeable improvement in the Table response time statistics for ORDS 24.1.0

Data Shape

The data shape is the RESTful Car-Racing Example. In particular the GET of TEAM and TEAM_DV data through ORDS. Using k6 scripts for generating requests, there are 10 concurrent clients that are sending GET resource item requests 1,000 times each. The resource item identifier, team_id, is random so not the same row is returned ever time. The data set is not huge by any stretch of the imagination and does not reflect a likely production data shape. However, the purpose here is to compare changes in response times and throughput.

Authentication

Those AutoREST services provided by ORDS for the tables and duality views are protected because that’s most likely to be the situation in production. The k6 script is hard coded with client id and client secret for authentication. When the k6 test script starts the first thing performed is to get an access / bearer token which is then subsequently used for every request generated by the script. You will notice in every script run this output indicating the token that will be sent for every request.

INFO[0000] {"access_token":"u2mkjkD3lyJXDQYqgjedag","token_type":"bearer","expires_in":3600}  source=console

Checks

There is also the output for checks performed on every response to ensure the expected data is returned. For example, if the authorisation token was invalid, the response status code would not be 200.

     ✓ is status 200
     ✓ is application/json
     ✓ is requested team resource

More on the k6 scripts later. Now let’s look at the database and ORDS configuration.

Configuration

Here’s a breakdown of the Developer DB VM configuration where the database and various versions of ORDS were run for the tests. The version of both ORDS and the Java Runtime Environment have been modified so if you are not familiar with that process see Get the latest – Enhance your DB Developer VM

Database

The same Oracle Database 23 Free edition that came with the Developer DB Virtual Machine is used. The HR schema is REST Enabled and contains the Car-Racing Duality View database objects.

ORDS Settings

The default configuration settings are used. These are the same configuration settings for all versions included in the test.

Default, out of the box, configuration that came with the Developer DB VM

Java Runtime Environment

The virtual machine was originally packaged with Oracle Java 11.0.19 but it is easy to change that. In this virtual machine Oracle GraalVM for Java 17 is used.

It is straight forward to download another JRE distribution and create a symbolic link to it from your $ORDS_HOME/jre directory. The ORDS shell script will use that Java Runtime Environment irrespective of the $PATH or $JAVA_HOME environment variable.

[oracle@localhost ~]$ /home/oracle/ords/jre/bin/java --version
java 17.0.9 2023-10-17 LTS
Java(TM) SE Runtime Environment Oracle GraalVM 17.0.9+11.1 (build 17.0.9+11-LTS-jvmci-23.0-b21)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.9+11.1 (build 17.0.9+11-LTS-jvmci-23.0-b21, mixed mode, sharing)

That is the setup for every test run. Startup a specific version of ORDS in standalone mode using the same configuration folder, and JRE, for each ORDS version.

  • Start ORDS in standalone mode: /home/oracle/ords-23.3.0/bin/ords serve
  • Start the test run: k6 run team_dv.js
  • Gather the results
  • Start the test run: k6 run team.js
  • Gather the results
  • Shutdown ORDS

Repeat for /home/oracle/ords-23.4.0/ and /home/oracle/ords-24.1.0/ to cover those releases too. Not sure how to get your Developer DB VM to have multiple versions of ORDS? Take a look at Get the latest – Enhance your DB Developer VM.

Results

The test runs involve sending requests to GET a random team record, either as a Team Duality View query or query directly on the Team Table that the duality view uses.

Results – ORDS 23.3.0

ORDS 23.3.0 AutoREST Duality View

Duality View row GET using ORDS 23.3.0
iteration_duration:
avg=474.61ms
min=77.88ms
med=469.71ms
max=1.79s
p(90)=671.22ms
p(95)=741.16ms

throughput 20.917985/s

ORDS 23.3.0 AutoREST Table

Table row GET using ORDS 23.3.0
iteration_duration:
avg=130.91ms
min=18.3ms
med=132.64ms
max=390.02ms
p(90)=190.82ms
p(95)=212.67ms

throughput: 75.742323/s

Results – ORDS 23.4.0

ORDS 23.4.0 AutoREST Duality View

Duality View row GET using ORDS 23.4.0
iteration_duration: 
avg=451.33ms
min=76.64ms
med=450.22ms
max=1.22s
p(90)=642.21ms
p(95)=696.75ms

throughput 22.055704/s

ORDS 23.4.0 AutoREST Table

Table row GET using ORDS 23.4.0
iteration_duration: 
avg=163.61ms
min=19.53ms
med=160.07ms
max=1s
p(90)=241.68ms
p(95)=270.31ms

throughput: 60.703901/s

Results – ORDS 24.1.0

Now for the test run with the latest version of ORDS.

Oracle REST Data Services version : 24.1.0.r0960841
Oracle REST Data Services server info: jetty/10.0.20
Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 17.0.9+11-LTS-jvmci-23.0-b21

ORDS 24.1.0 AutoREST Duality View

Duality View row GET with ORDS 24.1.0
iteration_duration:
avg=134.29ms
min=9.97ms
med=90.28ms
max=1.41s
p(90)=261.21ms
p(95)=522.78ms

throughput: 73.321615/s

ORDS 24.1.0 AutoREST Table

Table row GET with ORDS 24.1.0
iteration_duration:
avg=88.72ms
min=11.99ms
med=84.26ms
max=619.03ms
p(90)=134.83ms
p(95)=153.84ms

throughput: 111.435388/s

K6 Scripts

The scripts are quite simple. There are effectively only 3 files involved and they are all available at this github gist: https://gist.github.com/pobalopalous/dcf572abfce1b18f7f49168918231765

  • ords_auth.js provides a function for getting a bearer token
  • team.js tests the AutoREST endpoint for TEAM table and uses a function imported from ords_auth.js to get an access token. It is hardcoded with a client_id and client_secret and randomly gets team data based on their team_id.
  • team_dv.js tests the AutoREST endpoint for TEAM_DV duality view and uses a function imported from ords_auth.js to get an access token. It is hardcoded with a client_id and client_secret and randomly gets team data based on their team_id.

Do it yourself

With the RESTful Car-Racing Example data you should be able to run the same k6 scripts for testing your environment. If you have not done so already install k6 and download the team.js and team_dv.js script files. Just note that the CLIENT_ID and CLIENT_SECRET values will have to change for you database.

const ORDS_CLIENT_ID = 'ymDCZv5ePvte2kN8seOygw..';
const ORDS_CLIENT_SECRET = 'alU5L4Xp5GXOoT-qo9v6UQ..';

It is explained in the RESTful Car-Racing Example article how to get this information from the database. If your schema or port number is different, you’ll have to modify those too.

The upshot from my test runs is approximately 3 times improvement on throughput per second for JSON-Relational Duality Views and 2 times improvement for the throughput per second for a table. Your results may show different absolute response times but I expect you will see a similar improvement ratio when comparing with previous versions of ORDS.

Reply in the comments with your own findings. I look forward to hearing from you.