5 Introduction to Nashorn JavaScript Engine

Nashorn JavaScript engine enables running JavaScript directly in the database. The ability to reuse existing client-side JavaScript in the database, and then combining it with the Java SE libraries, enables the design of rich, powerful, and versatile data-bound applications that can run directly in RDBMS sessions. This section describes the Nashorn JavaScript Engine in the following sections:

5.1 About Using Nashorn JavaScript Engine

One of the important features of Java 8 is the Nashorn JavaScript engine. Starting from Oracle Database 12c Release 2 (12.2.0.1), Oracle JVM supports Java 8, so you can execute JavaScript in the database using the Nashorn JavaScript engine, built into Oracle JVM running Java 8 codebase. Executing JavaScript directly in the database provides the following benefits:

Benefits of Running JavaScript in Oracle Database

  • Reusing existing skills and code.

  • Avoiding shipping data residing in Oracle Database. Executing JavaScript in the database enables in-place and faster processing of JSON documents inside the database. This enables avoiding shipping data to external infrastructure and improving performance as network overhead is avoided.

  • Achieving new database capabilities.

For running JavaScript code, using the Nashorn engine of 12.2 Oracle JVM in your sessions, your schema must be granted the DBJAVASCRIPT role. This role includes the permissions that are required to run Nashorn in the database. The role can be granted by your DBA and must be done only once per user.

The preferred way to maintain JavaScript sources in Oracle Database is to load them into your schema as Java resources and then run the scripts within the database.

Note:

  • The direct invocation of Nashorn classes is restricted in Oracle JVM.

  • All scripting mode extensions are disabled in Oracle JVM.

  • You can run scripts either from strings or from file streams, using standard scripting APIs, but Oracle does not recommend it. Scripts invoked in this way can run only in sandboxed mode, with the default permissions.

5.1.1 Loading JavaScript Code into a Schema

Use the loadjava command to load your JavaScript code into your database schema. Scripts are loaded as Java resources. For example, suppose you have a JavaScript file hello.js as follows:

function hello()
{
/*
*This is a sample Javascript file that prints "Hello World".
*/
var hellow = "Hello World";
return hellow;
}

var output = hello();
print(output);

Use the following command to load the hello.js script file into a schema named yourschema:

loadjava -v -u yourschema hello.js

The loadjava command prompts you for a password and then creates a schema-local resource hello.js with the following output:

arguments: '-u' 'yourschema/***' '-v' 'hello.js'
creating : resource hello.js
loading  : resource hello.js
Classes Loaded: 0
Resources Loaded: 1
Sources Loaded: 0
Published Interfaces: 0
Classes generated: 0
Classes skipped: 0
Synonyms Created: 0
Errors: 0

You can also use the following command to verify whether you have successfully loaded the hello.js file into the database or not:

select object_name, object_type from user_objects;

This resource can then be passed to the Nashorn engine using the ways mentioned in the following sections:

5.1.2 How to run JavaScript in Oracle JVM

After loading the scripts in the database, you can run them in the following three ways:

5.1.2.1 Using the DBMS_JAVASCRIPT.RUN PL/SQL Procedure

This approach is useful for JavaScript procedures that do not return any value. Invoke the DBMS_JAVASCRIPT.RUN procedure for providing the resource name as the argument. Make sure that your schema is granted the DBJAVASCRIPT role. The DBMS_JAVASCRIPT.RUN procedure is a wrapper for the oracle.aurora.rdbms.DbmsJavaScript.run Java method, which invokes Nashorn functionality internally.

From SQL

call dbms_javascript.run('hello.js');

If your JavaScript code returns an output value, then you must use the following instructions to display the value on the standard output device:

SQL>set serveroutput on
SQL>call dbms_java.set_output(20000);
SQL>call dbms_javascript.run("hello.js");

From PL/SQL:

dbms_javascript.run('hello.js');

If your JavaScript code returns an output value, then you must use the following instructions to display the value on the standard output device:

SQL>set serveroutput on
SQL>call dbms_java.set_output(20000);
SQL>call dbms_javascript.run("hello.js");
5.1.2.2 Using the DbmsJavaScript Java Class

This is the preferred way to invoke the scripts from Java code in Oracle JVM because it enables you to apply the schema permissions to the JavaScript code. Invoke the JavaScript that you loaded as resource hello.js from your Java code running in the database. Invoke the method run of oracle.aurora.rdbms.DbmsJavaScript class, passing in the resource name as the argument, as shown in the following code snippet:

Note:

Make sure that your schema is granted the DBJAVASCRIPT role.

import oracle.aurora.rdbms.DbmsJavaScript; 
…
DbmsJavaScript.run("hello.js");
5.1.2.3 Using the Standard javax.script Java Package

This approach is useful for JavaScript functions that return values. Typically, you perform the following tasks:

Note:

You must make sure that your schema is granted the DBJAVASCRIPT role.

  1. Load the JavaScript in the database as a Java resource, using the loadjava command.

  2. Use the javax.script package to run the JavaScript.

  3. Instantiate a script manager

  4. Create an engine

  5. Pass your resource stream reader as the argument to the eval method of the engine

The following code snippet shows how to use the javax.script package to run code from resource hello.js:

import javax.script.*;
import java.net.*;
import java.io.*;
...

// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine =
    factory.getEngineByName("javascript");
// create schema resource URL
URL url = Thread.currentThread()
    .getContextClassLoader().getResource("hello.js");
engine.eval(new InputStreamReader(url.openStream()));
...

You can also read the JavaScript code from a String. The following code snippet shows how to read and evaluate JavaScript code from a String:

// evaluate JavaScript code from String
engine.eval(new StringReader(<script>));

Otherwise, you can also read the JavaScript code from a file. However, this involves extra privileges. The following code snippet shows how to read and evaluate JavaScript code from the script_name file:

import javax.script.*;
...
ScriptEngine engine = new ScriptEngineManager()
    .getEngineByName("JavaScript");
engine.eval(new FileReader("script_name"));

After evaluating the JacaScript code, you typically perform the following tasks for invoking JavaScript functions:

  1. Cast the Nashorn engine to Invocable and use invokeFunction.

    For example, if the hello.js script defines a JavaScript function named hello, then you can perform these tasks as shown in the following code snippet:

    // create a JavaScript engine as above
    Invocable invocable = (Invocable) engine;
    Object myResult = invocable.invokeFunction("hello");
    ...

    Note:

    An alternative way to use the invocable.invokeFunction is to pass the return value to a Java method invoked from inside of a script. This alternative way is preferable as it requires less boilerplate code and is compatible with the DBMSJAVASCRIPT.RUN procedure.

    For example, suppose your JavaScript resource hello.js defines the selectQuery function, and you want to pass the result to the print() Java method, which is defined in your class QueryTest as follows:

       public static void print (String results) { 
       System.out.println("my results: \n" + results);
           }

    Then, to achieve this from JavaScript, add the following lines to the hello.js file:

       var queryTest = Java.type("QueryTest");
       queryTest.print(selectQuery("all")); 

    Now, invoke hellp.js in one of the following ways:

    DbmsJavaScript.run("hello.js");
    
    DBMS_JAVASCRIPT.RUN('hello.js');
  2. Create a PL/SQL procedure for invoking the JavaScript application, as shown in the following example:

    -- Create a procedure for select
    CREATE OR REPLACE PROCEDURE selectproc(id IN varchar2)
    IS
     output varchar2(10000);
    BEGIN
     SELECT invokeScriptEval(id) INTO output from dual;
     htp.prn(output);
    END;
    /
    SHOW ERRORS;

5.2 JavaScript Data Access using JDBC

This section describes how to access data in RDBMS using JavaScript. Currently, there is no JavaScript standard for accessing data in RDBMS. The Nashorn JavaScript engine enables using standard JDBC within JavaScript.
Perform the following steps to publish your application using JDBC with JavaScript as a service:
  1. Create a table in the database and populate it using JSON, as shown in the following example:
    DROP TABLE employees PURGE;
    
    CREATE TABLE employees (
      id    RAW(16) NOT NULL,
      data  CLOB,
      CONSTRAINT employees_pk PRIMARY KEY (id),
      CONSTRAINT employees_json_chk CHECK (data IS JSON)
    );
    
    TRUNCATE TABLE employees;
    
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "100",
              "FirstName" : "Kuassi",
              "LastName"  : "Mensah",
              "Job"       : "Manager",
              "Email"     : "kuassi@oracle.com",
              "Address"   : {
                              "City" : "Redwood",
                              "Country" : "US"
                            }
             }');
    
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "200",
              "FirstName" : "Nancy",
              "LastName"  : "Greenberg",
              "Job"       : "Manager",
              "Email"     : "Nancy@oracle.com",
              "Address"   : {
                              "City" : "Boston",
                              "Country" : "US"
                            }
             }');
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "300",
              "FirstName" : "Suresh",
              "LastName"  : "Mohan",
              "Job"       : "Developer",
              "Email"     : "Suresh@oracle.com",
              "Address"   : {
                              "City" : "Bangalore",
                              "Country" : "India"
                            }
             }');
    
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "400",
              "FirstName" : "Nirmala",
              "LastName"  : "Sundarappa",
              "Job"       : "Manager",
              "Email"     : "Nirmala@oracle.com",
              "Address"   : {
                              "City" : "Redwood",
                              "Country" : "US"
                            }
             }');
    
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "500",
              "FirstName" : "Amarnath",
              "LastName"  : "Chandana",
              "Job"       : "Test Devloper",
              "Email"     : "amarnath@oracle.com",
              "Address"   : {
                             "City" : "Bangalore", 
                             "Country" : "India"
                            }
             }');
  2. Write a JavaScript application using JDBC, as shown in the following example and load the JavaScript code into a schema:
    var selectQuery = function(id)
    {
       var Driver = Packages.oracle.jdbc.OracleDriver;
       var oracleDriver = new Driver();
       var url = "jdbc:default:connection:";
       var output = "";
       var connection = oracleDriver.defaultConnection();
       var prepStmt;
    
       // Prepare statement
        if(id == 'all') {
           prepStmt = connection.prepareStatement("SELECT a.data FROM employees a");
          } else {
           prepStmt = connection.prepareStatement("SELECT a.data FROM employees a WHERE a.data.EmpId = ?");
           prepStmt.setInt(1, id);
           }
    
       // execute Query
        var resultSet = prepStmt.executeQuery();
    
       // display results
        while(resultSet.next()) {
            output = output + resultSet.getString(1) + "<br>";
        }
    
       // cleanup
        resultSet.close();
        prepStmt.close();
        connection.close();
        return output;
    }
  3. Create a Java resource, as shown in the following example:
    create or replace and compile java source named "InvokeScript" as
    import javax.script.*;
    import java.net.*;
    import java.io.*;
    
    public class InvokeScript {
        public static String eval(String inputId) throws Exception {
    	String output = new String();
        try {
            // create a script engine manager
            ScriptEngineManager factory = new ScriptEngineManager();
    
            // create a JavaScript engine
            ScriptEngine engine = factory.getEngineByName("javascript");
    
            //read the script as a java resource
    				engine.eval(new InputStreamReader(InvokeScript.class.getResourceAsStream("select.js")));
    				// Alternative approach
    				//engine.eval(Thread.currentThread().getContextClassLoader().getResource("select.js"));
    
    
            Invocable invocable = (Invocable) engine;
            Object selectResult = invocable.invokeFunction("selectQuery", inputId);
            output = selectResult.toString();
        } catch(Exception e) {
    		output =e.getMessage();
    	}
            return output;
        }
    }
    /
  4. Create a wrapper for the eval method of the engine, as shown in the following example:
    -- Create function
    CREATE OR REPLACE FUNCTION invokeScriptEval(inputId varchar2) return varchar2 as language java 
    name 'InvokeScript.eval(java.lang.String) return java.lang.String';
    /
  5. Invoke the invokeScriptEval JavaScript function from SQL, as shown in the following example:
    CREATE OR REPLACE PROCEDURE sqldemo(id IN varchar2)
    IS
     output varchar2(10000);
    BEGIN
     SELECT invokeScriptEval(id) INTO output from dual;
     dbms_output.put_line(output);
    END;
    /
    SHOW ERRORS;
  6. Invoke the sqldemo procedure from SQL, as shown in the following example:
    SQL> set serveroutput on
    SQL> call dbms_java.set_output(5000);
    SQL> call sqldemo('100');

5.3 REST Enable Your JavaScript Application

Perform the following steps to make your JavaScript application a cloud-enabled service using Oracle REST Data Services (ORDS):
  1. Download the Oracle REST Data Services (ORDS) installer file from the following location:

    http://www.oracle.com/technetwork/developer-tools/rest-data-services/downloads/index.html

    Note:

    • You can perform similar steps for making your Java in the Database application a cloud-enabled service using Oracle REST Data Services (ORDS).

    • For installing ORDS, you must have a USERS tablespace with at least 40 MB space.

  2. Install ORDS by selecting the default options, except the following:
    • When it prompts whether you want to use PL/SQL Gateway or not, select 2 for skipping the step.

    • When it prompts you to specify passwords for Application Express RESTful Services database users (APEX_LISTENER, APEX_REST_PUBLIC_USER), select 2 for skipping the step.

    • When it prompts whether you wish to start the service in standalone mode or exit, select 1 for starting the service in standalone mode.

  3. Assign a listening port to the service. By default, port 8080 is used, but you may change this value in the ords_params.properties file under the .../params directory. Then, use the following command to bounce the jersey server:
    java -jar ords.war
  4. Create a procedure similar to the following as a wrapper to the invokeScriptEval method:
    Rem Create a procedure for select
    CREATE OR REPLACE PROCEDURE selectproc(id IN varchar2)
    IS
     output varchar2(10000);
    BEGIN
     SELECT invokeScriptEval(id) INTO output from dual;
     htp.prn(output);
    END;
    /
    SHOW ERRORS;
  5. Create a procedure similar to the following for executing an external JavaScript SELECT query:
    begin
      ords.create_service(
        p_module_name => 'load.routes' ,
        p_base_path   => '/load/routes/',
        p_pattern     => 'nashorn/selectbyid/:id',
        p_source_type => 'plsql/block',
        p_source      => 'begin selectproc(:id); end;'
    );
       commit;
    end;
    /
  6. Invoke the service from your browser, where the URL will be of the following format:
    http://<server>:<port>/ords/<workspace>/load/routes/nashorn/<your_JSON_query>/<input>

    For example:

    http://localhost:8090/ords/ordstest/load/routes/nashorn/selectbyid/100

    Here, 100 specifies the employee ID of the employee, whose details need to be fetched. The following image illustrates the output:

    Figure 5-1 Output of the JavaScript Application

    Description of Figure 5-1 follows
    Description of "Figure 5-1 Output of the JavaScript Application"
The output is displayed in JSON format.