A Deep Dive into PL/v8

Back in August, Compose.io announced the addition of JavaScript as an internal language for all new PostgreSQL deployments. This was thanks to the PL/v8 project, which straps Google‘s rocket of a JavaScript engine (V8) to PostgreSQL. This got me thinking - PL/v8 offers a rich and featureful way to write functions in PostgreSQL. Let‘s take a look at what it offers by building a mini JavaScript module system on top of it, complete with basic support for the CommonJS API.

Enabling PL/v8 on Your Deployment

First thing‘s first: you‘ll need to enable it by creating the extension. The quickest and easiest way to do this is likely using psql from a terminal (below), but if you prefer using another tool it shouldn‘t pose any problems.

# You can find your login details over on your deployment‘s overview page
psql "sslmode=require host=[host] port=[port] dbname=[dbname] user=[username]"
-- Once you‘ve logged in, execute the statement below to enable plv8
create extension plv8;

PL/v8 also supports two extra JavaScript "dialects" - CoffeeScript, and LiveScript. CoffeeScript offers a more Ruby-esque syntax, and LiveScript is a more functional successor to CoffeeScript. We‘ll be using pure JavaScript in this article, but if you‘d like to use either of these languages you‘ll need to create their extensions below as well.

-- If you prefer a different JS dialect, both CoffeeScript and LiveScript
-- are supported. Create their extensions to use them!
create extension plcoffee;
create extension plls;

JavaScript in PostgreSQL: The Basics

Creating a function using PL/v8 looks like any other PostgreSQL function, with the exception of a language specifier change. Take the (basic) example below: we‘re simply incrementing each int in an Array by 2, and returning it as pure JSON.

create or replace function addtwo(vals int[])
returns json as $$
    return vals.map(function(i) {
        return i + 2;
    });
$$ language plv8;

select addtwo(array[0, 47, 30]);
-- Returns [2, 49, 32]

JavaScript isn‘t technically a functional programming language, but it has many elements of one. Those features shine here - mapping over results is incredibly expressive and easy to work with. While you can‘t get all crazy with any ES6 code yet (the version of V8 currently used is a bit older), pretty much all the good parts of ES5 are up for grabs. Couple that with the native JSON support PostgreSQL offers, and you can get many of the features (e.g, Schema-less storage) from Document-oriented databases like MongoDB or RethinkDB.

As far as working with SQL data in JavaScript goes, it‘s relatively straightforward. PL/v8 will convert most database types automatically for you, including polymorphic types (anyelementanyarrayanyenum and anynonarray) and bytea (through JavaScript‘s Typed Arrays). The only real "gotchas" to be aware of are that any Array you return must be flattened (unless you‘re returning JSON, in which case go for it), and you can‘t create your own Typed Arrays for use in functions; you can, however, modify and return the ones passed to your function.

While not strictly a "gotcha", the ever-so-fun issue of context in JavaScript is present in PL/v8. Each SQL function is called with a different_this_ value, and it can be confusing at first. SQL functions do share context though, as far as accessing globally declared variables and functions. As long as you pay attention to scoping issues, you can avoid context binding issues and write reusable code.

Hacking Together a Module System

V8 is synonymous with Node.js for many developers, and inevitably the question of importing modules comes up. There is no baked-in module system, but we can simulate one using some of the features of PL/v8. It‘s important to note that while this works, we‘re in a sandboxed environment - modules involving network calls or browser-related functionality won‘t work. We‘ll be simulating the CommonJS module.exports API though, so many modules should "just work" right off npm.

The first thing we‘ll need is a table to store our module source(s) in. We really only need two columns to start: the module name (module), and the source code (source). To sweeten the deal we‘ll add an autoload column (autoload) that we‘ll use to dictate whether a module should be transparently available at all times.

create table plv8_js_modules (
    module text unique primary key,
    autoload bool default true,
    source text
);

We‘ll need a function to handle wrapping the require() API, and ideally we‘ll want a cache for module loading so we‘re not pulling from a database table every time we require a module. The global plv8 object has a few things we‘ll make use of here - it brings important functionality like executing statements, subtransactions, logging and more to the table. We‘ll be eval()ing the source for each module, but we wrap it in a function to ensure nothing leaks into the global scope. Our autoloading of certain modules also takes place in this function, just to prime the module cache for later use.

create or replace function plv8_require()
returns void as $$
    moduleCache = {};

    load = function(key, source) {
        var module = {exports: {}};
        eval("(function(module, exports) {" + source + "; })")(module, module.exports);

        // store in cache
        moduleCache[key] = module.exports;
        return module.exports;
    };

    require = function(module) {
        if(moduleCache[module])
            return moduleCache[module];

        var rows = plv8.execute(
            "select source from plv8_js_modules where module = $1",
            [module]
        );

        if(rows.length === 0) {
            plv8.elog(NOTICE, ‘Could not load module: ‘ + module);
            return null;
        }

        return load(module, rows[0].source);
    };

    // Grab modules worth auto-loading at context start and let them cache
    var query = ‘select module, source from plv8_js_modules where autoload = true‘;
    plv8.execute(query).forEach(function(row) {
        load(row.module, row.source);
    });
$$ language plv8;

Now in terms of using this, we have that dangling context problem to consider - how do we make sure that require() is available to each PL/v8 function that needs it? Well, it just so happens that PL/v8 supports setting a specific function to run before any other functions run. We can use this hook to bootstrap our environment - while ordinarily you could set it in your config files, you don‘t have access to those on Compose. We can, however, SET this value every time we open a connection. As long as you do this prior to any function call (including CREATE FUNCTION itself) you‘ll have access to require().

-- PL/v8 supports a "start proc" variable that can act as a bootstrap
-- function. Note the lack of quotes!
SET plv8.start_proc = plv8_require;

Let‘s try it out by throwing together a module that implements the Fisher-Yates shuffle algorithm - we‘ll name the module "shuffle", to keep things simple, and go ahead and set it to autoload.

insert into plv8_js_modules (module, autoload, source) values (‘shuffle‘, true, ‘
    module.exports = function(arr) {
        var length = arr.length,
            shuffled = Array(length);

        for(var i = 0, rand; i < length; i++) {
            rand = Math.floor(Math.random() * (i + 1));
            if(rand !== i)
                shuffled[i] = shuffled[rand];
            shuffled[rand] = arr[i];
        }

        return shuffled;
    };
‘);

Now we should be able to require() this! We can try it immediately - a simple table of people and a super readable random_person() function works well.

create table people (
    id serial,
    name varchar(255),
    age int
);

insert into people (name, age) values
    (‘Ryan‘, 27),
    (‘Daniel‘, 25),
    (‘Andrew‘, 23),
    (‘Sam‘, 22);

create or replace function random_person()
returns json as $$
    var shuffle = require(‘shuffle‘),
        people = plv8.execute(‘select id, name, age from people‘);

    return shuffle(people);
$$ language plv8;

select random_person();

-- Example Response:
-- [{
--     "id": 3,
--     "name": "Andrew",
--     "age": 23
-- }, {
--     "id": 1,
--     "name": "Ryan",
--     "age":27
-- }, {
--     "id": 4,
--     "name": "Sam",
--     "age": 22
-- }, {
--     "id": 2,
--     "name": "Daniel",
--    "age": 25
-- }]

See how clean that becomes? A shuffle algorithm is only one application - modules like lodash are a prime candidate to check out for using here.

What Are You Waiting For?

Writing PostgreSQL functions in JavaScript can provide a few wins - if your stack is primarily JavaScript, there‘s less context-switching involved when dealing with your database. You can reap the benefits of Document-oriented, Schema-less databases while leaving yourself the option to get relational as necessary, and the native support for JSON marries perfectly with JavaScript. Modules can tie it all together and provide a method for organizing code that makes sense - you‘ll feel right at home.

Even more in-depth documentation on PL/v8 can be found over on the official docs. Try it out today!

原文地址:https://www.cnblogs.com/rongfengliang/p/11827849.html

时间: 2024-10-25 01:54:36

A Deep Dive into PL/v8的相关文章

X64 Deep Dive

zhuan http://www.codemachine.com/article_x64deepdive.html X64 Deep Dive This tutorial discusses some of the key aspects of code execution on the X64 CPU like compiler optimizations, exception handling, parameter passing and parameter retrieval and sh

A Deep Dive into Recurrent Neural Nets

A Deep Dive into Recurrent Neural Nets Last time, we talked about the traditional feed-forward neural net and concepts that form the basis of deep learning. These ideas are extremely powerful! We saw how feed-forward convolutional neural networks hav

Deep Dive 3 - NIO

我们来继续80后Deep Dive 3 - NIO. Java NIO(New IO)是Java 1.4中引入的,时间的话已经是2002年了,确实久远.NIO的全称是New IO,作者偷懒就直接称之为NIO, 反而听起来酷酷的. 1. NIO 之父 老样子,我们先看看NIO的作者,NIO之父Mark Reinhold. Mark大叔毕业于MIT Ph.D.,老SUN员工了.大叔1996年加入Sun,一待就是13年,全心开发Java,后来Oracle于2010年收购SUN,被迫变成Oracle员工

VXLAN Deep Dive

http://www.definethecloud.net/vxlan-deep-dive/ I've been spending my free time digging into network virtualization and network overlays.  This is part 1 of a 2 part series, part 2 can be found here: http://www.definethecloud.net/vxlan-deep-divepart-2

《Docker Deep Dive》Note - Docker 引擎

<Docker Deep Dive>Note Docker 引擎 1. 概览 graph TB A(Docker client) --- B(daemon) subgraph Docker 引擎 B --- C(containerd) C --- D(runc) end Docker 引擎是用来运行和管理容器的核心软件. 主要构成:Docker Client.Docker daemon(Docker守护进程).containerd.runc. 2. 详解 graph TB A(Docker c

《Docker Deep Dive》Note - 纵观 Docker

<Docker Deep Dive>Note 由于GFW的隔离,国内拉取镜像会报TLS handshake timeout的错误:需要配置 registry-mirrors 为国内源解决这个问题. 可以配置为阿里的加速源:https://cr.console.aliyun.com/undefined/instances/mirrors,阿里的加速器可以提升获取Docker官方镜像的速度. 登录开发者账号后,将自己的加速器地址复制到 Docker Settings > Daemon >

SQL optimizer -Query Optimizer Deep Dive

refer: http://sqlblog.com/blogs/paul_white/archive/2012/04/28/query-optimizer-deep-dive-part-1.aspx    SQL是一种结构化查询语言规范,它从逻辑是哪个描述了用户需要的结果,而SQL服务器将这个逻辑需求描述转成能执行的物理执行计划,从而把结果返回给用户.将逻辑需求转换成一个更有效的物理执行计划的过程,就是优化的过程. 执行SQL的过程: Input Tree We start by looking

DM9000驱动移植在mini2440(linux2.6.29)和FS4412(linux3.14.78)上的实现(deep dive)篇一

关于dm9000的驱动移植分为两篇,第一篇在mini2440上实现,基于linux2.6.29,也成功在在6410上移植了一遍,和2440非常类似,第二篇在fs4412(Cortex A9)上实现,基于linux3.14.78,用设备树匹配,移植过程中调试和整体理解很重要,一路上幸有良师益友指点,下面详细介绍: 1.物理时序分析相关 DM9000芯片是DAVICOM公司生产的一款以太网处理芯片,提供一个通用的处理器接口.一个10/100M自适应的PHY芯片和4K双字的SRAM.内部框架如下,涉及

SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)

由于工作的原因,对SPI的理解最为深刻,也和SPI最有感情了,之前工作都是基于OSEK操作系统上进行实现,也在US/OS3上实现过SPI驱动的实现和测试,但是都是基于基本的寄存器操作,没有一个系统软件架构的思想,感觉linux SPI驱动很强大,水很深,废话少说,SPI总线上有两类设备:一类是主机端,通常作为SOC系统的一个子模块出现,比如很多嵌入式MPU中都常常包含SPI模块.一类是从机被控端,例如一些SPI接口的Flash.传感器等等.主机端是SPI总线的控制者,通过使用SPI协议主动发起S