前言
在前幾年偶然間找到過一篇文章
使用JavaScript和Python實現Oracle數據庫的存儲過程?
當時這篇文章中就是用的dbms_mle包,但是oracle中當時實際上并沒有這個包,這篇文章應該是相關開發者在開發這個包時的測試記錄,可以看到這篇文章中,可以在plsql中使用python或者javascirpt的語法來編寫過程。其原理是使用了GraalVM (https://www.graalvm.org)
GraalVM是個相當有意思的項目,它自稱全棧虛擬機,可以讓你跨平臺使用多種開發語言,下面這張圖,框框里面的就是它能跑的開發語言,框框外的就是運行它的環境,按照這張圖描述的,你甚至可以在node.js里面跑python,在oracle里面跑ruby等等

這比我"AIO"的想法更瘋狂,我還只是想讓ORACLE能支持更多內容,而GraalVM這個項目是朝著大一統的方向走。
目前僅支持javascirpt
然后oracle在21c版本,正式在數據庫中集成了GraalVM,對應plsql的api就是dbms_mle,在此之前,mle只是作為apex插件存在。
另外我還搜索到老版本dbms_mle和新版本的一個官方解答
https://community.oracle.com/tech/developers/discussion/4348385/oracle-mle-is-dead
的確之前dbms_mle作為測試版,在github上進行過開源,但是原頁面目前已經404了,現在只能使用21c版本的了。
然而在官方文檔中DBMS_MLE只有javascirpt的示例,沒有python。
而且拿早期那篇測試文章和官方示例比較,包內的函數名及過程名是有差異的,比如
- “SCRIPT” 都被替換成了"context"
- “BIND_VARIABLE"變成了"export_to_mle”
- “CREATE_SCRIPT"變成了"eval”
- “VARIABLE_VALUE"變成了"import_from_mle”
但程序的基本結構還是差不多的,于是我嘗試使用早期測試文中python的例子,然后替換這些名稱進行測試,結果提示
ORA-04101: unsupported language PYTHON
ORA-06512: at “SYS.DBMS_MLE”, line 385
ORA-06512: at line 26
說是不支持python,那么它除了javascirpt以外,還支持哪些開發語言呢?
在DBMS_MLE包中,有一個函數get_available_languages,
FUNCTION GET_AVAILABLE_LANGUAGES RETURN LANGUAGES_T IS
RESULT DBMS_MLE.LANGUAGES_T := DBMS_MLE.LANGUAGES_T();
BEGIN
ICD_MLEGAL;
RESULT.EXTEND();
RESULT(1) := 'JAVASCRIPT';
RETURN RESULT;
END;
其中“ICD_MLEGAL”是c里面的,plsql中看不了,但按照這個寫法,很難不讓人覺得,它目前的確就只支持JAVASCRIPT。
看樣子要使用oracle官方提供的,在plsql中運行python代碼的方式目前只有oml4py了。
【ORACLE】在ORACLE數據庫中啟用機器學習功能(OML)以支持PYTHON腳本的運行(研究中)
OML4PY:SQL for Embedded Python Execution
調用javascirpt的語法分析
既然支持不了python ,那就來看看要怎么寫代碼才能讓它執行JAVASCRIPT代碼。
(國內其實有Eygle大佬簡單介紹了這個包,但是并沒有對這個包進行分析。 http://www.sunline.cc/db/43023 )
來看看官方示例
set serveroutput on;
declare
ctx dbms_mle.context_handle_t;
source clob;
greeting varchar2(100);
begin
ctx := dbms_mle.create_context(); -- Create execution context for MLE execution
dbms_mle.export_to_mle(ctx, 'person', 'World'); -- Export value from PL/SQL
source := q'~
var bindings = require("mle-js-bindings");
var person = bindings.importValue("person"); // Import value previously exported from PL/SQL
var greeting = "Hello, " + person + "!";
bindings.exportValue("greeting", greeting); // Export value to PL/SQL
~';
dbms_mle.eval(ctx, 'JAVASCRIPT', source); -- Evaluate the source code snippet in the execution context
dbms_mle.import_from_mle(ctx, 'greeting', greeting); -- Import value previously exported from MLE
dbms_output.put_line('Greetings from MLE: ' || greeting);
dbms_mle.drop_context(ctx); -- Drop the execution context once no longer required
end;
/
1.create_context() 生成一個context_handle_t(RAW(16))
2.export_to_mle,將參數傳到mle里面,此例為,把參數’person’的值賦成’World’
3.定義js代碼的字符串3.1 js代碼需要"mle-js-bindings"這個外部模塊來定義一個對象?先繼續往下看
3.2 變量person賦值,用importValue,這個就對應上面的export_to_mle
3.3 字符串"Hello, "拼接變量person
3.4 使用exportValue導出變量greeting
4.使用eval執行這段js腳本
5.使用import_from_mle,將參數greeting從mle中導入回plsql
6.打印greeting ,是 “Hello, World!”
7.最后刪掉最開始生成的context_handle_t
仔細看完才發現,這個功能不是把隨便寫點js代碼放進去就行的,就算js里面寫了return值,默認情況下也不會傳回到plsql。它實際上是在js環境中提前嵌入了一個"mle-js*"的庫,這個庫可以在運行時和oracle進行參數傳遞,
- plsql要傳入js,則plsql執行export_to_mle,js執行require(“mle-js-bindings”).importValue(“person”)
- js要傳回plsql,則js執行require(“mle-js-bindings”).exportValue,plsql執行import_from_mle
當然如果不進行參數傳遞,只進行純粹的js執行,倒也不用管這個,直接一個eval就好了.
也就是說,常規的js函數要能和plsql進行交互,就必須改造一下js代碼,使用mle的功能來進行參數的輸入及輸出。當然理論上也可以在外層進行引用,只是,如果要執行的代碼依賴多個js文件,這個又該怎么寫呢?
如何引用外部js模塊
(先補充一下,js中寫的"console.log();"也會出現在plsql的output池里,可以通過get_line獲取內容,這點和那篇早期測試文一致,這個功能其實可以方便我們調試js代碼的部分,但oracle官方的正式文檔中沒有提到。)
單一js代碼要實現很復雜的功能會是件麻煩事,一般的js項目都會創建多個js文件,這樣可以讓代碼更整潔,功能區分更明確,oracle官方的目的是,會js開發的程序員,也可以在plsql里寫代碼了,但是實際上這個功能更靠譜的場景應該是plsql程序員遇到了一個復雜功能的需求,剛好有外部開源的js程序,期望能直接在plsql里用上這個外部js程序。
突破點應該就在 require(“mle-js-bindings”) 這里,如果能在os上找到mle-js-bindings文件,那么理論上就能直接引用外部文件,

然而啥都沒有,那么如果直接寫絕對引用呢?
寫了一小段js,保存在了“/home/oracle/test.js”,然后在上面的js代碼中插入一行
var test = require("/home/oracle/test");
運行報錯
ORA-04103: module /home/oracle/test does not exist
ORA-06512: at “SYS.DBMS_MLE”, line 385
ORA-06512: at line 16
也就是說,它其實不是在os上運行js的,回到這個篇文章的起點,“GraalVM”,其實是個虛擬機,也就是說,它只能使用它內部已經有的東西。
那么,問題就變成了,怎么給這個虛擬機添加文件?
require(“path”),提示沒有這個module,用import也會報錯,把文件后綴改成"mjs"也不行。
還有很多js和node.js自帶的一些module也沒有。
翻閱了GraalVM的官方網站,所有有關dbms_mle的例子中,require的只有兩個,"mle-js-bindings"用于傳遞參數,"mle-js-oracledb"用于連接oracle數據庫執行sql,完全沒提到引用其他js,反倒是有說apex的mle功能更為強大和通用。
也難怪現在沒幾篇文章說這個東西。
https://mle-databaseraalvm/mle-executing-javascript-in-oracle-database-c545feb1a010 (有墻)
這篇文章算是說得最全的了,連架構圖都來了,


但是依舊沒說到怎么引用其他js文件,也沒說怎么接觸到GraalVM在ORACLE中的runtime。
畢竟21c是創新者版本,估計更多的功能要等以后完善了吧.
https://github.com/oracle/graaljs
完整版的GraalVM其本身是可以導入其他js模塊的,但是oracle版本的沒有說怎么弄。
但既然已經研究到了這里,不拿點實際的應用出來,有人會說這個玩意不實用了吧?我自己寫幾個例子試試。
寫點代碼試試
首先來個簡單的
declare
ctx dbms_mle.context_handle_t;
source clob;
r number;
begin
ctx := dbms_mle.create_context();
dbms_mle.export_to_mle(ctx, 'a', 5);
dbms_mle.export_to_mle(ctx, 'b', 6);
source := q'~
function myFunction(p1, p2) {
return p1 * p2;
}
var bindings = require("mle-js-bindings");
var a = bindings.importValue("a");
var b = bindings.importValue("b");
var r = myFunction(a, b);
bindings.exportValue("r", r);
~';
dbms_mle.eval(ctx, 'JAVASCRIPT', source);
dbms_mle.import_from_mle(ctx, 'r', r);
dbms_output.put_line('r: ' || r);
dbms_mle.drop_context(ctx);
end;
/
r: 30
/
這是個簡單的js函數,證實我前面的分析沒錯。這段可以改成plsql的函數,那么就可以在plsql中無感使用js了。
接下來再寫個更復雜的例子
在網上找到了一個生成二維碼的js
https://cdnjs.cloudflare.com/ajax/libs/qrcode-generator/1.4.4/qrcode.min.js
然后封裝成plsql的函數,效果如下

大概思路是用utl_http獲取這個js文件的內容,然后再在后面拼接上調用它的js代碼,執行這個拼接后的js,會返回一個base64字符串,然后再使用utl_encode轉回成二進制數據。
因為js是可以混淆、壓縮、打包的,所以,如果某個外部js模塊有很多個零散的js的文件,可以用一些工具比如webpack把它做成一個js文件,然后再用。如果數據庫不方便連外網,我們可以把這個js的內容保存到數據庫表的clob字段中,并給它命名,以方便使用不同js模塊的時候調用。
這個生成二維碼的plsql函數代碼我已經做成了單文件版,并已上傳到github
https://github.com/Dark-Athena/qrcode_plsql
總結
雖然DBMS_MLE功能目前不夠完善,但的確可以使用了,未來可期。
- 本文作者: DarkAthena
- 本文鏈接: https://www.darkathena.top/archives/about-dbmsmle
- 版權聲明: 本博客所有文章除特別聲明外,均采用CC BY-NC-SA 3.0 許可協議。轉載請注明出處!




