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!

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

  1. X64 Deep Dive

    zhuan http://www.codemachine.com/article_x64deepdive.html X64 Deep Dive This tutorial discusses some ...

  2. Deep Dive into Spark SQL’s Catalyst Optimizer(中英双语)

    文章标题 Deep Dive into Spark SQL’s Catalyst Optimizer 作者介绍 Michael Armbrust, Yin Huai, Cheng Liang, Rey ...

  3. Generating YouTube-like IDs in Postgres using PL/V8 and Hashids

    转自:https://blog.abevoelker.com/2017-01-03/generating-youtube-like-ids-in-postgres-using-plv8-and-has ...

  4. 《Docker Deep Dive》Note - Docker 引擎

    <Docker Deep Dive>Note Docker 引擎 1. 概览 graph TB A(Docker client) --- B(daemon) subgraph Docker ...

  5. 《Docker Deep Dive》Note - 纵观 Docker

    <Docker Deep Dive>Note 由于GFW的隔离,国内拉取镜像会报TLS handshake timeout的错误:需要配置 registry-mirrors 为国内源解决这 ...

  6. Deep Dive into Neo4j 3.5 Full Text Search

    In this blog we will go over the Full Text Search capabilities available in the latest major release ...

  7. 重磅解读:K8s Cluster Autoscaler模块及对应华为云插件Deep Dive

    摘要:本文将解密K8s Cluster Autoscaler模块的架构和代码的Deep Dive,及K8s Cluster Autoscaler 华为云插件. 背景信息 基于业务团队(Cloud BU ...

  8. vue3 deep dive

    vue3 deep dive vue core vnode vue core render / mount / patch refs https://www.vuemastery.com/course ...

  9. A Deep Dive Into Draggable and DragTarget in Flutter

    https://medium.com/flutter-community/a-deep-dive-into-draggable-and-dragtarget-in-flutter-487919f6f1 ...

随机推荐

  1. 如何用代码设置机器人初始坐标实现 2D Pose Estimate功能

    前言:ROS机器人有时候会遇到极端的情况:比如地面打滑严重,IMU精度差,导致积累误差严重,或是amcl匹配错误,导致机器人定位失败, 这时候如何矫正机器人位置变得非常重要,我的思路是利用相机或是在地 ...

  2. 《Interest Rate Risk Modeling》阅读笔记——第二章:债券价格、久期与凸性

    目录 第二章:债券价格.久期与凸性 思维导图 瞬时回报率-收益率的例子 第二章:债券价格.久期与凸性 思维导图 瞬时回报率-收益率的例子

  3. oracle批量新增更新数据

    本博客介绍一下Oracle批量新增数据和更新数据的sql写法,业务场景是这样的,往一张关联表里批量新增更新数据,然后,下面介绍一下批量新增和更新的写法: 批量新增数据 对于批量新增数据,介绍两种方法 ...

  4. SQL -------- TOP 查询前几行

    SELECT TOP 子句用于指定要返回的记录数量.并不是所有的数据库系统都支持SELECT TOP子句.MySQL支持LIMIT子句来选择有限数量的记录,而Oracle使用ROWNUM. top 后 ...

  5. day11——函数名的使用、f格式化、迭代器、递归

    day11 函数名的第一类对象及使用 1.可以当作值被赋值给变量 def func(): print(1) print(func) a = func a() 2.当作元素存放在容器中 def func ...

  6. Redis(九)高可用专栏之Sentinel模式

    本文讲述Redis高可用方案中的哨兵模式--Sentinel,RedisClient中的Jedis如何使用以及使用原理. Redis主从复制 Redis Sentinel模式 Jedis中的Senti ...

  7. Oracle的创建表空间及用户

    学习笔记: 1.创建表空间 --创建表空间 create tablespace thepathofgrace datafile 'c:\thepathofgrace.dbf' size 100m au ...

  8. layui提示框事件

    使用layui提示框的时候遇到个问题,点击“确定”“取消”之类的按钮会执行里面的方法,但点击×就不会执行,所以在添加数据的时候出现个BUG,就是保存数据后点击弹出提示框的×,继续保存,如此循环,就可以 ...

  9. c# 对XML进行数字签名并且让java验签成功

    实现: 1.c#将xml报文做数字签名发送到java服务,java服务成功验签. 2.c#服务对收到java服务推送的xml报文成功验签. 前提: 1.java服务要求 遇到问题: 1.Java和.n ...

  10. C# 常用工具方法之DataTable(一)

    1.DataTable 转 泛型T的List /// <summary> /// 数据集DataTable转换成List集合 /// </summary> /// <ty ...