Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Several fixes and improvements by Itagaki Takahiro.

There are incompatibilites in built-in functions and global variables.

  * Support PGXS to build the codes.
  * Added regression tests.
  * Support additional type conversions:
    - timestamp [with time zone]
    - date
    - record (input only)
    - oid
  * Logging function is renamed to print(elevel, ...). Acceptable elevels
    are DEBUG[1-5], LOG, INFO, NOTICE, and WARNING.  Use JavaScript
    exceptions (throw) to report errors; ERROR level are not allowed.
  * Query function is renamed to executeSql(sql). For SELECT statements,
    the returned value is an array of hashes. Each hash represents each
    record. Column names are mapped to hash keys.  For non-SELECT commands,
    the returned value is an integer that represents number of affected rows.
  * Trigger functions receives trigger conditions as function arguments:
    NEW, OLD, TG_NAME, TG_WHEN, TG_LEVEL, TG_OP, TG_RELID, TG_TABLE_NAME,
    TG_TABLE_SCHEMA, and TG_ARGV.
  * Support VARIADIC arguments.
  * Support unnamed arguments. They can be referred with 'arguments' or $N.
  * Fix error handling and exception handling. Postgre's errors (siglongjmp)
    are packed into C++ exceptions to invoke destructors properly,
    and extracted at the end of function.
  • Loading branch information...
commit 30f2336e65e29b64c0806b0038e948d88590f450 1 parent 4a21d56
@itagaki-takahiro itagaki-takahiro authored
View
39 Makefile
@@ -1,9 +1,30 @@
-all:
- g++ -O2 -Wall -fPIC -I ../postgres/pgsql/src/include -I ../v8/include -o plv8.o -c plv8.cc
- g++ -lv8 -shared -o plv8.so plv8.o
-clean:
- rm plv8.so plv8.o
-distclean: clean
-realclean: clean
-
-.PHONY: all clean distclean realclean
+V8DIR = ../v8
+
+SRCS = plv8.cc plv8_type.cc
+OBJS = $(SRCS:.cc=.o)
+DATA_built = plv8.sql
+DATA = uninstall_plv8.sql
+SHLIB_LINK += -lv8
+MODULE_big = plv8
+REGRESS = plv8
+
+CCFLAGS := $(filter-out -Wmissing-prototypes, $(CFLAGS))
+CCFLAGS := $(filter-out -Wdeclaration-after-statement, $(CCFLAGS))
+
+%.o : %.cc
+ g++ $(CCFLAGS) $(CPPFLAGS) -I $(V8DIR)/include -fPIC -c -o $@ $<
+
+ifdef USE_PGXS
+PGXS := $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/plv8
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+# remove dependency to libxml2 and libxslt
+LIBS := $(filter-out -lxml2, $(LIBS))
+LIBS := $(filter-out -lxslt, $(LIBS))
+
View
7 README
@@ -8,7 +8,7 @@ plv8 is shared library that provides a PostgreSQL procedual language powered by
* REQUIREMENT
plv8 requires:
PG: version 8.4.x (maybe older are allowed)
-V8: version 1.2
+V8: version 2.3.8
g++ and all tools that PG and V8 requires to be built.
@@ -29,7 +29,7 @@ CREATE FUNCTION plv8_call_validator(Oid) RETURNS void AS '$libdir/plv8' LANGUAGE
CREATE LANGUAGE plv8 HANDLER plv8_call_handler VALIDATOR plv8_call_validator;
-- create js function
-CREATE OR REPLACE FUNCTION plv8_test(keys text[], vals text[]) RETURNS text AS $$
+CREATE FUNCTION plv8_test(keys text[], vals text[]) RETURNS text AS $$
var o = {};
for(var i=0; i<keys.length; i++){
o[keys[i]] = vals[i];
@@ -42,8 +42,7 @@ SELECT plv8_test(ARRAY['name', 'age'], ARRAY['Tom', '29']);
* CAVEATS
- This is WIP version.
-- Tested on only CentOS 5 (x86), PostgreSQL 8.4 (8.5devel) and v8 1.2.
+- Tested on only CentOS 5 (x86), PostgreSQL 8.4 (9.0devel) and v8 2.3.8.
- There's no interface to call SPI.
-- You must specify argument names in function declarations.
- 1-dim array can be put as arguments. Multi dimension array is not supported.
- And many, many bugs...
View
55 download.sh
@@ -6,56 +6,7 @@ cd $idir
#hg pull https://plv8js.googlecode.com/hg/
#hg push # for push to googlecode
#hg commit -u username@gmail.com -m "commit message"
-g++ -O2 -Wall -fPIC -I ../postgres/pgsql/src/include -I ../v8/include -o plv8.o -c plv8.cc
-g++ -lv8 -shared -o plv8.so plv8.o
-ifile=/home/`whoami`/Software/PostgreSQL-9/lib/plv8.so
-sudo rm -f $ifile
-sudo ln -s $idir/plv8.so $ifile
-
-echo '';
-echo '======= testing PLV8'
-echo 'DROP FUNCTION IF EXISTS plv8_call_handler() CASCADE; DROP FUNCTION plv8_call_validator(Oid) CASCADE;' | psql
-echo "CREATE FUNCTION plv8_call_handler() RETURNS language_handler AS '/home/`whoami`/Software/PostgreSQL-9/lib/plv8' LANGUAGE C; CREATE FUNCTION plv8_call_validator(Oid) RETURNS void AS '/home/`whoami`/Software/PostgreSQL-9/lib/plv8' LANGUAGE C; CREATE LANGUAGE plv8 HANDLER plv8_call_handler VALIDATOR plv8_call_validator;" | psql
-
-echo '';
-echo '======= testing Function'
-echo 'CREATE OR REPLACE FUNCTION plv8_test(keys text[], vals text[]) RETURNS
-text AS $$
-var o = {};
-for(var i=0; i<keys.length; i++){
- o[keys[i]] = vals[i];
-}
-return JSON.stringify(o);
-$$ LANGUAGE plv8 IMMUTABLE STRICT; ' | psql
-echo "SELECT plv8_test(ARRAY['name', 'age'], ARRAY['Tom', '29']);" | psql
-
-echo '';
-echo '======= create test TABLE';
-echo 'DROP TABLE IF EXISTS test CASCADE;' | psql
-echo 'CREATE TABLE test ( za BIGSERIAL PRIMARY KEY, ztext TEXT );' | psql
-echo "INSERT INTO test(ztext) VALUES( 'a'),( 'b'),( 'c');" | psql
-
-echo '';
-echo '======= testing SPI'
-echo '
-CREATE OR REPLACE FUNCTION testing123() RETURNS text AS $____$
-var x = new SPI();
-//return x.query("INSERT INTO test (ztext) VALUES( $$x$$ ) RETURNING * ");
-return x.query("SELECT ztext FROM test");
-$____$ LANGUAGE "plv8";
-' | psql
-echo 'SELECT testing123();' | psql
-
-echo '';
-echo '======= testing Trigger'
-echo '
-CREATE OR REPLACE FUNCTION testingT() RETURNS trigger AS $____$
-var x = new TRIG();
-var y = new LOG();
-y.notice( "called from " + x.event() );
-return; // harusnya mengembalikan sesuatu.. apa ya?
-$____$ LANGUAGE "plv8";
-' | psql
-echo 'CREATE TRIGGER onDel1 AFTER DELETE ON test EXECUTE PROCEDURE testingT();' | psql
-echo 'DELETE FROM test;' | psql
+make USE_PGXS=1
+make USE_PGXS=1 install
+make USE_PGXS=1 installcheck
View
290 expected/plv8.out
@@ -0,0 +1,290 @@
+-- INSTALL
+SET client_min_messages = warning;
+\set ECHO none
+RESET client_min_messages;
+-- CREATE FUNCTION
+CREATE FUNCTION plv8_test(keys text[], vals text[]) RETURNS text AS
+$$
+ var o = {};
+ for (var i = 0; i < keys.length; i++)
+ o[keys[i]] = vals[i];
+ return JSON.stringify(o);
+$$
+LANGUAGE plv8 IMMUTABLE STRICT;
+SELECT plv8_test(ARRAY['name', 'age'], ARRAY['Tom', '29']);
+ plv8_test
+---------------------------
+ {"name":"Tom","age":"29"}
+(1 row)
+
+CREATE FUNCTION unnamed_args(text[], text[]) RETURNS text[] AS
+$$
+ var array1 = arguments[0];
+ var array2 = $2;
+ return array1.concat(array2);
+$$
+LANGUAGE plv8 IMMUTABLE STRICT;
+SELECT unnamed_args(ARRAY['A', 'B'], ARRAY['C', 'D']);
+ unnamed_args
+--------------
+ {A,B,C,D}
+(1 row)
+
+CREATE FUNCTION concat_strings(VARIADIC args text[]) RETURNS text AS
+$$
+ var result = "";
+ for (var i = 0; i < args.length; i++)
+ if (args[i] != null)
+ result += args[i];
+ return result;
+$$
+LANGUAGE plv8 IMMUTABLE STRICT;
+SELECT concat_strings('A', 'B', NULL, 'C');
+ concat_strings
+----------------
+ ABC
+(1 row)
+
+CREATE FUNCTION return_void() RETURNS void AS $$ $$ LANGUAGE plv8;
+SELECT return_void();
+ return_void
+-------------
+
+(1 row)
+
+CREATE FUNCTION return_null() RETURNS text AS $$ return null; $$ LANGUAGE plv8;
+SELECT r, r IS NULL AS isnull FROM return_null() AS r;
+ r | isnull
+---+--------
+ | t
+(1 row)
+
+-- TYPE CONVERTIONS
+CREATE FUNCTION int2_to_int4(x int2) RETURNS int4 AS $$ return x; $$ LANGUAGE plv8;
+SELECT int2_to_int4(24::int2);
+ int2_to_int4
+--------------
+ 24
+(1 row)
+
+CREATE FUNCTION int4_to_int2(x int4) RETURNS int2 AS $$ return x; $$ LANGUAGE plv8;
+SELECT int4_to_int2(42);
+ int4_to_int2
+--------------
+ 42
+(1 row)
+
+CREATE FUNCTION int4_to_int8(x int4) RETURNS int8 AS $$ return x; $$ LANGUAGE plv8;
+SELECT int4_to_int8(48);
+ int4_to_int8
+--------------
+ 48
+(1 row)
+
+CREATE FUNCTION int8_to_int4(x int8) RETURNS int4 AS $$ return x; $$ LANGUAGE plv8;
+SELECT int8_to_int4(84);
+ int8_to_int4
+--------------
+ 84
+(1 row)
+
+CREATE FUNCTION float8_to_numeric(x float8) RETURNS numeric AS $$ return x; $$ LANGUAGE plv8;
+SELECT float8_to_numeric(1.5);
+ float8_to_numeric
+-------------------
+ 1.5
+(1 row)
+
+CREATE FUNCTION numeric_to_int8(x numeric) RETURNS int8 AS $$ return x; $$ LANGUAGE plv8;
+SELECT numeric_to_int8(1234.56);
+ numeric_to_int8
+-----------------
+ 1234
+(1 row)
+
+CREATE FUNCTION int4_to_text(x int4) RETURNS text AS $$ return x; $$ LANGUAGE plv8;
+SELECT int4_to_text(123);
+ int4_to_text
+--------------
+ 123
+(1 row)
+
+CREATE FUNCTION text_to_int4(x text) RETURNS int4 AS $$ return x; $$ LANGUAGE plv8;
+SELECT text_to_int4('123');
+ text_to_int4
+--------------
+ 123
+(1 row)
+
+SELECT text_to_int4('abc'); -- error
+ERROR: invalid input syntax for integer: "abc"
+CREATE FUNCTION int4array_to_textarray(x int4[]) RETURNS text[] AS $$ return x; $$ LANGUAGE plv8;
+SELECT int4array_to_textarray(ARRAY[123, 456]::int4[]);
+ int4array_to_textarray
+------------------------
+ {123,456}
+(1 row)
+
+CREATE FUNCTION textarray_to_int4array(x text[]) RETURNS int4[] AS $$ return x; $$ LANGUAGE plv8;
+SELECT textarray_to_int4array(ARRAY['123', '456']::text[]);
+ textarray_to_int4array
+------------------------
+ {123,456}
+(1 row)
+
+CREATE FUNCTION timestamptz_to_text(t timestamptz) RETURNS text AS $$ return t.toUTCString() $$ LANGUAGE plv8;
+SELECT timestamptz_to_text('23 Dec 2010 12:34:56 GMT');
+ timestamptz_to_text
+-------------------------------
+ Thu, 23 Dec 2010 12:34:56 GMT
+(1 row)
+
+CREATE FUNCTION text_to_timestamptz(t text) RETURNS timestamptz AS $$ return new Date(t) $$ LANGUAGE plv8;
+SELECT text_to_timestamptz('23 Dec 2010 12:34:56 GMT') AT TIME ZONE 'GMT';
+ timezone
+--------------------------
+ Thu Dec 23 12:34:56 2010
+(1 row)
+
+CREATE FUNCTION date_to_text(t date) RETURNS text AS $$ return t.toUTCString() $$ LANGUAGE plv8;
+SELECT date_to_text('23 Dec 2010');
+ date_to_text
+-------------------------------
+ Thu, 23 Dec 2010 00:00:00 GMT
+(1 row)
+
+CREATE FUNCTION text_to_date(t text) RETURNS date AS $$ return new Date(t) $$ LANGUAGE plv8;
+SELECT text_to_date('23 Dec 2010 GMT');
+ text_to_date
+--------------
+ 12-23-2010
+(1 row)
+
+CREATE FUNCTION oidfn(id oid) RETURNS oid AS $$ return id $$ LANGUAGE plv8;
+SELECT oidfn('pg_catalog.pg_class'::regclass);
+ oidfn
+-------
+ 1259
+(1 row)
+
+-- RECORD TYPES
+CREATE TYPE rec AS (i integer, t text);
+CREATE FUNCTION scalar_to_record(i integer, t text) RETURNS rec AS
+$$
+ return { "i": i, "t": t };
+$$
+LANGUAGE plv8;
+SELECT scalar_to_record(1, 'a');
+ERROR: JSON to record is not implemented yet
+CREATE FUNCTION record_to_text(x rec) RETURNS text AS
+$$
+ return JSON.stringify(x);
+$$
+LANGUAGE plv8;
+SELECT record_to_text('(1,a)'::rec);
+ record_to_text
+-----------------
+ {"i":1,"t":"a"}
+(1 row)
+
+-- print()
+CREATE FUNCTION test_print(arg text) RETURNS void AS
+$$
+ print(NOTICE, 'args =', arg);
+ print(WARNING, 'warning');
+ print(20, 'ERROR is not allowed');
+$$
+LANGUAGE plv8;
+SELECT test_print('ABC');
+NOTICE: args = ABC
+WARNING: warning
+ERROR: invalid error level for print()
+DETAIL: test_print() LINE 4: print(20, 'ERROR is not allowed');
+-- executeSql()
+CREATE TABLE test_tbl (i integer, s text);
+CREATE FUNCTION test_sql() RETURNS integer AS
+$$
+ var rows = executeSql("SELECT i, 's' || i AS s FROM generate_series(1, 4) AS t(i)");
+ for (var r = 0; r < rows.length; r++)
+ {
+ var result = executeSql("INSERT INTO test_tbl VALUES(" + rows[r].i + ",'" + rows[r].s + "')");
+ print(NOTICE, JSON.stringify(rows[r]), result);
+ }
+ return rows.length;
+$$
+LANGUAGE plv8;
+SELECT test_sql();
+NOTICE: {"i":1,"s":"s1"} 1
+NOTICE: {"i":2,"s":"s2"} 1
+NOTICE: {"i":3,"s":"s3"} 1
+NOTICE: {"i":4,"s":"s4"} 1
+ test_sql
+----------
+ 4
+(1 row)
+
+SELECT * FROM test_tbl;
+ i | s
+---+----
+ 1 | s1
+ 2 | s2
+ 3 | s3
+ 4 | s4
+(4 rows)
+
+CREATE FUNCTION test_sql_error() RETURNS void AS $$ executeSql("ERROR") $$ LANGUAGE plv8;
+SELECT test_sql_error();
+ERROR: syntax error at or near "ERROR"
+DETAIL: test_sql_error() LINE 1: executeSql("ERROR")
+-- TRIGGER
+CREATE FUNCTION test_trigger() RETURNS trigger AS
+$$
+ print(NOTICE, "NEW = ", JSON.stringify(NEW));
+ print(NOTICE, "OLD = ", JSON.stringify(OLD));
+ print(NOTICE, "TG_OP = ", TG_OP);
+ print(NOTICE, "TG_ARGV = ", TG_ARGV);
+$$
+LANGUAGE "plv8";
+CREATE TRIGGER test_trigger
+ BEFORE INSERT OR UPDATE OR DELETE
+ ON test_tbl FOR EACH ROW
+ EXECUTE PROCEDURE test_trigger('foo', 'bar');
+INSERT INTO test_tbl VALUES(100, 'ABC');
+NOTICE: NEW = {"i":100,"s":"ABC"}
+NOTICE: OLD = undefined
+NOTICE: TG_OP = INSERT
+NOTICE: TG_ARGV = foo,bar
+UPDATE test_tbl SET i = 101, s = 'DEF' WHERE i = 1;
+NOTICE: NEW = {"i":101,"s":"DEF"}
+NOTICE: OLD = {"i":1,"s":"s1"}
+NOTICE: TG_OP = UPDATE
+NOTICE: TG_ARGV = foo,bar
+DELETE FROM test_tbl WHERE i >= 100;
+NOTICE: NEW = undefined
+NOTICE: OLD = {"i":100,"s":"ABC"}
+NOTICE: TG_OP = DELETE
+NOTICE: TG_ARGV = foo,bar
+NOTICE: NEW = undefined
+NOTICE: OLD = {"i":101,"s":"DEF"}
+NOTICE: TG_OP = DELETE
+NOTICE: TG_ARGV = foo,bar
+SELECT * FROM test_tbl;
+ i | s
+---+----
+ 2 | s2
+ 3 | s3
+ 4 | s4
+(3 rows)
+
+-- ERRORS
+CREATE FUNCTION syntax_error() RETURNS text AS '@' LANGUAGE plv8;
+ERROR: SyntaxError: Unexpected token ILLEGAL
+DETAIL: syntax_error() LINE 1: @
+CREATE FUNCTION reference_error() RETURNS text AS 'not_defined' LANGUAGE plv8;
+SELECT reference_error();
+ERROR: ReferenceError: not_defined is not defined
+DETAIL: reference_error() LINE 1: not_defined
+CREATE FUNCTION throw() RETURNS void AS $$throw new Error("an error");$$ LANGUAGE plv8;
+SELECT throw();
+ERROR: Error: an error
+DETAIL: throw() LINE 1: throw new Error("an error");
View
1,726 plv8.cc
@@ -1,944 +1,782 @@
-#include "plv8.h"
-
-#include <v8.h>
-#include <string>
-#include <sstream>
-using namespace std;
-
-#ifdef __cplusplus
-extern "C"{
-#endif
-
-#include "executor/spi.h"
-#include "funcapi.h"
-#include "catalog/pg_proc.h"
-#include "catalog/pg_type.h"
-#include "mb/pg_wchar.h"
-#include "utils/array.h"
-#include "utils/memutils.h"
-#include "utils/builtins.h"
-#include "utils/lsyscache.h"
-#include "utils/syscache.h"
-#include "commands/trigger.h"
-#include "../v8cgi/src/macros.h"
-
-PG_MODULE_MAGIC;
-
-PG_FUNCTION_INFO_V1(plv8_call_handler);
-PG_FUNCTION_INFO_V1(plv8_call_validator);
-
-void _PG_init(void);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-typedef struct plv8_type_info
-{
- Oid typid;
- Oid ioparam;
- int32 typmod;
- int16 len;
- bool byval;
- char align;
- char category;
- FmgrInfo fn_input;
- FmgrInfo fn_output;
-
- /* optional, only when category = TYPECATEGORY_ARRAY */
- struct plv8_type_info *elem;
-} plv8_type_info;
-
-typedef struct
-{
- v8::Persistent<v8::Function> function;
- char *proname;
- char *prosrc;
-
- TransactionId fn_xmin;
- ItemPointerData fn_tid;
-
- int nargs;
- plv8_type_info rettype;
- plv8_type_info argtypes[FUNC_MAX_ARGS];
-/*
- Oid rettype;
- Oid retioparam;
- int32 rettypmod;
- char retcategory;
- FmgrInfo *fn_retinput;
- int nargs;
- Oid argtypes[FUNC_MAX_ARGS];
- char argcategories[FUNC_MAX_ARGS];
- / * for array type arguments * /
- Oid elemtypes[FUNC_MAX_ARGS];
- int16 elemlens[FUNC_MAX_ARGS];
- bool elembyvals[FUNC_MAX_ARGS];
- char elemaligns[FUNC_MAX_ARGS];
-
- FmgrInfo *fn_argoutput[FUNC_MAX_ARGS];
-*/
-} plv8_info;
-
-typedef struct
-{
- char proc_name[NAMEDATALEN];
- plv8_info *info_data;
-} plv8_info_entry;
-
-
-v8::Persistent<v8::Context> global_context = v8::Context::New();
-v8::TryCatch global_try_catch;
-static HTAB *plv8_info_hash = NULL;
-
-
-//static Datum plv8_func_handler(PG_FUNCTION_ARGS);
-static plv8_info *compile_plv8_function(Oid fn_oid, bool is_trigger);
-
-static v8::Persistent<v8::Function> plv8_create_function(const char *proname, int proarglen,
- const char *proargs[], const char *prosrc);
-static Datum plv8_call_jsfunction(FunctionCallInfo fcinfo);
-static void plv8_fill_type(Oid typid, plv8_type_info *type, bool forinput, bool output);
-static Datum plv8_jsarray_to_datum(v8::Handle<v8::Value> value,
- bool *isnull, Oid elemtype, Oid elemioparam, int16 elemlen,
- bool elembyval, char elemalign, FmgrInfo *fn_eleminput);
-static Datum plv8_jsval_to_datum(v8::Handle<v8::Value> value,
- bool *isnull, Oid typid, Oid ioparam, FmgrInfo *fn_input);
-static v8::Handle<v8::Value> plv8_datum_to_jsarray(Datum datum,
- bool isnull, Oid elemtype, int16 elemlen,
- bool elembyval, char elemalign, FmgrInfo *fn_elemoutput);
-static v8::Handle<v8::Value> plv8_datum_to_jsval(Datum datum, bool isnull, Oid type, FmgrInfo *fn_output);
-static char *plv8_toutf(char *cstr);
-static char *plv8_tolocal(char *cstr);
-static void plv8_elog_exception(int errcode, v8::TryCatch *try_catch);
-static const char *ToCString(v8::String::Utf8Value &value);
-
-
-void
-_PG_init(void)
-{
- static bool inited = false;
- HASHCTL hash_ctl;
-
- if (inited)
- return;
-
- hash_ctl.keysize = NAMEDATALEN;
- hash_ctl.entrysize = sizeof(plv8_info);
-
- plv8_info_hash = hash_create("PLv8 Procedures",
- 32,
- &hash_ctl,
- HASH_ELEM);
-
- inited = true;
-}
-
-JS_METHOD(_SPI) {
- ASSERT_CONSTRUCTOR;
- return args.This();
-}
-
-JS_METHOD(_TRIG) {
- ASSERT_CONSTRUCTOR;
- return args.This();
-}
-
-JS_METHOD(_LOG) {
- ASSERT_CONSTRUCTOR;
- return args.This();
-}
-
-JS_METHOD(_query) {
-
- // donnect to SPI manager
- if (SPI_connect() != SPI_OK_CONNECT) {
- elog(ERROR, "failed to connect");
- return JS_BOOL(false);
- }
- // get first parameter
- v8::String::Utf8Value q(args[0]);
-
- // result variables
- string resultdatastr;
- bool resultdatabool = false;
- bool resulttype = false;
-
- // query status
- int status = 0;
-
- // old context
- MemoryContext oldcontext = CurrentMemoryContext;
-
- // try SPI_exec
- PG_TRY();
- {
- status = SPI_exec(*q,0);
- }
- PG_CATCH();
- {
- MemoryContextSwitchTo(oldcontext);
- ErrorData *edata = CopyErrorData();
- ostringstream temp;
- temp
- << "\n" << edata->message
- << "\n" << edata->detail
- << "\n" << edata->detail_log
- << "\n" << edata->hint
- << "\n" << edata->context
- << "\n" << edata->internalquery
- ;
- elog(ERROR, "SPI Error \n %s", temp.str().c_str());
- FlushErrorState();
- FreeErrorData(edata);
- }
- PG_END_TRY();
- /*
- false - boolean
- true - string
- */
- switch(status) {
- case SPI_OK_SELECT:
- case SPI_OK_INSERT_RETURNING:
- case SPI_OK_DELETE_RETURNING:
- case SPI_OK_UPDATE_RETURNING:
- /* // yang dipunya secara global untuk context ini:
- extern PGDLLIMPORT uint32 SPI_processed; // jumlah yg hasil di select
- extern PGDLLIMPORT Oid SPI_lastoid;
- extern PGDLLIMPORT SPITupleTable *SPI_tuptable;
- extern PGDLLIMPORT int SPI_result;
- typedef struct SPITupleTable
- {
- MemoryContext tuptabcxt; // memory context of result table
- uint32 alloced; // # of alloced vals
- uint32 free; // # of free vals
- TupleDesc tupdesc; // tuple descriptor
- HeapTuple *vals; // tuples
- } SPITupleTable;
- // ini gak tau apa~
- // desc->attrs[i]->attisdropped
- // bool is_null;
- // Datum vattr;
- // vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
- */
- if(SPI_tuptable && SPI_processed) {
- char *attname = 0;
- for(size_t x=0;x<SPI_processed;++x) {
- for(int z=0;z<SPI_tuptable->tupdesc->natts;++z) {
- attname = SPI_tuptable->tupdesc->attrs[z]->attname.data;
- char *attdata = SPI_getvalue(*(SPI_tuptable->vals+x), SPI_tuptable->tupdesc, z + 1);
- if( attdata ) {
- resultdatastr += attdata;
- resultdatastr += " ";
- }
- }
- resulttype = true;
- }
- }
- break;
- case SPI_OK_INSERT:
- case SPI_OK_DELETE:
- case SPI_OK_UPDATE:
- resultdatabool = true;
- break;
- default:
- ostringstream temp;
- temp
- << " status : " << status
- << "\n SPI_OK_CONNECT : " << SPI_OK_CONNECT
- << "\n SPI_OK_FINISH : " << SPI_OK_FINISH
- << "\n SPI_OK_FETCH : " << SPI_OK_FETCH
- << "\n SPI_OK_UTILITY : " << SPI_OK_UTILITY
- << "\n SPI_OK_SELECT : " << SPI_OK_SELECT
- << "\n SPI_OK_SELINTO : " << SPI_OK_SELINTO
- << "\n SPI_OK_INSERT_RETURNING : " << SPI_OK_INSERT_RETURNING
- << "\n SPI_OK_DELETE_RETURNING : " << SPI_OK_DELETE_RETURNING
- << "\n SPI_OK_UPDATE_RETURNING : " << SPI_OK_UPDATE_RETURNING
- << "\n SPI_OK_CURSOR : " << SPI_OK_CURSOR
- << "\n SPI_OK_INSERT : " << SPI_OK_INSERT
- << "\n SPI_OK_DELETE : " << SPI_OK_DELETE
- << "\n SPI_OK_UPDATE : " << SPI_OK_UPDATE
- << "\n SPI_OK_REWRITTEN : " << SPI_OK_REWRITTEN
- << "\n SPI_ERROR_CONNECT : " << SPI_ERROR_CONNECT
- << "\n SPI_ERROR_COPY : " << SPI_ERROR_COPY
- << "\n SPI_ERROR_CONNECT : " << SPI_ERROR_CONNECT
- << "\n SPI_ERROR_OPUNKNOWN : " << SPI_ERROR_OPUNKNOWN
- << "\n SPI_ERROR_UNCONNECTED : " << SPI_ERROR_UNCONNECTED
- << "\n SPI_ERROR_CURSOR : " << SPI_ERROR_CURSOR
- << "\n SPI_ERROR_ARGUMENT : " << SPI_ERROR_ARGUMENT
- << "\n SPI_ERROR_PARAM : " << SPI_ERROR_PARAM
- << "\n SPI_ERROR_TRANSACTION : " << SPI_ERROR_TRANSACTION
- << "\n SPI_ERROR_NOATTRIBUTE : " << SPI_ERROR_NOATTRIBUTE
- << "\n SPI_ERROR_NOOUTFUNC : " << SPI_ERROR_NOOUTFUNC
- << "\n SPI_ERROR_TYPUNKNOWN : " << SPI_ERROR_TYPUNKNOWN
- ;
- resultdatastr += temp.str();
- resulttype = true;
- break;
- }
-
- // disconnect from SPI manager
- SPI_finish();
-
- // return strings
- if(resulttype) {
- return JS_STR(resultdatastr.c_str(),resultdatastr.length());
- } else {
- return JS_BOOL(resultdatabool);
- }
-}
-
-FunctionCallInfo GLOBALfcinfo;
-
-JS_METHOD(_event) {
- TriggerData *tdata;
- TupleDesc tupdesc;
- tdata = (TriggerData *) GLOBALfcinfo->context;
- tupdesc = tdata->tg_relation->rd_att;
- if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) {
- return JS_STR("INSERT");
- }
- else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) {
- return JS_STR("DELETE");
- }
- else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) {
- return JS_STR("UPDATE");
- }
- else {
- elog(ERROR, "unknown firing event for trigger function");
- return JS_BOOL(false);
- }
-}
-
-JS_METHOD(_old) {
- /*
- // The basic variables
- add_assoc_string(retval, "name", tdata->tg_trigger->tgname, 1);
- add_assoc_long(retval, "relid", tdata->tg_relation->rd_id);
- add_assoc_string(retval, "relname", SPI_getrelname(tdata->tg_relation), 1);
- */
- return JS_STR("old_value");
-}
-
-JS_METHOD(_new) {
- return JS_STR("new_value");
-}
-
-JS_METHOD(_notice) {
- v8::String::Utf8Value q(args[0]);
- elog(NOTICE,"%s",*q);
- return JS_BOOL(true);
-}
-
-JS_METHOD(_error) {
- v8::String::Utf8Value q(args[0]);
- elog(ERROR,"%s",*q);
- return JS_BOOL(true);
-}
-
-void init_2() {
- v8::HandleScope handle_scope;
-
- // SPI
- v8::Handle<v8::FunctionTemplate> ft1 = v8::FunctionTemplate::New(_SPI);
- ft1->SetClassName(JS_STR("SPI"));
- v8::Handle<v8::ObjectTemplate> ot1 = ft1->InstanceTemplate();
- ot1->SetInternalFieldCount(0);
- v8::Handle<v8::ObjectTemplate> pt1 = ft1->PrototypeTemplate();
- pt1->Set("query", v8::FunctionTemplate::New(_query));
-
- // Trigger
- v8::Handle<v8::FunctionTemplate> ft2 = v8::FunctionTemplate::New(_TRIG);
- ft2->SetClassName(JS_STR("TRIG"));
- v8::Handle<v8::ObjectTemplate> ot2 = ft2->InstanceTemplate();
- ot2->SetInternalFieldCount(0);
- v8::Handle<v8::ObjectTemplate> pt2 = ft2->PrototypeTemplate();
- pt2->Set("old", v8::FunctionTemplate::New(_old));
- pt2->Set("new", v8::FunctionTemplate::New(_new));
- pt2->Set("event", v8::FunctionTemplate::New(_event));
-
- // Log
- v8::Handle<v8::FunctionTemplate> ft3 = v8::FunctionTemplate::New(_LOG);
- ft3->SetClassName(JS_STR("LOG"));
- v8::Handle<v8::ObjectTemplate> ot3 = ft3->InstanceTemplate();
- ot3->SetInternalFieldCount(0);
- v8::Handle<v8::ObjectTemplate> pt3 = ft3->PrototypeTemplate();
- pt3->Set("notice", v8::FunctionTemplate::New(_notice));
- pt3->Set("error", v8::FunctionTemplate::New(_error));
-
- // create those prototype on global scope
- v8::Context::Scope context_scope(global_context);
- v8::TryCatch try_catch;
- global_context->GetCurrent()->Global()->Set(JS_STR("SPI"), ft1->GetFunction());
- global_context->GetCurrent()->Global()->Set(JS_STR("TRIG"), ft2->GetFunction());
- global_context->GetCurrent()->Global()->Set(JS_STR("LOG"), ft3->GetFunction());
-}
-
-Datum
-plv8_call_handler(PG_FUNCTION_ARGS)
-{
- Oid funcOid = fcinfo->flinfo->fn_oid;
- GLOBALfcinfo = fcinfo;
- init_2();
-
- if (!fcinfo->flinfo->fn_extra)
- {
- fcinfo->flinfo->fn_extra = (void *) compile_plv8_function(funcOid, false);
- }
-
- PG_RETURN_DATUM(plv8_call_jsfunction(fcinfo));
-}
-
-Datum
-plv8_call_validator(PG_FUNCTION_ARGS)
-{
- Oid funcOid = PG_GETARG_OID(0);
- HeapTuple tuple;
- Form_pg_proc proc;
- char functyptype;
- int numargs;
- Oid *argtypes;
- char **argnames;
- char *argmodes;
- bool istrigger = false;
- int i;
-
- /* Get the new function's pg_proc entry */
- tuple = SearchSysCache(PROCOID,
- ObjectIdGetDatum(funcOid),
- 0, 0, 0);
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for function %u", funcOid);
- proc = (Form_pg_proc) GETSTRUCT(tuple);
-
- functyptype = get_typtype(proc->prorettype);
-
- /* Disallow pseudotype result */
- /* except for TRIGGER, RECORD, or VOID */
- if (functyptype == TYPTYPE_PSEUDO)
- {
- /* we assume OPAQUE with no arguments means a trigger */
- if (proc->prorettype == TRIGGEROID ||
- (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
- istrigger = true;
- else if (proc->prorettype != RECORDOID &&
- proc->prorettype != VOIDOID)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PL/Perl functions cannot return type %s",
- format_type_be(proc->prorettype))));
- }
-
- /* Disallow pseudotypes in arguments (either IN or OUT) */
- numargs = get_func_arg_info(tuple,
- &argtypes, &argnames, &argmodes);
- for (i = 0; i < numargs; i++)
- {
- if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PL/v8 functions cannot accept type %s",
- format_type_be(argtypes[i]))));
- }
-
- ReleaseSysCache(tuple);
-
- compile_plv8_function(funcOid, istrigger);
-
- /* the result of a validator is ignored */
- PG_RETURN_VOID();
-}
-
-//static Datum
-//plv8_func_handler(PG_FUNCTION_ARGS)
-//{
-//
-//}
-
-static plv8_info *
-compile_plv8_function(Oid fn_oid, bool is_trigger)
-{
- HeapTuple procTup;
- Form_pg_proc procStruct;
- char internal_proname[NAMEDATALEN];
- plv8_info *info = NULL;
- plv8_info_entry *hash_entry;
- bool found;
- bool isnull;
- Datum procsrcdatum;
- char *proc_source;
- Oid *argtypes;
- char **argnames;
- char *argmodes;
- Oid rettype;
-// Oid functyptype;
-// Oid retinput;
- MemoryContext oldcontext;
- int i;
-
- procTup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(fn_oid),
- 0, 0, 0);
- if (!HeapTupleIsValid(procTup))
- elog(ERROR, "cache lookup failed for function %u", fn_oid);
-
- sprintf(internal_proname, "__PLv8_%u", fn_oid);
-
- hash_entry = (plv8_info_entry *) hash_search(plv8_info_hash, internal_proname, HASH_FIND, NULL);
-
- if (hash_entry)
- {
- bool uptodate;
-
- info = hash_entry->info_data;
-
- uptodate = (info->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
- ItemPointerEquals(&info->fn_tid, &procTup->t_self));
-
- if (!uptodate)
- {
- pfree(info->prosrc);
- pfree(info->proname);
- info->function.Dispose();
- pfree(info);
- info = NULL;
- hash_search(plv8_info_hash, internal_proname, HASH_REMOVE, NULL);
- }
- else
- {
- ReleaseSysCache(procTup);
- return info;
- }
- }
-
- procStruct = (Form_pg_proc) GETSTRUCT(procTup);
-
- procsrcdatum = SysCacheGetAttr(PROCOID, procTup,
- Anum_pg_proc_prosrc, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
-
- proc_source = TextDatumGetCString(procsrcdatum);
-
- oldcontext = MemoryContextSwitchTo(TopMemoryContext);
-
- info = (plv8_info *) palloc0(sizeof(plv8_info));
- info->proname = pstrdup(NameStr(procStruct->proname));
- info->prosrc = pstrdup(proc_source);
- info->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
- info->fn_tid = procTup->t_self;
- info->nargs = get_func_arg_info(procTup,
- &argtypes, &argnames, &argmodes);
-
-// functyptype = get_typtype(procStruct->prorettype);
- rettype = procStruct->prorettype;
-
- ReleaseSysCache(procTup);
-
- for(i = 0; i < info->nargs; i++)
- {
- Oid argtype = argtypes[i];
- char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
-
- if (argmode != PROARGMODE_IN)
- {
- elog(ERROR, "argument %d must be IN parameter", i + 1);
- }
-
- plv8_fill_type(argtype, &info->argtypes[i], false, true);
- }
-
- plv8_fill_type(rettype, &info->rettype, true, false);
-
- info->function = plv8_create_function(internal_proname, info->nargs, (const char **) argnames, proc_source);
- hash_entry = (plv8_info_entry *) hash_search(plv8_info_hash, internal_proname, HASH_ENTER, &found);
- hash_entry->info_data = info;
-
- /* restore */
- MemoryContextSwitchTo(oldcontext);
-
- return info;
-}
-
-static v8::Persistent<v8::Function>
-plv8_create_function(const char *proname, int proarglen, const char *proargs[], const char *prosrc)
-{
- v8::HandleScope hscope;
- StringInfoData args;
- StringInfoData body;
- int i;
-
- initStringInfo(&args);
- initStringInfo(&body);
-
- for(i = 0; i < proarglen; i++)
- {
- appendStringInfo(&args, "%s", proargs[i]);
- if (i != proarglen - 1)
- {
- appendStringInfoString(&args, ", ");
- }
- }
-
- /*
- * function <proname>(<arg1, ...>){
- * <prosrc>
- * }
- * <proname>
- */
- appendStringInfo(&body, "function %s(%s){\n%s\n};\n%s", proname, args.data, prosrc, proname);
-
- v8::Handle<v8::Value> name = v8::String::New("compile");
- v8::Handle<v8::String> source = v8::String::New(body.data);
- v8::Context::Scope context_scope(global_context);
- v8::TryCatch try_catch;
- v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
-
- if (script.IsEmpty())
- {
- plv8_elog_exception(ERROR, &try_catch);
- }
-
- v8::Handle<v8::Value> result = script->Run();
- if (result.IsEmpty())
- {
- plv8_elog_exception(ERROR, &try_catch);
- }
-
- if (!result->IsFunction())
- {
- elog(ERROR, "!result->IsFunction()");
- }
-
- return v8::Persistent<v8::Function>::New(v8::Handle<v8::Function>::Cast(result));
-}
-
-static Datum
-plv8_call_jsfunction(FunctionCallInfo fcinfo)
-{
- v8::HandleScope handle_scope;
- v8::Context::Scope context_scope(global_context);
- v8::Handle<v8::Value> args[FUNC_MAX_ARGS];
- int i;
- plv8_info *info = (plv8_info *) fcinfo->flinfo->fn_extra;
- plv8_type_info *rettype = &info->rettype;
-
- for(i = 0; i < info->nargs; i++)
- {
- plv8_type_info *type = &info->argtypes[i];
-
- if (type->category == TYPCATEGORY_ARRAY)
- {
- plv8_type_info *elem = type->elem;
-
- args[i] = plv8_datum_to_jsarray(fcinfo->arg[i], fcinfo->argnull[i],
- elem->typid, elem->len,
- elem->byval, elem->align,
- &elem->fn_output);
- }
- else
- {
- args[i] = plv8_datum_to_jsval(fcinfo->arg[i], fcinfo->argnull[i],
- type->typid, &type->fn_output);
- }
- }
-
- v8::Handle<v8::Value> result = info->function->Call(global_context->Global(), info->nargs, args);
-
- if (result.IsEmpty())
- {
- plv8_elog_exception(ERROR, &global_try_catch);
- }
-
- if (rettype->category == TYPCATEGORY_ARRAY)
- {
- plv8_type_info *elem = rettype->elem;
-
- return plv8_jsarray_to_datum(result,
- &fcinfo->isnull,
- elem->typid,
- elem->ioparam,
- elem->len,
- elem->byval,
- elem->align,
- &elem->fn_input);
- }
- else
- {
- return plv8_jsval_to_datum(result, &fcinfo->isnull, rettype->typid, rettype->ioparam, &rettype->fn_input);
- }
-/*
- else if (result->IsUndefined() || result->IsNull())
- {
- cstr_result = NULL;
- fcinfo->isnull = true;
- return (Datum) 0;
- }
-
- switch(rettype->typid)
- {
- case INT2OID:
- if (result->IsNumber())
- return Int16GetDatum((int16) (result->Int32Value()));
- case INT4OID:
- if (result->IsNumber())
- return Int32GetDatum((int32) (result->Int32Value()));
- case INT8OID:
- if (result->IsNumber())
- return Int64GetDatum((int64) (result->IntegerValue()));
- case FLOAT4OID:
- if (result->IsNumber())
- return Float4GetDatum((float4) (result->NumberValue()));
- case FLOAT8OID:
- if (result->IsNumber())
- return Float8GetDatum((float8) (result->NumberValue()));
- case BOOLOID:
- if (result->IsBoolean())
- return BoolGetDatum(result->BooleanValue());
- default:
- v8::String::Utf8Value str(result);
- cstr_result = const_cast<char *>(ToCString(str));
- cstr_result = plv8_tolocal(cstr_result);
- return InputFunctionCall(&rettype->fn_input, cstr_result, rettype->ioparam, -1);
- }
- elog(ERROR, "return type and returned value type unmatch");
- return (Datum) 0;
-*/
-}
-
-static void
-plv8_fill_type(Oid typid, plv8_type_info *type, bool forinput, bool foroutput)
-{
- char category;
- bool ispreferred;
-
- type->typid = typid;
- get_type_category_preferred(typid, &category, &ispreferred);
- type->category = category;
- get_typlenbyvalalign(typid, &type->len, &type->byval, &type->align);
-
- if (forinput)
- {
- Oid input_func;
-
- getTypeInputInfo(typid, &input_func, &type->ioparam);
- fmgr_info(input_func, &type->fn_input);
- }
-
- if (foroutput)
- {
- Oid output_func;
- bool isvarlen;
-
- getTypeOutputInfo(typid, &output_func, &isvarlen);
- fmgr_info(output_func, &type->fn_output);
- }
-
- if (category == TYPCATEGORY_ARRAY)
- {
- Oid elemid = get_element_type(typid);
- plv8_type_info *elemtype = (plv8_type_info *) palloc0(sizeof(plv8_type_info));
-
- if (elemid == InvalidOid)
- {
- elog(ERROR, "type(%u):category == '%c' but elemid is invalid", typid, category);
- }
-
- plv8_fill_type(elemid, elemtype, forinput, foroutput);
- type->elem = elemtype;
- }
-}
-
-static Datum
-plv8_jsarray_to_datum(v8::Handle<v8::Value> value,
- bool *isnull,
- Oid elemtype,
- Oid elemioparam,
- int16 elemlen,
- bool elembyval,
- char elemalign,
- FmgrInfo *fn_eleminput)
-{
- int i, length;
- Datum *dvalues;
- bool *dnulls;
- int ndims[0];
- int lbs[] = {1};
- ArrayType *array_type;
-
- if (value->IsUndefined() || value->IsNull())
- {
- *isnull = true;
- return (Datum ) 0;
- }
-
- if (!value->IsArray())
- {
- elog(ERROR, "value is not an Array");
- }
-
- v8::Handle<v8::Array> array(v8::Handle<v8::Array>::Cast(value));
-
- length = array->Length();
- dvalues = (Datum *) palloc(sizeof(Datum) * length);
- dnulls = (bool *) palloc0(sizeof(bool) * length);
- ndims[0] = length;
- for(i = 0; i < length; i++)
- {
- v8::Handle<v8::Value> el = array->Get(v8::Int32::New(i));
- dvalues[i] = plv8_jsval_to_datum(el, &dnulls[i], elemtype, elemioparam, fn_eleminput);
- }
-
- array_type = construct_md_array(dvalues, dnulls, 1, ndims, lbs, elemtype, elemlen, elembyval, elemalign);
- pfree(dvalues);
- pfree(dnulls);
-
- return (Datum) array_type;
-}
-
-static Datum
-plv8_jsval_to_datum(v8::Handle<v8::Value> value,
- bool *isnull,
- Oid typid,
- Oid ioparam,
- FmgrInfo *fn_input)
-{
- *isnull = false;
- if (value->IsUndefined() || value->IsNull())
- {
- *isnull = true;
- return (Datum) 0;
- }
-
- switch(typid)
- {
- case INT2OID:
- if (value->IsNumber())
- return Int16GetDatum((int16) (value->Int32Value()));
- case INT4OID:
- if (value->IsNumber())
- return Int32GetDatum((int32) (value->Int32Value()));
- case INT8OID:
- if (value->IsNumber())
- return Int64GetDatum((int64) (value->IntegerValue()));
- case FLOAT4OID:
- if (value->IsNumber())
- return Float4GetDatum((float4) (value->NumberValue()));
- case FLOAT8OID:
- if (value->IsNumber())
- return Float8GetDatum((float8) (value->NumberValue()));
- case BOOLOID:
- if (value->IsBoolean())
- return BoolGetDatum(value->BooleanValue());
- default:
- char *cstr;
- v8::String::Utf8Value str(value);
- cstr = const_cast<char *>(ToCString(str));
- cstr = plv8_tolocal(cstr);
- return InputFunctionCall(fn_input, cstr, ioparam, -1);
- }
-
- elog(ERROR, "datum type and js value type unmatch");
- return (Datum) 0;
-}
-
-static v8::Handle<v8::Value>
-plv8_datum_to_jsarray(Datum datum,
- bool isnull,
- Oid elemtype,
- int16 elemlen,
- bool elembyval,
- char elemalign,
- FmgrInfo *fn_elemoutput)
-{
- Datum *dvalues;
- bool *dnulls;
- int nelems;
- int i;
-
- if (isnull)
- {
- return v8::Null();
- }
-
- deconstruct_array((ArrayType *) datum, elemtype, elemlen, elembyval, elemalign,
- &dvalues, &dnulls, &nelems);
- v8::Local<v8::Array> result = v8::Array::New(nelems);
- for(i = 0; i < nelems; i++)
- {
- v8::Handle<v8::Value> val = plv8_datum_to_jsval(dvalues[i], dnulls[i], elemtype, fn_elemoutput);
- result->Set(v8::Int32::New(i), val);
- }
-
- return result;
-}
-
-static v8::Handle<v8::Value>
-plv8_datum_to_jsval(Datum datum, bool isnull, Oid type, FmgrInfo *fn_output)
-{
- char *value_text;
-
- if (isnull)
- {
- return v8::Null();
- }
-
- switch(type)
- {
- case INT2OID:
- return v8::Int32::New((int32_t) DatumGetInt16(datum));
- case INT4OID:
- return v8::Int32::New((int32_t) DatumGetInt32(datum));
- case INT8OID:
- return v8::Number::New((double) DatumGetInt64(datum));
- case FLOAT4OID:
- return v8::Number::New((double) DatumGetFloat4(datum));
- case FLOAT8OID:
- return v8::Number::New((double) DatumGetFloat8(datum));
- case BOOLOID:
- return v8::Boolean::New((bool) DatumGetBool(datum));
- default:
- value_text = OutputFunctionCall(fn_output, datum);
- value_text = plv8_toutf(value_text);
- return v8::String::New(value_text);
- }
-}
-
-static char *
-plv8_toutf(char *cstr)
-{
- int db_encoding = GetDatabaseEncoding();
-
- if (db_encoding == PG_UTF8)
- return cstr;
-
- return (char *) pg_do_encoding_conversion(reinterpret_cast<unsigned char *>(cstr),
- strlen(cstr), db_encoding, PG_UTF8);
-}
-
-static char *
-plv8_tolocal(char *cstr)
-{
- int db_encoding = GetDatabaseEncoding();
-
- if (db_encoding == PG_UTF8)
- return cstr;
-
- return (char *) pg_do_encoding_conversion(reinterpret_cast<unsigned char *>(cstr),
- strlen(cstr), PG_UTF8, db_encoding);
-}
-
-static void
-plv8_elog_exception(int errcode, v8::TryCatch *try_catch)
-{
- v8::String::Utf8Value exception(try_catch->Exception());
- const char *exception_string = ToCString(exception);
-
- elog(errcode, "%s", exception_string);
-}
-
-static const char *
-ToCString(v8::String::Utf8Value &value)
-{
- return *value ? *value : "<string conversion failed>";
-}
+/*
+ * plv8.cc : PL/v8 handler routines.
+ */
+#include "plv8.h"
+#include <new>
+#include <sstream>
+
+extern "C" {
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "utils/memutils.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(plv8_call_handler);
+PG_FUNCTION_INFO_V1(plv8_call_validator);
+
+Datum plv8_call_handler(PG_FUNCTION_ARGS) throw();
+Datum plv8_call_validator(PG_FUNCTION_ARGS) throw();
+} // extern "C"
+
+using namespace v8;
+
+typedef struct plv8_proc
+{
+ Oid fn_oid;
+
+ Persistent<Function> function;
+ char proname[NAMEDATALEN];
+ char *prosrc;
+
+ TransactionId fn_xmin;
+ ItemPointerData fn_tid;
+
+ int nargs;
+ plv8_type rettype;
+ plv8_type argtypes[FUNC_MAX_ARGS];
+} plv8_proc;
+
+static HTAB *plv8_proc_hash = NULL;
+
+/*
+ * loser_case_functions are postgres-like C functions.
+ * They could raise errors with elog/ereport(ERROR).
+ */
+static plv8_proc *plv8_get_proc_cache(Oid fn_oid, bool validate, char ***argnames) throw();
+static void plv8_fill_type(plv8_type *type, Oid typid) throw();
+
+/*
+ * CamelCaseFunctions are C++ functions.
+ * They could raise errors with C++ throw statements, or never throw exceptions.
+ */
+static plv8_proc *Compile(Oid fn_oid, bool validate, bool is_trigger);
+static Persistent<Function> CreateFunction(const char *proname, int proarglen,
+ const char *proargs[], const char *prosrc, bool is_trigger);
+static Datum CallFunction(PG_FUNCTION_ARGS, plv8_proc *proc,
+ Handle<Context> global_context);
+static Datum CallTrigger(PG_FUNCTION_ARGS, plv8_proc *proc,
+ Handle<Context> global_context);
+static Handle<v8::Value> Print(const Arguments& args) throw();
+static Handle<v8::Value> PrintInternal(const Arguments& args);
+static Handle<v8::Value> ExecuteSql(const Arguments& args) throw();
+static Handle<v8::Value> ExecuteSqlInternal(const Arguments& args);
+static Handle<Context> GetGlobalContext() throw();
+
+
+Datum
+plv8_call_handler(PG_FUNCTION_ARGS) throw()
+{
+ Oid fn_oid = fcinfo->flinfo->fn_oid;
+ Handle<Context> global_context = GetGlobalContext();
+ bool is_trigger = CALLED_AS_TRIGGER(fcinfo);
+
+ try
+ {
+ if (!fcinfo->flinfo->fn_extra)
+ fcinfo->flinfo->fn_extra = Compile(fn_oid, false, is_trigger);
+
+ HandleScope handle_scope;
+ Context::Scope context_scope(global_context);
+ plv8_proc *proc = (plv8_proc *) fcinfo->flinfo->fn_extra;
+
+ if (is_trigger)
+ return CallTrigger(fcinfo, proc, global_context);
+ else
+ return CallFunction(fcinfo, proc, global_context);
+ }
+ catch (js_error& e) { e.rethrow(); }
+ catch (pg_error& e) { e.rethrow(); }
+
+ return (Datum) 0; // keep compiler quiet
+}
+
+static Datum
+CallFunction(PG_FUNCTION_ARGS, plv8_proc *proc, Handle<Context> global_context)
+{
+ Handle<v8::Value> args[FUNC_MAX_ARGS];
+ plv8_type *rettype = &proc->rettype;
+
+ TryCatch try_catch;
+
+ for (int i = 0; i < proc->nargs; i++)
+ args[i] = ToValue(fcinfo->arg[i], fcinfo->argnull[i], &proc->argtypes[i]);
+ Handle<v8::Value> result =
+ proc->function->Call(global_context->Global(), proc->nargs, args);
+ if (result.IsEmpty())
+ throw js_error(try_catch);
+ return ToDatum(result, &fcinfo->isnull, rettype);
+}
+
+static Datum
+CallTrigger(PG_FUNCTION_ARGS, plv8_proc *proc, Handle<Context> global_context)
+{
+ // trigger arguments are:
+ // 0: NEW
+ // 1: OLD
+ // 2: TG_NAME
+ // 3: TG_WHEN
+ // 4: TG_LEVEL
+ // 5: TG_OP
+ // 6: TG_RELID
+ // 7: TG_TABLE_NAME
+ // 8: TG_TABLE_SCHEMA
+ // 9: TG_ARGV
+ TriggerData *trig = (TriggerData *) fcinfo->context;
+ Relation rel = trig->tg_relation;
+ TriggerEvent event = trig->tg_event;
+ Handle<v8::Value> args[10];
+ Datum result = (Datum) 0;
+
+ TryCatch try_catch;
+
+ if (TRIGGER_FIRED_FOR_ROW(event))
+ {
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ Converter conv(tupdesc);
+
+ // TODO: cache Converter into fn_extra.
+
+ if (TRIGGER_FIRED_BY_INSERT(event))
+ {
+ result = PointerGetDatum(trig->tg_trigtuple);
+ // NEW
+ args[0] = conv.ToValue(trig->tg_trigtuple);
+ // OLD
+ args[1] = Undefined();
+ }
+ else if (TRIGGER_FIRED_BY_DELETE(event))
+ {
+ result = PointerGetDatum(trig->tg_trigtuple);
+ // NEW
+ args[0] = Undefined();
+ // OLD
+ args[1] = conv.ToValue(trig->tg_trigtuple);
+ }
+ else if (TRIGGER_FIRED_BY_UPDATE(event))
+ {
+ result = PointerGetDatum(trig->tg_newtuple);
+ // NEW
+ args[0] = conv.ToValue(trig->tg_newtuple);
+ // OLD
+ args[1] = conv.ToValue(trig->tg_trigtuple);
+ }
+ }
+ else
+ {
+ args[0] = args[1] = Undefined();
+ }
+
+ // 2: TG_NAME
+ args[2] = ToString(trig->tg_trigger->tgname);
+
+ // 3: TG_WHEN
+ if (TRIGGER_FIRED_BEFORE(event))
+ args[3] = String::New("BEFORE");
+ else
+ args[3] = String::New("AFTER");
+
+ // 4: TG_LEVEL
+ if (TRIGGER_FIRED_FOR_ROW(event))
+ args[4] = String::New("ROW");
+ else
+ args[4] = String::New("STATEMENT");
+
+ // 5: TG_OP
+ if (TRIGGER_FIRED_BY_INSERT(event))
+ args[5] = String::New("INSERT");
+ else if (TRIGGER_FIRED_BY_DELETE(event))
+ args[5] = String::New("DELETE");
+ else if (TRIGGER_FIRED_BY_UPDATE(event))
+ args[5] = String::New("UPDATE");
+ else if (TRIGGER_FIRED_BY_TRUNCATE(event))
+ args[5] = String::New("TRUNCATE");
+ else
+ args[5] = String::New("?");
+
+ // 6: TG_RELID
+ args[6] = Uint32::New(RelationGetRelid(rel));
+
+ // 7: TG_TABLE_NAME
+ args[7] = ToString(RelationGetRelationName(rel));
+
+ // 8: TG_TABLE_SCHEMA
+ args[8] = ToString(get_namespace_name(RelationGetNamespace(rel)));
+
+ // 9: TG_ARGV
+ Handle<Array> tgargs = Array::New(trig->tg_trigger->tgnargs);
+ for (int i = 0; i < trig->tg_trigger->tgnargs; i++)
+ tgargs->Set(i, ToString(trig->tg_trigger->tgargs[i]));
+ args[9] = tgargs;
+
+ Handle<v8::Value> newtup =
+ proc->function->Call(global_context->Global(), lengthof(args), args);
+ if (newtup.IsEmpty())
+ throw js_error(try_catch);
+
+ // TODO: replace NEW tuple if modified.
+
+ return result;
+}
+
+Datum
+plv8_call_validator(PG_FUNCTION_ARGS) throw()
+{
+ Oid fn_oid = PG_GETARG_OID(0);
+ HeapTuple tuple;
+ Form_pg_proc proc;
+ char functyptype;
+ bool is_trigger = false;
+ js_error error;
+
+ /* Get the new function's pg_proc entry */
+ tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(fn_oid), 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", fn_oid);
+ proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+ functyptype = get_typtype(proc->prorettype);
+
+ /* Disallow pseudotype result */
+ /* except for TRIGGER, RECORD, or VOID */
+ if (functyptype == TYPTYPE_PSEUDO)
+ {
+ /* we assume OPAQUE with no arguments means a trigger */
+ if (proc->prorettype == TRIGGEROID ||
+ (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+ is_trigger = true;
+ else if (proc->prorettype != RECORDOID &&
+ proc->prorettype != VOIDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/v8 functions cannot return type %s",
+ format_type_be(proc->prorettype))));
+ }
+
+ ReleaseSysCache(tuple);
+
+ try
+ {
+ (void) Compile(fn_oid, true, is_trigger);
+ /* the result of a validator is ignored */
+ PG_RETURN_VOID();
+ }
+ catch (js_error& e) { e.rethrow(); }
+ catch (pg_error& e) { e.rethrow(); }
+
+ return (Datum) 0; // keep compiler quiet
+}
+
+static plv8_proc *
+plv8_get_proc_cache(Oid fn_oid, bool validate, char ***argnames) throw()
+{
+ HeapTuple procTup;
+ Form_pg_proc procStruct;
+ plv8_proc *proc;
+ bool found;
+ bool isnull;
+ Datum prosrc;
+ Oid *argtypes;
+ char *argmodes;
+ Oid rettype;
+ MemoryContext oldcontext;
+
+ if (plv8_proc_hash == NULL)
+ {
+ HASHCTL hash_ctl = { 0 };
+
+ hash_ctl.keysize = sizeof(Oid);
+ hash_ctl.entrysize = sizeof(plv8_proc);
+ hash_ctl.hash = oid_hash;
+ plv8_proc_hash = hash_create("PLv8 Procedures", 32,
+ &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+ }
+
+ procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(fn_oid), 0, 0, 0);
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function %u", fn_oid);
+
+ proc = (plv8_proc *) hash_search(plv8_proc_hash, &fn_oid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ bool uptodate;
+
+ uptodate = (!proc->function.IsEmpty() &&
+ proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
+ ItemPointerEquals(&proc->fn_tid, &procTup->t_self));
+
+ if (!uptodate)
+ {
+ if (proc->prosrc)
+ {
+ pfree(proc->prosrc);
+ proc->prosrc = NULL;
+ }
+ proc->function.Dispose();
+ }
+ else
+ {
+ ReleaseSysCache(procTup);
+ return proc;
+ }
+ }
+ else
+ {
+ new(&proc->function) Persistent<Function>();
+ proc->prosrc = NULL;
+ }
+
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+ prosrc = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc");
+
+ // functyptype = get_typtype(procStruct->prorettype);
+ rettype = procStruct->prorettype;
+
+ strlcpy(proc->proname, NameStr(procStruct->proname), NAMEDATALEN);
+ proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
+ proc->fn_tid = procTup->t_self;
+ proc->nargs = get_func_arg_info(procTup, &argtypes, argnames, &argmodes);
+
+ if (validate)
+ {
+ /* Disallow pseudotypes in arguments (either IN or OUT) */
+ for (int i = 0; i < proc->nargs; i++)
+ {
+ if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/v8 functions cannot accept type %s",
+ format_type_be(argtypes[i]))));
+ }
+ }
+
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ proc->prosrc = TextDatumGetCString(prosrc);
+
+ ReleaseSysCache(procTup);
+
+ for (int i = 0; i < proc->nargs; i++)
+ {
+ Oid argtype = argtypes[i];
+ char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+
+ switch (argmode)
+ {
+ case PROARGMODE_IN:
+ case PROARGMODE_VARIADIC:
+ break;
+ default:
+ elog(ERROR, "OUT parameters are not supported");
+ }
+
+ plv8_fill_type(&proc->argtypes[i], argtype);
+ }
+
+ plv8_fill_type(&proc->rettype, rettype);
+
+ /* restore */
+ MemoryContextSwitchTo(oldcontext);
+
+ return proc;
+}
+
+static plv8_proc *
+Compile(Oid fn_oid, bool validate, bool is_trigger)
+{
+ plv8_proc *proc;
+ char **argnames;
+
+ PG_TRY();
+ {
+ proc = plv8_get_proc_cache(fn_oid, validate, &argnames);
+ }
+ PG_CATCH();
+ {
+ throw pg_error();
+ }
+ PG_END_TRY();
+
+ if (proc->function.IsEmpty())
+ proc->function = CreateFunction(
+ proc->proname,
+ proc->nargs,
+ (const char **) argnames,
+ proc->prosrc,
+ is_trigger);
+
+ return proc;
+}
+
+static Persistent<Function>
+CreateFunction(
+ const char *proname,
+ int proarglen,
+ const char *proargs[],
+ const char *prosrc,
+ bool is_trigger)
+{
+ HandleScope handle_scope;
+ StringInfoData src;
+ Handle<Context> global_context = GetGlobalContext();
+
+ initStringInfo(&src);
+
+ /*
+ * function _(<arg1, ...>){
+ * <prosrc>
+ * }
+ * _
+ */
+ appendStringInfo(&src, "function _(");
+ if (is_trigger)
+ {
+ if (proarglen != 0)
+ throw js_error("trigger function cannot have arguments");
+ // trigger function has special arguments.
+ appendStringInfo(&src,
+ "NEW, OLD, TG_NAME, TG_WHEN, TG_LEVEL, TG_OP, "
+ "TG_RELID, TG_TABLE_NAME, TG_TABLE_SCHEMA, TG_ARGV");
+ }
+ else
+ {
+ for (int i = 0; i < proarglen; i++)
+ {
+ if (i > 0)
+ appendStringInfoChar(&src, ',');
+ if (proargs && proargs[i])
+ appendStringInfoString(&src, proargs[i]);
+ else
+ appendStringInfo(&src, "$%d", i + 1); // unnamed argument to $N
+ }
+ }
+ appendStringInfo(&src, "){\n%s\n};\n_", prosrc);
+
+ Handle<v8::Value> name = ToString(proname);
+ Handle<String> source = ToString(src.data, src.len);
+ pfree(src.data);
+
+ Context::Scope context_scope(global_context);
+ TryCatch try_catch;
+ Handle<Script> script = Script::Compile(source, name);
+
+ if (script.IsEmpty())
+ throw js_error(try_catch);
+
+ Handle<v8::Value> result = script->Run();
+ if (result.IsEmpty())
+ throw js_error(try_catch);
+
+ Handle<Function> fn = Handle<Function>::Cast(result);
+ if (fn.IsEmpty())
+ throw js_error(try_catch);
+
+ return Persistent<Function>::New(fn);
+}
+
+static void
+plv8_fill_type(plv8_type *type, Oid typid) throw()
+{
+ bool ispreferred;
+
+ type->typid = typid;
+ get_type_category_preferred(typid, &type->category, &ispreferred);
+ get_typlenbyvalalign(typid, &type->len, &type->byval, &type->align);
+
+ if (type->category == TYPCATEGORY_ARRAY)
+ {
+ Oid elemid = get_element_type(typid);
+
+ if (elemid == InvalidOid)
+ ereport(ERROR,
+ (errmsg("cannot determine element type of array: %u", typid)));
+
+ type->typid = elemid;
+ get_typlenbyvalalign(type->typid, &type->len, &type->byval, &type->align);
+ }
+}
+
+/*
+ * v8 is not exception-safe! We cannot throw C++ exceptions over v8 functions.
+ * So, we catch C++ exceptions and convert them to JavaScript ones.
+ */
+static Handle<v8::Value>
+SafeCall(InvocationCallback fn, const Arguments& args) throw()
+{
+ HandleScope handle_scope;
+ MemoryContext ctx = CurrentMemoryContext;
+
+ try
+ {
+ return fn(args);
+ }
+ catch (pg_error& e)
+ {
+ MemoryContextSwitchTo(ctx);
+ ErrorData *edata = CopyErrorData();
+ Handle<String> message = ToString(edata->message);
+ // XXX: add other fields? (detail, hint, context, internalquery...)
+ FlushErrorState();
+ FreeErrorData(edata);
+
+ return ThrowException(message);
+ }
+}
+
+static Handle<v8::Value>
+Print(const Arguments& args) throw()
+{
+ return SafeCall(PrintInternal, args);
+}
+
+static Handle<v8::Value>
+PrintInternal(const Arguments& args)
+{
+ if (args.Length() < 2)
+ return ThrowException(String::New("usage: print(elevel, ...)"));
+
+ int elevel = args[0]->Int32Value();
+ switch (elevel)
+ {
+ case DEBUG5:
+ case DEBUG4:
+ case DEBUG3:
+ case DEBUG2:
+ case DEBUG1:
+ case LOG:
+ case INFO:
+ case NOTICE:
+ case WARNING:
+ break;
+ default:
+ return ThrowException(String::New("invalid error level for print()"));
+ }
+
+ if (args.Length() == 2)
+ {
+ // fast path for single argument
+ elog(elevel, "%s", CString(args[1]).str(""));
+ }
+ else
+ {
+ std::ostringstream stream;
+
+ for (int i = 1; i < args.Length(); i++)
+ {
+ if (i > 1)
+ stream << ' ';
+ stream << CString(args[i]);
+ }
+ elog(elevel, "%s", stream.str().c_str());
+ }
+
+ return Undefined();
+}
+
+static Handle<v8::Value>
+ExecuteSql(const Arguments& args) throw()
+{
+ return SafeCall(ExecuteSqlInternal, args);
+}
+
+/*
+ * executeSql(string sql) returns array<json> for query, or integer for command.
+ *
+ * TODO: support query arguments with SPI_execute_with_args().
+ */
+static Handle<v8::Value>
+ExecuteSqlInternal(const Arguments& args)
+{
+ if (args.Length() != 1)
+ return ThrowException(String::New("usage: executeSql(sql)"));
+
+ SPI_connect();
+
+ CString sql(args[0]);
+ Handle<v8::Value> result;
+ int status;
+
+ PG_TRY();
+ {
+ status = SPI_exec(sql, 0);
+ }
+ PG_CATCH();
+ {
+ throw pg_error();
+ }
+ PG_END_TRY();
+
+ switch (status)
+ {
+ case SPI_OK_SELECT:
+ case SPI_OK_INSERT_RETURNING:
+ case SPI_OK_DELETE_RETURNING:
+ case SPI_OK_UPDATE_RETURNING:
+ {
+ int nrows = SPI_processed;
+ Converter conv(SPI_tuptable->tupdesc);
+ Handle<Array> rows = Array::New(nrows);
+
+ for (uint32 r = 0; r < nrows; r++)
+ rows->Set(r, conv.ToValue(SPI_tuptable->vals[r]));
+
+ result = rows;
+ break;
+ }
+ default:
+ result = Int32::New(SPI_processed);
+ break;
+ }
+
+ SPI_finish();
+
+ return result;
+}
+
+static Handle<Context>
+GetGlobalContext() throw()
+{
+ static Persistent<Context> global_context;
+
+ if (global_context.IsEmpty())
+ {
+ HandleScope handle_scope;
+ Handle<ObjectTemplate> global = ObjectTemplate::New();
+
+ // built-in function print(elevel, ...)
+ global->Set(String::NewSymbol("print"),
+ FunctionTemplate::New(Print));
+ global->Set(String::NewSymbol("DEBUG5"), Int32::New(DEBUG5));
+ global->Set(String::NewSymbol("DEBUG4"), Int32::New(DEBUG4));
+ global->Set(String::NewSymbol("DEBUG3"), Int32::New(DEBUG3));
+ global->Set(String::NewSymbol("DEBUG2"), Int32::New(DEBUG2));
+ global->Set(String::NewSymbol("DEBUG1"), Int32::New(DEBUG1));
+ global->Set(String::NewSymbol("DEBUG"), Int32::New(DEBUG5));
+ global->Set(String::NewSymbol("LOG"), Int32::New(LOG));
+ global->Set(String::NewSymbol("INFO"), Int32::New(INFO));
+ global->Set(String::NewSymbol("NOTICE"), Int32::New(NOTICE));
+ global->Set(String::NewSymbol("WARNING"), Int32::New(WARNING));
+ // ERROR or higher severity levels are not allowed. Use "throw" instead.
+
+ // built-in function executeSql(sql)
+ global->Set(String::NewSymbol("executeSql"),
+ FunctionTemplate::New(ExecuteSql));
+
+ global_context = Context::New(NULL, global);
+ }
+
+ return global_context;
+}
+
+Converter::Converter(TupleDesc tupdesc) :
+ m_tupdesc(tupdesc),
+ m_colnames(tupdesc->natts),
+ m_coltypes(tupdesc->natts)
+{
+ for (int c = 0; c < tupdesc->natts; c++)
+ {
+ m_colnames[c] = ToString(SPI_fname(m_tupdesc, c + 1));
+ PG_TRY();
+ {
+ plv8_fill_type(&m_coltypes[c], m_tupdesc->attrs[c]->atttypid);
+ }
+ PG_CATCH();
+ {
+ throw pg_error();
+ }
+ PG_END_TRY();
+ }
+}
+
+// TODO: use prototype instead of per tuple fields to reduce
+// memory consumption.
+Handle<Object> Converter::ToValue(HeapTuple tuple)
+{
+ Handle<Object> obj = Object::New();
+
+ for (int c = 0; c < m_tupdesc->natts; c++)
+ {
+ Datum datum;
+ bool isnull;
+
+ datum = SPI_getbinval(tuple, m_tupdesc, c + 1, &isnull);
+ obj->Set(m_colnames[c], ::ToValue(datum, isnull, &m_coltypes[c]));
+ }
+
+ return obj;
+}
+
+js_error::js_error() throw()
+ : m_msg(NULL), m_detail(NULL)
+{
+}
+
+js_error::js_error(const char *msg) throw()
+{
+ m_msg = pstrdup(msg);
+ m_detail = NULL;
+}
+
+js_error::js_error(TryCatch &try_catch) throw()
+{
+ HandleScope handle_scope;
+ String::Utf8Value exception(try_catch.Exception());
+ Handle<Message> message = try_catch.Message();
+
+ m_msg = NULL;
+ m_detail = NULL;
+
+ try
+ {
+ m_msg = ToCStringCopy(exception);
+
+ if (!message.IsEmpty())
+ {
+ StringInfoData str;
+ CString script(message->GetScriptResourceName());
+ int lineno = message->GetLineNumber();
+ CString source(message->GetSourceLine());
+
+ /*
+ * Report lineno - 1 because "function _(...){" was added
+ * at the first line to the javascript code.
+ */
+ initStringInfo(&str);
+ appendStringInfo(&str, "%s() LINE %d: %s",
+ script.str("?"), lineno - 1, source.str("?"));
+ m_detail = str.data;
+ }
+ }
+ catch (...)
+ {
+ // nested error, keep quiet.
+ }
+}
+
+__attribute__((noreturn))
+void
+js_error::rethrow() throw()
+{
+ ereport(ERROR,
+ (m_msg ? errmsg("%s", m_msg) : 0,
+ m_detail ? errdetail("%s", m_detail) : 0));
+ exit(0); // keep compiler quiet
+}
+
+__attribute__((noreturn))
+void
+pg_error::rethrow() throw()
+{
+ PG_RE_THROW();
+ exit(0); // keep compiler quiet
+}
View
124 plv8.h
@@ -1,18 +1,106 @@
-#ifndef _PLV8_H_
-#define _PLV8_H_
-
-#ifdef __cplusplus
-extern "C"{
-#endif
-
-#include "postgres.h"
-#include "fmgr.h"
-
-Datum plv8_call_handler(PG_FUNCTION_ARGS);
-Datum plv8_call_validator(PG_FUNCTION_ARGS);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // _PLV8_H
+#include <v8.h>
+#include <vector>
+
+extern "C" {
+#include "postgres.h"
+
+#include "access/htup.h"
+#include "fmgr.h"
+#include "mb/pg_wchar.h"
+}
+
+#ifdef _MSC_VER
+#define __attribute__(what) __declspec what
+#elif !defined(__GNUC__)
+#define __attribute__(what)
+#endif
+
+/* js_error represents exceptions in JavaScript. */
+class js_error
+{
+private:
+ char *m_msg;
+ char *m_detail;
+
+public:
+ js_error() throw();
+ js_error(const char *msg) throw();
+ js_error(v8::TryCatch &try_catch) throw();
+ __attribute__((noreturn)) void rethrow() throw();
+};
+
+/*
+ * pg_error represents ERROR in postgres.
+ * Instances of the class should be thrown only in PG_CATCH block.
+ */
+class pg_error
+{
+public:
+ __attribute__((noreturn)) void rethrow() throw();
+};
+
+/*
+ * When TYPCATEGORY_ARRAY, other fields are for element types.
+ *
+ * Note that postgres doesn't support type modifiers for arguments and result types.
+ */
+typedef struct plv8_type
+{
+ Oid typid;
+ Oid ioparam;
+ int16 len;
+ bool byval;
+ char align;
+ char category;
+ FmgrInfo fn_input;
+ FmgrInfo fn_output;
+} plv8_type;
+
+/*
+ * A multibyte string in the database encoding. It works more effective
+ * when the encoding is UTF8.
+ */
+class CString
+{
+private:
+ v8::String::Utf8Value m_utf8;
+ char *m_str;
+
+public:
+ explicit CString(v8::Handle<v8::Value> value);
+ ~CString();
+ operator char* () { return m_str; }
+ operator const char* () const { return m_str; }
+ const char* str(const char *ifnull = NULL) const
+ { return m_str ? m_str : ifnull; }
+
+private:
+ CString(const CString&);
+ CString& operator = (const CString&);
+};
+
+/*
+ * Records in postgres to JSON in v8 converter.
+ */
+class Converter
+{
+private:
+ TupleDesc m_tupdesc;
+ std::vector< v8::Handle<v8::String> > m_colnames;
+ std::vector< plv8_type > m_coltypes;
+
+public:
+ Converter(TupleDesc tupdesc);
+ v8::Handle<v8::Object> ToValue(HeapTuple tuple);
+
+private:
+ Converter(const Converter&);
+ Converter& operator = (const Converter&);
+};
+
+extern Datum ToDatum(v8::Handle<v8::Value> value, bool *isnull, plv8_type *type);
+extern v8::Handle<v8::Value> ToValue(Datum datum, bool isnull, plv8_type *type);
+extern v8::Handle<v8::String> ToString(Datum value, plv8_type *type);
+extern v8::Handle<v8::String> ToString(const char *str, int len = -1, int encoding = GetDatabaseEncoding());
+extern char *ToCString(const v8::String::Utf8Value &value);
+extern char *ToCStringCopy(const v8::String::Utf8Value &value);
View
10 plv8.sql.in
@@ -0,0 +1,10 @@
+-- adjust this setting to control where the objects get created.
+SET search_path = public;
+
+BEGIN;
+
+CREATE FUNCTION plv8_call_handler() RETURNS language_handler AS 'MODULE_PATHNAME' LANGUAGE C;
+CREATE FUNCTION plv8_call_validator(oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C;
+CREATE LANGUAGE plv8 HANDLER plv8_call_handler VALIDATOR plv8_call_validator;
+
+COMMIT;
View
489 plv8_type.cc
@@ -0,0 +1,489 @@
+/*
+ * plv8.cc : Postgres from/to v8 data converters.
+ */
+#include "plv8.h"
+
+extern "C" {
+#include "catalog/pg_type.h"
+#include "utils/array.h"
+#include "utils/date.h"
+#include "utils/datetime.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/typcache.h"
+} // extern "C"
+
+//#define CHECK_INTEGER_OVERFLOW
+
+using namespace v8;
+
+static Datum ToScalarDatum(Handle<v8::Value> value, bool *isnull, plv8_type *type);
+static Datum ToArrayDatum(Handle<v8::Value> value, bool *isnull, plv8_type *type);
+static Datum ToRecordDatum(Handle<v8::Value> value, bool *isnull, plv8_type *type);
+static Handle<v8::Value> ToScalarValue(Datum datum, bool isnull, plv8_type *type);
+static Handle<v8::Value> ToArrayValue(Datum datum, bool isnull, plv8_type *type);
+static Handle<v8::Value> ToRecordValue(Datum datum, bool isnull, plv8_type *type);
+static double TimestampTzToEpoch(TimestampTz tm);
+static Datum EpochToTimestampTz(double epoch);
+static double DateToEpoch(DateADT date);
+static Datum EpochToDate(double epoch);
+
+Datum
+ToDatum(Handle<v8::Value> value, bool *isnull, plv8_type *type)
+{
+ if (type->category == TYPCATEGORY_ARRAY)
+ return ToArrayDatum(value, isnull, type);
+ else
+ return ToScalarDatum(value, isnull, type);
+}
+
+static Datum
+ToScalarDatum(Handle<v8::Value> value, bool *isnull, plv8_type *type)
+{
+ if (type->category == TYPCATEGORY_COMPOSITE)
+ return ToRecordDatum(value, isnull, type);
+
+ if (value->IsUndefined() || value->IsNull())
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+
+ *isnull = false;
+ switch (type->typid)
+ {
+ case OIDOID:
+ if (value->IsNumber())
+ return ObjectIdGetDatum(value->Uint32Value());
+ break;
+ case BOOLOID:
+ if (value->IsBoolean())
+ return BoolGetDatum(value->BooleanValue());
+ break;
+ case INT2OID:
+ if (value->IsNumber())
+#ifdef CHECK_INTEGER_OVERFLOW
+ return DirectFunctionCall1(int82,
+ Int64GetDatum(value->IntegerValue()));
+#else
+ return Int16GetDatum((int16) value->Int32Value());
+#endif
+ break;
+ case INT4OID:
+ if (value->IsNumber())
+#ifdef CHECK_INTEGER_OVERFLOW
+ return DirectFunctionCall1(int84,
+ Int64GetDatum(value->IntegerValue()));
+#else
+ return Int32GetDatum((int32) value->Int32Value());
+#endif
+ break;
+ case INT8OID:
+ if (value->IsNumber())
+ return Int64GetDatum((int64) value->IntegerValue());
+ break;
+ case FLOAT4OID:
+ if (value->IsNumber())
+ return Float4GetDatum((float4) value->NumberValue());
+ break;
+ case FLOAT8OID:
+ if (value->IsNumber())
+ return Float8GetDatum((float8) value->NumberValue());
+ break;
+ case NUMERICOID:
+ if (value->IsNumber())
+ return DirectFunctionCall1(float8_numeric,
+ Float8GetDatum((float8) value->NumberValue()));
+ break;
+ case DATEOID:
+ if (value->IsDate())
+ return EpochToDate(value->NumberValue());
+ break;
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ if (value->IsDate())
+ return EpochToTimestampTz(value->NumberValue());
+ break;
+ }
+
+ /* Use lexical cast for non-numeric types. */
+ int encoding = GetDatabaseEncoding();
+ CString str(value);
+ Datum result;
+
+ PG_TRY();
+ {
+ if (type->fn_input.fn_addr == NULL)
+ {
+ Oid input_func;
+
+ getTypeInputInfo(type->typid, &input_func, &type->ioparam);
+ fmgr_info(input_func, &type->fn_input);
+ }
+ result = InputFunctionCall(&type->fn_input, str, type->ioparam, -1);
+ }
+ PG_CATCH();
+ {
+ throw pg_error();
+ }
+ PG_END_TRY();
+
+ return result;
+}
+
+static Datum
+ToArrayDatum(Handle<v8::Value> value, bool *isnull, plv8_type *type)
+{
+ int length;
+ Datum *values;
+ bool *nulls;
+ int ndims[1];
+ int lbs[] = {1};
+ ArrayType *result;
+
+ if (value->IsUndefined() || value->IsNull())
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+
+ Handle<Array> array(Handle<Array>::Cast(value));
+ if (array.IsEmpty())
+ throw js_error("value is not an Array");
+
+ length = array->Length();
+ values = (Datum *) palloc(sizeof(Datum) * length);
+ nulls = (bool *) palloc(sizeof(bool) * length);
+ ndims[0] = length;
+ for (int i = 0; i < length; i++)
+ values[i] = ToScalarDatum(array->Get(i), &nulls[i], type);
+
+ result = construct_md_array(values, nulls, 1, ndims, lbs,
+ type->typid, type->len, type->byval, type->align);
+ pfree(values);
+ pfree(nulls);
+
+ *isnull = false;
+ return PointerGetDatum(result);
+}
+
+static Datum
+ToRecordDatum(Handle<v8::Value> value, bool *isnull, plv8_type *type)
+{
+ if (value->IsUndefined() || value->IsNull())
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+
+ throw js_error("JSON to record is not implemented yet");
+}
+
+Handle<v8::Value>
+ToValue(Datum datum, bool isnull, plv8_type *type)
+{
+ if (type->category == TYPCATEGORY_ARRAY)
+ return ToArrayValue(datum, isnull, type);
+ else
+ return ToScalarValue(datum, isnull, type);
+}
+
+static Handle<v8::Value>
+ToScalarValue(Datum datum, bool isnull, plv8_type *type)
+{
+ if (isnull)
+ return Null();
+
+ if (type->category == TYPCATEGORY_COMPOSITE)
+ return ToRecordValue(datum, isnull, type);
+
+ switch (type->typid)
+ {
+ case OIDOID:
+ return Uint32::New(DatumGetObjectId(datum));
+ case BOOLOID:
+ return Boolean::New((bool) DatumGetBool(datum));
+ case INT2OID:
+ return Int32::New(DatumGetInt16(datum));
+ case INT4OID:
+ return Int32::New(DatumGetInt32(datum));
+ case INT8OID:
+ return Number::New(DatumGetInt64(datum));
+ case FLOAT4OID:
+ return Number::New(DatumGetFloat4(datum));
+ case FLOAT8OID:
+ return Number::New(DatumGetFloat8(datum));
+ case NUMERICOID:
+ return Number::New(DatumGetFloat8(
+ DirectFunctionCall1(numeric_float8, datum)));
+ case DATEOID:
+ return Date::New(DateToEpoch(DatumGetDateADT(datum)));
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ return Date::New(TimestampTzToEpoch(DatumGetTimestampTz(datum)));
+ case TEXTOID:
+ case VARCHAROID:
+ case BPCHAROID:
+ case XMLOID:
+ {
+ void *p = PG_DETOAST_DATUM_PACKED(datum);
+ const char *str = VARDATA_ANY(p);
+ int len = VARSIZE_ANY_EXHDR(p);
+
+ Handle<String> result = ToString(str, len);
+
+ if (p != DatumGetPointer(datum))
+ pfree(p); // free if detoasted
+ return result;
+ }
+ default:
+ return ToString(datum, type);
+ }
+}
+
+static Handle<v8::Value>
+ToArrayValue(Datum datum, bool isnull, plv8_type *type)
+{
+ Datum *values;
+ bool *nulls;
+ int nelems;
+
+ if (isnull)
+ return Null();
+
+ deconstruct_array(DatumGetArrayTypeP(datum),
+ type->typid, type->len, type->byval, type->align,
+ &values, &nulls, &nelems);
+ Handle<Array> result = Array::New(nelems);
+ for (int i = 0; i < nelems; i++)
+ result->Set(i, ToScalarValue(values[i], nulls[i], type));
+
+ return result;
+}
+
+static Handle<v8::Value>
+ToRecordValue(Datum datum, bool isnull, plv8_type *type)
+{
+ if (isnull)
+ return Null();
+
+ HeapTupleHeader rec = DatumGetHeapTupleHeader(datum);
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupdesc;
+ HeapTupleData tuple;
+
+ PG_TRY();
+ {
+ /* Extract type info from the tuple itself */
+ tupType = HeapTupleHeaderGetTypeId(rec);
+ tupTypmod = HeapTupleHeaderGetTypMod(rec);
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+ }
+ PG_CATCH();
+ {
+ throw pg_error();
+ }
+ PG_END_TRY();
+
+ Converter conv(tupdesc);
+
+ /* Build a temporary HeapTuple control structure */
+ tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
+ ItemPointerSetInvalid(&(tuple.t_self));
+ tuple.t_tableOid = InvalidOid;
+ tuple.t_data = rec;
+
+ Handle<v8::Value> result = conv.ToValue(&tuple);
+
+ ReleaseTupleDesc(tupdesc);
+
+ return result;
+}
+
+Handle<String>
+ToString(Datum value, plv8_type *type)
+{
+ int encoding = GetDatabaseEncoding();
+ char *str;
+
+ PG_TRY();
+ {
+ if (type->fn_output.fn_addr == NULL)
+ {
+ Oid output_func;
+ bool isvarlen;
+
+ getTypeOutputInfo(type->typid, &output_func, &isvarlen);
+ fmgr_info(output_func, &type->fn_output);
+ }
+ str = OutputFunctionCall(&type->fn_output, value);