Oracle 数据库中的 JavaScript
本页介绍如何使用 Oracle 数据库多语言引擎 (MLE) 在 Oracle 数据库中运行 JavaScript。MLE 由 GraalVM 提供支持:它可以在 Linux x64 上的 Oracle Database 23ai(及更高版本)中运行 JavaScript 代码。
准备工作
- 获取一个 Oracle Cloud 免费套餐账户,并选择一个提供 Oracle Database 23ai 或更高版本的主区域。
- 预置一个自治数据库事务处理实例,然后启动您喜欢的 SQL IDE,例如以下之一
先决条件:请参阅 Oracle Database 23c Free - Developer Release 中 JavaScript 简介中标题为“在 Oracle Database 23c Free - Developer Release 中使用 JavaScript 的先决条件”的部分。(尽管针对的是早期版本,但它们仍然有效。)
示例
这些示例假设您已经创建了一个数据库账户,该账户具有创建 MLE 模块和环境以及 PL/SQL 函数、过程和包所需的权限。该账户还被授予执行 JavaScript 代码的权限。(有关更多信息,请参阅 MLE 安全。)
-
第一个示例展示了如何创建 MLE 模块。
create or replace mle module helper_module_inline 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 } /
注意:MLE 仅支持 ECMA Script 模块。CommonJS 和其他常用技术不可用。
上述模块定义了两个函数
string2obj()
obj2Str()
模块末尾的
export
关键字表示这些函数可以被其他 MLE 模块、PL/SQL 以及 SQL 调用。 -
第二个示例展示了如何使这些函数在 SQL 和 PL/SQL 中可用。JavaScript 模块中的函数不能直接在 SQL 和 PL/SQL 中调用。相反,您需要为它们创建调用规范。调用规范可以被认为是 PL/SQL 中的“包装器”。根据您的要求,您可以使用独立的函数和过程,或将它们分组到一个包中。由于这两个函数属于
helper_module
,它们将放入一个包中。create or replace package helper_pkg as function string2obj( p_inputString varchar2 ) return JSON as mle module helper_module_inline signature 'string2obj'; function obj2String( p_inputObject JSON ) return varchar2 as mle module helper_module_inline signature 'obj2String'; end helper_pkg; /
有了调用规范,您可以将一个由
;
分隔的字符串转换为 JavaScript 对象,并将其返回给调用者,如下所示select json_serialize( helper_pkg.string2obj( 'order_id=1;order_date=2023-04-24T10:27:52;order_mode=mail;promotion_id=1' ) pretty ) string2obj;
您应该看到以下输出
STRING2OBJ ---------------------------------------- { "order_id" : "1", "order_date" : "2023-04-24T10:27:52", "order_mode" : "mail", "promotion_id" : "1" }
-
从 Oracle Database 23.3 开始,如果您只需要调用一段标准的 JavaScript 代码,则可以在调用规范中内联提供 JavaScript 代码,如下所示
create or replace function date_to_epoch( "ts" timestamp ) return number as mle language javascript ; /
内联 JavaScript 过程有一些限制,例如它们不能导入其他 MLE 模块(但它们可以要求 MLE 附带的库)。这就是为什么您应该考虑使用 MLE 模块来编写更复杂的逻辑和/或使其在其他模块中可重用。您现在可以直接从 SQL 调用
date_to_epoch()
函数,如本例所示select date_to_epoch ( to_timestamp('2000-01-01 12:00:00', 'yyyy-mm-dd hh24:mi:ss') ) seconds_since_epoch /
-
您可以从其他模块调用 MLE 模块。这通常发生在创建更复杂逻辑或第三方模块时。将流行的
validator.js
模块加载到数据库中,如 在 Oracle Database 23c Free - Developer Release 中使用 JavaScript 社区模块中所述——遵守许可证和合规性等注意事项。现在,您可以从您喜欢的内容分发网络 (CDN) 导入该模块的 ECMAScript (ESM) 版本,如上述文章所述。假设验证器模块在数据库中创建为validator_module
。您可以在自己的代码中如下使用该模块create or replace mle module validator_usage_demo_module language javascript as import validator from 'validator'; export function emailCheck(str) { return validator.isEMail(str); }
将导入名称 (
validator
) 映射到模块的元素称为 MLE 环境。由于没有 MLE 可以用来解析导入名称的文件系统,您必须明确声明映射,如下所示create or replace mle env validator_env imports ( 'validator' module validator_module );
MLE 环境有助于将调用规范中的导入名称映射到模块。如果您想公开 SQL 函数
emailCheck()
,请创建以下调用规范create or replace function email_check(p_str varchar2) return boolean as mle module validator_usage_demo_module env validator_env signature 'emailCheck'; /
现在您几乎可以在任何地方调用
email_check
。
类型转换
了解类型转换的工作原理非常重要——无论何时您的代码“离开”SQL 或 PL/SQL,类型都必须转换为其对应的 JavaScript 类型。(有关类型转换的更多信息,请参阅 MLE 类型转换。)
您可能知道 JavaScript Number
是以双精度 64 位二进制格式 IEEE 754 值实现的。它可能存在可存储的最大数值以及精度损失问题。以下示例演示了潜在的精度损失。
create table demo_table (
id number constraint pk_demo_table primary key,
value number not null
);
insert into demo_table(
id,
value
) values
(1, 0.1),
(2, 0.2);
create or replace procedure precision_loss(
"id1" demo_table.id%type,
"id2" demo_table.id%type)
as mle language javascript
;
/
begin precision_loss(1, 2); end;
/
precision_loss()
过程在控制台中打印的值为 0.30000000000000004
(而不是 0.3
)。为了避免精度损失,您可以遵循 从 MLE JavaScript SQL 驱动程序调用 PL/SQL 和 SQL 中列出的许多策略。一种方法是指示 SQL 驱动程序将数字作为 OracleNumber
类型返回:这是 Oracle 在 JavaScript 中 Number 数据类型的包装器类型
create or replace procedure precision_preserved(
"id1" demo_table.id%type,
"id2" demo_table.id%type)
as mle language javascript
;
/
begin precision_preserved(1, 2); end;
/
precision_preserved()
过程现在在控制台中打印正确的结果 (0.3
)。包装器数据类型在 mle-js-plsqltypes
模块中有所记载。每当您需要数字精度时,请考虑使用其中一种包装器数据类型。
动态 JavaScript 执行
Oracle Database 21c 以 DBMS_MLE
包的形式引入了 MLE。它仍然可用,但其重点已转向框架开发人员和嵌入到 REPL(读取-评估-打印循环)系统中。APEX、Database Actions 及其他工具在底层使用该包。
以下是使用 DBMS_MLE
编码的基本示例
set serveroutput on;
declare
l_ctx dbms_mle.context_handle_t;
l_source_code clob;
begin
-- Create execution context for MLE execution
l_ctx := dbms_mle.create_context();
-- using q-quotes to avoid problems with unwanted string termination
l_source_code :=
q'~
const result = session.execute(
`select 'hello, world'`,
[],
{
outFormat: oracledb.OUT_FORMAT_ARRAY
}
);
const message = result.rows[0][0];
console.log(message);
~';
dbms_mle.eval(
context_handle => l_ctx,
language_id => 'JAVASCRIPT',
source => l_source_code,
source_name => 'example01'
);
dbms_mle.drop_context(l_ctx);
exception
when others then
dbms_mle.drop_context(l_ctx);
raise;
end;
/
匿名 PL/SQL 块在控制台打印 hello, world
。如果您想使用 MLE 内置模块之外的其他模块,则必须使用 MLE 环境。基于先前创建的 validator_env
所奠定的基础,您可以如下使用 DBMS_MLE
验证电子邮件
set serveroutput on;
declare
l_ctx dbms_mle.context_handle_t;
l_source_code clob;
begin
-- Create execution context for MLE execution and provide an environment
l_ctx := dbms_mle.create_context('VALIDATOR_ENV');
-- using q-quotes to avoid problems with unwanted string termination
l_source_code :=
q'~
(async() => {
const { default: validator } = await import ('validator');
const str = 'not an email address';
console.log(`Is ${str} a valid email address? ${validator.isEmail(str)}`);
})()
~';
dbms_mle.eval(
context_handle => l_ctx,
language_id => 'JAVASCRIPT',
source => l_source_code,
source_name => 'example02'
);
dbms_mle.drop_context(l_ctx);
exception
when others then
dbms_mle.drop_context(l_ctx);
raise;
end;
/
您不能使用静态 import
语句,因为 DBMS_MLE 在脚本模式下评估 JavaScript 代码。但是,您可以使用如上述片段所示的动态导入。
APEX 和 Database Actions 是很棒的工具,因为它们允许您专注于编写 JavaScript(在上述示例中为
l_source_code
)。
相关文档
- Oracle® 数据库 JavaScript 开发人员指南.
- Martin Bach 关于在 Oracle 数据库中使用 JavaScript 的博客文章。
- 使用 MLE 模块.