Introduction to the PL/SQL Foreign Function Interface
The Foreign Function Interface (FFI) is designed to provide straightforward access to PL/SQL packages in a familiar, JavaScript-like fashion.
Using the mle-js-plsql-ffi
API, wrappers are created around
PL/SQL packages and procedures so that in subsequent calls, you can interact with them
as if they were JavaScript objects and functions. This approach can be used in certain
cases as an alternative to using the MLE
JavaScript SQL driver.
A lot of database functionality is available in the form of PL/SQL packages; either built-in, those installed by frameworks such as APEX, or user-defined PL/SQL code. The Foreign Function Interface (FFI) allows you to access PL/SQL functionality in packages and procedures directly from JavaScript code without executing SQL statements, providing a seamless integration of existing PL/SQL functionality with server-side JavaScript applications. For example, database procedures can be invoked as JavaScript functions, passing JavaScript values as function arguments.
Consider the following JavaScript snippet that uses session.execute
to
employ the DBMS_RANDOM
package inside an anonymous PL/SQL block:
CREATE OR REPLACE FUNCTION get_random_number(
p_lower_bound NUMBER,
p_upper_bound NUMBER
) RETURN NUMBER
AS MLE LANGUAGE JAVASCRIPT
{{
const result = session.execute(
'BEGIN :randomNum := DBMS_RANDOM.VALUE(:low, :high); END;',
{
randomNum: {
type: oracledb.NUMBER,
dir: oracledb.BIND_OUT
}, low: {
type: oracledb.NUMBER,
dir: oracledb.BIND_IN,
val: P_LOWER_BOUND
}, high: {
type: oracledb.NUMBER,
dir: oracledb.BIND_IN,
val: P_UPPER_BOUND
}
}
);
return result.outBinds.randomNum;
}};
/
SELECT get_random_number(1,100);
Using FFI, you can cut down on the boilerplate code needed to implement the previous example. The following snippet achieves the same functionality as the previous one in a more concise way:
CREATE OR REPLACE FUNCTION get_random_number(
p_lower_bound NUMBER,
p_upper_bound NUMBER
) RETURN NUMBER
AS MLE LANGUAGE JAVASCRIPT
{{
const { resolvePackage } = await import ('mle-js-plsql-ffi');
const dbmsRandom = resolvePackage('dbms_random');
return dbmsRandom.value(P_LOWER_BOUND, P_UPPER_BOUND);
}};
/
SELECT get_random_number(1,100);
See Also:
Server-Side JavaScript API
Documentation for more information about the
mle-js-plsql-ffi
API
- Object Resolution Using FFI
A set of functions is available with themle-js-plsql-ffi
API, each returning a JavaScript object that represents its database counterpart. - Provide Arguments to a Subprogram Using FFI
Use thearg
andargOf
functions to handleIN OUT
andOUT
parameters with the Foreign Function Interface (FFI).
Object Resolution Using FFI
A set of functions is available with the
mle-js-plsql-ffi
API, each returning a JavaScript object that
represents its database counterpart.
The following functions are available to resolve packages and top-level functions and procedures:
resolvePackage('<pkg_name>')
resolveProcedure('<proc_name>')
resolveFunction('<func_name>')
If the object you want to resolve is in your own schema or has a public synonym, qualifying the object name with the owning schema is optional. If the object is in a different schema, you must have necessary permissions to access the object and must qualify its name with the owning schema. As with the MLE JavaScript SQL driver, all operations are performed in your own security context.
Note:
If the named database object does not exist or you do not have access to
it, a RangeError
is raised. If the given name resolves to a
database object that is not the correct type, a TypeError
is
raised. Database links are not supported. Attempting to resolve a name with a
database link results in an Error
.
Note:
The provided FFI functions follow the same case-sensitivity rules as PL/SQL, meaning names are auto-capitalized by default. For quoted identifiers, you must use JavaScript dictionary notation with a combination of double and single quotes to indicate case-sensitivity:
// call a procedure with case-sensitive name
myPkg['"MyProc"']();
// read a global variable with a case-sensitive name
console.log(myPkg['"MyVar"']);
Once a database object has been resolved, you can perform the following operations on the resulting object:
- Procedure: Execute
- Function: Execute
- Package:
- Execute procedure
- Execute function
- Read and write public package variables
- Read constants
With resolvePackage
, variables, constants, procedures, and
functions can be accessed directly through property reads of the resulting object. If
the package does not have the member provided in the property read, a
Reference
error is thrown. When the accessed member is a PL/SQL
function or procedure, the JavaScript object returns the same type of callable entity
that is resolved for top level functions and procedures. Consider the following snippets
for examples of the syntax:
// resolve a package
const myPkg = resolvePackage('my_package');
// call a procedure and function in the package
myPkg.my_proc();
let result = myPkg.my_func();
// read a global variable and constant in the package
console.log(myPkg.my_var);
console.log(myPkg.my_const);
// write a global variable in the package
myPkg.my_var = 42;
For package variables and constants, only non-named types are supported. The following types are not supported: PL/SQL record types, nested table types, associative arrays, vector types, and ADTs.
When resolving a procedure or function, you receive a callable object. With
functions, the overrideReturnType
instance method can optionally be
used to specify the return type and change other metadata. Consider the following
example that uses overrideReturnType
to increase the
maxSize
attribute:
- Start by creating a function that returns a
string:
CREATE OR REPLACE FUNCTION ret_string( MULTIPLIER NUMBER ) RETURN VARCHAR2 AS BEGIN return rpad('this string might be too long for the defaults ', MULTIPLIER, 'x'); END; /
- Create another function,
ret_string_ffi
, that uses FFI to resolve the functionret_string
:CREATE OR REPLACE FUNCTION ret_string_ffi( MULTIPLIER NUMBER ) RETURN VARCHAR2 AS MLE LANGUAGE JAVASCRIPT {{ const retStrFunc = plsffi.resolveFunction('ret_string'); return retStrFunc(MULTIPLIER); }}; /
- The
ret_string_ffi
function will work as long as the multiplier value is small enough, as in the following:SELECT ret_string_ffi(50);
Result:
RET_STRING_FFI(50) -------------------------------------------------------------------------------- this string might be too long for the defaults xxx
- With a larger multiplier value, the result can exceed the default
buffer length of 200 bytes and raise an
error:
SELECT ret_string_ffi(900);
Result:
SELECT ret_string_ffi(900) * ERROR at line 1: ORA-04161: Error: Exception during subprogram execution (6502): ORA-06502: PL/SQL: value or conversion error: character string buffer too small ORA-04171: at :=> (<inline-src-js>:3:12)
- You can solve this problem by using the
overrideReturnType
instance method to increase themaxSize
attribute of the returned message:CREATE OR REPLACE FUNCTION ret_str_ffi_override( MULTIPLIER NUMBER ) RETURN VARCHAR2 AS MLE LANGUAGE JAVASCRIPT {{ const retStrFunc = plsffi.resolveFunction('ret_string'); // overrideReturnType accepts either an oracledb type constant // such as oracledb.NUMBER, or a string containing the name of a // user defined database type. If more information is needed, as // in this example, a parameter of type ReturnInfo can be provided retStrFunc.overrideReturnType({ maxSize: 1000 }); return retStrFunc(MULTIPLIER); }}; /
- Using the new
ret_str_ffi_override
function, a call with a larger multiplier will now work:SELECT ret_str_ffi_override(900);
Parent topic: Introduction to the PL/SQL Foreign Function Interface
Provide Arguments to a Subprogram Using FFI
Use the arg
and argOf
functions to handle
IN OUT
and OUT
parameters with the Foreign Function
Interface (FFI).
JavaScript and PL/SQL handle parameters differently. For instance, JavaScript
doesn't allow for named parameters in the same way that PL/SQL does. Neither does
JavaScript have an equivalent for OUT
and IN OUT
parameters, nor is there an option for overloading functions. Last, but not least,
JavaScript types are different from the database's built-in type system. To be able to
call PL/SQL from JavaScript, the FFI must accommodate these differences.
For more information about PL/SQL subprogram parameters, see Oracle Database PL/SQL Language Reference.
- multiple parameters are defined.
- parameters provide a mix of
IN
,OUT
, andIN OUT
modes. - the default
maxSize
for aVARCHAR2
OUT
variable is insufficient
CREATE OR REPLACE PROCEDURE my_proc_w_args(
p_arg1 IN NUMBER,
p_arg2 IN NUMBER,
p_arg3 IN OUT JSON,
p_arg4 OUT TIMESTAMP,
p_arg5 OUT VARCHAR2
) AS
BEGIN
SELECT
JSON_TRANSFORM(p_arg3,
SET '$.lastUpdate' = systimestamp,
SET '$.value' = p_arg1 + p_arg2
)
into p_arg3;
p_arg4 := systimestamp;
-- the length of the string will exceed the default
-- length of 200 characters for the out bind, mandating
-- the use of maxSize in args().
p_arg5 := rpad('x', 255, 'x');
END;
/
Parameters passed using the IN
mode do not require any
special treatment. The FFI provides the arg()
and
argOf()
functions to handle OUT
and IN
OUT
parameters, respectively. Remember that all parameters provided using
the FFI are essentially bind parameters and thus their behavior can be influenced using
the same dir
, val
, type
, and
maxSize
properties you use if you call PL/SQL directly using
session.execute()
.
The arg
function generates an object that represents an argument. It
optionally accepts the same object as the MLE
JavaScript SQL driver, including any combination of the dir
,
val
, type
, and maxSize
properties.
The argOf
function generates an object that represents an argument of
the given value.
- As a list of positional arguments.
- Using an object to provide the arguments, simulating named parameters.
Based on the function created in the preceding example,
my_proc_w_args
, you can invoke the function with the FFI using
positional arguments as follows:
CREATE OR REPLACE PROCEDURE my_proc_w_args_positional(
"arg1" NUMBER,
"arg2" NUMBER
) AS MLE LANGUAGE JAVASCRIPT
{{
const myProc = plsffi.resolveProcedure('my_proc_w_args');
// arg3 is an IN OUT parameter of type JSON. my_proc_with_args
// will modify it in place and return it to the caller
const arg3 = plsffi.argOf({id: 10, value: 100});
// arg4 is a pure OUT parameter
const arg4 = plsffi.arg();
// arg5 represents an OUT parameter as well but due to the
// length of the return string, it must be provided with additional
// metadata
const arg5 = plsffi.arg({
maxSize: 1024
});
myProc(arg1, arg2, arg3, arg4, arg5);
console.log(`the updated JSON looks like this: ${JSON.stringify(arg3.val)}`);
console.log(`the calculation happened at ${arg4.val}`);
console.log(`the length of the string returned is ${arg5.val.length} characters`);
}};
/
The second option is to use named arguments, provided as a single, plain JavaScript object. The FFI API then maps each property to the argument that matches the name of the property.
CREATE OR REPLACE PROCEDURE my_proc_w_args_named(
"arg1" NUMBER,
"arg2" NUMBER
) AS MLE LANGUAGE JAVASCRIPT
{{
const myProc = plsffi.resolveProcedure('my_proc_w_args');
// arg3 is an IN OUT parameter of type JSON. my_proc_with_args
// will modify it in place and return it to the caller
const arg3 = plsffi.argOf({id: 10, value: 100});
// arg4 is a pure OUT parameter
const arg4 = plsffi.arg();
// arg5 represents an OUT parameter as well but due to the
// length of the return string must be provided with additional
// metadata
const arg5 = plsffi.arg({
maxSize: 1024
});
myProc({
p_arg1: arg1,
p_arg2: arg2,
p_arg3: arg3,
p_arg4: arg4,
p_arg5: arg5
});
console.log(`the updated JSON looks like this: ${JSON.stringify(arg3.val)}`);
console.log(`the calculation happened at ${arg4.val}`);
console.log(`the length of the string returned is ${arg5.val.length} characters`);
}};
/
Note the edge case where you have a PL/SQL subprogram that has a single argument that is
represented in JavaScript as an object
. Intuitively, you may want to
pass it as a single positional argument, however, in that case, the FFI will interpret
it as a named arguments object.
- You can wrap your argument in an object as if you were calling the subprogram with named arguments.
- You can wrap your argument with
plsffi.argOf()
and the FFI will recognize it as a single positional argument.
Consider the following example that demonstrates these options:
-- PL/SQL subprogram we want to call
CREATE OR REPLACE PROCEDURE my_proc(my_arg JSON) AS
BEGIN
-- Process my_arg
END;
-- JavaScript function that calls my_proc
CREATE OR REPLACE PROCEDURE my_javascript_proc
AS MLE LANGUAGE JAVASCRIPT
{{
const myProc = plsffi.resolveProcedure('my_proc');
const myArg = { prop1: 10, prop2: 'foo' };
// Catch the exception that will happen if the FFI tries
// to interpret this as a call with named arguments
try {
myProc(myArg);
} catch (err) {
console.log(`if uncaught, this would have been a ${err}`);
}
// Option 1: Make it into a real named argument call.
myProc({ my_arg: myArg });
// Option 2: Wrap with argOf() to let the FFI know that it's a
// positional argument list call.
myProc(plsffi.argOf(myArg));
}};
PL/SQL allows developers to overload signatures of functions and procedures that are defined in PL/SQL packages. The FFI does not perform overload selection, however, it still needs to decide what PL/SQL type to use for binding each argument. Unfortunately, it cannot make this decision on its own in all cases. In particular, in the following instances:
- No JavaScript value was given for an argument that is needed to determine the correct signature to call. Without a value, the FFI has no way of knowing the set of matching PL/SQL types.
- When one JavaScript type is viable for multiple PL/SQL types.
- Constants that start with
DB_TYPE_*
control how the JavaScript value is converted to a PL/SQL value. - All others are used to control how the returned PL/SQL value is converted to a JavaScript value.
If you are specifying the type of your argument in order to help with type resolution, it
is best to use one of the DB_TYPE_*
constants.
Consider the following PL/SQL package:
CREATE OR REPLACE package overload_pkg AS
FUNCTION my_func(
p_arg1 IN BINARY_FLOAT
) RETURN VARCHAR2;
FUNCTION my_func(
p_arg1 IN INTEGER
) RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY overload_pkg AS
FUNCTION my_func(
p_arg1 IN BINARY_FLOAT
) RETURN VARCHAR2 AS
BEGIN
RETURN 'binary_float';
END;
FUNCTION my_func(
p_arg1 IN INTEGER
) RETURN VARCHAR2 AS
BEGIN
RETURN 'integer';
END;
END;
/
As you can see, my_proc
is overloaded, accepting both a
BINARY_FLOAT
as well as an INTEGER
. In JavaScript,
both of these types are represented as the number data type and as such, multiple
possible overloads are valid. If the FFI API cannot select the correct resolution, it is
possible to force a particular overloaded PL/SQL function by providing the PL/SQL
type.
CREATE OR REPLACE PROCEDURE force_overload
AS MLE LANGUAGE JAVASCRIPT
{{
const myPkg = plsffi.resolvePackage('overload_pkg');
let result = 'not yet called';
// Catch error ORA-04161: Error: Exception during subprogram execution
// (4161): Multiple subprograms match the provided signature
try {
result = myPkg.my_func(42);
} catch (err) {
console.log(`if uncaught, this would have been a ${err}`);
}
// Solution: use argOf to make this work
result = myPkg.my_func(plsffi.argOf(42, {type: oracledb.DB_TYPE_BINARY_FLOAT}))
console.log(`and the result is: ${result}`);
}};
/
An error can also occur if the type is user-defined. For example, all JavaScript objects are considered viable for all PL/SQL records. In this case, it is enough to provide the name of the desired type.
Parent topic: Introduction to the PL/SQL Foreign Function Interface