使用JavaScript和Python实现Oracle数据库的存储过程?

本文研究了多语言引擎(MLE)如何在GraalVM的帮助下在Oracle数据库中使用JavaScript和Python,为数据密集型计算带来巨大的生态系统。使用GraalVM,我们不仅可以快速将新语言引入Oracle数据库,而且还可以轻松获得高性能的推测JIT编译器。它可用于为查询的关键部分生成有效代码,例如运行时的数据转换。

在数据库方面,SQL已经被证明是过去几十年中查询数据的首选语言,实现更复杂的业务逻辑,就会达到SQL的限制,这时存储过程出台了,但是人才匮乏,找到JavaScript或Python开发人员要比找到PL / SQL开发人员容易得多。

将新的编程语言与数据库系统集成包括嵌入一个全新的运行时,具有自己的内存管理,线程机制等。这显着增加了数据库系统的体系结构和代码库的复杂性。具有多语言功能的GraalVM及其对嵌入的支持为此问题提供了解决方案。只需要嵌入一个运行包,即可在数据库系统中实现多种编程语言的高性能实现。

进群:960410445  即可获取数十套PDF!

多语言引擎

在Oracle,我们目前正致力于将GraalVM嵌入到Oracle数据库和MySQL中,我们将这些扩展称为多语言引擎(MLE)。在本文中,我们仅关注MLE for Oracle数据库,Oracle Database MLE目前是一项实验性功能。

除了将GraalVM嵌入到Oracle数据库之外,我们还开发了尽可能方便地使用MLE的工具。例如,我们目前正在开发自动打包整个应用程序并使用单个命令将其部署到Oracle数据库的工具。

另一个主要工作领域是我们在Oracle数据库中提供的语言。语言需要扩展才能变得有用。例如,我们需要一个可以在数据库类型和语言类型之间进行转换的转换引擎,以及Oracle数据库的SQL引擎和新语言的SQL API之间的桥梁。

MLE提供了两种不同的方式来执行由MLE支持的语言编写的代码。首先,存储过程和用户​​定义的函数可以用MLE语言编写。其次,提供了一个名为DBMS_MLE的新PL / SQL包,用于动态脚本,即在运行时定义匿名脚本并执行它们。

使用动态MLE临时执行脚本

DBMS_MLE可以执行在PL / SQL中以字符串形式给出的脚本。数据通过所谓的绑定变量在两个方向(输入和输出)与脚本交换。最后,脚本可以打印将放入数据库 输出缓冲区 的消息。我们来看一个具体的例子:

DECLARE
 script_source CLOB;
 script_handle DBMS_MLE.SCRIPT_HANDLE_T;
 script_result CLOB;
BEGIN
 script_source := q'~
 var hist = {"b2000": "","b4000": "","b6000": ""};
 // mle.sql is an obect that gives access to the SQL engine
 for (var row of mle.sql.execute("SELECT SAL FROM EMP").rows) {
 if (row[0] < 2000)
 hist.b2000 += "█";
 else if (row[0] < 4000)
 hist.b4000 += "█";
 else if (row[0] < 6000)
 hist.b6000 += "█";
 }
 console.log(JSON.stringify(hist));
 // mle.binds is an object that holds all bind variables
 mle.binds.hello = mle.binds.hello.replace("PL/SQL","Graal JavaScript " + Graal.versionJS);
 ~';
 -- create a new MLE script for the JavaScript (JS) code above
 script_handle := DBMS_MLE.CREATE_SCRIPT('JS',script_source);
 -- assign "Hello from PL/SQL" to a bind variable named "hello"
 DBMS_MLE.BIND_VARIABLE(script_handle,'hello','Hello from PL/SQL');
 -- execute the dynamic MLE script
 DBMS_MLE.EXECUTE_SCRIPT(script_handle);
 -- store the value of "hello" after script execution in "script_result"
 DBMS_MLE.VARIABLE_VALUE(script_handle,script_result);
 -- remove the 
 DBMS_MLE.DROP_SCRIPT(script_handle);
 DBMS_OUTPUT.PUT_LINE(script_result);
END;

示例中的匿名PL / SQL块使用变量script_source来保存JavaScript代码片段。此变量传递给函数DBMS_MLE.CREATE_SCRIPT()以创建新的Dynamic MLE脚本,然后可以通过函数DBMS_MLE.EXECUTE_SCRIPT()执行该脚本。

在我们执行脚本之前,我们通过DBMS_MLE.BIND_VARIABLE()定义并设置一个名为hello的绑定变量。可以在周围的PL / SQL程序或Dynamic MLE脚本中定义,设置和读取绑定变量。

该脚本使用内置的MLE SQL驱动程序(自动作为mle.sql提供)来查询EMP中所有员工的工资表。出于演示目的,我们为工资创建一个简单的直方图,并将其放入输出缓冲区(console.log())。在将控制权转移回PL / SQL之前,脚本操纵绑定变量hello。然后,PL / SQL块使用DBMS_MLE.VARIABLE_VALUE()函数提取绑定变量的值,并将其打印到输出缓冲区。要执行PL / SQL块,我们可以将它作为单个语句从任何客户端发送到数据库。例如,可以将整个块复制到SQL * Plus会话中,并通过输入斜杠字符来执行。

在执行上面的匿名PL / SQL块之后(例如,在 SQL * Plus中 ),数据库输出缓冲区将具有以下内容(在SQL * Plus中显示SET SERVEROUTPUT ON或使用DBMS_OUTPUT.GET_LINE()来检索):

{"b2000":"████████","b4000":"█████","b6000":"█"}
Hello from Graal JavaScript 1.0

当然,我们可以轻松地使用Python来完成同样的事情:

DECLARE
 script_source CLOB;
 script_handle DBMS_MLE.SCRIPT_HANDLE_T;
 script_result CLOB;
BEGIN
 script_source := q'~
hist = { 'b2000': '','b4000': '','b6000': '' }
# mle.sql is an obect that gives access to the SQL engine
for row in mle.sql.execute('SELECT SAL FROM EMP'):
 if row[0] < 2000:
 hist['b2000'] += '█'
 elif row[0] < 4000:
 hist['b4000'] += '█'
 elif row[0] < 6000:
 hist['b6000'] += '█'
print(hist);
import sys
# mle.binds is a dictionary that holds all bind variables
mle.binds['hello'] = mle.binds['hello'].replace('PL/SQL','Python ' + sys.version);
 ~';
 -- create a new MLE script for the Python code above
 script_handle := DBMS_MLE.CREATE_SCRIPT('PYTHON',script_source);
 -- assign "Hello from PL/SQL" to a bind variable named "hello"
 DBMS_MLE.BIND_VARIABLE(script_handle,script_result);
 -- remove the 
 DBMS_MLE.DROP_SCRIPT(script_handle);
 DBMS_OUTPUT.PUT_LINE(script_result);
END;

跨语言的组件共享​​​​​​​

在我们的第一个示例中,我们介绍了MLE SQL驱动程序,并展示了如何在JavaScript和Python中使用它。它看起来像是用过的语言实现的模块,但事实并非如此。我们不是为我们添加的每种语言实现从语言的SQL API到Oracle数据库的SQL引擎的完整桥接,而是由于GraalVM的多语言功能,我们必须只完成一次工作的主要部分。

简而言之,多语言功能允许在GraalVM上运行的语言访问对象并调用属于另一种语言的函数。因此,我们将所有语言(如数据转换和MLE SQL驱动程序)所需的基本组件实现为可以直接从所有其他语言使用的新内部语言。为了实现新语言,GraalVM提供了 Truffle框架 我们用于此目的。我们在每个MLE语言之上添加了一个特定于语言的瘦层,以隐藏一些内部结构并使它们看起来非常原生。

Truffle框架不仅可以实现可共享组件,还可以充分利用GraalVM的推测JIT编译器。在数据库的背景下,后者具有极其重要的意义,因为数据转换通常是主要的成本因素。

MLE存储过程

虽然在许多情况下运行以现代语言编写的脚本很方便,但它并不适合开发大型和复杂的应用程序。动态MLE需要PL / SQL中的骨架,并且不能直接使用第三方库。此外,代码 最好由数据库管理, 类似于数据。为了释放MLE的全部功能,我们允许以由用户定义的函数和存储过程组成的模块的形式持久地在数据库中存储和维护用户代码。对于无痛包装和模块部署,我们计划提供外部工具,通过单个命令完成所有工作。

存储过程允许开发人员运行需要在数据库服务器进程内执行多个SQL语句的代码。这避免了数据库客户端(通常是中间件)与数据库之间昂贵的网络往返。今天,Oracle数据库允许开发人员在PL / SQL或Java中实现存储过程。使用MLE,开发人员还可以使用JavaScript和Python实现存储过程。

假设我们想提高员工的工资,但禁止非经理人的薪水超过10,000美元。我们可以从一个JavaScript函数开始,该函数更新员工的薪水并返回新薪水:

const sql = _dbRequire('@oracle/sql');
function updateGetSal(empno,raise) {
 if (sql.execute('UPDATE emp SET sal = sal + :1 WHERE empno = :2',[raise,empno]).rowsAffected != 0) {
 const row = sql.execute('SELECT sal FROM emp WHERE empno = :1',[empno]).firstRow();
 return row[0];
 }
 return false;
}

请注意,为了提高安全性和性能,我们在SQL语句中使用 绑定变量 。在这种特殊情况下,我们通过给出一个值数组来设置绑定变量的值。这意味着数组[raise,empno]中的值的位置确定它替换的绑定变量(即,第一个绑定变量将设置为raise的值,第二个绑定变量将设置为empno的值) 。或者,可以 按名称设置 绑定变量。

接下来,我们可以定义一个函数来检查员工是否是经理:

function isManager(empno) {
 const row = sql.execute('SELECT count(*) FROM emp WHERE mgr = :1',[empno]).firstRow();
 return row[0] > 0;
}

有了这两个辅助函数,我们现在可以实现我们的业务逻辑:

module.exports.salraise = function(empno,raise) {
const newsal = updateGetSal(empno,raise);
if (newsal && newsal > 10000 && !isManager(empno)) {
sql.execute('ROLLBACK');
} else {
sql.execute('COMMIT');
}
};

对module.exports的赋值用于将函数salraise()导出到数据库。将所有内容放在名为load_salraise.js的文件中,我们可以添加其他代码来执行部署到数据库中:

const async = require('async');
const oracledb = require('oracledb');
// the name of the module being created in the database
const moduleName = 'raise.js';
// database user
const dbUser = 'scott';
// database user password
const dbPwd = 'tiger';
// database connection string
const dbInstance = 'hr.us.example.com/hr.rdbms.dev.us.oracle.com';
oracledb.autoCommit = true;
const doConnect = function(cb) {
 oracledb.getConnection(
 {
 user: dbUser,password: dbPwd,connectString: dbInstance,},cb);
};
const doCleanup = function(conn) {
 conn.close(function(err) {
 if (err) {
 console.error(err.message);
 }
 });
};
const source = `
const sql = _dbRequire('@oracle/sql');
function updateGetSal(empno,[empno]).firstRow();
 return row[0];
 }
 return false;
}
function isManager(empno) {
 const row = sql.execute('SELECT count(*) FROM emp WHERE mgr = :1',[empno]).firstRow();
 return row[0] > 0;
}
module.exports.salraise = function(empno,raise) {
 const newsal = updateGetSal(empno,raise);
 if (newsal && newsal > 10000 && !isManager(empno)) {
 sql.execute('ROLLBACK');
 } else {
 sql.execute('COMMIT');
 }
};
`;
const createProcedure = function(conn,cb) {
 conn.execute(
 'CREATE OR REPLACE JAVASCRIPT SOURCE NAMED "' + moduleName + '" AS ' +
 source,function(err,result) {
 if (err) {
 return cb(err,conn);
 }
 conn.execute(
 'CREATE OR REPLACE PROCEDURE raiseSal (empno NUMBER,raise ' +
 'NUMBER) AS LANGUAGE JAVASCRIPT NAME ' +
 ''raise.js.salraise(empno number,raise number)';',conn);
 }
 return cb(null,conn);
 });
 }
 );
};
async.waterfall(
 [
 doConnect,createProcedure,doCleanup,],conn) {
 if (err) {
 console.error('In waterfall error cb: ==>',err,'<==');
 }
 if (conn) {
 doCleanup(conn);
 }
 });

我们现在可以运行部署模块代码的脚本,并通过Node.js 将函数salraise()注册为存储过程:

$ npm install oracledb async
$ node load_salraise.js

可以像调用任何其他过程一样调用新创建的JavaScript存储过程。例如,从SQL * Plus:

$ sqlplus scott/tiger
SQL> SELECT sal FROM emp WHERE empno = 7369;
 SAL
----------
 800
SQL> CALL salraise(7369,200);
Call completed.
SQL> SELECT sal FROM emp WHERE empno = 7369;
 SAL
----------
 1000

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Python中的函数(二) 在上一篇文章中提到了Python中函数的定义和使用,在这篇文章里我们来讨论下关于函数的一些更深的话题。在学习C语言函数的时候,遇到的问题主要有形参实参的区别、参数的传递和改变、变量的作用域。同样在Python中,关于对函数的理解和使用也存在这些问题。下面来逐一讲解。一.函
Python中的字符串 可能大多数人在学习C语言的时候,最先接触的数据类型就是字符串,因为大多教程都是以&quot;Hello world&quot;这个程序作为入门程序,这个程序中要打印的&quot;Hello world&quot;就是字符串。如果你做过自然语言处理方面的研究,并且用Python
Python 面向对象编程(一) 虽然Python是解释性语言,但是它是面向对象的,能够进行对象编程。下面就来了解一下如何在Python中进行对象编程。一.如何定义一个类 在进行python面向对象编程之前,先来了解几个术语:类,类对象,实例对象,属性,函数和方法。 类是对现实世界中一些事物的封装,
Python面向对象编程(二) 在前面一篇文章中谈到了类的基本定义和使用方法,这只体现了面向对象编程的三大特点之一:封装。下面就来了解一下另外两大特征:继承和多态。 在Python中,如果需要的话,可以让一个类去继承一个类,被继承的类称为父类或者超类、也可以称作基类,继承的类称为子类。并且Pytho
Python中的函数(一) 接触过C语言的朋友对函数这个词肯定非常熟悉,无论在哪门编程语言当中,函数(当然在某些语言里称作方法,意义是相同的)都扮演着至关重要的角色。今天就来了解一下Python中的函数用法。一.函数的定义 在某些编程语言当中,函数声明和函数定义是区分开的(在这些编程语言当中函数声明
在windows下如何快速搭建web.py开发框架 用Python进行web开发的话有很多框架供选择,比如最出名的Django,tornado等,除了这些框架之外,有一个轻量级的框架使用起来也是非常方便和顺手,就是web.py。它由一名黑客所创建,但是不幸的是这位创建者于2013年自杀了。据说现在由
将Sublime Text 2搭建成一个好用的IDE 说起编辑器,可能大部分人要推荐的是Vim和Emacs,本人用过Vim,功能确实强大,但是不是很习惯,之前一直有朋友推荐SUblime Text 2这款编辑器,然后这段时间就试了一下,就深深地喜欢上这款编辑器了...
Python中的模块 有过C语言编程经验的朋友都知道在C语言中如果要引用sqrt这个函数,必须用语句&quot;#include&lt;math.h&gt;&quot;引入math.h这个头文件,否则是无法正常进行调用的。那么在Python中,如果要引用一些内置的函数,该怎么处理呢?在Python中
Python的基础语法 在对Python有了基础的认识之后,下面来了解一下Python的基础语法,看看它和C语言、java之间的基础语法差异。一.变量、表达式和语句 Python中的语句也称作命令,比如print &quot;hello python&quot;这就是一条语句。 表达式,顾名思义,是
Eclipse+PyDevʽjango+Mysql搭建Python web开发环境 Python的web框架有很多,目前主流的有Django、Tornado、Web.py等,最流行的要属Django了,也是被大家最看好的框架之一。下面就来讲讲如何搭建Django的开发环境。一.准备工作 需要下载的
在windows下安装配置Ulipad 今天推荐一款轻便的文本编辑器Ulipad,用来写一些小的Python脚本非常方便。 Ulipad下载地址: https://github.com/limodou/ulipad http://files.cnblogs.com/dolphin0520/u...
Python中的函数(三) 在前面两篇文章中已经探讨了函数的一些相关用法,下面一起来了解一下函数参数类型的问题。在C语言中,调用函数时必须依照函数定义时的参数个数以及类型来传递参数,否则将会发生错误,这个是严格进行规定的。然而在Python中函数参数定义和传递的方式相比而言就灵活多了。一.函数参数的
在Notepad++中搭配Python开发环境 Python在最近几年一度成为最流行的语言之一,不仅仅是因为它简洁明了,更在于它的功能之强大。它不仅能够完成一般脚本语言所能做的事情,还能很方便快捷地进行大规模的项目开发。在学习Python之前我们来看一下Python的历史由来,&quot;Pytho
Python中的条件选择和循环语句 同C语言、Java一样,Python中也存在条件选择和循环语句,其风格和C语言、java的很类似,但是在写法和用法上还是有一些区别。今天就让我们一起来了解一下。一.条件选择语句 Python中条件选择语句的关键字为:if 、elif 、else这三个。其基本形式如
关于raw_input( )和sys.stdin.readline( )的区别 之前一直认为用raw_input( )和sys.stdin.readline( )来获取输入的效果完全相同,但是最近在写程序时有类似这样一段代码:import sysline = sys.stdin.readline()
初识Python 跟学习所有的编程语言一样,首先得了解这门语言的编程风格和最基础的语法。下面就让我们一起来了解一下Python的编程风格。1.逻辑行与物理行 在Python中有逻辑行和物理行这个概念,物理行是指在编辑器中实际看到的一行,逻辑行是指一条Python语句。在Python中提倡一个物理行只
当我们的代码是有访问网络相关的操作时,比如http请求或者访问远程数据库,经常可能会发生一些错误,有些错误可能重新去发送请求就会成功,本文分析常见可能需要重试的场景,并最后给出python代码实现。
1.经典迭代器 2.将Sentence中的__iter__改成生成器函数 改成生成器后用法不变,但更加简洁。 3.惰性实现 当列表比较大,占内存较大时,我们可以采用惰性实现,每次只读取一个元素到内存。 或者使用更简洁的生成器表达式 4.yield from itertools模块含有大量生成器函数可
本文介绍简单介绍socket的常用函数,并以python-kafka中的源码socketpair为例,来讲解python socket的运用
python实践中经常出现编码相关的异常,大多网上找资料而没有理解原理,导致一次次重复错误。本文对常用Unicode、UTF-8、GB2312编码的原理进行介绍,接着介绍了python字符类型unicode和str以及常见编解码错误UnicodeEncodeError和UnicodeDEcodeEr