How to Use Server-Side JavaScript in Oracle APEX with Oracle 23ai #JoelKallmanDay

#JoelKallmanDay

Every year in October, the Oracle Community shares technical (or non-technical) content to gain some attention. I began participating in 2021 with my (almost) first post on this blog. I remember feeling like I was part of something. A few years later, I am part of the Oracle ACE program (which is still unreal to me to be honest!), I work at Insum with many talented colleagues and I am still enjoy sharing content with the Oracle community, especially the Oracle APEX one. If you haven’t yet or are hesitant to start doing it 👇

Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something.
- Richard Stallman -

This year, I decided to write about a topic that I believe fits perfectly with #MOCA (Make Oracle Cool Again) 🤓

Introduction

Unless you've been living in a cave recently, you've probably heard about the new Oracle database release 23ai. This release brings the ability to use JavaScript directly in your database, thanks to the Oracle Database Multilingual Engine (MLE) powered by GraalVM.

You're probably wondering what the point is since we already have PL/SQL. That's a valid question, and even though I love JavaScript, I won't use it for everything. However, we have to admit that the JavaScript ecosystem is much larger than the PL/SQL one. There aren't many PL/SQL libraries available, but it's easy to find a JavaScript library. This is simply because PL/SQL is only used with Oracle Database, while JavaScript is almost everywhere these days!

For example, a few months ago, I created a simple Oracle APEX application that reads EXIF data from photos and displays it nicely. It would have been possible to do this in PL/SQL, but I would have needed to write an entire parser. By using an existing JavaScript library, exifr, I was able to complete it in just a few hours 😍

Alright, I think that's enough for the introduction. Let's take a closer look at the implementation 🤓

Configuring the database objects

To use JavaScript code in the database, you need to set up two database objects: an MLE Module and an MLE Environment. But first, let's understand why this is necessary. The Oracle Database Multilingual Engine (MLE) uses execution contexts to run JavaScript in the database. The MLE Environment allows you to customize how these contexts are executed by providing two things:

  • the language options, you can for example enforce strict mode using js.strict=true. The full list of options is available in the documentation

  • the list of the modules to import

The good news is that you can do this in just a few clicks without leaving the Oracle APEX builder.

Configuring the MLE Module

To start configuring the MLE Module, go to the Object Browser in your Oracle APEX environment. Scroll down the list of available objects until you find the section labeled "MLE Modules - JavaScript." Right-click on it to open a context menu. From the options, choose "Create MLE Module - JavaScript" to begin creating a new JavaScript module.

Screenshot showing the dropdown menu with options including to create an MLE Module by right clicking on the MLE Modules - JavaScript entry in the Object Browser

A modal page will open, allowing you to create your module. You have several options for adding your JavaScript code to the module. You can upload a file directly from your computer, which is useful if you have already prepared your code in a file format. Alternatively, you can paste the source code directly into the provided text area, which is convenient for quick edits or smaller scripts. Lastly, you can use a URL to link to an external source where your JavaScript code is hosted. This flexibility in input methods makes it easy to integrate your JavaScript code into the MLE Module.

Screen recording showing the three options available to create an MLE module: upload a file, paste the code or use an URL

For this application, I choose to go with the URL and use one of the CDN available for the exifr library: https://cdn.jsdelivr.net/npm/exifr@7.1.3/dist/full.esm.js. Here is the screenshot of the configured MLE Module

Screenshot showing the option used in this example: the URL with the CDN to load the exifr library

Configuring the MLE Environment

At the bottom of the object list, right-click on the MLE Environments entry and then select the "Create MLE Environment" option.

Screenshot showing the dropdown menu with options to create an MLE Environment by right clicking on the MLE Environment entry in the Object Browser

In the dialog box, enter a name for your environment and click "Create MLE Environment."

Screenshot showing the create MLE Environment dialog with fields to set the name and the language options

The final step is to import the module you created into the new environment. To do this, select the MLE Environment and click the "Add Import" button.

Screenshot showing where to add import of MLE modules

In the dialog, you can select the Module Owner, the module Name and finally an Import Name. See the configuration below

Screenshot showing the the import module dialog with options used in this blog post to load the exifr library

Oracle APEX configuration

Now that the MLE Module and Environment are set up, you need to specify which environment your Oracle APEX application will use. To do this, go to the Security Attributes of your app under Shared Components. On that page, click on the Database Session tab and select the MLE Environment you created earlier (see screenshot below).

Screenshot of the Oracle APEX App Builder interface, showing the "Edit Security Attributes" page. The "Database Session" tab is selected, "MLE Environment" set to "APEX_JS."

Writing server-side JS within Oracle APEX

After setting everything up, we still need to write JavaScript code. Let's look at a simple EXIF app as an example. The user interface is pretty simple, with only one file browser page item, and upon upload, the data is displayed. Here is what it looks like, and you can also test it yourself here.

Screen recording of the EXIF data viewer build with the use of server side JS in Oracle APEX

Storing the file in a collection

The page is quite simple, with one page item P1_FILE of type File Upload. It uses the table APEX_APPLICATION_TEMP_FILES as the storage type and restricts it to images only.

A screenshot of a File Upload configuration form with settings for identification, label, display, and storage options, such as file types and size.

On the change event of the page item, a Dynamic Action is submitting the page.

Screenshot showing the dynamic action on change event on the P1_FILE page item with the action SUBMIT

On page submit, a PL/SQL process stores the file content into a collection using the following code.

declare
    l_blob_content apex_application_temp_files.blob_content%type;
    l_mime_type apex_application_temp_files.mime_type%type;
    l_filename apex_application_temp_files.filename%type;
    l_id apex_application_temp_files.id%type;
begin
    apex_collection.create_or_truncate_collection(
        p_collection_name => 'C_UPLOADED_FILE'
    );

    select blob_content, mime_type, filename, id
      into l_blob_content, l_mime_type, l_filename, l_id
      from apex_application_temp_files
     where name = :P1_FILE;

    apex_collection.add_member(
        p_collection_name => 'C_UPLOADED_FILE',
        p_n001 => l_id,
        p_c001 => l_filename,
        p_c002 => l_mime_type,
        p_blob001 => l_blob_content
    );
end;

Extracting the EXIF data

Right after storing the file in the collection, another process uses the exifr library to extract the data. The process looks the same as a regular PL/SQL page process, except that the Language attribute is set to JavaScript (MLE) and the code is written in JavaScript 🤩

Screenshot showing page process in the Oracle APEX builder configured to use the Language JavaScript (MLE) instead of the PL/SQL

But, let’s see in detail the JS code there

// First we have to load the module
const { default: exifr } = await import ('exifr');

// Getting the file name from the page item P1_FILE
var fileName = apex.env.P1_FILE;

// Getting the blob from the database
// the blob is converted to an Uint8Array
// For more info about type conversion: https://docs.oracle.com/en/database/oracle/oracle-database/23/mlejs/mle-type-conversions.html
var result = apex.conn.execute(`
        select blob_content
          from apex_application_temp_files
         where name = :name`,
    {
        name: fileName
    }, {
        fetchInfo: {
            BLOB_CONTENT: {
                type: oracledb.UINT8ARRAY
            }
        }
    });

// Call the parse function of the exifr library
// The output is then assign to the page item P1_DATA
exifr.parse(result.rows[0].BLOB_CONTENT.buffer).then(output => apex.env.P1_DATA = JSON.stringify(output) );

Displaying the data

The collection is used to display the image using the built in Image template, for the tabular data, a simple json_table is used to parse the JSON stored in the P1_DATA page item. For example, here is the query used to display the Camera details

select 
    make,
    model,
    lens_make ||' ' || lens_model as lens,
    focal_length,
    '1/' || round(1 / exposure_time) || ' seconds' as exposure,
    aperture,
    iso,
    flash
  from json_table(:P1_DATA, '$'
    columns(
        make          varchar2(150) path '$.Make',
        model.        varchar2(150) path '$.Model',
        lens_make     varchar2(150) path '$.LensMake',
        lens_model    varchar2(150) path '$.LensModel',
        focal_length  varchar2(150) path '$.FocalLength',
        exposure_time number        path '$.ExposureTime',
        aperture      number        path '$.ApertureValue',
        iso           number        path '$.ISO',
        flash         varchar2(150) path '$.Flash'
    )
)

Conclusion

In this blog post, we have seen how to configure the required pieces to get started using server-side JavaScript code within an Oracle APEX application. The example demonstrates the power of leveraging the JS ecosystem, where open-source libraries can do pretty much everything. However, I recommend you think carefully before integrating any dependency within your project. As for APEX plug-ins, you have to consider the risk in terms of security and obsolescence. The second thing, which is really important and so often forgotten, is licensing. You have to ensure that the licensing of the third-party library is compatible with your own license.

That said, I am really excited to see how we can use this power in the near future, especially with Python coming soon. Well done Oracle: this is an amazing feature!-

References