Managing JavaScript modules and environment in Database Actions

August 3, 2023 | 9 minute read
Text Size 100%:

An earlier post described how to create a JavaScript module in Oracle Database 23c Free-Developer Release. Conceptually modules can be compared to PL/SQL packages, and just like packages they don't typically come on their own. The power of modular development is available in JavaScript as well. This post covers how to create modules, and more importantly, how a given module can import functionality exported by another module.

Example Code

The demo is built around 2 MLE JavaScript modules:

  • HELPER_MODULE exports a function accepting a delimited string, transforming it to a JavaScript object.
  • BUSINESS_LOGIC imports this function to process an order.

The complete code is shown in the appendix, only those parts relevant to the discussion are repeated here. This post was created using ...

  • Oracle Database 23c Free-Developer Release
  • Oracle Restful Data Services (ORDS) 23.2.0.

Understanding module dependencies

I started by logging into Database Actions. After successful authentication I entered the world of JavaScript by clicking on the MLEJS tile. Using the code in appendix I created both JavaScript modules. If you inspect the top part in BUSINESS_LOGIC closely you will find that it imports a function:

create mle module business_logic language javascript as

import { string2obj } from 'helpers';

export function processOrder(orderData) {

    // more code

Importing an (ECMAScript) module's functionality works in the exact same way in Oracle Database 23c Free-Developer Release as it does in node or deno. Except that modules are stored in the database rather than the filesystem. Enter the MLE environment: using an environment you can tell the JavaScript engine about import names and which module they correspond to.

Creating MLE environments in Database Actions

Using the tree-view on the left hand side I switched from Modules to Environments. Next I clicked on the "..." button next to the search box, then on create object to create the environment. A dialogue appeared next allowing me to set the environment specifics. You can see the result here:

create an MLE environment

I clicked on the create button, acknowledged the DDL shown and voila! A new environment appears.

Generating call specifications

I wanted to expose processOrder() to PL/SQL using a call specification. Compared with the previous example from the earlier post an additional step is needed: I had to tie module, environment, and the JavaScript function's signature to the call specification.

This can be done using Database Actions as well. I switched back to Modules, right-clicked BUSINESS_LOGIC and selected create -> call specification. I completed the wizard as per the screenshot:

create a call spec including module, env, and signature

I quickly created the orders table (shown in the appendix) and ran a test using the following anonymous PL/SQL block:

set serveroutput on
declare
    l_success boolean := false;
    l_str     varchar2(256);
begin
    l_str := 'order_id=1;order_date=2023-08-01T10:27:52;order_mode=theMode;customer_id=1;order_status=2;order_total=42;sales_rep_id=1;promotion_id=1';
    l_success := process_order(l_str);

    -- you should probably think of a better success/failure evaluation
    if l_success then
        dbms_output.put_line('success');
    else
        dbms_output.put_line('false');
    end if;
end;
/

It worked!

SQL> declare
  2      l_success boolean := false;
  3      l_str     varchar2(256);
  4  begin
  5      l_str := 'order_id=1;order_date=2023-08-01T10:27:52;order_mode=theMode;customer_id=1;order_status=2;order_total=42;sales_rep_id=1;promotion_id=1';
  6      l_success := process_order(l_str);
  7  
  8      -- you should probably think of a better success/failure evaluation
  9      if l_success then
 10          dbms_output.put_line('success');
 11      else
 12          dbms_output.put_line('false');
 13      end if;
 14  end;
 15* /
success


PL/SQL procedure successfully completed.

SQL> select * from orders;

   ORDER_ID ORDER_DATE    ORDER_MODE       CUSTOMER_ID    ORDER_STATUS    ORDER_TOTAL    SALES_REP_ID    PROMOTION_ID 
___________ _____________ _____________ ______________ _______________ ______________ _______________ _______________ 
          1 01-AUG-23     theMode                    1               2             42               1               1 

Generating a dependency diagram

Having an environment defined also allows me to show code dependencies. I loaded the BUSINESS_LOGIC module into the editor and associated business_module_env with it. Initially the environment didn't appear in the drop-down list next to the module name. I fixed that by clicking the reload button. The code dependency diagram can be brought up by using the option in the drop-down menu:

drop-down menu used to create the code-dependency diagram

The resulting diagram provides a good overview of the modules' exports and imports:

code dependency diagram showing helper module and business logic

The more modules you work with, the greater the benefit of the dependency diagram.

Summary

Database Actions greatly enhance the developer experience working with MLE modules and environments. In addition to creating, editing, and visualising dependencies you can use the MLE JS IDE to debug modules as well. This will be shown in a future article.

Appendix

If you would like to execute the code, make sure to create a user with the necessary privileges to create and run JavaScript code. Connect to the database using this account, and deploy the code:

Auxiliary Objects

create table orders (
    order_id     number(12) not null,
    order_date   date not null,
    order_mode   varchar2(8),
    customer_id  number(6) not null,
    order_status number(2),
    order_total  number(8,2),
    sales_rep_id number(6),
    promotion_id number(6),
    constraint pk_orders primary key(order_id)
);

HELPER_MODULE

create or replace mle module helper_module
language javascript as 

/**
 * convert a delimited string into key-value pairs and return JSON
 * @param {string} inputString - the input string to be converted
 * @returns {JSON}
 */
function string2obj(inputString) {
    if ( inputString === undefined ) {
        throw `must provide a string in the form of key1=value1;...;keyN=valueN`;
    }
    let myObject = {};
    if ( inputString.length === 0 ) {
        return myObject;
    }
    const kvPairs = inputString.split(";");
    kvPairs.forEach( pair => {
        const tuple = pair.split("=");
        if ( tuple.length === 1 ) {
            tuple[1] = false;
        } else if ( tuple.length != 2 ) {
            throw "parse error: you need to use exactly one '=' between " + 
                    "key and value and not use '=' in either key or value";
        }
        myObject[tuple[0]] = tuple[1];
    });
    return myObject;
}

/**
 * convert a JavaScript object to a string
 * @param {object} inputObject - the object to transform to a string
 * @returns {string}
 */
function obj2String(inputObject) {
    if ( typeof inputObject != 'object' ) {
        throw "inputObject isn't an object";
    }
    return JSON.stringify(inputObject);
}

export { string2obj, obj2String }
/

BUSINESS_LOGIC

create mle module business_logic language javascript as

import { string2obj } from 'helpers';

export function processOrder(orderData) {

    const orderDataJSON = string2obj(orderData);
    const result = session.execute(`
        insert into orders (
            order_id,
            order_date,
            order_mode, 
            customer_id, 
            order_status,
            order_total, 
            sales_rep_id, 
            promotion_id
        )
        select
            jt.*
        from 
            json_table(:orderDataJSON, '$' columns
                order_id             path '$.order_id',
                order_date timestamp path '$.order_date',
                order_mode           path '$.order_mode',
                customer_id          path '$.customer_id', 
                order_status         path '$.order_status',
                order_total          path '$.order_total', 
                sales_rep_id         path '$.sales_rep_id',
                promotion_id         path '$.promotion_id'
        ) jt`,
        {
            orderDataJSON: {
                val: orderDataJSON,
                type: oracledb.DB_TYPE_JSON
            }
        }
    );

    if ( result.rowsAffected === 1 ) {
        return true;
    } else {
        return false;
    }
}
/

BUSINESS_MODULE_ENV

create mle env business_module_env
imports (
    'helpers' module helper_module_inline
);

Martin Bach

Martin is a product manager at Oracle helping customers in Europe and around the world address their IT related problems. He is most interested in cloud technology, DevOps and how these can be used best with Oracle technology.


Previous Post

Integrating Helidon and WebLogic Microservices with Oracle MicroTx

Todd Little | 5 min read

Next Post


An Introduction to Post Execution Debugging in Oracle 23c Free - Developer Release

Martin Bach | 8 min read