├── .gitignore ├── .npmignore ├── .shipit.json ├── .travis.yml ├── History.md ├── LICENSE ├── Makefile ├── README.md ├── bin └── plv8x ├── package.json ├── package.ls ├── src ├── bootstrap.ls ├── cli.ls ├── index.ls └── sql.ls └── test ├── basics.ls ├── bundle-require.ls ├── inject-callback.ls ├── mocha.opts ├── operator.ls ├── poly.ls └── user-package ├── dummy1.js └── dummy2.js /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules/* 3 | *.swp 4 | *.swo 5 | *~ 6 | bin/*.js 7 | lib 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules/* 3 | *.swp 4 | *.swo 5 | *~ 6 | -------------------------------------------------------------------------------- /.shipit.json: -------------------------------------------------------------------------------- 1 | { 2 | "usePackageLS": true, 3 | "steps": ["FindVersion", "ChangeVersion", "CheckChangeLog", "Commit", "Tag", "Publish"], 4 | "CheckChangeLog": { 5 | "files": ["History.md"] 6 | }, 7 | "ChangeVersion": { 8 | "file": "package.ls" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - psql --version 3 | - sudo /etc/init.d/postgresql stop 4 | - sudo apt-get -y --purge remove postgresql libpq-dev libpq5 postgresql-client-common postgresql-common 5 | - sudo rm -rf /var/lib/postgresql 6 | - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 7 | - sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list" 8 | - sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg-testing main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list" 9 | - sudo apt-get update -qq 10 | - sudo apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::="--force-confnew" install postgresql-$PGVERSION postgresql-server-dev-$PGVERSION postgresql-$PGVERSION-plv8 11 | - sudo chmod 777 /etc/postgresql/$PGVERSION/main/pg_hba.conf 12 | - sudo echo "local all postgres trust" > /etc/postgresql/$PGVERSION/main/pg_hba.conf 13 | - sudo echo "local all all trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf 14 | - sudo echo "host all all 127.0.0.1/32 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf 15 | - sudo echo "host all all ::1/128 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf 16 | - sudo /etc/init.d/postgresql restart 17 | 18 | before_script: 19 | - createuser -U postgres -s travis 20 | - psql -c 'create database test' -U postgres 21 | 22 | script: 23 | - "TESTDBNAME=test npm run test" 24 | 25 | notifications: 26 | irc: "irc.freenode.org#pgrest" 27 | 28 | language: node_js 29 | 30 | env: 31 | matrix: 32 | - PGVERSION=9.4 33 | - PGVERSION=9.3 34 | - PGVERSION=9.2 35 | - PGVERSION=9.1 36 | - PGVERSION=9.0 37 | 38 | node_js: 39 | - 0.10 40 | 41 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 0.7.0 / 2014-02-18 2 | * Switch to browserify for bundling. 3 | 4 | 0.6.5 / 2014-02-03 5 | * Export `util`, which comes with the define-schema helper 6 | 7 | 0.6.4 / 2014-01-05 8 | * Fix plv8x not bootstrapped when client flag is specified 9 | 10 | 0.6.3 / 2013-12-18 11 | * Allow options in plv8x.new: 12 | * `client` flag to assume plv8x bootstrapped 13 | 14 | 0.6.2 / 2013-11-16 15 | ================== 16 | * Support array types for user-funcs 17 | 18 | 0.6.1 / 2013-11-06 19 | ================== 20 | * Use native pg 21 | 22 | 0.6.0 / 2013-10-28 23 | ================== 24 | * Support pg 9.3 25 | * polyfill row_to_json, array_to_json, to_json 26 | 27 | ================== 28 | * Fix object-style pg connection info 29 | 30 | 0.5.4 / 2013-08-06 31 | ================== 32 | * functions can now be marked to be imported in bootstrap stage 33 | 34 | 0.5.3 / 2013-07-25 35 | ================== 36 | * import-bundle-funcs 37 | 38 | 0.5.2 / 2013-07-10 39 | ================== 40 | * Restore pg 9.1 support 41 | * Update to use node-pg 2.1 42 | 43 | 0.5.1 / 2013-07-05 44 | ================== 45 | * add primary key to plv8x.code name 46 | * ignore pgrest and express when bundling 47 | 48 | 0.5.0 / 2013-06-22 49 | ================== 50 | * Update to use node-pg 2.0 51 | * Properly handle json return type, no more manual parse required 52 | 53 | 0.4.4 / 2013-06-21 54 | ================== 55 | * Fix -f with injected code that uses LiveScript primtives like $import 56 | * More reliable mk_func with existing functions being used in views 57 | * Allow specifying -f return type with a trailing colon 58 | * Fix -f functions without args 59 | 60 | 0.4.3 / 2013-05-08 61 | ================== 62 | * Restore support for PostgreSQL 9.0 63 | 64 | 0.4.2 / 2013-04-24 65 | ================== 66 | * Support injecting functions with callback callconv 67 | * Support injecting functions exported as root object 68 | 69 | 0.4.1 / 2013-04-24 70 | ================== 71 | * Fix importing module-name-with-dash (@selenamarie) 72 | 73 | 0.4.0 / 2013-04-21 74 | ================== 75 | * More helpful error message when --db is missing 76 | 77 | 0.3.4 / 2013-04-14 78 | ================== 79 | 80 | * Add -d alias for --db 81 | * Add -r and -e for eval in plv8x context 82 | * Add -c for executing queries 83 | * Add --json for json output 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | pgrest 2 | ====== 3 | The MIT License 4 | 5 | Copyright (c) 2012-2013 plv8x Project. http://github.com/clkao/plv8x 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | sequelize: 2 | npm i ../sequelize 3 | ln -sf ../../{util,events} node_modules/sequelize/node_modules/ 4 | onejs build node_modules/sequelize/package.json sequelize.js 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | plv8x 2 | ====== 3 | 4 | [![Build Status](https://travis-ci.org/clkao/plv8x.png?branch=master)](https://travis-ci.org/clkao/plv8x) 5 | 6 | plv8x helps you manage functions and packages in plv8, postgresql's javascript 7 | procedural language support. 8 | 9 | # Quick start with docker 10 | 11 | Using the docker-based postgresql with plv8js enabled: 12 | 13 | ``` 14 | % docker run -p 5433:5432 -d --name postgres clkao/postgres-plv8:9.4 15 | 16 | % createdb -U postgres -h 127.0.0.1 -p 5433 test 17 | % export PLV8XDB=postgres://postgres@127.0.0.1:5433/test 18 | 19 | % plv8x --list 20 | plv8x: 392.25 kB 21 | 22 | # import the qs package from npm 23 | % npm i qs; plv8x -i qs; plv8x --list 24 | plv8x: 392.25 kB 25 | qs: 9.37 kB 26 | 27 | # this is now evaluated inside postgresql 28 | % plv8x -e 'require("qs").parse("foo=bar&baz=1")' 29 | { foo: 'bar', baz: '1' } 30 | 31 | # .. which is actually equivalent to: 32 | % psql $PLV8DB -c 'select |> $$ require("qs").parse("foo=bar&baz=1") $$' 33 | ?column? 34 | ------------------------- 35 | {"foo":"bar","baz":"1"} 36 | (1 row) 37 | 38 | 39 | ``` 40 | 41 | 42 | # Install plv8js 43 | 44 | Note: Requires postgresql 9.0 or later. 45 | 46 | [postgresql PGDG apt respository](http://wiki.postgresql.org/wiki/Apt) now ships plv8js extension: 47 | 48 | ``` 49 | wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo apt-key add - 50 | sudo apt-get update 51 | sudo apt-get install postgresql-9.2-plv8 52 | ``` 53 | 54 | Or you can install with pgxnclient: 55 | 56 | 57 | ``` 58 | sudo easy_install pgxnclient 59 | sudo pgxn install plv8 60 | ``` 61 | 62 | # Install LiveScript (pre-requisite) 63 | 64 | % npm i -g LiveScript 65 | 66 | # Install plv8x 67 | 68 | % git clone git://github.com/clkao/plv8x.git; cd plv8x 69 | % npm i -g . 70 | 71 | # Quick start 72 | 73 | Enable plv8x for your database: 74 | 75 | % createdb test 76 | % plv8x -d test -l 77 | plv8x: 491425 bytes 78 | 79 | We support synonymous `PLV8XDB` and `PLV8XCONN` environment variables, 80 | so there's no need to type `-d` over and over again on the command line: 81 | 82 | % export PLV8XDB=test 83 | 84 | To connect with `ident` (local Unix user) authentication, specify the path 85 | to the socket directory with `-d`: 86 | 87 | % plv8x -d /var/run/postgresql -l 88 | plv8x: 491425 bytes 89 | 90 | Now create some test data with json columns: (example table from [Postgres 9.3 feature highlight: JSON operators](http://michael.otacoo.com/postgresql-2/postgres-9-3-feature-highlight-json-operators/)) 91 | 92 | % psql test 93 | test=# CREATE TABLE aa (a int, b json); 94 | CREATE TABLE 95 | test=# INSERT INTO aa VALUES (1, '{"f1":1,"f2":true,"f3":"Hi I''m \"Daisy\""}'); 96 | INSERT 0 1 97 | test=# INSERT INTO aa VALUES (2, '{"f1":{"f11":11,"f12":12},"f2":2}'); 98 | INSERT 0 1 99 | test=# INSERT INTO aa VALUES (3, '{"f1":[1,"Robert \"M\"",true],"f2":[2,"Kevin \"K\"",false]}'); 100 | INSERT 0 1 101 | 102 | Instead of `b->'f1'`, we use `b~>'this.f1'`, which means bind `b` as `this` and evaluate the right hand side (`this.f1`): 103 | 104 | test=# SELECT b~>'this.f1' AS f1, b~>'this.f3' AS f3 FROM aa WHERE a = 1; 105 | f1 | f3 106 | ----+-------------------- 107 | 1 | "Hi I'm \"Daisy\"" 108 | 109 | If you like coffee, `@` works too: 110 | 111 | test=# SELECT b~>'@f1' AS f1, b~>'@f3' AS f3 FROM aa WHERE a = 1; 112 | f1 | f3 113 | ----+-------------------- 114 | 1 | "Hi I'm \"Daisy\"" 115 | 116 | For multiple keys, you can of course do `b~>'@f1'~>'@f12'`, but single expression will do: 117 | 118 | test=# SELECT b~>'@f1'~>'@f12' AS f12_long, b~>'@f1.f12' AS f12 FROM aa WHERE a = 2; 119 | f12_long | f12 120 | ----------+----- 121 | 12 | 12 122 | 123 | Ditto for arrays: 124 | 125 | postgres=# SELECT b~>'@f1[0]' as f1_0 FROM aa WHERE a = 3; 126 | f1_0 127 | ------ 128 | 1 129 | 130 | Unary `~>` for just evaluating the expression: 131 | 132 | test=# SELECT ~>'[1 to 10]' AS f1 133 | f1 134 | ------------------------ 135 | [1,2,3,4,5,6,7,8,9,10] 136 | 137 | `~>` is actually a shorthand for `|> '~>...'`. Using raw `|>` for plain 138 | old javascript: 139 | 140 | test=# SELECT '{"foo": [1,2,3]}'::json |> 'function() { return this.foo[1] }'; 141 | ?column? 142 | ---------- 143 | 2 144 | 145 | Expression works too: 146 | 147 | test=# SELECT '{"foo": [1,2,3]}'::json |> 'return this.foo[1]'; 148 | ?column? 149 | ---------- 150 | 2 151 | 152 | CoffeeScript: 153 | 154 | test=# SELECT '{"foo": [1,2,3]}'::json |> '@foo[1]'; 155 | ?column? 156 | ---------- 157 | 2 158 | 159 | ```<|``` is ```|>``` reversed: 160 | 161 | test=# SELECT '@foo.1 * 5' <| '{"foo": [1,2,3]}'::json 162 | ?column? 163 | ---------- 164 | 10 165 | 166 | ```|>``` as unary operator: 167 | 168 | test=# SELECT |> '~> plv8x.require "LiveScript" .compile "-> \Hello" {+bare}'; 169 | ?column? 170 | -------------------------------------- 171 | "(function(){\n return Hello;\n});" 172 | 173 | # Importing nodejs modules and creating user functions 174 | 175 | Let's try reusing some existing npm modules: 176 | 177 | % npm i -g qs 178 | % plv8x -i qs # same as: plv8x -i qs:/path/to/qs/package.json 179 | % psql test 180 | 181 | # parse a query string 182 | test=# SELECT |>'require("qs").parse("foo=bar&baz=1")' AS qs; 183 | qs 184 | ------------------------- 185 | {"foo":"bar","baz":"1"} 186 | 187 | # actually use the parsed query string as json 188 | test=# SELECT qs~>'@foo' AS foo FROM (SELECT ~>'require("qs").parse("foo=bar&baz=1")' AS qs) a; 189 | foo 190 | ------- 191 | "bar" 192 | 193 | # create a user function from qs so we don't have to require it: 194 | % plv8x -f 'plv8x.json parse_qs(text)=qs:parse' 195 | ok plv8x.json parse_qs(text) 196 | # Now parse_qs is a postgresql function: 197 | test=# SELECT parse_qs('foo=bar&baz=1') AS qs; 198 | qs 199 | ------------------------- 200 | {"foo":"bar","baz":"1"} 201 | 202 | # Calling conventions for user functions 203 | 204 | We support both synchronous and async functions, as well as bare functions defined in 205 | `module.exports`. 206 | 207 | By default, the first two arguments to an async (back-call) function is taken 208 | to be `error` and `result` respectively: 209 | 210 | % plv8x -f 'fn(text):text=pkg:' # out = pkg(x) 211 | % plv8x -f 'fn(text):text=pkg:method' # out = pkg.method(in) 212 | % plv8x -f 'fn(text):text=pkg:<-' # pkg(x, cb(err, out)) 213 | % plv8x -f 'fn(text):text=pkg:<-method' # pkg.method(x, cb(err, out)) 214 | 215 | Using an underscore, one can specify exactly which async callback parameter 216 | to expect from the lifted function: 217 | 218 | % plv8x -f 'fn(text):text=pkg:<-' # pkg(x, cb(err, out)) 219 | % plv8x -f 'fn(text):text=pkg:_<-' # pkg(x, cb(out)) 220 | % plv8x -f 'fn(text):text=pkg:,_<-' # pkg(x, cb(_0, out)) 221 | % plv8x -f 'fn(text):text=pkg:,,_<-' # pkg(x, cb(_0, _1, out)) 222 | 223 | # License 224 | 225 | MIT 226 | 227 | -------------------------------------------------------------------------------- /bin/plv8x: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require("../lib/cli.js"); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": [ 4 | "Chia-liang Kao" 5 | ], 6 | "email": "clkao@clkao.org" 7 | }, 8 | "name": "plv8x", 9 | "description": "Use JavaScript expressions and modules in PostgreSQL plv8", 10 | "version": "0.7.0", 11 | "keywords": [ 12 | "postgres", 13 | "pg", 14 | "database", 15 | "plv8" 16 | ], 17 | "main": "lib/index.js", 18 | "bin": { 19 | "plv8x": "bin/plv8x" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/clkao/plv8x.git" 24 | }, 25 | "scripts": { 26 | "test": "env PATH=\"./node_modules/.bin:$PATH\" mocha", 27 | "prepublish": "env PATH=\"./node_modules/.bin:$PATH\" lsc -cj package.ls &&\nenv PATH=\"./node_modules/.bin:$PATH\" lsc -bc -o lib src" 28 | }, 29 | "engines": { 30 | "node": "*" 31 | }, 32 | "dependencies": { 33 | "async": "^0.9.0", 34 | "optimist": "0.6.x", 35 | "pg": "^4.2.0", 36 | "pg-native": "^1.8.0", 37 | "resolve": "0.6.x", 38 | "browserify": "^8.1.3", 39 | "tmp": "0.0.x", 40 | "LiveScript": "1.2.0", 41 | "pretty-bytes": "^1.0.3", 42 | "js-yaml": "3.0.x" 43 | }, 44 | "devDependencies": { 45 | "mocha": "^2.1.0", 46 | "chai": "^2.0.0", 47 | "sequelize": "git://github.com/clkao/sequelize.git", 48 | "uax11": "0.0.2" 49 | }, 50 | "optionalDependencies": {} 51 | } 52 | -------------------------------------------------------------------------------- /package.ls: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lsc -cj 2 | author: 3 | name: ['Chia-liang Kao'] 4 | email: 'clkao@clkao.org' 5 | name: 'plv8x' 6 | description: 'Use JavaScript expressions and modules in PostgreSQL plv8' 7 | version: '0.7.0' 8 | keywords: <[postgres pg database plv8]> 9 | main: \lib/index.js 10 | bin: 11 | plv8x: 'bin/plv8x' 12 | repository: 13 | type: 'git' 14 | url: 'git://github.com/clkao/plv8x.git' 15 | scripts: 16 | test: ' 17 | env PATH="./node_modules/.bin:$PATH" mocha' 18 | prepublish: """ 19 | env PATH="./node_modules/.bin:$PATH" lsc -cj package.ls && 20 | env PATH="./node_modules/.bin:$PATH" lsc -bc -o lib src 21 | """ 22 | engines: {node: '*'} 23 | dependencies: 24 | async: '^0.9.0' 25 | optimist: \0.6.x 26 | pg: '^4.2.0' 27 | 'pg-native': '^1.8.0' 28 | resolve: \0.6.x 29 | browserify: '^8.1.3' 30 | tmp: '0.0.x' 31 | LiveScript: \1.2.0 32 | 'pretty-bytes': '^1.0.3' 33 | # 'coffee-script': \* 34 | 'js-yaml': \3.0.x 35 | devDependencies: 36 | mocha: '^2.1.0' 37 | chai: '^2.0.0' 38 | sequelize: 'git://github.com/clkao/sequelize.git' 39 | uax11: \0.0.2 40 | optionalDependencies: {} 41 | -------------------------------------------------------------------------------- /src/bootstrap.ls: -------------------------------------------------------------------------------- 1 | {_mk_func, compile-coffeescript, compile-livescript, xpression-to-body} = require \.. 2 | {plv8x-sql,operators-sql} = require \./sql 3 | 4 | module.exports = (drop, cascade, done) -> 5 | if typeof drop is \function 6 | done = drop 7 | drop = false 8 | 9 | rows <~ @query "select version()" 10 | @conn 11 | ..query plv8x-sql drop, cascade 12 | 13 | [_, pg_version] = rows.0.version.match /^PostgreSQL ([\d\.]+)/ 14 | if pg_version >= \9.1.0 15 | ..query ''' 16 | SET client_min_messages TO WARNING; 17 | DO $$ BEGIN 18 | CREATE EXTENSION IF NOT EXISTS plv8; 19 | EXCEPTION WHEN OTHERS THEN END; $$; 20 | DO $$ BEGIN 21 | CREATE EXTENSION IF NOT EXISTS plls; 22 | EXCEPTION WHEN OTHERS THEN END; $$; 23 | ''' 24 | else 25 | ..query ''' 26 | DO $$ BEGIN 27 | CREATE FUNCTION plv8_call_handler() RETURNS language_handler AS '$libdir/plv8' LANGUAGE C; 28 | CREATE FUNCTION plv8_inline_handler(internal) RETURNS void AS '$libdir/plv8' LANGUAGE C; 29 | CREATE FUNCTION plv8_call_validator(oid) RETURNS void AS '$libdir/plv8' LANGUAGE C; 30 | CREATE TRUSTED LANGUAGE plv8 HANDLER plv8_call_handler INLINE plv8_inline_handler VALIDATOR plv8_call_validator; 31 | EXCEPTION WHEN OTHERS THEN END; $$; 32 | DO $$ BEGIN 33 | CREATE FUNCTION plls_call_handler() RETURNS language_handler AS '$libdir/plv8' LANGUAGE C; 34 | CREATE FUNCTION plls_inline_handler(internal) RETURNS void AS '$libdir/plv8' LANGUAGE C; 35 | CREATE FUNCTION plls_call_validator(oid) RETURNS void AS '$libdir/plv8' LANGUAGE C; 36 | CREATE TRUSTED LANGUAGE plls HANDLER plls_call_handler INLINE plls_inline_handler VALIDATOR plls_call_validator; 37 | EXCEPTION WHEN OTHERS THEN END; $$; 38 | ''' 39 | if pg_version < \9.2.0 40 | ..query """ 41 | DO $$ BEGIN 42 | INSERT INTO pg_catalog.pg_type (SELECT 'json' AS typname, 43 | typnamespace , typowner , typlen , typbyval , typtype , typcategory 44 | , typispreferred , typisdefined , typdelim , typrelid , typelem , 45 | typarray , typinput , typoutput , typreceive , typsend , typmodin 46 | , typmodout , typanalyze , typalign , typstorage , typnotnull , 47 | typbasetype , typtypmod , typndims , #{ if pg_version < \9.1.0 => '' else 'typcollation ,' } 48 | typdefaultbin , typdefault 49 | FROM pg_catalog.pg_type where typname = 'text'); 50 | EXCEPTION WHEN OTHERS THEN END; $$; 51 | """ 52 | ..query ''' 53 | select oid from pg_catalog.pg_type where typname = 'json' and typtype ='b'; 54 | ''', [], (err, res) ~> 55 | if res?rows?0 56 | @register-json-type that.oid 57 | else 58 | throw "json type not found" 59 | ..query ''' 60 | DO $$ BEGIN 61 | CREATE DOMAIN plv8x.json AS json; 62 | EXCEPTION WHEN OTHERS THEN END; $$; 63 | ''' 64 | if pg_version < \9.2.0 65 | @mk-user-func "plv8x.json row_to_json(anyelement)", ':-> it', -> 66 | @mk-user-func "plv8x.json array_to_json(anyarray)", ':-> it', -> 67 | 68 | if pg_version < \9.3.0 69 | @mk-user-func "plv8x.json to_json(anyelement)", ':-> it', -> 70 | 71 | @conn 72 | ..query _mk_func \plv8x.boot {} \void _boot 73 | ..query _mk_func \plv8x.eval {str: \text} \plv8x.json _eval 74 | ..query _mk_func \plv8x.apply {str: \text, args: \plv8x.json} \plv8x.json _apply 75 | ..query _mk_func \plv8x.json_eval {code: \text} \plv8x.json _mk_json_eval(0), {+cascade, +boot} 76 | ..query _mk_func \plv8x.json_eval {data: \plv8x.json, code: \text} \plv8x.json _mk_json_eval(-1), {+cascade, +boot} 77 | ..query _mk_func \plv8x.json_eval {code: \text, data: \plv8x.json} \plv8x.json _mk_json_eval(1), {+cascade, +boot} 78 | ..query _mk_func \plv8x.json_eval_ls {code: \text} \plv8x.json _mk_json_eval_ls(0), {+cascade, +boot} 79 | ..query _mk_func \plv8x.json_eval_ls {data: \plv8x.json, code: \text} \plv8x.json _mk_json_eval_ls(-1), {+cascade, +boot} 80 | ..query _mk_func \plv8x.json_eval_ls {code: \text, data: \plv8x.json} \plv8x.json _mk_json_eval_ls(1), {+cascade, +boot} 81 | ..query operators-sql! 82 | r = ..query "select plv8x.boot()" 83 | r.on \end done 84 | 85 | _eval = (str) -> eval str 86 | 87 | _apply = (func, args) -> 88 | func = "(function() {return (#func);})()" 89 | eval func .apply null args 90 | 91 | _require = (name) -> 92 | plv8.elog DEBUG, \require name, JSON.stringify {plv8x.require-stack, plv8x.global} 93 | return plv8x.global[name] if plv8x.global[name]? 94 | 95 | exclude = if plv8x.require-stack.length 96 | """where name not in (#{plv8x.require-stack.map(-> "'#it'").join \,})""" 97 | else 98 | "" 99 | 100 | res = plv8.execute "select name, code from plv8x.code #exclude", [] 101 | x = {} 102 | err = '' 103 | for {code,name:bundle} in res# when bundle is name 104 | plv8x.require-stack.push bundle 105 | try 106 | loader = """ 107 | (function() { 108 | var module = {exports: {}}; 109 | var global = {}; 110 | var require = function(name) { 111 | if (name === '#name') { 112 | throw new Error('Cannot find module "#name"'); 113 | } 114 | return plv8x.require(name); 115 | }; 116 | (function() { #code }).apply(global); 117 | if (module.exports.require) 118 | return module.exports.require('#name'); 119 | else 120 | return global['#name'] 121 | 122 | })() 123 | """ 124 | if eval loader 125 | plv8x.require-stack.pop! 126 | return plv8x.global[name] = that 127 | catch e 128 | err := e 129 | if e isnt /Cannot find module/ 130 | plv8x.require-stack = [] 131 | break 132 | plv8x.require-stack.pop! 133 | 134 | plv8.elog WARNING, "failed to load module #name: #err" 135 | plv8.elog WARNING, err.stack?substr(0, 235) if err.stack 136 | 137 | _mk_json_eval = (type=1) -> match type 138 | | (> 0) => (code, data) -> 139 | eval plv8x.xpression-to-body code .apply data 140 | | (< 0) => (data, code) -> 141 | eval plv8x.xpression-to-body code .apply data 142 | | otherwise => (code) -> 143 | eval plv8x.xpression-to-body code .apply @ 144 | 145 | _mk_json_eval_ls = (type=1) -> match type 146 | | (> 0) => (code, data) -> 147 | eval plv8x.xpression-to-body "~>#code" .apply data 148 | | (< 0) => (data, code) -> 149 | eval plv8x.xpression-to-body "~>#code" .apply data 150 | | otherwise => (code) -> 151 | eval plv8x.xpression-to-body "~>#code" .apply @ 152 | 153 | _boot = 154 | """ 155 | function() { 156 | if (typeof plv8x == 'undefined') 157 | plv8x = { 158 | require: #{_require.toString!replace /(['\\])/g, '\$1'}, 159 | xpressionToBody: #{xpression-to-body.toString!replace /(['\\])/g, '\$1'}, 160 | compileCoffeescript: #{compile-coffeescript.toString!replace /(['\\])/g, '\$1'}, 161 | compileLivescript: #{compile-livescript.toString!replace /(['\\])/g, '\$1'}, 162 | requireStack: [], 163 | global: {} 164 | }; 165 | plv8x_require = require = plv8x.require; 166 | } 167 | """ 168 | 169 | -------------------------------------------------------------------------------- /src/cli.ls: -------------------------------------------------------------------------------- 1 | require! <[fs resolve pg pretty-bytes]> 2 | plv8x = require \../ 3 | 4 | argv = require 'optimist' .usage 'Usage: plv8x {OPTIONS}' .wrap 80 5 | .option 'db' do 6 | alias: 'd' 7 | desc: 'database connection string' 8 | .option 'list' do 9 | alias: 'l' 10 | desc: 'List bundles' 11 | .option 'purge' do 12 | desc: 'Purge bundles' 13 | .option 'import' do 14 | alias: 'i' 15 | desc: 'Import bundles' 16 | .option 'delete' do 17 | desc: 'Delete bundles' 18 | .option 'inject' do 19 | alias: <[ fn f ]> 20 | desc: 'Define plv8 function from bundles or string' 21 | .option 'query' do 22 | alias: 'c' 23 | desc: 'Execute query' 24 | .option 'eval' do 25 | alias: 'e' 26 | desc: 'Eval the given expression in plv8x context' 27 | .option 'eval-ls' do 28 | alias: 'E' 29 | desc: 'Eval the given LiveScript expression in plv8x context' 30 | .option 'require' do 31 | alias: 'r' 32 | desc: 'Require the given file and eval in plv8x context' 33 | .option 'json' do 34 | alias: 'j' 35 | desc: 'Use JSON for output' 36 | .option 'yaml' do 37 | alias: 'y' 38 | desc: 'Use YAML for output (requires libyaml module)' 39 | .option 'help' do 40 | alias: 'h' 41 | desc: 'Show this message' 42 | .check (argv) -> 43 | throw '' if argv.help 44 | if process.argv.length <= 2 then throw 'Specify a parameter.' 45 | .argv 46 | 47 | conString = argv.db or process.env['PLV8XCONN'] or process.env['PLV8XDB'] or process.env.TESTDBNAME 48 | unless conString 49 | console.log "ERROR: Please set the PLV8XDB environment variable, or pass in a connection string to -d" 50 | process.exit! 51 | plx <- plv8x.new conString 52 | 53 | done = (output) -> 54 | if output 55 | if argv.json 56 | output = JSON.stringify output 57 | else if argv.yaml 58 | YAML = require \js-yaml 59 | output = YAML.dump output 60 | console.log output 61 | plx.end! 62 | 63 | switch 64 | | argv.import => 65 | [name, manifest=require.resolve("#name/package.json")] = argv.import.split \: 66 | plx.import-bundle name, manifest, -> done! 67 | | argv.delete => 68 | plx.delete-bundle argv.delete, -> done! 69 | | argv.inject => 70 | [spec, source] = argv.inject.split /\s*=\s*/ 71 | plx.mk-user-func spec, source, -> 72 | console.log \ok spec 73 | done! 74 | | argv.purge => 75 | plx.purge -> 76 | console.log it 77 | done! 78 | | argv.list => 79 | plx.list (res) -> 80 | unless argv.json 81 | res = ["#name: #{pretty-bytes length}" for {name, length} in res].join '\n' 82 | done res 83 | | argv.query => 84 | plx.query argv.query, done 85 | | argv.eval => 86 | code = plv8x.xpression-to-body argv.eval 87 | plx.eval "(#code)()", done 88 | | argv.'eval-ls' => 89 | code = plv8x.xpression-to-body "~>#{ argv.'eval-ls' }" 90 | plx.eval "(#code)()", done 91 | | argv.require => 92 | code = fs.readFileSync argv.require, \utf8 93 | code = plv8x.compile-livescript code if argv.require is /\.ls$/ 94 | plx.eval code, done 95 | | otherwise => console.log "Unknown command: #{argv._.0}"; done! 96 | -------------------------------------------------------------------------------- /src/index.ls: -------------------------------------------------------------------------------- 1 | class PLX 2 | (@conn) -> 3 | @eval = @plv8x-eval 4 | @ap = @plv8x-apply 5 | 6 | bootstrap: (...args) -> 7 | require \./bootstrap .apply @, args 8 | 9 | end: -> @conn.end! 10 | 11 | query: (...args) -> 12 | cb = args.pop! 13 | err, {rows}? <- @conn.query ...args 14 | throw err if err 15 | cb? rows 16 | 17 | plv8x-eval: (code, cb) -> 18 | code = "(#code)()" if typeof code is \function 19 | @query "select plv8x.eval($1) as ret", [code], -> cb it?0?ret 20 | 21 | plv8x-apply: (code, args, cb) -> 22 | code = "(#code)()" if typeof code is \function 23 | args = JSON.stringify args if typeof args isnt \string 24 | @query "select plv8x.apply($1, $2) as ret", [code, args], -> cb it?0?ret 25 | 26 | list: (cb) -> 27 | @query "select name, length(code) as length from plv8x.code", cb 28 | 29 | purge: (cb) -> 30 | @query "delete from plv8x.code", cb 31 | 32 | delete-bundle: (name, cb) -> 33 | @query "delete from plv8x.code where name = $1" [name], -> cb it.rows 34 | 35 | _bundle: (name, manifest, cb) -> 36 | require! <[browserify]> 37 | exclude = <[one browserify pg plv8x pgrest express optimist uax11]> 38 | 39 | if name is \pgrest 40 | # XXX temporary solution till we get proper manifest exclusion going 41 | exclude ++= <[express cors gzippo connect-csv]> 42 | 43 | b = browserify {+ignoreMissing, standalone: name} 44 | for module in exclude => b.exclude module 45 | b.require manifest - /\package\.json$/, {+entry} 46 | 47 | err, buf <- b.bundle 48 | console.log err if err 49 | cb buf 50 | 51 | import-bundle: (name, manifest, cb) -> 52 | bundle_from = (name, m, cb) ~> 53 | @_bundle name, m, cb 54 | {mtime} = require 'fs' .statSync manifest 55 | rows <~ @query "select updated from plv8x.code where name = $1" [name] 56 | if rows.length 57 | return cb! if rows.0.updated and rows.0.updated >= mtime 58 | code <~ bundle_from name, manifest 59 | [q, bind] = if rows.length # udpate 60 | ["update plv8x.code set code = $2, updated = $3 where name = $1" [name, code, mtime]] 61 | else 62 | ["insert into plv8x.code (name, code, updated) values($1, $2, $3)" [name, code, mtime]] 63 | @query q, bind, cb 64 | 65 | import-funcs: (name, pkg, bootstrap, cb) -> 66 | funcs = for funcname, f of pkg when \function is typeof f and f.$plv8x and f.$bootstrap is bootstrap => let funcname, f 67 | (done) ~> @mk-user-func "#funcname#{f.$plv8x}" "#name:#funcname", -> done! 68 | require! async 69 | <- async.waterfall funcs 70 | cb! 71 | 72 | import-bundle-funcs: (name, manifest, body) -> 73 | pkg = try require (manifest - /package\.json$/) 74 | unless pkg 75 | return @import-bundle name, manifest, (cb) ~> 76 | cb <~ body 77 | return cb! 78 | 79 | <~ @import-funcs name, pkg, true 80 | <~ @import-bundle name, manifest 81 | cb <~ body 82 | <~ @import-funcs name, pkg, void 83 | cb! 84 | 85 | mk-user-func: (spec, source, cb) -> 86 | [_, rettype, name, args, rettype-after] = spec.match //^ 87 | (?:([\.\w]+) \s+)? (\w+) \( (.*) \) (?:\s*:\s*([\.\s\w\[\]]+))? 88 | $// or throw "failed to parse #spec" 89 | 90 | rettype ?= rettype-after 91 | 92 | param-obj = {} 93 | if args 94 | for arg, idx in args.split /\s*,\s*/ 95 | [_, type, param-name] = arg.match /^([\.\w\[\]]+)\s*(\w+)?$/ or throw "failed to parse param #arg" 96 | param-name ?= "__#{idx}" 97 | param-obj[param-name] = type 98 | 99 | [_, pkg, expression] = source.match /^([\w-]*):([\S\s]*)$/ or throw "failed to parse source #source" 100 | 101 | body = if pkg 102 | boot = true 103 | plv8x-lift pkg, expression 104 | else 105 | xpression-to-body expression 106 | <~ @query _mk_func name, param-obj, rettype, body, {boot, +cascade} 107 | 108 | cb { rettype, name, param-obj, body } 109 | 110 | 111 | <[ bootstrap plv8xEval importBundle list purge deleteBundle mkUserFunc ]>.for-each !(key) ~> 112 | exports[key] = (conn, ...rest) -> 113 | console.error 'deprecated api, use PLX instead' 114 | plx = new PLX conn 115 | plx[key] ...rest 116 | 117 | exports.new = (db, config, cb) -> 118 | if \function is typeof config 119 | cb = config 120 | config = {} 121 | # db can aslo be an object, mostly for connceting with local socket 122 | db = "tcp://localhost/#db" if \string is typeof db && db.indexOf('/') < 0 123 | pg = require \pg .native 124 | 125 | conn = new pg.Client db 126 | ..connect! 127 | plx = new PLX conn 128 | 129 | plx.register-json-type = (oid) -> 130 | pg.types.setTypeParser oid, 'text', JSON.parse.bind JSON 131 | 132 | if config.client 133 | return plx.query 'select plv8x.boot()' -> cb? plx 134 | 135 | <- plx.bootstrap 136 | <- plx.query 'select plv8x.boot()' 137 | <- plx.import-bundle \plv8x require.resolve \../package.json 138 | cb? plx 139 | 140 | export function connect(db) 141 | require! pg 142 | new pg.Client db 143 | ..connect! 144 | 145 | export function xpression-to-body(code) 146 | clivescript = if plv8? then plv8x.compile-livescript else compile-livescript 147 | ccoffee = if plv8? then plv8x.compile-coffeescript else compile-coffeescript 148 | cls = -> "(function () {return #{clivescript it} })()" 149 | match code 150 | | /^\s*->/ => cls code 151 | | /^\s*~>/ => cls code.replace /~>/ \-> 152 | | /^\s*\=\>/ => ccoffee code.replace /\=\>/ \-> 153 | | /^function/ => "(#code)" 154 | | /^\s*@/ => cls "-> #code" 155 | | /^\s*&/ => cls "-> #code" 156 | | /\breturn[(\s]/ => "(function(){#code})" 157 | | otherwise => "(function(){return #code})" 158 | 159 | export function compile-livescript(expression) 160 | require \LiveScript .compile expression, {+bare} .replace /;$/, '' 161 | 162 | export function compile-coffeescript(expression) 163 | cs = require \CoffeeScript 164 | throw "CoffeeScript not found, use plv8x --import CoffeeScript:/path/to/extras/cofee-script.js to enable it" unless cs 165 | cs.compile expression, {+bare} .replace /;$/, '' 166 | 167 | export function _mk_func ( 168 | name, param-obj, ret, body, {lang = \plv8, skip-compile, cascade, boot} = {} 169 | ) 170 | params = [] 171 | args = for pname, type of param-obj 172 | params.push "#pname #type" 173 | if type is \plv8x.json 174 | "JSON.parse(#pname)" 175 | else pname 176 | 177 | if lang is \plls and not skip-compile 178 | lang = \plv8 179 | compiled = compile-livescript body 180 | 181 | compiled ||= body 182 | body = "(eval(#compiled))(#args)"; 183 | body = "JSON.stringify(#body)" if ret is \plv8x.json 184 | boot = if boot 185 | "if (typeof plv8x == 'undefined') plv8.execute('select plv8x.boot()', []);" 186 | else '' 187 | 188 | return """ 189 | 190 | SET client_min_messages TO WARNING; 191 | DO \$PLV8X_EOF\$ BEGIN 192 | 193 | DROP FUNCTION IF EXISTS #{name} (#params) #{if cascade => 'CASCADE' else ''}; 194 | EXCEPTION WHEN OTHERS THEN END; \$PLV8X_EOF\$; 195 | 196 | CREATE FUNCTION #name (#params) RETURNS #ret AS \$PLV8X__BODY__\$ 197 | #boot; 198 | return #body; 199 | \$PLV8X__BODY__\$ LANGUAGE #lang IMMUTABLE STRICT; 200 | """ 201 | 202 | export function plv8x-lift(module, func-name) 203 | if [_, capture, method]? = func-name.match /^([\s,_]*)<-([-\w]+)?$/ 204 | capture ||= "err, _" 205 | backcall = true 206 | else 207 | method = func-name 208 | 209 | fcall = if method => ".#method" else '' 210 | if backcall 211 | compile-livescript """ 212 | -> 213 | var $$rv 214 | f = (plv8x.require '#{module}') #{fcall} 215 | args = [].slice.call(arguments).concat (#{capture}) -> 216 | $$rv := _ 217 | f ...args 218 | $$rv 219 | """ 220 | else 221 | compile-livescript "-> plv8x.require '#{module}' #fcall ..." 222 | 223 | sql = require \./sql 224 | export util = sql{define-schema} 225 | -------------------------------------------------------------------------------- /src/sql.ls: -------------------------------------------------------------------------------- 1 | export function define-schema(name, comment, drop, cascade) 2 | dodrop = if drop => """ 3 | ELSE EXECUTE 'DROP SCHEMA #name #{if cascade => 'CASCADE' else ''}; 4 | CREATE SCHEMA #name';""" else "" 5 | 6 | """ 7 | DO $$ 8 | BEGIN 9 | 10 | IF NOT EXISTS( 11 | SELECT schema_name 12 | FROM information_schema.schemata 13 | WHERE schema_name = '#name' 14 | ) 15 | THEN 16 | EXECUTE 'CREATE SCHEMA #name'; 17 | #dodrop 18 | END IF; 19 | 20 | END 21 | 22 | $$; 23 | 24 | COMMENT ON SCHEMA #name IS '#comment'; 25 | """ 26 | 27 | export function plv8x-sql(drop=false, cascade=false) 28 | define-schema(\plv8x 'Out-of-table for loading plv8 modules', drop, cascade) + """ 29 | 30 | DO $$ BEGIN CREATE TABLE plv8x.code ( 31 | name text PRIMARY KEY, 32 | code text, 33 | load_seq int, 34 | updated timestamp 35 | ); EXCEPTION WHEN OTHERS THEN END; $$; 36 | """ 37 | 38 | export function operators-sql() 39 | ''' 40 | DROP OPERATOR IF EXISTS |> (NONE, text); CREATE OPERATOR |> ( 41 | RIGHTARG = text, 42 | PROCEDURE = plv8x.json_eval 43 | ); 44 | DROP OPERATOR IF EXISTS |> (plv8x.json, text); CREATE OPERATOR |> ( 45 | LEFTARG = plv8x.json, 46 | RIGHTARG = text, 47 | COMMUTATOR = <|, 48 | PROCEDURE = plv8x.json_eval 49 | ); 50 | DROP OPERATOR IF EXISTS <| (text, plv8x.json); CREATE OPERATOR <| ( 51 | LEFTARG = text, 52 | RIGHTARG = plv8x.json, 53 | COMMUTATOR = |>, 54 | PROCEDURE = plv8x.json_eval 55 | ); 56 | 57 | DROP OPERATOR IF EXISTS ~> (NONE, text); CREATE OPERATOR ~> ( 58 | RIGHTARG = text, 59 | PROCEDURE = plv8x.json_eval_ls 60 | ); 61 | DROP OPERATOR IF EXISTS ~> (plv8x.json, text); CREATE OPERATOR ~> ( 62 | LEFTARG = plv8x.json, 63 | RIGHTARG = text, 64 | COMMUTATOR = <~, 65 | PROCEDURE = plv8x.json_eval_ls 66 | ); 67 | DROP OPERATOR IF EXISTS <~ (text, plv8x.json); CREATE OPERATOR <~ ( 68 | LEFTARG = text, 69 | RIGHTARG = plv8x.json, 70 | COMMUTATOR = ~>, 71 | PROCEDURE = plv8x.json_eval_ls 72 | ); 73 | ''' 74 | -------------------------------------------------------------------------------- /test/basics.ls: -------------------------------------------------------------------------------- 1 | should = (require \chai).should! 2 | 3 | expect = (require \chai).expect 4 | var plx, plv8x, conString 5 | describe 'db', -> ``it`` 6 | .. 'loaded successfully.', (done) -> 7 | # Load home page 8 | conString := process.env.TESTDB ? "tcp://localhost/#{ process.env.TESTDBNAME }" 9 | console.log conString 10 | plv8x := require \.. 11 | plv8x.should.be.ok 12 | _plx <- plv8x.new conString 13 | plx := _plx 14 | plx.should.be.ok 15 | done! 16 | .. 'eval', (done) -> 17 | <- plx.query "select plv8x.eval('plus_one = function(a) { return a + 1 }')" 18 | rows <- plx.query "select plv8x.apply($1, $2) as ret" [\plus_one, JSON.stringify [42]] 19 | expect rows.0.ret .to.equal 43 20 | done! 21 | .. 'evalit', (done) -> 22 | rows <- plx.query "select plv8x.apply($1, $2) as ret" ['function(a) { return a + 1}', JSON.stringify [42]] 23 | expect rows.0.ret .to.equal 43 24 | done! 25 | .. 'purge', (done) -> 26 | <- plx.purge 27 | <- plv8x.new conString 28 | res <- plx.list 29 | expect [name for {name} in res] .to.be.deep.equal <[plv8x]> 30 | done! 31 | .. 'import', (done) -> 32 | <- plx.import-bundle \LiveScript, './node_modules/LiveScript/package.json' 33 | res <- plx.list 34 | expect [name for {name} in res] .to.be.deep.equal <[plv8x LiveScript]> 35 | done! 36 | .. 'import existing', (done) -> 37 | <- plx.import-bundle \LiveScript, './node_modules/LiveScript/package.json' 38 | res <- plx.list 39 | expect [name for {name} in res] .to.be.deep.equal <[plv8x LiveScript]> 40 | done! 41 | .. 'plv8x_require', (done) -> 42 | err, res <- plx.conn.query "select plv8x.eval($1) as ret", ["plv8x_require('LiveScript').VERSION"] 43 | expect(err).to.be.a('null'); 44 | {ret} = res.rows.0 45 | expect ret .to.equal \1.2.0 46 | done! 47 | .. 'lscompile', (done) -> 48 | plx.conn.query plv8x._mk_func \plv8x.lscompile {str: \text, args: \plv8x.json} \text plv8x.plv8x-lift "LiveScript", "compile" 49 | err, res <- plx.conn.query "select plv8x.lscompile($1, $2) as ret", ["-> 42", JSON.stringify {+bare}] 50 | expect(err).to.be.a('null'); 51 | {ret} = res.rows.0 52 | expect (eval ret)! .to.equal 42 53 | done! 54 | .. 'mk-user-func', (done) -> 55 | <- plx.mk-user-func "text lsgo(text, plv8x.json)", ':-> plv8x_require "LiveScript" .compile ...' 56 | err, res <- plx.conn.query "select lsgo($1, $2) as ret", ["-> 42", JSON.stringify {+bare}] 57 | expect(err).to.be.a('null'); 58 | {ret} = res.rows.0 59 | expect (eval ret)! .to.equal 42 60 | done! 61 | .. 'mk-user-func with dash-separated source', (done) -> 62 | <- plx.mk-user-func "plv8x.json patch_json(json, json[])", "fast-json-patch:apply" 63 | it.body.should.match /return plv8x.require\('fast-json-patch'\).apply.apply\(this, arguments\)/ 64 | done! 65 | .. 'required object persistency', (done) -> 66 | err, res <- plx.conn.query """select plv8x.eval('plv8x_require("LiveScript").xxx = 123')""" 67 | expect(err).to.be.a('null'); 68 | err, res <- plx.conn.query """select plv8x.eval('plv8x_require("LiveScript").xxx') as ret""" 69 | expect err .to.be.a('null'); 70 | {ret} = res.rows.0 71 | expect ret .to.equal 123 72 | done! 73 | .. 'required object persistency', (done) -> 74 | err, res <- plx.conn.query """select plv8x.json_eval('{"a": 1}'::json, '@b=2; @') as ret""" 75 | expect(err).to.be.a('null'); 76 | {ret} = res.rows.0 77 | expect ret .to.be.deep.equal a: 1, b: 2 78 | done! 79 | -------------------------------------------------------------------------------- /test/bundle-require.ls: -------------------------------------------------------------------------------- 1 | should = (require \chai).should! 2 | 3 | expect = (require \chai).expect 4 | var plx, conString 5 | describe 'user package', -> ``it`` 6 | .. 'loaded successfully.', (done) -> 7 | conString := process.env.TESTDB ? "tcp://localhost/#{ process.env.TESTDBNAME }" 8 | plv8x = require '..' 9 | plv8x.should.be.ok 10 | _plx <- plv8x.new conString 11 | plx := _plx 12 | plx.should.be.ok 13 | done! 14 | .. 'import', (done) -> 15 | <- plx.import-bundle 'uax11', './node_modules/uax11/package.json' 16 | done! 17 | .. 'eval', (done) -> 18 | res <- plx.eval 'require("uax11").toFullwidth("hi")' 19 | expect(res).to.equal('hi') 20 | done! 21 | .. 'import user package', (done) -> 22 | <- plx.import-bundle 'dummy1', './test/user-package/dummy1.js' 23 | done! 24 | .. 'user package works', (done) -> 25 | res <- plx.eval 'require("dummy1")("hi")' 26 | expect res .to.equal 'hi' 27 | done! 28 | .. 'import user package that requires root package', (done) -> 29 | <- plx.import-bundle 'dummy2', './test/user-package/dummy2.js' 30 | done! 31 | .. 'user package works', (done) -> 32 | res <- plx.eval 'require("dummy2").xpressionToBody("1")' 33 | expect res .to.equal '(function(){return 1})' 34 | done! 35 | xit 'nonexisting package fails to load', (done) -> 36 | load-module = -> 37 | plx.eval 'require("_nothing")' -> 38 | process.next-tick -> 39 | expect load-module .to.throw /failed to load module/ 40 | done! 41 | .. 'existing package still works', (done) -> 42 | res <- plx.eval 'require("dummy2").xpressionToBody("1")' 43 | expect res .to.equal '(function(){return 1})' 44 | done! -------------------------------------------------------------------------------- /test/inject-callback.ls: -------------------------------------------------------------------------------- 1 | should = (require \chai).should! 2 | 3 | expect = (require \chai).expect 4 | var plx, conString 5 | describe 'db', -> ``it`` 6 | .. 'loaded successfully.', (done) -> 7 | # Load home page 8 | conString := process.env.TESTDB ? "tcp://localhost/#{ process.env.TESTDBNAME }" 9 | plv8x = require \.. 10 | plv8x.should.be.ok 11 | _plx <- plv8x.new conString 12 | plx := _plx 13 | plx.should.be.ok 14 | done! 15 | .. 'import', (done) -> 16 | <- plx.import-bundle \uax11, './node_modules/uax11/package.json' 17 | done! 18 | .. 'test data (return type after signature)', (done) -> 19 | <- plx.mk-user-func "uax_full(text):text", 'uax11:_<-' 20 | err, {rows}? <- plx.conn.query "select uax_full($1) as ret", ["http://github.com/clkao/plv8x"] 21 | expect(err).to.be.a('null'); 22 | expect(rows.0.ret).to.equal('http://github.com/clkao/plv8x') 23 | done! 24 | .. 'test data (return type before signature)', (done) -> 25 | <- plx.mk-user-func "text uax_full(text)", 'uax11:_<-' 26 | err, {rows}? <- plx.conn.query "select uax_full($1) as ret", ["http://github.com/clkao/plv8x"] 27 | expect(err).to.be.a('null'); 28 | expect(rows.0.ret).to.equal('http://github.com/clkao/plv8x') 29 | done! 30 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require LiveScript 2 | --colors 3 | --compilers ls:LiveScript 4 | --timeout 5000 5 | -------------------------------------------------------------------------------- /test/operator.ls: -------------------------------------------------------------------------------- 1 | should = (require \chai).should! 2 | 3 | expect = (require \chai).expect 4 | var plx, conString 5 | describe 'db', -> ``it`` 6 | .. 'loaded successfully.', (done) -> 7 | # Load home page 8 | conString := process.env.TESTDB ? "tcp://localhost/#{ process.env.TESTDBNAME }" 9 | plv8x = require \.. 10 | _plx <- plv8x.new conString 11 | plx := _plx 12 | plx.should.be.ok 13 | done! 14 | .. 'javascript |>', (done) -> 15 | rows <- plx.query "select |> '1' as ret" 16 | expect JSON.parse rows.0.ret .to.equal(1) 17 | done! 18 | .. 'javascript |> return', (done) -> 19 | rows <- plx.query "select |> 'if (true) { return 21.5; }' as ret" 20 | expect JSON.parse rows.0.ret .to.equal(21.5) 21 | done! 22 | .. 'javascript |> function', (done) -> 23 | rows <- plx.query "select |> 'function() { return 42; }' as ret" 24 | expect JSON.parse rows.0.ret .to.equal(42) 25 | done! 26 | .. 'javascript data |> expression', (done) -> 27 | rows <- plx.query "select $1 |> $2 as ret", [ JSON.stringify({ hello: [2, 3, 4] }), 'this.hello[1]' ] 28 | expect JSON.parse rows.0.ret .to.equal(3) 29 | done! 30 | .. 'livescript |> ~>', (done) -> 31 | <- plx.import-bundle \LiveScript './node_modules/LiveScript/package.json' 32 | rows <- plx.query "select |> $1 as ret" ['~> plv8x.require "LiveScript" .VERSION'] 33 | console.log rows 34 | expect rows.0.ret .to.equal \1.2.0 35 | done! 36 | .. 'livescript data |> ->', (done) -> 37 | rows <- plx.query "select $1 |> $2 as ret", [ JSON.stringify({ hello: [2, 3, 4] }), '-> @hello.1' ] 38 | expect rows.0.ret .to.equal(3) 39 | done! 40 | .. 'livescript data |> expression', (done) -> 41 | rows <- plx.query "select $1 |> $2 as ret", [ JSON.stringify({ hello: [2, 3, 4] }), '@hello.1' ] 42 | expect JSON.parse rows.0.ret .to.equal(3) 43 | done! 44 | .. 'livescript ~>', (done) -> 45 | <- plx.import-bundle \LiveScript './node_modules/LiveScript/package.json' 46 | rows <- plx.query "select ~> $1 as ret" ['plv8x.require "LiveScript" .VERSION'] 47 | console.log rows 48 | expect rows.0.ret .to.equal \1.2.0 49 | done! 50 | .. 'livescript data ~>', (done) -> 51 | rows <- plx.query "select $1 ~> $2 as ret", [ JSON.stringify({ hello: [2, 3, 4] }), '-@hello.1' ] 52 | expect rows.0.ret .to.equal(-3) 53 | done! 54 | .. 'livescript <~ data', (done) -> 55 | rows <- plx.query "select $2 <~ $1 as ret", [ JSON.stringify({ hello: [2, 3, 4] }), '-@hello.1' ] 56 | expect rows.0.ret .to.equal(-3) 57 | done! 58 | # Currently Borked -- pullreqs welcome 59 | /* 60 | .. 'coffeescript |> =>', (done) -> 61 | <- plx.import-bundle \coffee-script './node_modules/coffee-script/package.json' 62 | rows <- plx.query "select |> $1 as ret" ['=> "1.6.2"'] 63 | console.log rows 64 | expect rows.0.ret .to.equal \1.6.2 65 | done! 66 | .. 'coffeescript data |> =>', (done) -> 67 | rows <- plx.query "select $1 |> $2 as ret", [ JSON.stringify({ hello: [2, 3, 4] }), '=> @hello[1]' ] 68 | expect rows.0.ret .to.equal(3) 69 | done! 70 | */ 71 | -------------------------------------------------------------------------------- /test/poly.ls: -------------------------------------------------------------------------------- 1 | should = (require \chai).should! 2 | 3 | expect = (require \chai).expect 4 | var plx, plv8x, conString, pg_version 5 | describe 'db', -> ``it`` 6 | .. 'loaded successfully.', (done) -> 7 | # Load home page 8 | conString := process.env.TESTDB ? "tcp://localhost/#{ process.env.TESTDBNAME }" 9 | plv8x := require \.. 10 | plv8x.should.be.ok 11 | _plx <- plv8x.new conString 12 | plx := _plx 13 | plx.should.be.ok 14 | rows <- plx.query "select version()" 15 | [, pg_version] := rows.0.version.match /^PostgreSQL ([\d\.]+)/ 16 | done! 17 | .. 'poly-func', (done) -> 18 | if pg_version < \9.2.0 19 | it.skip 'skipped for < 9.2', -> 20 | return done! 21 | 22 | <- plx.mk-user-func "plv8x.json polyelement(anyelement)", ':-> it' 23 | err, res <- plx.conn.query """ 24 | select polyelement(names) as ret 25 | FROM (values (1,'{"foo": 1}'::json), (2, '{"bar": 2}'::json)) as names(id, content) 26 | """ 27 | expect(err).to.be.a('null'); 28 | expect res.rows .to.be.deep.equal [ 29 | * ret: id: 1, content: foo: 1 30 | * ret: id: 2, content: bar: 2 31 | ] 32 | done! 33 | .. 'poly-func-array', (done) -> 34 | if pg_version < \9.2.0 35 | it.skip 'skipped for < 9.2', -> 36 | return done! 37 | <- plx.mk-user-func "plv8x.json polyarray(anyarray)", ':-> it' 38 | err, res <- plx.conn.query """ 39 | select polyarray(array_agg(_)) as ret from 40 | (select * from (values (1,'{"foo": 1}'::json), (2, '{"bar": 2}'::json)) as names(id, content)) _ 41 | """ 42 | expect(err).to.be.a('null'); 43 | {ret} = res.rows.0 44 | expect ret .to.be.deep.equal [ 45 | * id: 1, content: foo: 1 46 | * id: 2, content: bar: 2 47 | ] 48 | done! 49 | -------------------------------------------------------------------------------- /test/user-package/dummy1.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var uax_full = require("uax11"); 3 | 4 | module.exports = function(it) { return it }; 5 | 6 | module.exports.uax_full = uax_full; 7 | 8 | }).call(this); 9 | -------------------------------------------------------------------------------- /test/user-package/dummy2.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var plv8x = require("plv8x"); 3 | 4 | module.exports = function(it) { return it }; 5 | 6 | module.exports.xpressionToBody = plv8x.xpressionToBody; 7 | 8 | }).call(this); 9 | --------------------------------------------------------------------------------